Opencv 关于分水岭算法

关于分水岭算法的原理及表现,这篇博客不错,
https://blog.csdn.net/linqianbi/article/details/79121005

重要的思想:
在这里插入图片描述
上面的博客里面也有程序,用于计算图片中的硬币个数,程序都差不多,自己注释了一下,方便自己理解

实例一.分割粘连对象,实现形态学操作与对象计数
程序思路:
读取图像,将原图上进行pyrMeanShiftFiltering()处理,将背景变为纯色,同时避免过多的噪点,保留更多的边缘信息;
在平滑区进行滤波,保证后面二值化时的效果更好;
转成单通道,进行二值处理,进行距离变换,将距离变换的结果归一化,找到山峰;
然后再一次进行二值化处理,转到CV_8U类型的图像;进行寻找轮廓,绘制轮廓,每次绘制轮廓时用不同的值对每个轮廓进行标记;
进行形态学的操作,去掉干扰,让结果更好;
完成分水岭的变换,使用watershed函数,得到maskers根据masker中的像素值,进行索引颜色的填充

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main(int argc, char** argv) 
{
	Mat src = imread("coins_001.jpg");
 	if (src.empty())
 	{
 		printf("could not load image...\n");
  		return -1;
 	}
 	namedWindow("input image", WINDOW_AUTOSIZE);
 	imshow("input image", src);

	Mat gray, binary, shifted;  //定义灰度图,二值图,转换图
 	pyrMeanShiftFiltering(src, shifted, 21, 51); //作用:将背景变成纯色,避免过多的噪点
 	//函数pyrMeanShiftFiltering讲解:https://blog.csdn.net/dcrmg/article/details/52705087
 	//imshow("shifted", shifted); 可以自己将这步显示出来看看效果

	cvtColor(shifted, gray, COLOR_BGR2GRAY);  //转成灰度图
 	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);  //二值化
 	//imshow("binary", binary);

	//距离变换
 	Mat dist;
 	distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
 	//距离变换函数介绍:https://blog.csdn.net/liubing8609/article/details/78483667
 	normalize(dist, dist, 0, 1, NORM_MINMAX);
 	//矩阵归一化:normalize()函数
 	//void cv::normalize(InputArry src,InputOutputArray dst,double alpha=1,double beta=0,
 	//      int norm_type=NORM_L2,int dtype=-1,InputArray mark=noArry())
 	// src               输入数组;
 	// dst               输出数组,数组的大小和原数组一致;
 	// alpha     1, 用来规范值,2.规范范围,并且是下限;
 	// beta             只用来规范范围并且是上限;
 	// norm_type       归一化选择的数学公式类型;
 	// type              当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输如不同,不同的地方游dtype决定;
 	// mark              掩码。选择感兴趣区域,选定后只能对该区域进行操作。

	// binary,二值
 	threshold(dist, dist, 0.4, 1, THRESH_BINARY);
	 //imshow("distance binary", dist);

	// 进行计算标记的分割块   
 	Mat dist_m;
 	dist.convertTo(dist_m, CV_8U);  //将dist_m转换成dist矩阵的CV_8U类型
 	vector<vector<Point>> contours;//vector<Point>点的集合,一系列的点组成轮廓的集合
 	findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //寻找轮廓

	// create markers   生成种子
 	Mat markers = Mat::zeros(src.size(), CV_32SC1);
 	for (size_t t = 0; t < contours.size(); t++) 
 	{
 		//绘制轮廓
  		drawContours(markers, contours, static_cast<int>(t), Scalar::all(static_cast<int>(t) + 1), -1);
 	}
 	circle(markers, Point(5, 5), 3, Scalar(255), -1);
 	//imshow("markers", markers*10000);

	// 形态学操作 - 彩色图像,目的是去掉干扰,让结果更好
 	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
 	morphologyEx(src, src, MORPH_ERODE, k); //MORPH_ERODE表示选择的是腐蚀操作

	// 完成分水岭变换
 	watershed(src, markers);
 	Mat mark = Mat::zeros(markers.size(), CV_8UC1);
 	markers.convertTo(mark, CV_8UC1);
 	bitwise_not(mark, mark, Mat());
 	//imshow("watershed result", 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);
 	int index = 0;
 	for (int row = 0; row < markers.rows; row++) 
 	{
 		for (int col = 0; col < markers.cols; col++) 
 		{
 			index = markers.at<int>(row, col);
   			if (index > 0 && index <= contours.size()) 
   			{
   				dst.at<Vec3b>(row, col) = colors[index - 1];
   			}
   			else
   			{
   				dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
   			}
 		}
 	}

	imshow("效果图", dst);
 	printf("检测到的硬币个数 : %d\n", contours.size());
 	waitKey(0);
 	return 0;
}

该程序的效果如上面博客里面效果图那样
在这里插入图片描述实例二:基于分水岭算法的图像分割
程序如下:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

Mat watershedCluster(Mat &image, int &numSegments);
void createDisplaySegments(Mat &segments, int numSegments, Mat &image);

int main(int argc, char** argv) 
{
	Mat src = imread("1.jpg");
 	if (src.empty()) 
 	{
 		printf("could not load image...\n");
  		return -1;
 	}
 	namedWindow("input image", WINDOW_AUTOSIZE);
 	imshow("input image", src);

	int numSegments;   //定义分割的种类个数
 	Mat markers = watershedCluster(src, numSegments);
 	createDisplaySegments(markers, numSegments, src);
 	cout << "numSegments:" << numSegments << endl;
 	waitKey(0);
 	return 0;
}

//完成分水岭的变换,并返回轮廓的数目
Mat watershedCluster(Mat &image, int &numComp) 
{
	// 二值化
 	Mat gray, binary;
 	cvtColor(image, gray, COLOR_BGR2GRAY);
 	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
 	// 形态学与距离变换
 	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
 	//进行开运算,去掉小的点的干扰,分水岭分割是自动计算分类,如果有干扰,分类就会有很多
 	morphologyEx(binary, binary, MORPH_OPEN, k, Point(-1, -1));
 	
	Mat dist;
 	distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
 	normalize(dist, dist, 0.0, 1.0, NORM_MINMAX);

	// 开始生成标记
 	threshold(dist, dist, 0.1, 1.0, THRESH_BINARY);
 	normalize(dist, dist, 0, 255, NORM_MINMAX); //归一化
 	dist.convertTo(dist, CV_8UC1);  //将dist转成CV_8UC1才能进行如下的操作

	// 标记开始,先找到轮廓
 	vector<vector<Point>> contours;
 	vector<Vec4i> hireachy;
 	findContours(dist, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
 	if (contours.empty())  //若未找到轮廓
 	{
 		return Mat();
 	}

	//  再进行轮廓的填充,绘制markers
 	Mat markers(dist.size(), CV_32S);
 	markers = Scalar::all(0);
 	for (int i = 0; i < contours.size(); i++) 
 	{
 		drawContours(markers, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);
 	}
	circle(markers, Point(5, 5), 3, Scalar(255), -1);
	
	// 分水岭变换
 	watershed(image, markers);
 	numComp = contours.size();
 	return markers;
}

//这里注意是Mat &markers是引用传递的方式。
//形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,
//被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,
//但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过
//栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
//参考博客:https://blog.csdn.net/qq_35789421/article/details/89087183
void createDisplaySegments(Mat &markers, int numSegments, Mat &image) 
{
	// 生成随机颜色
 	vector<Vec3b> colors;
 	for (size_t 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 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++)
 		{
 			index = markers.at<int>(row, col);
   			if (index > 0 && index <= numSegments)
   			{
   				dst.at<Vec3b>(row, col) = colors[index - 1];
   			}
   			else
   			{
   				dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
   			}
 		}
 	}
 	imshow("分水岭图像分割-演示", dst); 	
}

效果图:
在这里插入图片描述

发布了25 篇原创文章 · 获赞 0 · 访问量 466

猜你喜欢

转载自blog.csdn.net/qq_45445740/article/details/103199774