使用高斯滤波的目的是平滑图像,滤除图像中的部分噪声(因为微分算子对噪声很敏感)。高斯滤波具体办法是生成一个高斯模板,使用卷积进行时域滤波:
img = im2double(img);
h = fspecial('gaussian', 5, 2); % gaussian平滑模板
img = imfilter(img, h);
一幅图像可以表示为函数 I = f(x, y),其中(x, y)为坐标,I表示该像素点的灰度值,梯度gradient表示函数f(x, y)在点(x, y)处最大的变化率,计算的表达式为:
G ( x ) = d f ( x , y ) d x + d f ( x , y ) d y G(x)=\frac{df(x,y)}{dx}+\frac{df(x,y)}{dy} G(x)=dxdf(x,y)+dydf(x,y)
对于图像,我们也可以计算梯度,由于数字图像是有离散的像素点的灰度值构成,所以微分运算就变成了差分,我们可以用相邻两个像素点之间的差分值表示该像素点在某个方向上灰度的变化情况。
由于图像在边缘的变化情况很剧烈,而在非边缘处变化平缓,所以计算一幅图像的梯度得到的结果中,图像的边缘将被凸显出来,sobel算子是性能不错的微分算子,下图描述了使用sobel算子计算梯度的卷积过程:
上图所示为一个卷积过程(由于sobel算子做一个180旋转后和旋转前差别不大,因此可以用卷积替代相关运算),橙色的矩阵E为 sobel 算子,它可以用来计算 x 方向上的灰度变化,如果我们把矩阵E与图像中某个像素及其8邻域的元素构成的矩阵的对应元素相乘,可以得到该像素点在x方向的差分,所有像素点在x方向上的差分构成矩阵Gx,同理我们也可以计算得到y方向上的差分Gy,于是梯度G的大小为:
G = ( G x 2 + G y 2 ) 1 2 G = (Gx^2 + Gy^2)^\frac{1}{2} G=(Gx2+Gy2)21
另外,为了后续处理,我们需要知道梯度的方向,在matlab中可以使用atan函数通过正切值计算角度(角度取值范围为-pi到pi)
小细节:
语句 Gx = conv2(gray_img, gx, 'same');
表示灰度图与x方向的sobel算子进行卷积,结果只保留与gray_img大小相同的部分(两个矩阵卷积,col=col1+col2-1, row=row1+row2-1)
gx = [1 2 1; 0 0 0; -1 -2 -1];
gy = [1 0 -1; 2 0 -2; 1 0 -1];
Gx = conv2(img, gx, 'same');
Gy = conv2(img, gy, 'same');
G = sqrt(Gx.^2 + Gy.^2);
theta = atan(Gy./Gx); % atan2(y, x)
figure, imshow(G)
title('梯度运算后')
set(gcf, 'color', [1 1 1])
目的:细化边缘,在上面的图像中,梯度计算得到的边缘很粗,一条边缘中央一般很亮,两边亮度逐渐降低,可以根据这个特点去掉非局部灰度最高的“假边”,达到细化边缘的目的。
主要步骤
注意,代码在梯度矩阵G的边缘加了一圈,避免数组越界
根据梯度的方向将角度分为四种情况,分别计算插值得到的两个灰度值,比较灰度值
[M, N] = size(G);
temp = zeros(M+2, N+2); % 补充一圈轮廓
temp(2:M+1, 2:N+1) = G;
G = temp;
temp(2:M+1, 2:N+1) = theta;
theta = temp;
Edge = G(2:M+1, 2:N+1);
for x = 2 : M+1
for y = 2 : N+1
% 插值 判断
direction = theta(x, y);
dx = abs(cot(direction));
dy = abs(tan(direction));
% 根据梯度的方向(-pi/2, pi/2)划分情况 插值
if (-pi/2 <= direction <= -pi/4)
n1 = (1-dx)*G(x, y-1) + dx*G(x+1, y-1);
n2 = (1-dx)*G(x, y+1) + dx*G(x-1, y+1);
elseif -pi/4 < direction <= 0
n1 = (1-dy)*G(x+1, y) + dy*G(x+1, y-1);
n2 = (1-dy)*G(x-1, y) + dy*G(x-1, y+1);
elseif 0 < direction <= pi /4
n1 = (1-dy)*G(x+1, y) + dy*G(x+1, y+1);
n2 = (1-dy)*G(x-1, y) + dy*G(x-1, y-1);
else
n1 = (1-dx)*G(x, y+1) + dx*G(x+1, y+1);
n2 = (1-dx)*G(x, y-1) + dx*G(x-1, y-1);
end
if Edge(x-1, y-1) < n1 || Edge(x-1, y-1) < n2 % 判断是否为梯度最大值(边)
Edge(x-1, y-1) = 0;
end
end
end
figure, imshow(Edge)
title('非极大值抑制后')
set(gcf, 'color', [1 1 1])
目的:减少伪边缘点非极大值抑制之后,检测到的边缘线条比较多,我们可以滤掉一些很暗的边缘,并让主要的边缘凸显出来
步骤:
小技巧:判断灰度值位于高阈值和低阈值之间的像素点八邻域是否含有真边,可以使用最大值滤波,模板大小为3 x 3
构建一个新的图像,用来保存最终的边缘,首先将灰度高于高阈值的点放进去,相当于定下边缘的一些点,接着把疑似边缘且八邻域有构成边缘像素点的像素点也放进去,用来连接边缘
%% 双阈值检测
% 设置阈值,先让两个阈值相等确定上下界限
threshold_low = 0.1; % 排除假边上的点,影响边的连续性
threshold_high = 0.18; % 确定真边上的点,影响边的数量
temp(2:M+1, 2:N+1) = Edge;
Edge = temp;
TrueEdge = zeros(M+2, N+2);
domain = ones(3, 3);
isEdge = ordfilt2(Edge, 6, ones(3, 3)); % 获得像素点八邻域内的最大值
for x = 2 : M+1
for y = 2 : N+1
% 高于阈值threshold_high可认为是边
if Edge(x, y) >= threshold_high
TrueEdge(x, y) = 1;
% 低于阈值threshold_low一定不是边
elseif Edge(x, y) <= threshold_low
TrueEdge(x, y) = 0;
% 在双阈值之间,若像素周围含边则也当作边
else
TrueEdge(x, y) = isEdge(x, y) > threshold_high;
end
end
end
TrueEdge = TrueEdge(2:M+1, 2:N+1);
figure, imshow(TrueEdge)
title('双阈值检测后')
set(gcf, 'color', [1 1 1])
通过双阈值检测,我们得到的边缘就细了(不足:未进行进一步的细化、边不是很顺畅):
matlab的edge函数是一个现成的边缘提取函数,调用时可以自己选择算子的类型
BW = edge(I,method,threshold,sigma)
可以参考小白学习图像处理——图像的边缘检测(matlab实现)
代码:
img = imread('baby_hand.jpg');
gray_img = rgb2gray(img);
sigma = 1;
threshold1 = 0.15;
threshold2 = 0.25;
img1 =edge(gray_img,'canny', threshold1, threshold2, sigma);
figure, imshow(img1), title('sobel')
以上就是使用canny边缘检测器全部内容了,实现步骤包含:
步骤 | 目的 |
---|---|
梯度计算 | 得到边缘及其方向 |
抑制非极大值 | 细化边缘 |
双阈值检测 | 消除伪边缘,连接边缘 |
完结