一、基于分水岭的分割算法是受自然界地貌启发而来的对灰度图的地形学解释,我们考虑以下三点:
1. 局部最小值点,该点对应一个盆地的最低点,当我们在盆地里滴一滴水的时候,由于重力作用,水最终会汇聚到该点。注意:可能存在一个最小值面,该平面内的都是最小值点。
2. 盆地的其它位置点,该位置滴的水滴会汇聚到局部最小点。
3. 盆地的边缘点,是该盆地和其它盆地交接点,在该点滴一滴水,会等概率的流向任何一个盆地。
假设我们在盆地的最小值点,打一个洞,然后往盆地里面注水,并阻止两个盆地的水汇集,我们会在两个盆地的水汇集的时刻,在交接的边缘线上(也即分水岭线),建一个坝,来阻止两个盆地的水汇集成一片水域。这样图像就被分成2个像素集,一个是注水盆地像素集,一个是分水岭线像素集。
二、在上面的水岭算法示意图中局部极小值、积水盆地,分水岭线以及水坝的概念可以描述为:
(1)区域极小值:导数为0的点,局部范围内的最小值点;
(2)集水盆(汇水盆地):当“水”落到汇水盆地时,“水”会自然而然地流到汇水盆地中的区域极小值点处;
(3)分水岭:当“水”处于分水岭的位置时,会等概率地流向多个与它相邻的汇水盆地中;
(4)水坝:人为修建的分水岭,防止相邻汇水盆地之间的“水”互相交汇影响。
watershed()函数的原型如下:
watershed(InputArray image, InputOutputArray markers)
可见,非常简单,就两个参数,但实际上不是那么简单的,官方文档对这个参数的说明翻译如下:
markers中存储了图像的大致轮廓,并且以值1,2,3...分别表示各个components.markers,通常由函数findContours() 和 drawContours()结合使用来获得。markers相当于watershed()运行时的种子参数。markers中,不属于轮廓的点的值应置为0.函数运行后,图像中的像素如果是在由某个轮廓种子生成的区域中,那么其值就置为这个种子的编号,如果像素不在轮廓种子生成的区域中,则置为-1。
金子塔均值漂移pyrMeanShiftFiltering
meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心(同时考虑像素的空间位置和颜色值,如果是灰度图,相当于三维数据点,彩色图相当于五维数据点),即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。 可以利用均值偏移算法的这个特性,实现彩色图像分割。这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节。
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main(int arc, char** argv) {
Mat src = imread("3.jpg");
namedWindow("input", CV_WINDOW_AUTOSIZE);
imshow("input", src);
////均值漂移滤波及二值化
Mat shifted, gray, binary;
pyrMeanShiftFiltering(src, shifted, 25, 55);
imshow("MeanShiftFilter", shifted);
cvtColor(shifted, gray, CV_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("MeanShiftFilter_binary", binary);
//距离变换及二值化
Mat dist ;
distanceTransform(binary, dist, 2, 3,CV_32F);//这里距离是选择L2,会有浮点数,所以类型为CV_32F
normalize(dist, dist, 0, 1, NORM_MINMAX);
imshow("distanceTransform", dist);
threshold(dist, dist, 0.4, 1, THRESH_BINARY);
Mat dist_u;
dist.convertTo(dist_u, CV_8U);
normalize(dist_u, dist_u, 0, 255, NORM_MINMAX);
imshow("distanceTransform_binary", dist_u);
//寻找种子点
vector<vector<Point>>contours;
findContours(dist_u, contours, RETR_EXTERNAL,CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat markers = Mat::zeros(src.size(), CV_32S);
for (int i = 0; i < contours.size(); i++) {
drawContours(markers, contours, i, Scalar(i+1), -1);//将Scalar值作为各类的索引值,则markers里的值为1到9
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);//再加上个背景,索引给个255
imshow("markers", markers*10000);
//腐蚀
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(src, src, MORPH_ERODE, kernel);
imshow("erode", src);
//分水岭分割并显示
watershed(src, markers);
Mat mark;
markers.convertTo(mark, CV_8UC1);
imshow("watershed", mark*20);
//用不同颜色显示
Mat wshed(markers.size(), CV_8UC3);
vector<Vec3b> colorTab;
RNG rng(1);
for (int i = 0; i < contours.size(); i++)
{
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
for (int i = 0; i < markers.rows; i++)
for (int j = 0; j < markers.cols; j++)
{
int index = markers.at<int>(i, j);
if (index > 0 && index <= contours.size())
wshed.at<Vec3b>(i, j) = colorTab[index - 1];
else
wshed.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
imshow("result", wshed);
printf("number of objects:%d", contours.size());
waitKey(0);
return 0;
}
原图及运行结果如下:
参考文献:https://blog.csdn.net/tangketan/article/details/39757513