修改并编译OpenCV源码提升霍夫变换线检测效果

版权声明:本文原创,博文可随意学习提议,转载请注明出处及作者

在做图像处理的时候,经常需要用到MATLAB验证与OpenCV实现共同进行,本文动手动机就是:OpenCV提供的Hough线检测不能满足我的要求,故需要对OpenCV源码进行修改。本人菜鸟,才学C++与OpenCV不久(事实+谦虚,实际上每次写完一个东西成功得到结果后都很膨胀,如同刚学C时候写个链表都巨开心,过段时间再看...跑题了跑题了)。本文需要图像处理基础和OpenCV基础,Hough线检测的理论部分本文不做介绍,可参阅OpenCV提供的文档等。

写在前面的问题描述

MATLAB作为一大神器做算法验证极其高效,我们来看MATLAB的hough线检测:

这里给出一个二值图像:修改并编译OpenCV源码提升霍夫变换线检测效果_第1张图片

clc;
clear all;
cameraProjRefValidMask = imread('cameraProjRefValidMask.png');
cameraProjmeaValidMask = imread('opencv提取有效相位proj.png');
cameraProjRefValidMask = imbinarize(rgb2gray(cameraProjRefValidMask));
cameraProjmeaValidMask = imbinarize(rgb2gray(cameraProjmeaValidMask));

cameraProjRefValidMaskEroded = projMaskErode(cameraProjRefValidMask, 9);
%cameraProjmeaValidMask = projMaskErode(cameraProjmeaValidMask, 6);

lines_1 = GetFramework(cameraProjRefValidMaskEroded);
%lines_2 = GetFramework(cameraProjmeaValidMask);

%figure, imshow(cameraProjRefValidMask + cameraProjmeaValidMask);
figure, imshow(cameraProjRefValidMask), hold on
for k = 1:length(lines_1)
	xy = [lines_1(k).point1; lines_1(k).point2];
	plot(xy(:, 1), xy(:, 2), 'LineWidth', 2);
end
function lines = GetFramework(maskSeed)
	[H, theta, rho] = hough(maskSeed, 'RhoResolution', 1,'ThetaResolution',0.1);
	peaks = houghpeaks(H, 100, 'Threshold', 0.6*max(max(H)), 'NHoodSize', [17, 15]);
	lines = houghlines(maskSeed, theta, rho, peaks);
    figure, imshow(H, []);
end
function erodedMask = projMaskErode( mask, iterations )
    operator = [0, 1, 0; 1, 1, 1; 0, 1, 0];
    for i = 1:iterations
        mask = imerode(mask, operator);
    end
    erodedMask = mask;
end

经过腐蚀操作,可以得到纹理的大致方向线,经过Hough线检测后,我们得到了漂亮的方向线:

修改并编译OpenCV源码提升霍夫变换线检测效果_第2张图片

这里需要提及Hough线检测在MATLAB里面的运行原理:得到变换矩阵H后,此处H矩阵如图:

修改并编译OpenCV源码提升霍夫变换线检测效果_第3张图片放大可以看到,累加最大值集中在中间竖向的一些亮点,MATLAB在选择最佳线的时候后,选出H矩阵的累加最大值后会依据事先给出的窗口大小(这里是17*15)将附近的累加值看作相近的线或者(这里也不太好描述,懂Hough检测原理就知道为什么这么做了)。这样找到的就是最佳的线。而OpenCV给出的接口就没有这个参数,所以得出的线有时候会有一大堆杂七杂八的不知道所以然的线,只能根据返回的线条参数进行后续的筛选。线条参数只有rho和theta,期初我才用求theta均值,相近的线保留theta最接近均值的线。然而效果并不好。OpenCV的HoughLines结果如下

修改并编译OpenCV源码提升霍夫变换线检测效果_第4张图片

这里以多通道显示原图和检测线的区别,蓝色是原图,白色是检测线,黑色就是黑色(摊手)。在图像中间水平方向上效果还不错,因为毕竟是中间,有效线的必经之路嘛。但是在放大圈出来的部分就可以看到,OpenCV给出的备选线,无论删减保留哪条,都不是我们想要的线,有人说把相近线段求theta的均值合并为一?没错,是有所改善,但是有的部分还是不尽如人意。这种做法本质上也是求H矩阵极值相近位置theta均值,在theta方向做了平滑。但是有的时候效果依旧不好,毕竟不是根据累加值做权值进行的平滑。这里最好的做法就是我们得到累加值(甚至是累加矩阵),但是stackoverflow上也没有大佬给出方法,于是需要我们修改OpenCV源码。

第一步:生成OpenCV工程,修改源码

我使用的环境为VS2017 + OpenCV3.3 自编译库,如何编译OpenCV到指定平台可以参考这篇博文这篇博文。这里不做赘述。打开OpenCV工程:

我们找到HoughLines的源代码:(modules/opencv_imgproc/src/hough)


void cv::HoughLines( InputArray _image, OutputArray _lines,
                    double rho, double theta, int threshold,
                    double srn, double stn, double min_theta, double max_theta )
{
    CV_INSTRUMENT_REGION()
    CV_OCL_RUN(srn == 0 && stn == 0 && _image.isUMat() && _lines.isUMat(),
               ocl_HoughLines(_image, _lines, rho, theta, threshold, min_theta, max_theta));
    Mat image = _image.getMat();
    std::vector lines;
    if( srn == 0 && stn == 0 )
        HoughLinesStandard(image, (float)rho, (float)theta, threshold, lines, INT_MAX, min_theta, max_theta );
    else
        HoughLinesSDiv(image, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), lines, INT_MAX, min_theta, max_theta);
    Mat(lines).copyTo(_lines);
}

可以看到这里返回的线都是二维浮点向量,即:rho和theta的向量。

static void
HoughLinesStandard( const Mat& img, float rho, float theta,
                    int threshold, std::vector& lines, int linesMax,
                    double min_theta, double max_theta )
{
    int i, j;
    float irho = 1 / rho;
    CV_Assert( img.type() == CV_8UC1 );
    const uchar* image = img.ptr();
    int step = (int)img.step;
    int width = img.cols;
    int height = img.rows;
    if (max_theta < min_theta ) {
        CV_Error( CV_StsBadArg, "max_theta must be greater than min_theta" );
    }
    int numangle = cvRound((max_theta - min_theta) / theta);
    int numrho = cvRound(((width + height) * 2 + 1) / rho);
/*		这里我删掉一部分自己去看源码		*/
    AutoBuffer _accum((numangle+2) * (numrho+2));
    std::vector _sort_buf;
    AutoBuffer _tabSin(numangle);
    AutoBuffer _tabCos(numangle);
    int *accum = _accum;
    float *tabSin = _tabSin, *tabCos = _tabCos;
    memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );

    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
    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);
        }
    // stage 3. sort the detected lines by accumulator value
    std::sort(_sort_buf.begin(), _sort_buf.end(), hough_cmp_gt(accum));
    // stage 4. store the first min(total,linesMax) lines to the output buffer
    linesMax = std::min(linesMax, (int)_sort_buf.size());
    double scale = 1./(numrho+2);
    for( i = 0; i < linesMax; i++ ){
        LinePolar line;
        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 = static_cast(min_theta) + n * theta;
        lines.push_back(Vec2f(line.rho, line.angle));
    }
}

可以看到开源项目大佬们写的代码都很规范,结合注释不难理解accum就是我们想要的H矩阵,accum[idx]就是我想要的累加值,所以我们重新导出一个接口(为不影响原OpenCV接口):

void cv::HoughLinesWithAccumMat(InputArray _image, OutputArray _lines, OutputArray accumMat,
									double rho, double theta, int threshold,
									double srn, double stn, double min_theta, double max_theta)
  /*在最后添加如下代码*/
 	 	lines.push_back(Vec3f(line.rho, line.angle, accum[idx]));
	//上面这句在lines的循环里
	Mat mat = Mat(numangle, numrho, CV_32SC1, accum);
	mat.copyTo(accumMat);

我们就可以在调用的时候得到H矩阵的结果并进行相关的操作,删减多余线条时候也可以以accum[idx]作为依据。

第二步:Build项目获得lib和dll并替换现有库(记得include的头文件也要更新啊)

我们分别在Debug和Release下分别build opencv_imgproc,就能在你的OpenCV工程的lib和bin目录得到lib和dll,我们发现我们得到imgproc的dll同时还得到了core的lib和dll,应该是二者有关联,但是这里我并没有深究,替换掉我们正在用的opencv库后(记得include的头文件也要改,因为增加了一个我们定义的新的外部函数)。调用我们修改过的接口后根据lines的accum[idx]筛选线条后(也可以使用H矩阵)这里偷懒了。得到新的检测线:修改并编译OpenCV源码提升霍夫变换线检测效果_第5张图片

至此我们就完成了一次修改OpenCV的风(zhuang)骚(13)走位。

你可能感兴趣的:(OpenCV)