基于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)
上一篇 2022年5月7日 上午11:20
下一篇 2022年5月7日 上午11:20


相关推荐

  • 什么是nano banana?附16种用法

    什么是nano banana?附16种用法

    2026年3月15日
    3
  • 用计算机最炫民族风乐谱,最炫民族风简谱「建议收藏」

    用计算机最炫民族风乐谱,最炫民族风简谱「建议收藏」最炫民族风苍茫的天涯是我的爱绵绵的青山脚下花正开什么样的节奏是最呀最摇摆什么样的歌声才是最开怀弯弯的河水从天上来流向那万紫千红一片海火辣辣的歌谣是我们的期待一路边走边唱才是最自在我们要唱就要唱得最痛快你是我天边最美的云彩让我用心把你留下来(留下来)悠悠的唱着最炫的民族风让爱卷走所有的尘埃(我知道)你是我心中最美的云彩斟满美酒让你留下来(留下来)永远都唱着最炫的民族风是整片天空最美的姿态(留下…

    2026年2月20日
    7
  • 【小白入门】JavaScript(二)—— D3.js

    【小白入门】JavaScript(二)—— D3.js目录 D3 是什么 D3 对 JavaScript 的简化 2 选择集与数据绑定 3 使用 d3 在 SVG 画布中绘图 4 比例尺的使用 4 1 线性比例尺 4 2 序数比例尺 D3 是什么 D3 的全称是 Data DrivenDocume 顾名思义可以知道是一个被数据驱动的文档 D3 的本质是 JavaScript 所以用 JavaScript 可以实现它全部的功能 但是有这个函数库可以极大的减少工作量 尤其在数据可视化方面 D3 对 JavaScript 的简化基本 JavaScript 代码实现文本替换

    2026年3月19日
    2
  • 运营 | 小白学数据分析之DNU/DAU

    运营 | 小白学数据分析之DNU/DAUhttp www sykong com 2014 05 19081http www sykong com tag E5 B0 8F E7 99 BD E5 AD A6 E8 BF 90 E8 90 A5 运营 小白学数据分析之 DNU DAU2014 05 0615 45 手游运营 19 100 文 jetp

    2026年3月17日
    2
  • webpack配置vue3项目

    webpack配置vue3项目用 webpack 搭建 Vue3 项目一 项目需要的基本依赖 1 vue 的依赖 2 ui 组件库依赖 3 AJAX 请求库依赖 axios 二 几个 loader 和 webpack 配置 1 引入库 2 目录结构三 main js 入口文件与 index html1 main js2 index html 四 webpack 配置文件五 路由文件总结声明 本文章采用 node 的包管理工具和 webpack 一 项目需要的基本依赖 1 vue 的依赖声明一点 自从 22 年 3 月依赖 vue 的默认版本就变成了 3 x 所有后面需要 指定版本 n

    2026年3月18日
    2
  • 腾讯元宝回答怎么设置成中文

    腾讯元宝回答怎么设置成中文

    2026年3月13日
    2

发表回复

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

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