版权声明:本文原创,博文可随意学习提议,转载请注明出处及作者
在做图像处理的时候,经常需要用到MATLAB验证与OpenCV实现共同进行,本文动手动机就是:OpenCV提供的Hough线检测不能满足我的要求,故需要对OpenCV源码进行修改。本人菜鸟,才学C++与OpenCV不久(事实+谦虚,实际上每次写完一个东西成功得到结果后都很膨胀,如同刚学C时候写个链表都巨开心,过段时间再看...跑题了跑题了)。本文需要图像处理基础和OpenCV基础,Hough线检测的理论部分本文不做介绍,可参阅OpenCV提供的文档等。
MATLAB作为一大神器做算法验证极其高效,我们来看MATLAB的hough线检测:
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线检测后,我们得到了漂亮的方向线:
这里需要提及Hough线检测在MATLAB里面的运行原理:得到变换矩阵H后,此处H矩阵如图:
放大可以看到,累加最大值集中在中间竖向的一些亮点,MATLAB在选择最佳线的时候后,选出H矩阵的累加最大值后会依据事先给出的窗口大小(这里是17*15)将附近的累加值看作相近的线或者(这里也不太好描述,懂Hough检测原理就知道为什么这么做了)。这样找到的就是最佳的线。而OpenCV给出的接口就没有这个参数,所以得出的线有时候会有一大堆杂七杂八的不知道所以然的线,只能根据返回的线条参数进行后续的筛选。线条参数只有rho和theta,期初我才用求theta均值,相近的线保留theta最接近均值的线。然而效果并不好。OpenCV的HoughLines结果如下
这里以多通道显示原图和检测线的区别,蓝色是原图,白色是检测线,黑色就是黑色(摊手)。在图像中间水平方向上效果还不错,因为毕竟是中间,有效线的必经之路嘛。但是在放大圈出来的部分就可以看到,OpenCV给出的备选线,无论删减保留哪条,都不是我们想要的线,有人说把相近线段求theta的均值合并为一?没错,是有所改善,但是有的部分还是不尽如人意。这种做法本质上也是求H矩阵极值相近位置theta均值,在theta方向做了平滑。但是有的时候效果依旧不好,毕竟不是根据累加值做权值进行的平滑。这里最好的做法就是我们得到累加值(甚至是累加矩阵),但是stackoverflow上也没有大佬给出方法,于是需要我们修改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]作为依据。
我们分别在Debug和Release下分别build opencv_imgproc,就能在你的OpenCV工程的lib和bin目录得到lib和dll,我们发现我们得到imgproc的dll同时还得到了core的lib和dll,应该是二者有关联,但是这里我并没有深究,替换掉我们正在用的opencv库后(记得include的头文件也要改,因为增加了一个我们定义的新的外部函数)。调用我们修改过的接口后根据lines的accum[idx]筛选线条后(也可以使用H矩阵)这里偷懒了。得到新的检测线:
至此我们就完成了一次修改OpenCV的风(zhuang)骚(13)走位。