分水岭算法(理论+opencv实现)
分水岭算法理论
从意思上就知道通过用水来进行分类,学术上说什么基于拓扑结构的形态学。。。其实就是根据把图像比作一副地貌,然后通过最低点和最高点去分类!
原始的分水岭:
就是上面说的方式,接下来用一幅图进行解释---->>>
把图像用一维坐标表示,二维和三维不好画,必须用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图像处理编程实例》
代码参考贾老师视频,原理早就看了毛星云的书本,但是当时一知半解,现在从头看一下子就懂了。
-------------------------------------------
个性签名:衣带渐宽终不悔,为伊消得人憔悴!
如果觉得这篇文章对你有小小的帮助的话,记得关注再下的公众号,同时在右下角点个“推荐”哦,博主在此感谢!