基于Matlab主成分分析的图像压缩与重建实践指南
2026/9/16 15:35:50 网站建设 项目流程

简介:基于MATLAB主成分分析(PCA)的图像压缩与重建完整参考资源,面向计算机、电子信息工程、数学等专业学生,适用于课程设计、期末大作业、毕业设计以及数据降维与图像处理方向的自学实践。该方案利用PCA去除图像像素间的相关性,将高维图像信息浓缩到少数几个主成分特征图像中,在保留主要视觉信息的同时实现数据压缩;需要时又可基于不同数量的主成分重建出相应细节层次的图像,兼顾压缩率与重建质量。压缩包共含8个文件,以5个.m源码文件为核心,覆盖主程序、PCA核心算法与多种示例调用,配套2个txt说明文档用于讲解原理与使用要点,另有1张png图片作为测试样例,总大小仅126KB,运行方便。目前已有204人学习下载,源码与文档均清晰易读,读者可自行修改主成分个数并调试运行,观察不同参数下的压缩重建效果,是一款具实操价值的图像降维学习资料。

1. 基于Matlab主成分分析的图像压缩和重建:适合谁、解决什么

Lena 图、特征脸、PCA 降维,这三个词经常一起出现,但真正用主成分分析做图像压缩时,"降维"背后的东西要复杂得多。PCA 图像压缩的起点不是灰度直方图,而是把图像切成小块、把每个块拉成一个样本向量,再在样本上算协方差矩阵、取特征值最大的几个方向当基。基是从图像自己学出来的,不像 JPEG 用写死的 DCT 变换,因此对纹理规律明显、内容重复度高的图像压缩质量反而好;对自然照片会明显弱于 JPEG。做这个题目的人大多来自三类场景:数字图像处理课设、毕业设计,以及头歌这类在线练习平台上的同款任务。阅读下面的内容只需要 Matlab 基础语法和矩阵操作的底子,代码可以直接抄着跑,参数和坑会一步步讲清。

2. 主成分分析原理:块样本、协方差矩阵与特征分解

2.1 为什么把图像切成块而不是整张图做主成分分析

整张 512×512 的图拉直是一个 262144 维向量,只有这一个样本,协方差矩阵根本估计不出来。所以 PCA 图像压缩的标准做法是分块:把图像切成 blockSize×blockSize 的小块,每块拉成 blockDim = blockSize² 维的行向量,整张图得到 N 个样本,组成矩阵 X(N×blockDim)。协方差矩阵这时才有统计意义,它刻画的是不同图像块之间像素值的共同变化模式。

块大小同时决定样本数和基的表达能力。块太大,比如 32×32,样本数缩到几百,协方差估计波动大,重建会出现块状感;块太小,比如 2×2,主成分维度太低,压缩比天花板跟着变低。8×8 是最常见的折中,和 JPEG 的分块尺寸一致。分块去相关的思路在 JPEG 里体现为 DCT,在 PCA 里体现为自适应的特征向量基,这是两者最大的分水岭。

2.2 协方差矩阵、特征值与累计方差贡献率

对去均值后的 Xc = X − μ,其中 μ 是 X 每列的均值,协方差矩阵 C = Xc'·Xc / (N−1) 是 blockDim 阶实对称矩阵。特征分解 C·v = λ·v 得到的特征值 λ 表示该特征向量方向上的样本方差:方差越大,这个方向越能解释图像块之间的差异。把所有特征值降序排列,前 k 个对应的特征向量就构成保留的主成分基 Vk。

这里有一个和普通机器学习流程不同的点:图像像素都在同一个量纲里(0~255 或 0~1),所以不需要像其它 PCA 应用那样先做 zscore 标准化。强行把每列标准化成单位方差,等于把暗部噪声和亮部纹理放到同等权重,重建出的图像信噪比反而下降。

累计方差贡献率是定 k 的关键指标:

累计贡献率 = (λ₁+λ₂+…+λk) / (λ₁+…+λblockDim)

在 Matlab 里可以这样验证:

Xc = X - mean(X, 1); C = (Xc' * Xc) / (size(X, 1) - 1); [V, D] = eig(C); [lambda, idx] = sort(diag(D), 'descend'); V = V(:, idx); cumContribution = cumsum(lambda) / sum(lambda); kRef = find(cumContribution >= 0.95, 1, 'first');

代码说明:eig 返回的对角阵 D 不保证特征值降序,必须 sort 后把 V 的列顺序同步重排,否则取前 k 列取到的不是主成分方向。cumsum 得到累计贡献率曲线,一般前一小部分特征值就能吃掉 95% 以上的能量,kRef 就是该图的参考主成分数量。

2.3 投影与重建的数学关系,以及和 SVD 的等价性

主成分向量两两正交,满足 Vk'·Vk = I,所以压缩和重建是一对可逆的线性变换:

Y = Xc·Vk(N×k,压缩后的得分) X̂ = Y·Vk' + μ(恢复块矩阵,μ 是长度为 blockDim 的均值向量)

重建误差完全来自被丢弃的 blockDim−k 个方向上的成分,这正是 PCA 有损压缩的本质:保留方差大的方向,丢弃方差小的方向。μ 必须随压缩数据一起保存,它不是一个标量,而是每个像素位置在所有块上的均值。

如果对 Xc 做奇异值分解 Xc = U·S·W',右奇异向量 W 就是协方差矩阵 C 的特征向量,奇异值的平方正比于特征值。Matlab 的 pca 函数内部走的就是这个 SVD 路径,数值上比"先算 C 再 eig"更可靠,特别是 blockDim 较大或者图像块数量接近 blockDim 时。如果你后面接触 Matlab 的深度学习工具箱,PCA 也可以理解成一个没有非线性激活的单层自编码器,最优权重就是 Vk,线性重建的思路和多层重建网络一脉相承。

3. Matlab实现主成分分析图像压缩的完整流程与源码

3.1 图像读取、灰度化、归一化与边界处理

动手写 PCA 之前先处理三个前置问题。第一,imread 读进来是 uint8,uint8 相减小于 0 会截断成 0,所有去均值操作必须在 double 下做,用 im2double 归一到 [0,1] 最省心。第二,彩色图有 R/G/B 三个通道,分别做 PCA 的话存储翻三倍,课程实验普遍先用 rgb2gray 转灰度。第三,行列不一定是 blockSize 的整数倍,直接 reshape 会错位,要先裁剪到能整除。

img = imread('lena.bmp'); if size(img, 3) == 3 img = rgb2gray(img); end img = im2double(img); % 归一化到 [0,1] blockSize = 8; [r, c] = size(img); rows = r - mod(r, blockSize); % 裁剪边界 cols = c - mod(c, blockSize); img = img(1:rows, 1:cols);

参数说明:im2double 把像素映射到 [0,1],后面 MSE/PSNR 公式里最大亮度取 1;如果保留 0~255 的量纲,PSNR 公式里的最大值要换成 255²,两种写法都能用,但不能混用。

3.2 构造块样本矩阵 X:循环与 im2col 两种写法

循环写法逐个块拉直,直观、不依赖图像处理工具箱:

numBlocksR = rows / blockSize; numBlocksC = cols / blockSize; N = numBlocksR * numBlocksC; X = zeros(N, blockSize^2); idx = 1; for i = 1:numBlocksR for j = 1:numBlocksC r0 = (i-1)*blockSize + 1; c0 = (j-1)*blockSize + 1; blk = img(r0:r0+blockSize-1, c0:c0+blockSize-1); X(idx, :) = blk(:)'; idx = idx + 1; end end

一行替代写法:

X = im2col(img, [blockSize blockSize], 'distinct')';

im2col 是图像处理工具箱的函数,distinct 表示不重叠分块,输出默认每块一列,转置后变成每块一行。如果选 sliding 会得到大量重叠块,样本数爆炸且信息冗余,图像压缩里不用它。循环写法适合没有工具箱的机器,im2col 写法适合后面要做参数扫描的场景。

3.3 两种求主成分的方式:eig 手写与 pca 函数

mu = mean(X, 1); Xc = X - mu; % 方式 A:协方差矩阵 + eig C = (Xc' * Xc) / (N - 1); [V, D] = eig(C); [~, idx] = sort(diag(D), 'descend'); V = V(:, idx); % 方式 B:pca 函数(Statistics Toolbox) [coeff, score, ~, ~, explained] = pca(X);

两种方式得到的主成分矩阵方向一致,区别在返回值组织方式。pca 的 coeff 每列是一个主成分方向,按特征值降序排列;score 是投影得分,每行对应一个块;explained 是各主成分解释的方差百分比,sum(explained(1:k)) 就是前 k 个的累计贡献率乘 100。pca 默认 Centered=true,内部已经去均值,重建时用 X̂ = score·coeff' + mu 补回均值,mu 可以直接用 mean(X,1) 算,也可以用 pca 的第六个返回值,两者等价,注意不要重复去均值。另外当样本数 N 小于 blockDim 时,pca 最多返回 min(N−1, blockDim) 个主成分,k 的取值要留这个余量。

3.4 压缩、存储、重建与 PSNR 一条龙

把上面的步骤合起来,写成两个函数:

function [Y, basis, mu, bs, rows, cols] = pcaImageCompress(img, bs, k) [r, c] = size(img); rows = r - mod(r, bs); cols = c - mod(c, bs); img = img(1:rows, 1:cols); X = im2col(img, [bs bs], 'distinct')'; % 每行一个块 mu = mean(X, 1); [basis, Y] = pca(X, 'NumComponents', k); % basis: blockDim×k, Y: N×k end function rec = pcaImageReconstruct(Y, basis, mu, bs, rows, cols) Xr = Y * basis' + mu; rec = col2im(Xr', [bs bs], [rows cols], 'distinct'); end img = im2double(rgb2gray(imread('lena.bmp'))); [Y, basis, mu, bs, rows, cols] = pcaImageCompress(img, 8, 16); rec = pcaImageReconstruct(Y, basis, mu, bs, rows, cols); mse = mean((img(1:rows,1:cols) - rec).^2, 'all'); psnr = 10 * log10(1 / mse); fprintf('PSNR = %.2f dB\n', psnr); imshowpair(img(1:rows,1:cols), rec, 'montage');

代码逻辑:pcaImageCompress 里 im2col 拿到全部块样本,pca 的 NumComponents 参数直接指定保留 k 个主成分,输出的 score 就是压缩产物 Y;basis 是基矩阵,和 mu 一起保存。pcaImageReconstruct 先用 Y·basis' 还原块向量矩阵,再加回均值,最后 col2im 把块拼回图像。col2im 的第三个参数必须是裁剪后的 [rows cols],尺寸错了会直接报错。PSNR 这里因为做过了 im2double,分母取 1。mean 的 'all' 选项需要 R2018b 之后的版本,旧版写成 mean(mse(:)) 即可。

提示:score 是 double 类型,N×k 个浮点数直接存盘往往比原图更大。课程实验统计压缩比时常用"元素个数比",也就是只数 Y、basis、mu 的元素总数,但真正要落地成压缩文件,还要对 score 做量化和熵编码,这一步属于后处理,很多说明文档把它略过了。

压缩函数里几个参数的常用取值和含义:

参数/函数常用取值说明
blockSize4 / 8 / 16分块边长,8 是默认推荐
k(NumComponents)1 ~ blockSize²保留主成分个数,k 越大质量越高、压缩比越低
Centeredtrue(默认)pca 是否去均值;设为 false 时第一个主成分会偏向全局均值
im2col 分块方式'distinct'不重叠分块,压缩场景固定用这个
score / coeff 类型double存储开销见上面的提示

4. 主成分分析压缩比与重建质量的权衡:块大小和 k 值怎么配

4.1 压缩比公式与模型开销

压缩数据由三块构成:Y(N×k)、basis(blockDim×k)、mu(blockDim×1)。原始数据是 N×blockDim。理论压缩比:

CR = N·blockDim / (N·k + blockDim·k + blockDim)

图像块数 N 远大于 blockDim 时,第二三项可以忽略,CR ≈ blockDim / k,也就是 blockSize² / k。8×8 块配 k=8,压缩比约 8;k=2 时约 32。但注意 basis 和 mu 是固定模型开销,图越小占比越高。512×512、8×8 块时 N=4096,k=8,分子 262144,分母 4096×8 + 64×8 + 64 = 33344,实际 CR 约 7.9;换 128×128 的小图,同样的 k,CR 会掉到 7 以下。小图省 k 比改块大小更划算。

另外,以上 CR 都是按元素个数算的。score 如果保持 double,每个元素占 8 字节,一档 8×8、k=8 的压缩结果,实际字节数反而比原图大;要做成真正的压缩文件,得把 score 量化到 8bit 或 12bit 再做熵编码,量化步长和重建 PSNR 的关系就是另一组调参了。

4.2 参数扫描:块大小、k 与 PSNR 的关系

以下是一组典型观察值(256×256 灰度 Lena,未量化,CR 按元素个数算):

blockSizekCRPSNR(dB)观察
428.024.1块太小,基表达纹理能力弱
482.031.8质量高但没有压缩意义
888.029.6平衡点,做课设从这里起步
8164.033.2质量档,CR 减半
16832.022.4CR 很高但失真明显
16328.030.5样本少,重建有块状感

换一张测试图绝对值会变,但趋势是确定的:k 翻倍 CR 近似减半,PSNR 大约上升 2~4dB;块从 8 增到 16,CR 抬升约 4 倍,PSNR 会掉 5dB 以上。主观上看,k 偏小时最先出现的是水平和竖直的方块边缘,因为块间均值差异没有被完全建模,PSNR 还维持在 25dB 以上但视觉已不可接受的情况在 PCA 压缩里很常见。另外 16×16 块只有 256 个样本,pca 最多输出 min(N−1, blockDim) 个主成分,k 取 256 会直接报错,扫描脚本里要把块数限制写进约束。

4.3 自动定 k:用累计贡献率代替手工试错

X = im2col(im2double(rgb2gray(imread('lena.bmp'))), [8 8], 'distinct')'; [coeff, score, ~, ~, explained] = pca(X); for target = [0.85, 0.90, 0.95, 0.99] k = find(cumsum(explained) >= target*100, 1, 'first'); rec = score(:,1:k) * coeff(:,1:k)' + mean(X, 1); rec = col2im(rec', [8 8], [256 256], 'distinct'); fprintf('target=%.2f, k=%d, CR=%.1f\n', target, k, 64/k); end

说明:explained 本身是百分比数值,所以和 target*100 比较。四个阈值对应四档参数,用途区分明确:存档选 0.95,做中间结果选 0.99,追求高压缩比可以试 0.85 以下。如果 find 返回空,说明该阈值超过这批数据能表达的范围,跳过或降低阈值。想批量跑多张测试图时,把这段包成函数再放进 for 循环,每张图重算 rows/cols 并把结果写进日志。现在用 codex 这类 AI 编程助手辅助操作 Matlab 批量任务很常见,脚本结构写得越规整,助手越不容易改错矩阵维度;但视觉质量它判断不了,每档参数都要保留 imshowpair 的人工抽检。

4.4 说明文档里该记录什么

既然发布包叫"源码+图片+说明文档",文档质量很大程度决定这个包能不能被别人复现。我建议至少记录五块内容:运行环境,写明 Matlab 版本、是否安装 Statistics Toolbox 和 Image Processing Toolbox,新版本 Matlab 的 pca 行为基本一致,重点是工具箱别缺;函数清单与调用关系,让读者知道从哪个入口函数跑;参数定义表,blockSize、k、Centered 各自的含义和默认值;测试结果表,至少三张图,记录图名、分辨率、块大小、k、CR、PSNR 和主观评价,直接采用 4.2 那种格式;最后是复现步骤,从读图到出指标的三五条命令即可。一张结果表胜过三段描述。

5. 主成分分析重建质量验证:指标、Bug 与自动调参技巧

5.1 PSNR 之外必须补一个 SSIM

PSNR 只看逐像素均方误差,块效应造成的结构失真它反映不出来。k 调小、CR 超过 16 时,PSNR 可能还停留在 25dB 以上,但方块边界已经肉眼可见,这时要看 SSIM。Matlab 里用ssim(rec, ref)一行拿到结果,范围 [-1,1],0.95 以上人眼几乎不可辨。判断重建质量的完整做法是 PSNR 和 SSIM 一起报,图像超分辨率重建任务里评估模型也是同一套指标,这套验证脚本可以留着复用。

ssimVal = ssim(rec, img(1:rows,1:cols)); % 需要 Image Processing Toolbox

5.2 最容易犯的三个 Bug

uint8 相减截断排第一:没转 double 就做 X − mean(X,1),负值全变成 0,重建图整体偏灰。第二种是 eig 之后不排序直接取前 k 列,特征值不是降序时取到的不是主要方向,重建图会出现斜向条纹,排查时先看 V 的第一列是否对应最大方差方向。第三种是 col2im 尺寸不匹配,报错提示 length(B) 必须等于 prod(blockSize)*prod(gridSize),回去核对 rows/cols 是不是裁剪后的尺寸。还有一个隐蔽点:pca 默认去均值,重建时又手工减一次 μ,等于双重去均值,整张图亮度偏移一个常数,SSIM 往往比 PSNR 更能暴露这类问题。

5.3 一个实用技巧:自动生成参数决策表

把 4.3 的扫描改写成通用函数,输入图片路径、备选块大小和贡献率阈值,输出结构体数组,每条记录包含块大小、k、CR、PSNR、SSIM,方便直接写进说明文档或导出 CSV:

function stats = pcaSweep(imgPath, blockSizes, targets) img = im2double(rgb2gray(imread(imgPath))); stats = struct(); for bs = blockSizes [r, c] = size(img); rows = r - mod(r, bs); cols = c - mod(c, bs); X = im2col(img(1:rows,1:cols), [bs bs], 'distinct')'; [coeff, score, ~, ~, explained] = pca(X); for t = targets k = find(cumsum(explained) >= t*100, 1, 'first'); rec = col2im(score(:,1:k)*coeff(:,1:k)' + mean(X,1), ... [bs bs], [rows cols], 'distinct'); stats(end+1).blockSize = bs; %#ok<SAGROW> stats(end).target = t; stats(end).k = k; stats(end).cr = rows*cols / (size(X,1)*k + bs^2*k + bs^2); stats(end).psnr = 10*log10(1/mean((img(1:rows,1:cols)-rec).^2, 'all')); stats(end).ssim = ssim(rec, img(1:rows,1:cols)); end end T = struct2table(stats); writetable(T, 'pca_sweep.csv'); end

stats(end+1) 是 Matlab 扩展结构体数组的标准写法,循环结束后用 struct2table 转表、writetable 存 CSV。跑完一组图,对比各行的 SSIM,找到质量明显下滑的拐点对应的 k 值,后续调参就以拐点为中心左右各取一个值做细扫,比全区间扫描省一半时间。块数少于等于 blockSize² 时 pca 会降秩告警,这组结果直接丢弃不用犹豫。

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

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

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

立即咨询