前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >终于可以摆脱OpenCV中Hough圆调参的烦恼了

终于可以摆脱OpenCV中Hough圆调参的烦恼了

作者头像
用户9831583
发布2022-06-16 14:48:02
2.3K0
发布2022-06-16 14:48:02
举报
文章被收录于专栏:码出名企路

OpenCV图像项目中,圆的检测很常见。

例如:检测烂苹果的个数,寻找目标靶心,人眼,嘴巴识别等

其中用到的关键技术是OpenCV中集成的霍夫圆检测函数。

代码语言:javascript
复制
HoughCircles(
    InputArray image,         // 输入图像 ,必须是8位的单通道灰度图像
    OutputArray circles,     // 输出结果,发现的圆信息
    Int method,             // 方法 - HOUGH_GRADIENT
    Double dp,                 // dp = 1; 
    Double mindist,         // 10 最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows/8
    Double param1,             // canny edge detection low threshold
    Double param2,             // 中心点累加器阈值 – 候选圆心
    Int minradius,             // 最小半径
    Int maxradius            //最大半径 
)

此方法有两个明显的缺点:

调参困难,运行速度慢

如何实现准确而又高效的圆的检测呢?博主在实际项目开发中遇到的问题及对比如下所述。

1 霍夫圆检测

代码语言:javascript
复制

#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>  
#include"opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
 
int main() {
  Mat srcImage = imread("../img/3.png");
  Mat midImage, dstImage;
  cvtColor(srcImage, midImage, COLOR_BGR2GRAY);//灰度
  //imshow("gray", midImage);
  GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);//高斯滤波
  //imshow("gauss", midImage);
  vector<Vec3f> cicles;
  HoughCircles(midImage, cicles,HOUGH_GRADIENT, 1.5, 35, 200, 150,0,0);//霍夫圆变换
  cout << "count:" << cicles.size()<<endl;
  for (size_t i = 0; i < cicles.size(); i++)
  {
    Point center(cvRound(cicles[i][0]), cvRound(cicles[i][1]));
    int radius = cvRound(cicles[i][2]);
    circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
    circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
  }
  imshow("result", srcImage);
  waitKey(0);
  return 0;
}

}

2 最小包裹圆

代码语言:javascript
复制
void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
    points:输入信息,可以为包含点的容器(vector)或是Mat。
    center:包覆圆形的圆心。
    radius:包覆圆形的半径。
代码语言:javascript
复制
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>  
#include"opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main() {

    Mat src_gray,gray_image;
  Mat src = imread("../img/2.png");
    cvtColor( src, src_gray, CV_BGR2GRAY );
    GaussianBlur( src_gray , gray_image, Size(9, 9), 2, 2 );  
    Canny(gray_image,gray_image ,50,100,3);
    imshow("result", gray_image);
  waitKey(0);

    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(gray_image, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    Mat cimage = Mat::zeros(gray_image.size(), CV_8UC3);
    cout<<"contours.size() "<<contours.size()<<endl;
    vector<cv::Point>  approx(contours.size());
    Point2f center[contours.size()];
    float r[contours.size()];
    for(unsigned int i=0;i<contours.size();i++)
    {
        minEnclosingCircle(contours[i],center[i],r[i]);    
      //  Mat dst(src.size(), CV_8UC3, Scalar::all(0));
        Point center1;
        center1.x  = cvRound (center[i].x);
        center1.y = cvRound(center[i].y);
        int radius1 = cvRound(r[i]);
   
        if(radius1 >=56 && radius1 <=68)
        {
                 cout<<"r "<<radius1<<endl;
            circle(src,center1, radius1, Scalar(0,0,255), 2, 8);
      //  dst.setTo(Scalar(255,255,255), src);
        imshow("src ", src);
       waitKey(0);
        }  
    } 
  return 0;
}

3 椭圆拟合

代码语言:javascript
复制
RotatedRect fitEllipse(InputArray points)
class CV_EXPORTS RotatedRect
{   public:  //构造函数
      RotatedRect();
    RotatedRect(const Point2f& center, const Size2f& size, float angle);
    RotatedRect(const CvBox2D& box);
    void points(Point2f pts[]) const;    //!返回矩形的4个顶点
     Rect boundingRect() const;  //返回包含旋转矩形的最小矩形
   operator CvBox2D() const;  //!转换到旧式的cvbox2d结构
   Point2f center; //矩形的质心
   Size2f size;    //矩形的边长
    float angle;    //旋转角度,当角度为0、90、180、270等时,矩形就成了一个直立的矩形
}
代码语言:javascript
复制

#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>  
#include"opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main() {

    Mat src_gray,gray_image;
  Mat src = imread("../img/2.png");
    cvtColor( src, src_gray, CV_BGR2GRAY );
    GaussianBlur( src_gray , gray_image, Size(9, 9), 2, 2 );  
    Canny(gray_image,gray_image ,50,100,3);
    imshow("result", gray_image);
  waitKey(0);
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    findContours(gray_image, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    Mat cimage = Mat::zeros(gray_image.size(), CV_8UC3);
    cout<<"contours.size() "<<contours.size()<<endl;
    //最小外接椭圆,输入点一定大于5个
    vector<RotatedRect> box(contours.size());

    for (unsigned int i = 0; i < contours.size(); i++)
    {   
          
    //拟合的点至少为6
    size_t count = contours[i].size();
    if( count < 6 )
      continue;
     else
        {   
            //返回包覆输入信息的最小正矩形。
            cv::Rect r = cv::boundingRect(contours[i]);
            
          //  cout<<"矩阵长/宽  "<<r.width <<" "<<r.height<<endl;
              //矩形长宽比
             double ratio=(double)r.height/ r.width;
 
           if (ratio>=1.95 && ratio <= 3.94)
            {   
                //二维点集,要求拟合的点至少为6个点
                //输出:RotatedRect 类型的矩形,是拟合出椭圆的最小外接矩形
               box[i] = fitEllipse(Mat(contours[i]));
                 
                //cout<<"椭圆长/短 "<<box[i].size.width<<" "<<box[i].size.height<<endl;
                double r=(double)box[i].size.height/box[i].size.width;
               // double ang=box[i].angle;
                double square=(double)box[i].size.height*box[i].size.width 
                 if( r >=1.8 && r<=2.3 && square >=17350 && square <=21100)
          {    
                 cout<<"square "<<square<<endl;
               // cout<<"角度 "<<box[i].angle<<endl;
                cout<<"椭圆长/短 "<<box[i].size.width<<" "<<box[i].size.height<<endl;
                    //画出追踪出的轮廓
            drawContours(cimage, contours, (int)i, Scalar::all(255), 1, 8);
            //画出拟合的椭圆
             ellipse(cimage, box[i], Scalar(0,0,255), 1, CV_AA);
                 imshow("cimage", cimage);
               waitKey(0);
                 ellipse(src, box[i], Scalar(0,0,255), 1, CV_AA);
                  imshow("srccc", src);
               waitKey(0);
                }     
            }
        }
    }
  return 0;
}

总结:

拟合椭圆方法在准确度上明显优于霍夫圆变换和最小包裹圆方法;

并且可以摆脱Hough检测调参的烦恼了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码出名企路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档