一、霍夫变换(Hough transform)
常见的理论概述是这样的:
1、简单介绍
霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,它通过一种投票算法检测具有特定形状的物体。Hough变换是图像处理中从图像中识别几何形状的基本方法之一。Hough变换的基本原理在于利用点与线的对偶性,将原始图像空间的给定的曲线通过曲线表达形式变为参数空间的一个点。这样就把原始图像中给定曲线的检测问题转化为寻找参数空间中的峰值问题。也即把检测整体特性转化为检测局部特性。比如直线、椭圆、圆、弧线等。
2、Hough变换的基本思想
设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用y=k*x+b 来表示,其中k和b是参数,分别是斜率和截距。过某一点(x0,y0)的所有直线的参数都会满足方程y0=kx0+b。即点(x0,y0)确定了一族直线。方程y0=kx0+b在参数k--b平面上是一条直线,(你也可以是方程b=-x0*k+y0对应的直线)。这样,图像x--y平面上的一个前景像素点就对应到参数平面上的一条直线。我们举个例子说明解决前面那个问题的原理。设图像上的直线是y=x, 我们先取上面的三个点:A(0,0), B(1,1), C(2,2)。可以求出,过A点的直线的参数要满足方程b=0, 过B点的直线的参数要满足方程1=k+b, 过C点的直线的参数要满足方程2=2k+b, 这三个方程就对应着参数平面上的三条直线,而这三条直线会相交于一点(k=1,b=0)。 同理,原图像上直线y=x上的其它点(如(3,3),(4,4)等) 对应参数平面上的直线也会通过点(k=1,b=0)。这个性质就为我们解决问题提供了方法,就是把图像平面上的点对应到参数平面上的线,最后通过统计特性来解决问题。假如图像平面上有两条直线,那么最终在参数平面上就会看到两个峰值点,依此类推。
简而言之,Hough变换思想为:在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。
在实际应用中,y=k*x+b形式的直线方程没有办法表示x=c形式的直线(这时候,直线的斜率为无穷大)。所以实际应用中,是采用参数方程p=x*cos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p---theta平面上的一条曲线上,其它的还是一样。
3、Hough transform的原理
要理解Hough 变换极其注意的是,原笛卡尔坐标系里的直线 上所有的点带入直线方程后,进行变换后在斜率k和截距b为横纵坐标的hough坐标系里面又是另外一条直线
其中
是已知的,相当于笛卡尔坐标系里面的斜率和截距,即
是一条Hough坐标系里面的一条直线。如果所有的参数点 均来自
上,那么其对应的Hough坐标系里面的k和b,即横纵坐标。且必有一条直线和原直线
重合,在Hough坐标系里面显示,就是过
点。
下面图片是自己对hough变换的理解:
1.1 霍夫直线变换(Hough Lines Transform)
1.1.1 直线坐标参数空间
在图像x−y坐标空间中,经过点(xi,yi)的直线表示为:
其中,参数a为斜率,b为截矩。
通过点(xi,yi)的直线有无数条,且对应于不同的a和b值。如果将xi和yi视为常数,而将原本的参数a和b看作变量,则式子(1)可以表示为:
(2)
这样就变换到了参数平面a−b。这个变换就是直角坐标中对于(xi,yi)点的Hough变换。该直线是图像坐标空间中的点(xi,yi)在参数空间的唯一方程。考虑到图像坐标空间中的另一点(xj,yj),它在参数空间中也有相应的一条直线,表示为:
(3)
这条直线与点(xi,yi)在参数空间的直线相交于一点 (a0,b0),如图所示:
图像坐标空间中过点(xi,yi)和点(xj,yj)的直线上的每一点在参数空间a−b上各自对应一条直线,这些直线都相交于点(a0,b0),而a0、b0就是图像坐标空间x−y中点(xi,yi)和点(xj,yj)所确定的直线的参数,即其中a0和b0就是原图像坐标空间直线的斜率和截距。
反之,在参数空间相交于同一点的所有直线,在图像坐标空间都有共线的点与之对应。根据这个特性,给定图像坐标空间的一些边缘点,就可以通过Hough变换确定连接这些点的直线方程。
具体计算时,可以将参数空间视为离散的。建立一个二维累加数组A(a,b),第一维的范围是图像坐标空间中直线斜率的可能范围,第二维的范围是图像坐标空间中直线截矩的可能范围。开始时A(a,b)初始化为0,然后对图像坐标空间的每一个前景点(xi,yi),将参数空间中每一个aa的离散值代入式子(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峰值等等,它们对应于原图中共线点略少一些的直线。
注意:由于原图中的直线往往具有一定的宽度,实际上相当于多条参数极其接近的单像素宽直线,往往对应于参数空间中相邻的多个累加器。因此每找到一个当前最大的峰值点后,需要将该点及其附近点清零,以防算法检测出多条极其邻近的“假”直线
对于上图的Hough变换空间情况如下图所示。
这种利用二维累加器的离散方法大大简化了Hough变换的计算,参数空间a−ba−b上的细分程度决定了最终找到直线上点的共线精度。上述的二维累加数组A也被称为Hough矩阵。
注意:使用直角坐标表示直线,当直线为一条垂直直线或者接近垂直直线时,该直线的斜率为无限大或者接近无限大,从而无法在参数空间$a-b$上表示出来。为了解决这个问题,可以采用极坐标。
1.1.2 极坐标参数空间
极坐标中用如下参数方程表示一条直线。
(4)
其中,ρ代表直线到原点的垂直距离,θ代表x轴到直线垂线的角度,取值范围为±90°,如图所示。
与直角坐标类似,极坐标中的Hough变换也将图像坐标空间中的点变换到参数空间中。
在极坐标表示下,图像坐标空间中共线的点变换到参数空间中后,在参数空间都相交于同一点,此时所得到的ρ、θ即为所求的直线的极坐标参数。与直角坐标不同的是,用极坐标表示时,图像坐标空间的共线的两点(xi,yi)和(xj,yj)映射到参数空间是两条正弦曲线,相交于点(ρ0,θ0),如上图所示。
具体计算时,与直角坐标类似,也要在参数空间中建立一个二维数组累加器A,只是取值范围不同。对于一副大小为D×D的图像,通常ρ的取值范围为[−2D/2,2D/2],θ的取值范围为[−90°,90°]。计算方法与直角坐标系中累加器的计算方法相同,最后得到最大的A所对应的(ρ,θ)。
1.2 霍夫圆变换(Hough Circles Transform)
Hough变换同样适用于方程已知的曲线检测。
图像坐标空间中的一条已知的曲线方程也可以建立其相应的参数空间。由此,图像坐标空间中的一点,在参数空间中就可以映射为相应的轨迹曲线或者曲面。若参数空间中对应各个间断点的曲线或者曲面能够相交,就能找到参数空间的极大值以及对应的参数;若参数空间中对应各个间断点的曲线或者曲面不能相交,则说明间断点不符合某已知曲线。
Hough变换做曲线检测时,最重要的是写出图像坐标空间到参数空间的变换公式。
例如,对于已知的圆方程,其直角坐标的一般方程为:
(5)
其中,(a,b)为圆心坐标,r为圆的半径。
那么,参数空间可以表示为(a,b,r),图像坐标空间中的一个圆对应参数空间中的一个点。具体计算时,与前面讨论的方法相同,只是数组累加器为三维A(a,b,r)。 计算过程是让a,b在取值范围内增加,解出满足上式的r值,每计算出一个(a,b,r)值,就对相应的数组元素A(a,b,r)加1.计算结束后,找到的最大的A(a,b,r)所对应的a,b,r就是所求的圆的参数。与直线检测一样,曲线检测也可以通过极坐标形式计算。
注意:通过Hough变换做曲线检测,参数空间的大小将随着参数个数的增加呈指数增长的趋势。所以在实际使用时,要尽量减少描述曲线的参数数目。因此,这种曲线检测的方法只对检测参数较少的曲线有意义。
霍夫圆检测原理:
- 从平面坐标到极坐标转换三个参数Cx0,y0,r其中x0,y0
是圆心
- 假设平面坐标的任意一个圆上的点,转换到极坐标中:
处有最大值,霍夫变换正是利用这个原理实现圆的检测。
1.3 任意形状的检测
这里所说的任意形状的检测,是指应用广义Hough变换去检测某一任意形状边界的图形。它首先选取该形状中的任意点(a,b)为参考点,然后从该任意形状图形的边缘每一点上,计算其切线方向ϕ和到参考点(a,b)位置的偏移矢量r,以及r与x轴的夹角α。
参考点(a,b)的位置可由下式算出:
a=x+r(ϕ)cos(α(ϕ)) (6)
b=x+r(ϕ)sin(α(ϕ)) (7)
OpenCV里面API介绍:
霍夫直线检测
- 标准的霍夫变换 cv::HoughLines从平面坐标转换到霍夫空间,最终输出是
表示极坐标空间
- 霍夫变换直线概率 cv::HoughLinesP最终输出是直线的两个点
cv::HoughLines(
InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长
double theta, //生成极坐标时候的角度步长,一般
CV_PI/180
int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double srn=0,// 是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double stn=0,//是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double min_theta=0, // 表示角度扫描范围 0 ~180之间, 默认即可
double max_theta=CV_PI)
// 一般情况是有经验的开发者使用,需要自己反变换到平面空间
cv::HoughLinesP(
InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180
Int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double minLineLength=0,// 最小直线长度
double maxLineGap=0// 最大间隔)
霍夫圆检测
相关API cv::HoughCircles
- 因为霍夫圆检测对噪声比较敏感,所以首先要对图像做滤波处理。
- 基于效率考虑,Opencv中实现的霍夫变换圆检测是基于图像梯度的实现,分为两步:
1. 检测边缘,发现可能的圆心
2. 基于第一步的基础上从候选圆心开始计算最佳半径大小
cv::HoughCircles(
InputArray image, // 输入图像 ,必须是8位的单通道灰度图像
OutputArray circles, // 输出结果,发现的圆信息
Int method, // 方法 - HOUGH_GRADIENT
Double dp, // dp = 1;
Double mindist, // 10 最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows/8
Double param1, // canny edge detection low threshold
Double param2, // 中心点累加器阈值 – 候选圆心
Int minradius, // 最小半径
Int maxradius//最大半径 )
示例程序:
一、直线检测
//霍夫变换-直线检测
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;
int main(int argc, char*argv)
{
Mat src;
src = imread("C:\\Users\\59235\\Desktop\\imag\\LineAndWord2.png");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
namedWindow("input", CV_WINDOW_AUTOSIZE);
imshow("input", src);
Mat gray_src, dst;
cvtColor(dst, gray_src, CV_GRAY2BGR);
//Canny边缘检测
Canny(gray_src, dst, 100, 200, 3);
imshow("1", gray_src);
imshow("2", dst);
vector<Vec4f>plines;//定义一个存放直线信息的向量
//Hough直线检测API
HoughLinesP(dst, plines, 1, CV_PI / 180, 70, 100, 50);
//InputArray src, // 输入图像,必须8-bit的灰度图像;OutputArray lines, // 输出的极坐标来表示直线;double rho, // 生成极坐标时候的像素扫描步长;double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180;int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线;double minLineLength = 0;// 最小直线长度;double maxLineGap = 0;// 最大间隔
//标记出直线
for (size_t i = 0; i < plines.size(); i++)
{
Vec4f point1 = plines[i];
line(src, Point(point1[0], point1[1]), Point(point1[2], point1[3]), Scalar(255, 255, 0), 2, LINE_AA);
}
imshow("result", src);
waitKey(0);
return 0;
}
效果图: (原图)
(未进行Canny边缘检测得到的效果图)
(Canny边缘检测) (绘制的直线图)
分析:由实验结果图,没有对灰度图像进行边缘检测的,而直接进行霍夫变换直线检测,显然不能很好的检测出图像的直线。因此,可以知道Canny边缘检测有助于霍夫直线变换更好的过滤干扰点,从而更准确地判断图像中的直线。
二、圆检测
//霍夫变换-圆检测
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
#include<highgui.h>
using namespace cv;
using namespace std;
int main(int argc, char*argv)
{
Mat src;
src = imread("C:\\Users\\59235\\Desktop\\imag\\mixed_01.png");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
namedWindow("input", CV_WINDOW_AUTOSIZE);
imshow("input", src);
Mat gray_src, dst, dst1;
medianBlur(src, dst, 5);//因为霍夫圆检测对噪声比较敏感,故须先做中值滤波
cvtColor(dst, gray_src, CV_BGR2GRAY);
imshow("gray", gray_src);
//Canny检测边缘
//Canny(dst, dst1, 100, 200, 3);
//hough变换检测圆(基于灰度空间)
vector<Vec3f> circles;
HoughCircles(gray_src, circles, HOUGH_GRADIENT, 1, 10, 40, 40, 5, 100);
//InputArray image, // 输入图像 ,必须是8位的单通道灰度图像
//OutputArray circles, // 输出结果,发现的圆信息
//Int method, // 方法 - HOUGH_GRADIENT
//Double dp, // dp = 1;
//Double mindist, // 10 最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows/8
//Double param1, // canny edge detection low threshold
//Double param2, // 中心点累加器阈值 – 候选圆心
//Int minradius, // 最小半径
//Int maxradius//最大半径
//cvtColor(gray_src, dst1, CV_GRAY2BGR);//重新转回RGB色彩空间
//标记出圆和圆心
for (size_t i = 0; i <circles.size(); i++)
{
Vec3f cc = circles[i];
cout << "=" << endl << cc << endl;//查看图像中圆的信息
circle(src, Point(cc[0], cc[1]), cc[2], Scalar(0, 0, 255), 2, LINE_AA);//标记出圆
circle(src, Point(cc[0], cc[1]), 2, Scalar(255, 0, 0), 2, LINE_AA);//标记出圆心(这里把圆的半径设为2,并把标记线的粗细设为2,刚好画出一个实心的圆心)
}
imshow("result", src);
waitKey(0);
return 0;
}
效果图:
(原图) (灰度图)
(Canny检测边缘图) (结果图)
(并未检测边缘,直接对灰度图检测的结果)
分析:本实验有点失败,在对灰度图像进行边缘检测的基础上,再进行霍夫圆检测,这种正确的步骤的基础上,却得到一个效果相当差的结果。而不进行边缘检测,直接进行霍夫圆检测的结果,是只能检测出工件的内接圆,其他一些外接圆则不能检测出来。这个实验还有待进行下一步及改进,我觉得应该出在对图像的预处理这一块,OpenCV里面的圆检测这个应该是没什么问题的。