自OpenCV 4.3.0版本,imgproc模块的HoughCircles()函数,新增加了检测算法HOUGH_GRADIENT_ALT,精度得到了很大的提升。
1、举例opencv v4.5.5版本源码,
https://github.com/opencv/opencv/blob/4.5.5/modules/imgproc/src/hough.cpp
static void HoughCircles( InputArray _image, OutputArray _circles,
int method, double dp, double minDist,
double param1, double param2,
int minRadius, int maxRadius,
int maxCircles, double param3 )
{
CV_INSTRUMENT_REGION();
int type = CV_32FC3;
if( _circles.fixedType() )
{
type = _circles.type();
CV_CheckType(type, type == CV_32FC3 || type == CV_32FC4, "Wrong type of output circles");
}
CV_Assert(!_image.empty() && _image.type() == CV_8UC1 && (_image.isMat() || _image.isUMat()));
if( dp <= 0 || minDist <= 0 || param1 <= 0)
CV_Error( Error::StsOutOfRange, "dp, min_dist and canny_threshold must be all positive numbers" );
switch( method )
{
case HOUGH_GRADIENT:
{
......
}
break;
case HOUGH_GRADIENT_ALT:
{
std::vector<EstimatedCircle> circles;
Mat image = _image.getMat();
HoughCirclesAlt(image, circles, dp, minDist, minRadius, maxRadius, param1, param2);
std::sort(circles.begin(), circles.end(), cmpAccum);
size_t i, ncircles = circles.size();
if( type == CV_32FC4 )
{
std::vector<Vec4f> cw(ncircles);
for( i = 0; i < ncircles; i++ )
cw[i] = GetCircle4f(circles[i]);
if (ncircles > 0)
Mat(1, (int)ncircles, cv::traits::Type<Vec4f>::value, &cw[0]).copyTo(_circles);
}
else if( type == CV_32FC3 )
{
std::vector<Vec3f> cwow(ncircles);
for( i = 0; i < ncircles; i++ )
cwow[i] = GetCircle(circles[i]);
if (ncircles > 0)
Mat(1, (int)ncircles, cv::traits::Type<Vec3f>::value, &cwow[0]).copyTo(_circles);
}
else
CV_Error(Error::StsError, "Internal error");
}
break;
default:
CV_Error( Error::StsBadArg, "Unrecognized method id. Actually only CV_HOUGH_GRADIENT is supported." );
}
}
static void HoughCirclesAlt( const Mat& img, std::vector<EstimatedCircle>& circles, double dp, double rdMinDist,
double minRadius, double maxRadius, double cannyThreshold, double minCos2 )
{
const int MIN_COUNT = 10;
const int RAY_FP_BITS = 10;
const int RAY_FP_SCALE = 1 << RAY_FP_BITS;
const int ACCUM_FP_BITS = 6;
const int RAY_SHIFT2 = ACCUM_FP_BITS/2;
const int ACCUM_ALPHA_ONE = 1 << RAY_SHIFT2;
const int ACCUM_ALPHA_MASK = ACCUM_ALPHA_ONE - 1;
const int RAY_SHIFT1 = RAY_FP_BITS - RAY_SHIFT2;
const int RAY_DELTA1 = 1 << (RAY_SHIFT1 - 1);
const double ARC_DELTA = 80;
const double ARC_EPS = 0.03;
const double CIRCLE_AREA_OFFSET = 4000;
const double ARC2CLUSTER_EPS = 0.06;
const double CLUSTER_MERGE_EPS = 0.075;
const double FINAL_MERGE_DIST_EPS = 0.01;
const double FINAL_MERGE_AREA_EPS = CLUSTER_MERGE_EPS;
if( maxRadius <= 0 )
maxRadius = std::min(img.cols, img.rows)*0.5;
if( minRadius > maxRadius )
std::swap(minRadius, maxRadius);
maxRadius = std::min(maxRadius, std::min(img.cols, img.rows)*0.5);
maxRadius = std::max(maxRadius, 1.);
minRadius = std::max(minRadius, 1.);
minRadius = std::min(minRadius, maxRadius);
cannyThreshold = std::max(cannyThreshold, 1.);
dp = std::max(dp, 1.);
Mat Dx, Dy, edges;
Scharr(img, Dx, CV_16S, 1, 0);
Scharr(img, Dy, CV_16S, 0, 1);
Canny(Dx, Dy, edges, cannyThreshold/2, cannyThreshold, true);
Mat mask(img.rows + 2, img.cols + 2, CV_8U, Scalar::all(0));
double idp = 1./dp;
......
}
2、算子说明
HoughCircles(
InputArray image, //输入图像,必须是8位的单通道灰度图像
OutputArray circles, //输出结果,找到的圆信息
Int method, //方法是HOUGH_GRADIENT或HOUGH_GRADIENT_ALT
Double dp, //dp = 1或1.5
Double mindist, //最短距离-可以分辨是两个圆的,否则认为是同心圆
Double param1, //canny edge detection low threshold,Canny边缘检测的较大阈值
Double param2, //方法不同,含义不同
Int minradius, //最小半径
Int maxradius //最大半径
image:8 位、单通道、灰度输入图像
circles:找到圆的输出向量。每个向量编码为 3 或 4 个元素的浮点向量 $(x,y,radius)(x,y,radius,votes)$
method:检测方法。目前实现的方法是HOUGH_GRADIENT或HOUGH_GRADIENT_ALT
dp:累加器分辨率与图像分辨率的反比。例如,如果 dp=1,则累加器具有与输入图像相同的分辨率。如果 dp=2,则累加器的宽度和高度都是一半
对于HOUGH_GRADIENT_ALT,推荐值为 dp=1.5,除非需要检测一些非常小的圆。
minDist:检测到圆的中心之间的最小距离。如果参数太小,除了一个真实的圆之外,可能错误地检测到多个相邻的圆圈。如果太大,可能会遗漏一些圆。
param1:第一个特定方法参数。在HOUGH_GRADIENT和HOUGH_GRADIENT_ALT 的情况下,它是传递给 canny 边缘检测算子的高阀值,而低阀值为高阀值的一半
注意,HOUGH_GRADIENT_ALT使用Scharr算法计算图像导数,因此阈值通常更高,例如 300 或正常曝光和对比度的图像。
param2:第二个特定方法参数。在HOUGH_GRADIENT的情况下,它是检测阶段圆心的累加阀值。它越小,可以检测到更多的假圆。它越大,能通过检测的圆就更加接近完美的圆形。
在HOUGH_GRADIENT_ALT算法的情况下,这是圆形“完美”度量。它越接近 1,形状圆算法选择的越好。在大多数情况下,0.9 应该没问题。
如果您想更好地检测小圆圈,您可以将其降低到 0.85、0.8 甚至更低。
minRadius:最小半径
maxRadius:最大圆半径。如果≤0,则使用最大图像尺寸;如果<0,则返回中心而不查找半径
3、应用
由于HoughCircles函数内是调用Canny函数进行边缘检测,opencv的Canny函数是不包括平滑滤波这一步的,因此为了增强抗干扰能力,在使用HoughCircles函数之前,我们先对原图进行滤波处理,我们使用的是高斯模糊方法。
void hough()
{
cv::Mat src, dst;
src = cv::imread("d:\\circle.jpg");
//中值滤波,因为霍夫圆检测对噪声比较敏感,所以首先要对图像做中值滤波
cv::Mat mOut;
cv::medianBlur(src, mOut, 3);
cv::Mat grayImage;
cv::cvtColor(mOut, grayImage, cv::COLOR_BGR2GRAY);
//对灰度矩阵进行图像平滑处理(高斯模糊)
cv::GaussianBlur(grayImage, grayImage, cv::Size(9, 9), 2, 2);
//找霍夫圆
std::vector<cv::Vec3f> cir;
//cv::HoughCircles(grayImage, cir, cv::HOUGH_GRADIENT, 1, 15, 100, 30, 1, 100);
cv::HoughCircles(grayImage, cir, cv::HOUGH_GRADIENT_ALT, 1.5, 15, 300, 0.8, 1, 100);
src.copyTo(dst);
for (size_t i = 0; i < cir.size(); i++)
{
cv::Vec3f cc = cir[i];
cv::circle(dst, cv::Point(cc[0], cc[1]), cc[2], cv::Scalar(0, 0, 255), 2, cv::LINE_AA); //圆周
cv::circle(dst, cv::Point(cc[0], cc[1]), 2, cv::Scalar(255, 0, 0), 2, cv::LINE_AA); //圆心
}
cv::namedWindow("hough", cv::WINDOW_NORMAL);
cv::imshow("hough", dst);
cv::waitKey(0);
}