大家好,又见面了,我是你们的朋友全栈君。
边缘概述
边缘可以认为是图像中一定数量点亮度发生变化的地方,边缘检测大体上就是计算这个亮度变化的导数,依据导数的大小,判断亮度变化大小,从而界定目标与背景。在经典的边缘检测算法中Roberts算子,Prewitt算子,Sobel算子属于一阶差分算子,LoG算子,Canny算子属于二阶差分算子。一阶差分算子,就是求图像灰度变化曲线的导数,从而可以突出图像中的对象边缘,而二阶差分算子,求图像灰度变化导数的导数,对图像中灰度变化强烈的地方很敏感,从而可以突出图像的纹理结构。即一阶求边缘,二阶不仅检测出边缘还可检测出弱边缘(纹理)
**(源码资源****有常(注意目前是有常哦)私我vx:xdsqczkyqs713,源码(带GUI,GUI是用户界面的意思)40圆,
加一份课设报告50圆
给的联系方式有常求源码的小可爱们加,非诚勿扰/<->认真脸)****
这里给一个报告目录供大家参考:
1 概述 3
2边缘检测 4
2.1 一阶检测算子 4
2.1.1 Roberts算子 4
2.1.2 Prewitt算子 5
2.1.3 Sobel算子 6
2.2 二阶Log算子 7
2.3 Canny算法 8
3 程序设计 10
3.1 Canny算法实现 10
3.2 GUI程序设计 12
4结果展示与分析 14
4.1 GUI操作说明 14
4.2 程序测试 16
4.3 结果分析 19
5 心得体会 20
6 参考文献 21
有毕设徐秋,Canny算法改进,见这篇基于双边滤波的改进型Canny算法边缘检测冠状动脉CT图像
Canny算法
从表面效果上来讲,Canny算法是对Sobel、Prewitt等算子效果的进一步细化和更加准确的定位,同时借鉴了LoG算子先进行高斯滤波(噪声平滑)再进行图像梯度计算的思想。
Canny算法的步骤如下:
1、对输入图像进行高斯平滑,降低错误率。
实现方法就是采用高斯平滑算子进行卷积运算。假设原图像为img0
gauss = [1 2 1; 2 4 2;1 2 1] / 16; % Gauss平滑模板
img = conv2(img0, gauss, ‘same’); % 平滑
2、计算梯度幅度和方向来估计每一点处的边缘强度与方向。
一般采用Sobel算子的水平方向和竖直方向的模板分别进行卷积,分别得到水平方向的检测结果DX和垂直方向的检测结果DY,进一步可得到梯度的幅度:
sobelx = [-1 0 1; -2 0 2; -1 0 1]; % Sobel水平边缘模板
sobely = sobelx’; % Sobel垂直边缘模板
gradx = conv2(img, sobelx, ‘same’); % 水平边缘卷积
grady = conv2(img, sobely, ‘same’); % 垂直边缘卷积
M = 根号下DX的平方+DY的平方
为了简化计算,通常也可以近似写作:
M=|DX|+|DY|
边缘方向为:
θ=arctan(DY/DX)
3、根据梯度方向,对梯度幅值进行非极大值抑制。
本质上是对Sobel、Prewitt等算子结果的进一步细化。
非极大值抑制是Canny算法最重要的思想,它不像其他的算子仅仅利用了梯度值的大小,还利用了梯度值的方向,这也是Canny算法求出的边缘具有无方向性,任意方向的边缘检测效果都很好的原因。
那么怎么利用梯度方向呢?看下边这张盗图:
哦,原来梯度方向与边缘方向满足左手定则(掌心对图像,大拇指指向边缘方向,四指则是梯度方向),也就是说通过计算出的某个像素点的梯度方向我们可以得到该位置的边缘方向。
在33区域内,边缘可以划分为垂直、水平、45°、135°,4个方向,据此将360等分4份(不论正负,区域关于中心方向对称),同样的梯度方向也为四个方向,可划分为4份*。
水平边缘–梯度方向为垂直: – pi / 8<alpha<pi / 8
135°边缘–梯度方向为45°: -3pi / 8 <alpha< – pi / 8
垂直边缘–梯度方向为水平: – pi / 2<alpha<3pi / 8, 3pi / 8<alpha<pi /2
45°边缘–梯度方向为135°: pi / 8 <alpha< 3 * pi / 8
或者式子看起来烦就看下这边这个图:
在每一点上,领域中心 x 与沿着其对应的梯度方向(由θ的值确定)的两个像素相比,若中心像素为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。
4、用双阈值处理和连接边缘。
关于阈值设置的问题,大佬说了“一般”下边这样搞好,就这样搞呗,不多解释(我也解释不来,hhh)
阈值一般为图像非极大值抑制的图像最大像素值max和系数TH(高阈值系数),TL(低阈值系数)确定:Hedge=THmax,Ledge=TLmax,系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1)然后将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点为确定边缘点),赋1或255;将小于高阈值,大于低阈值的点使用8连通区域确定。
这个步骤完成了孤立边缘的舍去和不完整边缘的一个连接,去除了假边缘,优化了真边缘。不得不说大佬说的就是对。
经过上述步骤,Canny算法的边缘检测实现了以下目标:
1、低错误率。所有边缘都应被找到,且没有伪响应。
2、边缘点应该被很好地定位。已定位的边缘必须尽可能接近真实边缘。
3、单一的边缘点响应。这意味在仅存一个单一边缘点的位置,检测器不应指出多个像素边缘。
砍你(Canny)任务完成了,留给小菜鸡一堆疑问,边缘检测出的不是2值图像吗???
哦,大佬觉得下边的工作配不上他的才华,唉这样的工作也就小菜鸡来做了。
Canny算法完成后图像还是一个double型的灰度图,首先得转为一个uint8类型的灰度图。如果直接输出的话可以采用下边的语句:
imshow(image,[ ])
其实这个语句的意思就是把double型数据最大的赋值为255,最低的赋值为0,中间部分按照高于最低值的部分,占最高值的百分比,再乘以255,完成赋值即可。
假设canny算法后得到的图像为TLedge,转换方法如下:
pmin=min(min(TLedge));pmax=max(max(TLedge));
I1=uint8((double(TLedge)-pmin)/(pmax-pmin)255);
最后转为二值图像:
I1=im2bw(I1,k);
这个k就是一个转换阈值(0<k<1),高于255k则赋值为1(白色)
通过这个k就可以控制边缘输出多少,k越大,阈值越大,边缘输出越少。
给k设置个滑动条就非常有效果,看下边这个动图(滑动条mycanny为字写,对照函数是matlab自带),砍你算法名不虚传,一个打所有:
Canny算法因为本身的边缘检测能力最强,所以可以通过控制边缘阈值达到其他模板算子的效果(赢者通吃),甚至检测出的边缘更细,更加符合实际。
好啦,讲到这里。下边到了大家最开心的贴代码环节,两个函数myCanny.m(主体),traverse.m(第4步连接边缘递归用到的)。
都好好写注释了的,有帮助记得双击么么哒。
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);
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;
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/139282.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...