图像处理中的边缘检测就是把一幅图片的边缘给提取出来,为特征分割或形态学处理做准备,其核心操作就是一个卷积的过程;再直白点说就是创建一个小矩阵(有时也可为向量,即单行矩阵),和原图像的每个像素点(逐像素)及其邻域(图片中的一个像素点的周边上下左右的像素点称为该中心像素点的邻域)作乘积,且此邻域尺寸与小矩阵尺寸大小相同;同时这个小矩阵还得具有与在图片中图像轮廓边缘上的像素点及其邻域作卷积运算时,卷积运算为一个较大的值,并把该值作为一个新空白图像(即与原图像相同大小的输出图像)中相同位置的像素点的值;而对于图像中的平滑区域,小矩阵与这其中的像素点邻域乘积应为零或为较小值;这样,输出的图像便是一幅原图像边缘部分被像素值较大,其他区域像素值较小或为零的灰度图像。
综上,我们不难发现,由于一幅待处理图像其实可看作一个常量,所以边缘检测结果好坏的关键在于与之进行运算的小矩阵的构造,而通常这样的小矩阵我们学术的(形容词)将其称为算子,在滤波操作中又被称为滤波器、模板等。但是,我们如何根据自己的需要构造出满足要求的边缘检测算子呢?首先,正如物理学中在涉及到速度的变化问题时老师所扯淡的,单位时间内,速度变化越快,那么加速度也就越大;同理,我们在寻找图像边缘的时候也运用到同样的原理,即对于一幅图像来说,边缘地区肯定是几个像素点间的像素值相差较大的地区。好,在此基础上,再给图像处理零基础的同学说明一下,一幅图像可由二元离散函数f(x,y)表示;因此,我们对f(x,y)求导(离散函数求导为作差分,就是diff那个玩意儿),得到的值越大,说明在原图像中该处的边缘越明显,反之若求导值为0或很小,说明此处为平滑规整图像区域。对于二元离散函数f(x,y)求导,我们用差分来代替求导,可表示为下列等式:
至于为什么二元函数求导等于
是由高等数学中梯度知识推导出来的,此处不再说明,不懂的记住这个结论即可。在此基础上,再结合其他操作,就能得到各种不同效果的边缘检测算子了。
在Matlab中,目前支持如下算子:
1.Prewitt算子
Prewitt算子就是上述思想的一个直接体现,通过算子形式我们可看出(a)为x的差分,(b)为y的差分,其大小为3×3,:
BW =
edge(I,'prewitt')
BW =
edge(I,'prewitt',thresh)
thresh为一个阈值,当上述的Prewitt模板与每个像素点进行运算之后,将其得到的值与thresh比较,若>thresh,则视为边缘像素点,若
当输入此命令时还可将matlab算出的thresh输出
[BW,thresh] =
edge(I,'prewitt',)
BW =
edge(I,'prewitt',thresh,direction)
Direction表示检测方向,与每个像素点进行运算既可沿着竖直方向运算也可沿着水平方向运算,也可两个方向都进行,于是direction有如下命令:
'horizontal'
or'vertical'edges,
or'both'(默认情况为both).
BW =
edge(I,'prewitt',OPTIONS)
OPTIONS提供两个操作'nothinning'、
'thinning','nothinning'是对得到的边缘检测图像不进行细化处理,'thinning'则是进行细化处理,因为边缘检测后的图像难免会因为原始图像中的边缘部分灰度过渡问题而出现过渡区域全被视为边缘部分,'thinning'操作后能得到单像素线的边缘检测图像。
Matlab中的源码程序主体如下:
function eout = edgeinprewitt(I)
a = I;
kx = 1;%
ky = 1;%kx
ky均为1,检测方式为both
%Transform to a double precision intensity image if
necessary
if ~isa(a,'double')
&&~isa(a,'single')
a=
im2single(a);
end
op = fspecial('prewitt')/6; % Prewitt approximation
toderivative
x_mask = op';
y_mask = op;
%compute the gradient in x and y direction
bx = imfilter(a,x_mask,'replicate');
by = imfilter(a,y_mask,'replicate');
%compute the magnitude
b = kx*bx.*bx +
ky*by.*by;%将沿竖直方向边缘检测和沿水平方向边缘检测得到的边缘图像叠加在一起
cutoff =
4*mean2(b);%用边缘检测后的图像的平均值来近似表示信噪比
%
if thinning
%
e
=computeedge(b,bx,by,kx,ky,int8(offset),100*eps,cutoff);
%边缘细化算法,由于computeedge为内部MEX函数,故此处将其注释掉,输出的便是具有粗边缘的图像
%
else
e = b >
cutoff;%得出便是未经过边缘细化的图像
%
end
eout = e;
1.Sobel算子
Sobel只是Prewitt的一个扩展,即中间元素乘以2,是为了起平滑图像的作用。而在Matlab中的用法则和Prewitt算子完全相同,故此处不再赘述,仅列出其用法。
在Matlab中Sobel的调用格式如下:
BW =
edge(I,'sobel')
BW =
edge(I,'sobel',thresh)
[BW,thresh] =
edge(I,'sobel')
BW =
edge(I,'sobel',thresh,direction)
BW =
edge(I,'sobel',...,OPTIONS)
1.Roberts算子
Roberts算子是边缘检测算子中最古老的一种,也是最简单的一种,因为在部分功能上的限制,所以这种检测算子的使用明显少于其他几种算子(比如Roberts算子是非对称的,且不能检测45°倍数的边缘),具体形式如下:
在Matlab中的调用方式也和上述两种算子相同。
BW =
edge(I,'roberts')
BW =
edge(I,'roberts',thresh)
BW =
edge(I,'roberts',...,OPTIONS)
[BW,thresh] =
edge(I,'roberts',...)
4.Laplacian of Gaussian 算子(简称Log算子)
基于拉普拉斯和高斯滤波的边缘检测算子,在二维空间,拉普拉斯表达式为:
由上式我们不难看出,拉普拉斯算子计算过程实质为对f(x,y)求二次导,这对于强对比边缘来说具有更好的检测作用,但是无疑也对噪声更加敏感,因此在进行拉普拉斯运算之前先进行高斯滤波以平滑图像,整个处理过程可表达为:
因此,基于高斯滤波的拉普拉斯运算的边缘检测可直接由经拉普拉斯运算后的高斯模板与输入图像卷积获得。一个5×5邻域的Log算子可表示为:
同时我们不难发现,由于拉普拉斯适用于检测较强对比度的图像边缘,而对于灰度变化相对缓慢的过渡区域边缘,其检测结果可能为一条较粗的边缘线或者一个封闭狭长的区域,狭长区域中被置0的点称为过零点或零交叉点,所以对于正常未被高对比度处理的图像来说,基于拉普拉斯边缘检测算子处理之后的边缘都存在过零点现象,我们便可通过检测过零点来检测图像的边缘。
基于高斯滤波的拉普拉斯边缘检测算法在Matlab中的具体调用格式为:
BW =
edge(I,'log')
直接调用,’log’表示使用的边缘检测算子为基于高斯平滑滤波的拉普拉斯边缘检测算子,其他参数均由Matlab根据输入图像计算得出或者取默认值。
BW =
edge(I,'log',thresh)
Thresh表示任何灰度值低于该阈值的边缘都不会被检测到,若未指定该阈值,则Matlab会根据输入图像自动算出
BW =
edge(I,'log',thresh,SIGMA)
SIGMA为高斯滤波模板的方差,默认值为2且高斯滤波模板大小未指定时尺寸计算公式为:
N=CEIL(SIGMA*3)*2+1.
[BW,thresh] =
edge(I,'log',...)
当然,如果想知道Matlab根据输入图像算出的最优thresh为何值,上述调用方法便能得到。
BW =
edge(I,'zerocross',thresh,H)
除此之外,若需要自己指定算子进行Log边缘检测的话,也可使用此调用方式,其中H为自己指定的算子,thresh同上。
[BW,thresh] =
edge(I,'zerocross',...)
本质上,Log边缘检测算法的主体思路在于先用高斯平滑对图像进行平滑滤波,然后用拉普拉斯计算得到零交叉点,最后找出图像中的零交叉点并以此作为被检测图像的边缘。但是,LOG仅仅是对利用零交叉点法对高频噪声进行了滤除,而一幅图像中不仅仅只有高频噪声,低频同质化区域往往也是干扰检测质量的一个重要因素。于是,在基于LOG检测思想上,另一种基于高斯差分边缘检测算法被提出来;其具体实现形式为建立两个仅方差不同的高斯滤波模板,并把这两模板作差分运算,将其差分后的结果作为卷积的模板,最后得到的也是含交叉零点的边缘检测图像,因此LoG与DoG检测算法的本质不同就是卷积时的模板不同而已。其过程的数学表达式如下:
由于Log和DoG边缘检测时只是卷积的模板不同,因此将二者的程序写在一起,便于比较。且Matlab的edge函数中并没有直接用于DoG检测的调用方式。
自行建立边缘检测模板程序如下:
mask1 =
fspecial('gaussian',fsize,2);
mask2
=fspecial('gaussian',fsize,1.5);
op = mask1 - mask2;
LoG、DoG的Matlab实现原程序大体如下:
I = imread('cameraman.tif');
sigma = 2;�fult
% Transform to a double precision intensity image if
necessary
if
~isa(I,'double')&&
~isa(I,'single')
a =
im2single(I);%转化为单精度类型,计算时出现负数的话uint8类型会将其置零
end
[m,n] = size(a);
e = false(m,n);%输出矩阵
rr = 2:m-1;
cc=2:n-1;
%确定起始开始找零的位置,忽略图像的最外圈。
fsize = ceil(sigma*3) * 2 +1; %
choose an odd fsize >
6*sigma;确定滤波模板大小
op =
fspecial('log',fsize,sigma);%创建一个大小为fsize,高斯分布方差为sigma,再经过拉普拉斯运算后的模板
%若将OP换为下面注释中的的计算方式,便为DoG边缘检测。
% mask1 =
fspecial('gaussian',fsize,2);
% mask2 = fspecial('gaussian',fsize,1.5);
% op = mask1 - mask2;
%
op = op -
sum(op(:))/numel(op);%将op的sum值归零化
b = imfilter(a,op,'replicate');
thresh
=.75*mean2(abs(b));
%近似信噪比值作为log之后找交叉零点的阈值
%寻找交叉零点:
+-,
-+及他们的转置(即竖直方向的-+、+-)
% We arbitrarily choose the edge to be the negative
point
%我们在此规定灰度值为负的像素点作为边缘
[rx,cx] = find( b(rr,cc) <
0& b(rr,cc+1) > 0 ...
& abs( b(rr,cc)-b(rr,cc+1) ) >thresh
); % [- +]
e((rx+1) + cx*m) = 1;
[rx,cx] = find( b(rr,cc-1) >0
& b(rr,cc) < 0 ...
& abs( b(rr,cc-1)-b(rr,cc) ) >thresh
); % [+ -]
e((rx+1) + cx*m) = 1;
[rx,cx] = find( b(rr,cc) <
0& b(rr+1,cc) > 0 ...
& abs( b(rr,cc)-b(rr+1,cc) )
>thresh); % [-
+]'
e((rx+1) + cx*m) = 1;
[rx,cx] = find( b(rr-1,cc) >0
& b(rr,cc) < 0 ...
& abs( b(rr-1,cc)-b(rr,cc) )
>thresh); % [+
-]'
e((rx+1) + cx*m) = 1;
[rz,cz] = find( b(rr,cc)==0 );
if ~isempty(rz)
% Look for the zero crossings:+0-, -0+ and
their transposes
%
The edge lies on the Zeropoint
%设定边缘在灰度为0的像素点上
zero = (rz+1) + cz*m; % Linear
index for zero points
zz
= (b(zero-1) < 0 & b(zero+1)
>0 ...
& abs( b(zero-1)-b(zero+1) )
>2*thresh);
% [- 0 +]'
e(zero(zz)) = 1;
zz
= (b(zero-1) > 0 & b(zero+1)
<0 ...
& abs( b(zero-1)-b(zero+1) )
>2*thresh);
% [+ 0 -]'
e(zero(zz)) = 1;
zz
= (b(zero-m) < 0 & b(zero+m)
>0 ...
& abs( b(zero-m)-b(zero+m) )
>2*thresh);
% [- 0 +]
e(zero(zz)) = 1;
zz
= (b(zero-m) > 0 & b(zero+m)
<0 ...
& abs( b(zero-m)-b(zero+m) )
>2*thresh);
% [+ 0 -]
e(zero(zz)) = 1;
end
figure('name','LOG'),imshow(e);
%figure('name','DOG'),imshow(e);
从处理结果来看,DoG不但在计算效率上要由于LOG,而且在处理细节上似乎也要优于LOG。
5.Canny 算子
Canny算子是目前最有效的边缘检测算子。Matlab中的canny边缘检测算法主要实现步骤如下:
1)首先对输入的灰度图像进行高斯平滑滤波;值得注意的是,Matlab的高斯平滑滤波是分x、y两个方向分别进行的,分方向滤波后,再把平滑后的原图像与梯度运算(差分求导)之后的高斯模板进行卷积;根据卷积公式,该操作过程相当于对x、y分别求偏导,能得到x、y两个方向的偏导数。之所以要分两个方向进行,而不是由一个模板一次性完成,是为了得到被处理图像的偏导数dx、dy矩阵,以计算梯度的方向,且模板拆分为小模板还能加快运算速度。
2)计算出梯度和梯度方向(梯度方向在canny边缘检测中大致被分为4种:水平、竖直、45°方向、135°方向,其中梯度方向被量化为一个0-180°之间的角度值,且当被遍历点的梯度角度值在0-22.5°及157.5-180°之间时,则被划分为水平方向,若在22.5-67.5之间则为45°方向,67.5-112.5则被划分为竖直方向,112.5-157.5°则被划分为135°方向),
需要提及的是,在梯度方向的划分上,只是简单的采用了四个方向的梯度分类,但还可以通过双线性插值方法求取当前点在梯度方向上两边点的梯度值,然后再进行梯度值的比较。
3)得到的梯度矩阵中梯度值大的点可能是边缘点,也可能为噪声,在此,遍历梯度矩阵中的每个点,根据该点的梯度方向,找到以该点为中心点的8邻域上与之梯度方向相同的两个点,例如,若中心点梯度方向值为20°,则被划分为水平方向,与z5梯度方向相同的点为z4、z6
比较他们的梯度值大小,若z5的梯度值大于z4、z6,且满足这该点被判断为边缘上的点
4)但这样得到的边缘可能还包含噪声或者虚假边缘,canny算子还采用双阈值法检测和连接边缘,首先用低阈值在检测最大值梯度所在点作为边缘点的图像中滤掉一部分噪声,再与强阈值比较后的具有较少假边缘但是边缘不连续的图像叠加找出封闭边缘,最后得到较好效果的边缘检测图像。
5)基于canny算子的边缘检测算法在Matlab中的调用方式如下:
BW = edge(I,'canny')
直接调用,模板、方差均采用matlab默认值
BW = edge(I,'canny',thresh)
thresh为指定阈值,注,不指定时默认为lowThresh=0.0375,highThresh=0.0938
BW = edge(I,'canny',thresh,SIGMA)
SIGMA为高斯滤波器的方差,默认为1
[BW,thresh] = edge(I,'canny',...)
返回Matlab根据灰度直方图自适应计算出的高低阈值(滞后阈值)thresh
6)Matlab实现canny算子边缘检测的主体代码如下:
I = imread('pout.tif');
a = im2single(I);
%%%%%%高斯平滑%%%%%%%%%%%%%%%%%%%%、
% Create an even-length 1-D
separable Derivative of Gaussian filter
% Determine filter
length
x =-4:4;%Matlab默认滤波模板长度
% Create 1-D Gaussian
Kernel
c = 1/(sqrt(2*pi));
gaussKernel = c * exp(-(x.^2)/2);
% Normalize to ensure
kernel sums to one归一化高斯核
gaussKernel = gaussKernel/sum(gaussKernel);%
% Create 1-D Derivative of
Gaussian Kernel高斯核求梯度
derivGaussKernel =
gradient(gaussKernel);%
% Normalize to ensure
kernel sums to zero
negVals = derivGaussKernel < 0;
posVals = derivGaussKernel > 0;
derivGaussKernel(posVals) =
derivGaussKernel(posVals)/sum(derivGaussKernel(posVals));
derivGaussKernel(negVals) =
derivGaussKernel(negVals)/abs(sum(derivGaussKernel(negVals)));
% Compute smoothed
numerical gradient of image I along x (horizontal)
% direction. GX corresponds
to dG/dx, where G is the Gaussian Smoothed
% version of image
I.
GX = imfilter(a, gaussKernel', 'conv',
'replicate');%水平方向的高斯滤波
平滑图像,减弱噪声
dx = imfilter(GX, derivGaussKernel, 'conv',
'replicate');%求水平方向导数,即关于x的偏导数。
% Compute smoothed
numerical gradient of image I along y (vertical)
% direction. GY corresponds
to dG/dy, where G is the Gaussian Smoothed
% version of image
I.
GY = imfilter(a, gaussKernel, 'conv',
'replicate');
dy = imfilter(GY, derivGaussKernel',
'conv', 'replicate');%求竖直方向导数,即关于y的偏导数。
% Calculate Magnitude of
Gradient计算梯度
magGrad = hypot(dx, dy);%等同于sqrt(dx^2+dy^2),只不过hypot运算效率更高
% Normalize for threshold
selection归一化梯度值
magmax = max(magGrad(:));
if magmax > 0
magGrad = magGrad /
magmax;
end
% Determine Hysteresis
Thresholds计算滞后阈值
[m,n] = size(magGrad);
% Select the
thresholds
counts=imhist(magGrad, 64);
highThresh =
find(cumsum(counts) > 0.7*m*n,...
1,'first') / 64;
lowThresh =
0.4*highThresh;%取默认自适应滞后阈值
% Perform Non-Maximum
Suppression Thining(抑制非极大值) and Hysteresis Thresholding of
Edge
%magGrad为归一化后的图像上每个像素点的梯度值
E =
cannyFindLocalMaximainfer(dx,dy,magGrad,lowThresh);%找到梯度局部极大值,滤掉低阈值部分,且边缘点被定义为梯度方向上局部强度最大的点。
if ~isempty(E)
[rstrong,cstrong] =
find(magGrad>highThresh &
E);%根据高阈值得到一个边缘图像,记录下所有非零点的位置
if
~isempty(rstrong) %
result is all zeros if idxStrong is empty
e = bwselect(E, cstrong, rstrong,
8);%将通过高阈值部分找到的边缘闭合,bwselect函数的作用为将所有非零点的8连通域置1。
例:
BW1 =
imread('text.png');
c = [126
187 11];
r = [34
172 20];
BW2 =
bwselect(BW1,c,r,4);
figure,
imshow(BW1)
figure,
imshow(BW2)
由图可见,(126, 34) , (
11,20)
分别为T和d连通域上的一非零点,所以其所在的T和d连通域都置1,而(187,172)为零点,没有连通域。
else
e = false(size(E));
end
else
e =
false(size(E));
end
thresh = [lowThresh highThresh];
function E =
cannyFindLocalMaximainfer(dx,dy,magGrad,lowThresh)
thea = rad2deg(atan(dy./dx)+pi/2);%将计算所得的梯度方向由弧度值转化为角度制,并将角度范围调整为
0-180.
[m,n] = size(magGrad);
E = false(m,n);
r = m - 1;
c = n - 1;
for i = 2 : r
for j = 2 :
c
if 112.5 <=
thea(i,j) &&
thea(i,j) < 157.5
direction
= 135;
if
max([magGrad(i-1,j-1) magGrad(i,j)
magGrad(i+1,j+1)]) == magGrad(i,j)
&& magGrad(i,j) >
lowThresh
E(i,j) = true;
else
E(i,j) = false;
end
elseif 22.5 <= thea(i,j)
&& thea(i,j)
< 67.5
direction
= 45;
if
max([magGrad(i+1,j-1) magGrad(i,j)
magGrad(i-1,j+1)]) == magGrad(i,j)
&& magGrad(i,j) >
lowThresh
E(i,j) = true;
else
E(i,j) = false;
end
elseif 67.5 <= thea(i,j)
&& thea(i,j)
< 112.5
direction
= 90;
if
max([magGrad(i,j-1) magGrad(i,j)
magGrad(i,j+1)]) == magGrad(i,j) &&
magGrad(i,j) >
lowThresh
E(i,j) = true;
else
E(i,j) = false;
end
else
direction
= 0;
if
max([magGrad(i-1,j) magGrad(i,j)
magGrad(i+1,j)]) == magGrad(i,j) &&
magGrad(i,j) >
lowThresh
E(i,j) = true;
else
E(i,j) = false;
end
end
end
end
%注:Matlab中的寻找局部极大值函数为MEX格式,无法打开,我参考冈萨雷斯和网上资料写
%出的类似处理效果的程序,但是核心思想是相同的,同时不足之处也难免存在,欢迎大家批评
%指正。附上处理效果对比图。从对比图可看出,自编的寻找梯度极大值法具有和Matlab自带
�nny算子检测具有几乎完全相同的效果。