连通域标记算法(二) 基于深度优先搜索的连通域标记算法(opencv C++实现)

        上一篇我们讲到了MATLAB中的bwlabel连通域标记算法的C++实现https://blog.csdn.net/Dhane/article/details/81633723,今天我来讲一讲另一种相对比较容易想到的连通域标记算法。简单点说就是每次以一个需要标记的像素点为种子,然后不断向其周围扩散,找出其他的与其相连通的可标记的像素点,这样就能标记出一个连通域,然后再以另一个连通域中的某一个需要标记的点为种子点,如此不断循环,直到把整幅图都遍历一遍,所有连通域也就都标记出来了。

        可能因为我的语言表达能力有限,所以上面的简单描述一些同学还是没能够太明白,不要着急,下面我们再来把这个过程分析一遍。

        还是先上一张老图,这样说起来更清晰一点:

连通域标记算法(二) 基于深度优先搜索的连通域标记算法(opencv C++实现)_第1张图片

        

上图中,共有1,2,3三个连通成分,我们需要找出这张图中的所有连通成分,那么至少需要把图像中的每一个像素都遍历一遍,并且遍历的时候还有判定该像素点的值是否在集合V中。我们可以大概分为以下几步来实现:

  1. 从左到右,从上到下遍历每一个像素点,然后判断该像素的值是否在集合V中(这里是一张二值图,我们假设黑色的值为0)。
  2. 如果遇到一个值为0的点(在上图中为第2行第4个像素点p[1][3]),说明在该点附近可能存在与该点相连通的像素点,即可能存在连通域。那么我们就暂时停止之前的遍历,开始以该点为种子点,查找该点附近的邻域中是否存在与其连通的像素点,若与该点连通,则将其存到一个堆栈中,并将该点的标签赋值为与种子点p[1][3]相同的标签,并对访问过的像素点置一个表示已访问的标志,以避免后面对其重复访问。若以上图为例,查看p[1][3]的四邻域(p[1][2]、p[0][3]、p[1][4]、p[2][3])的值是否也为0,很明显,只有p[2][3]的值为0,说明p[1][3]和p[2][3]在同一个连通成分中,把p[2][3]的位置push到一个堆栈cdd中。
  3. 那么我们接下来就从堆栈cdd中取出点p[2][3],然后查看该点的四邻域,发现p[2][4]和p[3][3]都与p[2][3]相连通,则依次把这两个点push到堆栈cdd中。
  4. 下一次继续从堆栈中取出点,并遍历该点的四邻域,如此循环,直到堆栈变为空,则说明已经遍历完了所有的与我们的初始点p[1][3]相连通的连通成分。标签label加1,以便对后面的连通成分标签和该连通成分标签加以区分。
  5. 接下来就可以继续之前的从左到右、从上到下的遍历工作了,我们开始从点p[1][4]开始访问后面的点,直到访问到p[2][3]时,这个点我们之前访问过,其访问标志为1,所以不再进行对其访问,直接跳过,其他之前访问过的也同理。
  6. 当访问到一个新的在集合V内的像素点时,则又以该点为种子点进行四邻域的遍历循环,并标记上相应的标签和访问标志,直到标记完整个连通成分,则标签加1,继续后面的遍历。如此循环往复。直到访问完最后一个像素点,我们的连通成分也标记完了。

        这种方法当时也是我想出来的,为了完成任务嘛,就拿张纸在图上乱画,当时也没学什么算法,后来在学习一些算法的时候发现,这应该是属于经典的深度优先搜索算法。又自恋了一下下,哈哈哈哈,每次发现自己所想的算法和一些前辈们想出的算法雷同的时候都特别兴奋。我就想今后是不是我也能够研究出一些新的经典算法,哈哈哈。

        好了,不自恋了。下面简单说一下我的代码实现。我是用一个数组来存储所嗅探到的可标记像素点的,相当于一个堆栈吧,然后再不断从堆栈中拿出来作为新的种子点。不多说了,直接上代码吧。


//Based on opencv
//Created by HeQiang on 2018/03/03 in Wuhan 
//

#include 
#include 
#include 
#include 

void getConnectedDomain2(cv::Mat& src_img, cv::Mat &flag_img, int iFlag)//
{
	int img_row = src_img.rows;
	int img_col = src_img.cols;
	int postemp1, postemp2;
	flag_img = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC1);//标志矩阵,为0则当前像素点未访问过
	uchar *ptrsrc = src_img.data;
	uchar *ptrflag = flag_img.data;
	cv::Point2f cdd[80000];                  //大小可根据实际图像大小来设置
	long int cddi = 0;
	int next_label = 1;    //连通域标签
	int tflag;
	if (iFlag == 0)
		tflag = 0;
	else
		tflag = 255;       //需标记的像素点所满足的条件,可修改
	for (int i = 0; i < img_row; i++)
	{
		for (int j = 0; j < img_col; j++)
		{
			postemp1 = i*img_col + j;
			if (ptrsrc[postemp1] == tflag && ptrflag[postemp1] == 0)   //满足条件且未被访问过
			{
				cdd[cddi++] = cv::Point2f(j, i);
				ptrflag[postemp1] = next_label;
				while (cddi != 0)
				{
					cv::Point2f tmp = cdd[cddi - 1];
					cddi--;
					cv::Point2f p[4];//邻域像素点,这里用的四邻域
					p[0] = cv::Point2f(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);
					p[1] = cv::Point2f(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_row - 1, tmp.y);
					p[2] = cv::Point2f(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);
					p[3] = cv::Point2f(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);
					for (int m = 0; m < 4; m++)
					{
						postemp2 = p[m].y*img_col + p[m].x;
						if (ptrsrc[postemp2] == tflag && ptrflag[postemp2] == 0)
						{
							cdd[cddi++] = p[m];
							ptrflag[postemp2] = next_label;
						}
					}
				}
				next_label++;
			}
		}
	}

	std::cout << "output_img data : " << std::endl;
	for (int i = 0; i < flag_img.rows; i++)
	{
		uchar *opt_img = flag_img.ptr(i);
		for (int j = 0; j < flag_img.cols; j++)
		{
			std::cout << (int)opt_img[j] << " ";
		}
		std::cout << std::endl;
	}
}


int main()
{
	cv::Mat flag_img;
	cv::Mat src = cv::imread("test.png");        //输入图像
	cv::resize(src, src, cv::Size(28, 28));                 //方便观察,这里把图像resize到28*28
//	flag_img = cv::Mat::zeros(src.size(), src.type());
	cv::cvtColor(src, src, CV_BGR2GRAY);    //这一句很重要,必须保证输入的是单通道的图,否则所读取的数据是错误的
	getConnectedDomain2(src,flag_img,1);

	cv::imshow("src", src);
	cv::imshow("flag_img", flag_img);   //如果要观察图像,可以给不同标签附上不同颜色显示

	cv::waitKey();

	return 0;
}

        上面代码用到了opencv来读取图像,实际上如果只对一个矩阵进行处理的话应该也是可行的。深度优先搜索的方法还可以使用递归来实现,当时也写了一个递归实现的函数,由于递归次数过多,传参容易出问题,程序容易崩溃,这里就不把代码放上来了。大家也可以自己实现看一下。上面的代码经过了我的多次验证。如果有什么问题大家可以及时和我联系。

        非常希望能够给大家一些启发,也希望能够互相交流,一起进步。

你可能感兴趣的:(图像处理)