霍夫直线变换的核心在于笛卡尔坐标系和霍夫空间的变换,笛卡尔坐标系下的直线在霍夫空间中表示为点;笛卡尔坐标系下的点在霍夫空间中表示为曲线,如果霍夫空间中的多条曲线交于同一个点,则在笛卡尔坐标系中就表现为多个点组成了一条直线,这就给了我们找出图像中直线的方法——找出霍夫空间中多条曲线的交点。
笛卡尔坐标系到霍夫空间的变换实际上就是到极坐标系的变换。一条直线可以用斜截式表示为: y = k x + b y=kx+b y=kx+b(斜率 ∞ \infty ∞暂时不考虑),设原点到该直线的垂线与X轴正半周的夹角为 θ 0 \theta_0 θ0,则由两垂线斜率互为负倒数的关系可以得到 k = − c o s θ s i n θ k=-{{cos\theta}\over{sin\theta}} k=−sinθcosθ(此时若 k = ∞ k=\infty k=∞, θ = 0 \theta=0 θ=0即可),再设直线距离为 ρ 0 \rho_0 ρ0,则可以得到 b = ρ s i n θ b={{\rho}\over{sin\theta}} b=sinθρ,从而这条直线也就由 ρ 0 、 θ 0 \rho_0、\theta_0 ρ0、θ0两参数唯一确定: ρ 0 = x c o s θ 0 + y s i n θ 0 \rho_0=xcos\theta_0+ysin\theta_0 ρ0=xcosθ0+ysinθ0,这就完成了直线的坐标系变换;而假设存在一个点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),则经过他的直线必然满足 ρ = x 0 c o s θ + y 0 s i n θ \rho=x_0cos\theta+y_0sin\theta ρ=x0cosθ+y0sinθ,这就实现了点的坐标系变换。
第一张图片给出了直线的霍夫变换过程,第二张图给出了点的霍夫变换,由图二可以看出在同一直线上的三个点映射到霍夫空间上的曲线确实经过了同一点。
霍夫空间中 θ \theta θ的范围为0~180,这是因为原直线斜率的范围就在0~180度范围内变动,给出原直线与x轴正方向夹角 α \alpha α、斜率k和 θ \theta θ的对应关系如下:
α \alpha α | k | θ \theta θ |
---|---|---|
0 | 0 | 90 |
45 | 1 | 135 |
90 | ∞ \infty ∞ | 180(0) |
135 | -1 | 45 |
180(0) | 0 | 90 |
霍夫直线变换的算法思想分为以下几个步骤:
opencv函数原型:
HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
ouble srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI );
第一个参数为输入图像,应该为8位单通道边缘图像,即原图经过边缘检测之后的图像;
第二个参数为输出直线,通常二维向量vecto
第三个参数为角度分辨率,越小识别出直线越多;
第四个参数为距离分辨率,越小识别出的直线越多;
第五个参数为阈值,即霍夫空间中某点被多少条曲线经过才可认为是直线;
第六、七个参数用于多尺度的霍夫直线检测,取0时就使用普通的霍夫直线检测,多尺度还没涉及,搁置。
第八、九个参数为霍夫空间中 θ \theta θ的取值范围,通常就是取默认值0~180;
实例程序:
int main()
{
Mat src,gray,dst;
vector<Vec2f> lines; //二维向量,接收theta和r
src = imread("E:\\material\\building.png");
if (src.empty())
{
cout << "未找到该图片";
return -1;
}
Canny(src, gray, 50,200 );
cvtColor(gray, dst, COLOR_GRAY2BGR);
HoughLines(gray,lines,1,CV_PI/180,200); //霍夫直线检测,三参数为扫描像素时的步长,四参数为扫描像素时的角度步长
//画图
for(size_t i =0;i<lines.size();i++)
{
float rho = lines[i][0], theta = lines[i][1]; //记录直线特征值
Point p1, p2;
double a = cos(theta),b = sin(theta);
double x0 = a * rho, y0 = b * rho;
p1.x = cvRound(x0 + 1000 * (-b));
p1.y = cvRound(y0 + 1000 * a);
p2.x = cvRound(x0 - 1000 * (-b));
p2.y = cvRound(y0 - 1000 * a); //因为不知道端点,所以这里取直线长度为1000,以直线和其过原点的法线的交点为原点左右延长
line(dst, p1, p2, Scalar(55, 100, 195),1,LINE_AA);
}
imshow("原图", src);
imshow("边缘检测后图", gray);
imshow("霍夫变换后", dst);
waitKey(0);
}
霍夫概率变化的原理和霍夫直线变换的数学原理是一样的,没有区别,都是将笛卡尔坐标系上的点映射为霍夫空间上的曲线,再根据每个点所被经过的曲线次数多少还原为直线。
在算法层面上,霍夫概率变换和霍夫直线变换就产生了些许差异,它的基本步骤如下:
opencv源码:
HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
第一个参数为输入图像,应该为8为单通道边缘图;
第二个参数为输出直线数组,通常为具有四个元素的向量vector,保存了直线的端点;
第三个参数为角度分辨率;
第四个参数为距离分辨率;
第五个参数为霍夫空间的阈值,即一个点要被多少曲线经过才认为对应直线;
第六个参数为判断好直线的最小长度;
第七个参数为直线的最小间断距离,即能能容忍的最大间隔;
示例程序:
int main()
{
Mat src, gray, dst;
src = imread("E:\\material\\building.png");
if (src.empty())
{
cout << "not found the picture";
return -1;
}
Canny(src, gray, 100, 200, 3); //边缘检测
cvtColor(gray, dst, COLOR_GRAY2BGR);
vector<Vec4i> lines; //创建容器容纳向量
HoughLinesP(gray, lines, 1, CV_PI / 180, 130, 50, 10); //倒数第二个参数为线段最短距离,倒数第一个为当两直线距离小于此值是认为为同一条直线
for (size_t i = 0;i < lines.size();i++)
{
Vec4i l=lines[i];
line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(142, 54, 231), 1);
}
imshow("原图", src);
imshow("累积概率霍夫变换", dst);
waitKey(0);
}
参考文献:
霍夫线/圆变换从原理到源码详解
LSD直线检测和霍夫线变换的学习建议
Opencv源代码分析HoughLines
OpenCV3』霍夫变换原理及实现