快捷通道一:不看背景科普废话,直达本文主题

快捷通道二:不想看正文废话,直达运行主程序


图像质量评估-IQA

图像超分辨率(Image Super Resolution)

超分辨率(Super-Resolution)即通过硬件或软件的方法提高原有图像的分辨率,通过一系列低分辨率的图像来得到一幅高分辨率的图像过程就是超分辨率重建。超分辨率重建的核心思想就是用时间带宽(获取同一场景的多帧图像序列)换取空间分辨率,实现时间分辨率向空间分辨率的转换。

图像超分方法

图像超分就是让模糊图片变清晰的技术,目前主流方法分三类:基于插值、基于重建和基于学习。

超分方法 原理 优点 缺点
基于插值 通过数学公式在像素间 “猜” 新值,放大图像 计算快,适合实时处理 边缘易模糊,细节恢复差,可能存在锯齿、模糊、块状效应
基于重建 利用多张低分辨率图像或先验知识优化重建,解决退化模型问题 细节保留更好,适合医学影像等专业领域 计算复杂,耗时较长,可能存在过平滑、边缘失真、振铃效应。
基于学习 用深度学习模型从大量数据中学习低分辨率到高分辨率的映射 细节和纹理恢复最自然,效果最好 需大量数据和算力,模型可能过拟合,可能存在纹理幻觉、结构错位、高频噪声

图像质量评方法估分类

图像超分客观评估方法

评估方法分类

评估应用提示

相关工具推荐

IQA-PyTorch 及其应用

官方介绍翻译

IQA-PyTorch 是一个基于纯 Python 和 PyTorch 构建的全面图像质量评估(IQA)工具包。我们重新实现了许多广泛使用的全参考(FR)和无参考(NR)指标,在有官方 MATLAB 脚本的情况下,结果均已针对其进行校准。借助 GPU 加速,我们的实现比其 Matlab 对应版本快得多。

仓库地址

环境准备

无法联网(HuggingFace)的环境

import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

或:

# Linux/Mac
export HF_ENDPOINT=https://hf-mirror.com

# Windows
set HF_ENDPOINT=https://hf-mirror.com

做如上设置后,仍然存在类似相关报错(安全起见,建议开始就使用这种方法,全部使用本地部署的方式,哪怕能够连上 HF): (MaxRetryError("HTTPSConnectionPool(host='hf-mirror.com' Max retries exceeded with url: **/model.safetensors,那么按照下面三个步骤操作:

HF 模型下载 网页中选择目标 pth 文件下载到本地,如果无法访问,自行查找科学上网方法或请人帮忙下载

修改源文件: %USERPROFILE%\AppData\Local\Programs\Python\Python310\Lib\site-packages\pyiqa\archs\hypernet_arch.py

self.base_model = timm.create_model(
    # 修改 pretrained=True 为 pretrained=False
    base_model_name, pretrained=True, features_only=True
)

步骤一中下载的权重文件可以直接保存到 %USERPROFILE%\\.cache\torch\hub\pyiqa下,这样程序中可以不用指定路径使用默认的权重文件进行加载。也可以下载到固定的目录,加载时自行指定路径:

metric = pyiqa.create_metric('qualichlip', pretrained=False)
metric.load_weights('your_path/QualiCLIP.pth')

metric = pyiqa.create_metric('qualichlip', pretrained_model_path='your_path/QualiCLIP.pth')

解决 load_state_dict 报错问题

运行主程序获取结果

指标使用说明

使用提示和个人见解

自定义模型训练与数据集使用

IQA-PyTorch 项目为 CLIPIQA、CNNIQA、DBCNN、HyperNet、NIMA、QualiCLIP、TOPIQ、WaDIQaM 这几个深度学习类神经网络模型提供了可自定义训练的默认数据集配置,在项目 options/train 目录下,以 QualiCLIP 的 KonIQ-10k 数据集为例,配置为 options/train/QualiCLIP/train_QualiCLIP_koniq10k.yml 文件。

训练配置通常包含 学习率及其调度策略、批量大小、训练周期数、优化器选择、损失函数配置,用户可以根据自己的硬件条件和数据集特性调整这些参数,以获得最佳的训练效果。clone 完项目,下载好对应的数据集,修改好训练配置后就可以开始自行训练:

# 在项目根目录下
python ./pyiqa/train.py --opt options/train/train_QualiCLIP_koniq10k.yml

全部可自定义训练的配置:

模型名称 数据集(需用户指定) 配置文件名(示例)
CLIPIQA KonIQ-10k train_CLIPIQA_koniq10k.yml
CNNIQA KonIQ-10k train_CNNIQA.yml
DBCNN LIVEC
KonIQ-10k
TID2008
train_DBCNN.yml
train_DBCNN_koniq10k.yml
train_DBCNN_tid.yml
HyperNet KonIQ-10k train_HyperNet.yml
NIMA AVA
KonIQ-10k
SPAQ
train_NIMA.yml
train_NIMA_inception_ava.yml
train_NIMA_inception_koniq.yml
train_NIMA_inception_spaq.yml
QualiCLIP live
KonIQ-10k
SPAQ
train_QualiCLIP_clive.yml
train_QualiCLIP_flive.yml
train_QualiCLIP_koniq10k.yml
train_QualiCLIP_spaq.yml
TOPIQ resnet50_ava
CGFIQA
GFIQA
KonIQ-10k
Swin_ava
Swin_CGFIQA
train_TOPIQ_res50_ava.yml
train_TOPIQ_res50_cgfiqa.yml
train_TOPIQ_res50_gfiqa.yml
train_TOPIQ_res50_koniq.yml
train_TOPIQ_swin_ava.yml
train_TOPIQ_swin_cgfiqa.yml
WaDIQaM general_iqa_dataset
KonIQ-10k
train_WaDIQaM_FR_kadid.yml
train_WaDIQaM_NR_koniq.yml

附一 图像评分主计算程序

import pyiqa
import torch
import os
import cv2
import time
import glob
import shutil
import os.path as osp
import logging
import piexif
import exifread
import metric_conf as mc
from PIL import Image
from datetime import datetime as dt
from pillow_heif import register_heif_opener

# 配置日志
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] [%(levelname)s]: %(message)s')
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

# 如非调试, 关闭大部分日志, 修改源文件, 删除一些不必要的 print
pyiqa_logger = logging.getLogger('pyiqa')
pyiqa_logger.setLevel(logging.ERROR)


def get_timestamp_list(image_dir, ts_begin_index=0):
    time_set = []
    for ff in os.listdir(image_dir):
        fp = osp.join(image_dir, ff)
        if osp.isdir(fp):
            continue
        ts = ts_from_file_name(ff, ts_begin_index)
        if ts not in time_set:
            time_set.append(ts)

    return time_set


def ts_from_file_name(file_name, begin_index = 0):
    name_arr = file_name.rsplit(".", 1)[0].split("_")
    i_1, i_2, i_3 = begin_index, begin_index + 1, begin_index + 2
    cut_time = name_arr[i_2] if len(name_arr[i_2]) == 6 else name_arr[i_2][0:6]
    timestamp = f"{name_arr[i_1]}_{cut_time}"
    if len(name_arr) > i_3 and name_arr[i_3].isdigit() and len(name_arr[i_3]) == 1:
        timestamp += f"_{name_arr[i_3]}"
    return timestamp


def convert_heif_to_rgb(image_path, delete_heic=False):
    """
    heic 格式转换到 jpg 格式, 画质会大幅损失, 哪怕quality设置为95%
    """
    register_heif_opener()
    image_name = osp.basename(image_path)
    if image_name.lower().endswith('.jpg'):
        return image_path
    jpg_path = image_path.replace('.heic', '.jpg')

    with Image.open(image_path) as image:
        rgb_image = image.convert('RGB')
        exif_bytes = image.info.get('exif')
        save_kwargs = {'quality': 95, 'subsampling': 1}
        if exif_bytes:
            try:
                exif_dict = piexif.load(exif_bytes)
                save_kwargs['exif'] = piexif.dump(exif_dict)
            except Exception as e:
                logger.error(f"error while handling exif info: {e}")
        if osp.exists(jpg_path):
            os.remove(jpg_path)
        rgb_image.save(jpg_path, **save_kwargs)
        logger.warning(f'image {image_name} converted to jpg, quality will be reduced a lot')

    if delete_heic:
        os.remove(image_path)
        logger.info(f'heif image {image_path} removed')

    return jpg_path


def get_jpeg_exif_text(image_path):
    exif = {
        'iso': 0,
        'fl': 0,
        'et': '0',
        'ap': ''
    }
    with open(image_path, 'rb') as f:
        exif_data = exifread.process_file(f)
        for key, val in exif_data.items():
            if key == 'EXIF ISOSpeedRatings':
                exif['iso'] = str(val).rjust(5, ' ')
            if key == 'EXIF FocalLengthIn35mmFilm':
                exif['fl'] = str(val).rjust(4, ' ')
            if key == 'EXIF ExposureTime':
                et_arr = str(val).split("/")
                exposure_time = et_arr[0].rjust(6, ' ')
                if len(et_arr) > 1:
                    exposure_time = f"1/{str(round(int(et_arr[1]) / int(et_arr[0])))}".rjust(6, ' ')
                exif['et'] = exposure_time
            if key == 'EXIF ApertureValue':
                ap_arr = str(val).split("/")
                aperture_value = f'F/{ap_arr[0]}'.rjust(6, ' ')
                if len(ap_arr) > 1:
                    aperture_value = f"F/{round(int(ap_arr[0]) / int(ap_arr[1]), 1)}".rjust(6, ' ')
                exif['ap'] = aperture_value

    return f"AP:{exif['ap']}  ET:{exif['et']}s  FL:{exif['fl']}mm  ISO:{exif['iso']}"


def file_pre_handle(hr_dir, lr_dir, ts_begin_index=0):
    file_dict = dict()
    ts_list = get_timestamp_list(hr_dir, ts_begin_index)
    file_dict['file_info'] = list()

    for ts in ts_list:
        lr_files = glob.glob(osp.join(lr_dir, f"*{ts}_iso*.jpg"))
        lr_files += glob.glob(osp.join(lr_dir, f"*{ts}_iso*.heic"))        
        if len(lr_files) == 0:
            ts_list.remove(ts)
            logger.warning(f"no lr image found by timestamp {ts}")
            continue
        lr_file = convert_heif_to_rgb(lr_files[0])

        hr_files = glob.glob(osp.join(hr_dir, f"*{ts}_iso*.heic"))
        hr_files += glob.glob(osp.join(hr_dir, f"*{ts}_iso*.jpg"))
        if len(hr_files) == 0:
            ts_list.remove(ts)
            logger.warning(f"no hr image found by timestamp {ts}")
            continue
        hr_file = convert_heif_to_rgb(hr_files[0])

        file_info = {
            'timestamp': ts,
            'lr_file': lr_file,
            'hr_file': hr_file,
            'exif_text': get_jpeg_exif_text(hr_file)
        }

        file_dict['file_info'].append(file_info)
    file_dict['image_count'] = len(ts_list)
    file_dict['count_width'] = len(str(len(ts_list)))

    return file_dict


def metric_score_normalize(score, metric):
    if score == 0:
        return score

    mv = metric['value_range'][1]
    lb = metric['lower_better']

    # 限制0除外的非纯数学评估结果的下限, 下限为最大值的10%
    limit_score = max(score, mv / 10) if score < mv / 10 else min(score, mv)

    # 最大100, 最小: 10
    return 10 * mv / limit_score if lb else limit_score * 100 / mv


def get_enabled_metrics():
    """
    获取启用的度量指标列表, 如果启用的度量指标的总权重不等于1, 将平均设置每个度量指标的权重
    """
    enabled_metrics = []
    total_weight = 0.0
    for metric in mc.metrics:
        if metric['current_enabled'] and metric['can_be_used']:
            enabled_metrics.append(metric)
            total_weight += metric['score_weight'] if 'score_weight' in metric else 0.0

    enabled_count = len(enabled_metrics)
    if enabled_count == 0:
        return []

    # 如果总分数权重相加不等于 1, 则作废权重直接平均计算
    if total_weight != 1.0:
        logger.warning(f"score weight total is not 1, for each one, reset to {1 / enabled_count}")
        for metric in enabled_metrics:
            metric['score_weight'] = 1 / enabled_count

    return enabled_metrics


def image_math_metric_calc(file_dict, metric_conf):
    """
    计算图像通用纯数学指标, 仅清晰度相关指标, 所以固定使用灰度图
    参数:
        file_dict: 预处理好的文件信息字典
        metric_conf: 评估指标配置
    返回:
        包含各时间戳分数的字典
    """
    scores = dict()
    metric_name = metric_conf['metric_name']
    function = metric_conf['math_calc_func']
    params = {} if 'math_calc_params' not in metric_conf else metric_conf['math_calc_params']

    image_count = file_dict['image_count']
    count_width = file_dict['count_width']
    for index, file_info in enumerate(file_dict['file_info']):
        ts = file_info['timestamp']
        hr_file = file_info['hr_file']
        index_text = f'{str.zfill(str(index + 1), count_width)} / {image_count}'
        try:
            scores[ts] = dict()
            exif_text = file_info['exif_text']
            image_gray = cv2.imread(hr_file, cv2.IMREAD_GRAYSCALE)
            score = function(image_gray, **params)
            normalized_score = metric_score_normalize(score, metric_conf)
            scores[ts][metric_name] = (score, normalized_score)
            logger.debug(f"[{index_text}]: [{ts}] [{exif_text}] {metric_name}_score: {score:.2f} normalized : {normalized_score:.2f}")
        except Exception as e:
            logger.error(f"[{index_text}]: [{ts}] [{exif_text}] error while calculating {metric_name} score: {e}")
            scores[ts][metric_name] = 0.0
    return scores


def iqa_score_calc(file_dict, metric_conf):
    """
    通用图像质量评估分数计算函数    
    参数:
        file_dict: 预处理好的文件信息字典
        metric_conf: 评估指标配置    
    返回:
        包含各时间戳分数的字典
    """
    metric_class = metric_conf['metric_class'].upper()
    if metric_conf['metric_type'] == 'folder':
        return dir_score_calc(file_dict, metric_conf)

    if metric_conf['metric_type'] == 'math':
        return image_math_metric_calc(file_dict, metric_conf)

    metric_name = metric_conf['metric_name']
    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    metric = pyiqa.create_metric(metric_name, device=device)
    # 如果用户配置和指定了权重文件就加载
    if 'weights_path' in metric_conf and metric_conf['weights_path']:
        metric.load_weights(metric_conf['weights_path'], weight_keys='params')
    scores = dict()
    image_count = file_dict['image_count']
    count_width = file_dict['count_width']

    for index, file_info in enumerate(file_dict['file_info']):
        ts = file_info['timestamp']
        lr_file = file_info['lr_file'] if metric_class == 'FR' else None
        hr_file = file_info['hr_file']
        index_text = f'{str.zfill(str(index + 1), count_width)} / {image_count}'

        try:
            scores[ts] = dict()

            exif_text = file_info['exif_text']
            score = metric(hr_file).item() if metric_class == 'NR' else metric(hr_file, lr_file).item()
            normalized_score = metric_score_normalize(score, metric_conf)
            scores[ts][metric_name] = (score, normalized_score)
            logger.debug(f"[{index_text}]: [{ts}] [{exif_text}] {metric_name}_score: {score:.2f} normalized : {normalized_score:.2f}")
        except Exception as e:
            logger.error(f"[{index_text}]: [{ts}] [{exif_text}] error while calculating {metric_name} score: {e}")
            scores[ts][metric_name] = 0.0

    return scores


def dir_score_calc(file_dict, metric_conf):
    """
    按照文件夹进行图像质量评估分数计算
    参数:
        file_dict: 预处理好的文件信息字典
        metric_conf: 评估指标配置
    返回:
        包含各时间戳分数的字典
    """
    metric_name = metric_conf['metric_name']
    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    metric = pyiqa.create_metric(metric_name, device=device)
    # 如果用户配置和指定了权重文件就加载
    if 'weights_path' in metric_conf and metric_conf['weights_path']:
        metric.load_weights(metric_conf['weights_path'], weight_keys='params')

    scores = dict()
    image_count = file_dict['image_count']
    count_width = file_dict['count_width']

    for index, file_info in enumerate(file_dict['file_info']):
        ts = file_info['timestamp']
        index_text = f'{str.zfill(str(index + 1), count_width)} / {image_count}'
        lr_file = file_info['lr_file']
        hr_file = file_info['hr_file']

        try:
            scores[ts] = dict()
            exif_text = file_info['exif_text']
            temp_hr_dir = osp.join(hr_dir, osp.basename(hr_file))
            temp_hr_dir = osp.splitext(temp_hr_dir)[0]
            if osp.exists(temp_hr_dir):
                shutil.rmtree(temp_hr_dir)
            os.makedirs(temp_hr_dir, exist_ok=True)
            # 最少 2 张才能正常运行
            shutil.copy(hr_file, osp.join(temp_hr_dir, '1.jpg'))
            shutil.copy(hr_file, osp.join(temp_hr_dir, '2.jpg'))

            temp_lr_dir = osp.join(lr_dir, osp.basename(lr_file))
            temp_lr_dir = osp.splitext(temp_lr_dir)[0]
            if osp.exists(temp_lr_dir):
                shutil.rmtree(temp_lr_dir)
            os.makedirs(temp_lr_dir, exist_ok=True)
            # 最少 2 张才能正常运行
            shutil.copy(lr_file, osp.join(temp_lr_dir, '1.jpg'))
            shutil.copy(lr_file, osp.join(temp_lr_dir, '2.jpg'))

            score = metric(temp_hr_dir, temp_lr_dir).item()
            score = 0.0 if score < 0.1 else score
            normalized_score = metric_score_normalize(score, metric_conf)
            scores[ts][metric_name] = (score, normalized_score)
            logger.debug(f"[{index_text}]: [{ts}] [{exif_text}] {metric_name}_score: {score:.2f} normalized : {normalized_score:.2f}")
            shutil.rmtree(temp_hr_dir)
            shutil.rmtree(temp_lr_dir)
        except Exception as e:
            logger.error(f"[{index_text}]: [{ts}] [{exif_text}] error while calculating {metric_name} score: {e}")
            scores[ts][metric_name] = 0.0

    return scores


def evaluate_main(lr_dir, hr_dir, ts_begin_index=0, log_dir=''):
    """
    图像综合质量评估分数计算过程    
    参数:
        lr_dir: 输入图片目录
        hr_dir: 输出图片目录
        ts_begin_index: 时间戳在文件中出现的位置, 所有文件名格式必须统一, 例如: 20251021_142422_iso100_20.0X_IP17Pro_HR.jpg
    """
    if log_dir:
        now = dt.now().strftime('%Y%m%d_%H%M%S')
        log_file = osp.join(log_dir, f'iqa_evaluation_{now}.log')
        file_handler = logging.FileHandler(log_file)
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

    enabled_metrics = get_enabled_metrics()
    enabled_count = len(enabled_metrics)
    if enabled_count == 0:
        logger.error(f"no metric is enabled. please check the [current_enabled] key in metric.conf")
        return

    # 预处理, 剔除所有不符合过滤条件的数据, 返回一个字典
    file_dict = file_pre_handle(hr_dir, lr_dir, ts_begin_index)

    # 计算函数里, scores 在字典各个深度上一定要给个初始默认值, 否则后续需要做额外判断
    composite_scores = dict()

    for metric in enabled_metrics:
        scores = dict()
        metric_class = metric['metric_class'].upper()
        metric_name = metric['metric_name']
        better_mark = 'lower is better' if metric['lower_better'] else 'higher is better'

        logger.info(f"=========================== begin to evaluate image by {metric_class}-metric [{metric_name.upper()} ({better_mark})] ===========================")
        if metric_class in ['NR', 'FR']:
            scores = {**iqa_score_calc(file_dict, metric), **scores}
        else:
            logger.error(f"unsupported metric class [{metric_class}] of metric [{metric_name}]")
            continue

        for file_info in file_dict['file_info']:
            ts = file_info['timestamp']
            if ts not in scores:
                scores[ts] = dict()
            if ts not in composite_scores:
                composite_scores[ts] = dict()
            if metric_name not in scores[ts]:
                scores[ts][metric_name] = (0.0, 0.0)

            composite_scores[ts] = {**composite_scores[ts], **scores[ts]}

    logger.info(f"=========================== begin to calculate composite scores ===========================")
    image_count = file_dict['image_count']
    count_width = file_dict['count_width']
    for index, file_info in enumerate(file_dict['file_info']):
        ts = file_info['timestamp']
        if ts not in composite_scores:
            continue
        exif_text = file_info['exif_text']
        index_text = f'{str.zfill(str(index + 1), count_width)} / {image_count}'
        composite_score, file_error, aigc_fail = 0.0, False, False
        for metric_name, (score, normalized_score) in composite_scores[ts].items():
            metric = [m for m in mc.metrics if m['metric_name'] == metric_name][0]
            score_weight = metric['score_weight']
            if score == 0.0:
                if metric['metric_type'] == 'folder':
                    aigc_fail = True
                else:
                    file_error = True
                break
            composite_score += normalized_score * score_weight

        if aigc_fail:
            logger.warning(f"[{index_text}]: [{ts}] [{exif_text}] aigc may not effected, hr is similar to lr")
            continue
        if file_error:
            logger.warning(f"[{index_text}]: [{ts}] [{exif_text}] file error, no permission or mis-rotated")
            continue
        logger.info(f"[{index_text}]: [{ts}] [{exif_text}] composite score: {composite_score:.2f}")


if __name__ == '__main__':
    """
    验证过的指标请参考 README.md, 启用哪些指标来评估, 通过 metric_conf.py 的 current_enabled 来配置
    参数说明:
        lr_dir: 输入/原始图片目录
        hr_dir: 超分后的图片目录
        ts_begin_index: 以下划线分割, 时间戳在文件中出现的位置, 所有文件名格式必须统一, 例如: 1001_20251021_142422_iso100_20.0X_Pixel10Pro_HR.jpg
    """

    lr_dir = r'D:\images\temp\test_LR'
    hr_dir = r'D:\images\temp\test_HR'
    evaluate_main(lr_dir=lr_dir, hr_dir=hr_dir, ts_begin_index=1, log_dir=r'D:\images\temp')

附一: 经过实测的部分指标(一知半解,出错或不适用勿怪)

全部指标参见官方文档 METRICS

全参考指标名 提出时间 值的大小说明 评估结果说明 结果值范围
sfid 2024 低分代表高质量 准确性较好,主要用于生成模型(如 GAN)的评估,其核心是‌统计学方法 0~100
fid 2024 低分代表高质量 准确性较好,主要用于生成模型(如 GAN)的评估,其核心是‌统计学方法 0~100
lpips 2018 低分代表高质量 神经网络类指标,一张 4k 图使用 CPU 约需要 3s 0~1
lpips+ 2020 低分代表高质量 神经网络类指标,准确性远胜 lpips,一张 4k 图使用 CPU 约需要 3s,FR 首选 0~1
stlpips 2020 低分代表高质量 神经网络类指标,准确性较好,一张 4k 图使用 CPU 约需要 10~15s 0~1
lpips-vgg 2018 低分代表高质量 神经网络类指标,32G 内存的 Windows11 PC 无法支撑其内存需求 0~1
lpips-vgg+ 2023 低分代表高质量 神经网络类指标,32G 内存的 Windows11 PC 无法支撑其内存需求 0~1
stlpips-vgg 2020 低分代表高质量 神经网络类指标,32G 内存的 Windows11 PC 无法支撑其内存需求 0~1
nlpd 2006 低分代表高质量 一张 4k 图使用 CPU 约需要 2s 0~1
gmsd 2014 低分代表高质量 梯度幅度相似性偏差 0~1
dists 2020 低分代表高质量 神经网络类指标,图像深度相似性,计算所需的空闲内存至少 20GB 0~1
psnr 2002 高分代表高质量 传统峰值信噪比指标,准确性可信但对于 AIGC 来说参考意义不大 0~100
ssim 2004 高分代表高质量 结构相似性,计算所需的空闲内存至少 20GB 0~1
ms_ssim 2003 高分代表高质量 多尺度结构相似性,计算所需的空闲内存至少 12GB 0~1
cw_ssim 2010 高分代表高质量 带权重的结构相似性,计算所需的空闲内存至少 20GB 0~1
fsim 2011 高分代表高质量 特征相似性,一张 4k 图使用 CPU 约需要 2~5s,准确性一般,对于 AIGC 来说参考意义不大 0~1
ahiq 2014 高分代表高质量 神经网络类指标,一张 4k 图使用 CPU 约需要 110~120s 0~1
wadiqam_fr 2018 高分代表高质量 神经网络类指标,加权平均深度图像质量度量,现有模型评估结果为负数,暂不采用 -1~1
无参考指标名 提出时间 值的大小说明 评估结果说明 结果值范围
qualiclip 2025 高分代表高质量 神经网络类指标,清晰度评估相对准确,一张 4k 图使用 CPU 约需要 8~10s,NR 首选 0~1
dbcnn 2019 高分代表高质量 神经网络类指标,清晰度评估相对准确,一张 4k 图使用 CPU 约需要 15~30s 0~1
niqe 2012 低分代表高质量 清晰度评估相对准确,matlab 模型,一张 4k 图使用 CPU 约需要 5s 0~10+
niqe_matlab 2012 低分代表高质量 清晰度评估相对准确,matlab 模型,一张 4k 图使用 CPU 约需要 5s 0~10+
cnniqa 2014 高分代表高质量 神经网络类指标,清晰度评估相对准确,一张 4k 图使用 CPU 约需要 3s 0~1
musiq 2021 高分代表高质量 神经网络类指标,清晰度评估相对准确,一张 4k 图使用 CPU 约需要 70~90s 0~1
ilniqe 2015 低分代表高质量 matlab 模型,准确性一般 0~20
hyperiqa 2020 高分代表高质量 神经网络类指标,现有模型区分度一般,在‌预测准确性‌上领先,适合高精度评估,计算开销大 0~100
nima 2018 高分代表高质量 神经网络类指标,现有模型区分度一般,在‌美学感知‌上最贴近人类偏好,但对技术失真不敏感 0~10
piqe 2015 低分代表高质量 现有模型区分度较差 0~100
arniqa 2023 高分代表高质量 神经网络类指标,现有模型区分度较差 0~1
brisque 2012 低分代表高质量 神经网络类指标,现有模型区分度较差 0~100
pi 2018 低分代表高质量 不可用 0~100
maniqa 2022 高分代表高质量 神经网络类指标,不可用 0~100
nrqm 2016 高分代表高质量 matlab 模型,不可用,没有任何输出 0~1
clipiqa 2023 高分代表高质量 神经网络类指标,准确性尚可,但区分度不高,一张 4k 图使用 CPU 约需要 8s 0~1
maclip 2024 高分代表高质量 神经网络类指标,截至 pyiqa-0.1.14.1,尚未实现 0~1
liqe 2023 高分代表高质量 准确性尚可,一张 4k 图使用 CPU 约需要 1s 0~1
paq2piq 2020 高分代表高质量 神经网络类指标,偏向人类主观感知质量,对真实世界复杂失真(如手机拍摄)具有强泛化能力 0~1
topiq_nr 2023 高分代表高质量 神经网络类指标,偏向衡量图像质量对下游视觉任务性能的影响,不适用 0~1
tres 2023 高分代表高质量 旨在衡量图像质量对下游视觉任务性能的影响,而非人类主观感知,不适用 0~1

附二: metric_conf.py

# coding=utf-8

import cv2
from math import log2
import numpy as np

"""
    'metric_name': 指标名称
    'metric_class': 分为FR-全参考评估、NR-无参考评估
    'current_enabled': 当前项目质量评估是否启用, 其实可以将metrics拆分成启用和未启用的两个, 更方便调试
    'weights_path': 用户自己指定或者训练的权重文件, 不指定会按照官方默认的文件加载
    'metric_type': 评估指标类型, 分为指定文件 (绝大多数指标), 或者指定目录 (如fid、sfid等), 或纯数学计算 (也是指定文件)
    'math_calc_func': 自定义数学指标的计算实现函数, 仅在 metric_type 为math时有效
    'math_calc_params': 自定义数学指标的计算实现函数的参数, 仅在 metric_type 为math时有效
    'score_weight': 得分在最终加权计算中的权重
    'can_be_used': 该指标是否经过验证可用, 有些指标实现上有问题, 有些网络环境不支持, 有些准确性太差
    'lower_better': 是否低分代表高质量
    'value_range': 取值范围, 闭区间
    'created_at': 指标提出/创建年份
    'description': 该指标的一些描述, 建议补全其作用和测试验证的效果信息
"""


def image_entropy_calc(image_gray):
    hist, bins = np.histogram(image_gray.flatten(), 256, [0, 256])
    px = hist / float(image_gray.shape[0] * image_gray.shape[1])
    score = -np.sum([px[i] * log2(px[i] + 1e-10) for i in range(256)])
    return score


def image_detail_calc(image_gray, sobel_ksize=3):
    # 根据Sobel算子调整分数, 缩小到一定范围(200以下, 通常高倍长焦会在100以内)
    sobel_ratios = {1: 4, 3: 1, 5: 0.07, 7: 0.005}
    sobelx = cv2.Sobel(image_gray, cv2.CV_64F, 1, 0, ksize=sobel_ksize)
    sobely = cv2.Sobel(image_gray, cv2.CV_64F, 0, 1, ksize=sobel_ksize)
    sobel = cv2.magnitude(sobelx, sobely)
    score = np.mean(np.abs(sobel)) * sobel_ratios[sobel_ksize]
    return score


metrics = [
    {
        'metric_name': 'detail',
        'metric_class': 'nr',
        'current_enabled': True,
        'weights_path': '',
        'metric_type': 'math',
        'math_calc_params': {'sobel_ksize': 3},
        'math_calc_func': image_detail_calc,
        'score_weight': 0.1,
        'can_be_used': True,
        'lower_better': False,
        'value_range': [0, 100],  # 人工固化
        'created_at': 1968,
        'description': '使用Sobel算子灰度图的梯度幅值, 然后得出梯度幅值的平均值, 平均梯度越大, 表示图像边缘越锐利、细节越丰富, 清晰度越高'
    },
    {
        'metric_name': 'entropy',
        'metric_class': 'nr',
        'current_enabled': True,
        'weights_path': '',
        'metric_type': 'math',
        'math_calc_params': {},
        'math_calc_func': image_entropy_calc,
        'score_weight': 0.1,
        'can_be_used': True,
        'lower_better': False,
        'value_range': [0, 8],
        'created_at': 1948,
        'description': '熵是指图像的平均信息量,它从信息论的角度衡量图像中信息的多少,图像中的信息熵越大,说明图像包含的信息越多,适用于同构图对比'
    },
    {
        'metric_name': 'qualiclip',
        'metric_class': 'nr',
        'current_enabled': True,
        'weights_path': '',
        'metric_type': 'file',
        'score_weight': 0.2,
        'can_be_used': True,
        'lower_better': False,
        'value_range': [0, 1],
        'created_at': 2025,
        'description': '神经网络类指标, 准确性好, 使用CPU一张4k图约需要8~10s, NR首选'
    },
    {
        'metric_name': 'sfid',
        'metric_class': 'fr',
        'current_enabled': True,
        'weights_path': '',
        'metric_type': 'folder',
        'score_weight': 0.4,
        'can_be_used': True,
        'lower_better': True,
        'value_range': [0, 100],
        'created_at': 2024,
        'description': '准确性较好, 主要用于生成模型 (如GAN) 的评估,衡量生成图像与真实图像在‌特征空间分布‌上的相似性,其核心是‌统计学方法'
    },
    {
        'metric_name': 'lpips+',
        'metric_class': 'fr',
        'current_enabled': True,
        'weights_path': '',
        'metric_type': 'file',
        'score_weight': 0.2,
        'can_be_used': True,
        'lower_better': True,
        'value_range': [0, 1],
        'created_at': 2020,
        'description': '神经网络类指标, 准确性较好, 使用CPU一张4k图约需要2~3s, FR首选'
    }
]


↙↙↙阅读原文可查看相关链接,并与作者交流