C/C++ 图像处理(6)------图像の连通域查找和分别上色算法研究

    本文参考自这篇文章,由于其贴出来的代码运行效率较低而且不太符合本人的想法和习惯,所以对其进行了算法的重新设计和代码的重写。

 所谓图像的连通域,指的是图像上像素点值相同或者相近的点两两相邻接所组成的一块区域。而对于邻接,有四邻接和八邻接两种,如下:

技术分享技术分享

四邻接          八邻接

 上图所示的‘O‘与‘X’相邻接,本文采取的是4邻接方式。

 在查找图像连通域的时候,一般都需要经过一个二值化的过程,将图像的像素值简化成非此即彼的情况,然后通过遍历图像来查找其中一个值是由几个连通域所组成的。如下图所示:

    技术分享

    该图像经过二值化处理之后,两个个值:黑色->255,白色->0,我们可以通过算法来找出其中黑色是由多少个连通域所组成,并且给各个连通域分别上色。该图也作为本文的示例图。

    连通域搜索算法是本文的核心,本文参照上面文章提到的二次搜索算法,提出了一个可以一次遍历出结果的算法,该算法的流程如下(单看流程可能会感觉很混乱,建议主要看代码,并以此为提供看):

    1.遍历图像,一旦碰到像素值不为0的像素进入第2步

    2.判断该像素是不是属于一个连通域,如果不属于任何已知连通域进入第3(a)步,如果属于一个已知连通域进入第3(b)步

    3(a).将连通域计数加1,并将该计数值赋给此像素点作为连通域的标志,并将该值记录到一个映射关系数组的相应位置,如a[3]=3。回到第1步继续。

    3(b).将该像素值和其上下左右的像素值不为0的像素的值进行比较,找其中的最小值赋值给所有非零的像素作为连通域标志,并调整映射关系数组。回到第1步继续。

    4.经过上面的遍历,映射关系数组记录了修改过的图像的连通域信息,然而该信息比较杂乱,同一个连通域可能分别被几个值所标志,通过遍历该数组可以调整这个情况,将同一个连通域的映射值用其最小值进行标志。

    5.遍历图像结合映射关系数组对连通域值进行调整,经过这一步同一连通域将有相同的像素值

    6.根据相同的像素值对同一连通域进行上色工作

    实现代码如下(为了方便使用了OPENCV,然而并没有用到其很多复杂的功能,只要稍作修改可以用来直接处理图像的RGB数据区):

#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>//OpenCV包含头文件  
#include <opencv2/highgui/highgui.hpp>
#include <vector>//容器头文件 
using namespace std;
using namespace cv;

int valuearray[1000] = {0};//记录连通域数值对应关系

class colorobj
{
public:
	int value;
	Scalar mycolor;
};

vector<colorobj> setcolor;//收集需要上色的灰度对象

bool equal255or0(int &value)//判断元素是否等于255或者0
{
	if (value == 255 || value == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void setvalue(int &value, int &minimun)//设置配对值
{
	if (value != 0)
	{
		if (valuearray[value] > minimun)
		{
			valuearray[value] = minimun;
		}
		
		value = minimun;
	}
}

void compare(int &value, int &minimun)//比较大小
{
	if (value != 0)
	{
		if (minimun>=value)
		{
			minimun = value;
		}
	}
}

Scalar GetRandomColor()//彩色显示
{
	uchar r = 255 * (rand() / (1.0 + RAND_MAX));
	uchar g = 255 * (rand() / (1.0 + RAND_MAX));
	uchar b = 255 * (rand() / (1.0 + RAND_MAX));
	return cv::Scalar(b, g, r);
}

void main()
{
	long time = clock();//记录计算时间
	Mat Image = cv::imread("无标题.png", 0);//读入图像,并将图像灰度化
	threshold(Image, Image, 50, 255, CV_THRESH_BINARY_INV);//二值化图像
	//imshow("a", Image);
	int contourmark = 1; //连通域标志

	Mat IntImage;//图片对象,用以将图像的像素变量从uchar转为int,以防止后面标志位大于255程序无法工作的情况
	Image.convertTo(IntImage, CV_32SC1);//将图像像素变量转为int

	//遍历图像,搜索连通域
	for (int Y = 1; Y < IntImage.rows - 1; Y++)//遍历图像,Y为行,X为列
	for (int X = 1; X < IntImage.cols - 1; X++)
	{
		if (IntImage.at<int>(Y, X) != 0)//记住这里是先行后列
		{
			//如果不属于任何一个连通域
			if (equal255or0(IntImage.at<int>(Y - 1, X)) && //上方元素
				equal255or0(IntImage.at<int>(Y + 1, X)) && //下方元素
				equal255or0(IntImage.at<int>(Y, X - 1)) && //左方元素
				equal255or0(IntImage.at<int>(Y, X + 1)))//右方元素
			{
				valuearray[contourmark] = contourmark;
				IntImage.at<int>(Y, X) = contourmark;
				if (IntImage.at<int>(Y - 1, X) == 255)
				{
					IntImage.at<int>(Y - 1, X) = contourmark;
				}
				if (IntImage.at<int>(Y + 1, X) == 255)
				{
					IntImage.at<int>(Y + 1, X) = contourmark;
				}
				if (IntImage.at<int>(Y, X - 1) == 255)
				{
					IntImage.at<int>(Y, X - 1) = contourmark;
				}
				if (IntImage.at<int>(Y, X + 1) == 255)
				{
					IntImage.at<int>(Y, X + 1) = contourmark;
				}
				contourmark++;
			}
			else//已经属于某一个连通域
			{
				int getminimum = IntImage.at<int>(Y, X);
				//取得上下左右最小的标志位
				compare(IntImage.at<int>(Y - 1, X), getminimum);
				compare(IntImage.at<int>(Y + 1, X), getminimum);
				compare(IntImage.at<int>(Y, X - 1), getminimum);
				compare(IntImage.at<int>(Y, X + 1), getminimum);
				//将最小的标志位赋值给目标
				setvalue(IntImage.at<int>(Y, X), getminimum);
				setvalue(IntImage.at<int>(Y - 1, X), getminimum);
				setvalue(IntImage.at<int>(Y + 1, X), getminimum);
				setvalue(IntImage.at<int>(Y, X - 1), getminimum);
				setvalue(IntImage.at<int>(Y, X + 1), getminimum);
			}
		}
	}

	for (size_t i = 1; i < contourmark; i++)//将同一个连通域的对象映射表调整好,做完这一步映射表制作完成
	{
		//printf("%d %d\n", i, valuearray[i]);
		valuearray[i] = valuearray[valuearray[i]];
	}

	for (int Y = 1; Y < IntImage.rows; Y++)//根据映射表对图像像素进行重新赋值
	for (int X = 1; X < IntImage.cols; X++)
	{
		if (IntImage.at<int>(Y, X) != 0)
		{
			IntImage.at<int>(Y, X) = valuearray[IntImage.at<int>(Y, X)];
		}
	}

	for (int j = 1; j < contourmark; j++)//获得需要上色的对象值
	{
		if (valuearray[j] != j)
		{
			bool dopush = true;
			for (int i = 0; i < setcolor.size(); i++)
			{
				if (setcolor[i].value == valuearray[j])
				{
					dopush = false;
					break;
				}
			}
			if (dopush == true)
			{
				colorobj mycolorobj;
				mycolorobj.value = valuearray[j];
				mycolorobj.mycolor = GetRandomColor();
				setcolor.push_back(mycolorobj);
			}
		}
	}

	//彩色显示
    Mat colorLabelImg;
	colorLabelImg.create(IntImage.rows, IntImage.cols, CV_8UC3);
	colorLabelImg = Scalar::all(255);//初始化,将待显示图片的背景设置为白色

	for (int Y = 0; Y < IntImage.rows; Y++)
	for (int X = 0; X < IntImage.cols; X++)
	{
		if (IntImage.at<int>(Y, X) != 0)
		{
			for (int i = 0; i < setcolor.size(); i++)
			{
				if (IntImage.at<int>(Y, X) == setcolor[i].value)
				{
					colorLabelImg.at<Vec3b>(Y, X)[0] = setcolor[i].mycolor[0];
					colorLabelImg.at<Vec3b>(Y, X)[1] = setcolor[i].mycolor[1];
					colorLabelImg.at<Vec3b>(Y, X)[2] = setcolor[i].mycolor[2];
					break;
				}
			}
		}
	}
	printf("标志位值%d\n", contourmark);
	printf("花费时间%dms\n", clock() - time);
	imshow("colorLabelImg", colorLabelImg);
	waitKey(0);
}
    处理的结果如下:

技术分享

    OK,大功告成,转载记得指明出处:原文地址



    




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。