基于距离变换与分水岭的图像分割(基于图像的拓扑结构)

1、图像分割概念;
2、距离变换和分水岭介绍;
3、相关API;
4、Code;

图像分割(Image Segmentation)

1、图像分割: 将对象从背景中分离出来;常见的图像分割算法实现有很多;

2、图像分割的目标是将图像中的像素根据一定的规则(算法)分为若干个(cluster)集合,每个集合包含一类像素;
3、图像分割算法可以分为监督学习方法和无监督学习方法,图像分割算法多数都是无监督学习方法(Kmeans); 属于数据处理与机器学习范围;

距离变换与分水岭介绍

1、距离变换:

2、分水岭:(与水淹山头现象大概类似);理论为分水岭浸泡理论


3、距离变换算法的两种原理:①不断膨胀腐蚀(dilate,erode)得到;②基于倒角距离;分水岭变换常见算法:①基于浸泡理论实现;

相关API

1、距离变换API :cv::distanceTransform();
labels : 距离变换出来的相同距离的分类;
distancetype : 距离类型;
maskSize : 腐蚀和膨胀的掩膜
2、分水岭变换API : cv::watershed(); shed : 分水岭
markers : 输出分割标线;



使用流程

1、将白色背景改为黑色,为变换做准备;
2、使用filter2D与Laplace算子实现图像对比度的提高;sharp锐化
3、通过threshold()转换为二值图像;
4、距离变换;
5、对距离变换的结果归一化到0-1之间;
6、使用阈值,再次二值化,得到标记;
7、腐蚀(erode)得到每个Peak;(peak : 山峰,山顶 )
8、发现轮廓;
9、绘制轮廓;
10、分水岭变换;
11、对每个分割区域着色输出结果;

Code

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

const char* input_win = "input image";
const char* output_win = "watershed segementation demo";

int main(int argc, char** argv)
{
   
	Mat src = imread("C:\\Users\\hello\\Desktop\\39.jpg");
	if (src.empty())
	{
   
		cout << "could not load the image..." << endl;
		return -1;
	}
	namedWindow(input_win, WINDOW_AUTOSIZE);
	namedWindow(output_win, WINDOW_AUTOSIZE);
	imshow(input_win, src);
	//1、改变背景色
	for (int row = 0; row < src.rows; row++)
	{
   
		for (int col = 0; col < src.cols; col++)
		{
   
			if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255))
			{
   
				src.at<Vec3b>(row, col)[0] = 0;
				src.at<Vec3b>(row, col)[1] = 0;
				src.at<Vec3b>(row, col)[2] = 0;
			}
		}
	}
	imshow("black backbround", src);
	//2、锐化Sharp --- Sharpen
	Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);  //定义掩膜
	Mat imgLaplace;
	Mat sharpenImg ;
	filter2D(src, imgLaplace, CV_32F, kernel, Point(-1, -1),0,BORDER_DEFAULT);  //imgLaplace 的位图深度应为32F
	src.convertTo(sharpenImg, CV_32F);
	Mat resultImg = sharpenImg - imgLaplace;  //加减必须类型一致
	//锐化图像转换
	resultImg.convertTo(resultImg, CV_8UC3);
	imgLaplace.convertTo(imgLaplace, CV_8UC3);
	imshow("sharpen image", resultImg);
	src = resultImg; //copy back 

	//3、二值图像变换 convert to binary
	Mat binaryImg;
	cvtColor(src, resultImg, CV_BGR2GRAY);
	threshold(resultImg, binaryImg, 40, 255, THRESH_OTSU | THRESH_BINARY);  //产生二值图像,自动寻找阈值
	imshow("binary image", binaryImg);

	//4、距离变换
	Mat distImg;
	distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);  //距离变换
	//5、归一化
	normalize(distImg, distImg, 0, 1, NORM_MINMAX, -1, Mat());  //归一化
	imshow("distance result", distImg);    
	//6、再次阈值化
	threshold(distImg, distImg, 0.4, 1, THRESH_BINARY);  //阈值0.4是自己提前试出来的
	imshow("distance binary image", distImg);
	//7、腐蚀
	Mat k1 = Mat::zeros(3, 3, CV_8UC1);
	erode(distImg, distImg, k1, Point(-1, -1));
	imshow("erode image", distImg);
	//8、mask标记
	Mat dist_8u;
	distImg.convertTo(dist_8u, CV_8U);
	vector<vector<Point>> contours;
	findContours(dist_8u, contours, RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

	Mat markers = Mat::zeros(src.size(), CV_32SC1);

	for (size_t i = 0; i < contours.size(); i++)
	{
   
		drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1),-1); //thicknesss : -1代表填充
	}
	circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);  
	imshow("my markers", markers * 1000); //markers像素值很低,*1000放大

	//根据markers对src进行分水岭
	watershed(src, markers);    //计算值会放到markers中

	Mat mark = Mat::zeros(markers.size(), CV_8UC1);
	markers.convertTo(mark, CV_8UC1);

	bitwise_not(mark, mark, Mat());
	imshow("watershed image", mark);    //分水岭图像为灰度图像,不太清晰

	//产生随机颜色填充
	vector<Vec3b> colors;
	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);
	for (int row = 0; row < markers.rows; row++)
	{
   
		for (int col = 0; col < markers.cols; col++)
		{
   
			int index = markers.at<int>(row, col);    
			if (index > 0 && index <= static_cast<int>(contours.size()))
			{
   
				dst.at<Vec3b>(row, col) = colors[index - 1];
			}
			else
			{
   
				dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
			}
		}
	}
	imshow("final result", dst);

	waitKey(0);
	return 0;
}

效果


锐化图:

二值图:

距离变换:

再次二值化:

腐蚀:

Mark标记图像:

最终分水岭图像:

着***r>