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


    本篇博客是课程《基于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上面有非常好的体现,一定要寻找真正的专家,并且就普遍关心的问题进行深入的工作,这样才能进入能力提高“正循环”。





  • 相关阅读:
    javaday19_List接口_Set接口
    01玩转数据结构_04_最基础的动态数据结构:链表
    10 拖拽的对话框_滚动条_放大镜_
    01玩转数据结构_03_栈和队列
    java小技巧
    01玩转数据结构_02_不要小瞧数组
    01玩转数据结构_01_课程介绍
    javaday18_ArrayList
    JZOJ.3777【NOI2015模拟8.17】最短路(shortest)
    JZOJ.5230【NOIP2017模拟8.5】队伍统计
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/13275392.html
Copyright © 2020-2023  润新知