霍夫直线检测原理
- 霍夫变换 (Hough Transform)
霍夫变换是图像处理必然接触到的一个算法,它通过一种投票算法检测具有特定形状的物体,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果,该方法可以进行圆,直线,椭圆等形状的检测。例如:在车道线检测中,当初考虑的一个方案便是采用霍夫变换检测直线进行车道线提取。
霍夫变换于1962年由 Paul Hough 首次提出,后于1972年由 Richard Duda 和 Peter Hart 推广使用,是图像处理领域内从图像中检测几何形状的基本方法之一。经典霍夫变换用来检测图像中的直线,后来霍夫变换经过扩展可以进行任意形状物体的识别,例如圆和椭圆。霍夫变换运用两个坐标空间之间的变换,将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。 - 霍夫直线检测原理
1、基本原理:
Hough直线检测的基本原理在于利用点与线的对偶性,在我们的直线检测任务中,即图像空间中的直线与参数空间中的点是一一对应的,参数空间中的直线与图像空间中的点也是一一对应的。这意味着我们可以得出两个非常有用的结论:
1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示;
2)图像空间中的直线上任何一部分线段在参数空间对应的是同一个点。
因此Hough直线检测算法就是把在图像空间中的直线检测问题转换到参数空间中对点的检测问题,通过在参数空间里寻找峰值来完成直线检测任务。
2、关于对偶性:
1)图像空间中的点与参数空间中的直线一一对应;
2)图像空间中的直线与参数空间中的点一一对应;
3、参数空间的选择:
这样就把在图像空间中检测直线的问题转化为在极坐标参数空间中找通过点(r,θ)的最多正弦曲线数的问题。霍夫空间中,曲线的交点次数越多,所代表的参数越确定,画出的图形越饱满。
霍夫直线检测步骤
-
检测过程
在理论上,一个点对应无数条直线或者说任意方向的直线(在参数空间中坐标轴表示的斜率k或者说θ有无数个),但在实际应用中,我们必须限定直线的数量(即有限数量的方向)才能够进行计算。
因此,我们将直线的方向θ离散化为有限个等间距的离散值,参数ρ也就对应离散化为有限个值,于是参数空间不再是连续的,而是被离散量化为一个个等大小网格单元。将图像空间(直角坐标系)中每个像素点坐标值变换到参数空间(极坐标系)后,所得值会落在某个网格内,使该网格单元的累加计数器加1。当图像空间中所有的像素都经过霍夫变换后,对网格单元进行检查,累加计数值最大的网格,其坐标值(ρ0, θ0)就对应图像空间中所求的直线。如图7所示,为一个离散化过程。
-
使用霍夫变换检测直线具体步骤:
1.彩色图像->灰度图
2.去噪(高斯核)
3.边缘提取(梯度算子、拉普拉斯算子、canny、sobel)
4.二值化(判断此处是否为边缘点,就看灰度值==255)
5.映射到霍夫空间(准备两个容器,一个用来展示 hough-space 概况,一个数组 hough-space 用来储存voting的值,因为投票过程往往有某个极大值超过阈值,多达几千,不能直接用灰度图来记录投票信息)
6.取局部极大值,设定阈值,过滤干扰直线
7.绘制直线、标定角点
-
霍夫直线检测的优缺点
优点: Hough直线检测的优点是抗干扰能力强,对图像中直线的残缺部分、噪声以及其它共存的非直线结构不敏感,能容忍特征边界描述中的间隙,并且相对不受图像噪声的影响;
缺点: Hough变换算法的特点导致其时间复杂度和空间复杂度都很高,并且在检测过程中只能确定直线方向,丢失了线段的长度信息。由于霍夫检测过程中进行了离散化,因此检测精度受参数离散间隔制约。
API
- 在opencv中霍夫直线检测算法定义了两个函数:
HoughLines
、和HoughLinesP
CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI );
//InputArray image:输入图像,必须是8位单通道图像。
//OutputArray lines:检测到的线条参数集合。
//double rho:以像素为单位的距离步长。
//double theta:以弧度为单位的角度步长。
//int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
//double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。
//double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
//如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。
HoughLines 函数输出检测到直线的矢量表示集合,每一条直线由具有两个元素的矢量(ρ, θ)表示,其中ρ表示直线距离原点(0, 0)的长度,θ表示直线的角度(以弧度为单位)。HoughLines函数无法输出图像空间中线段的长度,这也是霍夫变换本身的弱点。
CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
//InputArray image:输入图像,必须是8位单通道图像。
//OutputArray lines:检测到的线条参数集合。
//double rho:直线搜索时的距离步长,以像素为单位。
//double theta:直线搜索时的角度步长,以弧度为单位。
//int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
//double minLineLength:默认值为0,表示最小线段长度阈值(像素)。
//double maxLineGap:线段上最近两点之间的阈值.默认值为0,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离小于该值,则被认为是一条线段,否则认为是两条线段。
HoughLinesP 能够检测出线端,即能够检测出图像中直线的两个端点,确切地定位图像中的直线。HoughLinesP函数输出检测到直线的矢量表示集合,每一条直线由具有四个元素的矢量(x1, y1, x2, y2)表示,其中(x1, y1)表示线段的起点,(x2, y2)表示线段的终点。
- 实例
void hough_lines_demo(){
Mat src = imread("F:/code/images/lines.png");
CV_Assert(!src.empty());
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
//二值图
//GaussianBlur(src, src, Size(3, 3), 0);
Mat gray, binary;
cvtColor(src, gray, COLOR_RGB2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 25, 10);
imshow("binary", binary);
//霍夫直线检测
vector<Vec3f>lines;
HoughLines(binary, lines, 1, CV_PI / 180.0, 100, 0, 0);
//绘制直线
Point pt1, pt2;
for (size_t i = 0; i < lines.size(); i++) {
float rho = lines[i][0];//距离
float theta = lines[i][1];//角度
float acc = lines[i][2];//累加值
printf("rho:%.2f,theta:%.2f,acc:%.2f\n", rho, theta, acc);
//通用代码
double a = cos(theta);
double 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));
int angle = round((theta / CV_PI) * 180);
printf("angle : %d \n", angle);
if (rho > 0) {
if (angle == 90) { // 水平线
line(src, pt1, pt2, Scalar(0, 255, 0), 1, 8, 0);
}
if (angle <= 1) {// 垂直线
line(src, pt1, pt2, Scalar(255, 0, 0), 1, 8, 0);
}
if (angle > 1 && (angle != 90)) {
line(src, pt1, pt2, Scalar(0, 0, 255), 2, 8, 0);
}
}
else {
line(src, pt1, pt2, Scalar(255, 255, 255), 1, 8, 0);
}
}
imshow("hough line detection", src);
}
void hough_linesp_demo() {
Mat src = imread("F:/code/images/morph01.png");
Mat binary;
Canny(src, binary, 80, 160, 3, false);//采用canny边缘提取得到二值图
imshow("binary", binary);
vector<Vec4i> lines;
HoughLinesP(binary, lines, 1, CV_PI / 180, 80, 100, 10);
Mat result = Mat::zeros(src.size(), src.type());
for (int i = 0; i < lines.size(); i++) {
line(result, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(0, 0, 255), 1, 8);
}
imshow("hough linesp demo", result);
}
PS:霍夫直线检测的输入条件是二值图像,那么这个二值图可以来自于canny等边缘提取的结果,也可以来自我们通过阈值分割得到的二值图!其实还可以来自于图像形态学处理的结果!