简介这份资源面向图像处理方向的学习者与开发者系统整理了图像质量评价领域的经典算法代码实现涵盖BRISQUE、RankIQA、NIMA等代表性方法可用于监控视频传输质量评估、图像压缩与增强算法效果验证、医疗影像分析等场景兼顾学术研究与工程落地需求。压缩包共821个文件约155.08MB以py、m、cpp三种语言源码为主配套jpg、bmp、png等测试图像以及prototxt、cfg、h5、pb等模型与配置文件另有txt、md、pdf说明文档和mexw64、mexa64等跨平台编译产物方便在MATLAB、C与Python环境下直接运行调试。目前已有8686人学习下载热度较高。读者可从中获得无参考质量评估的完整实现思路、局部统计特征与深度网络两类技术路线的对照代码以及多语言工程化参考适合作为入门实践与算法复现的起点。1. 图像质量评价经典算法代码从 BRISQUE 到 NIMA一套能跑的实验底座做图像增强、超分、去噪或者生成模型的人迟早会撞上一个尴尬问题模型指标涨了人眼看着却更糟。PSNR、SSIM 这类全参考指标在真实场景里经常失灵因为拿不到无损参考图。这时候就得靠无参考图像质量评价NR-IQA来兜底。这份资源打包了 BRISQUE、RankIQA、NIMA 等几套经典算法的代码实现覆盖了从传统手工特征到深度排序学习再到美学打分的主流路线。适合正在做图像复原、画质调优、数据集清洗的开发者也适合想复现 IQA 论文但不想从零搭训练框架的人。拿到手就能跑推理改几行就能接自己的数据。2. 项目定位与技术选型为什么是这几套算法2.1 三类算法的分工BRISQUE 走的是传统路线在空域提取自然场景统计特征NSS用 SVR 回归出质量分。它的优势是轻量、不需要 GPU、推理快适合做数据预筛选。RankIQA 的核心思路是用排序代替绝对打分通过成对比较学习相对质量缓解标注噪声问题。NIMA 则是端到端回归用 CNN 直接预测质量分布输出 1 到 10 分的概率直方图而不是单一分数。这三者放在一起基本覆盖了 IQA 从手工特征到深度模型的主要范式。代码组织上常见做法是按算法分目录每个目录下放模型定义、推理脚本和权重加载逻辑。依赖方面BRISQUE 依赖 OpenCV 和 scikit-learnRankIQA 和 NIMA 依赖 PyTorch 或 TensorFlow。如果资源里用的是 PyTorch那 NIMA 的骨干网络大概率是 MobileNet 或 VGG 的变体因为原论文就是基于这两种结构做的实验。2.2 目录结构长什么样一个典型的 IQA 项目目录会这样组织iqa-algorithms/ ├── brisque/ │ ├── brisque.py # 特征提取与打分 │ ├── svr_model.pkl # 预训练 SVR 权重 │ └── demo.py # 单图推理示例 ├── rankiqa/ │ ├── model.py # Siamese 网络定义 │ ├── train.py # 排序损失训练 │ └── infer.py # 成对比较推理 ├── nima/ │ ├── nima_model.py # 骨干网络 分布头 │ ├── predict.py # 单图质量分布预测 │ └── weights/ # 预训练权重 ├── utils/ │ ├── image_loader.py # 统一图像读取与预处理 │ └── metrics.py # 评估指标计算 └── requirements.txt这个结构的好处是每个算法自包含改一个不影响另一个。utils 里放公共逻辑避免重复造轮子。如果你要加新算法照着已有目录复制一份改就行。2.3 技术选型的取舍BRISQUE 用 OpenCV 实现特征提取是常见做法因为 OpenCV 自带 Gabor 滤波和局部归一化操作比纯 NumPy 快不少。RankIQA 的 Siamese 结构需要成对输入训练时要注意正负样本的构造策略——通常是从同一张参考图的不同失真版本里采样。NIMA 的输出层是 softmax 加期望计算把分布转成标量分数这一步在推理脚本里容易写错后面会细说。注意如果资源里同时包含 TensorFlow 和 PyTorch 版本建议先确认自己的环境支持哪个别两个都装容易出依赖冲突。3. 环境搭建与快速跑通从零到第一张图的分数3.1 依赖安装与版本确认先看 requirements.txt 里锁定的版本。IQA 项目常见的坑是 PyTorch 版本和 torchvision 不匹配导致预训练权重加载失败。我一般会先建虚拟环境再按顺序装python -m venv iqa_env source iqa_env/bin/activate # Windows 用 iqa_env\Scripts\activate pip install -r requirements.txt如果 requirements.txt 里没锁死版本建议手动指定几个关键包pip install torch1.13.1 torchvision0.14.1 pip install opencv-python4.7.0.72 pip install scikit-learn1.2.2 pip install scipy1.10.1版本不是越新越好。比如 scikit-learn 1.3 之后 SVR 的某些默认参数变了加载旧版 pkl 模型可能报 warning 甚至预测偏移。OpenCV 4.8 对某些图像格式的读取行为也有调整如果 BRISQUE 特征提取结果和预期对不上先降回 4.7 试试。3.2 BRISQUE 推理跑通BRISQUE 的推理脚本通常长这样import cv2 import numpy as np from brisque import brisque # 假设特征提取函数封装在这里 def predict_brisque(image_path, svr_model): # 读取图像BRISQUE 通常在灰度图上计算 img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise ValueError(f无法读取图像: {image_path}) # 提取 36 维 NSS 特征 features brisque.extract_features(img) # features 形状为 (1, 36)直接送入 SVR features features.reshape(1, -1) # 预测质量分分数越低表示质量越差 score svr_model.predict(features)[0] return score if __name__ __main__: import joblib model joblib.load(brisque/svr_model.pkl) print(predict_brisque(test.jpg, model))这里的关键是特征维度必须和训练时一致。BRISQUE 原论文用的是 36 维特征包括 MSCN 系数的统计量和相邻系数乘积的统计量。如果自己重新训练 SVR特征提取函数不能改否则模型直接失效。分数范围一般在 0 到 100 之间越低越好但不同数据集训练出来的模型尺度可能不同别跨模型比较绝对值。3.3 NIMA 推理跑通NIMA 的推理多了一步分布转标量import torch from PIL import Image from torchvision import transforms from nima.nima_model import NIMA def predict_nima(image_path, model, devicecuda): # NIMA 输入通常是 224x224 的 RGB 图 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(image_path).convert(RGB) input_tensor transform(img).unsqueeze(0).to(device) model.eval() with torch.no_grad(): # 输出 10 维概率分布对应 1 到 10 分 dist model(input_tensor) # 转成标量分数对 1 到 10 做加权求和 score torch.sum(dist * torch.arange(1, 11).to(device)) return score.item(), dist.cpu().numpy() if __name__ __main__: model NIMA(backbonemobilenet).to(cuda) model.load_state_dict(torch.load(nima/weights/nima_mobilenet.pth)) score, dist predict_nima(test.jpg, model) print(f质量分: {score:.2f}) print(f分布: {dist})加权求和那一步是 NIMA 的核心。模型输出的是 softmax 后的概率直接取 argmax 会丢失信息用期望值才能反映整体质量倾向。如果发现分数总是集中在 5 附近可能是权重没加载对或者输入预处理和训练时不一致。3.4 RankIQA 的成对推理RankIQA 不太一样它需要两张图一起输入def compare_rankiqa(img1_path, img2_path, model, devicecuda): # 两张图分别过同一个骨干网络 img1 load_and_transform(img1_path).to(device) img2 load_and_transform(img2_path).to(device) model.eval() with torch.no_grad(): score1 model(img1) score2 model(img2) # 分数高的质量更好 if score1 score2: return 图1质量更好, score1.item(), score2.item() else: return 图2质量更好, score1.item(), score2.item()RankIQA 的输出是相对分数单独看一张图的绝对值意义不大重点是两张图的差值。实际用的时候可以拿一张基准图和待测图比或者在一个批次里做排序。4. 核心模块拆解特征提取、网络结构与损失函数4.1 BRISQUE 的 NSS 特征提取BRISQUE 的特征提取分两步先做局部归一化再算统计量。局部归一化用的是 MSCN 系数def compute_mscn(img): # img 是灰度图float 类型 mu cv2.GaussianBlur(img, (7, 7), 7/6) sigma np.sqrt(cv2.GaussianBlur(img**2, (7, 7), 7/6) - mu**2) # 防止除零 sigma np.maximum(sigma, 1e-10) mscn (img - mu) / (sigma 1e-10) return mscn这里高斯核的大小和 sigma 是原论文定的7x7 窗口、sigma7/6。改这两个参数会直接影响特征分布进而影响 SVR 预测。我试过用 5x5 窗口分数整体偏移了将近 10 分所以别乱动。拿到 MSCN 系数后用广义高斯分布拟合得到两个参数再算相邻系数的乘积统计量一共 36 维。这部分代码通常封装在 brisque.py 里调用即可。4.2 NIMA 的网络结构NIMA 的骨干网络可以是 MobileNet、VGG 或 ResNet输出层换成 10 维全连接import torch.nn as nn from torchvision import models class NIMA(nn.Module): def __init__(self, backbonemobilenet, dropout0.5): super().__init__() if backbone mobilenet: base models.mobilenet_v2(pretrainedTrue) # 去掉原始分类头 self.features base.features in_features 1280 elif backbone vgg: base models.vgg16(pretrainedTrue) self.features base.features in_features 512 * 7 * 7 self.classifier nn.Sequential( nn.Dropout(dropout), nn.Linear(in_features, 10), # 10 个质量等级 nn.Softmax(dim1) ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)MobileNet 版本轻量适合部署VGG 版本精度高但慢。如果资源里两个权重都有建议先用 MobileNet 跑通流程再换 VGG 对比效果。Dropout 设 0.5 是原论文的做法推理时记得 model.eval() 关掉。4.3 RankIQA 的排序损失RankIQA 训练时用的是成对排序损失核心是让质量高的图得分高于质量低的图def rank_loss(score_high, score_low, margin0.1): # 希望 score_high 比 score_low 至少大 margin diff score_high - score_low loss torch.mean(torch.relu(margin - diff)) return lossmargin 是个超参设太小模型学不到明显区分设太大容易过拟合。原论文用的是 0.1 左右。训练数据构造上同一张参考图的不同失真版本天然构成排序对失真程度越重质量越低。如果自己造数据注意别把不同内容的图混在一起比较那样学出来的模型没有意义。4.4 评估指标的计算IQA 模型评估通常看 PLCC线性相关系数和 SROCC斯皮尔曼秩相关系数from scipy.stats import pearsonr, spearmanr def evaluate(pred_scores, gt_scores): plcc, _ pearsonr(pred_scores, gt_scores) srocc, _ spearmanr(pred_scores, gt_scores) return {PLCC: plcc, SROCC: srocc}PLCC 衡量线性相关性SROCC 衡量单调性。好的 IQA 模型 SROCC 通常在 0.9 以上。如果发现 PLCC 高但 SROCC 低说明预测值和真实值的关系不是单调的可能是模型输出尺度有问题做个非线性回归映射能改善。5. 避坑与常见问题排查5.1 图像读取方式不一致导致分数异常现象同一张图用 PIL 读和用 OpenCV 读BRISQUE 分数差了十几分。原因PIL 默认 RGBOpenCV 默认 BGR转灰度时权重不同。解决统一用 cv2.imread(path, cv2.IMREAD_GRAYSCALE) 或者 PIL 转灰度后再转 NumPy全流程保持一致。5.2 NIMA 权重加载报 key 不匹配现象load_state_dict 报 missing keys 或 unexpected keys。原因骨干网络结构改了比如把 MobileNetV2 换成 V3层名对不上。解决打印模型 state_dict 的 key 和权重文件的 key逐层对比必要时用 strictFalse 加载但只加载匹配的部分。5.3 BRISQUE 的 SVR 模型跨环境预测偏移现象本地跑出来 30 分服务器上跑出来 45 分。原因scikit-learn 版本不同SVR 内部数值精度有差异。解决锁定 scikit-learn 版本或者把 SVR 模型导出成 ONNX 再推理避免运行时依赖。5.4 RankIQA 成对输入顺序影响结果现象交换两张图的输入顺序比较结果反了。原因Siamese 网络如果共享权重没写好两个分支可能不对称。解决确认两个分支用的是同一个模型实例或者用 clone 保证权重同步。推理时固定一张为基准图避免顺序歧义。5.5 GPU 显存不足导致批量推理崩溃现象单张图能跑batch_size 调到 8 就 OOM。原因NIMA 的 VGG 版本参数量大中间特征图占显存。解决用 MobileNet 版本或者把 batch_size 降到 4再不行就逐张推理。推理时加 torch.no_grad() 也能省不少显存。6. 二次开发与改造思路接自己的数据、换骨干、做服务化拿到这套代码后最常改的三个方向是换数据集微调、换骨干网络、封装成推理服务。换数据集微调时NIMA 的标签是 1 到 10 的分布如果你的标注是 MOS 分需要先转成分布。常见做法是用高斯核把 MOS 分扩散成 10 维向量sigma 取 0.5 到 1 之间。RankIQA 的数据构造更麻烦需要成对样本我一般会写个采样器每次从同一参考图的不同失真版本里随机抽两张保证一高一低。换骨干网络时注意特征维度要改。比如把 MobileNetV2 换成 EfficientNetin_features 从 1280 变成 1280 或 1408得查一下具体版本。另外预训练权重的归一化参数也要跟着换EfficientNet 的 mean/std 和 MobileNet 不一样这个坑我踩过分数直接崩到 1 分附近。封装成服务的话用 FastAPI 起一个接口接收图片 base64 或 multipart返回分数和分布。注意 BRISQUE 的 SVR 模型不是线程安全的多线程并发时要么加锁要么每个线程加载一份模型。NIMA 的 PyTorch 模型推理时记得 model.eval() 和 torch.no_grad()不然显存涨得飞快。验证改造是否成功别只看 loss 降了一定要在留出集上算 SROCC 和 PLCC。我习惯每次改完跑一遍评估脚本和 baseline 对比SROCC 掉超过 0.02 就回滚。从那以后我每次动模型结构或者数据管道都强制走一遍这个小评估流程省得训练半天发现还不如不改。希望帮到你。本文还有配套的精品资源点击获取