(一)OpenCV图像处理基础_32_基于距离变换与分水岭的图像分割

  1. 图像分割(Image Segmentation)是图像处理最重要的处理手段之一
    图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
    根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans
  2. 距离变换
    ①不断膨胀/腐蚀得到
    ②基于倒角距离
distanceTransform(InputArray  src, OutputArray dst,  OutputArray  labels,  int  distanceType,  int maskSize,  int labelType=DIST_LABEL_CCOMP)
distanceType = DIST_L1/DIST_L2
maskSize = 3x3,最新的支持5x5,推荐3x3
labels离散维诺图输出
dst输出8位或者32位的浮点数,单一通道,大小与输入图像一致
  1. 分水岭变换(基于浸泡理论实现)
watershed(InputArray image, InputOutputArray  markers)
  1. 步骤:
    将背景变成黑色-目的是为后面的变换做准备
    使用filter2D与拉普拉斯算子实现图像对比度提高sharp
    转为二值图像通过threshold
    距离变换
    对距离变换结果进行归一化到[0~1]之间
    使用阈值,再次二值化,得到标记
    腐蚀得到每个Peak-erode
    发现轮廓findContours
    绘制轮廓drawContours
    分水岭变换 watershed
    对每个分割区域着色输出结果
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
	Mat src;
	src = imread("../path.png");
	if (src.empty())
	{
		cout << "could not load image1..." << endl;
		return -1;
	}
	namedWindow("1src", WINDOW_AUTOSIZE);
	imshow("1src", src);

	//将背景变成黑色 - 目的是为后面的变换做准备
	for (int row = 0; row < src.rows; row++)
	{
		for (int col = 0; col < src.cols; col++)
		{
			if (src.at<Vec3b>(row, col) == Vec3b(0, 0, 0))//把透明底(纯黑色)覆盖为黑色
			{
				src.at<Vec3b>(row, col)[0] = 0;
				src.at<Vec3b>(row, col)[1] = 0;
				src.at<Vec3b>(row, col)[2] = 0;
			}
		}
	}
	namedWindow("2background_black", WINDOW_AUTOSIZE);
	imshow("2background_black", src);

	//使用filter2D与拉普拉斯算子实现图像对比度提高sharp
	Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1,
		1, -8, 1,
		1, 1, 1);//定义卷积核
	Mat Laplacian_img;
	Mat temp = src;//把src赋给中间变量temp,temp进行卷积变换生成Laplacian_img,再把src数据类型转换给中间变量temp(src全过程不变,temp始终是中间变量)
	filter2D(temp, Laplacian_img, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);//自定义卷积//增强对比度
	src.convertTo(temp, CV_32F);//矩阵数据类型转换
	Mat  Sharp_img = temp - Laplacian_img;//图像锐化

	Sharp_img.convertTo(Sharp_img, CV_8UC3);//矩阵数据类型转换
	Laplacian_img.convertTo(Laplacian_img, CV_8UC3);//矩阵数据类型转换
	namedWindow("3Sharp", WINDOW_AUTOSIZE);
	imshow("3Sharp", Sharp_img);

	//转为二值图像通过threshold
	Mat thresh_img;
	cvtColor(Sharp_img, thresh_img, COLOR_BGR2GRAY);//转换为灰度图像
	threshold(thresh_img, thresh_img, 40, 255, THRESH_BINARY | THRESH_OTSU);//边缘变黑,即像素值为0
	namedWindow("4threshold", WINDOW_AUTOSIZE);
	imshow("4threshold", thresh_img);

	//距离变换
	Mat distance_img;
	distanceTransform(thresh_img, distance_img, DIST_L2, 3);//L2为欧式距离度量//无标记距离变换(使得边缘为0,非边缘为非0)
	normalize(distance_img, distance_img, 0, 1, NORM_MINMAX);//对距离变换结果进行归一化到[0~1]之间
	namedWindow("5distance_img", WINDOW_AUTOSIZE);
	imshow("5distance_img", distance_img);

	threshold(distance_img, distance_img, 0.2, 1, THRESH_BINARY);//使用阈值,再次二值化,得到标记
	//执行一些形态学操作,以便从图像中提取峰值
	//腐蚀得到每个Peak-erode
	Mat kernel_2 = Mat::ones(5, 5, CV_8UC1);//自定义卷积核
	erode(distance_img, distance_img, kernel_2);//腐蚀(缩减明亮区域)
	namedWindow("6erode", WINDOW_AUTOSIZE);
	imshow("6erode", distance_img);

	//发现轮廓findContours//为分水岭算法创建种子/标记
	Mat contours_img;
	distance_img.convertTo(contours_img, CV_8U);
	vector<vector<Point>> contours;//定义轮廓
	findContours(contours_img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());//查找轮廓
	//绘制轮廓drawContours
	Mat markers = Mat::zeros(src.size(), CV_32SC1);//定义标志Mat
	for (size_t i = 0; i < contours.size(); i++)
	{
		//drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1),1,8,Mat());//绘制轮廓
		drawContours(markers,//待绘制轮廓的图像
					contours, //要绘制的轮廓
					static_cast<int>(i), //轮廓索引值//需要绘制的是contours参数中的某一条轮廓还是全部轮廓
					Scalar::all(static_cast<int>(i) + 1), -1);//颜色等
	}
	// 创建marker,标记的位置 如果在要分割的图像块上 会影响分割的结果,如果不创建,分水岭变换会无效//所以标记在纯黑背景上
	circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1); //在markers这个Mat图像上画一个点,被点上(标记)的区域会被认为是一个连通区域
	markers.convertTo(markers, CV_8U);
	namedWindow("7markers", WINDOW_AUTOSIZE);
	imshow("7markers", markers*1000);//imshow只能显示8U类型,而markers是32SC1类型//markers灰度级很低,所以要乘1000,不然显示出来会感觉什么都没有
	markers.convertTo(markers, CV_32SC1);

	//分水岭变换
	watershed(src, markers);
	Mat mark = Mat::zeros(markers.size(), CV_8UC1);
	markers.convertTo(mark, CV_8UC1);//数据类型转换	
	bitwise_not(mark, mark, Mat());//取反//让每个区域显灰白
	namedWindow("8watershed", WINDOW_AUTOSIZE);
	imshow("8watershed", 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));//STL
	}

	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);
			}
		}
	}
	namedWindow("9Final Result", WINDOW_AUTOSIZE);
	imshow("9Final Result", dst);

	waitKey(0);
	return 0;
}

src为:(格式为png的透明底图像,来源网络)
在这里插入图片描述
输出结果:
在这里插入图片描述
分割结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值