霍夫变换圆原理
霍夫圆变换的基本原理和上面讲的霍夫线变化大体上是很类似的,只是点对应的二维极径极角空间被三维的圆心点x, y还有半径r空间取代。说“大体上类似”的原因是,如果完全用相同的方法的话,累加平面会被三维的累加容器所代替:在这三维中,一维是x,一维是y,另外一维是圆的半径r。这就意味着需要大量的内存而且执行效率会很低,速度会很慢。
对直线来说, 一条直线能由参数极径极角(r,θ)表示. 而对圆来说, 我们需要三个参数来表示一个圆, 也就是:
OpenCV中,我们一般通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题。
霍夫梯度法的原理
以下摘自《OpenCV3编程入门》
霍夫梯度法的原理是这样的:
- 首先对图像应用边缘检测,比如用canny边缘检测。
- 然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度。
- 利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
- 同时,标记边缘图像中每一个非0像素的位置。
- 然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。
- 接下来对每一个中心,考虑所有的非0像素。
- 这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。8.如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
这个实现可以使算法执行起来更高效,或许更加重要的是,能够帮助解决三维累加器中会产生许多噪声并且使得结果不稳定的稀疏分布问题。
变换圆步骤
- 图片加载
- 中值滤波(因为霍夫圆检测对噪声比较敏感,所以首先要对图像做中值滤波)
- 检测边缘(发现可能的圆心)
- 基于第一步的基础上从候选圆心开始计算最佳半径大小(基于效率考虑,Opencv中实现的霍夫变换圆检测是基于图像梯度的实现)
- 图像显示
相关API
HoughCircles
函数原型
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//最大半径 )
代码示例
#include <iostream> #include <math.h> #include <opencv2/opencv.hpp> #include <opencv2/highgui.hpp> #include <opencv2/highgui/highgui_c.h> using namespace cv; using namespace std; int main(int argc, char** argv) { Mat src, dst; src = imread("./yuan.jpg"); if (!src.data) { printf("could not load image...\n"); return -1; } char INPUT_TITLE[] = "input image"; char OUTPUT_TITLE[] = "hough circle demo"; namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE); namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE); imshow(INPUT_TITLE, src); // 中值滤波 Mat moutput; medianBlur(src, moutput, 3); cvtColor(moutput, moutput, CV_BGR2GRAY); // 霍夫圆检测 vector<Vec3f> pcircles; HoughCircles(moutput, pcircles, CV_HOUGH_GRADIENT, 1, 10, 100, 30, 5, 50); src.copyTo(dst); for (size_t i = 0; i < pcircles.size(); i++) { Vec3f cc = pcircles[i]; circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(0, 0, 255), 2, LINE_AA); circle(dst, Point(cc[0], cc[1]), 2, Scalar(198, 23, 155), 2, LINE_AA); } imshow(OUTPUT_TITLE, dst); waitKey(0); return 0; }