【Matlab】【原创】【数字图像处理】经典Canny边缘检测算子的手动实现

1、使用高斯滤波器平滑输入图像,消除高频噪声
2、计算输入图像的梯度幅值与梯度角度。。。使用sobel算子计算梯度
3、进行非极大值抑制
(1)首先,我们创建角度模板,规定边缘的四个方向:-45°、0°、45°、90°,并创建一个矩阵,储存3x3邻域中,与上述4个方向对应的坐标偏移量,即相对于邻域中心点的右下角、下方、左下角、右方。
(2)其次,由于我们使用的arctan函数计算梯度方向,那么得到的角度矩阵中的值域就为-90°到90°,为了方便下一步寻找在上一步的四个角度中哪一个与梯度方向最近,我们要将梯度方向进行归一化,即将-90°到90°的值域归一化到-67.5°到112.5°(其中将小于-67.5°的部分+180度旋转到对面去)。在这里插入图片描述
(3)将每一个像素对应的梯度方向与四个角度做差,求出最小值,这里我们需要最小值对应的索引,例如索引值1对应-45度,即梯度方向离-45度最近,那么我们将邻域中心像素的坐标加上-45度对应的偏移量即可,即储存有坐标偏移量矩阵的第一行数据。通过直接调用索引值,我们就能实现在像素邻域中相应角度的像素选取。
(4)当求出所有距离像素梯度方向最近的角度之后,我们得到了元素为1、2、3、4的矩阵,即对应角度的索引值。现在,我们计算在每个3x3邻域中,中心像素与其相应最近角度所在直线方向上的两个8邻接像素之间的关系,例如如果某个中心像素的梯度方向距离0°最近,那么就考察该像素的梯度幅值与它上面和下面的像素的梯度幅值之间的关系。
(5)如果邻域中心像素的梯度幅值都不小于它的两个邻居的值,那么就保留该梯度幅值;如果情况相反,则丢弃该位置的梯度幅值。
4、进行滞后阈值处理
人为设置一高一低两个阈值,认为大于高阈值的梯度幅值所在位置为强边缘的位置,在高阈值和低阈值之间的梯度幅值所在位置为弱边缘的位置
5、进行边缘连接
在存储有强边缘的图像中定位所有强边缘像素的位置,并将该位置记作是最终的边缘,然后在每个强边缘像素的八邻域中,检测是否存在弱边缘像素,如若存在,则将其也记作是最终的边缘。即:将强边缘看成是轨道,沿着该轨道检测沿途的3x3区域中是否存在弱边缘。
6、进行边缘细化
由于检测出的边缘比较粗,为了方便使用,利用数学形态学进行细化。

在本例中,使用大小为13x13,标准差为2的高斯窗进行高斯滤波。(注:为了保证在n*n的区域中,二维高斯概率密度函数能包含有99.7%的概率,需要将n设置为6倍标准差的最小正奇数),高低阈值分别为0.1与0.04。

f=imread('Fig1006(a).tif');
f=double(f);
f=f/255;
%高斯滤波,平滑图像,去除高频噪点
G=fspecial('gaussian',13,2);
f_gaussian=imfilter(f,G,'replicate');
figure(1),imshow(f_gaussian);

%使用Sobel算子计算梯度
S_x=fspecial('sobel');
f_sobel_x=imfilter(f_gaussian,S_x,'replicate');
S_y=S_x';
f_sobel_y=imfilter(f_gaussian,S_y,'replicate');
f_sobel_amp=sqrt(f_sobel_x.^2+f_sobel_y.^2);
f_sobel_ang=atan(f_sobel_y./f_sobel_x);%注意arctan函数的值域为-pi/2到pi/2,即f_sobel_ang中的值只会在这两个数之间
figure(2),imshow(f_sobel_amp);
figure(3),imshow(f_sobel_ang);

%非最大值抑制
%将4个基本边缘方向的角度值存放在向量direction中
direction=[-45 0 45 90];
%将4个基本边缘方向的角度值对应的邻域中相对于中心像素位置的平移量保存在2维矩阵direction_offset中
direction_offset=[1 -1;1 0;1 1;0 1];
%将角度单位转成弧度单位
direction=direction/180*pi;
%将f_sobel_ang中的弧度值转化进-67.5度对应的弧度到90度对应的弧度范围间
f_sobel_ang_trans=zeros(size(f_sobel_ang));
for i=1:size(f_sobel_ang,1)
    for j=1:size(f_sobel_ang,2)
        if f_sobel_ang(i,j)<-67.5/180*pi
            f_sobel_ang_trans(i,j)=f_sobel_ang(i,j)+pi;
        else
            f_sobel_ang_trans(i,j)=f_sobel_ang(i,j);
        end
    end
end
f_sobel_ang_appro=zeros(size(f_sobel_ang));%保存弧度近似的结果
%寻找与f_sobel_ang_trans中的每个弧度最近的向量direction中的弧度对应的索引值,并将近似弧度的索引值保存在f_sobel_ang_appro中
for i=1:size(f_sobel_ang_trans,1)
    for j=1:size(f_sobel_ang_trans,2)
        [~,indices]=min(abs(f_sobel_ang_trans(i,j)-direction));
        f_sobel_ang_appro(i,j)=indices;
    end
end
f_sup=zeros(size(f,1)+2,size(f,2)+2);%保存最大值抑制的结果
%在f_sobel_amp的任一3x3邻域中判断邻域中心值是否全部大于沿近似角度方向和反方向上的两个邻域值。
%为避免滑窗时溢出,先进行图像边界填充
f_sobel_amp=padarray(f_sobel_amp,[1,1],'replicate');
%为方便角度信息使用,对f_sobel_ang_appro进行图像边界填充
f_sobel_ang_appro=padarray(f_sobel_ang_appro,[1,1],'replicate');
for i=2:size(f_sobel_amp,1)-1
    for j=2:size(f_sobel_amp,2)-1
        %邻域像素的坐标计算
        a(1,:)=[i,j]+direction_offset(f_sobel_ang_appro(i,j),:);
        a(2,:)=[i,j]-direction_offset(f_sobel_ang_appro(i,j),:);        
        if f_sobel_amp(i,j) >= f_sobel_amp(a(1,1),a(1,2)) && ... %a(1)为行数,a(2)为列数
           f_sobel_amp(i,j) >= f_sobel_amp(a(2,1),a(2,2)) 
            f_sup(i,j)=f_sobel_amp(i,j);
        else
            f_sup(i,j)=0;
        end
    end
end
%将最大值抑制结果的尺寸调整成输入图像的尺寸
f_sup=f_sup(2:size(f_sup,1)-1,2:size(f_sup,2)-1);
figure(4),imshow(f_sup);

%滞后阈值处理
%Canny建议低阈值:高阈值=1:3
low=0.04;
high=0.1;
f_sup_low=(f_sup>=low);%储存强边缘+弱边缘
f_sup_high=(f_sup>=high);%储存强边缘
f_sup_thresh=f_sup_low-f_sup_high;%储存弱边缘,即low<=f_sup_thresh<=high
figure(5),imshow(f_sup_thresh);
figure(6),imshow(f_sup_high);

%边缘连接操作
%由于需要对图像进行3x3邻域的滑窗操作,所以需要扩展图像大小
f_sup_thresh=padarray(f_sup_low,[1,1],'replicate');
f_sup_high=padarray(f_sup_high,[1,1],'replicate');
f_sup_conn=zeros(size(f_sup_thresh));
neigh=[-1 -1;-1 0;-1 1;0 -1;0 1;1 -1;1 0;1 1];%8邻域坐标偏移模板
for i=2:size(f_sup_conn,1)-1
    for j=2:size(f_sup_conn,2)-1
        %判断在第i行,第j列是否存在强边缘
         if f_sup_high(i,j)==1
             %若存在,则将该点看作是边缘点,显示在f_sup_conn中
             f_sup_conn(i,j)=1;
             %在点(i,j)的8邻域中进行操作,检查是否存在弱边缘点
             for k=1:8
                 %若8邻域中的某一点是弱边缘点,则将该点看做是边缘点,显示在f_sup_conn中
                 if f_sup_thresh(i+neigh(k,1),j+neigh(k,2))==1
                     f_sup_conn(i+neigh(k,1),j+neigh(k,2))=1;
                 end
             end
         end
    end
end
f_sup_conn=f_sup_conn(2:size(f_sup_conn,1)-1,2:size(f_sup_conn,2)-1);
figure(7),imshow(f_sup_conn);

%对比
f_comparison=edge(f,'canny',[low,high],2);
figure(8),imshow(f_comparison);

实验结果:
1、输入图像:
在这里插入图片描述

2、先高斯滤波,后使用sobel算子得到的梯度幅值图像
在这里插入图片描述
3、梯度方向图像
在这里插入图片描述
上图中,亮度相同的区域代表梯度方向相同

4、非极大值抑制
在这里插入图片描述
与梯度幅值图进行比较,可以看出,非极大值抑制后,抑制了由高斯滤波而产生的边缘模糊,就像是没有开启抗锯齿的游戏效果。

5、弱边缘图像
在这里插入图片描述
6、强边缘图像
在这里插入图片描述
可以看到边缘比较粗,需要细化。

7、边缘连接并细化后的图像
在这里插入图片描述
8、与matlab自带的canny算子进行比较
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u011861755/article/details/85218530