1.目的
(1)如何使用openCV的HoughCircles在图像中检测圆区域
2.原理
[1]标准霍夫变换
霍夫圆变换可以根据霍夫线变换来实现 ,通过极坐标来表示圆(a,b)表示圆心,R表示半径,则圆表示为:
x = a + Rcosθ
y = b + Rsinθ
θ的值为0-360
一开始我们假设R是已知的,那么我们就可以把x,y空间的公式变换为关于a、b空间的公式:
a = x1 – Rcosθ
b = y1 – Rsinθ
x1、y1为x、y空间中的每一个非零像素点,如此一来,后面的变换就可以跟霍夫线变换一样操作了。
具体操作步骤为:
①、读取一副图像
②、检测边缘,产生一副二值图像
③、对于每个非零像素转换到ab空间中
④、对于每个ab空间中的点,进行累加
⑤、提取数量最多的点作为圆点
PS:在上面提到R是假设已知的,那么当R是未知的情况怎么办?其实很简单,就是将R设置为1、2、3……这样子已知下去,慢慢试,再对R和圆心数量进行阈值就可以了。
[2]霍夫梯度法
霍夫梯度法的原理是这样的。
【1】 首先对图像应用边缘检测,比如用canny边缘检测。
【2】 然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度。
【3】 利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
【4】 同时,标记边缘图像中每一个非0像素的位置。
【5】 然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。
【6】 接下来对每一个中心,考虑所有的非0像素。
【7】 这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。
【8】如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
这个实现可以使算法执行起来更高效,或许更加重要的是,能够帮助解决三维累加器中 会产生许多噪声并且使得结果不稳定的 稀疏分布问题。
霍夫梯度法的缺点:
<1> 在霍夫梯度法中,我们使用Sobel导数来计算局部梯度,那么随之而来的假设是,其可以视作等同于一条局部切线,并这个不是一个数值稳定的做法。在大多数情况下,这样做会得到正确的结果,但或许会在输出中产生一些噪声。
<2> 在边缘图像中的整个非0像素集被看做每个中心的候选部分。因此,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。
<3> 因为中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前 已经接受的中心的话,就不会被保留下来。且当有许多同心圆或者是近似的同心圆时,霍夫梯度法的倾向是保留最大的一个圆。可以说这是一种比较极端的做法,因 为在这里默认Sobel导数会产生噪声,若是对于无穷分辨率的平滑图像而言的话,这才是必须的。 (实际上可以设置min_dist来解决该问题)
PS:openCV中实现的是霍夫梯度法
3.部分代码解释
(1)HoughCircles
/*
HoughCircles参数解释
edge:通过边缘检测获得的二值图像,一般可以使用canny边缘检测算子
circles:检测到的圆的参数(圆心Point([0],[1]),半径radius([2]))
CV_HOUGH_GRADIENT:霍夫梯度法
dp = 1:累加器图像的反比分辨率
min_dist = src_gray.rows/8: 检测到圆心之间的最小距离
param_1 = 200: Canny边缘函数的高阈值
param_2 = thresh:圆心检测阈值
min_radius:最小能够检测的圆大小
max_radius:最大能够检测的圆大小
ps:min_radius, max_radius默认值为0,当max_radius设置为0时候,可以检测任意大小的圆
*/
HoughCircles(edge, circles, CV_HOUGH_GRADIENT, 1, edge.rows/8, 200, thresh, 0, edge.rows/2);
4.完整代码
(1)CommonInclude.h
#ifndef COMMON_INCLUDE
#define COMMON_INCLUDE
#include<iostream>
#include<vector>
using namespace std;
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
#endif
(2)HoughCircles.cpp
#include"CommonInclude.h"
int thresh = 50;
int max_thresh = 200;
Mat src, gray, edge, dst;
char windowName[] = "HoughCircles";
void HoughCirclesTrans(int, void*){
vector<Vec3f> circles;
/*
HoughCircles参数解释
edge:通过边缘检测获得的二值图像,一般可以使用canny边缘检测算子
circles:检测到的圆的参数(圆心Point([0],[1]),半径radius([2]))
CV_HOUGH_GRADIENT:霍夫梯度法
dp = 1:累加器图像的反比分辨率
min_dist = src_gray.rows/8: 检测到圆心之间的最小距离
param_1 = 200: Canny边缘函数的高阈值
param_2 = thresh:圆心检测阈值
min_radius:最小能够检测的圆大小
max_radius:最大能够检测的圆大小
ps:min_radius, max_radius默认值为0,当max_radius设置为0时候,可以检测任意大小的圆
*/
HoughCircles(edge, circles, CV_HOUGH_GRADIENT, 1, edge.rows/8, 200, thresh, 0, edge.rows/2);
Point center;
float radius;
cvtColor(edge, dst, CV_GRAY2BGR);
for(int i=0; i<circles.size(); i++){
center = Point(circles[i][0], circles[i][1]);
radius = circles[i][2];
circle(dst, center, 2, Scalar(255,0,0), 2, 8);
circle(dst, center, radius, Scalar(0,0,255), 2, 8);
}
imshow(windowName, dst);
}
int main(int argc, char** argv){
if(argc<2){
cout << "more parameters are required!!!" << endl;
return(-1);
}
src = imread(argv[1]);
if(!src.data){
cout << "error to read image!!!" << endl;
return(-1);
}
namedWindow(windowName, CV_WINDOW_AUTOSIZE);
//高斯平滑,去除噪声
GaussianBlur(src, src, Size(5,5), 0, 0);
//转换成灰度图像
cvtColor(src, gray, CV_BGR2GRAY);
imshow("gray", gray);
//Canny边缘检测
Canny(gray, edge, 50, 200, 3);
createTrackbar("Thresh", windowName,
&thresh, max_thresh,
HoughCirclesTrans);
HoughCirclesTrans(0,0);
waitKey(0);
return(0);
}