前段时间做了一个车牌检测识别的项目,我的任务是将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算子及阈值化产生的边缘,原图)

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

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

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

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
05-13 14:55