连通域标记——实现硬币自动计件

前言

在自动计算图像中有几枚硬币的任务中,分离出前景和背景后是否就可以马上实现自动计件,如果可以,如何实现?如果不可以,为什么?
答案是否定的。二值化之后我们的得到的只是前景总像素的多少,并不知道哪些像素属于同一枚硬币。想要实现自动计件功能还需要用到连通域标记的知识。
连通域标记的方法这里我们使用种子填充法:
方法

算法步骤:

1、遍历一幅图像。
2、如果遇到前景且该点未被标记,说明在该点附近可能存在与该点相连通的像素点,即可能存在连通域,停止遍历。否则继续遍历。
3、以该点为seed点,遍历seed点4邻域或者8邻域。如果同为前景,将坐标存到一个栈中,并将这点贴上label,表示已经访问过该像素,避免重复访问。
4、将栈中的坐标取出,以该点为seed点,重复2操作。
5、直到栈中的所有元素都取出,说明已经遍历完了该label的所有元素。
6、label++;从一开始停止遍历的点继续遍历。
7、重复2-6直到遍历到最后一个像素

代码实现:

*--------------------------【练习】连通域标记-------------------------------------*//*参数说明:
src_img:输入图像 
flag_img:作为标记的空间(在函数内部设置为单通道)
draw_img:作为输出的图像,不同的连通域的颜色不同
iFlag:作为判断属于连通域的像素目标值,一般来说我们是对二值图进行连通域分析,所以这个值为0或者255,物体是0/1,则iFlag是0/1
type:		type==4 :用4邻域			type==8 :用8邻域
nums:	设定的label像素个数截断值,被标记的连通域像素个数必须大于nums才算是正确的连通域。用来防止二值化后的效果并不好的情况。
*/
void seed_Connected_Component_labeling(Mat& src_img,Mat& flag_img,Mat& draw_img, int iFlag,int type, int nums)
{int img_row = src_img.rows;int img_col = src_img.cols;flag_img = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC1);//标志矩阵,为0则当前像素点未访问过draw_img = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC3);//绘图矩阵Point cdd[111000];                  //栈的大小可根据实际图像大小来设置long int cddi = 0;int next_label = 1;    //连通域标签int tflag = iFlag;long int nums_of_everylabel[100] = { 0 };	//存放每个区域的像素个数//Mat(纵坐标,横坐标)//Point(横坐标,纵坐标)for (int j = 0; j < img_row; j++)			//height{for (int i = 0; i < img_col; i++)		//width{//一行一行来if ((src_img).at<uchar>(j, i) == tflag && (flag_img).at<uchar>(j, i) == 0)   //满足条件且未被访问过{//将该像素坐标压入栈中cdd[cddi] = Point(i, j);cddi++;//将该像素标记(flag_img).at<uchar>(j, i) = next_label;//将栈中元素取出处理while (cddi != 0){Point tmp = cdd[cddi - 1];cddi--;//对4邻域进行标记if (type == 4){Point p[4];//邻域像素点,这里用的四邻域p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左p[1] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右p[2] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上p[3] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//顺时针//p[0] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上//p[1] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右//p[2] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//p[3] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左//逆时针//p[3] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上//p[2] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右//p[1] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左for (int m = 0; m < 4; m++){if ((src_img).at<uchar>(p[m].y, p[m].x) == tflag && (flag_img).at<uchar>(p[m].y, p[m].x) == 0) //满足条件且未被访问过{//将该像素坐标压入栈中cdd[cddi] = p[m];cddi++;//将该像素标记(flag_img).at<uchar>(p[m].y, p[m].x) = next_label;}}}//对8邻域进行标记else if (type == 8){Point p[8];//邻域像素点,这里用的四邻域p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y - 1 > 0 ? tmp.y - 1 : 0);		//左上p[1] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上p[2] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1,tmp.y - 1 > 0 ? tmp.y - 1 : 0);		//右上p[3] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左p[4] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右p[5] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//左下p[6] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下p[7] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//右下for (int m = 0; m < 7; m++){if ((src_img).at<uchar>(p[m].y, p[m].x) == tflag && (flag_img).at<uchar>(p[m].y, p[m].x) == 0) //满足条件且未被访问过{//将该像素坐标压入栈中cdd[cddi] = p[m];cddi++;//将该像素标记(flag_img).at<uchar>(p[m].y, p[m].x) = next_label;}}}}next_label++;}}}next_label = next_label - 1;int all_labels = next_label;std::cout << "labels : " << next_label <<std::endl;//给不同连通域的涂色并且记录下每个连通域的像素个数for (int j = 0;j < img_row;j++)	//行循环{for (int i = 0;i < img_col;i++)	//列循环{int now_label = (flag_img).at<uchar>(j, i);		//当前像素的labelnums_of_everylabel[now_label]++; float scale = now_label * 1.0f / all_labels;//-------【开始处理每个像素】---------------draw_img.at<Vec3b>(j, i)[0] = 255 - 255 * scale;		//B通道draw_img.at<Vec3b>(j, i)[1] = 128 - 128 * scale;		//G通道draw_img.at<Vec3b>(j, i)[2] = 255 * scale;		//R通道//-------【处理结束】---------------}}std::cout << "初步结论 : " << std::endl;for (int i = 1;i <= next_label;i++){std::cout << "labels : " << i<<"像素个数   " << nums_of_everylabel[i] <<std::endl;}std::cout << "最后结论 : " << std::endl;std::cout << "截断像素数目 : " << nums << std::endl;for (int i = 1;i <= next_label;i++){if (nums_of_everylabel[i] <= nums){all_labels--;}}std::cout << "labels : " << all_labels << std::endl;}int main()
{Mat flag_img;Mat draw_img;Mat srcImage = imread("D:\\opencv_picture_test\\阈值处理\\硬币.png", 0);	//读入的时候转化为灰度图//Mat srcImage = imread("D:\\opencv_picture_test\\阈值处理\\黑白.jpg", 0);	//读入的时候转化为灰度图namedWindow("原始图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("原始图", srcImage);cout << "srcImage.rows : " << srcImage.rows << endl;		//308cout << "srcImage.cols : " << srcImage.cols << endl;		//372Mat dstImage;dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);//阈值处理+二值化My_artificial(&srcImage, &dstImage, 84);//	flag_img = cv::Mat::zeros(src.size(), src.type());//cvtColor(src, src, COLOR_RGB2GRAY);    //这一句很重要,必须保证输入的是单通道的图,否则所读取的数据是错误的double time0 = static_cast<double>(getTickCount());	//记录起始时间seed_Connected_Component_labeling(dstImage,flag_img,draw_img,255,4,500);		//白色部分被标记time0 = ((double)getTickCount() - time0) / getTickFrequency();cout << "此方法运行时间为:" << time0 << "秒" << endl;	//输出运行时间imshow("dstImage", dstImage);namedWindow("draw_img", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("draw_img", draw_img);waitKey(0);return 0;
}

实现效果:

原图:
1
二值图(可以看到有几个噪点,而且图像的右边和上边是白色的,这是因为原图我是截图的,边界并没有剪裁好,这点在下面的连通域标记会有影响)
2
我给属于不同连通域的物体涂上不同的颜色。
3
下面是打印出来的信息:初步得到的label是19个,其中label1就是我所说的截图边界问题。其他的几个像素个数小的就是噪点。
通过设定门限,像素个数小于500的标签物体我们将它视为噪声。最后得到的label数目正好是10,也就是硬币的数目。
4

发现的问题

连通域标记函数代码部分,可以看到我还尝试了其他两种遍历seed周围元素的方式,分别是顺时针和逆时针。但是运算速度没有第一种快,至于原因我没有深究。希望有心人能给我讲解一波。此外,试了一下8邻域,运算速度也得到了下降。
代码
这就是我说的剪裁错误,嘿嘿。剪裁错误
此外,二值化的方法我是用的人工调整,原图受到非均匀光线的照射,全局大津阈值得到的效果并不是很好,反而由于直方图双峰性比较明显,迭代法看起来还不错。不过为了连通域标记的时候能够准确一点,我就用滑条调整阈值了。
滑动条调整阈值的代码在这儿:https://blog.csdn.net/qq_42604176/article/details/104764731
迭代法、大津的代码在这儿:https://blog.csdn.net/qq_42604176/article/details/104341126

3.15更新,加入形态学腐蚀操作

首先回顾之前遇到的问题:受到噪声影响,十个硬币竟然贴了19个labels,尽管利用限制像素个数的方法来限制,但这种方法有许多弊端。
这几天学习了一些简单的形态学操作,其中腐蚀操作有个作用:去除黏连像素以及噪声
这不就正好能解决之前遇到的问题嘛!
操作也很简单,加上两行代码就行。
代码
结果运行如下(把自己简陋的限制像素函数去掉了)
效果
效果很好啊!
效果
关于腐蚀的详细讲解请看这边:
https://blog.csdn.net/qq_42604176/article/details/104815801

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Storm资料汇总

一、Storm集群安装部署 网上关于storm集群部署都大同小异。 Storm下载地址&#xff1a;http://storm-project.net Storm项目地址&#xff1a;https://github.com/nathanmarz/storm 目前的版本不支持ZooKeeper3.4.5版本&#xff0c;而支持ZooKeeper3.3.3版本。 我当时没注意这…

getlong_Java即时类| 带示例的getLong()方法

getlong即时类getLong()方法 (Instant Class getLong() method) getLong() method is available in java.time package. getLong()方法在java.time包中可用。 getLong() method is used to get the value as long for the given temporal field from this Instant. getLong()方…

python作品阐述_Python网络编程基础的作品鉴赏-

Python网络编程基础的作品鉴赏Python网络编佳入门图书&#xff01;175个完整&#xff0c;实践出真知&#xff0c;SocketsDNS、Web Service、FTP、Email、SMTP、POP、IMAP、SocketServe、CGI、XML、Forking、数据库客户端、多线程、异步通信……完整涵盖网络编程的方方面面“这可…

09-梯度运算

梯度运算膨胀操作-腐蚀操作&#xff0c;这里的-操作是图像的减法&#xff0c;可不是简单的加减乘除运算 为了更加形象生动&#xff0c;先将膨胀和腐蚀操作的结果进行合并展示&#xff0c;然后再与梯度运算进行比较 cv2.morphologyEx(pie,cv2.MORPH_GRADIENT,kernel) 第一个参数…

JavaScript学习笔记-我的浅显认识

一&#xff1a;什么是javascript? 它是一种基于对象和事件驱动的解释性的安全的与平台无关的免费的浏览器脚本语言。 二&#xff1a;有什么用途&#xff1f; 使表单的验证放在客户端&#xff0c;更快捷反应&#xff0c;增强网页的交互性。设计一些特效&#xff0c;如菜单&…

典型瀑布模型四个阶段_古典瀑布模型的不同阶段

典型瀑布模型四个阶段The different phases that are included in the classical waterfall model are: 经典瀑布模型包含的不同阶段是&#xff1a; Feasibility study 可行性研究 Requirement analysis and specification 需求分析和规范 Design 设计 Coding and unit testin…

Unity-Shader-渲染队列

Unity-Shader-渲染队列 渲染简介Unity中的几种渲染队列Background (1000)最早被渲染的物体的队列。Geometry (2000) 不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染&#xff0c;也就是Unity Shader中默认的渲染队列。AlphaTest (2450) 有透明通道&#xff0c;需要进…

MyBatis ResultMap(2)

SQL 映射XML 文件是所有sql语句放置的地方。需要定义一个workspace&#xff0c;一般定义为对应的接口类的路径。写好SQL语句映射文件后&#xff0c;需要在MyBAtis配置文件mappers标签中引用&#xff0c;例如&#xff1a; Xml代码 <mappers> <mapper resource&qu…

形态学操作——腐蚀与膨胀

预备知识 结构元&#xff08;SE&#xff09; 1、结构元的中心一般来说是放在其重心位置处&#xff0c;但原则上原点的选择是依赖于你要解决的问题的。 2、对图像操作时&#xff0c;我们要求结构元是矩形阵列。&#xff08;在结构元的基础上添加较少的背景元素实现&#xff09…

java集合转换_java各种集合的转换

内容&#xff1a;1、List转Array 2、Array转List3、String转int[],String[](对单个字符) 4、数组、List、Set、Map相互转换5、一行输入多个元素方法常用集合&#xff1a;Map、Set、List、Array、String1、List转Array&#xff…

10-礼帽与黑帽操作

cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel) 第一个参数&#xff1a;图像对象名称 第二个参数&#xff1a;运算类型TOPHAT为礼帽运算 第三个参数&#xff1a;卷积核的大小 礼帽运算&#xff1a;原始的输入-开运算&#xff08;先腐蚀再膨胀&#xff09; 原始带刺儿&#xff…

2011 cnblogs开通--给力吧

在信息传递如此迅速的时代&#xff0c;自己慢慢的老去&#xff0c;是否要想写些什么&#xff0c;跟大家一起分享学习、工作、生活的酸甜苦辣&#xff0c;提高自己的各方面的能力;往后将慢慢记录自己的程序人生吧&#xff0c;就什么多了&#xff0c;作为自己cnblogs第一篇吧^_^转…

Android----获取activity上所有的控件

01/**02 * note 获取该activity所有view03 * author liuh04 * */05 public List<View> getAllChildViews() {06 View view this.getWindow().getDecorView();07 return getAllChildViews(view);08 }09 10 private List<View> …

微信小程序 查找兄弟节点_使用C ++程序在链接列表中查找节点

微信小程序 查找兄弟节点Given a linked list and an integer N, you need to find and return index where N is present in the Linked List. Return -1 if n is not present in the Linked List. 给定一个链表和一个整数N&#xff0c;您需要查找并返回索引&#xff0c;其中链…

形态学操作——开闭运算、顶帽底(黑)帽变换

膨胀和腐蚀运算的问题&#xff1a; 边缘形状发生了变化&#xff0c;膨胀发生了扩张&#xff0c;腐蚀发生了收缩 目标物体变形&#xff0c;对识别时的特征提取会造成影响 解决方法&#xff1a; 开操作: B对A的开操作就是先B对A腐蚀&#xff0c;紧接着用B对结果进行膨胀 先腐…

java 基础实战_Java基础实战(三)

是否是否是否是否获取字符串字符数组大写?小写?数字?非字母与数字大写字母小写字母数字i结束ii1第一步 拆分字符串为字符数组&#xff1a;static void count(String str) {// 将字符串拆分为字符数组char[] charArray str.toCharArray();}第二步 定义相关变量记录结果&…

11-图像梯度-Sobel算子

图像梯度是指图像某像素在x和y两个方向上的变化率&#xff08;与相邻像素比较&#xff09;&#xff0c;是一个二维向量&#xff0c;由2个分量组成&#xff0c;X轴的变化、Y轴的变化 。 其中X轴的变化是指当前像素右侧&#xff08;X加1&#xff09;的像素值减去当前像素左侧&…

给IE有效指定编码

<title>下一站</title> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> IE每次打开&#xff0c;均是一片空白&#xff0c;查看右键&#xff0d;编码&#xff0c;显示是GB2312。要手功改为UTF-8后才能正常显示页面…

形态学操作——击中击不中变换

操作目的 HitMiss变换是形态检测的一个工具&#xff0c;通过定义形状模板可以在图像中获取同一形状物体的位置坐标。 算法讲解 1、用击中结构去腐蚀原始图像得到击中结果X&#xff08;这个过程可以理解为在原始图像中寻找和击中结构完全匹配的模块&#xff0c;匹配上了之后&…

stack.pop()方法_C.示例中的Stack.Pop()方法

stack.pop()方法C&#xff03;Stack.Pop()方法 (C# Stack.Pop() method) Stack.Pop() method is used to remove an object from the top of the stack. The method removes and returns the object from the top. Stack.Pop()方法用于从堆栈顶部删除对象。 该方法从顶部删除并…