MATLAB的边缘检测函数中隐含的细化(非极大值抑制)算法

前段时间做了一个车牌检测识别的项目,我的任务是将MATLAB中的算法移植成C++代码。在车牌区域提取的过程中,用到了水平方向的Sobel算子检测垂直边缘,一开始我直接把MATLAB中的

bw = edge(I, 'sobel', 'vertical');

语句改写成OpenCV中的

cv::Mat sobel_kernel = (cv::Mat_<float>(3,3) << -0.125, 0, 0.125,
                                                -0.25, 0, 0.25,
                                                -0.125, 0, 0.125);
cv::Mat edges;
cv::filter2D(gray_img, edges, gray_img.type(), sobel_kernel);

之后,整个检测算法产生了一些意想不到的输出。追根溯源,我发现问题的根源就是在这个边缘检测步骤里:MATLAB的edge函数产生的是一个细化的二值边缘,而OpenCV中输出的是模板卷积后的浮点型的梯度值,若直接对其阈值化,将产生一个粗边缘,如下图所示(从左到右分别为edge函数输出边缘,OpenCV中直接使用Sobel算子及阈值化产生的边缘,原图)

研究了一下edge的实现代码,我发现这么一个函数

 

computeEdgesWithThinning函数实现了非极大值抑制和阈值化的效果,这个函数的实现方式已经被MATLAB封装,无法查看。一番波折之后,我模拟出一个效果基本一致的细化及阈值化算法(默认的阈值T为4乘以每个点梯度的模的平方的均值):

设 M(i, j) 为某点的梯度的模的平方
M(i, j) 大于阈值 T 且:
若 M(i, j) > M(i - 1, j) 且 M(i, j) > M(i + 1, j)  
或者 M(i, j) > M(i, j - 1) 且 M(i, j) > M(i, j + 1)
则将输出边缘图像的 (i, j) 位置设为 1 

简要地说,就是判断一个点的梯度是否是水平或者垂直方向的上的局部极大值,当然,梯度值首先得大于阈值。经过实验,加上这个非极大值抑制的步骤后,输出图片与MATLAB的edge函数产生的边缘图片基本一致,下面整个边缘检测加细化的MATLAB实现代码(只检测垂直的边缘)

function e = sobel_thin(img)
op = fspecial('sobel') / 8; 
x_mask = op'; 
a = im2double(img);
scale = 4; 
bx = imfilter(a,x_mask,'replicate');
b = bx.*bx;
cutoff = 4 * mean2(b);
[m, n] = size(b);
 
for r = 1 : m
    for c=1 : n
        if ((c - 1) < 1)
            b1 = true;
        else
            b1 = (b(r, c - 1) <= b(r, c));
        end
        if (c + 1) > n
            b2 = true;
        else
            b2 = (b(r, c) > b(r, c + 1));
        end
        if ((r - 1) < 1)
            b3 = true;
        else
            b3 = (b(r - 1, c) <= b(r, c));
        end
        if ((r+1) > m)
            b4 = true;
        else
            b4 = (b(r, c) > b(r + 1, c));
        end
        e(r, c) = (b(r, c) > cutoff) &  ((b1 & b2) | (b3 & b4));
    end
end
posted @ 2013-10-27 21:05  何磊  阅读(5146)  评论(0编辑  收藏  举报