基于matlab的Canny算法的边缘检测(附源代码)

基于matlab的Canny算法的边缘检测(附源代码)边缘概述边缘可以认为是图像中一定数量点亮度发生变化的地方,边缘检测大体上就是计算这个亮度变化的导数,依据导数的大小,判断亮度变化大小,从而界定目标与背景。在经典的边缘检测算法中Roberts算子,Prewitt算子,Sobel算子属于一阶差分算子,LoG算子,Canny算子属于二阶差分算子。一阶差分算子,就是求图像灰度变化曲线的导数,从而可以突出图像中的对象边缘,而二阶差分算子,求图像灰度变化导数的导数,对图像中灰度变化强烈的地方很敏感,从而可以突出图像的纹理结构。即一阶求边缘,二阶不仅检测出边缘还可检测

大家好,又见面了,我是你们的朋友全栈君。

边缘概述

边缘可以认为是图像中一定数量点亮度发生变化的地方,边缘检测大体上就是计算这个亮度变化的导数,依据导数的大小,判断亮度变化大小,从而界定目标与背景。在经典的边缘检测算法中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<3
pi / 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),高于255
k则赋值为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;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/139282.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • mysql隔离级别为什么设置为可重复读_隔离性的4个级别

    mysql隔离级别为什么设置为可重复读_隔离性的4个级别知识点总结1.数据库默认隔离级别:mysql—repeatable,oracle,sqlserver—readcommited2.mysqlbinlog的格式三种:statement,row,mixed3.为什么mysql用的是repeatable而不是readcommitted:在5.0之前只有statement一种格式,而主从复制存在了大量的不一致,故选用repeata…

    2022年9月11日
    0
  • 求两个向量的夹角_数量积和向量积的公式

    求两个向量的夹角_数量积和向量积的公式如图所示,我们要计算任意两个向量之间的夹角。(图中的坐标数字是估计值,随手给定)python代码如下importmathAB=[1,-3,5,-1]CD=[4,1,4.5,4.5]EF=[2,5,-2,6]PQ=[-3,-4,1,-6]defangle(v1,v2):dx1=v1[2]-v1[0]dy1=v1[3]-v1[1]dx2=v2[2]-v2[…

    2022年9月25日
    0
  • 怎么测试服务器端口是否对外开放_如何查看windows某个端口是否打开

    怎么测试服务器端口是否对外开放_如何查看windows某个端口是否打开在工作中处理服务器故障问题的时候,经常需要检测一下Windows的服务器业务端口是否开放,是否能正常在外面通讯。下面小编与大家分享一下如何在windows环境下检测服务器端口是否开放。1.我们在工作中经常接触的都是TCP,UDP的端口都是很少用的。检测TCP端口能否在外面访问的方法很简单,telnet可以测试出来结果例如telnetwww.baidu.com80…

    2022年10月10日
    0
  • keil MDK5搭建STM32开发环境

    keil MDK5搭建STM32开发环境1.安装keil到keil的官方网站http://www.keil.com/download/product/下载MDK-ARM并安装,注意可以更改安装路径,但是不能安在需要管理员权限的文件夹,例如不能在ProgramFiles,否则会出现一些问题。2.注册未注测有代码容量限制,需要破解。网上找KEIL_Lic。3.安装器件包打开PackInstaller左边选择STMicro

    2022年5月10日
    74
  • Java Netty Codecs 程序「建议收藏」

    服务端定义了一个Handler和三个Decoder。Handler接收客户端的信息,然后传递给decoder过滤处理。1.服务端packagecom.learn.netty.codecs;importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.ChannelInitializer;importio.netty.channel.E.

    2022年4月12日
    39
  • WPF教程(二十五)WrapPanel[通俗易懂]

    WPF教程(二十五)WrapPanel[通俗易懂]WrapPanel用于一个接一个的排列子控件,以水平或者垂直方向,当空间不足时就会自动切换到下一行。适合于需要水平或者垂直排列控件且能自动换行的情况。水平方向排列时,每一行所有子控件的高度都被统一成固定的值,这个值由最高的那个决定;每一列垂直方向排列时,所有子控件的宽度都被统一成固定的值,这个值由最宽的那个决定。我们先来看默认情况下的WrapPanel:

    2022年7月22日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号