Opencv2.4.9源码分析——HoughLines

霍夫变换是一种特征提取技术。经典的霍夫变换能够识别出图像中的直线,后来又发展到能够识别出任意形状,但更常见的识别形状是圆和椭圆。函数HoughLines的作用就是能够检测出边缘图像中的直线。

在直角坐标系下,直线被定义为:

y = mx + b         (1)

其中,m为斜率,b为与y轴的截距,只要确定了mb,一条直线就可以被唯一地确定下来。如果用ρ0表示原点到该直线的代数距离,θ0表示该直线的正交线与x轴的夹角,则:

  (2)

      (3)

则该直线又可表示为:

     (4)

写成更一般的形式:

       (5)

很容易想到,(ρθ)是极坐标的表示形式。但如果把(ρθ)也用直角坐标的形式表示,即把ρθ做正交处理,则(ρθ)就被称为霍夫空间。

在直角坐标系中的一点,对应于霍夫空间的一条正弦曲线。直线是由无数个点组成的,在霍夫空间就是无数条正弦曲线,但这些正弦曲线会相交于一点(ρ0θ0),把该点带入公式2和公式3就得了直线的斜率和截距,这样一条直线就被确定了下来。因此在用霍夫变换识别直线时,霍夫空间中的极大值就有可能对应一条直线。

下面给出函数HoughLines的霍夫变换直线检测的步骤:

1、对边缘图像进行霍夫空间变换;

2、在4邻域内找到霍夫空间变换的极大值;

3、对这些极大值安装由大到小顺序进行排序,极大值越大,越有可能是直线;

4、输出直线。

 

函数HoughLines的原型为:

void HoughLines(InputArray image,OutputArray lines, double rho, double theta, int threshold, double srn=0,double stn=0 )

image为输入图像,要求是单通道的二值图像

lines为输出直线向量,两个元素的向量(ρθ)代表一条直线,ρ是从原点(图像的左上角)的距离,θ是直线的角度(单位是弧度),0表示垂直线,π/2表示水平线

rho为距离分辨率

theta为角度分辨率

threshold为阈值,在步骤2中,只有大于该值的点才有可能被当作极大值,即至少有多少条正弦曲线交于一点才被认为是直线

srn和stn在多尺度霍夫变换的时候才会使用,在这里我们只研究经典霍夫变换

 

 

HoughLines函数是在sources/modules/imgproc/src/hough.cpp文件中定义的:

void cv::HoughLines( InputArray _image, OutputArray _lines,
                     double rho, double theta, int threshold,
                     double srn, double stn )
{
    //申请一段内存,用于存储霍夫变换后所检测到的直线
    Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
    //提取输入图像矩阵
    Mat image = _image.getMat();
    CvMat c_image = image;
    //调用由C语音写出的霍夫变换的函数
    CvSeq* seq = cvHoughLines2( &c_image, storage, srn == 0 && stn == 0 ?
                    CV_HOUGH_STANDARD : CV_HOUGH_MULTI_SCALE,
                    rho, theta, threshold, srn, stn );
    //把由cvHoughLines2函数得到的霍夫变换直线序列转换为数组
    seqToMat(seq, _lines);
}

 

cvHoughLines2函数是霍夫变换直线检测的关键,函数的输出为所检测到的直线序列,它的第3个形参“srn == 0 && stn == 0 ? CV_HOUGH_STANDARD :CV_HOUGH_MULTI_SCALE”表示的是该霍夫变换是经典霍夫变换还是多尺度霍夫变换,它是由变量srn和stn决定的,只要这两个变量有一个不为0,就进行多尺度霍夫变换,否则为经典霍夫变换。另外cvHoughLines2函数不仅可以用于经典霍夫变换和多尺度霍夫变换,还可以用于概率霍夫变换。

CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, void* lineStorage, int method,
               double rho, double theta, int threshold,
               double param1, double param2 )
{
    CvSeq* result = 0;

    CvMat stub, *img = (CvMat*)src_image;
    CvMat* mat = 0;
    CvSeq* lines = 0;
    CvSeq lines_header;
    CvSeqBlock lines_block;
    int lineType, elemSize;
    int linesMax = INT_MAX;    //输出最多直线的数量,设为无穷多
    int iparam1, iparam2;

    img = cvGetMat( img, &stub );
    //确保输入图像是8位单通道
    if( !CV_IS_MASK_ARR(img))
        CV_Error( CV_StsBadArg, "The source image must be 8-bit, single-channel" );
    //内存空间申请成功,以备输出之用
    if( !lineStorage )
        CV_Error( CV_StsNullPtr, "NULL destination" );
    //确保rho,theta,threshold这三个参数大于0
    if( rho <= 0 || theta <= 0 || threshold <= 0 )
        CV_Error( CV_StsOutOfRange, "rho, theta and threshold must be positive" );

    if( method != CV_HOUGH_PROBABILISTIC )    //经典霍夫变换和多尺度霍夫变换
    {
        //输出直线的类型为32位浮点双通道,即ρ和θ两个浮点型变量
        lineType = CV_32FC2;
        elemSize = sizeof(float)*2;
    }
    else    //概率霍夫变换
    {
        //输出直线的类型为32位有符号整型4通道,即两个像素点的4个坐标
        lineType = CV_32SC4;
        elemSize = sizeof(int)*4;
    }
    //判断lineStorage的类型,经分析lineStorage只可能是STORAGE类型
    if( CV_IS_STORAGE( lineStorage ))
    {
        //在lineStorage内存中创建一个序列,用于存储霍夫变换的直线,lines为该序列的指针
        lines = cvCreateSeq( lineType, sizeof(CvSeq), elemSize, (CvMemStorage*)lineStorage );
    }
    else if( CV_IS_MAT( lineStorage ))
    {
        mat = (CvMat*)lineStorage;

        if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 && mat->cols != 1) )
            CV_Error( CV_StsBadArg,
            "The destination matrix should be continuous and have a single row or a single column" );

        if( CV_MAT_TYPE( mat->type ) != lineType )
            CV_Error( CV_StsBadArg,
            "The destination matrix data type is inappropriate, see the manual" );

        lines = cvMakeSeqHeaderForArray( lineType, sizeof(CvSeq), elemSize, mat->data.ptr,
                                         mat->rows + mat->cols - 1, &lines_header, &lines_block );
        linesMax = lines->total;
        cvClearSeq( lines );
    }
    else
        CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* nor CvMat*" );

    iparam1 = cvRound(param1);
    iparam2 = cvRound(param2);

    switch( method )
    {
    case CV_HOUGH_STANDARD:    //经典霍夫变换
          icvHoughLinesStandard( img, (float)rho,
                (float)theta, threshold, lines, linesMax );
          break;
    case CV_HOUGH_MULTI_SCALE:    //多尺度霍夫变换
          icvHoughLinesSDiv( img, (float)rho, (float)theta,
                threshold, iparam1, iparam2, lines, linesMax );
          break;
    case CV_HOUGH_PROBABILISTIC:    //概率霍夫变换
          icvHoughLinesProbabilistic( img, (float)rho, (float)theta,
                threshold, iparam1, iparam2, lines, linesMax );
          break;
    default:
        CV_Error( CV_StsBadArg, "Unrecognized method id" );
    }
    //在前面判断lineStorage类型时,已经确定它是STORAGE类型,因此没有进入else if内,也就是没有对mat赋值,所以在这里进入的是else
    if( mat )
    {
        if( mat->cols > mat->rows )
            mat->cols = lines->total;
        else
            mat->rows = lines->total;
    }
    else
        result = lines;
    //返回lines序列的指针,该序列存储有霍夫变换的直线
    return result;
}

 

对于经典霍夫变换,调用的是icvHoughLinesStandard函数:

static void
icvHoughLinesStandard( const CvMat* img, float rho, float theta,
                       int threshold, CvSeq *lines, int linesMax )
{
    cv::AutoBuffer<int> _accum, _sort_buf;
    cv::AutoBuffer<float> _tabSin, _tabCos;

    const uchar* image;
    int step, width, height;
    int numangle, numrho;
    int total = 0;
    int i, j;
    float irho = 1 / rho;
    double scale;
    //再次确保输入图像的正确性
    CV_Assert( CV_IS_MAT(img) && CV_MAT_TYPE(img->type) == CV_8UC1 );

    image = img->data.ptr;    //得到图像的指针
    step = img->step;    //得到图像的步长
    width = img->cols;    //得到图像的宽
    height = img->rows;    //得到图像的高
    //由角度和距离的分辨率得到角度和距离的数量,即霍夫变换后角度和距离的个数
    numangle = cvRound(CV_PI / theta);
    numrho = cvRound(((width + height) * 2 + 1) / rho);

    //为累加器数组分配内存空间
    //该累加器数组其实就是霍夫空间,它是用一维数组表示二维空间
    _accum.allocate((numangle+2) * (numrho+2));
    //为排序数组分配内存空间
    _sort_buf.allocate(numangle * numrho);
    //为正弦和余弦列表分配内存空间
    _tabSin.allocate(numangle);
    _tabCos.allocate(numangle);
    //分别定义上述内存空间的地址指针
    int *accum = _accum, *sort_buf = _sort_buf;
    float *tabSin = _tabSin, *tabCos = _tabCos;
    //累加器数组清零
    memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );

    float ang = 0;
    //为避免重复运算,事先计算好sinθi/ρ和cosθi/ρ
    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
    //执行步骤1,逐点进行霍夫空间变换,并把结果放入累加器数组内
    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;
                    //r表示的是距离,n表示的是角点,在累加器内找到它们所对应的位置(即霍夫空间内的位置),其值加1
                    accum[(n+1) * (numrho+2) + r+1]++;
                }
        }

    // stage 2. find local maximums
    //执行步骤2,找到局部极大值,即非极大值抑制
    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 &&    //必须大于所设置的阈值
                //在4邻域内进行非极大值抑制
                accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] &&
                accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] )
                //把极大值位置存入排序数组内——sort_buf
                sort_buf[total++] = base;
        }

    // stage 3. sort the detected lines by accumulator value
    //执行步骤3,对存储在sort_buf数组内的累加器的数据按由大到小的顺序进行排序
    icvHoughSortDescent32s( sort_buf, total, accum );

    // stage 4. store the first min(total,linesMax) lines to the output buffer
    //执行步骤4,只输出前linesMax条直线,这里linesMax就等于total,即输出所有直线
    linesMax = MIN(linesMax, total);
    //事先定义一个尺度
    scale = 1./(numrho+2);
    for( i = 0; i < linesMax; i++ )
    {
        // CvLinePolar结构在该文件的前面被定义
        CvLinePolar line;
        //idx为极大值在累加器数组的位置
        int idx = sort_buf[i];
        //分离出该极大值在霍夫空间中的位置
        int n = cvFloor(idx*scale) - 1;
        int r = idx - (n+1)*(numrho+2) - 1;
        //最终得到极大值所对应的角度和距离
        line.rho = (r - (numrho - 1)*0.5f) * rho;
        line.angle = n * theta;
        //存储到序列内
        cvSeqPush( lines, &line );
    }
}

由于函数HoughLines只输出直线所对应的角度和距离,所以在进行直线检测的时候还要把其转换为直角坐标系下的数据,另外输入图像还必须是边缘图像,下面就是具体的实例:

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
	Mat src, edge;
	src=imread("building.jpg");
	if( !src.data )  
		return -1;  
    //Canny边缘检测
	Canny(src,edge,50,200,3);
    //定义输出数组,用于存储直线的角度和距离这两个变量
	vector<Vec2f> lines;
    //距离分辨率为1,角度分辨率为π/180,阈值为215
    //阈值的选取直接影响到输出直线的数量
	HoughLines(edge,lines,1,CV_PI/180,215,0,0);
    //画直线
    for( size_t i = 0; i < lines.size(); i++ )
    {
        //提取出距离和角度
        float rho = lines[i][0], theta = lines[i][1];
        //定义两个点,两点确定一条直线
        //计算得到的两点的坐标为(ρcosθ-1000sinθ,ρsinθ+1000cosθ),(ρcosθ+1000sinθ,ρsinθ-1000cosθ)
        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));
        //在原图上画宽带为2的红线
        line( src, pt1, pt2, Scalar(0,0,255),2);
    }
 
    namedWindow( "lines", CV_WINDOW_AUTOSIZE );
    imshow( "lines", src );
    waitKey(0);

    return 0;
}
下图霍夫变换直线检测的结果:
Opencv2.4.9源码分析——HoughLines_第1张图片



你可能感兴趣的:(Opencv2.4.9源码分析——HoughLines)