简介:基于方向离散余弦变换(DCT)与主成分分析(PCA)的图像融合Matlab代码,面向计算机、电子信息工程、数学等专业学生,可作为课程设计、期末大作业或毕业设计的核心参考。压缩包内共13个文件,以8个M源码文件为主体,涵盖DDCT正逆变换、PCA融合、最大/平均/能量等多种融合规则以及融合效果评估;另附3张测试图片、1个说明文件和1个辅助数据文件,整体仅159KB,轻量易用,可直接运行验证。代码采用参数化编程,注释明确,支持Matlab 2014/2019a/2024a,便于按需调整参数对比不同策略。资源已有48人学习,适合希望深入理解频域与统计特征结合思路,并快速产出可复现实验结果的读者。
1. 图像融合里DDCT和PCA为什么总被放在一起
红外与可见光融合、多聚焦图像合并、医学多模态影像叠加,这几类任务里你大概率见过同一个组合:方向离散余弦变换(DDCT)加主成分分析(PCA)。原因不是它们各自多强,而是它们恰好在两个不同维度上互补。DCT家族擅长把空间能量压到低频系数里,JPEG能压到那个程度就是证明;但标准二维DCT只有水平和垂直两个方向,遇到斜向纹理、边缘走向各异的图,系数扩散得厉害。DDCT把变换基按方向旋转了一组角度再做分块变换,相当于把“方向”显式写进了频域分解。PCA则在图像融合里干的是另一件事:统计去相关、按能量差异确定权重。它在像素域或低频系数域算一遍主成分,融合时自动偏向信息更丰富的一侧,不需要人工调权。两者叠加后,DDCT负责结构和细节的稀疏表达,PCA负责在系数合并时按主成分方向做自适应加权。对做课程设计、毕业设计或者拿融合算法做baseline对比的人来说,这套组合上手成本低,Matlab代码里改参数就能适配不同图源,这也是它常年在图像融合作业里出现的原因。
2. 从DDCT.m到IDDCT.m:方向变换与逆变换的实现边界
2.1 DDCT.m里变换核是怎么构造的
方向离散余弦变换不是一种固定的数学变换,它是一类“先旋转坐标,再做标准二维DCT”的方法统称。常见做法是对图像分块后,对每个图像块按预设的角度集合K = {k1, k2, ...}做坐标旋转,然后对旋转后矩阵执行二维DCT。逆过程就是把系数逆DCT后再反向旋转回来。DDCT.m里通常循环处理所有块,角度集合由外层参数控制。
function coefs = DDCT(img, blkSize, anglesDeg) % img: 输入灰度图, double型 % blkSize: 分块边长, 典型值8或16 % anglesDeg: 方向角度数组, 例如[0 15 30 45 60 75] [rows, cols] = size(img); padRows = ceil(rows / blkSize) * blkSize - rows; padCols = ceil(cols / blkSize) * blkSize - cols; imgPadded = padarray(img, [padRows padCols], 'replicate', 'post'); coefs = cell(1, numel(anglesDeg)); for ai = 1:numel(anglesDeg) theta = anglesDeg(ai) * pi / 180; R = [cos(theta) -sin(theta); sin(theta) cos(theta)]; blockCoefs = zeros(size(imgPadded)); % 按块处理, 块大小为 blkSize x blkSize for i = 1:blkSize:size(imgPadded, 1) for j = 1:blkSize:size(imgPadded, 2) block = imgPadded(i:i+blkSize-1, j:j+blkSize-1); % 坐标旋转换到局部方向坐标系 rotatedBlock = imrotate(block, anglesDeg(ai), 'bilinear', 'crop'); % 标准二维DCT: 每个方向得到一组系数 blockCoefs(i:i+blkSize-1, j:j+blkSize-1) = dct2(rotatedBlock); end end coefs{ai} = blockCoefs; end end这段代码里anglesDeg直接决定了变换对方向纹理的敏感度。角度跨度小、步长大,适合边缘走向相对单一的自然图像;跨度覆盖到更多方向,对复杂纹理更友好,但计算量按角度数线性增加。imrotate用'bilinear'采样是为了在旋转时不引入过多高频伪影。值得注意的一个点是,DDCT里的方向角度数组通常不是对称分布的——正负角度覆盖的区域在融合阶段会自然对称,所以只需要列单侧角度。
2.2 IDDCT.m的重构约束
逆变换不是直接把dct2换成idct2就完事。因为旋转操作本身是有损插值,逆DDCT要做到尽量可逆,必须保证旋转角度和插值方式与正变换完全一致,同时分块边界要做裁剪而非填充。
function recImg = IDDCT(coefs, blkSize, anglesDeg, originalSize) % coefs: DDCT.m 输出的 cell 数组, 每个元素对应一个角度 % 由于分成多个方向的系数集合, 逆变换通常是把各方向系数还原后取平均 [rows, cols] = size(coefs{1}); acc = zeros(rows, cols); for ai = 1:numel(anglesDeg) theta = anglesDeg(ai) * pi / 180; blockRec = zeros(rows, cols); for i = 1:blkSize:rows for j = 1:blkSize:cols blockCoef = coefs{ai}(i:i+blkSize-1, j:j+blkSize-1); blockRec(i:i+blkSize-1, j:j+blkSize-1) = idct2(blockCoef); end end % 先还原旋转 backRotated = imrotate(blockRec, -anglesDeg(ai), 'bilinear', 'crop'); acc = acc + backRotated; end recImg = acc / numel(anglesDeg); % 裁剪回原始尺寸 recImg = recImg(1:originalSize(1), 1:originalSize(2)); end这里对不同方向的逆结果取平均是一种折中:每个方向系数单独重建的图像都有旋转插值误差,平均操作会把误差分散掉,代价是高频细节会被轻微平滑。如果原始图像有非常尖锐的边缘,用'crop'模式旋转至少在边界上不会引入浑圆过渡。DDCT不具备标准DCT那种正交完备性加完美重构的性质,做融合实验前,建议先用同一张图过一遍DDCT -> IDDCT的往返误差,PSNR低于某个阈值就说明角度步长太密、插值损失过大,需要放大角度间隔。
2.3 DDCTIF_demo.m把流程串起来
demo脚本本质上是把读图、DDCT分解、规则融合、IDDCT重建四个环节串成一条流水线。阅读时最值得关注的不是I/O代码,而是两个变量:源图像是否做了灰度/亮度对齐、融合规则函数返回的是系数选中的索引矩阵还是直接返回融合系数。
clear; clc; im1 = im2double(imread('saras92.jpg')); % 源图像A im2 = im2double(imread('saras91.jpg')); % 源图像B if size(im1, 3) == 3, im1 = rgb2gray(im1); end if size(im2, 3) == 3, im2 = rgb2gray(im2); end blkSize = 8; angles = [0 30 60 90 120 150]; coefs1 = DDCT(im1, blkSize, angles); coefs2 = DDCT(im2, blkSize, angles); % 融合规则: 每对系数块按局部能量取优 fusedCoefs = cell(size(coefs1)); for ai = 1:numel(angles) fusedCoefs{ai} = fuse_pca(coefs1{ai}, coefs2{ai}); end recImg = IDDCT(fusedCoefs, blkSize, angles, size(im1)); imshow(recImg);blkSize设置为8是默认稳妥选项,块太大时方向纹理被平均化,块太小时方向细节碎片化。demo里angles用的是30度均匀间隔,如果你处理的图像里边缘方向优势明显,比如建筑物、道路,压缩成[0 45 90]也可以;而自然纹理复杂如树叶、岩石,均匀6方向更好。融合结果看起来有雾感,通常是fuse_pca输入的两组系数能量没有被归一化,两个源图的亮度差异直接带进了融合系数。
3. DDCTIFek.m、DDCTIFmax.m、DDCTIFav.m:三种融合规则的取舍
3.1 系数域里“选谁”比“怎么变换”更关键
DDCT把这个频域系数分到了多个方向,但融合规则决定了最终哪个信息被保留。DDCTIF文件夹里三个文件分别对应三种规则:ek基于局部能量加权,max直接取绝对值大者,av取平均。融合规则之间的差别在视觉上远大于变换选择的影响,一组对比实验往往能直观看出这一点。
| 规则 | 适用场景 | 优点 | 失效情况 |
|---|---|---|---|
| 系数绝对值取大 max | 边缘细节突出的自然图像 | 保留强边缘和高频细节最彻底 | 噪声较大时噪声系数也被保留 |
| 局部能量加权 ek | 红外与可见光融合 | 抗噪性好,过渡自然 | 两个源图能量接近时权重趋同,无增益 |
| 系数平均 av | 医学多模态图像 | 计算量最小,灰度平滑 | 直接产生双影,边缘模糊明显 |
用DDCTIFek.m做融合实验时,重点关注它的能量窗口大小。窗口太小,只有3x3,能量统计波动太大,融合结果容易产生块效应;窗口太大,像15x15这种,局部细节被平均掉,融合结果接近低通滤波。
3.2 fuse_pca.m里权重是怎么算出来的
fuse_pca.m不是做DCT系数的PCA,而是对两个源图做像素域的PCA计算,求出每个源图的贡献权重,再把这个权重应用到DDCT分解后的系数上。它的典型实现是先把两幅源图像拉成两列向量,合并成观测矩阵,算协方差矩阵的特征向量,取最大特征值对应的特征向量作为权重方向。
function fusedCoef = fuse_pca(coefA, coefB) % 低通成分的融合权重由PCA决定 [rows, cols] = size(coefA); % 将低频近似部分分离表达 lowA = coefA(1:floor(rows/2), 1:floor(cols/2)); lowB = coefB(1:floor(rows/2), 1:floor(cols/2)); vecA = lowA(:); vecB = lowB(:); X = [vecA, vecB]; % 协方差矩阵的最大特征向量即主轴方向 covM = cov(X); [V, D] = eig(covM); [~, idx] = max(diag(D)); w = V(:, idx); % 归一化使权重和为1 w = abs(w) / (abs(w(1)) + abs(w(2))); % 高频部分按局部方差自适应加权 fusedHigh = max(abs(coefA) - abs(coefB), 0) .* coefA + ... max(abs(coefB) - abs(coefA), 0) .* coefB; fusedCoef = w(1) * coefA + w(2) * coefB; % 高频自适应成分占主导, PCA权重只保留低频趋势 fusedCoef = fusedCoef + 0.5 * fusedHigh; end这段代码举例说明的是PCA权重的计算方式:eig返回的特征向量矩阵每一列是一个主轴,最大特征值对应的主轴方向就是两幅图像主要差异的方向,差异大的一方获得更大的融合权重。
cov函数在这里处理的是两个变量的协方差,得到的2x2矩阵特征向量恰好对应源图A和B的最佳投影方向。这里有一个容易出错的细节:eig输出的特征值不是按降序排列的,必须用idx显式抓取最大值。直接取第一列特征向量是Matlab代码里常见的潜在bug,因为它只在特征值恰好排好序时才正确。
fusedCoef的计算中,PCA部分实际上主要作用于低频亮度和结构趋势,而高频细节靠max函数做逐系数的竞争。这是我推荐的处理方式:低频要平滑过渡,高频要锐利清晰,一个规则不可能同时满足两者,必须拆开处理。
3.3 三个脚本在什么时候应该切换
DDCTIFmax.m适合源图像质量比较高、噪声不太明显的地物遥感图;DDCTIFav.m适合医学影像中灰度差异本来就小的模态对;DDCTIFek.m是万金油,适合大多数课程设计场景。
需要注意的点是,三个脚本的高频部分如果都用了系数最大策略,它们之间的视觉差异只在低频融合部分体现。一个实测技巧是:设置好规则后,先用小尺寸预览图跑一遍,计算融合结果的熵和平均梯度,与直接像素平均的结果做对比,如果熵没有提高,就说明规则参数设置有问题,多半是方向角度采样不够或能量窗口过小。
提示:
DDCTIFek.m和DDCTIFmax.m的融合结果差异如果过小,可以先检查两个源图是否已经做完严格配准。存在1-2像素偏移时,高频竞争规则会自动选择偏移边缘,造成伪影,这时切到ek规则的表现反而更好。
4. 融合质量评价:im_fuse_per_eval.m 里的指标怎么反过来指导参数调整
4.1 指标计算
im_fuse_per_eval.m做的是融合后图像的定量评价,它通常输出四个核心指标:熵、平均梯度、互信息、空间频率。指标脚本本身不长,但它是调参时的导航仪。
function [ent, avgGrad, miVal, sf] = im_fuse_per_eval(imF, imA, imB) % 1. 信息熵: 度量融合图包含的信息量 p = imhist(imF) / numel(imF); p(p == 0) = []; ent = -sum(p .* log2(p)); % 2. 平均梯度: 清晰度指标, 越大边缘越锐利 [gx, gy] = gradient(imF); avgGrad = mean(sqrt(gx(:).^2 + gy(:).^2)); % 3. 互信息: 融合图与两个源图的共享信息总和 miA = sum(sum(imhist2(imA, imF) .* log2(... imhist2(imA, imF) ./ (imhist(imA)*imhist(imF)' + eps)))); miB = sum(sum(imhist2(imB, imF) .* log2(... imhist2(imB, imF) ./ (imhist(imB)*imhist(imF)' + eps)))); miVal = miA + miB; % 4. 空间频率: 行/列差分平方和开根号, 反映纹理活跃度 rowFreq = sqrt(sum(sum(diff(imF, 1, 1).^2)) / numel(imF)); colFreq = sqrt(sum(sum(diff(imF, 1, 2).^2)) / numel(imF)); sf = sqrt(rowFreq^2 + colFreq^2); end熵反映融合图的信息丰富度,平均梯度反映纹理清晰度,互信息反映融合图从源图里继承了多少信息,空间频率衡量图像整体的活跃程度。这四个指标单独看都有盲区,比如噪声会同时抬高熵和平均梯度,所以必须组合解读。
4.2 指标异常对应的参数调整方向
做融合实验时,指标不会告诉你是哪个参数错了,它只告诉你结果不对。常见的情况是:熵很高但互信息很低,这通常说明融合系数处引入了源图不存在的虚假信息;平均梯度低,说明DDCT的角度太多且融合权重向低频倾斜。以下调整思路来自实测经验,可以按顺序排查。
| 现象 | 根因 | 处理方式 |
|---|---|---|
| 熵偏高、互信息偏低 | 融合过程引入伪纹理 | 缩小角度数组,从6方向减到3方向 |
| 熵和平均梯度双低 | 系数取平均过度平滑 | 从av切换至ek,或减小能量窗口 |
| 互信息接近但平均梯度过高 | 噪声被当成细节保留 | 高频竞争改用局部能量加权代替绝对值比较 |
| 空间频率异常低 | 分块尺寸偏大,融合粒度太粗 | blkSize从16下调到8甚至4 |
im_fuse_per_eval.m里imhist2是联合直方图函数,如果Matlab环境中没有现成函数,自己实现一个二维直方图即可:把两幅图像灰度值配对后分箱统计。互信息对灰度量化级别敏感,一般用256 bin就足够。参数调整不是一次性完成的,正确流程是跑一次评价指标,改一个参数,再跑一次,四个指标对比完之后才能确定新参数是否真的更优。
提示:不要只对比融合结果图的主观视觉。人的视觉对亮度差异敏感,但对纹理质量的判断不稳定。把熵、平均梯度、互信息三列数值写在一张表里,比反复看图更能发现问题。
5. 代码在不换Matlab版本时的兼容排查
5.1 旧版本代码跑到新版本报错怎么办
资源里的代码注释写着支持Matlab 2014、2019a、2024a,但跨版本运行最常见的报错不是语法而是函数行为变化。imrotate的插值算法在两个版本之间的边界处理略有差异,padarray对'replicate'的支持在R2016a之后才稳定。在旧版本写死'replicate'的代码在新版本能跑,反过来用比较新的语法写旧版本兼容代码就比较麻烦。
% 兼容性检查: 在脚本开头加一段自动诊断 function compatCheck() v = ver('matlab'); release = v.Release; % 形如 '(R2019a)' fprintf('当前Matlab版本: %s\n', release); % 检查 imrotate 是否支持指定插值方法 try imrotate(zeros(8), 30, 'bilinear', 'crop'); fprintf('imrotate 接口: OK\n'); catch ME fprintf('imrotate 接口异常: %s\n', ME.message); end % 检查函数式编程相关函数是否可用 if exist('imhist2', 'file') ~= 2 warning('未找到 imhist2, 将使用自定义联合直方图替代'); end end在脚本启动时调用compatCheck可以提前暴露接口差异,而不是等融合跑到一半才中断。ver('matlab')能拿到当前Release信息,方便根据版本来选择分支代码。旧版本最大的坑是imhist2不存在,如果评价脚本里直接调用它,代码会在最后一步崩溃,那时整个融合已经执行完了,时间浪费掉。
5.2 多组测试图验证融合结果不受尺寸影响
默认提供的saras92.jpg、saras91.jpg尺寸一致,但课程设计换用自己的测试图时,两幅源图尺寸不匹配是很常见的情况。DDCT分块处理时,padarray会把图像补到分块尺寸的整数倍,逆变换后再裁剪回来。如果两个源图尺寸差太大,补丁区域会覆盖图像有效区域的影响:
function [im1, im2] = alignImages(im1, im2) % 统一两幅图尺寸: 较小图边缘补零到与较大图一致 [r1, c1] = size(im1); [r2, c2] = size(im2); r = max(r1, r2); c = max(c1, c2); im1 = padarray(im1, [r - r1, c - c1], 'replicate', 'post'); im2 = padarray(im2, [r - r2, c - c2], 'replicate', 'post'); endalignImages用'replicate'而不是0填充,是因为边缘重复能避免在DDCT分块边界产生突兀的灰度跳变。补零会在边界制造大量高频成分,融合规则会把这些假细节当真实信息保留下来,熵和平均梯度虚高。用边缘复制填充之后,边界处的系数过渡自然,评价指标也更接近真实水平。
验证换图后算法不崩的最小方案是:准备三组尺寸差异明显的图像对,比如512x512、768x1024、1200x900,分别跑一遍DDCTIF_demo.m,如果三组都能输出融合结果且指标数值量级一致,说明代码的尺寸兼容性没问题。如果跑大图时Matlab内存溢出,优先把blkSize从8改成16,块数量大幅度减少,内存占用下降,代价是融合精度略微下降。
5.3 保存中间系数的验证技巧
调试时,在DDCT分解后把系数保存成.mat文件,可以反复实验不同融合规则而不需要重新做变换。
save('ddct_coefficients.mat', 'coefs1', 'coefs2', 'angles', 'blkSize'); % 第二次实验时直接加载系数, 跳过DDCT计算 % load('ddct_coefficients.mat', 'coefs1', 'coefs2', 'angles', 'blkSize');这样调试效率提升非常明显,尤其当测试图尺寸较大时。DDCT变换耗时占比在整条融合流程中通常在70%以上,把变换结果缓存起来,调整融合规则时几乎实时出结果,调参效率完全不在一个量级。
本文还有配套的精品资源,点击获取