og真人 用于图像分割的分水岭算法

日期:2021-01-17 21:14:41 浏览量: 168

使用C ++和opencv进行图像的分水岭分割

分水岭的概念基于图像的三维可视化:两个是坐标,另一个是灰度。基于对“地形”的这种解释,我们考虑了三种类型的点:

a。属于局部极小值的点也可能具有最小曲面,并且平面中的所有点都是极小点

b。当在特定位置滴一滴水时,水必须降到一个最小点

c。当水在某个点时,水将以相同的概率流到一个以上的最小点。

对于特定区域的最小值,满足条件(b)的点的集合称为该最小值的“集水盆地”或“分水岭”。满足条件(c)的点集构成了地形表面的峰线,称为“分界线”或“分界线”。

分水岭算法

分水岭分割方法是一种基于拓扑理论的数学形态学分割方法。当前有两种比较著名和常用的算法:

([1)自下而上的洪水模拟算法(2)自上而下的降水模拟算法

这是泛洪算法的过程。

算法的主要思想:

我们将图像视为大地测量学中的拓扑拓扑。图像中每个像素的灰度值代表该点的高度。模拟洪泛算法的基本思想是:假设每个区域中最小值的位置在上方形成一个洞亚博网页版 ,并让水以均匀的上升速度从洞中涌出分水岭算法,从低到高淹没整个地形。当不同收敛盆地中的水将要汇合时,大坝的建设将防止汇合。水将到达在水线上只能看到水坝顶部的位置。这些水坝的边界对应于分水岭的分界线。因此,它们是分水岭算法提取的(连续)边界线。

原始图片:地形俯视图:

分水岭算法

分水岭算法

原始图像显示一个简单的灰度图像,其中“山”的高度与输入图像的灰度值成比例。为了防止上升的水从这些结构的边缘溢出,我们设想用比最高的山高的大坝围住整个地形图。最高峰的值取决于输入图像的灰度级最大值。

分水岭算法

分水岭算法

分水岭算法

图片1是被水淹没的第一阶段,其中水由浅灰色表示,覆盖了与图片中深色背景相对应的区域。在图2和图3中澳洲幸运8APP ,我们分别看到第一和第二集水盆地的水位上升。随着水的继续上升,最终水将从一个集水区溢出到另一个集水区。

分水岭算法

分水岭算法

溢出的第一个迹象如左图所示。在这里,水确实从左侧的水盆溢出到右侧的水盆,并且两者之间有一个短的“坝”(由一个像素组成)BG真人 ,以防止该高度的水聚集在一起。如右图所示,随着水位持续上升。该图显示了两个集水盆地之间的水坝较长,另一个水坝位于右上角。该水坝可防止流域内的水与对应于背景的水汇聚。

此过程一直持续到达到最大水位(对应于图像中的最大灰度)为止。大坝的最后剩余部分对应于分水岭线,这是要获得的分段结果。

分水岭算法

在此示例中,分水岭显示为叠加在原始图像上的一像素宽的暗路径。请注意,一个重要的属性是分水岭形成一条连接的路径,该路径在区域之间提供了连续的边界。

动画显示了整个分水岭算法的过程:

分水岭算法

算法实现:

分水岭算法

分水岭算法

算法应用程序:

分水岭算法对噪声等影响非常敏感。因此,在真实图像中,由于存在噪声点或其他干扰因素,使用分水岭算法经常会出现过分分割现象。这是由于存在许多小的局部极端点,例如下面的图像。这种细分效果是无用的。

分水岭算法

分水岭算法

为了解决过度分割的问题,可以使用基于标记图像的分水岭算法,该算法使用先验知识指导分水岭算法以获得更好的图像分割效果。在通常的标记图像中,某些区域中定义了一些灰度。在该区域的洪水过程中,水平面从定义的高度开始,这可以避免对一些非常小的噪声极端区域进行分割。 。以下动画很好地演示了基于标记的分水岭算法过程:

分水岭算法

对于上面过度分割的图像,通过指定标记区域,我们可以获得良好的分割效果:

分水岭算法

分水岭算法

以上参考:Gonzalez的“数字图像处理(第三版)”和

相关API:

void setMousecallback(const string&winname,MouseCallback onMouse,void * userdata =0)

winname:窗口的名称

onMouse:鼠标响应函数,回调函数。指定每次在指定窗口中发生鼠标时间时要调用的函数指针。此函数的原型应为void on_Mouse(int event,int x,int y,int flags,void * param);

userdate:传递给回调函数的参数

void on_Mouse(int事件,int x,int y,int标志,void *参数)

事件:CV_EVENT_ *变量之一

x和y:鼠标指针在图像坐标系(而不是窗口坐标系)中的坐标

flags:CV_EVENT_FLAG的组合,param是传递给setMouseCallback函数调用的用户定义参数。

附加的常见事件:CV_EVENT_MOUSEMOVE,CV_EVENT_LBUTTONDOWN,CV_EVENT_RBUTTONDOWNBG视讯 ,CV_EVENT_LBUTTONUP亚博体彩app ,CV_EVENT_RBUTTONUP

与标志有关:CV_EVENT_FLAG_LBUTTON

C ++:无效分水岭(InputArray图像,InputOutputArray标记)

第一个参数,即InputArray类型的src,即输入图像(即源图像),只需填充Mat类的对象,并且它必须是8位三通道彩色图像。

第二个参数InputOutput Array类型标记,将函数调用后的运算结果存储在此处分水岭算法,输入/输出32位单通道图像标记结果。也就是说,此参数用于调整功能后存储输出结果,并且其大小和类型必须与源图像相同。

代码实现:

#include "stdafx.h"
#include "opencv2/imgproc/imgproc.hpp"  
#include "opencv2/highgui/highgui.hpp"  
#include   
#include   
using namespace cv;
using namespace std;
#define WINDOW_NAME1 "【程序窗口1】"        //为窗口标题定义的宏   
#define WINDOW_NAME2 "【分水岭算法效果图】"        //为窗口标题定义的宏  
//描述:全局变量的声明  
Mat g_maskImage, g_srcImage;
Point prevPt(-1, -1);
//描述:全局函数的声明  
static void ShowHelpText();
static void on_Mouse(int event, int x, int y, int flags, void*);
int main()
{
	//【0】改变console字体颜色  
	system("color 02");
	//【1】载入原图并显示,初始化掩膜和灰度图
	g_srcImage = imread("D:\\pic-sam\\哀.JPG", 1);
	namedWindow(WINDOW_NAME1, WINDOW_NORMAL);
	imshow(WINDOW_NAME1, g_srcImage);
	Mat srcImage, grayImage;
	g_srcImage.copyTo(srcImage);
	cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);
	cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);
	g_maskImage = Scalar::all(0);
	//【2】设置鼠标回调函数
	setMouseCallback(WINDOW_NAME1, on_Mouse, 0);
	//【3】轮询按键,进行处理
	while (1)
	{
		//获取键值
		int c = waitKey(0);
		//若按键键值为ESC时,退出
		if ((char)c == 27)
			break;
		//按键键值为2时,恢复源图
		if ((char)c == '2')
		{
			g_maskImage = Scalar::all(0);
			srcImage.copyTo(g_srcImage);
			imshow("image", g_srcImage);
		}
		//若检测到按键值为1或者空格,则进行处理
		if ((char)c == '1' || (char)c == ' ')
		{
			//定义一些参数
			int i, j, compCount = 0;
			vector > contours;
			vector hierarchy;
			//寻找轮廓
			findContours(g_maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
			//轮廓为空时的处理
			if (contours.empty())
				continue;
			//拷贝掩膜
			Mat maskImage(g_maskImage.size(), CV_32S);
			maskImage = Scalar::all(0);
			//循环绘制出轮廓
			for (int index = 0; index >= 0; index = hierarchy[index][0], compCount++)
				drawContours(maskImage, contours, index, Scalar::all(compCount + 1), -1, 8, hierarchy, INT_MAX);
			//compCount为零时的处理
			if (compCount == 0)
				continue;
			//生成随机颜色
			/*vector colorTab;
			for (i = 0; i < compCount; i++)
			{
				int b = theRNG().uniform(0, 255);
				int g = theRNG().uniform(0, 255);
				int r = theRNG().uniform(0, 255);
				colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
			}*/
			//计算处理时间并输出到窗口中
			double dTime = (double)getTickCount();
			watershed(srcImage, maskImage);
			dTime = (double)getTickCount() - dTime;
			printf("\t处理时间 = %gms\n", dTime*1000. / getTickFrequency());
			//双层循环,将分水岭图像遍历存入watershedImage中
			Mat watershedImage(maskImage.size(), CV_8UC3);
			int index1 = 0;
			for (i = 0; i < maskImage.rows; i++)
				for (j = 0; j < maskImage.cols; j++)
				{
					if(maskImage.at(i, j)>index1)
					index1 = maskImage.at(i, j);
				}
			for (i = 0; i < maskImage.rows; i++)
				for (j = 0; j < maskImage.cols; j++)
				{
					int index = maskImage.at(i, j);
					//对watershed函数生成的index的规律不是很清楚,经测试,并不是按照标记顺序给出index的
					//具体每一块的index是怎么给出的还需要研究源码
					if (index == -1)
						watershedImage.at(i, j) = Vec3b(255, 255, 255);
					else if (index <= 0 || index > compCount)
						watershedImage.at(i, j) = Vec3b(0, 0, 0);
					else if (index ==index1)
						watershedImage.at(i, j) = Vec3b(255, 255, 255);
					else
						watershedImage.at(i, j) = Vec3b(index*10, 0, 0);//这里想给不同的物体标记为不同程度的颜色
																				//方便后面去除背景,显示目标物体
				}
			//混合灰度图和分水岭效果图并显示最终的窗口
			//watershedImage = watershedImage*0.5 + grayImage*0.5;
			imshow(WINDOW_NAME2, watershedImage);//直接显示分水岭的效果图
			//这里想直接根据index,将背景显示为黑色,需要分割出来的目标物体直接显示
			//但对index生成的规律还未搞清楚,结果可能不是很稳定
			Mat src = imread("D:\\pic-sam\\哀.JPG", 1);
			for (int i = 0; i < src.rows; i++)
				for (int j = 0; j < src.cols; j++)
				{
					int a = abs(watershedImage.at(i, j)[0] - 250) / 150;
					src.at(i, j)[0] *= a;
					src.at(i, j)[1] *= a;
					src.at(i, j)[2] *= a;
				}
			namedWindow("dst", WINDOW_NORMAL);
			imshow("dst", src);
		}
	}	
	return 0;
}
//鼠标消息回调函数  
static void on_Mouse(int event, int x, int y, int flags, void*)
{
	//处理鼠标不在窗口中的情况  
	if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows)
		return;
	//处理鼠标左键相关消息  
	if (event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))
		prevPt = Point(-1, -1);
	else if (event == CV_EVENT_LBUTTONDOWN)
		prevPt = Point(x, y);
	//鼠标左键按下并移动,绘制出线条  
	else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
	{
		Point pt(x, y);
		if (prevPt.x < 0)
			prevPt = pt;
		line(g_maskImage, prevPt, pt, Scalar::all(255), 4, 8, 0);
		line(g_srcImage, prevPt, pt, Scalar::all(255), 4, 8, 0);
		prevPt = pt;
		imshow(WINDOW_NAME1, g_srcImage);
	}
}
//      描述:输出一些帮助信息    
static void ShowHelpText()
{
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION);
	printf("\n\n  ----------------------------------------------------------------------------\n");
	//输出一些帮助信息    
	printf("\n\n\n\t欢迎来到【分水岭算法】示例程序~\n\n");
	printf("\t请先用鼠标在图片窗口中标记出大致的区域,\n\n\t然后再按键【1】或者【SPACE】启动算法。"
		"\n\n\t按键操作说明: \n\n"
		"\t\t键盘按键【1】或者【SPACE】- 运行的分水岭分割算法\n"
		"\t\t键盘按键【2】- 恢复原始图片\n"
		"\t\t键盘按键【ESC】- 退出程序\n\n\n");
}

源图像:

分水岭算法

要标记的图像:

分水岭算法

通过分水岭算法获得的图像:

分水岭算法

分割后的图像:

分水岭算法

代码的108-122行用于分析由opencv分水岭算法生成的结果图。目前,由分水岭函数生成的指标的规律还不是很清楚。测试后,索引未按标签顺序给出。如何给一个索引还需要研究源代码

代码的130-138行,目的是根据分水岭算法生成的图像中的索引直接将背景显示为黑色,并直接显示需要分割的目标对象,但是规则索引生成的过程尚未阐明。结果可能不是很稳定

参考以上部分:毛星云“ OpenCV3编程简介”

--------------------------------------------------- ------

于2019年4月19日添加:

请参考opencv分水岭算法,“循环绘制轮廓”时使用参数compCount。该参数不用于记录轮廓数量。它的功能是将每个轮廓设置为相同的像素值,而在maskImage中,像素值标记为1-compcount的像素值,因此问题转化为不知道算法找到轮廓的顺序并将其放入寻找轮廓时的向量。