分水嶺算法理論
從意思上就知道通過用水來進行分類,學術上說什么基於拓撲結構的形態學。。。其實就是根據把圖像比作一副地貌,然后通過最低點和最高點去分類!
原始的分水嶺:
就是上面說的方式,接下來用一幅圖進行解釋---->>>
把圖像用一維坐標表示,二維和三維不好畫,必須用matlab了,我不會用,意思可以表述到位
- 第一步:找到圖像的局部最低點,這個方法很多了,可以用一個內核去找,也可以一個一個比較,實現起來不難。
- 第二步:從最低點開始注水,水開始網上滿(圖像的說法就是梯度法),其中那些最低點已經被標記,不會被淹沒,那些中間點是被淹沒的。
- 第三步:找到局部最高點,就是圖中3位置對應的兩個點。
- 第四步:這樣基於局部最小值,和找到的局部最大值,就可以分割圖像了。
分類圖
模擬結果圖
是不是感覺上面的方法很好,也很簡單?接着看下面的圖:
利用上面的步驟,第一步找到了三個點,然后第二步開始漫水,這三個點都被記錄下來了,又找到兩個局部最大值。
這是我們想要的嗎?
回答是否定的!其中中間那個最小值我們不需要,因為只是一個很少並且很小的噪點而已,我們不需要圖像分割的那么細致。
缺陷顯露出來了吧?沒關系,下面我們的opencv把這個問題解決了。
模擬分類圖
模擬結果圖
opencv改進的分水嶺算法:
針對上面出現的問題,我們想到的是能不能給這種小細節一個標記,讓它不屬於我們找的最小的點呢?
opencv對其改進就是使用了人工標記的方法,我們標記一些點,基於這些點去引導分水嶺算法的進行,效果很好!
比如我們對上面的圖像標記了兩個三角形,第一步我們找到三個局部最小點,第二步淹沒的時候三個點都被淹沒了,然而中間那個沒被標記,那就淹死了(沒有救生圈),其余兩個點保留,這樣就可以達到我們的想要的結果了。
注釋:這里的標記是用不同的標號進行的,我為了方便使用了同樣的三角形了。因為標記用來分類,所以不同的標記打上不同的標號!這在下面opencv程序中體現了。。。
模擬分類圖
模擬結果圖
注釋:具體的實現沒有完成,感覺原理懂了會使用了這樣就可以了,當你需要深入的時候再去研究實現的算法,當你淺淺的使用懂了原理應該會改一點,面試過了完全可以啊!哈哈哈~~
opencv實現:
1 #include <opencv2/opencv.hpp> 2 #include <iostream> 3 4 using namespace cv; 5 using namespace std; 6 7 void waterSegment(InputArray& _src, OutputArray& _dst, int& noOfSegment); 8 9 int main(int argc, char** argv) { 10 11 Mat inputImage = imread("coins.jpg"); 12 assert(!inputImage.data); 13 Mat graImage, outputImage; 14 int offSegment; 15 waterSegment(inputImage, outputImage, offSegment); 16 17 waitKey(0); 18 return 0; 19 } 20 21 void waterSegment(InputArray& _src,OutputArray& _dst,int& noOfSegment) 22 { 23 Mat src = _src.getMat();//dst = _dst.getMat(); 24 Mat grayImage; 25 cvtColor(src, grayImage,CV_BGR2GRAY); 26 threshold(grayImage, grayImage, 0, 255, THRESH_BINARY | THRESH_OTSU); 27 Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9), Point(-1, -1)); 28 morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel); 29 distanceTransform(grayImage, grayImage, DIST_L2, DIST_MASK_3, 5); 30 normalize(grayImage, grayImage,0,1, NORM_MINMAX); 31 grayImage.convertTo(grayImage, CV_8UC1); 32 threshold(grayImage, grayImage,0,255, THRESH_BINARY | THRESH_OTSU); 33 morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel); 34 vector<vector<Point>> contours; 35 vector<Vec4i> hierarchy; 36 Mat showImage = Mat::zeros(grayImage.size(), CV_32SC1); 37 findContours(grayImage, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1)); 38 for (size_t i = 0; i < contours.size(); i++) 39 { 40 //這里static_cast<int>(i+1)是為了分水嶺的標記不同,區域1、2、3。。。。這樣才能分割 41 drawContours(showImage, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i+1)), 2); 42 } 43 Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); 44 morphologyEx(src, src, MORPH_ERODE, k); 45 watershed(src, showImage); 46 47 //隨機分配顏色 48 vector<Vec3b> colors; 49 for (size_t i = 0; i < contours.size(); i++) { 50 int r = theRNG().uniform(0, 255); 51 int g = theRNG().uniform(0, 255); 52 int b = theRNG().uniform(0, 255); 53 colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r)); 54 } 55 56 // 顯示 57 Mat dst = Mat::zeros(showImage.size(), CV_8UC3); 58 int index = 0; 59 for (int row = 0; row < showImage.rows; row++) { 60 for (int col = 0; col < showImage.cols; col++) { 61 index = showImage.at<int>(row, col); 62 if (index > 0 && index <= contours.size()) { 63 dst.at<Vec3b>(row, col) = colors[index - 1]; 64 } 65 else if (index == -1) 66 { 67 dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255); 68 } 69 else { 70 dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0); 71 } 72 } 73 } 74 }
分水嶺合並代碼:
1 void segMerge(Mat& image, Mat& segments, int& numSeg) 2 { 3 vector<Mat> samples; 4 int newNumSeg = numSeg; 5 //初始化變量長度的Vector 6 for (size_t i = 0; i < newNumSeg; i++) 7 { 8 Mat sample; 9 samples.push_back(sample); 10 } 11 for (size_t i = 0; i < segments.rows; i++) 12 { 13 for (size_t j = 0; j < segments.cols; j++) 14 { 15 int index = segments.at<uchar>(i, j); 16 if (index >= 0 && index <= newNumSeg)//把同一個區域的點合並到一個Mat中 17 { 18 if (!samples[index].data)//數據為空不能合並,否則報錯 19 { 20 samples[index] = image(Rect(j, i, 1, 1)); 21 } 22 else//按行合並 23 { 24 vconcat(samples[index], image(Rect(j, i, 2, 1)), samples[index]); 25 } 26 } 27 //if (index >= 0 && index <= newNumSeg) 28 // samples[index].push_back(image(Rect(j, i, 1, 1))); 29 } 30 } 31 vector<Mat> hist_bases; 32 Mat hsv_base; 33 int h_bins = 35; 34 int s_bins = 30; 35 int histSize[2] = { h_bins , s_bins }; 36 float h_range[2] = { 0,256 }; 37 float s_range[2] = { 0,180 }; 38 const float* range[2] = { h_range,s_range }; 39 int channels[2] = { 0,1 }; 40 Mat hist_base; 41 for (size_t i = 1; i < numSeg; i++) 42 { 43 if (samples[i].dims > 0) 44 { 45 cvtColor(samples[i], hsv_base, CV_BGR2HSV); 46 calcHist(&hsv_base, 1, channels, Mat(), hist_base, 2, histSize, range); 47 normalize(hist_base, hist_base, 0, 1, NORM_MINMAX); 48 hist_bases.push_back(hist_base); 49 } 50 else 51 { 52 hist_bases.push_back(Mat()); 53 } 54 } 55 double similarity = 0; 56 vector<bool> merged;//是否合並的標志位 57 for (size_t i = 0; i < hist_bases.size(); i++) 58 { 59 for (size_t j = i+1; j < hist_bases.size(); j++) 60 { 61 if (!merged[j])//未合並的區域進行相似性判斷 62 { 63 if (hist_bases[i].dims > 0 && hist_bases[j].dims > 0)//這里維數判斷沒必要,直接用個data就可以了 64 { 65 similarity = compareHist(hist_bases[i], hist_bases[j], HISTCMP_BHATTACHARYYA); 66 if (similarity > 0.8) 67 { 68 merged[j] = true;//被合並的區域標志位true 69 if (i != j)//這里沒必要,i不可能等於j 70 { 71 newNumSeg --;//分割部分減少 72 for (size_t p = 0; p < segments.rows; p++) 73 { 74 for (size_t k = 0; k < segments.cols; k++) 75 { 76 int index = segments.at<uchar>(p, k); 77 if (index == j) segments.at<uchar>(p, k) = i; 78 } 79 } 80 } 81 } 82 } 83 } 84 } 85 } 86 numSeg = newNumSeg;//返回合並之后的區域數量 87 }
參考:
http://blog.csdn.net/iracer/article/details/49225823
http://www.cnblogs.com/mikewolf2002/p/3304118.html
http://lib.csdn.net/article/opencv/22776
《opencv圖像處理編程實例》
代碼參考賈老師視頻,原理早就看了毛星雲的書本,但是當時一知半解,現在從頭看一下子就懂了。