【笔记】hough变换理解

概要

hough变换最早Paul Hough提出,用来提取图像中的直线,后来Richard Duda和Peter Hart推广到提取图像中任意形状,多为圆和椭圆。本文学习经典hough变换。

hough直线变换

原理

hough变换利用点、线对偶的思想,把提取图像空间中直线的问题转换成在参数空间/hough空间中计算点的峰值的问题。
x − y x-y xy坐标系中,假设有一条直线过点 ( 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 bm坐标系中, ( x 0 , y 0 ) (x_0,y_0) (x0,y0)就确定了一条直线。
回到 x − y x-y xy坐标系,对直线 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 xy,mb,θρ坐标系,我们分别称他们为图像空间、参数空间、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)

算法流程

  • 提取图像边缘,这是hough变换的前提
  • 构建一个 ( θ , ρ ) (\theta,\rho) (θ,ρ)二维矩阵计数器,用来记录 ( θ i , ρ i ) (\theta_i,\rho_i) (θi,ρi)对出现的次数
num_angle=180;
num_rho=(int)(im_w+im_h)/2;
vector<int> _accum((numangle+2) * (numrho+2));//用一维数组表示二维矩阵
 //因为需要多次用到sin/cos值,先做个表,直接读表`
vector<float> tabSin(numangle);
vector<float> 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
                }
        }
  • θ , ρ \theta,\rho θ,ρ计数矩阵里找4连通域的最大值
  • 把这些最大值排排序,越靠前的就越可能是要提取的直线
  • 上面步骤完成后,得到的只是很多对 ( θ i , ρ i ) (\theta_i,\rho_i) (θi,ρi),把它们代入公式6即可

分析

houghlines的计算效率比较低O(im_w*im_h*numangle),耗时较长,而且没有检测出直线的端点。
改进
统计概论霍夫直线检测houghlinesP是一个改进,不仅执行效率较高,而且能检测到直线的两个端点。
思想:先随机检测出一部分直线,然后将直线上点的排查掉,再进行其他直线的检测

a)首先仅统计图像中非零点的个数,对于已经确认是某条直线上的点就不再变换了。
b)对所以有非零点逐个变换到霍夫空间
- 并累加到霍夫统计表(图像)中,并统计最大值
- 最大值与阈值比较,小于阈值,则继续下一个点的变换
- 若大于阈值,则有一个新的直线段要产生了
- 计算直线上线段的端点、长度,如果符合条件,则保存此线段,并mark这个线段上的点不参与其他线段检测的变换

附录

附录1

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<Vec2f>& 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<int> _accum((numangle+2) * (numrho+2));//计数器,统计参数对出现的次数
    std::vector<int> _sort_buf;
    AutoBuffer<float> _tabSin(numangle);//sin,cos表
    AutoBuffer<float> _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<float>(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);
        }

	//排序,输出 参数对儿
    ...
}

附录2

使用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<Vec2f> 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;

参考

  • https://blog.csdn.net/zhaocj/article/details/50281537
  • https://blog.csdn.net/viewcode/article/details/8090932
  • https://blog.csdn.net/autocyz/article/details/42649187
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值