news 2026/9/16 8:02:58

Matlab边缘检测源码拆解:Canny、LoG与Susan算子从原理到调参

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Matlab边缘检测源码拆解:Canny、LoG与Susan算子从原理到调参

简介:这份基于Matlab的边缘检测资源包,面向计算机、电子信息及数学等专业学生,可作为图像处理课程设计、期末大作业或毕业设计的参考资料,重点解决边缘检测算法实现与验证问题。压缩包共14个文件,包含9个.m源代码文件、4张jpg测试图像和1份doc程序运行说明,整体仅139KB,结构紧凑。源码覆盖Canny、Susan、LOG等经典边缘检测算子,并提供相应阈值辅助函数;测试图像包含棋盘、荷花等典型场景,便于读者直观对比不同算子的检测效果。程序运行说明有助于快速理清运行流程,读者可在此基础上自行调整参数或扩展功能。资源已有414人浏览学习,适合具备一定Matlab基础、希望动手调试代码的开发者参考使用。

1. 从Matlab源码包拆边缘检测:Canny、LoG、Susan 一次跑通

这个压缩包解压之后,没有复杂的工程框架,就是几个.m文件加几张测试图:canny_edge.mlog_edge.msusan.mcannyFindLocalMaxima.msusan_threshold.m,配了hehua.JPGqipan.JPGdiaohua.jpgxinglong.jpg。说实话,我第一反应是先把canny_edge.m拖进编辑器直接运行,结果发现输入参数得自己给,图像路径也要改,和纯调edge()门禁完全不一样。这也是很多课程设计里“源码能跑”和“真正看懂”之间的差距。下面就从预处理开始,把边缘检测从灰度化、滤波、梯度计算、非极大值抑制到双阈值连接拆开讲,顺便把 LoG 和 Susan 的参数坑也填上。适合要做期末大作业、课程设计,或者想摆脱一行调包思路的人。

2. 图像读取、灰度化与噪声抑制:边缘检测前的预处理逻辑

在运行canny_edge.m这类源码前,我一般会先把测试图读进来,统一转成灰度图。这个包里给的xinglong.jpghehua.JPG都是彩色图,直接传给边缘检测函数,要么报维度错误,要么在三通道上分别出边缘又被程序自己扔掉,结果非常不稳定。

2.1 从 imread 到 double:Matlab 图像处理的第一步

Matlab 的imread默认把彩色 JPEG 读成uint8,值域 0~255。uint8在做卷积时很容易溢出,比如两个很大的像素相减,结果可能变成负数,超出 0~255 的有效范围。所以我一般先im2double转成double,把值域映射到 0~1,再做滤波和梯度计算。下面是包外测试时我最常用的一段读取代码:

% 读取彩色图像并转灰度 img = imread('hehua.JPG'); if size(img, 3) == 3 gray = rgb2gray(img); else gray = img; end gray = im2double(gray); % 转成 double,值域 [0,1] whos gray

这段代码里size(img, 3)是取第三维长度,等于 3 就说明是 RGB 图像,rgb2gray按 0.299R+0.587G+0.114B 的权重合成灰度。im2double会把uint8的 0~255 线性映射到 0~1,这也是后面imgradientxy和自定义卷积结果能统一比较的前提。如果读进来的是索引图像,还要先ind2grayind2rgb处理,不过这个包里都是普通 JPG,用不上。

从资源里ex6.mex7.m这些命名能看出,作者应该是按实验编号存的脚本,ex6.m可能是 Sobel 验证,ex7.m是 Canny 主流程。实际跑的时候不要在每个脚本里重复读图,最好把读取、灰度化、去噪写成一个公共小节,后面所有算子都复用同一份gray

2.2 高斯滤波与中值滤波:为什么不能直接求梯度

边缘本质上是灰度突变区域,突变意味着相邻像素差很大,也就是梯度幅值高。但直接对原始图像求梯度,会把传感器噪声、压缩噪声也当成边缘放大。所以 Canny、LoG 这类算子在求梯度之前都会先平滑。高斯滤波的核由 sigma 决定,sigma 越大,图像越模糊,细小纹理被抑制,代价是真正的边缘位置也会偏移。

我一般在噪声不严重的图上用imgaussfilt(gray, sigma),sigma 取 1.0~1.5;如果图像有明显椒盐噪声,比如扫描图,就先用medfilt2(gray, [3 3])过一遍中值滤波,再做高斯。中值滤波对孤立噪声点效果比高斯好,而且不会把边缘磨得太圆。这里顺便说一句,很多人一提到边缘检测就想到 Prewitt 边缘检测原理,Prewitt 是两个 3x3 模板分别检测水平和垂直梯度,实现简单,但对噪声极其敏感。这个包里没有单独给 Prewitt 的.m文件,不过用fspecial('prewitt')可以自己验证,把它和 Canny 放在同一个预处理流程里对比时,Prewitt 的响应明显更碎。

下面是我对这个包里的图做预处理时的固定流程:

sigma = 1.2; gaussImg = imgaussfilt(gray, sigma); % 如果输入图像有椒盐噪声,先执行下面一行 % gaussImg = medfilt2(gray, [3 3]);

imgaussfilt是 Matlab 自带的快速高斯滤波函数,比imfilter(gray, fspecial('gaussian', ...))更快,边界处理默认是'replicate'medfilt2只对二维灰度图有效,彩色图要先转灰度,否则要用medfilt3或分通道处理。

2.3 平滑与基础算子参数对照

在这一步最容易踩的坑是“滤波窗口过大”。窗口越大,参与卷积的邻域像素越多,高频细节丢失越严重。所以我把常用算子和参数整理成一张表,方便做实验时对照:

处理方式函数/模板典型参数适用场景需要注意
高斯滤波imgaussfiltsigma=1.0~1.5高斯白噪声、一般图像平滑sigma过大边缘偏移
中值滤波medfilt2[3 3] 或 [5 5]椒盐噪声窗口太大会抹掉细线
LoG核fspecial('log')hsize=2ceil(3sigma)+1二阶导边缘零交叉检测逻辑要自己写
Prewitt算子fspecial('prewitt')无额外参数理解梯度原理对噪声敏感,边缘较粗
Sobel算子fspecial('sobel')无额外参数比Prewitt稍好仍无法处理弱边缘

我自己的习惯是,先在小图上用imshow(gray)观察噪声类型,再决定用高斯还是中值。像qipan.JPG这种棋盘图,边缘本身非常锐利,sigma 稍微大一点就容易把角点圆角化,所以我用 sigma=1.0;而hehua.JPG背景纹理复杂,sigma=1.5 更干净。预处理做完,后面 Canny 和 LoG 才有可比性。

3. Canny 算子实现拆解:梯度、非极大值抑制与双阈值

Canny 是这个包的核心,canny_edge.mcannyFindLocalMaxima.m两个文件就是完整实现。Canny 能不能出好效果,关键不在梯度计算,而在后面的非极大值抑制和滞后连接。只看edge()函数的人很难理解为什么 Canny 比 Sobel 的线更细、更连续。

3.1 Canny 边缘检测的标准五步流程

Canny 把边缘检测拆成五步:高斯平滑、计算梯度幅值和方向、非极大值抑制、双阈值分割、滞后连接。这个包里canny_edge.m应该就是按这个顺序写的,cannyFindLocalMaxima.m专职做局部极大值搜索。

用普通文本表示,梯度幅值M = sqrt(Gx.^2 + Gy.^2),方向theta = atan2(Gy, Gx)。但我不建议在源码里直接写sqrtatan,Matlab 的imgradientimgradientxy已经优化过,而且能同时返回方向数组。下面是按这个流程整理的简化版主函数骨架,和包里canny_edge.m的结构一致:

function [edgeMap] = canny_edge(grayImg, sigma, thLow, thHigh) % 输入:灰度图,高斯sigma,低阈值,高阈值 smoothImg = imgaussfilt(grayImg, sigma); % 用Sobel模板计算梯度 [Gx, Gy] = imgradientxy(smoothImg, 'sobel'); [Gmag, Gdir] = imgradient(Gx, Gy); % 非极大值抑制,得到细化后的梯度脊 nms = cannyFindLocalMaxima(Gmag, Gdir); % 双阈值:强边缘直接保留,弱边缘等滞后连接判断 strong = nms > thHigh; weak = (nms >= thLow) & (nms <= thHigh); edgeMap = hysteresis(strong, weak); end

imgradientxy(smoothImg, 'sobel')返回GxGy,默认的'sobel'是 3×3 模板,也可以换成'prewitt''central',这也是为什么 Canny 可以和 Prewitt 共用梯度计算基础。thLowthHigh是双阈值,我用thLow=0.05thHigh=0.15作为初值,实际调试时按照边缘断裂或噪声斑点的比例再调。

3.2 非极大值抑制:cannyFindLocalMaxima.m 为什么关键

cannyFindLocalMaxima.m做的事情是:沿着梯度方向看当前像素的幅值是不是局部最大,如果不是,就置 0。这样梯度幅值图上那些“宽”的边缘会被压缩成单像素宽。实现上,梯度方向被量化到四个扇区:水平、垂直、45°、135°。

function nms = cannyFindLocalMaxima(mag, dir) nms = zeros(size(mag)); [rows, cols] = size(mag); for i = 2:rows-1 for j = 2:cols-1 d = dir(i,j); if (abs(d) <= 22.5 || abs(d) >= 157.5) n1 = mag(i, j-1); n2 = mag(i, j+1); elseif (d >= 22.5 && d < 67.5) || (d <= -112.5 && d > -157.5) n1 = mag(i-1, j+1); n2 = mag(i+1, j-1); elseif abs(d) >= 67.5 && abs(d) < 112.5 n1 = mag(i-1, j); n2 = mag(i+1, j); else n1 = mag(i-1, j-1); n2 = mag(i+1, j+1); end if mag(i,j) >= n1 && mag(i,j) >= n2 nms(i,j) = mag(i,j); end end end end

这段代码里dir是角度值,范围是 -180° 到 180°。abs(d) <= 22.5 || abs(d) >= 157.5对应水平边缘,因为梯度方向垂直于边缘,水平边缘的梯度方向接近竖直。这里最容易写错的是扇区边界,比如 67.5° 到底归到对角还是竖直,差一点会让边缘出现断裂或错位。我的建议是在对比图上直接打印dir的中位数,观察分布再调阈值。

3.3 双阈值与滞后连接

双阈值包含高低两个值。高于thHigh的像素确定为强边缘,低于thLow的直接丢弃,介于中间的标记为弱边缘。弱边缘如果和强边缘有连通关系,就保留,否则删除。滞后连接的常见做法是用bwlabel给强边缘连通域编号,再检查弱边缘是否落在强边缘邻近:

function edgeRes = hysteresis(strong, weak) edgeRes = strong; [L, ~] = bwlabel(strong, 8); % 如果弱边缘像素的8邻域中存在强边缘,就把它加入结果 for i = 2:size(weak,1)-1 for j = 2:size(weak,2)-1 if weak(i,j) && sum(sum(strong(i-1:i+1, j-1:j+1))) > 0 edgeRes(i,j) = 1; end end end end

注意bwlabel默认按 8 连通编号,弱边缘必须落在强边缘的 8 邻域内才算连接。如果直接对weak & strongimdilate再取交集,速度更快,但可读性不如循环。包里的cannyFindLocalMaxima.m函数名和常见 Canny 教学代码同名,说明作者参考的是 Gonzalez 那套教材实现。

3.4 Canny 与其他梯度算子的效果对比表

对于qipan.JPG这种棋盘格,边缘在四方向都有响应,Prewitt 和 Sobel 会把角点附近重复计算,形成两条交叉亮线,Canny 则能得到单像素宽的网格。差别见下表:

算子边缘宽度抗噪能力连续性双阈值控制典型场景
Prewitt2~3像素一般棋盘、简单物体
Sobel2像素一般通用入门
Canny1像素纹理复杂、弱边缘

实际跑canny_edge.m时,如果把thLow提到 0.1,弱边缘大量减少,荷花花瓣上的细纹会被滤掉;如果把thHigh降到 0.08,噪声斑点会连成短线。两个阈值合理范围需要看梯度幅值分布,我一般先histogram(nms(:)),选梯度幅值直方图的 30% 和 70% 分位点作为低高阈值起点。

4. LoG 与 Susan 边缘检测:从二阶导到非梯度思路

Canny 是典型的基于一阶梯度的算法。这个包里另外两个文件log_edge.msusan.m走的是完全不同的路线:LoG 用二阶导数的零交叉,Susan 用邻域灰度相似性。理解它们之后,遇到低对比度图像就知道该选谁。

4.1 LoG 算子的原理与 log_edge.m 实现

LoG(Laplacian of Gaussian)先高斯平滑,再求拉普拉斯二阶导。理论上,灰度从亮到暗的过渡区域,梯度幅值会出现极大值,二阶导数在那个位置为零,所以“零交叉点”就是边缘。

Matlab 里可以直接用fspecial('log', hsize, sigma)生成 LoG 卷积核。我一般把核的尺寸设为ceil(sigma * 6),确保核边界的值衰减到接近 0。实现:

function edgeMap = log_edge(grayImg, sigma) hsize = ceil(sigma * 6); if mod(hsize, 2) == 0 hsize = hsize + 1; end logKernel = fspecial('log', hsize, sigma); logResp = imfilter(grayImg, logKernel, 'replicate'); edgeMap = zeros(size(logResp)); % 零交叉检测 for i = 2:size(logResp,1)-1 for j = 2:size(logResp,2)-1 patch = logResp(i-1:i+1, j-1:j+1); if max(patch(:)) > 0 && min(patch(:)) < 0 edgeMap(i,j) = 1; end end end end

fspecial('log', hsize, sigma)生成的核同时包含高斯平滑和拉普拉斯运算。imfilter默认会做边界填充,'replicate'是复制边缘像素,比默认的'zero'效果更稳。零交叉检测这里用了 3×3 邻域,只要正负极值同时出现就标记为边缘。更严谨的做法是检查相邻像素是否异号,但对于课程设计级别,3×3 扫描已经够用。

LoG 对 sigma 非常敏感。sigma=0.5 时,卷积核太尖锐,响应会出现大量细碎边缘;sigma=2.0 时,边缘连续性好,但对细线的定位偏差也大。diaohua.jpg这种雕刻纹路复杂、边缘密集的图,sigma 取 1.5 左右比较平衡。如果直接用edge(gray, 'log'),Matlab 会自动选 sigma 并内部处理阈值,但效果往往不如自己控制。

4.2 Susan 算子的原理与 susan_threshold.m 参数

Susan 算子的全称是 Smallest Univalue Segment Assimilating Nucleus。它用一个小圆模板遍历图像,比较模板内每个像素与中心像素的灰度差,统计与中心像素灰度相近的区域面积(USAN)。

边缘或角点处的 USAN 面积会小于平坦区域,所以 USAN 越小,该中心越可能是角点或边缘点。susan_threshold.m里的核心参数应该是灰度差阈值t,我习惯取 25~35(0~255 范围)。t越大,认为相似的像素越多,USAN 面积变大,检测出的边缘越少;t越小,敏感度越高,但噪声会被当成边缘。

下面是一个简化版 Susan 边缘响应计算:

function susanRes = susan_threshold(grayImg, t) grayImg = im2double(grayImg) * 255; % 回到0~255方便设阈值 [rows, cols] = size(grayImg); susanRes = zeros(rows, cols); % 半径为1的圆形掩膜,共9个像素(含中心) mask = [0 1 0; 1 1 1; 0 1 0]; for i = 2:rows-1 for j = 2:cols-1 patch = grayImg(i-1:i+1, j-1:j+1); center = grayImg(i,j); diff = abs(patch - center); % 灰度差 < t 的像素计数 usan = sum(mask(:) & (diff(:) < t)); % USAN面积越小,响应越强 susanRes(i,j) = 9 - usan; end end end

这个版本把 USAN 响应定义为“模板内不相似像素的个数”,数值越大越像边缘。实际工程里 Susan 还会用几何阈值g做二次判断,把低于g的响应置零,用来过滤伪响应。包里的susan.m应该负责主流程,susan_threshold.m负责阈值判断,我一般按“先算 USAN,再和 g 比较”的逻辑去读这两个文件。ex8.mex9.m的命名顺序也暗示作者把 LoG 和 Susan 的验证分开了,建议按ex6.mex8.mex9.m的顺序跑一遍。

4.3 三种算子在测试图上的横向对比

我用hehua.JPG做对比时发现一个明显现象:Canny 在花瓣边缘有连续闭环,LoG 在花瓣高光区域会出现双边缘,Susan 则把荷叶上的纹理也捡了进来。这说明没有绝对最优的算子,只有参数能不能匹配图像场景。下表是我对这组图的实际观感记录:

算子棋盘图荷花图雕花图参数敏感度
Canny网格清晰花瓣边缘连续纹理边缘偏碎高低阈值影响大
LoG边缘有重影高光处双边缘细纹响应好sigma影响最大
Susan棋盘角点过强纹理响应较多雕刻轮廓完整对t和g都敏感

从这组结果来看,如果目标是做后续的 Hough 直线检测,优先用 Canny;如果是想保留雕刻这类细致纹理的轮廓,LoG 配合中等 sigma 反而更合适;如果图像亮度和对比度不均,Susan 对光照变化更强健。

4.4 参数调整建议

我一般会把log_edgesusan_threshold的输入参数抽出来放到脚本顶部,避免反复编辑。例如:

sigmaLog = 1.5; tSusan = 27; gSusan = 8;

然后调用imshowpair并排显示结果,观察哪一个参数让边缘断得最少。注意 LoG 输出是二值图,Susan 的原始响应是连续灰度图,需要再设阈值或归一化才能和二值图直接对比。这块源码包里没有统一封装,我在实验里是单独写后处理函数处理的。

5. 用棋盘、荷花和雕花图验证边缘检测结果:参数微调与常见坑

5.1 棋盘和荷花:不同噪声下的调参侧重

qipan.JPG的高频边缘多,跑 Canny 先把 sigma 拉到 1.0 以内,避免网格交叉点被圆角化。hehua.JPG的低对比花瓣边缘需要降低低阈值到 0.03~0.05,同时记得用medfilt2预处理一下。diaohua.jpg的雕刻纹路适合 LoG,sigma 不要超过 2.0,否则相邻纹路会粘连。

5.2 结果保存与数据精度的坑

最后收尾时提一个常见坑:用imwrite(edgeMap, 'result.png')时,如果edgeMaplogical类型,Matlab 会直接写成二值图,没有问题;但如果是从canny_edge.m里拿到的double类型,值只有 0 和 1,直接imwrite会写成一个全黑或全白的图。这是因为imwritedouble数组按 0~1 范围处理,边界值 1 映射到 255,中间没有值就不会出现可见边缘。正确做法是先转成logicaluint8

imwrite(uint8(edgeMap) * 255, 'result_edge.png');

或者:

imwrite(logical(edgeMap), 'result_edge_binary.png');

uint8(edgeMap) * 255把 double 二值图映射到 0/255,再保存就不会出现全黑全白的问题。另一个验证手段是在 Matlab 里直接对比边缘像素占比:sum(edgeMap(:)) / numel(edgeMap),棋盘图通常在 5%~10%,荷花图可能在 2%~8%。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 8:02:27

MATLAB夫琅禾费衍射仿真:fft2实现、坐标映射与光栅缺级

简介&#xff1a;基于MATLAB的光学仿真资源&#xff0c;聚焦夫琅禾费衍射、双缝干涉、平面光栅衍射与单缝衍射四个经典波动光学主题&#xff0c;适用于本科、硕士阶段的课程设计、实验报告和课题入门。压缩包共12个文件&#xff0c;包含4个.m源代码、7张运行结果图&#xff08;…

作者头像 李华
网站建设 2026/9/16 8:02:08

STM32H750移植FATFS:寄存器库驱动SD卡完整攻略

简介&#xff1a;面向使用STM32H7系列单片机的嵌入式开发者&#xff0c;这份源码包以STM32H750为例&#xff0c;采用寄存器库直接驱动硬件的方案&#xff0c;实现了FATFS文件管理系统&#xff0c;适用于需要将运行数据、配置参数或日志记录到SD卡等存储介质的工业控制、数据采集…

作者头像 李华
网站建设 2026/9/16 8:01:33

STM32F411裸机开发:从复位向量到main()的链接脚本详解

1. 项目概述&#xff1a;为什么一份“从复位到 main()”的链接脚本&#xff0c;比你想象中更重要如果你正在用 GNU Arm Embedded Toolchain 编译 STM32F411 的裸机程序&#xff0c;却在烧录后板子毫无反应、串口没输出、调试器连不上、甚至 J-Link 报“Target not halted”&…

作者头像 李华
网站建设 2026/9/16 8:00:28

大模型公司云原生工程师:从K8s到GPU调度的实战指南

1. 大模型公司搞云原生&#xff0c;真不只是"运维容器"那么简单这两年大模型赛道火得一塌糊涂&#xff0c;但有个现象挺有意思——各家基模公司在疯狂招算法人才的同时,云原生研发工程师的岗位需求也在悄悄暴涨。我身边不少朋友看到"基础模型公司招聘云原生研发…

作者头像 李华
网站建设 2026/9/16 7:59:37

二进制信号量和计数信号量区别(FreeRTOS)

目录 前言 一. FreeRTOS信号量 1.1 核心函数 1.1.1 创建信号量 1.1.2 释放信号量 1.1.3 获取信号量 1.2 STM32Cubenx初始化 1.2.1 创建3个任务 1.2.2 创建1个二进制信号量 1.3 使用演示 1.3.1 (二进制)信号量示例 1.3.2 (计数)信号量示例 二. 使用二进制信号量示例 2.1 task任务…

作者头像 李华
网站建设 2026/9/16 7:58:41

治好了我的电子仓鼠症!偶然挖到一款贼好用的文件夹整理工具

治好了我的电子仓鼠症&#xff01;偶然挖到一款贼好用的文件夹整理工具&#xff08;Win/Mac&#xff09; 不知道大家有没有跟我一样的毛病&#xff1a;下载文件夹和桌面永远像个垃圾场。 平时写代码、下安装包、收各种工作群发来的 PDF、电子发票、截图、解压包……下载的时候…

作者头像 李华