霍夫变换是在图像处理中进行直线、圆形检测和拟合一种非常重要的手段。无论是对直线还是圆形进行检测和拟合,霍夫变换的中心思想就是将图像像素坐标转换到参数坐标。
关于直线的霍夫变换原理,下面一张图描述得非常清楚:
鉴于Opencv中有关于霍夫变化可以直接调用的接口,该文下面就主要介绍这些接口函数的调用方法和参数的意义,常用的接口函数包括:HoughLines()、HoughLinesP()、HoughCircles()
- HoughLines()
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
参数说明:
- image: 8位单通道二进制图像,可以载入任意图像由函数修改成此格式
- lines: 输出直线矢量,每一条线有两个元素的矢量(ρ , θ)表示,ρ是离坐标原点(0, 0)也就是图像左上角的距离,θ是弧度线条旋转角度(0度表示垂直线,π/2度表示水平线)。
- rho: 以像素为单位的距离精度,其它表述还有是直线搜索时的进步尺寸的单位半径
- theta: 以弧度为单位的角度精度。其它表述还有是直线搜索时的进步尺寸的单位角度。
- threshold: 累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的直线才可以被检测通过并返回到结果中。
- srn: 对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho, 而精确的累加器进步尺寸为rho/srn。有默认值0
- stn:对于多尺度霍夫变换,stn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0, 就表示使用经典的霍夫变换,扶着这两个参数都应该为正数。经典霍夫变换和多尺度霍夫变换就是在这进行区分的。有默认值0
代码演示:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
Mat image;
image=imread("5.jpg");
Mat image_copy;
image_copy=image.clone();
imshow("1",image);
cvtColor(image,image,CV_RGB2GRAY);//灰度化
GaussianBlur(image,image,Size(9,9),2,2);//高斯模糊
Canny(image,image,30,60);//Canny边缘检测
vector<Vec2f> lines;
HoughLines(image, lines, 1, CV_PI/180,230, 0, 0);//霍夫变换
for(size_t i = 0; i < lines.size(); i++)//显示检测到的直线
{
float rho = lines[i][0];
float theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line(image_copy, pt1, pt2, Scalar(0, 0, 255), 2);
}
imshow("2",image_copy);
waitKey(0);
return 0;
}
在进行直线检测之前,我们先将RGB图转换成为灰度图像,然后进行高斯模糊,通过Canny边缘检测算法得到图像的二值边缘图像,将二值边缘图像作为HoughLines()函数的图像输入,输入的vector容器中包含的就是检测到的直线的信息。效果如下图所示:
- HoughLinesP()
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
参数说明:
- image: 8位单通道二进制图像,可以载入任意图像由函数修改成此格式
- lines: 线段输出矢量,每一条之先都由四个矢量元素(x1, y1, x2, y2)表示,其中(x1, y1)和(x2, y2)分别是检测到直线线段的两个端点。
- rho: 以像素为单位的直线搜寻步长
- theta: 以弧度为单位的直线搜寻步长
- threshold: 累加器阈值参数,只有大于threshold的直线结果才能被检测通过并返回到结果中
- minLineLength: 线段长度最小值,如果线段长度小于这个值则线段结果不显示,有默认值0
- maxLineGap: 允许点连接到相同直线的中间距离的最大值,如果超过这个最大值则点不会被划分到该直线。有默认值0
代码演示:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
Mat image;
image=imread("5.jpg");
Mat image_copy;
image_copy=image.clone();
imshow("1",image);
cvtColor(image,image,CV_RGB2GRAY);
blur(image,image,Size(5,5));
Canny(image,image,30,60);
vector<Vec4i> Lines;
HoughLinesP(image, Lines, 1, CV_PI / 180,40,40,5);//霍夫直线检测
//在图中绘出线段
for(int i=0;i<Lines.size();i++)
line(image_copy,Point(Lines[i][0],Lines[i][1]),Point(Lines[i][2],Lines[i][3]),Scalar(0,255,0),2);
imshow("2",image_copy);
waitKey(0);
return 0;
}
HoughLinesP()函数保存直线信息的方式和HoughLines()函数的方式也不一样,HoughLines()函数保存的信息是截距和斜率,而HoughLinesP()函数保存的是在直线上的两点(两点确定一条直线)。示例代码中设置的参数限制了线段的最小长度和艰巨最大值,可见效果如下:
- HoughCircles()
void cv::HoughCircles ( InputArray image,
OutputArray circles,
int method,
double dp,
double minDist,
double param1 = 100,
double param2 = 100,
int minRadius = 0,
int maxRadius = 0
)
参数说明:
- image: 输入图像为8位单通道灰度图
- circles: 检测到圆的输出矢量,每个矢量都是三个浮点元素构成(x,y,radius)
- method: 检测方法,可以查看HoughModes查看具体细节,但目前只有HOUGH_GRADIENT(霍夫梯度)一种方法可以使用。
- dp: 用来检测圆心的累加器图像的分辨率与输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。例如,如果dp=1,累加器和输入图像具有相同的分辨率,如果dp=2累加器便有输入图像一半那么大的宽度和高度。
- minDist: 霍夫检测到的圆的圆心之间的最小距离,即让算法能明显区分的两个不同圆之间的最小距离。这个参数如果太小的话,多个相邻的圆可能被错误的检测成了一个重合的圆。繁殖这个参数设置太大,某些圆就不能被检测出来了。也就是超过这个距离就是两个圆,否则是一个圆。
- param1: 指定检测方法的第一个参数,当前可用方法是HOUGH_GRADIENT,它表示传递给Canny边缘检测算子的高阈值(低阈值是高阈值的一半),有默认值100
- param2: 指定检测方法的第二个参数,对于HOUGH_GRADIENT方法,它表示在检测阶段圆心的累加器阈值。它越小就越可以检测到更多根本不存在的圆,而它越大的话能通过检测的圆就更加接近完美的圆形了。有默认值100
- minRadius: 圆的最小半径
- maxRadius: 圆的最大半径
代码演示:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
Mat image;
image=imread("4.jpg");
Mat image_copy;
image_copy=image.clone();
imshow("1",image);
cvtColor(image,image,CV_RGB2GRAY);
blur(image,image,Size(5,5));
Canny(image,image,30,60);
vector<Vec3f> circles;
HoughCircles(image, circles, CV_HOUGH_GRADIENT, 1, image.rows/8, 100, 100, 0, 0);
//绘制检测到的圆
for(size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
circle(image_copy, center, 3, Scalar(0, 255, 0), -1, 8, 0);
circle(image_copy, center, radius, Scalar(0, 255, 0), 3, 8, 0);
}
imshow("2",image_copy);
waitKey(0);
return 0;
}
效果图:
霍夫圆变换检测
总结:
霍夫变换是在二值边缘图像的基础上进行计算的,这也就涉及到一个问题:二值边缘图像的像素数量将会直接影响霍夫变换的速度,也就是说二值边缘图像中的边缘像素点越少,则霍夫变换的计算开销越小,像素点越多则计算开销越大,而且这种开销的增加不是呈线性的。因此,在对实时性要求较高的工程中,如果使用到了霍夫变换,那么就要严格控制输入的二值边缘图像的边缘像素的数量,该文中的边缘检测使用的都是Canny算法,Canny的参数以及Hough变换函数的参数需要在效果和速度两者权衡的情况下设置得当。