图像处理之霍夫(Hough)变换
4、说一下霍夫(Hough)变换原理
答:霍夫(Hough)变换是一个非常重要的检测间断点边界形状的方法。它通过将图像坐标空间变换到参数空间,来实现直线和曲线的拟合。
直线检测
1.1 直线坐标参数空间
在图像x−y坐标空间中,经过点(xi,yi)的直线表示为:
yi = a*xi + b (1)
其中,参数a为斜率,b为截矩。
通过点(xi,yi)的直线有无数条,且对应于不同的a和b值。
如果将xi和yi视为常数,而将原本的参数a和b看作变量,则式子(1)可以表示为:
b = −xi*a + yi (2)
这样就变换到了参数平面a−b。这个变换就是直角坐标中对于(xi,yi)点的Hough变换。该直线是图像坐标空间中的点(xi,yi)在参数空间的唯一方程。考虑到图像坐标空间中的另一点(xj,yj),它在参数空间中也有相应的一条直线,表示为:
b = −xj*a + yj (3)
这条直线与点(xi,yi)在参数空间的直线相交于一点(a0,b0),而(a0,b0)就是图像坐标空间中点(xi,yi)和点(xj,yj)所确定的直线的参数。反之,在参数空间中相交于同一点的所有直线,在图像坐标空间中都有贡献的点与之对应。根据这个特性,给定图像坐标空间的一些边缘点,就可以通过Hough变换确定连接这些点的直线方程。如图所示:
具体计算时,可以将参数空间视为离散的。建立一个二维累加数组A(a,b),第一维的范围是图像坐标空间中直线斜率的可能范围,第二维的范围是图像坐标空间中直线截矩的可能范围。开始时A(a,b)初始化为0,然后对图像坐标空间的每一个前景点(xi,yi),将参数空间中每一个a的离散值代入式子(2)中,从而计算出对应的b值。每计算出一对(a,b),都将对应的数组元素A(a,b)加1,即A(a,b)=A(a,b)+1。所有的计算结束之后,在参数计算表决结果中找到A(a,b)的最大峰值,所对应的a0、b0就是源图像中共线点数目最多(共A(a,b)个共线点)的直线方程的参数;接下来可以继续寻找次峰值和第3峰值和第4峰值等等,它们对应于原图中共线点略少一些的直线。
【在直线坐标系下共线的点,在参数空间中都相交于同一点。】
注意:使用直角坐标表示直线,当直线为一条垂直直线或者接近垂直直线时,该直线的斜率为无限大或者接近无限大,从而无法在参数空间a-b上表示出来。为了解决这个问题,可以采用极坐标系。
1.2 极坐标参数空间
极坐标中用如下参数方程表示一条直线:
ρ = x*cosθ + y*sinθ (4)
其中,ρ代表直线到原点的垂直距离,θ代表x轴到直线垂线的角度,取值范围为±90∘如下图所示。
与直角坐标类似,极坐标中的Hough变换也将图像坐标空间中的点变换到参数空间中。在极坐标表示下,图像坐标空间中共线的点变换到参数空间中后,在参数空间都相交于同一点,此时所得到的 ρ、θ 即为所求的直线的极坐标参数。与直角坐标不同的是,用极坐标表示时,图像坐标空间的共线的两点(xi,yi)和(xj,yj)映射到参数空间是两条正弦曲线,相交于点(ρ0,θ0),如上图所示。
具体计算时,与直角坐标类似,也要在参数空间中建立一个二维数组累加器A,只是取值范围不同。对于一副大小为D×D的图像,通常
计算方法与直角坐标系中累加器的计算方法相同,最后得到最大的A所对应的(ρ,θ)。
OpenCV代码示例:
// 霍夫直线检测.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
static void help()
{
cout << "\nThis program demonstrates line finding with the Hough transform.\n"
"Usage:\n"
"./houghlines <image_name>, Default is pic1.png\n" << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
//Mat src = imread("../pic1.png", 1);
Mat src = imread("../road1.jpg", 0);
if(src.empty())
{
help();
cout << "can not open ../pic1.png" << endl;
return -1;
}
cout << "src.channels = " << src.channels() << endl;
namedWindow("source", 0);
imshow("source", src);
waitKey();
Mat mat_binary;
// binary
cv::threshold(src, mat_binary, 0, 255.0, cv::THRESH_BINARY | cv::THRESH_OTSU);
namedWindow("binary", 0);
imshow("binary", mat_binary);
waitKey();
Mat dst, cdst;
Canny(mat_binary, dst, 50, 200, 3); //边缘检测后的图像为灰度图
cout << "canny_dst.channels = " << dst.channels() << endl;
cvtColor(dst, cdst, COLOR_GRAY2BGR);
#if 0
vector<Vec2f> lines;
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 ); //标准霍夫变换
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], 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( cdst, pt1, pt2, Scalar(255,0,0), 1, CV_AA);
}
#else
vector<Vec4i> lines;
//累计概率霍夫变换
HoughLinesP(
dst, //输入图像,需为8位单通道二进制图像
lines, //存储检测到的线条的输出矢量
1, //以像素为单位的距离精度,直线搜索时的进步尺寸的单位半径
CV_PI/180, //以弧度为单位的角度精度,直线搜索时的进步尺寸的单位角度
100, //累加平面的阈值参数
100, //最低线段的长度
60 ); //允许将同一行点与点之间连接起来的最大距离
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 1, CV_AA);
}
#endif
imwrite("houghLine.jpg", dst);
namedWindow("detected lines", 0);
imshow("detected lines", cdst);
waitKey();
return 0;
}
还没看明白的,可参考此处霍夫变换直线检测(Line Detection)原理及示例_leonardohaig的博客-CSDN博客_霍夫直线检测,该博主写的还不错。