2021-10-28

基于距离变换与分水岭的图像分割

1、 图像分割(Image Segmentation)

图像分割(Image Segmentation)是图像处理最重要的处理手段之一。图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法-Kmeans。
在这里插入图片描述
2、 距离变换和分水岭
(1) 距离变换
在这里插入图片描述

距离变换的定义 :计算图像中像素点到最近零像素点的距离,也就是零像素点的最短距离。
距离变换的方法:首先对图像进行二值化处理,然后给每个像素赋值为离它最近的背景像素点与其距离(Manhattan距离or欧氏距离),得到distance metric(距离矩阵),那么离边界越远的点越亮。
距离变换常见算法有两种
1)不断膨胀/腐蚀得到
2)基于倒角距离
距离变换常用应用:
• 细化轮廓;
• 寻找质心;

(2) 分水岭变换
传统的分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,如下图所示。
在这里插入图片描述

图像中每一像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆地,而集水盆地的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸人水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝如下图所示,即形成分水岭。
因为传统分水岭算法存在过分割的不足,OpenCV提供了一种改进的分水岭算法,使用一系列预定义标记来引导图像分割的定义方式。使用OpenCV的分水岭算法cv::wathershed,需要输入一个标记图像,图像的像素值为32位有符号正数(CV_32S类型),每个非零像素代表一个标签。它的原理是对图像中部分像素做标记,表明它的所属区域是已知的。分水岭算法可以根据这个初始标签确定其他像素所属的区域。
在这里插入图片描述

分水岭变换常见的算法:
基于浸泡理论实现。假设图像每个位置的像素值为不同的地貌势必会形成山峰和山谷,在山底不停加水,直到各大山头之间形成了明显的分水线——分水岭算法的基本思想。
3、 相关API
(1)距离变换
distanceTransform( InputArray src, OutputArray dst,int distanceType, int maskSize, int dstType=CV_32F);
• src – 8-bit, 单通道(二值化)输入图片。
• dst – 输出结果中包含计算的距离,这是一个32-bit float 单通道的Mat类型数组,大小与输入图片相同。
• distanceType – 计算距离的类型那个,可以是 CV_DIST_L1、CV_DIST_L2 、CV_DIST_C。在这里插入图片描述
• maskSize – 距离变换掩码矩阵的大小,可以是
在这里插入图片描述

(2)分水岭变换
watershed( InputArray image, InputOutputArray markers );
• image – 8-bit, 三通道彩色输入图片。
• markers–图片调用函数后的结果放在这里
4、 处理流程
处理流程分为11步,如下图所示。
在这里插入图片描述

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

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
	char input_win[] = "input image";
	char watershed_win[] = "watershed segmentation demo";
	Mat src = imread("D:/vcprojects/images/cards.png");
	// Mat src = imread("D:/kuaidi.jpg");
	if (src.empty()) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow(input_win, CV_WINDOW_AUTOSIZE);
	imshow(input_win, src);
	// 1.将白色背景变为黑色change background
	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;
			}
		}
	}
	namedWindow("black background", CV_WINDOW_AUTOSIZE);
	imshow("black background", src);

	//2.锐化sharpen
	Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
	Mat imgLaplance;
	Mat sharpenImg = src;
	filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
	src.convertTo(sharpenImg, CV_32F);
	Mat resultImg = sharpenImg - imgLaplance;

	resultImg.convertTo(resultImg, CV_8UC3);
	imgLaplance.convertTo(imgLaplance, 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_BINARY | THRESH_OTSU);
	imshow("binary image", binaryImg);

	//4.距离变换distance transform
	Mat distImg;
	distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);//距离变换:计算源图像的每个像素到最近的零像素的距离

	//5.对距离变换结果进行归一化(0-1之间)
	normalize(distImg, distImg, 0, 1, NORM_MINMAX);
	imshow("distance result", distImg);
	
	//6.再次二值化
	threshold(distImg, distImg, .4, 1, THRESH_BINARY);

	//7.二值腐蚀
	Mat k1 = Mat::ones(13, 13, CV_8UC1);
	erode(distImg, distImg, k1, Point(-1, -1));
	imshow("distance binary image", distImg);

	//8.发现轮廓
	Mat dist_8u;
	distImg.convertTo(dist_8u, CV_8U);
	vector<vector<Point>> contours;
	findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));

	//9.绘制轮廓
	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);
	}
	circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
	imshow("my markers", markers*1000);//markers灰度级很低,所以乘以1000

	//10.分水岭变换
	watershed(src, 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值