霍夫变换将轮廓从X-Y空间转换为参数空间。然后使用目标曲线(像线条或圆形)的特定参数属性来确立匹配该目标曲线在参数空间的点。比如,让我们来处理图像边缘检测的输出中的线条检测问题(注:检测边缘里的线条)。
利用霍夫变换检测线条
2D图像里的点可以以两种方式表示:
? (X, Y) 坐标
? (r, theta) 坐标:r是到(0,0)的距离而theta是与一条参考线的夹角,通常是x轴。如图6-3所示。
图6-3 (r, theta) 坐标表示
这些坐标之间的关系是:
x*cos(theta) + y*sin(theta) = 2*r
正如你所见,点(x,y)在(r,theta)参数空间的绘图是一个正弦曲线。因此,笛卡尔空间里共线的点在霍夫空间里对应不同的正弦曲线,会相交在一个共同的(r,theta)点,这个(r,theta)点代表笛卡尔空间里通过所有这些点的一条直线。给你一些例子,图6-4分别展示了一个点、一对点、和五个点在霍夫空间里的表现。生成该图的Matlab代码见清单6-3 。
图 -6-4 一个点,一对点和五个点(从上至下,顺时针)各自的霍夫变换
清单6-3 用Matlab代码来理解霍夫变换
%% one point theta = linspace(0, 2*pi, 500); x = 1; y = 1; r = 0.5 * (x * cos(theta) + y * sin(theta)); figure; plot(theta, r); xlabel(‘theta‘); ylabel(‘r‘); %% two points theta = linspace(0, 2*pi, 500); x = [1 3]; y = 2*x + 1; r1 = 0.5 * (x(1) * cos(theta) + y(1) * sin(theta)); r2 = 0.5 * (x(2) * cos(theta) + y(2) * sin(theta)); figure; plot(theta, r1, theta, r2); xlabel(‘theta‘); ylabel(‘r‘); %% five collinear points theta = linspace(0, 2*pi, 500); x = [1 3 5 7 9]; y = 2*x + 1; figure; hold on; r = zeros(numel(x), numel(theta)); for i = 1 : size(r, 1) r(i, :) = 0.5 * (x(i) * cos(theta) + y(i) * sin(theta)); plot(theta, r(i, :)); end xlabel(‘theta‘); ylabel(‘r‘);
然后我们可以使用如下策略来检测直线:
?定义一个离散霍夫空间的二维矩阵,比如r行和theta列。
?对边缘图像里的每一个(x,y)点,用方程式求出可能的(r,theta)值的列表,然后增加霍夫空间矩阵里对应的项(这个矩阵也叫累加器)
?当你对所有的边缘点这样处理之后,累加器里的有一些(r,theta)值会很高。这些就是直线,因为每一个(r,theta)点代表一条唯一的直线
OpenCV函数HoughLines()实现了这一策略并且有如下输入:
?二值边缘图像(比如,Canny边缘检测器的输出)
?r和theta分辨率
?认定一个(r,theta)点为一条直线的累加器阈值
使用霍夫变换检测圆形
圆形可以用三个参数表示:中心两个然后半径一个。因为圆的中心是在圆上每一点的法线上的,我们可以使用如下策略:
?使用一个二维累加器(对应图像上的点)来增加投票以找到中心。对于每一个边缘点,根据法线沿着的像素增加累计器位置来投票。当你对圆上所有点做该处理后,因为中心位于所有法线上,中心的票数就会增长。你可以通过累加器做阈值来找到圆的中心。
?下一步,对每一个候选中心点做一维半径直方图来估计半径。属于候选中心点围绕的圆形上的每一个边缘点几乎会投票给相同的半径(因为与中心点距离几乎相等),而其他边缘点(可能属于其它候选中心点围绕的圆)将会投票给其他虚假半径
实现霍夫圆形检测的OpenCV函数叫HoughCircles()。需要如下输入:
?灰度图像(用来应用Canny边缘检测)
?累加器分辨率与图像分辨率的反比率(用来检测圆中心)。例如,如果设置为2,累加器就是图像大小的一半
?检测圆形的中心之间的最小距离。此参数可以用来排斥虚假的中心点
?输入图像预处理Canny边缘检测时使用的高阈值(低阈值设置为高阈值的一半)
?累加器阈值
?最小和最大圆半径,可以用来过滤微小噪音的以及大的错误圆形
图6-5 显示了通过滑块调节累加器阈值利用霍夫变换来检测直线和圆形,和一个开关来选择检测直线还是圆形。代码如下见清单6-4
图 6-5 利用霍夫变换在不同累加器阈值下的圆和直线检测
清单 6-5 编程展现利用霍夫变换的直线和圆形检测
// Program to illustrate line and circle detection using Hough transform // Author: Samarth Manoj Brahmbhatt, University of Pennsylvania #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> using namespace std; using namespace cv; Mat img; int shape = 0; //0 -> lines, 1 -> circles int thresh = 100; // Accumulator threshold void on_trackbar(int, void *) { // Circles if(shape == 1) { Mat img_gray; cvtColor(img, img_gray, CV_RGB2GRAY); // Find circles vector<Vec3f> circles; HoughCircles(img_gray, circles, CV_HOUGH_GRADIENT, 1, 10, 100, thresh, 5); // Draw circles Mat img_show = img.clone(); for(int i = 0; i < circles.size(); i++) { Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); // draw the circle center circle(img_show, center, 3, Scalar(0, 0, 255), -1); // draw the circle outline circle(img_show, center, radius, Scalar(0, 0, 255), 3, 8, 0); } imshow("Shapes", img_show); } else if(shape == 0) { // Lines Mat edges; Canny(img, edges, 50, 100); // Find lines vector<Vec2f> lines; HoughLines(edges, lines, 1, CV_PI/180.f, thresh); // Draw lines Mat img_show = img.clone(); for(int i = 0; i < lines.size(); i++) { float rho = lines[i][0]; float theta = lines[i][1]; double a = cos(theta), b = sin(theta); double x0 = a * rho, y0 = b * rho; Point pt1(cvRound(x0 + 1000 * (-b)), cvRound(y0 + 1000 * (a))); Point pt2(cvRound(x0 - 1000 * (-b)), cvRound(y0 - 1000 * (a))); line(img_show, pt1, pt2, Scalar(0, 0, 255)); } imshow("Shapes", img_show); } } int main() { img = imread("hough.jpg"); namedWindow("Shapes"); // Create sliders createTrackbar("Lines/Circles", "Shapes", &shape, 1, on_trackbar); createTrackbar("Acc. Thresh.", "Shapes", &thresh, 300, on_trackbar); // Initialize window on_trackbar(0, 0); while(char(waitKey(1)) != ‘q‘) {} return 0; }