图像分割是按照一定的原则将一幅图像分割为若干个互不相干的小的局部的过程。分水岭算法会把临近像素间的相似性作为一个重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点相互连接起来,构成一个封闭的轮廓。封闭性是分水岭的算法的一个重要的特征。
分水岭算法是一个种基于拓补理论的数学形态学的分割。其思想是把图像看做测地学上的拓补地貌,图像中的买一个像素可以看做是地理上的海报高度,每一个局部的极小值及其影响区域称为集水盆地,而集水盆地的边界便是分水岭。在每一个局部极小值的表面,刺穿一个小孔然后吧整个模型慢慢浸入到水中,随着浸入的加深,每一个局部极小值的影响慢慢的向外面拓展,在两个集水盆地汇合处构建大坝,即形成了分水岭。
分水岭算法的原理: 把图像看做一个平面,图像中灰度值高的区域看做是山峰,灰度值低的地方看做是山谷,不同区域的山谷可以用不同的颜色来标记。
void watershed(
InputArray image, // 是一个8位的三通道的彩色图像矩阵序列,
InputOutputArray markers // 必须包含种子的信息,大意说的是在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
);
第一个参数可以不说,但是第二个参数很重要:markers 必须包含种子的信息,在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位是通过findContours方法来寻找的,这个是执行分水岭之前的要求。算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
watershed图像自动分割的实现步骤:
1. 将图像灰度化、滤波、边缘检测(canny算子)
2. 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。
3. waterhord函数进行分水岭运算
4. 填充不同的颜色来查看不同的区域
----------------------------------------------------------------------------------------------------------------
// 生产随机数字的
Vec3b RandomColor(int value)
{
value = value % 255;
RNG rng;
int b = rng.uniform(0, value);
int g = rng.uniform(0, value);
int r = rng.uniform(0, value);
return Vec3b(b,g,r);
}
int main()
{
// 第一 图像预处理
Mat src = imread("C:\\Users\\19473\\Desktop\\opencv_images\\540.png");
if (!src.data)
{
printf("could not load image....\n");
}
imshow("原图",src);
Mat gray;
cvtColor(src,gray,CV_BGR2GRAY);
GaussianBlur(gray,gray,Size(5,5),2); //高斯滤波
imshow("高斯图像", gray);
Canny(gray, gray, 60, 150);
imshow("Canny Image", gray);
// 第二 开始找轮廓
vector> contours;
vector hierarchy;
findContours(gray, contours, hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
Mat imageContour = Mat::zeros(src.size(),CV_8SC1); // 轮廓的图像
====================================== watershord的第二个参数,这个参数就是分水岭算法的灵魂=================================
Mat marks(src.size(),CV_32S);
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(imageContour, contours, index, Scalar(255), 1, 8, hierarchy);
}
Mat markshows;
convertScaleAbs(marks, markshows);
imshow("markshow ", markshows);
imshow("轮廓 ", imageContour);
Mat watered;
watershed(src,marks);
convertScaleAbs(marks, watered);
imshow("convertScaleAbs", watered);
// 对每个区域进行颜色填充
Mat wateredImage= Mat::zeros(src.size(), CV_8UC3);
for (int i=0;i(i,j);
if (marks.at(i,j)==-1)
{
// 分界线的颜色是白色的
wateredImage.at(i,j) = Vec3b(255,255,255);
}
else
{
wateredImage.at(i, j) = RandomColor(gray);
}
}
}
Mat wshed;
//分割并填充颜色的结果跟原始图像融合
addWeighted(src, 0.4, wateredImage, 0.6, 0, wshed);
imshow("与原图合并之后的图像", wshed);
waitKey(0);
return 0;
}