这一章在干嘛?
把前面学过的「矩阵就是数据」这个直觉推到极致 —— 任何图像都是矩阵,矩阵里的数就是颜色;任何声音都是向量,向量里的数就是振幅。学完你就能用 MATLAB 读写图片、做滤镜、把语音合成出来,再用一个会响应点击的小程序把它们打包起来。整章分四块:图像(13.1~13.3)、GUI(13.4~13.7)、声音(13.8)。
13.4 GUI 基础:figure 与 uicontrol
13.7 GUIDE 与 App Designer:可视化搭界面
13.1 图像就是矩阵
「图像就是矩阵」是这一章最重要的认知 —— 一旦接受这个前提,后面所有 image、imshow、imread、imwrite 都只是矩阵操作的一种新写法而已。这跟你在前面章节里学的「数据 = 矩阵」「信号 = 向量」是同一种世界观:把现实信号数字化成最基本的数组,让 MATLAB 把数组画出来 / 算出来。
像素(pixel,picture element)是图像的最小单元。把一张图按行按列拆开,每个像素就是一个数。整张图就是一个二维矩阵:
- 一张 的灰度图 = 一个 的矩阵;
- 一张 的彩色图(RGB,真彩色)= 一个 的三维矩阵;
- 一张 的索引图(indexed image)= 一个 的整数矩阵 + 一个 的「调色板」。
下面这张示意图把索引图的两层结构画得很清楚 —— 左边图像矩阵里每个格子是个「指向调色板的指针」,右边的调色板每一行存储一种真实颜色:

这张图想说明的是:图像矩阵和调色板矩阵是「两张表」,图像矩阵本身不存颜色,只存「去查表的下标」。这种设计在科学可视化里非常常见 —— 温度场、压力场、应力场只需要几十种「色阶」,用索引图比每个像素直接存 RGB 省 90% 内存。
为什么工程师喜欢用 MATLAB 处理图像?
因为它把像素处理降维成了矩阵运算 —— 你想给整张图加亮,只需
image = image + 30;想左右翻转,只需image(:, end:-1:1, :);想做卷积滤波,直接调用conv2。这是 OpenCV 或 C 写一行循环才能搞定的事。MATLAB 之所以能在图像/信号处理领域几十年长盛不衰,本质原因就是它把「像素」「采样点」「单元格」全都统一成了「矩阵元素」这一种数据结构。
常见坑:uint8 与 double 的取值范围
真彩色图像常常以
uint8类型存储,每个通道取值 0~255。但很多图像处理函数(卷积、归一化、灰度变换)默认要求double取值 0~1。把它们混用就会出现「加出来全是白色」或「plot 出来一条直线」之类的诡异现象。解决套路:处理前先im2double转成 0~1 的 double,处理完要保存再im2uint8转回去。后面 13.3 节还会用阳台雪景图演示这条 pipeline。
13.2 颜色的两种编码方式:索引图与 RGB
颜色编码是这一节的硬核。理解清楚「索引图」和「RGB 图」的区别,等于掌握了 90% 的图像处理编程模型。
13.2.1 索引图(colormap 方式)
索引图的思路是「调色板复用」:颜色信息不存到每个像素里,而是放在一张叫 colormap 的 矩阵里( 是颜色总数)。图像矩阵的每个元素是一个整数下标(1~),指出「这个像素的颜色在调色板的第几行」。
MATLAB 自带多个命名调色板:默认 parula(64 色)、jet(蓝→青→绿→黄→红,64 色)、pink、hot、gray、hsv 等等。colormap 不带参数时返回当前调色板,带参数时切换。
>> map = colormap; % 把当前调色板存成 64x3 的 double 矩阵
>> [r, c] = size(map)
r = 64
c = 3
>> map(60:64, :) % 看最后 5 行:黄到亮黄的渐变
ans =
0.9626 0.8705 0.1309
0.9589 0.8949 0.1132
0.9598 0.9218 0.0948
0.9661 0.9514 0.0755
0.9763 0.9831 0.0538注意 colormap 里的颜色分量都是 double,范围 01(不是 0255)。这是约定 —— 索引图和 RGB 矩阵类型约定不同:索引图 + colormap 走 double(01),而 RGB 矩阵走 uint8(0255)。
最直观的演示是把 1~64 排成一个 矩阵喂给 image,就能看到整个 parula 调色板:
>> imagemat = reshape(1:64, 8, 8);
>> image(imagemat)
切到 jet 调色板会看到完全不同的彩虹渐变(蓝→青→绿→黄→红):
>> colormap(jet) % 把当前调色板换成 jet
>> image(imagemat) % 同一矩阵,色彩立即改变
对比上面两张图:调色板不一样,同一个整数矩阵显示出来天差地别。这说明图像矩阵本身不含颜色,颜色是由矩阵 + colormap 共同决定的。你要保存为图片发给同事,要么发带 colormap 的 .fig 文件,要么把它先转成 RGB 矩阵再 imwrite。
随手生成一张「伪图像」也很容易 —— 把 1~颜色数之间的随机整数塞进矩阵即可:
>> mat = randi(64, 5); % 5x5 矩阵,元素是 1~64 的随机整数
>> image(mat)
把网格放大到 看上去才像「真的图」:
>> mat = randi(64, 500);
>> image(mat)
你也可以自己造调色板。下面只用三种颜色(黑、白、红)做一张 40×40 的随机图:
>> mycolormap = [0 0 0; 1 1 1; 1 0 0]; % 黑、白、红
>> colormap(mycolormap)
>> mat = randi(3, 40); % 元素只能是 1/2/3
>> image(mat)
调色板的颜色分量不必是整数。下面用 6 级深红展示 colormap 是怎么「渐变」的:
>> colors = [0 0 0;
0.2 0 0;
0.4 0 0;
0.6 0 0;
0.8 0 0;
1 0 0];
>> colormap(colors)
>> vec = 1:length(colors);
>> image(vec)
调色板不只服务于 image,任何把数值映射到颜色的 plot 函数(surf、mesh、imagesc)都能用 colormap 切换:
>> [x, y, z] = sphere(20); % 单位球坐标
>> colormap(pink)
>> surf(x, y, z)
>> title('Pink sphere')
>> colorbar
R2014b 之后还可以让一个 Figure 里不同子图用不同调色板 —— 把 axes 句柄传给 colormap:
[x, y, z] = sphere(20);
ax1 = subplot(1, 2, 1);
colormap(ax1, pink); % 左子图:粉色调
surf(x, y, z); title('Pink sphere');
ax2 = subplot(1, 2, 2);
colormap(ax2, jet); % 右子图:jet 彩虹
surf(x, y, z); title('Jet sphere');

13.2.2 真彩色(RGB / truecolor)
真彩色绕开了调色板,直接把每个像素的 R/G/B 三个分量都存在矩阵里:
- 三维矩阵的尺寸是 ;
(:,:,1)是红色层,(:,:,2)是绿色层,(:,:,3)是蓝色层;- 一个 8-bit RGB 图的元素是
uint8,每个通道取值 0~255(0 = 该通道无光,255 = 该通道最亮)。
下面创建一个 的「四色像素图」:(1,1) 红、(1,2) 蓝、(2,1) 绿、(2,2) 黑:
>> mat = zeros(2, 2, 3); % 预分配黑色
>> mat(1, 1, 1) = 255; % 左上:R=255 → 纯红
>> mat(1, 2, 3) = 255; % 右上:B=255 → 纯蓝
>> mat(2, 1, 2) = 255; % 左下:G=255 → 纯绿
>> mat = uint8(mat);
>> image(mat)
想单独看某条通道?把对应层抽出来做一张「只剩红/绿/蓝」的图:
matred = uint8(zeros(2,2,3)); matred(:,:,1) = mat(:,:,1);
matgreen = uint8(zeros(2,2,3)); matgreen(:,:,2) = mat(:,:,2);
matblue = uint8(zeros(2,2,3)); matblue(:,:,3) = mat(:,:,3);
subplot(2,2,1); image(mat); title('原图')
subplot(2,2,2); image(matred); title('R 通道')
subplot(2,2,3); image(matgreen); title('G 通道')
subplot(2,2,4); image(matblue); title('B 通道')
把三张通道子图相加(重叠的部分会变白)就回到原图。这说明 RGB 表示法的本质就是「三层灰度图叠加」。理解这点后,对图像处理的算子就有直觉了 —— 比如「加红滤镜」就是把 R 通道整体加大,「去蓝」就是把 B 通道置零。
下面是一个练习题:构造一个 的 RGB 矩阵(绿色背景 + 红色方块):
>> pic = zeros(3,3,3,'uint8');
>> pic(:,:,1) = 200; pic(:,:,2) = 220; pic(:,:,3) = 80; % 整张铺成黄绿色
>> pic(2,3,:) = [255 0 0]; % 中行右列塞个红块
>> image(pic)
常见坑:colorbar 上 0
1 与 0255 的混淆colormap 矩阵用 double(0
1);RGB 图像用 uint8(0255)。colorbar显示的是 colormap 的值(01),而1),要么在画 RGB 图前image显示的是矩阵元素。要保持一致,要么把所有数据归一化到 double(0caxis([0 255])。
13.2.3 灰度图与强度图
工程上第三种常见图像是灰度图 / 强度图(intensity image)。它的矩阵元素是 0~1 的 double,表示灰度(0 = 黑,1 = 白)。rgb2gray 把 RGB 转灰度,im2double 把 uint8 转 double。强度图常常搭配 imagesc(image scale,自动缩放到当前 colormap)来画,例如画一个灰度热图:
>> A = magic(10); % 一个 10x10 的「魔方矩阵」
>> imagesc(A); % 自动按当前 colormap 着色
>> colorbar;几个常见函数的混淆
image:纯粹按矩阵元素画,不缩放imagesc:自动缩放到 [min max] 再映射到当前 colormap,画「按比例填色」imshow:图像处理工具箱的图像显示函数,自动设置坐标轴比例(保持原图长宽比)
13.3 图像文件的读写与基本处理
真实工程里图片都是存成文件的(JPG、PNG、TIFF、BMP)。MATLAB 用 imread 读成矩阵、用 imwrite 把矩阵写成文件,imshow 是个比 image 更「图像友好」的显示函数(自动处理坐标轴、长宽比)。
>> porchimage = imread('snowyporch.JPG'); % 读入一张 JPG
>> size(porchimage) % 注意是 3 维的
ans =
2848 4272 3 % 高 2848 × 宽 4272 × 3 通道
>> class(porchimage)
ans =
uint8这张阳台雪景 4272 × 2848 ≈ 1200 万像素,是手机拍的照片。直接 image(porchimage) 显示:
>> image(porchimage)
想「整体压暗」,最直接的方式就是乘 0.5(uint8 矩阵乘以 0.5 会自动截断到 0~255):
>> dimmer = 0.5 * porchimage;
>> imwrite(dimmer, 'dimporch.JPG'); % 把处理后的图写回文件
工程实战:从代码到图像处理的 3 个动作
- 读:
I = imread('pic.jpg');- 改:所有你想得到的 numpy 矩阵操作都能用 ——
I(end:-1:1, :, :)上下翻转、I(:, 100:300, :)横向裁剪、rgb2gray(I)转灰度。- 写:
imwrite(I, 'out.png');或imshow(I);只看不存。没有「图像 API」这回事,就是矩阵。
下面这几道教材练习题涵盖了图像处理入门的高频操作,可以一一对照原图:
Practice 13.7(蓝绿渐变调色板)
「10 蓝 + 10 aqua + 10 绿」三段渐变,铺成 3×10 图像:
cmap = zeros(30, 3);
% 蓝色段:R=0,G=0,B 从 0.1 增到 1
cmap(1:10, 3) = linspace(0.1, 1, 10);
% aqua 段:R=0,G 与 B 都从 0.1 增到 1
cmap(11:20, 2:3) = repmat(linspace(0.1, 1, 10)', 1, 2);
% 绿色段:R=0,G 从 0.1 增到 1,B=0
cmap(21:30, 2) = linspace(0.1, 1, 10);
colormap(cmap);
img = [1:10; 11:20; 21:30]; % 3x10 矩阵,索引对应 cmap 各行
image(img);
Practice 13.6(黑白噪声 + 颜色反转)
bw = [0 1; 1 0]; % 黑白颜色:第 1 行黑,第 2 行白
colormap(bw);
mat = randi(2, 50, 50); % 50x50 随机选 0/1
subplot(1,2,1); image(mat); title('Yin');
subplot(1,2,2); image(1-mat); title('Yang'); % 颜色反转
Practice 13.4(RGB 色块)
50x50 的 RGB 图,绿/黄/红三色方块互相重叠:
rgb = zeros(50, 50, 3, 'uint8');
% 绿块在左上
rgb(10:33, 10:33, 2) = 255; rgb(10:33, 10:33, 1) = 0; rgb(10:33, 10:33, 3) = 0;
% 红块在右下
rgb(20:42, 20:42, 1) = 255; rgb(20:42, 20:42, 2) = 0; rgb(20:42, 20:42, 3) = 0;
% 黄块 = 红 + 绿,在重叠处
rgb(20:33, 20:33, 1) = 255; rgb(20:33, 20:33, 2) = 255;
image(rgb);
Practice 13.9(亮度感知错觉)
「同一灰度在不同背景下显得不一样亮」。构造两个 3×3 RGB 图,中块同色、外圈不同色:
% 左图:外圈深蓝,中块稍亮
left = zeros(3,3,3,'uint8'); left(:,:,3) = 200; left(2,2,:) = [120 120 200];
% 右图:外圈草绿,中块同色稍亮
right = zeros(3,3,3,'uint8'); right(:,:,2) = 180; right(2,2,:) = [120 200 120];
subplot(1,2,1); image(left);
subplot(1,2,2); image(right);

这两张图的中央像素颜色完全相同,但因为周围环境不同,肉眼会感觉左边中央更亮、右边更暗。这就是著名的「亮度恒常错觉」(brightness illusion),在图像处理做对比度增强时是绕不开的现象。
Exercise 26(随机游走纹理)
教材最后给了一个 ranwalk(n) 函数,模拟从中心出发的随机游走并把路径存成矩阵。运行两次( 与 )对比,能看到「确定性 + 随机性」是怎样长成两种完全不同的视觉形态的:
walk_small = ranwalk(8); % 8x8 矩阵,色块稀疏
walk_big = ranwalk(100); % 100x100 矩阵,纹理丰富
figure;
subplot(1,2,1); image(walk_small); axis square; title('ranwalk(8)');
subplot(1,2,2); image(walk_big); axis square; title('ranwalk(100)');

随机游走在物理上模拟的是森林火灾蔓延、枝晶生长、布朗运动等扩散现象。把模拟结果当「图」画出来,本质上还是个矩阵赋值 + image。
13.4 GUI 基础:figure 与 uicontrol
GUI(Graphical User Interface,图形用户界面)让程序不再只是「跑完结束」,而是「等待用户点击、滑块拖动、文字输入」之后再跑。MATLAB 里一切 GUI 都是 11 章讲过的「句柄对象」的延伸 —— 一个 Figure 是父,下面挂着若干 uicontrol 子对象。
13.4.1 第一个 GUI
下面这个例子只做一件事:在窗口中央显示一行静态文字。
% simpleGui.m
function simpleGui
% 创建一个只有静态文本框的极简 GUI
% Format: simpleGui 或 simpleGui()
% 1) 先建一个隐形窗口
f = figure('Visible', 'off', ...
'Color', 'white', ...
'Position', [300 400 450 250]); % [左 下 宽 高],单位像素
% 2) 在窗口里放一个静态文本框
htext = uicontrol('Style', 'text', ...
'Position', [200 50 100 25], ...
'String', 'My First GUI string');
% 3) 给窗口起名字,挪到屏幕中央,最后才显示
f.Name = 'Simple GUI';
movegui(f, 'center');
f.Visible = 'on'; % 关键:在所有对象都建好之后才 Visible
end