OpenCV图像项目中,圆的检测很常见。
例如:检测烂苹果的个数,寻找目标靶心,人眼,嘴巴识别等。
其中用到的关键技术是OpenCV中集成的霍夫圆检测函数。
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 霍夫圆检测
#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 最小包裹圆
void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
points:输入信息,可以为包含点的容器(vector)或是Mat。
center:包覆圆形的圆心。
radius:包覆圆形的半径。
#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 椭圆拟合
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等时,矩形就成了一个直立的矩形
}
#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检测调参的烦恼了。