☰
MATLAB图像处理入门:从矩阵、边缘检测到滤波与形态学
2026/10/29 6:46:39 网站建设 项目流程

简介:《matlab图像处理基础实例》面向数字图像处理初学者、课程实验学生及需要快速查阅 MATLAB 基础用法的自学者,围绕图像读取与显示、灰度转换、裁剪与子图展示、区域选择与掩膜、滤波增强、FFT 与 DCT、Radon 变换和减采样等入门操作展开。包内共 1 个 PDF 文件,大小约 5.94MB,内容以函数用法、命令片段与处理效果说明相结合的方式编排,便于按知识点检索和复现。已有 2003 人学习,适合作为课堂练习和自学练手的参考材料。内容涉及 imread、imshow、rgb2gray、edge、imcrop、roipoly、roifilt2、fft2、dct2、radon 等常用函数,也包含均值滤波、中值滤波、维纳滤波、图像信息查看与区域填充等示例,读者可借此梳理典型处理流程、对照实验结果并积累基础调试经验。整体篇幅紧凑,适合按章节顺序阅读,也方便在实验课中查找对应函数和处理思路;对刚接触图像处理、需要建立基本操作框架的读者较为友好。

1. 先别急着敲代码:MATLAB 图像处理到底在干什么

很多新手第一次听到 MATLAB 图像处理,脑子里浮现的是手机 App 里那种一键加滤镜的操作,于是兴冲冲打开软件,看到满屏命令行和光标就懵了。其实它做的事非常朴素:把一张图变成一堆数字,用几条命令改这些数字,再把数字变回图给你看。你手机里那张自拍,在 MATLAB 眼里就是一张几百行几百列的表格,每个格子记着一个像素的亮度。所谓边缘检测、滤波、锐化,本质都是在这张表格上做加减乘除。

这件事适合谁?适合刚学完矩阵运算、想找个地方立刻看见结果的人,也适合做课程设计、要快速验证一个想法的同学。你不需要先把算法啃透,第一天的目标只有一个:让一张图在屏幕上按你的意思变个样子。这份实例文档的价值也就在这里,它把读图、转灰度、边缘、裁剪、滤波、变换这些常用动作一个个拆开给你看,你照着敲一遍,就能摸到图像处理的门把手。

2. 动手前的三件小事:认识图像在 MATLAB 里长什么样

2.1 一张图在 MATLAB 眼里就是一个数字表格

想象你用 Excel 打开一张 300×200 的小照片,每个格子填一个 0 到 255 之间的整数,填满六万个格子,这张表就是图片在内存里的样子。MATLAB 最擅长的就是处理这种表格,它管这个叫矩阵。灰度图是一个二维矩阵,每个格子是一个像素的亮度,0 是纯黑,255 是纯白,中间的数字就是深浅不同的灰。彩色图稍微复杂一点,它由三个同样大小的二维矩阵叠在一起组成,分别记录红、绿、蓝三个通道的强度,MATLAB 里把这种叠起来的结构叫三维数组。

你可以用 whos 命令随时查看一个变量到底是几维、多大、什么类型:

a = imread('onion.png'); % 读一张 MATLAB 自带的示例图 whos a % 查看 a 的尺寸、维度和数据类型

输出里会出现类似 384x512x3 uint8 这样的信息,意思是高 384、宽 512、三个通道、每个像素用 8 位无符号整数存。看懂这一行,后面很多报错你就知道从哪查了——比如函数要求灰度图,你传了个三通道彩色图进去,它会直接告诉你维度不对。养成每读一张新图就先 whos 一下的习惯,能省掉大量来回猜的时间。

这里有个新手绕不开的概念:uint8 和 double。MATLAB 读进来的图默认是 uint8,取值范围 0 到 255,只能存整数。但很多数学运算,比如傅里叶变换、滤波、除法,需要小数,所以要先转成 double。麻烦在于,double 类型的图在 imshow 眼里默认只认 0 到 1 这个范围,你把 0 到 255 的 double 图直接丢进去,它会把大于 1 的全当白色,屏幕上一片惨白。所以要么先除以 255 归一化,要么显示的时候写 imshow(i, []),让 MATLAB 按数据的最小最大值自动拉伸。

提示:遇到图显示不出来、全白或者全黑,九成是数据类型和显示范围没对上,先查 whos 输出的类型,再决定要不要归一化。

除了类型,你还要建立"坐标"的概念。矩阵的索引是先行后列,a(3, 5) 指的是第 3 行第 5 列那个像素;而很多图像函数接受的是 (x, y) 形式的坐标,x 是列、y 是行。这两种写法在 MATLAB 里同时存在,imcrop 用后者,直接索引用前者。第一次接触会觉得别扭,记住一句话就行:矩阵索引先数行,图像坐标先数列。

2.2 读图、看图、问图:imread / imshow / imfinfo 三件套

这三个函数是后面所有例子的地基,先把它们用熟再说别的。

f = imread('onion.png'); % 把图片文件读进内存,返回一个矩阵 imshow(f); % 在图形窗口里把这个矩阵显示出来 info = imfinfo('onion.png'); % 读取文件头里的元信息,不解码像素 disp(info.Width); % 图像宽度,单位是像素 disp(info.Height); % 图像高度,单位是像素 disp(info.ColorType); % 颜色类型,比如 truecolor 或 grayscale

imread 的参数就是文件路径字符串,返回值是矩阵。文件不在当前文件夹时,要么写全路径,要么先把当前文件夹切过去。imshow 的第二个可选参数是显示范围,写成 imshow(f, []) 表示按实际数据范围自动映射到 0 到 255 显示。imfinfo 返回一个结构体,里面装着宽、高、位深、颜色类型、压缩方式这些档案信息,处理批量图片时先用它筛一遍尺寸,不合适的小图直接跳过,能省很多事。

显示多张图的时候,subplot 是绕不开的。subplot(121) 的意思是"把绘图区切成 1 行 2 列,现在激活第 1 个格子",后面紧跟的 imshow 就画在这个格子里。subplot(121) 和 subplot(1,2,1) 完全等价,逗号可写可不写,但写成三个参数更不容易看错。figure 则是新开一个窗口,如果你不写 figure 也不写 subplot,所有图会一层层盖在同一个窗口里,最后你只能看到最上面那张,还以为是代码写坏了。

a = imread('onion.png'); subplot(121); imshow(a); title('原图'); subplot(122); imshow(a(:, :, 1)); title('只取红色通道');

第二行里的 a(:, :, 1) 是 MATLAB 的索引写法,冒号表示这一维全要,最后的 1 表示取第三个维度的第 1 层,也就是红色通道。单独看一个通道会得到一张灰度图,这是理解彩色图结构最直观的方式,建议你自己动手把 1 换成 2 和 3 各看一遍,感受一下三个通道亮度的差异。

2.3 灰度图是怎么来的:rgb2gray 为什么几乎是每个例子的第一步

后面你要学的边缘检测、形态学、阈值分割,绝大多数函数只接受二维灰度图。所以你会在几乎每一个示例里看到 rgb2gray 的身影,它出场频率高到像开机自检。

a = imread('onion.png'); i = rgb2gray(a); % 三通道彩色图转单通道灰度图 subplot(121); imshow(a); title('彩色原图'); subplot(122); imshow(i); title('灰度图');

rgb2gray 不是简单地把三个通道取平均,它按人眼对三种颜色的敏感程度加权:0.299 倍红加 0.587 倍绿加 0.114 倍蓝。绿色权重最高,因为人眼对绿光最敏感,这样算出来的灰度更接近你肉眼感受到的明暗。如果你自己写 (r+g+b)/3,图会显得偏亮、对比偏平,不是不能用,只是和专业工具算出来的结果有肉眼可见的差别。

那直接把彩色图丢给 edge 或者 im2bw 会怎样?有些版本会报维度错误,有些版本会自动帮你转,但内部按什么规则转的你看不见,结果不好解释,这就成了黑匣子。血泪经验是:凡是输入要求灰度图的函数,前面都显式写一行 rgb2gray,多打几个字,省下半小时排查时间。调试的时候你会感谢这行代码,因为它让你清楚地知道每一步的输入长什么样。

注意:不是所有图都要转灰度。做彩色分割、颜色识别、彩色向量梯度这类任务时,恰恰要在 RGB 或 HSI 空间里操作,别养成见图就转灰的惯性。

最后补一个和类型相关的操作:im2double。它是把 uint8 的 0 到 255 线性映射到 double 的 0 到 1,而不是简单改个类型名。这两种写法差别巨大:double(i) 只是换类型,数值还是 0 到 255,后面算 log、算除法很容易溢出或者全白;im2double(i) 换类型的同时把范围缩到 0 到 1,后面所有数学运算都在舒适区里。原料里那些做 FFT、做 DCT、做对数的例子,前面都隐含着这个转换,你自己写的时候别漏掉。

3. 第一个能跑通的例子:从读一张图到画出它的边缘

3.1 把环境准备好:当前文件夹与图片文件

新手第一个卡点往往不是代码,是图片放哪。MATLAB 的当前文件夹就是它默认找文件的地方,在命令行敲 pwd 就能看到现在站在哪个目录。最省事的做法是新建一个文件夹,把脚本和要处理的图片都扔进去,再把该文件夹设为当前文件夹,之后 imread('xxx.png') 就能直接找到。

原料里出现的 onion.png、football.jpg、kids.tif、trees.tif 这些,都是工具箱自带的示例图。找它们的路径可以用 which 命令:

which onion.png % 打印这张示例图的完整路径 fullfile(matlabroot, 'toolbox', 'images', 'imdata') % 示例图通常所在的目录

第一行直接告诉你这张图在哪,第二行告诉你它们通常聚集在哪个目录。知道路径之后,你就可以用自己的图替换示例图,把文件名换成你的,脚本其余部分一行都不用改。这种"先用示例图跑通逻辑、再换成自己的图验证"的顺序,比一上来就拿自己的材料试错高效得多,因为示例图是官方配好的,尺寸、通道数、位深都规规矩矩,先把它们跑顺能排除掉一大半环境问题。

提示:文件名大小写和扩展名必须完全一致,在一种系统上不敏感,换到另一种系统就可能翻车,从一开始就养成写全小写、写全后缀的习惯。

3.2 五行代码跑出第一张边缘图

f = imread('lbxx.bmp'); % 读入图片 a = rgb2gray(f); % 转成灰度图,edge 只吃灰度图 [g, t] = edge(a, 'canny'); % 用 canny 算子做边缘检测 imshow(g); % 显示二值边缘图 disp(t); % 打印本次自动选出的阈值

edge 的第一个参数是灰度图,第二个参数是方法名,常见的可选值有 'sobel'、'prewitt'、'roberts'、'log'、'canny'。返回值 g 是一张二值图,边缘处是 1 显示为白,平坦区域是 0 显示为黑。第二个返回值 t 是这次检测用到的阈值,canny 会返回两个元素,一个低阈值一个高阈值,其他算子一般只返回一个。打印 t 能让你知道这次为什么检测出这么多或这么少的边,比盯着结果盲猜有用得多。

有一种情况会让新手困惑:明明图看起来是灰的,rgb2gray 却报错或者转完还是三通道。那是因为扫描件、截图、某些相机导出的"黑白照片"其实存成了三通道,只是三个通道数值几乎相同。判断方法很简单,size(a, 3) 看看第三维是不是 1,是 1 才是真灰度。这个检查我建议你写进每一个脚本的开头,两秒钟的事,能挡住后面一连串莫名其妙的报错。

3.3 把结果摆在一起看:subplot 与裁剪 imcrop

a = imread('onion.png'); b = imcrop(a, [75 68 130 112]); % 从原图里抠出一块 subplot(121); imshow(a); title('原图'); subplot(122); imshow(b); title('裁剪后');

imcrop 的第二个参数是一个四元向量,顺序是 [XMIN YMIN WIDTH HEIGHT],起点在左上角,X 是列方向也就是水平,Y 是行方向也就是垂直,所以前两个数是"从左边数第几列、从上边数第几行",后两个数是"宽多少、高多少"。这四个数搞反是最常见的翻车点:把 WIDTH 和 HEIGHT 写反,你会得到一个又扁又长的怪图;把 X 和 Y 写反,抠出来的位置就跑到别处去了。第一次用建议先直接输 imcrop(a),弹出窗口手动框选,框完在命令行看它返回的坐标,照着抄进脚本,比看图猜数字靠谱。

裁剪不只是为了看清楚,它还是减少计算量的手段。一张 4000×3000 的大图直接做 FFT 可能等半天,先裁到 512×512 把逻辑跑通,再放大到原图,这是很实用的工作习惯。同理,调试边缘检测参数时也别拿全图试,裁一块纹理最有代表性的区域,参数调好再放回去,速度快十倍。

3.4 换个算子试试:canny / sobel / prewitt 的差别

a = imread('kids.tif'); subplot(221); imshow(a); title('原图'); subplot(222); imshow(edge(a, 'canny')); title('canny'); subplot(223); imshow(edge(a, 'prewitt')); title('prewitt'); subplot(224); imshow(edge(a, 'sobel')); title('sobel');

同一张图,三种算子出来的结果差别很明显。sobel 和 prewitt 都基于一阶差分的卷积,思路是相邻像素差得越多越像边缘,实现简单、速度快,但边缘容易粗、容易断。canny 多做了几步:先高斯平滑去噪,再算梯度,再做非极大值抑制把粗边压成细线,最后用双阈值把强弱边缘连起来。所以 canny 出来的线更细、更连贯,代价是参数更多、跑得更慢一点。入门阶段先用 canny 拿一个"好看"的结果建立信心,再回头看另外两个,理解它们为什么会被淘汰在大多数实战场景之外。

原料里那行 [g, t] = edge(a, 'canny') 还可以手动给阈值,写成 edge(a, 'canny', [0.1 0.3])。两个数越低,检测出的边缘越多,噪点也越多;两个数越高,只留下最明显的边。这个参数没有标准答案,同一组数字换张图效果可能完全不一样,所以别指望抄一组参数走天下。真正靠谱的做法是:先看 disp(t) 打出来的自动阈值,再在它附近上下浮动试试,找到你要的颗粒度。

3.5 圈出你关心的那块:roipoly 与二值掩膜

a = imread('onion.png'); i = rgb2gray(a); c = [200 250 278 248 199 172]; % 多边形顶点的列坐标 r = [21 21 75 121 121 75]; % 多边形顶点的行坐标 b = roipoly(i, c, r); % 返回一张和 i 同尺寸的二值掩膜 subplot(121); imshow(a); title('原图'); subplot(122); imshow(b); title('掩膜');

roipoly 的作用是把"你圈出来的区域"变成一张只有 0 和 1 的图,圈内是 1、圈外是 0,这张图就叫掩膜。有了掩膜,后面就能做"只对这块区域滤波""只统计这块区域的直方图"这类操作。注意 c 和 r 分别是列坐标和行坐标,顺序和直觉里的 (x, y) 一致,但和矩阵索引的 (行, 列) 是反的,第一次写很容易搞混,建议画完掩膜立刻 imshow 看一眼,位置对不对一眼就知道。

不带参数直接写 b = roipoly(i) 也行,会弹出一个窗口让你用鼠标点,点一圈之后双击或者回车闭合,适合临时试一试。但写进脚本里时一定要用坐标参数,否则别人跑你的代码还得手动点一遍,完全没法复现。掩膜的用处远不止看看,它是后续区域滤波 roifilt2、区域填充 roifill 的输入,也是你自己写"只处理某一块"的代码时最干净的接口。

4. 常用积木拆开看:滤波、变换、形态学各管一段

4.1 滤波:把噪点和细节分开处理

a = imread('onion.png'); i = rgb2gray(a); k1 = filter2(fspecial('average', 3), i) / 255; % 3x3 均值模板 k2 = filter2(fspecial('average', 5), i) / 255; % 5x5 均值模板 k3 = filter2(fspecial('average', 7), i) / 255; % 7x7 均值模板 subplot(221); imshow(i); title('原图'); subplot(222); imshow(k1); title('3x3 均值'); subplot(223); imshow(k2); title('5x5 均值'); subplot(224); imshow(k3); title('7x7 均值');

fspecial('average', n) 生成一个 n×n 的均值模板,里面每个元素都是 1/n²。filter2 拿这个模板在图上逐像素滑动,每滑到一个位置就把模板盖住的邻域乘加一遍,结果写回中心像素。这就是最朴素的平滑:用周围像素的平均值代替自己,噪点被摊平了,边缘也一起被摊糊了。模板越大糊得越厉害,7×7 出来的图基本只剩色块轮廓。这个对比实验一定要自己做一遍,比看十段文字描述都管用。

末尾那个除以 255 不能省。filter2 的输出是 double,而原图是 uint8 的 0 到 255,不归一化的话 imshow 看到大于 1 的数全当白,你只会得到一张白纸。除以 255 把范围拉回 0 到 1,显示就正常了。另一种写法是 imshow(k1, []),让 MATLAB 自己拉伸,效果类似但不完全等价,前期建议老老实实除,理解自己在做什么。

中值滤波和维纳滤波各有各的适用场景:

i = rgb2gray(imread('onion.png')); j1 = medfilt2(i, [3 3]); % 中值滤波,擅长去椒盐噪声 j2 = wiener2(i, [5 5]); % 自适应维纳滤波,擅长去高斯噪声 subplot(131); imshow(i); title('原图'); subplot(132); imshow(j1); title('中值滤波'); subplot(133); imshow(j2); title('维纳滤波');

medfilt2 取邻域的中位数而不是平均值,所以对孤立的黑白点也就是椒盐噪声特别有效,而且比均值滤波更能保住边缘。wiener2 会根据局部方差自适应调整平滑强度,平坦区域多平滑,边缘区域少平滑,适合处理整体带颗粒感的图。选哪个不是看谁更高级,而是看你图里是什么噪声:孤点用中值,整体颗粒用维纳,先做一步诊断再动手,别上来就全套安排。

4.2 频域变换:FFT 与 DCT 在图像里到底看什么

f = zeros(100, 100); f(20:70, 40:60) = 1; % 手工造一个白色矩形 subplot(121); imshow(f); title('原图'); F = fft2(f, 256, 256); % 补零到 256x256 再做二维傅里叶变换 F2 = fftshift(F); % 把零频分量挪到中心 subplot(122); imshow(log(abs(F2) + 1), []); title('频谱'); colorbar;

fft2 的第二、第三个参数是补零后的行数和列数。补零不是加信息,而是让变换后的频率采样更密,频谱看起来更平滑,同时缓解循环卷积带来的边缘效应。fftshift 把低频从四个角挪到正中心,因为人更习惯"中间亮、四周暗"的频谱图。log(abs(...)+1) 是对幅度取对数,原因是频谱的动态范围太大,直接显示只有中心一个亮点,其他全黑,加 1 是为了避开 log 零。

离散余弦变换在图像里的地位比 FFT 还高,JPEG 压缩用的就是它。思路是:把图切成小块,每块用一组余弦波去拟合,能量会集中到左上角的低频系数上,右下角的高频系数很多都接近 0,把这些小系数直接扔掉再反变换,图几乎看不出差别,但数据量少了一大截。这就是有损压缩四个字的真实含义,不是玄学,是系数被丢了。

i = rgb2gray(imread('onion.png')); i = im2double(i); % 转 double 并归一化到 0~1 j = dct2(i); % 二维离散余弦变换 j(abs(j) < 0.01) = 0; % 小系数清零,模拟压缩 k = idct2(j); % 反变换还原 subplot(131); imshow(log(abs(j)), []); title('DCT 系数'); subplot(132); imshow(i); title('原图'); subplot(133); imshow(k, []); title('还原');

阈值 0.01 是我随手写的,你可以改成 0.001 或者 0.1,观察还原图什么时候开始明显变糊。这个实验做一遍,比背十遍概念都扎实。

4.3 形态学:腐蚀、膨胀与分水岭分割

a = imread('football.jpg'); t = graythresh(a); % 大津法自动算一个全局阈值 bw = im2bw(a, t); % 二值化,得到黑白图 se3 = strel('square', 3); % 3x3 方形结构元素 se5 = strel('square', 5); % 5x5 方形结构元素 e3 = imerode(bw, se3); % 腐蚀 d3 = imdilate(bw, se3); % 膨胀 subplot(131); imshow(bw); title('二值化'); subplot(132); imshow(e3); title('3x3 腐蚀'); subplot(133); imshow(d3); title('3x3 膨胀');

腐蚀的意思是:结构元素在图上滑动,只有结构元素覆盖的区域内全是白,中心点才保留白色,所以白块会缩小一圈,细小噪点直接消失。膨胀反过来,邻域里只要有一个白点,中心就变白,白块胀大一圈,断开的地方会被连起来。结构元素越大效果越猛,5×5 比 3×3 明显得多,原料里的对比图就是这个思路。

分水岭是形态学里偏进阶的用法,思路是把灰度图看成地形,亮度当海拔,往低处灌水,水洼之间形成分界线,那条线就是分割边界

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询