hough变换最早Paul Hough提出,用来提取图像中的直线,后来Richard Duda和Peter Hart推广到提取图像中任意形状,多为圆和椭圆。本文学习经典hough变换。
hough变换利用点、线对偶的思想
,把提取图像空间中直线
的问题转换成在参数空间/hough空间中计算点的峰值
的问题。
在 x − y x-y x−y坐标系中,假设有一条直线过点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),那么我们可以把这条直线的方程记为 (1) y = m x + b y=mx+b \tag{1} y=mx+b(1)现在,如果我们把参数和变量的身份对换一下,即(m,b)是变量,(x,y)是参数
,那么公式1可以写成 (2) b = − x 0 m + y 0 b=-x_0m+y_0\tag{2} b=−x0m+y0(2),那么在 b − m b-m b−m坐标系中, ( x 0 , y 0 ) (x_0,y_0) (x0,y0)就确定了一条直线。
回到 x − y x-y x−y坐标系,对直线 y = m x + b y=mx+b y=mx+b,记原点到它的距离是 ρ \rho ρ,它的正切线与x轴的夹角是 θ , θ ∈ [ 0 , 180 ] \theta,\theta \in[0,180] θ,θ∈[0,180],那么易得 (3) m = − 1 t a n θ m=-\frac1{tan\theta} \tag{3} m=−tanθ1(3) (4) b = ρ s i n θ b=\frac\rho{sin\theta} \tag{4} b=sinθρ(4),把公式3,4代入公式1,整理可得 (5) ρ = x c o s θ + y s i n θ \rho=xcos\theta+ysin\theta \tag{5} ρ=xcosθ+ysinθ(5)
由公式5可知,如果是在 θ − ρ \theta-\rho θ−ρ坐标系中, ( x 0 , y 0 ) (x_0,y_0) (x0,y0)就确定了一条正弦曲线
。
上述的所谓 x − y , m − b , θ − ρ x-y,m-b,\theta-\rho x−y,m−b,θ−ρ坐标系,我们分别称他们为图像空间、参数空间、hough空间
。通过上面的分析可知,图像空间中的任何一个坐标点都对应着参数空间中的一条直线(或者hough空间中的一条正弦曲线)
,那么很多点就可以在参数空间中对应很多直线,这些直线间会相交,而每一个相交点 ( m i , b i ) (m_i,b_i) (mi,bi)都代表着在此处相交的若干条直线 对应着的在图像空间中的哪些点应该在同一条直线上,并且这条直线的(斜率,截距)就是(m_i,b_i)
,此时,我们只需要计算哪些相交点有更多的直线经过,那这些相交点就更有可能是我们想要提取的原图像中的直线。
在实际操作时,我们使用 θ , ρ \theta,\rho θ,ρ,因为有些直线的斜率根本不存在或者很大,比并且 ( θ i , ρ i ) (\theta_i,\rho_i) (θi,ρi)也能表示图像空间中的一条直线,把公式3,4代入1 (6) y = − 1 t a n θ x + ρ s i n θ y=-\frac1{tan\theta}x+\frac\rho{sin\theta} \tag{6} y=−tanθ1x+sinθρ(6)
num_angle=180;
num_rho=(int)(im_w+im_h)/2;
vector _accum((numangle+2) * (numrho+2));//用一维数组表示二维矩阵
//因为需要多次用到sin/cos值,先做个表,直接读表`
vector tabSin(numangle);
vector tabCos(numangle);
for(int n = 0; n < numangle; n++ )
{
tabSin[n] = sin(n*3.1415926/180);
tabCos[n] = cos(n*3.1415926/180);
}
memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );
for( i = 0; i < img_h; i++ )
for( j = 0; j < img_w; j++ )
{
if( image[i * step + j] != 0 )# 需要是边缘点
for(int n = 0; n < numangle; n++ )
{
int r = cvRound( j * tabCos[n] + i * tabSin[n] );# 向上取整
r += (numrho - 1) / 2;
accum[(n+1) * (numrho+2) + r+1]++; # (theta_i,rho_i)对出现次数加1
}
}
houghlines的计算效率比较低O(im_w*im_h*numangle),耗时较长,而且没有检测出直线的端点。
改进
统计概论霍夫直线检测houghlinesP是一个改进,不仅执行效率较高,而且能检测到直线的两个端点。
思想:先随机检测出一部分直线,然后将直线上点的排查掉,再进行其他直线的检测
a)首先仅统计图像中非零点的个数,对于已经确认是某条直线上的点就不再变换了。
b)对所以有非零点逐个变换到霍夫空间
- 并累加到霍夫统计表(图像)中,并统计最大值
- 最大值与阈值比较,小于阈值,则继续下一个点的变换
- 若大于阈值,则有一个新的直线段要产生了
- 计算直线上线段的端点、长度,如果符合条件,则保存此线段,并mark这个线段上的点不参与其他线段检测的变换
opencv3的标准hough变换关键代码截取
源码在opencv_path/source/opencv-x.x.x/modules/imgproc/src/hough.cpp
static void
HoughLinesStandard( const Mat& img, float rho, float theta,
int threshold, std::vector& lines, int linesMax,
double min_theta, double max_theta )
{
...
...
int numangle = cvRound((max_theta - min_theta) / theta);//离散化theta
int numrho = cvRound(((width + height) * 2 + 1) / rho);//离散化rho
...
AutoBuffer _accum((numangle+2) * (numrho+2));//计数器,统计参数对出现的次数
std::vector _sort_buf;
AutoBuffer _tabSin(numangle);//sin,cos表
AutoBuffer _tabCos(numangle);
int *accum = _accum;
float *tabSin = _tabSin, *tabCos = _tabCos;
memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );
//建表 sin/cos
float ang = static_cast(min_theta);
for(int n = 0; n < numangle; ang += theta, n++ )
{
tabSin[n] = (float)(sin((double)ang) * irho);
tabCos[n] = (float)(cos((double)ang) * irho);
}
// stage 1. fill accumulator ,第一步,填充计数器
for( i = 0; i < height; i++ )
for( j = 0; j < width; j++ )
{
if( image[i * step + j] != 0 )
for(int n = 0; n < numangle; n++ )
{
int r = cvRound( j * tabCos[n] + i * tabSin[n] );
r += (numrho - 1) / 2;
accum[(n+1) * (numrho+2) + r+1]++;
}
}
// stage 2. find local maximums,第二步,在寻找4连通域最大值
for(int r = 0; r < numrho; r++ )
for(int n = 0; n < numangle; n++ )
{
int base = (n+1) * (numrho+2) + r+1;
if( accum[base] > threshold &&
accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] &&
accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] )
_sort_buf.push_back(base);
}
//排序,输出 参数对儿
...
}
使用opencv::hough
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
Mat im_src, im_edge;
src=imread("test.jpg");
if( !src.data ) return -1;
//Canny边缘检测,这个是hough变换的前提!!!
Canny(im_src,im_edge,40,180,3);
vector lines;//检测到的(theta,rho)对儿
//距离分辨率为1,角度分辨率为π/180,阈值为215
HoughLines(im_edge,lines,1,CV_PI/180,215,0,0);
//画线
for( inti = 0; i < lines.size();++i)
{
float rho = lines[i][0], theta = lines[i][1];
//计算得到的两点的坐标为(ρcosθ-1000sinθ,ρsinθ+1000cosθ),(ρcosθ+1000sinθ,ρsinθ-1000cosθ,这里的1000是为了让直线的两个端点离的更远
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));
//调用opencv库函数在图中把以pt1,pt2为端点的线画出
line( im_src, pt1, pt2, Scalar(0,255,0),2);
line(im_edge,pt1,pt2,Scalar(0,255,0),2);
}
imshow( "hough", im_src );
waitKey(0);
return 0;