前言
分水岭算法主要用于图像的分割!
这个算法需要输入一个灰度图,在接下来的洪水漫堤过程中,相邻的积水盆地之间的分水岭便慢慢构建起来。一般情况下,这会引起过分割,尤其是具有噪声的图像。
图像必须要预处理,以消除噪声;分割结果必须要基于一些根据进行区域合并,以减小过分割造成的影响!
技术
假设我们要将下面这幅图像进行分割,需要几步?分三步,第一步,冰箱门打开,第二步。。。打住,- -| 进入正题,我们要用OpenCV来实现,不就一步么?调用那个watershed函数不就行了?实际上,可没那么简单!且听我细细道来 ~-~!
OpenCV实现了一个基于标记图层的分水岭算法,所谓基于标记图层,就是不用手动选择种子点了,直接输入一个包含标记点(种子点)的图像即可。函数原型如下:
C++: void watershed(InputArray image, InputOutputArray markers)
其中,image代表输入图像(必须是8-bit3通道图像,也就是说是灰度图像),这也就是说,要对一个彩色图像进行分割,就要先将image转换为灰度图,幸运的是这可以通过cvtColor()(原型为C++: void cvtColor( InputArray src,OutputArray dst, int code, int dstCn=0 ),code代表转换类型,具体的所有代码在这里可以找到)做到;markers是输入的标记图层(包含标记点的图层,这些标记点就是进行洪水的入口,一般是区域极小值点)。
利用cvtColor将原图转换为灰度图如下:这就是说要利用分水岭算法对一幅图像进行分割,必须提供其对应的标记图层。那么问题来了?挖掘机技术哪家强?。。。额,串词了 - -| 那么markers图层从哪里来呢?不用怕,需要的markers图层可以通过findContours()函数勾勒出来,findContours()函数的原型如下:
C++: void findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point()) C++: void findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())
C++: void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
利用以上思路转换为二值图后的结果如下:
findContours结果如下:
OK,将markers文件传入watershed函数的参数,最终结果如下:
代码我给附上:
/** * 定义一个使用分水岭算法的辅助类 */ class WatershedSegmenter{ private: cv::Mat markers; public: void setMarkers(cv::Mat& markerImage) { markerImage.convertTo(markers, CV_32S); } cv::Mat process(cv::Mat &image) { cv::watershed(image, markers); markers.convertTo(markers,CV_8U); return markers; } };
/** * 接受一个图像参数,显示出分水岭算法分割的结果 */ void watershedSegment (Mat img){ Mat gray(img.rows, img.cols,CV_8UC1); cvtColor(img, gray, CV_BGR2GRAY); //转换为8-bit,3通道的灰度图 Mat binary = Mat::zeros(gray.rows, gray.cols, CV_8UC1); adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 5, 10); //将灰度图转换为二值图 Mat markers = Mat::zeros(gray.rows, gray.cols, CV_8UC1); //使用findContour()函数找出图像的轮廓 vector<vector<Point> > contours; vector<Vec4i> hierarchy; findContours(binary, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); //将contours结果放入到markers中,便于访问 int idx = 0; for( ; idx >= 0; idx = hierarchy[idx][0]){ Scalar color(rand()&255, rand()&255, rand()&255); drawContours(markers, contours, idx, color, CV_FILLED, 8, hierarchy); } //调用分水岭算法分割图像 WatershedSegmenter segmenter; segmenter.setMarkers(markers); cv::Mat result = segmenter.process(img); //显示分割结果 namedWindow("segmentation_result", 0); imshow("segmentation_result", result); }
总结
Watershed是一个很好的算法,效率也很高。在图像分割中,应用的比较广泛,据说很多商业软件都有实现!
但是其缺点也很明显——过分割现象。怎么办呢?可以有两种思路,从源头或者过后收拾残局:从源头?就是在分割过程中控制分割行为,这个好复杂的说!要修改算法,以兄弟我现在的水平,可是力不从心,哈哈;那么我们可以采取第二种思路,将过分割的地物对象进行合并分类,听起来好像很有道理的样子哦,我们下次讨论一下哈。