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

2022-06-16 14:48:02 浏览数 (1)

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检测调参的烦恼了。

0 人点赞