压板识别项目分析
1.图片为保护压板正常方式投入,要求图像识别能正确判断,并将保护压板投退状态显示出来。
2.保护压板漏投(误投),要求图像识别能发现报警,并显示出是哪些保护压板漏投。
3.保护压板有投退异常现象,要求图像识别能发现报警,并显示出是哪些保护压板异常。
4.通过保护盘二维码(或条形码),要能识别出来保护盘柜名称
(正常情况下的图片)
(存在问题的图片1)
(存在问题的图片2)
二、初步分析:
其实需求主要是两个方面,一个是识别出压板的情况,二个是识别条码。
2.1识别压板:
常见的图像初步处理(换色彩域->OSTU->投影分析),对于初始的三幅图像,都能够得到比较干净的结果。但是这种情况过于理想了,在实
际项目中,需要考虑到由于其他原因引起的噪音,可能需要采用一定的方法去除噪音。此外,第5和第9个开关,采用的是和背景颜色比
较接近的压板,在实际项目中容易混乱,但是这个结果是原始需求直接关注的结果,可能要采用其他方法来强化 。
//经过ostu运算,得到质量比较好的图像 threshold(matSplit[ 1],ostu, 100, 255,THRESH_OTSU); dstclone = ostu.clone(); // 做竖直的投影。这里由于更关心截断的情况,而不关心具体升降,所以是画出截断线而不是画波形 for ( int i = 0;i <ostu.cols;i ++) { Mat data = ostu.col(i); int itmp = countNonZero(data); vectorV.push_back(itmp); } //上波形为VUpper,下波形为VDown for ( int i = 1;i <vectorV.size();i ++) { if (vectorV[i - 1] == 0 && vectorV[i] > 0) { VUpper.push_back(i); } if (vectorV[i - 1] > 0 && vectorV[i] == 0) { VDown.push_back(i); } } //计算结果 for ( int i = 0;i <VUpper.size();i ++) { Mat roitmp = ostu(Rect(VUpper[i], 0,VDown[i] -VUpper[i],ostu.rows)); dilate(roitmp,roitmp,Mat()); //对ostu的结果适当膨胀 int uppertimes = 0; for ( int j = 0;j <ostu.rows;j ++) { Mat data = roitmp.row(j); int itmp = countNonZero(data); vectorH.push_back(itmp); } for ( int j = 0;j <vectorH.size() - 1;j ++) { if (vectorH[j] > 0 && vectorH[j + 1] == 0) { HDower.push_back(j); } if (vectorH[j] == 0 && vectorH[j + 1] > 0) { HUpper.push_back(j); } } if (HUpper.size() < = 1) { result[i] = 1; //printf("结果为连在一起的\n"); } else { int iresult = 0; for ( int j = 0;j <HDower.size() - 1;j ++) { //得出之间空白的区域 int iwidth = HUpper[j + 1] - HDower[j]; if (iwidth > 10) { iresult = iresult + 1; } } if (iresult > 0 ) { result[i] = 0; // printf("结果为断开的\n"); } else { result[i] = 1; //printf("结果为连在一起的\n"); } iresult = 0; } vectorH.clear(); HUpper.clear(); HDower.clear(); uppertimes = 0; }
Mat sobel; Mat canny; Mat canny_output; int imax = 0 ; int imaxcontour = - 1 ; std : : vector < std : : vector < cv : : Point >> contours; Mat cannyClone = Mat : : zeros(Size(gray.cols,gray.rows),gray.type()); Canny(gray,canny, 100 , 255 ); Mat element = getStructuringElement(MORPH_ELLIPSE,Size( 7 , 3 )); morphologyEx(canny,canny,CV_MOP_DILATE,element); morphologyEx(canny,canny,CV_MOP_ERODE ,element); findContours(canny,contours,CV_RETR_TREE,CV_CHAIN_APPROX_NONE); for ( int i = 0 ;i < contours.size();i ++ ) { int itmp = contourArea(contours[i]); if (imaxcontour < itmp ) { imax = i; imaxcontour = itmp; } } //找到轮廓的处理 Rect boundRect; //最小外接矩形 drawContours(cannyClone,contours,imax,Scalar( 255 ), - 1 ); boundRect = boundingRect(Mat(contours[imax])); Mat srcRoi = src(boundRect); imwrite( "barcode.jpg" ,srcRoi);
三、难点攻关:
3.1第5和第9压板的特殊情况。
由于第5和第9压板的颜色和背景颜色非常接近,所以采用特殊的方法来进行处理。通过观察,结合常理。压板打开之后,必然带来的结果就
是下垂并且将下面的字符牌遮挡。那么可以通过反过来判断字符牌是否被遮盖来判断压板是否被打了下来。
(字符遮挡)
/将第5和第9条单独取出来 Mat roi05 = src(Rect( 6 * VUpper[ 4 ], 0 , 6 * (VDown[ 4 ] - VUpper[ 4 ]),src.rows)); Mat roi09 = src(Rect( 6 * VUpper[ 8 ], 0 , 6 * (VDown[ 8 ] - VUpper[ 8 ]),src.rows)); cvtColor(roi05,roi05,CV_BGR2GRAY); cvtColor(roi09,roi09,CV_BGR2GRAY); threshold(roi05,roi05, 100 , 255 ,THRESH_OTSU); threshold(roi09,roi09, 100 , 255 ,THRESH_OTSU); threshold(roi05,roi05, 0 , 255 ,THRESH_BINARY_INV); threshold(roi09,roi09, 0 , 255 ,THRESH_BINARY_INV); std : : vector < std : : vector < cv : : Point >> contours2; findContours(roi05,contours2,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE); int imax2 = 0 ; int imaxcontour2 = - 1 ; for ( int i = 0 ;i < contours2.size();i ++ ) { int itmp = contourArea(contours2[i]); if (imaxcontour2 < itmp ) { imax2 = i; imaxcontour2 = itmp; } } Rect boundRect2; //最小外接矩形 boundRect2 = boundingRect(Mat(contours2[imax2])); if (boundRect2.y + boundRect2.height > 750 ) { //printf("第5个为打开的\n"); result[ 4 ] = 0 ; } else { //printf("第5个为关闭的\n"); result[ 4 ] = 1 ; } contours2.clear(); findContours(roi09,contours2,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE); imax2 = 0 ; imaxcontour2 = - 1 ; for ( int i = 0 ;i < contours2.size();i ++ ) { int itmp = contourArea(contours2[i]); if (imaxcontour2 < itmp ) { imax2 = i; imaxcontour2 = itmp; } } boundRect2 = boundingRect(Mat(contours2[imax2])); if (boundRect2.y + boundRect2.height > 750 ) { //printf("第9个为打开的\n"); result[ 8 ] = 0 ; } else { //printf("第9个为关闭的\n"); result[ 8 ] = 1 ; }
3.2倾斜情况下,条码识别错误
受到摄像头分辨率的限制,使得图像中条形码在倾斜的时候,受到插值算法的影响,边缘变得模糊。虽然尝试了许多方法,但是都无法达到
能够让zxing识别的程度。这个问题不知道哪位有更好的方法,如果可以的话,希望能够告之。
四、系统集成:
由于目前还没有很好地将zxing集成到mfc的环境中来。由于我对“csharp通过dll方式调用console程序”比较熟悉,所以这里尝试采用的是"csharp通过程序调用console的形式"。也就是主要图像处理的部分还是写的console程序,并且运算出相应的结果和图片,而后在csharp的程序中合并得到最后的结果。过程中发现这种方法的问题还是比较多的,包括参数的传递、程序重复运行时的控制等,应该说不是一种很成熟的方法,在以后面对类似的问题的时候,最好是能够直接将代码集成到mfc中,否则就要采用“csharp通过dll方式调用console程序”的方式。
最后的结果如下,并且可以多次测试都没有问题:
五、设计小结:
工作完成了,那么除了对代码进行重构并且提取出可以被重复使用的函数外,对于思路的小结也非常重要。在本例中:
5.1 提出了具有创造性的一个想法:采取分析下面的字符牌是否被遮挡的方式来判断开关闭合情况。逆向思维取得了稳定的结果;
5.2 对于色彩空间转换、对于投影的灵活运用构成了识别的主体。
不足的地方
5.3 对于一维/二维码识别没有构建稳定的库或解决方案,现在使用的zxing可以解决一部分问题,但是不过不了解原理,遇到不能解决的问题就无法继续优化;目前提取条码的方法应该被提出出来。
5.4 验证了"csharp通过程序调用console的形式"是不合算的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!