
简介面向图像融合研究与算法评测的Python实用工具包针对信息熵、空间频率、标准差、峰值信噪比、均方误差、互信息、视觉保真度、平均梯度、相关系数、差异相关和、基于梯度的融合性能Qabf、结构相似度SSIM、多尺度结构相似度MS-SSIM、基于噪声评估的融合性能Nabf等十余种指标提供统一实现可快速量化不同融合算法的效果适合论文实验、横向对比或工程验收等场景。压缩包共540个文件、约123.55MB其中504个PNG测试图像用于结果展示与可视化对比9个Python脚本为核心评估实现另有pyc编译文件、XML工程配置、README说明和.gitignore等辅助内容目录组织便于直接调用。脚本支持单幅图像评估、单算法全量融合结果评估以及一次性计算所有对比算法的指标并可将结果写入Excel省去手工整理数据的时间同时兼容常见图像读取与处理流程方便接入已有研究项目。已有5357人学习适合图像融合、质量评价方向的研究者与开发者是算法验证和横向对比的实用工具。 做图像融合相关的研究或工程落地最让人头疼的往往不是算法本身而是怎么向别人证明“这张融合图确实比那张好”。尤其是多聚焦图像融合、多模态医学图像融合、红外与可见光融合这类场景没有标准答案光靠肉眼去看很容易被主观感受带偏。我在实际项目里踩过不少次这个坑自己辛辛苦苦调了一版融合算法主观上看觉得细节好了不少结果汇报的时候拿不出量化数据别人一句“好在哪”就把我问住了。后来我把图像融合评估指标整套在 Python 里落了地才算把这块短板补上。这篇内容把这些指标的具体含义、适用场景、计算公式和 Python 实现一次性讲清楚同时附上我在实操过程中积累的注意事项和踩坑经验。无论你是刚接触图像融合的学生还是已经在做算法评测的工程师都可以直接照着这套思路搭建自己的评估脚本。1. 评估需求拆解先搞清楚你融合的是哪一类图1.1 图像融合的三种典型场景图像融合的本质是把多幅来自同一场景但信息互补的图像合并成一张信息更丰富的图像。按应用场景分主流的有三类。第一类是多聚焦融合。镜头景深有限拍微距或者场景纵深很大的照片时很难让所有物体都清晰。多聚焦融合就是把焦点在前景和焦点在背景的多张图合成一张全清晰的图。这类融合结果通常存在一张理想的“全清晰图”只是现实中拍不到所以我们只能用间接指标去评价。第二类是多模态医学图像融合。比如把 MRI 的软组织信息和 CT 的骨骼信息融合到同一张图上帮助医生做诊断。这类场景对组织结构和边缘纹理的保持要求极高评估时更看重结构相似性和边缘保留程度。第三类是红外与可见光融合。红外图能突出热目标但在背景纹理上很弱可见光图纹理丰富但在夜间或者遮挡场景下目标不清晰。融合的目的是兼顾热目标检测和场景理解常用于安防监控和自动驾驶感知。1.2 指标选择的底层逻辑有参考和无参考两条路评估指标必须先分类因为不同融合场景能拿到的“标准答案”是不一样的。有参考评估是指存在一张理想的参考图像或者可以通过实验构造出参考图。比如多聚焦融合中可以用全清晰图像作为理想参考参与计算。有参考指标计算简单、解释性强最常用的包括峰值信噪比PSNR、均方根误差RMSE、结构相似性指数SSIM等。无参考评估则是没有任何参考图时走的路。实际工程中绝大多数情况都属于这一类因为真正理想的融合图是不存在的。无参考指标通过计算融合图自身的信息量、梯度能量、边缘保持度等特性来间接衡量融合质量。我的经验是实际评估时绝不能只依赖某一个指标应该把有参考和无参考的指标组合成一个评估矩阵从多个维度同时考察融合算法的表现。这个思路在后面 Python 实现中会具体体现出来。2. 核心评估指标详解公式、意义与适用边界2.1 像素级基础指标RMSE、PSNR 与相关系数均方根误差RMSE是最直观的参考指标它计算融合图与参考图之间逐像素差异的均方根值。数值越小表示融合图越接近参考图。公式上就是先算两张图的差求平方再求平均最后开根号。峰值信噪比PSNR本质上是对 RMSE 做了一次对数变换。PSNR 的单位是 dB数值越高说明失真越小。它设定了一个像素值动态范围的上限通常是 255然后与 RMSE 做比值最后取对数乘以 20。实际项目中 PSNR 高于 30dB 通常已经算可以接受但这个阈值会随图像内容剧烈变化不能盲目套用。相关系数CCCorrelation Coefficient衡量的是融合图和参考图之间的线性相关程度范围在 -1 到 1 之间。CC 接近 1 说明两张图的整体亮度变化趋势高度一致。CC 对轻微的像素值偏移不敏感所以它可以和 PSNR 互补一起揭示融合图的“整体调性”是否准确。这三个指标计算成本低、可解释性强适合做算法调优过程中的快速反馈。但它们的局限也很明显对图像的空间结构感知能力很弱两张视觉上差异巨大的图可能算出几乎一样的 PSNR。所以在结构敏感的场景里我会更看重 SSIM。2.2 结构感知指标SSIM 与多尺度结构相似性SSIMStructural Similarity Index是图像质量评估领域使用频率最高的指标之一。它的核心思路很朴素人眼对图像结构的感知主要体现在亮度、对比度和结构三个方面的相似性上。SSIM 把这三点分别建模然后做乘积融合。用个生活化的类比如果拿两张照片 P 图和原图对比PSNR 只关心“每个像素的颜色差了多少”而 SSIM 会更像人眼——先看整体明暗是否一致亮度再看对比是否强烈对比度最后看纹理结构是否吻合结构。所以 SSIM 往往比 PSNR 更符合主观感受。MS-SSIMMulti-Scale SSIM是 SSIM 的增强版。它把图像降采样成多个尺度在每个尺度上分别计算对比度和结构相似度最后加权融合。为什么需要多尺度因为人眼看图时既会关注整体布局又会关注局部细节。单尺度 SSIM 可能漏掉某些尺度上的失真。我在评估医学图像融合时倾向使用 MS-SSIM因为组织结构往往跨尺度存在多尺度评估更可靠。2.3 融合场景专属的无参考指标边缘保持度、互信息与空间频率无参考指标是融合评估的重头戏因为它们真正回答的是“没有参考图时怎么判断融合质量”。边缘保持度 QAB/F是融合领域一个非常经典的指标。它先把融合图和两张输入图都提取梯度分别计算融合图对每张输入图边缘强度和方向的保留程度最后加权得到总分。QAB/F 的取值在 0 到 1 之间越接近 1 说明融合图越完整地保留了输入图的边缘细节。这个指标对多聚焦融合和红外可见光融合特别有效因为这两类场景最核心的要求就是边缘不丢失。互信息MI源自信息论衡量的是融合图从两张输入图中“提取”到了多少信息。直观理解就是融合图应该同时携带输入图 A 和输入图 B 的独特信息如果某张输入图的信息在融合图里完全丢失那么互信息就偏低。计算上需要估计图像的概率分布和联合概率分布通常通过直方图统计来实现。空间频率SFSpatial Frequency衡量融合图在水平和垂直方向上的灰度变化频率。SF 越高说明图像包含的高频细节越多整体越清晰锐利。但它容易受到噪声干扰所以单独看 SF 意义不大需要配合其他指标一起评估。我把这些指标的使用建议整理成了下表指标类型适用场景参考方向计算成本RMSE有参考快速回归调优越小越好极低PSNR有参考通用质量评估越大越好低SSIM有参考结构敏感场景越接近1越好中MS-SSIM有参考医学图像、跨尺度内容越接近1越好中互信息 MI无参考信息保留度评估越大越好中边缘保持度 QAB/F无参考多聚焦、红外可见光越接近1越好较高空间频率 SF无参考清晰度快速检测越大越好低3. Python 环境搭建与工具库选型3.1 依赖安装的注意事项做图像融合评估Python 生态里最常用的库是 numpy、scikit-image 和 opencv-python。numpy 负责数值计算scikit-image 提供 SSIM、PSNR 等现成实现opencv 负责图像读取和基础处理。安装时有个顺序和建议。如果你用的是 Linux 系统直接用 pip 安装最稳妥pip install numpy scikit-image opencv-python如果你用的是 Windows我建议优先用 Anaconda 或 Miniconda 创建虚拟环境再在虚拟环境里安装依赖避免系统 Python 环境被弄乱。特别是同时装了多个 Python 版本的时候一定要先激活目标环境再执行安装命令否则容易出现包装到了别的解释器里导入时报错找不到模块的情况。关于 scikit-image 的版本不同 API 变化比较大。以 SSIM 为例0.16 之前用skimage.measure.compare_ssim0.16 之后改名成skimage.metrics.structural_similarity。如果你的代码是从旧项目里抄的注意检查版本兼容问题。我后来习惯了统一用skimage.metrics下的新接口顺便减少一个踩坑点。3.2 开发环境与调试建议我建议使用 Jupyter Notebook 做指标计算的快速验证因为图像融合评估会频繁做可视化对比Notebook 的交互式输出非常适合。等代码验证稳定后再整理成独立的 .py 脚本方便批量处理数据。Visual Studio Code 配合 Python 插件是写脚本阶段的好选择。配置好 Python 解释器和调试断点处理大批量图像评估时会省大量时间。如果只写代码不调试定位问题时只能靠 print 输出效率会低很多。4. 评估脚本的核心实现细节4.1 图像读取与数据预处理处理的第一步是统一图片读入格式。opencv 的imread默认读进来是 BGR 通道顺序而 scikit-image 通常按 RGB 处理。如果混用评估结果会被通道顺序莫名其妙地影响。我的习惯是统一用 opencv 读图然后显式转换成 RGBimport cv2 import numpy as np def load_image(path, sizeNone): # 按 BGR 读入后转 RGB避免通道顺序混淆 img cv2.imread(path) if img is None: raise ValueError(f图像读取失败请检查路径: {path}) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if size is not None: img cv2.resize(img, size, interpolationcv2.INTER_LINEAR) return img.astype(np.float64)注意最后转成 float64 类型。很多指标计算涉及平方、开方、对数等操作uint8 类型下做运算容易溢出或丢失精度先转 float 是避免这类问题最简单的手段。4.2 有参考指标的 Python 实现PSNR 的实现非常简单可以直接手写也可以直接用 scikit-image 的peak_signal_noise_ratio函数。这里给出手写版本方便你理解它到底在算什么def calculate_psnr(img_true, img_test, dynamic_range255.0): mse np.mean((img_true - img_test) ** 2) if mse 0: return float(inf) psnr 10 * np.log10(dynamic_range ** 2 / mse) return psnrSSIM 直接用 scikit-image 的接口更可靠因为它的实现经过了充分验证还支持多通道和 Gaussian 窗口配置from skimage.metrics import structural_similarity as ssim def calculate_ssim(img_true, img_test): # multichannel 参数根据输入维度自动判断 win_size 7 # 如果图像尺寸小于 win_size需要动态调整 min_dim min(img_true.shape[:2]) if min_dim win_size: win_size min_dim if min_dim % 2 1 else min_dim - 1 score, _ ssim(img_true, img_test, win_sizewin_size, fullTrue, channel_axis-1) return score这里有一个容易被忽略的坑SSIM 的窗口大小不能超过图像尺寸小尺寸图像直接调用默认参数会报错。所以我加了一个动态调整逻辑当图像过小时自动缩小窗口。4.3 无参考指标的 Python 实现互信息的计算需要先估计两张图像的联合概率分布。最简单粗暴的方式是计算二维直方图然后归一化得到联合概率。下面是完整的实现from scipy import ndimage def calculate_mutual_information(img_a, img_b, bins64): # 先离散量化到指定区间 a (img_a - img_a.min()) / (img_a.max() - img_a.min() 1e-10) b (img_b - img_b.min()) / (img_b.max() - img_b.min() 1e-10) a (a * (bins - 1)).astype(np.uint8) b (b * (bins - 1)).astype(np.uint8) # 二维直方图 hist_2d, _, _ np.histogram2d(a.ravel(), b.ravel(), binsbins) # 归一化得到联合概率 p_ab hist_2d / hist_2d.sum() # 边缘概率 p_a p_ab.sum(axis1) p_b p_ab.sum(axis0) p_b p_b[:, np.newaxis] p_a p_a[np.newaxis, :] # 计算互信息避免 log(0) p_ab_safe np.clip(p_ab, 1e-12, None) p_a_safe np.clip(p_a, 1e-12, None) p_b_safe np.clip(p_b, 1e-12, None) mi np.sum(p_ab * np.log(p_ab_safe / (p_a_safe p_b_safe))) return mi互信息计算里最需要留意的是零概率问题。图像灰度分布往往集中在有限区间二维直方图会有很多格子计数为零直接取对数会得到负无穷。加一个小的 epsilon 再 clip 是常规操作。边缘保持度 QAB/F 的实现相对复杂这里给出简化版本的核心逻辑先用 Sobel 算子求两张输入图和融合图的梯度幅值然后逐像素计算边缘强度保留度最后加权平均。完整项目级实现建议参考网上开源的 qabf 实现核心代码如下def edge_strength(img): # Sobel 梯度幅值 gx cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize3) gy cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize3) return np.sqrt(gx ** 2 gy ** 2) def edge_preservation(img_a, img_b, img_f): g_a edge_strength(img_a) g_b edge_strength(img_b) g_f edge_strength(img_f) # 融合图相对输入图的边缘强度保持比例 ratio_af np.minimum(g_f / (g_a 1e-10), (g_a 1e-10) / (g_f 1e-10)) ratio_bf np.minimum(g_f / (g_b 1e-10), (g_b 1e-10) / (g_f 1e-10)) q_af np.mean(ratio_af) q_bf np.mean(ratio_bf) return np.mean([q_af, q_bf])这个简化版虽然不如完整版那么精细但在算法调优时足够判断相对好坏。需要精确数值用于论文发表时建议使用完整版实现。4.4 把多个指标封装成统一评估脚本实际的评估场景不会只算一个指标。我习惯把所有指标封装到一个类里传进去两张输入图和融合图一次性返回全部指标class FusionMetrics: def __init__(self, img_a, img_b, img_f, has_referenceNone): self.img_a img_a.astype(np.float64) self.img_b img_b.astype(np.float64) self.img_f img_f.astype(np.float64) self.has_reference has_reference def compute_all(self): metrics {} metrics[SF] self.spatial_frequency() # 融合图与输入 A 的互信息 metrics[MI_A] calculate_mutual_information(self.img_a, self.img_f) metrics[MI_B] calculate_mutual_information(self.img_b, self.img_f) metrics[MI_Sum] metrics[MI_A] metrics[MI_B] metrics[Q_ABF] edge_preservation(self.img_a, self.img_b, self.img_f) # 如果有参考图再算有参考指标 if self.has_reference is not None: metrics[PSNR] calculate_psnr(self.has_reference, self.img_f) metrics[SSIM] calculate_ssim(self.has_reference, self.img_f) return metrics def spatial_frequency(self): row_diff np.diff(self.img_f, axis0) col_diff np.diff(self.img_f, axis1) rf np.mean(row_diff ** 2) cf np.mean(col_diff ** 2) return np.sqrt(rf cf)封装成类的好处是后面要评估新的融合结果时不管融合算法是自己写的还是复现的只要传入相同的输入图和融合图就能得到统一格式的指标报告大大减少重复劳动。5. 评估实操中的典型问题与避坑经验5.1 指标结果互相矛盾怎么办实际评估中经常会出现一个指标变好、另一个指标变差的情况。比如某次多聚焦融合实验中QAB/F 从 0.68 提升到 0.72但互信息反而从 1.83 降到了 1.65。最直接的归因是融合算法加强了边缘纹理的保留但牺牲了部分亮度分布的一致性导致融合图与输入图之间的信息重叠部分减少。遇到这种情况不要急着否定算法先检查几个技术细节。第一确认两张输入图和融合图的尺寸完全一致尺寸不一致会导致指标计算错位。第二确认像素值范围一致如果一张是 0 到 255另一张是 0 到 1计算出来的指标基本不可信。第三确认三张图的文件名对应关系没有张冠李戴。如果技术细节没问题还是矛盾就需要结合实际应用场景做取舍。目标检测场景下的红外与可见光融合优先看边缘保持度和互信息因为这两个指标对目标轮廓和上下文信息更敏感。视觉效果展示场景多参考 SSIM 和空间频率。我的原则是指标服务于应用目标不服务于算法“分数好看”。5.2 小尺寸图像的评估陷阱小尺寸图像是评估里非常容易翻车的地方。比如一张 32x32 的红外图像块直接调用 SSIM 的默认 win_size7 反而还正常但如果下采样到 16x16窗口就会超过图像尺寸。另一个典型问题是梯度类指标在小尺寸图像上噪声巨大。图像只有几十个像素时一两个异常像素对空间频率的影响会被放大到离谱的程度。所以遇到小尺寸测试图我一般会做两件事一是统一调整到合理尺寸后再评估比如 256x256二是在加载图像时做去噪处理用高斯滤波把孤立噪声点先磨掉再算指标。5.3 批量评估时的数据管理批量评估多组图像时最怕的是结果和图像对应不上。我早期吃过一次亏批量跑了 20 组实验最后汇总数据时发现文件命名顺序不对导致整批评估结果作废。后来改用字典结构管理路径和结果import os import json def batch_evaluate(root_dir): results {} files sorted(os.listdir(root_dir)) for name in files: if not name.endswith(.png): continue # 解析文件名中的输入图A、输入图B、融合图 parts name.split(_) if len(parts) 3: continue img_a load_image(os.path.join(root_dir, parts[0] _A.png)) img_b load_image(os.path.join(root_dir, parts[0] _B.png)) img_f load_image(os.path.join(root_dir, name)) metrics FusionMetrics(img_a, img_b, img_f).compute_all() results[name] metrics # 导出 JSON 存档 with open(eval_results.json, w, encodingutf-8) as f: json.dump(results, f, indent2, ensure_asciiFalse) return results文件命名规则越规范后面的返工成本越低。强烈建议在项目开始前就约定统一的命名格式比如“exp01_A.png、exp01_B.png、exp01_fused.png”然后在评估脚本里写清楚对应关系。5.4 Python 代码运行时的常见报错我整理了几个新手容易碰到的报错和解决思路。AttributeError 报错多出现在 scikit-image 版本过旧或过新导致 API 变化。0.19 及以后版本统一推荐skimage.metrics下的函数如果报错就检查一下版本必要时升级到 0.19 以上。MemoryError 通常出现在批量处理高分辨率图像时。2048x2048 的灰度图算二维直方图还好但如果是多通道的 4K 图像同时计算多个指标内存就可能撑不住。对策是把图像转成灰度图再评估或者分块计算。TypeError 经常由数据类型不一致引起。比如一张图是 float64另一张是 intnumpy 在做广播运算时会要么报错要么悄悄做截断转换。我的统一做法是在加载图像后立刻转 float64后续所有计算都在浮点域进行。6. 评估工具链的下一步扩展方向我把这套评估脚本在项目里跑顺之后发现它不仅能用于算法验收还能反过来指导算法改进。比如每次调参后跑一组指标把 QAB/F、互信息、SSIM 的变化趋势画成折线图就能非常直观地看出哪次改动有效、哪次改动只是在“原地打转”。对于重复性高、需要频繁做对比实验的场景把评估脚本接进批处理流程也很值得做。用 pandas 汇总指标结果用 matplotlib 生成对比图做成一份自动化的评估报告不仅省时间汇报时也更有说服力。以后如果数据量变大还可以考虑引入深度学习模型来评估感知质量但底层的像素级和结构级指标计算仍然会长期作为图像融合评估的基础设施存在。把这套 Python 工具维护好无论研究还是工程都能省下大量精力。本文还有配套的精品资源点击获取