C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法

        在图像分割与抠图(1)和(2)中说到的图像分割的方式都是机器学习的方法,分水岭算法是基于图像图像形态学和结构的方法来进行图像的分割。基于机器学习的方式本质是通过概率统计与建模,通过数学的方式对图像进行分割与分类。分水岭算法是通过图像本身的特征对图像进行分割与分类。

1.分水岭算法概述

1.1.分水岭算法的理解

        分水岭(Watershed)是基于地理形态的分析的图像分割算法,模仿地理结构(比如山川、沟壑,盆地)来实现对不同物体的分类。分水岭算法中会用到一个重要的概念——测地线距离

        图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。其中的水就是用于二值化的gray threshold level,二值化阈值可以理解为水平面,比水平面低的区域会被淹没,刚开始用水填充每个孤立的山谷(局部最小值)。

        当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第1张图片

 在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。

 1.2.分水岭算法的过程

分水岭算法的整个过程:

  1. 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
  2. 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
  3. 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。
  4. 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第2张图片

        用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。

其中的解决方法:

  1. 对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。
  2. 不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第3张图片

 其中标记的每个点就相当于分水岭中的注水点,从这些点开始注水使得水平面上升,但是如上图所示,图像中需要分割的区域太多了,手动标记太麻烦,我们可是使用距离转换的方法进行标记,OpenCV中就是使用的这种方法。

 1.3.分水岭算法应用

  • 分割粘连对象。实现形态学操作与对象计数
  • 图像分割

1.4.基于距离的分水岭分割流程

              C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第4张图片

 2.案例实战

2.1粘连对象分离与计数

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);
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第5张图片

        如果用二值化后的图,由于还有很多的小点,再利用分水岭算法可能会导致过度分割。因此我们可以先使用均值偏移再进行二值化就可以消除出毛点。

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第6张图片

 通过均值漂移后再进行二值化毛点得以处理。

 再对二值化后的图像进行距离变换:

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);

}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第7张图片

 左图是二值化的, 右图是距离变换后的,在图像中最亮的部分就是山头最高的部分(像素值最大的部分)

 距离变换之后寻找种子:

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);
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第8张图片

 可以看到一共有九个种子。

 分水岭操作以后结果如下:

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);
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第9张图片

但是我们可以通过原图看出,如下图用红色标记的,两个三个硬币之间是有缝隙的,但是经过分水岭操作以后缝隙没有了,所以我们需要对他先做一个形态学操作,去除干扰再进行分水岭操作。

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第10张图片

 经过形态学操作以后再进行分水岭操作结果如下:

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);
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第11张图片

 这样的效果就很好了。

然后生成随机颜色对他进行颜色。

全部代码如下:

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());
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第12张图片

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第13张图片

2.2图像分割

#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;
}

C++OpenCV系统学习(16)——图像分割与抠图(3)分水岭算法_第14张图片

你可能感兴趣的:(c++opencv图像处理,opencv,c++,算法)