openCV cvHoughLines2 函数源码解析(CV_HOUGH_PROBABILISTIC 基于概率的霍夫变换)

霍夫变换:

为了能够正确识别和检测任意方向的和任意位置的直线,使用Duda和Hart提出的直线极坐标方程:

 

这就是霍夫变换的公式。


openCV cvHoughLines2 函数源码解析(CV_HOUGH_PROBABILISTIC 基于概率的霍夫变换)_第1张图片

openCV 的基于概率的算法是根据“Robust Detection of Lines Using the Progressive Probabilistic Hough Transform”写的,我们看一下算法源码:

/*

Image

输入图像

rho

与象素相关单位的距离精度

theta

弧度测量的角度精度

threshold

阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段

lineLength

它是最小线段长度

lineGap

表示在同一条直线上进行碎线段连接的最大间隔值

lines

保存直线两端的坐标

linesMax

直线的个数阈值(常数 INT_MAX)

*/

static void
icvHoughLinesProbabalistic( CvMat* image,
                            float rho, float theta, int threshold,
                            int lineLength, int lineGap,
                            CvSeq *lines, int linesMax )
{
		CvMat* accum = 0;//累加器
		CvMat* mask = 0;//保存0,1图像
		CvMat* trigtab = 0;//保存cos、sin与距离精度(irho)的乘积
    CvMemStorage* storage = 0;

    CV_FUNCNAME( "icvHoughLinesProbalistic" );

    __BEGIN__;
    
    CvSeq* seq;
    CvSeqWriter writer;
    int width, height;
    int numangle, numrho;
    float ang;
    int r, n, count;
    CvPoint pt;
    float irho = 1 / rho;
    CvRNG rng = cvRNG(-1);//产生随机数
    const float* ttab;
    uchar* mdata0;

    CV_ASSERT( CV_IS_MAT(image) && CV_MAT_TYPE(image->type) == CV_8UC1 );

    width = image->cols;
    height = image->rows;

    numangle = cvRound(CV_PI / theta);
    numrho = cvRound(((width + height) * 2 + 1) / rho);

    CV_CALL( accum = cvCreateMat( numangle, numrho, CV_32SC1 ));
    CV_CALL( mask = cvCreateMat( height, width, CV_8UC1 ));
    CV_CALL( trigtab = cvCreateMat( 1, numangle, CV_32FC2 ));
    cvZero( accum );
    
    CV_CALL( storage = cvCreateMemStorage(0) );
    
    for( ang = 0, n = 0; n < numangle; ang += theta, n++ )
    {
        trigtab->data.fl[n*2] = (float)(cos(ang) * irho);
        trigtab->data.fl[n*2+1] = (float)(sin(ang) * irho);
    }
    ttab = trigtab->data.fl;
    mdata0 = mask->data.ptr;

    CV_CALL( cvStartWriteSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage, &writer )); 

    //第一步生成0,1图像,即:选择非零的点
    // stage 1. collect non-zero image points
    //count = 0 在这里毫无意义
    for( pt.y = 0, count = 0; pt.y < height; pt.y++ )
    {
        const uchar* data = image->data.ptr + pt.y*image->step;
        uchar* mdata = mdata0 + pt.y*width;
        for( pt.x = 0; pt.x < width; pt.x++ )
        {
            if( data[pt.x] )
            {
                mdata[pt.x] = (uchar)1;
                CV_WRITE_SEQ_ELEM( pt, writer );//存入链表
            }
            else
                mdata[pt.x] = 0;
        }
    }

    seq = cvEndWriteSeq( &writer );
    count = seq->total;

		//随机处理
    // stage 2. process all the points in random order
    for( ; count > 0; count-- )
    {
		    // choose random point out of the remaining ones
				int idx = cvRandInt(&rng) % count;//生成随机数
				int max_val = threshold-1, max_n = 0;
				CvPoint* pt = (CvPoint*)cvGetSeqElem( seq, idx );
				CvPoint line_end[2] = {{0,0}, {0,0}};
				float a, b;
				int* adata = accum->data.i;
				int i, j, k, x0, y0, dx0, dy0, xflag;
				int good_line;
				const int shift = 16;
		
				i = pt->y;
				j = pt->x;

				//注意这行代码是为了覆盖pt指向的内容,也就是说pt指向的链表seq的内容被count-1位置上的内容覆盖了
        // "remove" it by overriding it with the last element
        *pt = *(CvPoint*)cvGetSeqElem( seq, count-1 );

        // check if it has been excluded already (i.e. belongs to some other line)
        if( !mdata0[i*width + j] )
            continue;
            
				//更新 累加器,查找最大概率的线
        // update accumulator, find the most probable line
        for( n = 0; n < numangle; n++, adata += numrho )
        {
            r = cvRound( j * ttab[n*2] + i * ttab[n*2+1] );
            r += (numrho - 1) / 2;//这行程序没有意义
            int val = ++adata[r];
            if( max_val < val )
            {
                max_val = val;
                max_n = n;
            }
        }

				//如果点的个数max_val < threshold 就被认为是不符合条件的候选点(i,j)
        // if it is too "weak" candidate, continue with another point
        if( max_val < threshold )
            continue;

				//如果点的个数max_val >= threshold 就被认为是符合条件的候选点(i,j)
			  // from the current point walk in each direction
			  // along the found line and extract the line segment
				//极坐标中的方向角是直线的垂线与极轴正向的夹角,在图像中夹角是第四象限的角
				//(极轴正向逆时针旋转,极轴就是在平面直角坐标系中的x轴正方向,对于图像来说,y轴正向是向下的)
				//所以sin取负值,cos不变
					a = -ttab[max_n*2+1];
					b = ttab[max_n*2];
					x0 = j;
					y0 = i;
				
				//计算步长dx0,dy0
        if( fabs(a) > fabs(b) )
        {
            xflag = 1;
            dx0 = a > 0 ? 1 : -1;
            dy0 = cvRound( b*(1 << shift)/fabs(a) );
            y0 = (y0 << shift) + (1 << (shift-1));
	    			//1 << shift这是为了把浮点数计算转化为整数计算
        }
        else
        {
            xflag = 0;
            dy0 = b > 0 ? 1 : -1;
            dx0 = cvRound( a*(1 << shift)/fabs(b) );
            x0 = (x0 << shift) + (1 << (shift-1));
        }

				//当点的位置和cos、sin确定后,每条直线都有两个方向
        for( k = 0; k < 2; k++ )
        {
            int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0;
            
            if( k > 0 ) //控制两个方向(正好相反)
                dx = -dx, dy = -dy;

            // walk along the line using fixed-point arithmetics,
            // stop at the image border or in case of too big gap
            for( ;; x += dx, y += dy )
            {
                uchar* mdata;
                int i1, j1;

                if( xflag )
                {
                    j1 = x;
                    i1 = y >> shift;
                }
                else
                {
                    j1 = x >> shift;
                    i1 = y;
                }

                if( j1 < 0 || j1 >= width || i1 < 0 || i1 >= height )
                    break;

                mdata = mdata0 + i1*width + j1;

                // for each non-zero point:
                //    update line end,
                //    clear the mask element
                //    reset the gap
                if( *mdata )
                {
									gap = 0;
									line_end[k].y = i1;
									line_end[k].x = j1;
                }
                else if( ++gap > lineGap )//像素间隙大于lineGap 则退出
                    break;
            }
        }

				//分别计算X、Y方向距离
        good_line = abs(line_end[1].x - line_end[0].x) >= lineLength ||
                    abs(line_end[1].y - line_end[0].y) >= lineLength;

        for( k = 0; k < 2; k++ )
        {
            int x = x0, y = y0, dx = dx0, dy = dy0;
            
            if( k > 0 )
                dx = -dx, dy = -dy;

            // walk along the line using fixed-point arithmetics,
            // stop at the image border or in case of too big gap
            for( ;; x += dx, y += dy )
            {
                uchar* mdata;
                int i1, j1;

                if( xflag )
                {
                    j1 = x;
                    i1 = y >> shift;
                }
                else
                {
									j1 = x >> shift;
									i1 = y;
								}

                mdata = mdata0 + i1*width + j1;

                // for each non-zero point:
                //    update line end,
                //    clear the mask element
                //    reset the gap
                //如果*mdata == 1则设置为0,去除已经检测过的点
                if( *mdata )
                {
                		//如果是直线,则去除累加器里面的值
                    if( good_line )
                    {
                        adata = accum->data.i;
                        for( n = 0; n < numangle; n++, adata += numrho )
                        {
                            r = cvRound( j1 * ttab[n*2] + i1 * ttab[n*2+1] );
                            r += (numrho - 1) / 2;//这行程序没有意义
                            adata[r]--;
                        }
                    }
                    *mdata = 0;
                }

                if( i1 == line_end[k].y && j1 == line_end[k].x )
                    break;
            }
        }

        if( good_line )
        {
        		//哎,openCV 也有这样的乱用情况
            CvRect lr = { line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y };
            cvSeqPush( lines, &lr );
            if( lines->total >= linesMax )
                EXIT;
        }
    }

    __END__;

    cvReleaseMat( &accum );
    cvReleaseMat( &mask );
    cvReleaseMat( &trigtab );
    cvReleaseMemStorage( &storage );
}

问题:

1)for( pt.y = 0, count = 0; pt.y < height; pt.y++ )// count = 0;毫无意义可言,为什么加这个代码?

2)r += (numrho - 1) / 2;//感觉这句话也是多余,去掉之后,效果完全一样

3)CvRect lr = { line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y };//不是保存四个点吗?怎么会使用CvRect?来个数组不行吗?CvPoint points[4];

4)openCV的程序基本上是C++编译的,为什么这么说呢?是因为还有相当一部分代码是C语言风格(函数指针或大段的宏定义等等)。但是这个函数的风格有点太随便了些。所有变量定义都在函数开始处,都C++编译了,为什么还保留这种写法?比如:int r, n, count;中的变量n,完全可以拿到算法里面去临时定义

5)变量mdata0 和变量mdata的定义!是不是mdata应该在外层,mdata0应该在内层啊。


读完这个函数之后,发现程序写的如此“随便”。

你可能感兴趣的:(openCV算法解析,hough,算法,opencv,图像基本处理函数)