【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法

图像分割

  • 图像分割(Image Segmentation)是图像处理最重要的处理手段之一。
  • 图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
  • 根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 (KMeans !)

距离变换

距离变换的定义 :计算图像中像素点到最近零像素点的距离,也就是零像素点的最短距离。

距离变换的常见算法

  • 不断膨胀/腐蚀得到
  • 基于倒角距离(distance transform)
    ------对于一个有特征点和非特征点的二值图像,此距离变换就是求解每一个点到最近特征点的距离

距离变换常用应用

  • 细化轮廓;
  • 寻找质心;

API

C++: void distanceTransform(
InputArray src,
OutputArray dst,
OutputArray labels, 
int distanceType,
int maskSize,
int abelType=DIST_LABEL_CCOMP)

参数说明

  • src – 8-bit, 单通道(二值化)输入图片。
  • dst – 输出结果中包含计算的距离,这是一个32-bit float 单通道的Mat类型数组,大小与输入图片相同。
  • labels – 可选的2D标签输出(离散 Voronoi 图),类型为 CV_32SC1 大小同输入图片。
  • distanceType – 计算距离的类型那个,可以是 CV_DIST_L1、CV_DIST_L2 、CV_DIST_C。
    -----CV_DIST_L2(maskSize=5)的计算结果是精确的,CV_DIST_L2(maskSize=3)是一个快速计算方法。
  • mask_size 距离变换掩模的大小,可以是 3 或 5. 对 CV_DIST_L1 或 CV_DIST_C 的情况,参数值被强制设定为 3, 因为 3×3 mask 给出 5×5 mask 一样的结果

图像分水岭

将图像中的边缘转换成”山脉”,将均匀区域转化为“山谷”,这样有助于分割目标

算法: 基于浸泡理论 ------- 简单来说就是输出图像的极大值

处理流程

【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第1张图片

代码

//关键:画一个圆作为标记这个不理解

# include <opencv2\opencv.hpp>
# include <iostream>
using namespace std;
using namespace cv;
Mat src, src_gray;
int main(int argc, char**argv) {

	src = imread("H:\\tuku\\jiuwei.jpg");
	if (!src.data)
	{
		cout << "can't find the picture...";
		return -1;
	}
	imshow("sourceimg", src);
	//换背景为黑色
	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("blackground src", src);
	Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);

	//sharpen
	Mat imgLaplance;
	Mat sharpenimg = src;
	filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1),0,BORDER_DEFAULT);

	//拉普拉斯有浮点数计算,位数要提高到32
	src.convertTo(sharpenimg, CV_32F);

	//原图减边缘(白色)实现边缘增强
	Mat resultImg = sharpenimg- imgLaplance;
	resultImg.convertTo(resultImg, CV_8UC3);
	imshow("sharpen Image", resultImg);
	src = resultImg;
	//转化成2值图像
	Mat binary;
	cvtColor(resultImg, resultImg, CV_BGR2GRAY);
	threshold(resultImg, binary, 80, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("binary", binary);

	//距离变换
	Mat distImg;
	distanceTransform(binary, distImg, DIST_L1, 5,CV_32F);

	//归一化
	normalize(distImg, distImg, 0, 1, NORM_MINMAX);
    imshow("distance chang", distImg);

	//again 二值化
	threshold(distImg, distImg, 0.55, 1, THRESH_BINARY);
	imshow("again binary threshold", distImg);

	//腐蚀(使连在一起的分开)
	Mat k1 = Mat::ones(7,7, CV_8UC1);
	erode(distImg, distImg, k1);
	imshow("erode", distImg);

	//标记
	Mat dist_8u;
	distImg.convertTo(dist_8u, CV_8U);
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	findContours(dist_8u, contours, hierachy,RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //finContours只支持CV_8UC1的格式,所以要进行通道转换

	//创建标记
	Mat marsker = Mat::zeros(src.size(), CV_32SC1);// 如果使用 CV_8UC1 ,watershed 函数会报错
	                      //因为masker最后的边缘存储是-1,所以必须使用有符号的
	
	//话标记
	for (size_t i = 0; i < contours.size(); i++) {
		drawContours(marsker, contours, static_cast<int>(i), Scalar(static_cast<int>(i) + 1), -1);
	}
	circle(marsker, Point(5, 5), 3, Scalar(255, 255, 255), -1);
	//关键代码!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	// 创建marker,标记的位置如果在要分割的图像块上会影响分割的结果,如果不创建,分水岭变换会无效
	imshow("marsker", marsker*1000);  //执行后,dist_m的像素值十分的小,扩大了1000倍,才看出来了轮廓

	//分水岭变换
	watershed(src, marsker); //根据距离变换的标记,在原图上分离
	            
	Mat water = Mat::zeros(marsker.size(), CV_8UC1);
	marsker.convertTo(water, CV_8UC1);
	bitwise_not(water, water, Mat());
	imshow("water Image", water);
	// generate random color
     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));
		
	}
	
		    // fill with color and display final result
	Mat dst = Mat::zeros(marsker.size(), CV_8UC3);
	for (int row = 0; row < marsker.rows; row++) {
		  for (int col = 0; col < marsker.cols; col++) {
			   int index = marsker.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;
	waitKey(0);
	return 0;
}

输出效果

【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第2张图片【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第3张图片

【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第4张图片【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第5张图片【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第6张图片【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第7张图片【OpenCV学习笔记35】基于距离变换与分水岭图像分割算法_第8张图片

输出的图像不知道为何,不是特别理想。第一次写博客,有大神路过请不宁赐教

你可能感兴趣的:(opencv)