作者:RayChiu_Labloy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
目录
关于图像分割和几种方法:
原理:
传统分水岭算法的整个过程:
传统分水岭算法缺点:
解决方法:
改进的分水岭算法
OpenCV中分水岭算法
相关函数:
C++测试:
python实现
关于图像分割和几种方法:
图像分割是按照一定的原则,将一幅图像分为若干个互不相交的小局域的过程,它是图像处理中最为基础的研究领域之一。
目前有很多图像分割方法,其中分水岭算法是一种基于区域的图像分割算法,另外还有阈值分割法、边缘提取分割法。
原理:
传统的分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆地,而集水盆地的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。
当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。
在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。
传统分水岭算法的整个过程:
- 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离(测地线距离就是地球表面两点之间的最短路径(可执行路径)的距离)阈值。
- 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
- 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类
- 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。
效果:
传统分水岭算法缺点:
然而由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域基于梯度图像的直接分水岭算法容易导致图像的过分割,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域,输入的图像存在过多的极小区域而产生许多小的集水盆地,从而导致分割后的图像不能将图像中有意义的区域表示出来。所以必须对分割结果的相似区域进行合并。
解决方法:
- 对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。
- 不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法。
改进的分水岭算法
传统的分水岭分割算法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割。
我们所做的是给我们知道的对象赋予不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新,对象的边界值将为-1。
下面三个图分别是原图,分水岭过分割的图以及基于标记的分水岭算法得到的图:
其中标记的每个点就相当于分水岭中的注水点,从这些点开始注水使得水平面上升,但是如上图所示,图像中需要分割的区域太多了,手动标记太麻烦,我们可是使用距离转换的方法进行标记,OpenCV中就是使用的这种方法。
OpenCV中分水岭算法
在OpenCV中,我们需要给不同区域贴上不同的标签。用大于1的整数表示我们确定为前景或对象的区域,用1表示我们确定为背景或非对象的区域,最后用0表示我们无法确定的区域。然后应用分水岭算法,我们的标记图像将被更新,更新后的标记图像的边界像素值为-1。
相关函数:
void watershed( InputArray image, InputOutputArray markers );
第一个参数 image,必须是一个8bit 3通道彩色图像矩阵序列,第一个参数没什么要说的。关键是第二个参数 markers.
在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。
接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。
C++测试:
测试图片:
C++测试代码:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"#include <iostream>using namespace cv;
using namespace std;Vec3b RandomColor(int value); //生成随机颜色函数int main(int argc, char* argv[])
{Mat image = imread("2.jpg"); //载入RGB彩色图像imshow("Source Image", image);//灰度化,滤波,Canny边缘检测Mat imageGray;cvtColor(image, imageGray, COLOR_BGR2GRAY);//灰度转换GaussianBlur(imageGray, imageGray, Size(5, 5), 2); //高斯滤波imshow("Gray Image", imageGray);Canny(imageGray, imageGray, 80, 150);imshow("Canny Image", imageGray);//查找轮廓vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());Mat imageContours = Mat::zeros(image.size(), CV_8UC1); //轮廓 Mat marks(image.size(), CV_32S); //Opencv分水岭第二个矩阵参数marks = Scalar::all(0);int index = 0;int compCount = 0;for (; index >= 0; index = hierarchy[index][0], compCount++){//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);}//我们来看一下传入的矩阵marks里是什么东西Mat marksShows;convertScaleAbs(marks, marksShows);imshow("marksShow", marksShows);imshow("轮廓", imageContours);watershed(image, marks);//我们再来看一下分水岭算法之后的矩阵marks里是什么东西Mat afterWatershed;convertScaleAbs(marks, afterWatershed);imshow("After Watershed", afterWatershed);//对每一个区域进行颜色填充Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);for (int i = 0; i < marks.rows; i++){for (int j = 0; j < marks.cols; j++){int index = marks.at<int>(i, j);if (marks.at<int>(i, j) == -1){PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);}else{PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);}}}imshow("After ColorFill", PerspectiveImage);//分割并填充颜色的结果跟原始图像融合Mat wshed;addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);imshow("AddWeighted Image", wshed);waitKey();
}Vec3b RandomColor(int value)//生成随机颜色函数</span>
{value = value % 255; //生成0~255的随机数RNG rng;int aa = rng.uniform(0, value);int bb = rng.uniform(0, value);int cc = rng.uniform(0, value);return Vec3b(aa, bb, cc);
}
效果:
python实现
opencv-python 实现用分水岭算法做图像分割_RayChiu757374816的博客-CSDN博客
【如果对您有帮助,交个朋友给个一键三连吧,您的肯定是我博客高质量维护的动力!!!】