文章目录
1.霍夫变换综述
这部分主要参考YuYuanTang大佬的博客
1.1.历史发展
霍夫变换的发展大致可以分为三个阶段:
- 1962年,Paul Hough发表了名为Method and Means for Recognizing Complex Patterns(用于识别复杂图案的方法和手段)的专利,其中描述的就是最初形态的霍夫变换。采用的是笛卡尔坐标系下的截距参数,由于垂线的斜率无限大会导致无限变换空间的问题(unbounded transform space)。
- 1972年,Richard Duda和Peter Hart提出了“广义霍夫变换”[GHT],(Use of the Hough Transformation to Detect Lines and Curves in Pictures,1972)。可以用来检测图像中的直线和曲线(最常见的是直线,圆和椭圆),最大的优势在于分割结果对不完整数据或噪声不敏感,具有较好鲁棒性。。
- 1981年,Dana H. Ballard的计算机视觉社区中出现一篇文章名为 Generalizing the Hough transform to detect arbitrary shapes,利用模板匹配的方法将霍夫变换推广到了任意形状。他们利用指定形状的任意非解析形状的边界来构造图像空间和霍夫空间的映射,进而利用这个映射检测图像中指定形状的实例,形状的变化(旋转、缩放等)在映射中是直接转换。最重要的是,该方法可以利用简单的形状映射组成复杂形状映射,实现任意形状的霍夫变换。
在此之后也出现了诸如KHT,3DKHT这样的变种,这里暂且不论。
1.2.简介
霍夫变换是图像处理和计算机视觉中的一种特征提取技术。其运用图像空间和霍夫空间两个坐标空间的变换将一个空间中具有相同形状的曲线或直线映射到另一个坐标空间内的点上投票,形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
霍夫变换最重要的特点是:由于引入了概率峰值统计,使得其对于噪声或者不完整形状具有较好的鲁棒性。
2.霍夫直线检测
2.1.霍夫直线检测概论
霍夫直线检测利用图像与霍夫空间中点与线的对偶性1,将图像空间中待检测的离散像素点集通过参数方程映射为霍夫空间中极坐标上的曲线集,并将曲线的交点作为通过n个离散点的直线方程参数映射到图像空间中的直线。即点(图像空间)-线(霍夫空间)-交点(霍夫空间)-线(图像空间)。将找直线问题转换为找交点问题,同时考虑到笛卡尔坐标系存在垂直线无法检测的问题,因而利用极坐标去表示,转换为曲线交点检测。
2.2.霍夫直线检测原理
2.2.1.笛卡尔坐标系的霍夫直线检测
这一段更具体可以参考资料这里
霍夫空间变换是一种非常简单的变换,其实就是直线方程y=kx+q的另一种写法:
- 图像空间:y=kx+q (变量是y和x)
- 霍夫空间:q=-xk+y(变量是q和k)
根据分析,图像与霍夫空间(笛卡尔坐标系下)存在以下4种对应关系(对偶性):
- 图像空间中的一个点对应霍夫空间的一条直线。
- 图像空间的一条直线对应霍夫空间的一个点
- 图像空间中共点的线,在霍夫空间中的对应点共于一线
- 图像空间中共线的点,在霍夫空间中的对应直线交于一点(该点k,q即共线直线参数),这一条是霍夫直线变换的主要原理。
4种情况如下图所示(图片内容来自这里)
但是,按照直角坐标系表示的话会出现下图的情况:当图像空间中点共的线垂直于x轴时,斜率无限大,在霍夫空间无法找到交点。因而,人们最终引入了极坐标的表示法。
2.2.2.极坐标下的霍夫直线检测
极坐标下的霍夫直线检测原理与直角坐标系下完全一致,唯一需要重新推导的是与霍夫空间的极坐标参数函数,这一段图片和公式摘自这里:
给定图像空间一条直线的θ,r(r是直线到原点的垂线距离),其极坐标空间表达式为
转换为θ,r为变量的形式
因而,对于给定的图像空间下坐标x,y,霍夫空间中表现为一条正弦曲线,而给定共线的离散点在霍夫空间中对应的曲线交点,即为共线直线的参数。正弦曲线的形状取决于点到所定义原点的距离r,r越大振幅越大。
2.2.3.霍夫直线检测的实现方法
上述都是基本原理,下面要讨论的是具体情况,即给定一副边缘检测过后的图像边缘点集,如何检测图像中的直线段?
&emsp首先,我们肯定不知道过边缘点的哪条直线是直线边缘,过一个边缘点可以有无数条直线,而这无数条直线在霍夫空间的对应点形成一条曲线。为了简化计算这条曲线,我们就需要采样,也就是量化θ,r的值。所以霍夫空间(参数空间)不是连续,而是由一个个矩形单元格(累计单元)组成的累计数组。也就是说,根据边缘点我们得到参数方程,参数方程根据我们给定θ,r的分辨率,在参数空间中为每个对应点计一票,将每个边缘点重复这个过程,如果有直线存在则必然会出现某些点投票数是局部最大值(也就是峰值),我们只需要设定阈值就可以提取出这些代表直线参数的点。
借用4中的一句话:图像中存在的直线可以作为累计数组中的高值累计单元被检测出来,检测到的直线参数由累计数组的坐标给出,结果是图像中直线的检测被为累计空间中的局部极值的检测。
下面我们以YuYuanTang大佬的博客中的实例来简单介绍一下
从上至下,左图依次为边缘提取后原图、添加椒盐噪声、存在截断的轮廓,中图为霍夫空间中投票情况,颜色越亮票数越高,右图为找到的对应直线。从中我们可以发现几个性质:
- 检测直线和原始图像线的对齐精度并不完美,而这取决于累加器数组的量化程度
- 从还原的直线情况来看,霍夫直线检测对于噪声、间断有鲁棒性。
2.2.4.基本霍夫变换的局限性
这部分依然主要参考YuYuanTang大佬的博客的内容。
- 投票箱大小的选择:因为计算结果不可能都正好等于量化值,所以需要设定投票箱,类似于投票阈值,满足该阈值内的点计入该投票箱,霍夫变换需要大量选票进入正确分箱时才能有效,因此投票箱大小设计很关键。
- 多参数情况的霍夫变换:当复杂性状检测涉及三个以上参数霍夫变换时,单箱内的平均投票非常低,复杂度增加较快,因而要小心应用于线和圆的其他情况。
- 噪声依然是影响霍夫变换的比较麻烦的问题。
2.3.OpenCV实现
Opencv中的霍夫线变换的直接输入只能是边缘二值图像,因而需要先对图像进行边缘检测处理(比如canndy,sobel算子)。Opencv支持三种类型的霍夫线变换,即
- 标准霍夫变换(Standard Hough Transform,SHT),通过HoughLine函数调用
- 多尺度霍夫变换(Multi-Scale HoughTransform,MSHT),即经典霍夫变换在多尺度下的变种,通过HoughLine函数调用
- 累计概率霍夫变换(Progressive Probabilistic Transform,PPHT),即SHT的改进算法,缩短计算时间。之所以称PPHT为“概率”的,是因为并不将累加器平面内的所有可能的点累加,而只是累加其中的一部分,如果峰值足够高,则只需要一小部分时间去寻找它就够了,从而实质性地减少计算时间。通过HoughLinesP调用,执行效率很高,建议使用。
2.3.1.HoughLine函数
HoughLine(inputimage,outputline,double rho,double theta,int tereshold,double srn=0,double stn=0)
@para1:输入图像,需要为8位单通道二进制图像
@para2:储存输出的矢量线条,用θ,ρ表示。ρ为直线离坐标原点(图像左上角)的距离。θ表示弧度线条旋转角度(0为垂线,π/2为水平线)
@para3:以像素为单位的距离精度(即搜索直线时ρ轴的步进分辨率,一般为1)
@para4:以弧度为单位的角度精度(即搜索直线时θ轴的步进分辨率,一般为CV_PI/180,即1度)
@para5:累加空间的阈值参数,即识别直线时在累加空间必须达到的阈值,大于threshold的线段才能被认为检测到。
@para6:默认值0。对于多尺度霍夫变换srn表示@para3步进分辨率的除数距离。
@para7:默认值0。对于多尺度霍夫变换stn表示@para4步进分辨率的除数距离。如果srn和stn都为0时表示经典霍夫变换,否则应为正数。
2.3.2.HoughLineP函数
HoughLineP(inputimage,outputline,double rho,double theta,int tereshold,double minLineLength=0,double maxLineGap=0)
@para1:输入图像,需要为8位单通道二进制图像
@para2:储存输出的矢量线条,用4个矢量表示(x_1,y_1,x_2,y_2),其中(x_1,y_1),(x_2,y_2)分别为直线起始点和结束点
@para3:以像素为单位的距离精度(即搜索直线时ρ轴的步进分辨率,一般为1)
@para4:以弧度为单位的角度精度(即搜索直线时θ轴的步进分辨率,一般为CV_PI/180,即1度)
@para5:累加空间的阈值参数,即识别直线时在累加空间必须达到的阈值,大于threshold的线段才能被认为检测到。
@para6:默认值0。表示最低线段的长度,低于该阈值长度不显示
@para7:默认值0。允许将同一行点与点连接起来的最大距离
此函数在HoughLine基础上加了个代表概率的P,表示它采用累计概率霍夫变换(PPHT)来找直线。 注意到,HoughLineP函数是可以直接得到线段的(输出line用四个矢量控制),所以输出的线段可以直接绘制。
2.3.3.OpenCV霍夫直线检测基本流程
- 读取图像,边缘检测+灰度图转换
- 预定义一个Vector2f储存line,调用HoughLineP
- 在图中依次绘制出每条线段
3.霍夫圆检测
可以参考YuYuanTang大佬的博客的内容,因为暂时不需要用到所以暂且不论。
4.综合实例应用
背景无杂波的情况下,效果是非常好的,这里我主要简述我其他应用时发现的一些问题。希望有能人士能够解答。
如下图所示,我以图1为原图进行了霍夫直线检测,图2是没有高斯模糊的canny边缘提取结果。
图3是用HoughLines函数提取出的效果,可以看出原图噪声影响非常的严重,已经什么都看不出来了
图4是加了高斯模糊并用HoughLinesP函数调参的结果,虽然提取出了一些边缘,但是车牌的竖直边无论如何都提不出来让我非常费解,同样的情况在图5和图6中也非常明显,斑马线和房屋的顶线在canny边缘中非常明显但就是提不出来。
OpenCV3代码如下:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;
int main()
{
//Mat oriPhoto = imread("D:\\Project All\\opencv3\\car_recognition\\car_recognition\\resource\\car-reco.jpg", IMREAD_COLOR);
Mat oriPhoto = imread("C:\\笔记图片\\test2.jpg", IMREAD_COLOR);
if (oriPhoto.empty())
{
return -1;
}
Mat midImage,dstImage;
Mat gbImg;
//高斯模糊去噪
GaussianBlur(oriPhoto, gbImg, Size(7, 7), 0);
//转换为灰度图像
cvtColor(gbImg, dstImage, COLOR_BGR2GRAY);
//canny算子检测边缘
Canny(dstImage, midImage, 50, 200, 3);
//houghLinesP函数
vector<Vec4i> lines;
HoughLinesP(midImage, lines, 1, CV_PI / 180, 60, 30, 10);
//依次绘制线段
for (size_t i = 0; i < lines.size() ;i++)
{
Vec4i l = lines[i];
line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(50, 180, 255), 1, LINE_AA);
}
/*HoughLines函数(测试备用)
vector<Vec2f> lines;
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 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(dstImage, pt1, pt2, Scalar(255, 0, 0), 1, CV_AA);
}
*/
imshow("yuantu", oriPhoto);
imshow("canny", midImage);
imshow("effect", dstImage);
waitKey();
return 0;
}
对偶性:傅里叶变换中的时域与频域表达就是一种典型的对偶性,它们虽然理论不同,但是描述的是同样物理结果 ↩︎