在图像分割与抠图(1)和(2)中说到的图像分割的方式都是机器学习的方法,分水岭算法是基于图像图像形态学和结构的方法来进行图像的分割。基于机器学习的方式本质是通过概率统计与建模,通过数学的方式对图像进行分割与分类。分水岭算法是通过图像本身的特征对图像进行分割与分类。
分水岭(Watershed)是基于地理形态的分析的图像分割算法,模仿地理结构(比如山川、沟壑,盆地)来实现对不同物体的分类。分水岭算法中会用到一个重要的概念——测地线距离
图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。其中的水就是用于二值化的gray threshold level,二值化阈值可以理解为水平面,比水平面低的区域会被淹没,刚开始用水填充每个孤立的山谷(局部最小值)。
当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。
在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。
分水岭算法的整个过程:
用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。
其中的解决方法:
其中标记的每个点就相当于分水岭中的注水点,从这些点开始注水使得水平面上升,但是如上图所示,图像中需要分割的区域太多了,手动标记太麻烦,我们可是使用距离转换的方法进行标记,OpenCV中就是使用的这种方法。
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
pyrMeanShiftFiltering(image, shiffted, 21, 51);
imshow("shiftd", shiffted);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
}
如果用二值化后的图,由于还有很多的小点,再利用分水岭算法可能会导致过度分割。因此我们可以先使用均值偏移再进行二值化就可以消除出毛点。
通过均值漂移后再进行二值化毛点得以处理。
再对二值化后的图像进行距离变换:
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
//均值漂移:消除毛点
pyrMeanShiftFiltering(image, shiffted, 21, 51);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
//distance transform
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);//由于距离变换后值变得非常小,需要归一化到0-1
imshow("distance transform result", dist);
}
左图是二值化的, 右图是距离变换后的,在图像中最亮的部分就是山头最高的部分(像素值最大的部分)
距离变换之后寻找种子:
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
//均值漂移:消除毛点
pyrMeanShiftFiltering(image, shiffted, 21, 51);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
//distance transform
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);//由于距离变换后值变得非常小,需要归一化到0-1
imshow("distance transform result", dist);
//x寻找种子:距离变换只有再进行二值化,主要是为了找局部最大
threshold(dist, dist, 0.3, 1, THRESH_BINARY);//由于是经过归一化后的数据,所以阈值我们设置在0.4
imshow("distance binary", dist);
}
可以看到一共有九个种子。
分水岭操作以后结果如下:
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
//均值漂移:消除毛点
pyrMeanShiftFiltering(image, shiffted, 21, 51);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//imshow("binary", binary);
//distance transform
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);//由于距离变换后值变得非常小,需要归一化到0-1
//imshow("distance transform result", dist);
//寻找种子:距离变换只有再进行二值化,主要是为了找局部最大
threshold(dist, dist, 0.2, 1, THRESH_BINARY);//由于是经过归一化后的数据,所以阈值我们设置在0.4
//imshow("distance binary", dist);
//生成Masker
Mat dist_m;
dist.convertTo(dist_m, CV_8U);//转换了8位单通道
//找轮廓
vector>contours;
findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
//create markers
Mat markers = Mat::zeros(image.size(), CV_32SC1);
for (size_t t = 0; t < contours.size(); t++)
{
drawContours(markers, contours, static_cast(t), Scalar::all(static_cast(t) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
//imshow("marker", markers*10000);
//完成分水岭变换
watershed(image, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed result", mark);
}
但是我们可以通过原图看出,如下图用红色标记的,两个三个硬币之间是有缝隙的,但是经过分水岭操作以后缝隙没有了,所以我们需要对他先做一个形态学操作,去除干扰再进行分水岭操作。
经过形态学操作以后再进行分水岭操作结果如下:
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
//均值漂移:消除毛点
pyrMeanShiftFiltering(image, shiffted, 21, 51);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//imshow("binary", binary);
//distance transform
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);//由于距离变换后值变得非常小,需要归一化到0-1
//imshow("distance transform result", dist);
//寻找种子:距离变换只有再进行二值化,主要是为了找局部最大
threshold(dist, dist, 0.2, 1, THRESH_BINARY);//由于是经过归一化后的数据,所以阈值我们设置在0.4
//imshow("distance binary", dist);
//生成Masker
Mat dist_m;
dist.convertTo(dist_m, CV_8U);//转换了8位单通道
//找轮廓
vector>contours;
findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
//create markers
Mat markers = Mat::zeros(image.size(), CV_32SC1);
for (size_t t = 0; t < contours.size(); t++)
{
drawContours(markers, contours, static_cast(t), Scalar::all(static_cast(t) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
//imshow("marker", markers*10000);
//形态学操作,目的是去掉干扰,让结果更好
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(image, image, MORPH_ERODE, k);
//完成分水岭变换
watershed(image, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed result", mark);
}
这样的效果就很好了。
然后生成随机颜色对他进行颜色。
全部代码如下:
void MyApi::Watershed_image_segmentation(Mat& image)
{
Mat gray, binary, shiffted;
//均值漂移:消除毛点
pyrMeanShiftFiltering(image, shiffted, 21, 51);
cvtColor(shiffted, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//imshow("binary", binary);
//distance transform
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);//由于距离变换后值变得非常小,需要归一化到0-1
//imshow("distance transform result", dist);
//寻找种子:距离变换只有再进行二值化,主要是为了找局部最大
threshold(dist, dist, 0.2, 1, THRESH_BINARY);//由于是经过归一化后的数据,所以阈值我们设置在0.4
//imshow("distance binary", dist);
//生成Masker
Mat dist_m;
dist.convertTo(dist_m, CV_8U);//转换了8位单通道
//找轮廓
vector>contours;
findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
//create markers
Mat markers = Mat::zeros(image.size(), CV_32SC1);
for (size_t t = 0; t < contours.size(); t++)
{
drawContours(markers, contours, static_cast(t), Scalar::all(static_cast(t) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
//imshow("marker", markers*10000);
//形态学操作,目的是去掉干扰,让结果更好
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(image, image, MORPH_ERODE, k);
//完成分水岭变换
watershed(image, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed result", mark);
//generate random color
vectorcolors;
for (size_t i = 0; i < contours.size(); i++)
{
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
//颜色填充与最终显示
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
int index = 0;
for (int row = 0; row < markers.rows; row++)
{
for (int col = 0; col < markers.cols; col++)
{
if (index > 0 && index <= contours.size())
{
dst.at(row, col) = colors[index - 1];
}
else
{
dst.at(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("Finall result", dst);
printf("number of object:%d", contours.size());
}
#include
#include
using namespace std;
using namespace cv;
//执行分水岭算法函数
Mat watershedCluster(Mat &srcImg, int &numSegments);
//结果显示函数
void DisplaySegments(Mat &markersImg, int numSegments);
void test()
{
Mat srcImg;
srcImg = imread("toux.jpg");
if (srcImg.empty())
{
cout << "could not load image...\n" << endl;
}
namedWindow("Original image", CV_WINDOW_AUTOSIZE);
imshow("Original image", srcImg);
int numSegments;
Mat markers = watershedCluster(srcImg, numSegments);
DisplaySegments(markers, numSegments);
}
Mat watershedCluster(Mat &srcImg, int &numSegments)
{
//二值化
Mat grayImg, binaryImg;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);
//形态学和距离变换
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binaryImg, binaryImg, MORPH_OPEN, kernel, Point(-1, -1));
Mat distImg;
distanceTransform(binaryImg, distImg, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(distImg, distImg, 0.0, 1.0, NORM_MINMAX);
//开始生成标记
threshold(distImg, distImg, 0.1, 1.0, THRESH_BINARY);
normalize(distImg, distImg, 0, 255, NORM_MINMAX);
distImg.convertTo(distImg, CV_8UC1); //CV_32F 转成 CV_8UC1
//标记开始
vector>contours;
vectorhireachy;
findContours(distImg, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
if (contours.empty())
{
return Mat();
}
Mat markersImg(distImg.size(), CV_32S);
markersImg = Scalar::all(0);
for (int i = 0; i < contours.size(); i++)
{
drawContours(markersImg, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);
}
circle(markersImg, Point(5, 5) ,3, Scalar(255), -1);
//分水岭变换
watershed(srcImg, markersImg);
numSegments = contours.size();
return markersImg;
}
void DisplaySegments(Mat &markersImg, int numSegments)
{
//生成随机颜色
vectorcolors;
for (int i = 0; i < numSegments; i++)
{
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
//颜色填充和最终显示
Mat dstImg = Mat::zeros(markersImg.size(), CV_8UC3);
int index = 0;
for (int i = 0; i < markersImg.rows; i++)
{
for (int j = 0; j < markersImg.cols; j++)
{
index = markersImg.at(i, j);
if (index > 0 && index <= numSegments)
{
dstImg.at(i, j) = colors[index - 1];
}
else
{
dstImg.at(i, j) = Vec3b(255, 255, 255);
}
}
}
cout << "number of objects:" << numSegments << endl;
namedWindow("Final Result", CV_WINDOW_AUTOSIZE);
imshow("Final Result", dstImg);
}
int main()
{
test();
waitKey(0);
return 0;
}