如何判断轮廓是否为圆(包含联通区域处理)

    我们已经得到了感兴趣的轮廓,下一步就是要对轮廓进行选择,有一些轮廓是需要——有一些是不需要的,是噪音。通过判断一个轮廓是否为圆,在很多情况下可以帮助我们来做这至关重要的一步。
    简单的情况,比如下图的啤酒瓶缺口检测:
     由于瓶口是有缺陷的,造成最大外轮廓不闭合——这显然和“圆”差距很远,那反过来说,那些差距比较小的轮廓可能就是没有缺陷的。
    再来看比较复杂的情况,我们来“数钢管”。下图是connection效果,我们发现了很多轮廓,但是只有一部分更接近圆——这些可能就是我们需要寻找的目标。
    至关重要的一步就是建立数学模型。2017年左右为解决实际问题,我建立了模型一
    基于圆的定义:  “平面上到定点的距离等于定长的所有点组成的图形叫做圆.定点称为圆心,定长称为半径.”。那么通过判断当前轮廓到一个定点的距离是否为定长,就可以得到当前轮廓是否更像圆。这个定点就可以采用外接圆圆心。这里的度量方式可以是方差。
   
 //根据轮廓点和圆心计算方差 参考代码
float ComputeVariance(std : : vector < cv : : Point > theContour,Point2f theCenter)
{
    int a[ 65535 ],n;
    float aver,s;
    float sum = 0 ,e = 0 ;
    n = theContour.size();
    for ( int i = 0 ;i < n;i ++ )
    {
        a[i] = GetDistance(theContour[i],theCenter);
        sum += a[i];
    }
    aver = sum / n;
    for ( int i = 0 ;i < n;i ++ )
        e += (a[i] - aver) * (a[i] - aver);
    e /= n - 1 ;
    s = sqrt(e);
    return e;
}

 

    模型一使用了3年左右,也解决了很多问题,特别对于简单问题来说,效果是很好的。但是它有一个明显的缺点,就是关于这个结果方差的度量,每次都需要不断试验才能够得出一个较好的值。2020年我遇到了前面的“数钢管”问题,出现了新的困难,比如下图:
当轮廓为长条形的时候(上图红圈),会错误地识别为圆。这种长条型轮廓可以抽象为下图红圈:
    这种情况的方差可能不是很大,虽然直观理解其它,它很不是一个圆。
    经过一段时间思索和寻找,建立模型二:
    基于“圆度”的定义:设平面上一个封闭图形(内部无空洞)的面积为S,它的周长为C,则定义该图形的圆度为:
    
                4 * PI * S
         Afa = ------------
                  C * C

       正圆的afa为1,轮廓的afa值越接近1,轮廓越解决圆
           
  //afa参考代码
         double s  = cv : :contourArea(contours_test[i]); //轮廓面积
         double c  = cv : :arcLength(contours_test[i],  true);  //轮廓周长
         float afa  =  4  * PI *s  / (c *c);  //afa计算
        afa  = abs(afa  -  1);



比较不同afa阈值情况下,对此轮廓筛选结果

afa(越小越好) 结果
0.5  
0.3
0.4
  
    从结果上可以明显看出,afa方法很好地过滤掉了噪音。
    感谢阅读至此,希望有所帮助。!

参考资料:
P.S
connection效果参考代码,来自GOCVHelper,在Github上可以找到,说明文件:

//寻找并绘制出彩色联通区域
vector <VP > connection2(Mat src, Mat & draw) {
    RNG rng( 12345);
    draw  = Mat : :zeros(src.rows, src.cols, CV_8UC3);
    vector <VP >contours;
    findContours(src.clone(), contours, RETR_LIST,CHAIN_APPROX_SIMPLE);
     //由于给大的区域着色会覆盖小的区域,所以首先进行排序操作
     //冒泡排序,由小到大排序
    VP vptmp;
     for ( int i  =  1; i  < contours.size(); i ++) {
         for ( int j  = contours.size()  -  1; j  > = i; j --) {
             if (contourArea(contours[j])  < contourArea(contours[j  -  1]))
            {
                vptmp  = contours[j  -  1];
                contours[j  -  1]  = contours[j];
                contours[j]  = vptmp;
            }
        }
    }
     //打印结果
     for ( int i  = contours.size()  -  1; i  > =  0; i --) {
        Scalar  color  = Scalar(rng.uniform( 0,  255), rng.uniform( 0,  255), rng.uniform( 0,  255));
        drawContours(draw, contours, i, color,  - 1);
    }
     return contours;
}

 







posted on 2022-12-03 15:29  jsxyhelu  阅读(113)  评论(0编辑  收藏  举报

导航