图像质量评价三指标:平均梯度、信息熵、标准差原理与Python实现
2026/9/15 2:40:48 网站建设 项目流程

简介:面向图像处理学习者和科研人员的图像质量评价工具包,围绕平均梯度、信息熵、标准差、清晰度等核心指标,提供可直接运行的算法脚本,可用于图像融合、压缩、去噪等处理效果的快速评估。压缩包共十二个文件,含十一个脚本与一份说明文档;脚本覆盖梯度计算、香农熵、标准差、均值、清晰度及综合评价等典型环节,整体仅五KB,轻量易读,说明文档对模块用途做了简要交代。目前已有七百七十八人学习下载。通过阅读这些脚本,能够理解各指标的计算逻辑和适用场景,掌握融合图像质量对比与评价标准选用的实践方法;脚本结构简洁,便于初学者巩固理论,也方便研究者直接复用或二次开发,搭建自己的评价基线。这一套资料将理论指标与实际计算衔接起来,可有效支撑课程实验、论文验证或工程测试中的质量评估需求。

1. 图像质量评价.zip 的敲门砖:为什么大家都先算梯度、熵和标准差

拿到一个名为「图像质量评价」的工具包,最常见的三个函数名准是 average_gradient、information_entropy 和 standard_deviation。做融合对比、超分重建评测时,这三项指标总在结果表第一行,分别对应人眼最在意的事:边缘清不清晰、灰度层次丰不丰富、画面有没有对比度。三者放一起,就能把「这张图行不行」翻译成三个可比较的数值。但多数人只把脚本当黑盒:差分方向怎么取、熵的 log 底数用几、标准差除 N 还是 N-1,换一个脚本,结论就对不上。

这篇笔记把定义、Python 实现和融合评价里的用法逐个拆开,用最小可复现脚本把每一步讲清楚。读完后你不仅能把 zip 里的脚本改造成自己的工具,还能回答「为什么我的融合图熵值最高但看着最差」这类让人卡壳的问题。适合被审稿人追问指标计算方式的人,也适合需要给融合算法写验收脚本的工程师。

2. 平均梯度、信息熵、标准差的数学定义与判读逻辑

2.1 平均梯度:一阶差分逼近清晰度

平均梯度(Average Gradient)度量的是图像边缘的锐利程度。它的思路很朴素:清晰图像相邻像素变化大,模糊图像相邻像素变化小,用一阶差分把这种变化量化出来。

对 M×N 的灰度图 I,先求水平差分 ΔIx(i,j) = I(i, j+1) - I(i, j) 和垂直差分 ΔIy(i,j) = I(i+1, j) - I(i, j),再对每个像素求梯度幅值,最后取平均:

AG = 1/((M-1)(N-1)) · Σ √( (ΔIx² + ΔIy²) / 2 )

因为差分会让输出尺寸在两个方向上各缩一个像素,所以分母用 (M-1)(N-1)。根号里的 1/2 是最容易被忽略的细节:它让水平、垂直和对角方向的边缘响应一致,保证指标与边缘方向无关。有些开源实现写的是 √(ΔIx² + ΔIy²),那会让数值整体放大 1.414 倍,排序一般不变,但和论文里的绝对值对比时必须先确认变体。

import numpy as np gx, gy = np.array(2.0), np.array(3.0) print(np.sqrt(gx**2 + gy**2)) # 不除 2 的版本:3.606 print(np.sqrt((gx**2 + gy**2) / 2)) # 各向同性版本:2.550

同一个 (2, 3) 的梯度,两个公式能差出 1 以上。按我的习惯,实现一律用带 1/2 的版本,并把公式写进代码注释,省得三个月后自己都核对不上。另外要留意差分算子的选择:用 np.diff 是裸差分,对单像素细节最敏感;Sobel 自带 3×3 平滑核,对噪声更稳,但会低估超分结果里的细纹理。论文对照时优先裸差分,和定义式严格对应。

2.2 图像信息熵:灰度分布的不确定性度量

图像信息熵借用香农信息论:把图像看成灰度随机变量的一个样本,灰度分布越均匀,不确定性越大,熵越高。对 8 位灰度图:

H = -Σ p_k · log₂(p_k),k 从 0 到 255

p_k 是灰度级 k 的出现频率。log 以 2 为底时单位是 bit,均匀分布时熵达到最大值 8 bit;整张图只有一种灰度时熵为 0。实现上,用直方图统计频率再套公式即可,唯一要处理的是 p_k = 0 的桶不能参与 log 运算。

熵的解读比梯度更依赖场景。细节丰富的自然图像熵通常在 7 bit 左右,但这不意味着 7.5 就一定比 7.0 好——白噪声图像的熵可以非常接近 8,因为每个灰度级都均匀出现。所以熵高只说明灰度层次多,至于它是「丰富的细节」还是「一堆噪声」,要靠梯度、标准差和肉眼一起判断。

2.3 图像标准差:对比度与动态范围

标准差衡量像素值相对均值 μ 的离散程度:

σ = √( 1/(MN) · Σ (I(i,j) - μ)² )

它直观对应画面的「反差」:标准差大,亮暗拉得开,视觉上更透亮;标准差小,图像发灰、发闷。numpy 的 np.std 默认除 N(总体标准差),有的脚本用除 N-1 的样本标准差,对常见尺寸的图像两者差异在 0.05% 以内,可以忽略;真正影响可比性的是灰度范围——0-255 和 0-1 两种尺度算出的 σ 相差 255 倍。

另外注意,标准差对整体亮度偏移不敏感:同一张图整体加 50 的灰度偏移,σ 不变。若想排除亮度影响只看对比度,可以用变异系数 CV = σ/μ,这在红外和可见光融合里常用来比较不同模态图像的反差水平。

2.4 三指标联动判读:先看“形状”再看“数值”

单看任何一个指标都会误判,三个放一起能筛掉大部分假阳性:

图像状态平均梯度信息熵标准差典型原因
过曝/欠曝偏低明显偏低偏低灰度被压到极值附近,层次丢失
高斯模糊明显偏低略偏低略偏低高频细节被滤掉,直方图被抹平
叠加噪声异常偏高接近 8中等偏高噪声本身贡献梯度,熵被推高
正常细节丰富7 左右边缘清晰、层次完整、反差适中

判读经验是:梯度异常高但画面看着不干净,先怀疑噪声;熵接近 8 且梯度也高,先看直方图有没有均匀「拖尾」;标准差高但均值偏移严重,先检查预处理是不是做了不该做的归一化。把三个数字和直方图放一起看,比单独报一个指标靠谱得多。

3. 用 Python 实现图像质量评价:一次算完平均梯度/信息熵/标准差

3.1 最小可复现脚本:从 PIL 到三项指标

把前面定义落成代码,我会写成三个独立函数加一个入口,方便单独 import 到其他项目:

import numpy as np from PIL import Image def average_gradient(img: np.ndarray) -> float: """平均梯度:输入二维灰度数组,值越大边缘越锐利。""" img = img.astype(np.float64) gx = np.diff(img, axis=1) # 水平差分,形状 (H, W-1) gy = np.diff(img, axis=0) # 垂直差分,形状 (H-1, W) gx = gx[:-1, :] # 裁剪公共区域 (H-1, W-1) gy = gy[:, :-1] return float(np.mean(np.sqrt((gx ** 2 + gy ** 2) / 2.0))) def information_entropy(img: np.ndarray) -> float: """信息熵:8 位灰度图,均匀分布时为 8 bit。""" img = np.asarray(img, dtype=np.uint8) hist = np.bincount(img.ravel(), minlength=256) p = hist / hist.sum() # 归一化为概率 p = p[p > 0] # 剔除零概率,避免 log2(0) return float(-np.sum(p * np.log2(p))) def standard_deviation(img: np.ndarray) -> float: """标准差:反映对比度,ddof 默认 0(总体标准差)。""" return float(np.std(img.astype(np.float64))) def evaluate_quality(image_path: str) -> dict: """读取图像并返回四项基础统计量。""" arr = np.array(Image.open(image_path).convert('L')) return { 'average_gradient': round(average_gradient(arr), 4), 'information_entropy': round(information_entropy(arr), 4), 'std_deviation': round(standard_deviation(arr), 4), 'mean': round(float(arr.mean()), 4), } if __name__ == '__main__': import sys for p in sys.argv[1:]: print(p, evaluate_quality(p))

逐段说明。average_gradient 里 np.diff 沿 axis 的结果各少一个像素,所以先算完再统一裁剪到 (H-1, W-1),保证 gx 和 gy 逐元素对应;如果先裁剪再差分,边界像素会丢更多,数值偏小。information_entropy 里 np.bincount 要求输入为非负整数,uint8 正好满足,minlength=256 保证灰度级 255 也有对应桶位。std 直接用它默认的总体标准差,大图上 ddof 取 0 还是 1 的差异可以忽略。

命令行用法:python eval_metrics.py image1.png image2.png,脚本会把每个文件的四项指标打出来。这是最小闭环,做批量评价时只要在外面套一层目录遍历。

3.2 关键参数细节:差分选择、熵底数与灰度位数

换不同实现时最容易翻车的是三个参数:

参数常见取值影响建议
梯度算子一阶差分 / Sobel / PrewittSobel 带平滑核,对噪声更稳但会低估高频细纹理论文对照优先一阶差分,与公式严格对应
熵底数log2 / ln数值相差 ln(2)≈0.693 倍统一用 log2,单位 bit 最直观
灰度位数8bit / 归一化 [0,1]熵和标准差的数值完全不同8 位图直接按 0-255 统计,不归一化

Sobel 的问题在超分辨率评测里特别明显:它自带 3×3 平滑核,会把单像素细线这类高频细节抹掉,算出的梯度偏低。如果你的结果要和某篇论文的 AG 值逐位对齐,先确认对方代码里有没有 filter2D 或 convolve,没有就是裸差分。

信息熵还有个隐藏前提:直方图分箱必须是 0-255 共 256 个桶。有的脚本对归一化后的 [0,1] 数据直接 np.histogram,默认分箱数对不上,熵值就不可比。

提示:如果输入是 PNG 但实际存的是调色板索引,直接 ravel 统计的是索引值而不是真实灰度,先 convert('L') 再算。

3.3 彩色图像与 16 位图像:转灰度还是逐通道

彩色图像常见两种策略。第一种是整体转灰度,PIL 的 convert('L') 用 ITU-R BT.601 系数 L = 0.299R + 0.587G + 0.114B,优点是快、和大多数论文口径一致;缺点是抹掉色差信息,两张饱和度不同但亮度分布相同的图指标完全相同。

第二种是逐通道计算再平均,保留色彩对指标的影响:

def evaluate_color(image_path: str) -> dict: arr = np.array(Image.open(image_path)) if arr.ndim == 2: return evaluate_quality(image_path) arr = arr[:, :, :3] # RGBA 图去掉 alpha 通道 names = ['average_gradient', 'information_entropy', 'std_deviation'] acc = {k: [] for k in names} for c in range(arr.shape[2]): ch = arr[:, :, c] acc['average_gradient'].append(average_gradient(ch)) acc['information_entropy'].append(information_entropy(ch)) acc['std_deviation'].append(standard_deviation(ch)) return {k: round(float(np.mean(v)), 4) for k, v in acc.items()}

对每个通道算完指标后取平均,等价于把三通道看成三个独立样本。这样处理的好处是,色偏严重的融合图会被熵值反映出来——某一通道分布被压扁时,该通道熵明显低于其余通道,平均后就能看见差异。注意 RGBA 图的 alpha 通道是透明掩膜,不是色彩信息,必须先切片去掉,否则标准差会失真。

16 位图像的坑更隐蔽:uint16 范围是 0-65535,直接套 256 桶的 bincount 会把高位信息全挤进第一个桶。做法是先做线性映射到 0-255 再算,或者把熵的分箱数改成 4096/65536 并重新归一化联合分布。我一般只对 8 位图报绝对熵值,16 位图只报相对大小,不做跨数据集比较。

4. 融合评价实战:用三项指标比较融合算法的输出质量

4.1 无参考评价的对照逻辑:孤立指标没有意义

图像融合场景里,评价分两类。有真值(比如仿真时把一张清晰图人为模糊成多张源图)可以用 PSNR、SSIM 这类全参考指标;但大多数真实融合任务没有真值,只能做无参考评价,平均梯度、信息熵、标准差就是最常用的一组。

无参考不等于无对照。失败案例大多是「算了个融合图的熵,7.2,很好」——这没有意义。融合的目标是把源图的有效信息尽可能搬到结果里,所以融合图的每个指标都要和源图放在同一张表里比,看的是差值而不是绝对值。

判读逻辑有三条。第一,融合图的平均梯度应达到或超过源图中较清晰的那张;第二,信息熵不应低于任一源图,明显降低说明灰度层次被压没;第三,标准差应保持在中上水平,异常低说明动态范围被压缩,异常高往往是振铃伪影在撑数值。

4.2 多聚焦融合图的判读实验脚本

多聚焦是最典型的场景:源图 A 左清晰右模糊,源图 B 右清晰左模糊,融合算法把两者合成一张全清晰图。写个循环把一组图全算一遍:

import os def evaluate_folder(folder: str) -> list: rows = [] for name in sorted(os.listdir(folder)): if name.lower().endswith(('.png', '.jpg', '.bmp', '.tif')): m = evaluate_quality(os.path.join(folder, name)) m['filename'] = name rows.append(m) return rows rows = evaluate_folder('./fusion_results') for r in rows: print(f"{r['filename']:<20} AG={r['average_gradient']:.3f} " f"H={r['information_entropy']:.3f} std={r['std_deviation']:.3f}")

跑出来的表按文件名对齐,源图 A、源图 B、融合图 F 各一行。判读时先看 AG 列:F 的 AG 应该接近 max(AG_A, AG_B),如果比两者都低,说明融合在「做平均」而不是「做选择」,边缘被中和掉了。再看熵列:F 的熵不低于任一源图才算合格,但接近 8 且 AG 也比源图高出一截时,要怀疑算法引入了伪纹理。

场景AG 表现熵表现结论倾向
融合图清晰选中目标区域与较清晰源图相当或略高不低于两源图融合有效
融合图边缘糊明显低于两源图略低于源图像素平均为主,需换策略
融合图有振铃异常高偏高伪影贡献梯度,需人工复核

需要提醒的是,这些指标无法区分「真实细节」和「伪影细节」,任何自动判读都必须配一张局部放大图。我通常会在脚本里顺手裁三个 100×100 的区域存成对比图,指标表和视图一起交出去。

注意:计算前所有图像必须经过完全相同的预处理,包括位深、归一化和格式转换。融合算法输出的 16 位结果直接和 8 位源图比熵,数值没有意义。

4.3 补充互信息指标:衡量融合结果对源图的信息保留

三项基础指标之外,融合评价里出场率最高的补充指标是互信息(Mutual Information, MI),衡量融合图 F 与各源图 S 之间的信息重合度。常用形式是 MI_f = MI(F, S1) + MI(F, S2),值越大说明从源图继承的信息越多。

def mutual_information(a: np.ndarray, b: np.ndarray) -> float: """计算两幅 8 位灰度图的互信息,单位 bit。""" a = np.asarray(a, dtype=np.uint8).ravel() b = np.asarray(b, dtype=np.uint8).ravel() joint, _, _ = np.histogram2d(a, b, bins=256, range=[[0, 255], [0, 255]]) joint /= joint.sum() pa = joint.sum(axis=1, keepdims=True) pb = joint.sum(axis=0, keepdims=True) with np.errstate(divide='ignore', invalid='ignore'): term = np.log2(joint / (pa * pb)) term[~np.isfinite(term)] = 0.0 return float(np.sum(joint * term))

np.histogram2d 生成 256×256 的联合直方图,除以总和得到联合概率;边缘概率 pa、pb 分别由联合分布按行列求和得到。log2(joint / (pa * pb)) 里 joint 为 0 的位置会产生 NaN,用 errstate 配合 isfinite 清零后参与求和。MI 的下界是 0,表示两图完全独立;同图自信息是 8 bit,所以融合评价里 MI 总和一般落在 2-6 bit 区间。

审稿较严的场合还会要求 QABF(基于边缘保持度的融合指标),它需要方向梯度算子和边缘强度归一化,实现比 MI 复杂得多,但和 AG 互补——AG 看绝对值,QABF 看边缘信息保留比例。论文里有空间引,工程验收里我一般只算 MI 做交叉验证。

5. 批量图像质量评价的工程细节:CSV 汇总、数值验证与判读误区

5.1 目录批量计算并输出 CSV

实验要跑十几个算法、每个算法几十张图时,print 到控制台就没法看了。我习惯把 evaluate_folder 的结果直接写成 CSV,Excel 或 pandas 都能继续处理:

import csv def export_csv(rows: list, out_path: str) -> None: with open(out_path, 'w', newline='', encoding='utf-8-sig') as fp: writer = csv.DictWriter(fp, fieldnames=list(rows[0].keys())) writer.writeheader() writer.writerows(rows) rows = evaluate_folder('./fusion_results') export_csv(rows, 'metrics_summary.csv')

DictWriter 用第一个字典的键当表头,后续每个字典一行。编码用 utf-8-sig 是为了让 Excel 直接打开时不乱码。导出后我会在 CSV 里加两列派生指标:ΔAG = AG_fused - max(AG_src1, AG_src2),ΔH = H_fused - max(H_src1, H_src2),验收标准就变成两条硬规则:ΔAG > 0 且 ΔH 不低于 -0.3 bit。这两条能挡掉八成不合格的融合结果。

5.2 用合成图验证实现是否正确

指标实现有没有写错,拿真实图是看不出来的,因为没人知道「正确值」是多少。每次写完先用两张合成图自检:

# 合成图 1:256x256 斜坡图,灰度随列递增 ramp = np.tile(np.arange(256, dtype=np.float64), (256, 1)) print(average_gradient(ramp)) # 期望 0.7071 = 1/sqrt(2) print(information_entropy(ramp)) # 期望 8.0,均匀分布 print(standard_deviation(ramp)) # 期望约 73.9 # 合成图 2:全零图 zero = np.zeros((256, 256)) print(average_gradient(zero)) # 期望 0.0 print(information_entropy(zero)) # 期望 0.0 print(standard_deviation(zero)) # 期望 0.0

斜坡图每列递增 1,水平差分恒为 1、垂直差分恒为 0,所以 AG = √(1/2) ≈ 0.7071;灰度在 256 级上均匀分布,熵精确等于 8.0;离散均匀分布的标准差理论值约 73.9。如果这三个值和理论对不上,说明差分裁剪或分箱统计有 bug。这个验证十秒跑完,比拿真实图瞎猜可靠得多。

5.3 判读中的三个高频误区

第一个误区是跨脚本比绝对值。不同实现的分箱、差分、归一化不一致,AG 差 30% 很常见,所以验收必须固定一套脚本跑完全部对比,绝不允许 A 算法用论文里的数、B 算法自己跑一遍。第二个误区是把高熵当高质量,噪声图熵接近 8,判读时先看直方图,看它是不是均匀「白」出来的。第三个误区是只看融合图自带指标、不看源图基线,AG 7.0 看着不错,但如果源图清晰区本来就有 7.5,这个融合结果反而是减分的。我的验收流程固定为:同一脚本算全部图 → CSV 输出 → 加 ΔAG 和 ΔH 两列 → 抽查三处局部放大图,指标与视图互为印证后才算过。

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

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

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

立即咨询