应该是大半年前了,老师带领我做的一个项目主体部分已经完成了,但是投入运行的时候有一个很小的问题,就是需要在图像中知道一个圆形区域的边缘,由于这个圆形区域的半径是非递减随时间变化的而且圆心是固定的.当时解决的办法是找到变化的规律,然后将半径设置成随时间变化即可.不过此方法治标不治本,万一中途实际运行过程中发生了暂停类似时间,而程序还在计时,会有不同步的问题发生.后来希望由程序依托于图像自动找到变化的半径来解决这个问题,不过尽管最终识别的结果还算理想,但是考虑到系统已经投入测试运行了,修改的过程涉及到多次的测试过程,而且发现设置为随时间自动变化的效果还挺不错,就没把这段代码给嵌入进实际运行的系统中了.
半年过去了,当我回顾这段代码的时候,发现了以前写代码的一些问题,同时也希望再了解一下这个算法,下面就从要解决的问题和项目中的代码中窥探过去.
要识别的原图:
原图
黄线为需要识别出来的圆
可能这样的图会比较好识别,但是如果是下面的这几副可能就没那么好识别了:
当时使用过大律法阈值判定,OpenCV的霍夫圆变换但效果都不是我想要的.
而且图中需要识别出来的也并非为完整的圆.
前面也说过了,这个是在测试现场提出来的一个需求,时间比较紧迫.可能我也没花太多时间去找更多的方法来适应这个问题.
好,下面是具体问题:
已知:
求解:
我的思路:
寻找灰度值变化率大于某个阈值次数最多的那个半径.
我认为不足之处在于最后的那个阈值的选取,这个阶段需要人工的提前通过测试样本选取出一个合理的值.
不过意识到这个问题后,我选取了满足条件次数最多的十条半径作为参考,因为实际过程中半径是非递减的而且不会发生突变,所以可以根据上一个获取的结果来从十个参考结果中选取一个较为合理的半径作为这次识别的最终结果.
(图一)
圆上的点到圆心的连线,白色是为了显示效果,实际为图像坐标对应的像素
(图二)
选取十个变化率最大的可参考半径
(图三)
选取变化率最大的那个半径
嗯,看起来目前问题得到了很好的解决.实事也的确如此,我测试了一个大概有3k帧的图像,效果还是很不错的.
(图四)
几个预计比较难识别的图像
而且分析的时间满足肉眼对实时处理的要求(小于0.05s/帧,大于20FPS).
但是,当我打开了含有源代码的文件时,关于这个项目的介绍只有...
我很庆幸我写了时间,要不是工程文件名取得阔以:) 我真不知道这几个cpp是干嘛的.
但至少从这个项目中我已经开始了模块化的行为,使得我review省了点力.
findInterCircle.scan(frame,scanValue); raduis = findInterCircle.getBestRaduis()
1 void CCFindInterCircle::scan(Mat & _readImage,scanValue_s &_scanValue){ preProcess(_readImage,_scanValue); Process (); basicClear(); 11 12 }
主要的过程在Process()函数中.
这里有一个函数命名习惯不统一的问题.三个函数的首字母大小写应该相同,可能当时觉得这个函数很重要,不大写凸显不出与其他函数的不同.
哈哈.
1 void CCFindInterCircle::Process (){ 2 size_t result = 0; result = calInterWithCircle(); result = calTotalLineCoorInfo(); result = calTotalLineDeri(); result = calReferRaduis(); filterRefeRaduis(); }
这里的功能区分能让阅读者更好的查看所示意的功能,说明当时我的思路还算清晰.
其实当时也是为了更好的排错.典型一个方便自己,幸福他人的举措.
1.生成与圆的交点:
我找increaseAngle的初始化还挺费劲,最后发现它初始化为5.
PI = 3.141926; 2 Point2i interPoint; 3 float origAngle = 0.0; 4 while (origAngle < 360) { interPoint.x = outsideRadius * cos(origAngle * PI/180); 7 interPoint.y = outsideRadius * sin(origAngle * PI/180); 8 interPointVec.push_back(interPoint); 9 10 origAngle += increaseAngle; 11 }
2.获取连线的像素信息
这里的vector使用已经预示着变量命名难以区别的问题了.