图像的边缘检测:
比较拉普拉斯算子,LOG算子,Canny算子三种边缘检测算法。
编程思路:
图像边缘就是图像灰度值突变的地方,也就是图像在该部分的像素值变化速度非常之快,就比如在坐标轴上一条曲线有刚开始的平滑突然来个大转弯,在变化出的导数非常大。
LoG算子,Canny算子属于二阶差分算子
Laplace算子是各项同性的,即具有旋转不变性。
二阶拉普拉斯方程:
∇^2 f(x,y)=(∂^2 f)/(∂^2 x)+(∂^2 f)/∂y
写成二维离散形式:
∇^2 f(x,y)=f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y)
拉普拉斯的卷积模板就是下图,这里我们使用第一个。
1980年,Marr和Hildreth提出将Laplace算子与高斯低通滤波相结合,提出了LOG(Laplace and Guassian)算子。
步骤如下:
源代码:
%% 三种边缘检测比较
clc;
clear;
close all;
img = imread('tom.png'); %读取图像
img = rgb2gray(img);
%img=imnoise(img,"salt & pepper"); %添加椒盐噪声
[h w]=size(img);
Lapu=zeros(h,w);
t = 128;
for row = 2 : h-1 %实现拉普拉斯
for col = 2 : w-1
Lapu(row,col)=abs(4*img(row,col) - img(row-1,col) -img(row,col-1) - img(row,col+1) - img(row+1,col+1));
end
end
LOG = edge(img,'log'); %实现LOG
LOG1 = myLog(img,0.1);
% Canny算子
CANNY= edge( img, 'canny'); %使用Canny算子进行边缘检测,得到二值边界图像
CANNY1 = myCanny(img,0.1);
figure();
subplot(231),imshow(img),title("原图");
subplot(232),imshow(Lapu),title("拉普拉斯算子后的图");
subplot(233),imshow(LOG),title("LOG算子后的图");
subplot(234),imshow(LOG1),title("手写LOG算子后的图");
subplot(235),imshow(CANNY),title("canny算子后的图");
subplot(236),imshow(CANNY1),title("手写canny算子后的图");
myLog.m
function [BW] = myLog(I,th)
grayI = I;%判断是否为灰度图,不是则转为
gauss = [1 2 1; 2 4 2;1 2 1] / 16; % Gauss平滑模板
grayI = conv2(grayI, gauss, 'same'); % 平滑
[m,n]=size(grayI);
newI=grayI;
LogNum=0;
for j=2:m-1
for k=2:n-1
LogNum = abs(4*grayI(j,k)-grayI(j-1,k)-grayI(j+1,k)-grayI(j,k+1)-grayI(j,k-1));
if(LogNum > th*255)
newI(j,k)=255;
else
newI(j,k)=0;
end
end
end
BW = im2bw(newI);
myCanny.m
function I1= myCanny(I,k)
img0 = double(I);
gauss = [1 2 1; 2 4 2;1 2 1] / 16; % Gauss平滑模板
sobelx = [-1 0 1; -2 0 2; -1 0 1]; % Sobel水平边缘模板
sobely = sobelx'; % Sobel垂直边缘模板
img = conv2(img0, gauss, 'same'); % 平滑
gradx = conv2(img, sobelx, 'same'); % 水平边缘卷积
grady = conv2(img, sobely, 'same'); % 垂直边缘卷积
% M = sqrt(gradx .^ 2 + grady .^ 2); % 边缘高度
M = abs(gradx)+ abs(grady); % 边缘高度
alpha = atan(grady ./ gradx); % 边缘方向
N = zeros(size(M)); % 非最大抑制图像
for i = 2: length(M(:, 1)) - 1
for j = 2: length(M(1, :)) - 1
dirc = alpha(i, j); % 四个基本方向判断并进行非最大抑制,比如矩阵是
% [1 2 3;4 5 6;7 8 9],边缘延[4 5 6]方向,那
% 么我们关心的是元素2和元素8与元素5的大小关系
if abs(dirc) <= pi / 8
if M(i, j) == max([(M(i, j - 1)), M(i, j), M(i, j + 1)])%竖直梯度,水平边缘
N(i, j) = M(i, j);
end
elseif abs(dirc) >= 3 * pi / 8
if M(i, j) == max([(M(i - 1, j)), M(i, j), M(i + 1, j)])%水平梯度,竖直边缘
N(i, j) = M(i, j);
end
elseif dirc > pi / 8 && dirc < 3 * pi / 8
if M(i, j) == max([(M(i - 1, j - 1)), M(i, j), M(i + 1, j + 1)])
N(i, j) = M(i, j);
end
elseif dirc > - 3 * pi / 8 && dirc < - pi / 8
if M(i, j) == max([(M(i + 1, j - 1)), M(i, j), M(i - 1, j + 1)])
N(i, j) = M(i, j);
end
end
end
end
TH = 0.05* max(max(N)); % 高阈值
TL = 0.025 * max(max(N)); % 低阈值
THedge = N;
TLedge = N;
THedge(THedge < TH) = 0; % 强边缘
TLedge(TLedge < TL) = 0; % 弱边缘
THedge = padarray(THedge, [1, 1], 0, 'both'); % 进行边扩展,防止遍历时出错
TLedge = padarray(TLedge, [1, 1], 0, 'both');
TLedge0 = TLedge;
isvis = ones(size(THedge)); % 是否遍历过某像素,是为0,不是为1(便于计算)
while(sum(sum(THedge)))
[x, y] = find(THedge ~= 0, 1); % 寻找8邻域内非0像素,作为下一步搜索的起点
THedge = THedge .* isvis; % 搜索过的点标记为0
[TLedge0, isvis] = traverse(TLedge0, x, y, isvis); % 递归遍历,最终剩下的是未遍历的元素,即孤立点或非目标边缘
end
TLedge = TLedge - TLedge0; % 作差求出Canny边缘
THedge(:, end) = []; THedge(end, :) = []; THedge(1, :) = []; THedge(:, 1) = []; % 删去扩展的边缘
TLedge(:, end) = []; TLedge(end, :) = []; TLedge(1, :) = []; TLedge(:, 1) = [];
pmin=min(min(TLedge));pmax=max(max(TLedge));
I1=uint8((double(TLedge)-pmin)/(pmax-pmin)*255);
I1=im2bw(I1,k);
Traverse.m
function [output, isvis] = traverse(mat, i, j, isvis)
mat(i, j) = 0;
isvis(i, j) = 0;
neighbor = mat(i - 1: i + 1, j - 1: j + 1);
while(sum(sum(neighbor)))
[x, y] = find(neighbor ~= 0, 1);
neighbor(x, y) = 0;
mat(i - 2 + x, j - 2 + y) = 0;
[mat, isvis] = traverse(mat, i - 2 + x, j - 2 + y, isvis);
end
output = mat;
结果:
Log和Canny两个k都为0.1的图
分析:
从以上的检测结果中可以看出:
没有写阈值判断的拉普拉斯,效果不好,因为首先有一部分没有显示出来,头发那里的细节太多了,将不需要的结果展现了出来。
手写:拉普拉斯算子特点:在有噪声的时候,log算子双倍加强了噪声的影响,去噪功能不是太好。当给Log算子给的阈值为0.1的时候效果还行,当大一点的时候效果就不明显了,不能有效地提取边缘。这里我手写的log算子效果在有噪声的时候和没噪声的时候都没有自带的Log算子效果好。
Canny算子而言,我感觉手写的效果比系统带的效果更好,其次我感觉去噪效果都不好。
LOG算子容易受尺度的影响,不同尺度下的边缘点要用不同尺度的LOG算子检测,Canny 算子受尺度的影响不太明显,不同尺度下,边缘点的位置都有偏差,但几乎相同; LOG算子对噪声的抑制能力随着尺度的增加而增加,相同尺度下的Canny算子比LOG算子的抗噪声能力强,而LOG算子比Canny算子的边缘点准确;在尺度选择合适的情况下,LOG算子对图像边缘点检测的位置非常准确,能够保留边缘点比较细致的组织结构,而Canny算子对图像边缘检出率比较高,包括纹理区域,以及对比度很弱的边缘点,但是对这些边缘点的组织结构刻画得不是特别细致,边缘点的位置有小范围的偏差。