Fork me on GitHub

找圆方法的总结和比较(三种主要识别方法的比较和融合)

本篇博客是课程《基于OpenCV的钢管计数项目实战》第7课的提纲。在前面已经详细分析3种主要算法的基础上,本节的重点在于如果将每一种算法找到的目标有效地融合起来,并且进一步横向分析研究算法间的关系,最后就整套算法创造过程中产生的思考进行联想和畅谈,希望多少能够给关注这个方向、有类似需求的创作者一些思考。
一、算法流程
首先对于自然图片,通过blod detection获得准确的半径以及一些准确的钢管;
而后基于准确的半径,分别调用HoughCircle以查漏补缺,对图片进行预处理后再调用Contours分析,寻找水泥管。
最后,以上获得的结果,需要进行融合筛选。
二、融合方法
数据结构:
使用KeyPoint 和vector<KeyPoint> ,这种数据结构能够保存x,y和size,对于圆这种对象来说,非常对口。
融合依据:
在添加的过程中首先进行判断:
        for (size_t i = 0; i < specialKeypoints.size(); i++)
        {
            bool isNear = true;
            for (size_t j = 0; j < keypoints.size(); j++)
            {
                double dist = norm(specialKeypoints[i].pt - keypoints[j].pt);
                isNear = (dist <= radius * 2.5f);
                if (isNear)
                {
                    break;
                }
            }
            if (isNear)
            {
                keypoints.push_back(specialKeypoints[i]);
            }
        }
在全部点叠加之后,再进行筛选:
        std::vector < KeyPoint > resultKeypoints;
        for (size_t i = 0; i < keypoints.size(); i++)
        {
            bool isNew = true;
            for (size_t j = 0; j < resultKeypoints.size(); j++)
            {
                double dist = norm(keypoints[i].pt - resultKeypoints[j].pt);
                isNew  =  (dist >= keypoints[i].size/2 && dist >= resultKeypoints[j].size/2);
                if (!isNew)
                {
                    break;
                }
            }
            if (isNew && keypoints[i].size > radius)
                resultKeypoints.push_back( keypoints[i]);
        }
当然你也可以尝试将所有的点全部融合在一起后,再进行全局筛选。逻辑上是没有问题的,实际操作上效果要差一些。
三、算法异同
在程序的实现过程中,没有过多考虑并行等因素
        // 基于Blob方法进行圆的寻找
        SimpleBlobDetector::Params params;
        params.filterByColor = false;
        params.minThreshold = 0;
        params.maxThreshold = 250;
        vector<KeyPoint> keypoints;    
        cv::Ptr<cv::SimpleBlobDetector> detector = cv::SimpleBlobDetector::create(params);
        detector->detect(srcNormal, keypoints);
        //获得半径
        std::vector<double> dists;
        double radius;
        for (size_t pointIdx = 0; pointIdx  < keypoints.size(); pointIdx++)
        {
            dists.push_back(keypoints[pointIdx].size);
        }
        std::sort(dists.begin(), dists.end());
        radius = (dists[dists.size() / 2]) / 2.;
        //基于HoughCircle方法进行圆的寻找
        vector<KeyPoint> tmpKeypoints = findPipMethodHough(srcNormal, radius);
        //基于轮廓方法,专门寻找“水泥管”
        vector<KeyPoint> specialKeypoints = findConcretePip(srcNormal, radius);
我们将三类算法的结构进行比较:
算法代码注释讲解
  
        SimpleBlobDetector::Params params;
        params.filterByColor = false;
        params.minThreshold = 0;
        params.maxThreshold = 250;
        vector<KeyPointkeypoints;    
        cv::Ptr<cv::SimpleBlobDetectordetector = cv::SimpleBlobDetector::create(params);
        detector->detect(srcNormalkeypoints);
        //获得半径
        std::vector<doubledists;
        double radius;
        for (size_t pointIdx = 0; pointIdx  < keypoints.size(); pointIdx++)
        {
            dists.push_back(keypoints[pointIdx].size);
        }
        std::sort(dists.begin(), dists.end());
        radius = (dists[dists.size() / 2]) / 2.;
基于Blob方法进行圆的寻找,除了参数的定义有一些学问以外,基本上就是对自然图片进行基本处理,有个特点是然会radius
    Mat gray;
    vector<cv::KeyPointvecCenters;
    vector<Vec3fvec3f_method_hough;
    //处理彩色图片,进行Hough处理
    if (src.channels() == 3 || src.channels() == 4)
        cvtColor(srcgrayCOLOR_BGR2GRAY);
    else
        gray = src.clone();
    blur(graygraycv::Size(3, 3));
    HoughCircles(grayvec3f_method_houghHOUGH_GRADIENT, 2, p_radius*2, 100, 33, p_radius-2, p_radius + 2);
    for (int i = 0; i < vec3f_method_hough.size(); i++)
    {
        Point center(cvRound(vec3f_method_hough[i][0]), cvRound(vec3f_method_hough[i][1]));
        cv::KeyPoint kpt(center, (float)(p_radius * 2.0f));
        vecCenters.push_back(kpt);
    }
    return vecCenters;
基于Hough方法进行圆的寻找,除了参数中radius是确定的以外,基本上就是通用的找圆算法
    float f_area = CV_PI * p_radius * p_radius;//数学公式
    vector<cv::Pointvec_method_normal;//返回结果
    Mat gray;
    Mat tmp25;
    Mat draw;
    Mat srcClone = src.clone();
    //局部阈值方法算法
    cvtColor(srcgrayCOLOR_BGR2GRAY);
    blur(graygraySize(3, 3)); //简单平滑
    adaptiveThreshold(graytmp25, 255, ADAPTIVE_THRESH_MEAN_CTHRESH_BINARY,(intp_radius * 2 + 1, 0.0);  //局部阈值
    
    //形态学变换(膨胀)
    Mat elementTest = getStructuringElement(MORPH_ELLIPSESize(5, 5));
    morphologyEx(tmp25tmp25cv::MORPH_ERODEelementTest);
    //边界的钢管识别(边界填充)
    rectangle(tmp25cv::Rect(0, 0, tmp25.colstmp25.rows), Scalar(255, 255, 255), 1);
    //轮廓分析
    std::vector < std::vector<Point> > contours;
    findContours(tmp25contoursRETR_LISTCHAIN_APPROX_NONE);
    vector<cv::KeyPointvecCenters;
    int minArea = f_area * 0.5f;
    int maxArea = 5000;
    float minCircularity = 0.47f;
    float maxCircularity = std::numeric_limits<float>::max();
    for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++)
    {
        //筛选条件
        Moments moms = moments(contours[contourIdx]);
        //area
        double area = moms.m00;
        if (area < minArea || area >= maxArea)
            continue;
        //circularity
        double perimeter = arcLength(contours[contourIdx]true);
        double ratio = 4 * CV_PI * area / (perimeter * perimeter);
        if (ratio < minCircularity || ratio >= maxCircularity)
            continue;
        Point2d center  = Point2d(moms.m10 / moms.m00moms.m01 / moms.m00);
        cv::KeyPoint kpt(center, (float)(p_radius * 2.0f));
        vecCenters.push_back(kpt);
        
    }
    drawKeypoints(srcClonevecCentersdrawScalar(0, 255, 0), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    return vecCenters;
虽然找不到几个目标,但是本函数是最复杂的(最长)的,在实现的过程中包含了圆度和面积的分析。特别是算法预处理这块,明显是有针对性地优化若干目标项目。
四、算法衍生
这一次的实现,是比较系统且复杂的轮廓分析算法,从结果上来看,基本能够满足设计要求;从对于自己的提高来看,引导我深入地研究相关知识,增强了能力,主要是能够针对不同情况,有选择性地选择算法实验。
回顾我做过的一些案例,有很多是涉及到轮廓分析的,这里进行回顾和比对:
1、树叶测量
页面分析系统是基于OpenCV+MFC(RIBBON)的精度测量项目。
其关键的一个部分在于实现了3种标定方法,能够获得“像素-距离”的实际关系。
最终通过轮廓分析,获得每一片叶子的面积、周长等信息。
在这个例子中,轮廓分析的主要用途是做联通区域处理,而后区分的结果进行分析和进一步处理。从这个角度上来说,使用联通区域那两个函数更适合。

这个例子使用了Ribbon,这是难得的能够和OpenCV结合良好的框架。相关材料去除敏感信息后出了相关教程。
2、石材大板
使用了类似“树叶分析”的程序框架,但是重点完全不一样。“石材大板”是石料行业中,需要采用计算机的方法对进出库的石材进行有效面积计算的过程(后续应该还可以进一步引导切割)。由于和工业深度融合,该项目对精度、可靠性、可扩充性要求都比较高。
我依然是使用OpenCV+MFC(Ribbon)来完成本项目,并且顺利交付。现在看来,当时的很多算法是臃肿的,界面由于需要有实现很多“辅助线”,最终的实现也不是很巧妙。这里进行介绍,能够帮助大家了解在工业上面,(传统)图像处理项目的基本方法。比较而言,钢管识别的实现体系要现代许多,但是对应起来,只能计数码,不能度量。
3、中药项目
比较早的一个项目,基于OpenCV+MFC,对黑箱采集的图片进行精度测量。
其中也使用了标定的过程。当时这个项目是给合肥一个学校做的,用于标准的制定。现在看来,实现过程中从算法到界面都已经全面过时了。但是当时这个项目仍然是做成功的,因为我采用的视觉经典测量的方法是可以解释的。我相信肯定也有这样的商品,就是做成一个黑箱,而后来测量。
4、答题卡项目
答题卡项目不是完全的轮廓分析,应该说是基于二值区域的投影分析,这些都是不同尺度上的应用。我分别使用c++和mfc进行了实现。现在想来,答题卡这个项目,如果实现pybind++的调用,结合手机,变成“小猿答题”这样的app,似乎很有趣味。
从中我们也可以看出,轮廓(团块)分析的确是强有力的分析工具,在OpenCV中也提供了不同层次的实现,非常值得我们深入研究、灵活运用。
在我之前做过的项目中,应该说以精度策略和数量策略为主,这可能是市场对轮廓这块的具体需求。
五、设计杂谈
【想结合算法的设计实现,谈一些务虚的东西】
为了提高算法能力,达到预定目的,我对OpenCV本身进行了较深入研究,应该说有“新发现,新认识”。
5.1、算法库的涉及范围广泛,需要进一步挖掘
OpenCV到底有多少算法,那些算法进行了优质实现?这些内容,如果不去进行具体研究,是很难得出明确结论的。这一次在findblob、hough的具体研究中,我发现OpenCV从算法本身、实现方法、实现细节,都是有很多值得深入研究的。
在之前很长,我都满足于对算法进行初步试用、认识较为模糊。这一次,为了效率,比如完整的、深刻地认识算法原理,并且“举重若轻”地提出解决方案。
这种方式,在将来是“新常态”;我也需要在一次又一次的“淬炼”中成为真正的专家。
5.2、算法库影响广泛、代码规范,都是将来发展重要依托
在OpenCV PR的过程中,我接触到了“规范的代码”,这潜移默化地改变了我的编码习惯。现在很多方面,我的工作是模糊的,毕竟不是做专门的工作。但是正确的东西就是正确的东西。
必须增强自己在算法实现过程中的影响力、锻炼正确的方法,这些都是未来的重要依托。
5.3、Github的维护方式,是进一步“刻意练习”的重要依托
“可以练习”强调的是focus feed-back fix ,这在Github上面有非常好的体现,一定要寻找真正的专家,并且就普遍关心的问题进行深入的工作,这样才能进入能力提高“正循环”。





posted @ 2020-07-09 18:44  jsxyhelu  阅读(507)  评论(0编辑  收藏  举报