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

来源:互联网 发布:数据库冷备份怎么恢复 编辑:程序博客网 时间:2024/05/22 17:24

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

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


四邻接          八邻接

 上图所示的'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[200000000] = { 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]!=255){int savemidvalue = valuearray[value];while (true)//将映射表调整{if (valuearray[savemidvalue] > minimun){int mid = valuearray[savemidvalue];valuearray[savemidvalue] = minimun;savemidvalue = mid;}else{break;}}valuearray[value] = minimun;}value = minimun;}}void compare(int &value, int &minimun)//比较大小{if (value != 0 && value!=255){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("test.bmp", 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 (IntImage.at<int>(Y, X) == 255 && //本元素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++;if (contourmark==255)//防止冲突{valuearray[contourmark] = 255;contourmark = 256;}}else//已经属于某一个连通域{int getminimum = 200000000;//取得上下左右最小的标志位compare(IntImage.at<int>(Y, X), getminimum);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++)//将同一个连通域的对象映射表调整好,做完这一步映射表制作完成{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 (j==255)//跳过无意义值{continue;}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);imwrite("a.bmp", colorLabelImg);imshow("colorLabelImg", colorLabelImg);waitKey(0);}

    处理的结果如下:


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



    




1 0
原创粉丝点击