// 注:本內容爲做者原創,禁止在其餘網站複述內容以及用於商業盈利,如需引用,請標明出處:https://www.cnblogs.com/lv-anchoret/
html
今天咱們來介紹用C++算法如何來實現圖像分割算法中的區域生長算法ios
區域生長的簡介算法
咱們解決的是對一整張圖像全部內容進行區域生長分類,固然,若是是對圖像中的某一類型進行區域生長可能更容易一些數據結構
我的理解函數
區域生長算法須要肯定一個閾值,這個值表明同一類圖像信息灰度值的差值,好比,我要一我的臉圖(假設眼睛是藍色的),頭髮是黑色的可是不一樣光線反射緣由或者位置不一樣,圖像中顯示的灰度顏色值有五、十、3等,雖然灰度值不同,可是他們表明的都是黑色,都是頭髮,區域生長,就是把這些類似灰度值的一類事物統一標註顯示,這也就達到了分類識別的目的,關於閾值,好比上述的頭髮,咱們須要將全部是頭髮的像素點都標註出來,那麼咱們的閾值就應該設置爲10,若是設置爲3,可能灰度值爲3和5的點會統一識別,可是10就被排除在外了。優化
算法核心就是一個bfs,設立一個種子點,向四周擴張,若是相鄰的點的灰度值相對於種子點在閾值範圍以內,那麼,咱們把它識別幷包含統一進來,擴張完畢以後,全部類似的一類圖像將被統一標註。網站
關於標註咱們還須要說一下,一開始,想起了四色定理,即用四種顏色就能夠吧整個地圖分類標註且相鄰類別顏色不一樣,後來想了想還不如把同一類型區域中的全部點都設置爲種子點灰度像素值。spa
以後想起來咱們光線追蹤一直用的ppm彩色文件格式,索性將灰度值轉成rgb彩色圖看着更爽設計
區域生長算法流程3d
1. 找種子點
2. 擴張原則
3. 終止條件
數據介紹
咱們的數據是一張灰度圖 : 見 紋理相冊夾中的第二張圖
咱們處理輸出的是一張彩色圖像,圖像格式是咱們光線追蹤的文件格式 .ppm,用咱們光線追蹤的圖片解析器(ppmviewer)就能打開(沒有的也不要緊,搜索一下,下載不超過十幾秒,超輕量級ppm解讀器)
咱們都知道,C/C++ 中讀取圖像麻煩,特別是這種.jpg複雜格式數據,因此,咱們用matlab先把圖像讀出來,輸出到一個TXT中,存儲爲二維矩陣形式,而後用C++代碼讀取TXT文件內容,存儲到一個二維數據序列中。(都有代碼在後面)
咱們側重實現算法自己,因此關於圖像的讀取和寫入咱們不作過多描述
算法介紹
算法自命名:首次左上區域生長算法
時間複雜度:O(圖像大小*種子數量*log(種子數量))
1、區域生長的三大要素確立:
(1)生長合併規則:
用戶自定義閾值,根據種子點和當前點的差值,若是在閾值以內,那麼合併,將當前點的灰度值設爲種子灰度值
(2)種子選取:
將圖像左上角第一個點做爲第一個種子,在擴張的過程當中第一個不符合生長合併規則的位置,做爲下一次生長的種子,即首次選定,後期自適應肯定。
(3)算法結束:
種子爲空
2、優缺點
該算法的優勢: 針對大型全局生長而衍生
該算法種子不會將同一個位置做爲種子屢次重複生長(時間空間複雜度優化)
某個種子在開始生長時,若是已經被包含於另外一個種子的生長區域中,那麼該種子將不會進行生長(時間複雜度優化)
該算法的缺點: 首次選定法不能用合適的灰度表明整個區域,只能是座標小的點的灰度值
生長出來的區域可能不是很完美,由於該區域是由該區域座標最小的點生長而成的。
3、灰度值轉rgb算法設計
由於要將單一的灰度值映射到r、g、b,使其表明的顏色具備獨特性
這個能夠本身設計,個人設計以下:
4、構架設計
含有一個類 —— regional
數據成員
_img:用於存儲圖像灰度矩陣
reset:用於記錄某個位置的灰度是否被重置
_delt:閾值
成員函數
readfile:讀圖像灰度矩陣文件
bfs:進行區域生長
out:輸出處理後的圖像灰度矩陣
readout:讀取處理後的圖像灰度矩陣文件
gograph:將灰度圖像轉爲rgb圖像,因爲ppmview顯示空間有限,因此將此圖劃分爲6塊(將原圖像分爲3行2列的6塊),分別輸出6個圖像
流程圖以下:
5、數據結構設計:(C++描述)
用bfs算法進行的話,必然須要隊列,可是種子們不能用隊列去存,某個種子進行生長的時候能夠用隊列記錄每個生長狀態
此法採用8領域進行生長
用隊列存儲當前種子生長過程當中的狀態點,進行bfs擴展,肯定該種子生長所造成的區域
用set容器存儲各個種子,保證了種子點惟一性,即優勢2,同時,set容器還會根據位置自動排序,因此致使了缺點2,其次,set容器的存取操做的時間複雜度均爲O(n log n)
Bfs狀態採用只包含x、y座標的一個結構體
採用C++描述的首次左上區域生長算法針對2012*1881的灰度衛星圖像矩陣數據處理時間爲:78.9s
閾值爲20
圖1
首先藍色圈所表明的部分,若是是水域的深淺,那麼這一塊仍是被劃分的很清楚的,大體分了5部分
再看下閾值爲25的圖
圖2
如咱們所預期的那樣,圖1中藍色圈的水深劃分等級更少了
其次,咱們看圖1的紅色圈表明的水體,內部中間劃分出來的區域更小了,或者說水體的邊緣區域擴張了。
再如,黑色圈表明的水域,中間的黑色圈有一塊東西,它的區域更小了,不利於捕捉細微的水內情況
若是圖1的黑色內的小黑圈部分看不太清楚,那麼能夠看一下下面這個,都是同樣的
XnView打開效果
圖3
若是把黃色部分看作是豎着的地質錘,那麼圖2顯然少了錘頭~
還有水邊一片房子彙集地,也被基本劃分爲一種色調
圖4
而針對下圖以及圖4以及原圖上方一片森林山脈,將各類處理方法進行疊加,效果可能會更好,方案以下:
有不少星星點點的噪聲,能夠選擇先去噪聲,可是,效果也不是很好
若是要將其歸入到統一的大片區域中,仍是選擇先作一個平滑處理,將其尖銳的邊緣過渡更加平滑些,再進行區域生長,加以閾值調整,星點可能會減小,可能還存在一些,可是不會那麼顯眼,和周圍環境的色差不會那麼大了
圖5
7、代碼
matlab 代碼
matlab:
function writetxt I = imread('poyanghu.jpg'); fid = fopen('image.txt','w'); [x,y] = size(I); fprintf(fid,'%d %d\n',x,y); for i = 1:x for j = 1:y fprintf(fid,'%d ',I(i,j)); end fprintf(fid,'\n'); end fclose(fid);
C++:
regional.h
//regional.h
#pragma once namespace region { constexpr int dir[8][2] { {-1,-1}, {-1, 0}, {-1, 1}, { 0,-1}, { 0, 1}, { 1,-1}, { 1, 0}, { 1, 1} }; constexpr size_t H = 2012; constexpr size_t L = 1881; class regional { public: struct pos { int _x, _y; pos(const int a, const int b) :_x(a), _y(b) { } bool operator<(const pos& p)const { if (_x == p._x) return _y < p._y; return _x < p._x; } }; public: regional(const size_t delt); void readfile(); void bfs(); void out(); void readout(); void gograph()const; private: short _img[2012 + 1][1881 + 1]; bool reset[H + 1][L + 1]; size_t _delt; }; }
regional.cpp
#include "regional.h" #include <iostream> #include <fstream> #include <queue> #include <set> using namespace std; using namespace region; regional::regional(const size_t delt) :_delt(delt) { memset(reset, false, sizeof reset); } void regional::readfile() { ifstream infile; infile.open("image.txt"); if (!infile.is_open()) cerr << "open failed" << endl; int x, y; infile >> x >> y; for (int i = 1; i <= x; ++i) for (int j = 1; j <= y; ++j) infile >> _img[i][j]; infile.close(); } void regional::bfs() { queue<pos> Qcurrent; set<pos> Qnew; Qnew.insert(pos(1, 1)); while (Qnew.size()) { Qcurrent.push(*Qnew.begin()); Qnew.erase(Qnew.begin()); if (reset[Qcurrent.front()._x][Qcurrent.front()._y])//該種子點已經訪問過 { Qcurrent.pop(); continue; } while (Qcurrent.size()) { pos seed = Qcurrent.front(); reset[seed._x][seed._y] = true; Qcurrent.pop(); for (int trans = 0; trans < 8; ++trans) { pos p(seed._x + dir[trans][0], seed._y + dir[trans][1]); if (p._x > 0 && p._x <= H && p._y > 0 && p._y <= L && !reset[p._x][p._y]) if (abs(_img[p._x][p._y] - _img[seed._x][seed._y]) < _delt) { _img[p._x][p._y] = _img[seed._x][seed._y]; reset[p._x][p._y] = true; Qcurrent.push(p); } else Qnew.insert(p); } } } } void regional::out() { ofstream outfile; outfile.open("outall.txt"); if (!outfile.is_open()) cerr << "open failed" << endl; for (int i = 1; i <= H; ++i) { for (int j = 1; j <= L; ++j) outfile << _img[i][j] << " "; outfile << endl; } outfile.close(); } void regional::readout() { ifstream infile("outall.txt"); if (!infile.is_open()) { cerr << "error open" << endl; } for (int i = 1; i <= H; ++i) for (int j = 1; j <= L; ++j) infile >> _img[i][j]; infile.close(); } void regional::gograph()const { ofstream file; auto left = [&](int cnt) { for (int i = (cnt - 1) * 700 + 1; i <= 700 * cnt; ++i) for (int j = 1; j <= 1000; ++j) file << (int)((0.2 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.5 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.7 + float(_img[i][j] % 10) / 10)*_img[i][j]) << endl; }; auto right = [&](int cnt) { for (int i = (cnt - 1) * 700 + 1; i <= 700 * cnt; ++i) for (int j = L - 1000 + 1; j <= L; ++j) file << (int)((0.2 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.5 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.7 + float(_img[i][j] % 10) / 10)*_img[i][j]) << endl; }; file.open("slip1'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(1); file.close(); file.open("slip2'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(1); file.close(); file.open("slip3'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(2); file.close(); file.open("slip4'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(2); file.close(); file.open("slip5'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(3); file.close(); file.open("slip6'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(3); file.close(); }
main.cpp
#include "regional.h" using namespace region; #include <iostream> #include <fstream> #define stds std:: int main() { regional reg(25); reg.readfile(); reg.bfs(); reg.out(); //reg.readout(); reg.gograph(); stds cout << "complished" << stds endl; return 0; }
感謝您的閱讀,生活愉快~