【笔记】hough变换理解

本文深入讲解Hough变换原理,从直线检测到任意形状提取,包括经典Hough变换、Hough直线变换原理,以及如何利用点线对偶思想将图像空间问题转化为参数空间问题。文章还详细介绍了算法流程、计算效率分析及统计概论霍夫直线检测的改进。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概要

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

hough直线变换

原理

hough变换利用点、线对偶的思想,把提取图像空间中直线的问题转换成在参数空间/hough空间中计算点的峰值的问题。
x−yx-yxy坐标系中,假设有一条直线过点(x0,y0)(x_0,y_0)(x0,y0),那么我们可以把这条直线的方程记为(1)y=mx+by=mx+b \tag{1} y=mx+b(1)现在,如果我们把参数和变量的身份对换一下,即(m,b)是变量,(x,y)是参数,那么公式1可以写成(2)b=−x0m+y0b=-x_0m+y_0\tag{2}b=x0m+y0(2),那么在b−mb-mbm坐标系中,(x0,y0)(x_0,y_0)(x0,y0)就确定了一条直线。
回到x−yx-yxy坐标系,对直线y=mx+by=mx+by=mx+b,记原点到它的距离是ρ\rhoρ,它的正切线与x轴的夹角是θ,θ∈[0,180]\theta,\theta \in[0,180]θθ[0,180],那么易得(3)m=−1tanθm=-\frac1{tan\theta} \tag{3}m=tanθ1(3) (4)b=ρsinθb=\frac\rho{sin\theta} \tag{4}b=sinθρ(4),把公式3,4代入公式1,整理可得(5)ρ=xcosθ+ysinθ\rho=xcos\theta+ysin\theta \tag{5}ρ=xcosθ+ysinθ(5)
由公式5可知,如果是在θ−ρ\theta-\rhoθρ坐标系中,(x0,y0)(x_0,y_0)(x0,y0)确定了一条正弦曲线
上述的所谓x−y,m−b,θ−ρx-y,m-b,\theta-\rhoxy,mb,θρ坐标系,我们分别称他们为图像空间、参数空间、hough空间。通过上面的分析可知,图像空间中的任何一个坐标点都对应着参数空间中的一条直线(或者hough空间中的一条正弦曲线),那么很多点就可以在参数空间中对应很多直线,这些直线间会相交,而每一个相交点(mi,bi)(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=−1tanθx+ρsinθ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.youkuaiyun.com/zhaocj/article/details/50281537
  • https://blog.youkuaiyun.com/viewcode/article/details/8090932
  • https://blog.youkuaiyun.com/autocyz/article/details/42649187
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值