图像质量评估数据集构建实战:从 0 到 1 生成 5 种失真图像与主观评分

图像质量评估数据集构建实战:从 0 到 1 生成 5 种失真图像与主观评分

图像质量评估数据集构建实战:从失真生成到主观评分全流程指南

1. 引言:为什么需要自建IQA数据集?

在计算机视觉领域,图像质量评估(IQA)扮演着至关重要的角色。无论是优化图像采集设备、测试压缩算法,还是评估图像增强效果,我们都需要可靠的图像质量评价体系。虽然学术界已提供了多个标准数据集(如LIVE、TID2013等),但在实际项目中,这些数据集往往存在以下局限:

  • 失真类型单一:大多数公开数据集仅包含JPEG压缩、高斯模糊等基础失真,难以覆盖真实场景中的复杂退化
  • 评分标准固化:主观评分受文化背景、观察条件等因素影响,预收集的MOS/DMOS可能不符合特定需求
  • 领域适配性差:医疗、遥感等专业领域的图像质量评估需要针对性数据

本教程将手把手带您完成从原始图像采集、失真模拟到主观实验设计的全流程,构建符合研究需求的定制化IQA数据集。我们将重点实现:

  1. 使用Python代码生成5种可控失真图像
  2. 设计科学的主观评分实验方案
  3. 构建包含客观指标与主观评价的完整数据集

2. 失真图像生成实战

2.1 环境配置与基础准备

首先确保已安装以下Python库:

pip install opencv-python numpy scikit-image pillow matplotlib

准备一组高质量原始图像(建议10-30张),尺寸统一为512×512像素。创建项目目录结构:

/project_root │── /src_images # 存放原始图像 │── /distorted_images # 输出失真图像 │── utils.py # 失真生成工具函数 │── config.py # 参数配置文件

2.2 五种核心失真类型实现

2.2.1 JPEG压缩失真
import cv2 import numpy as np def add_jpeg_compression(img, quality=70): """ 生成JPEG压缩失真 :param img: 输入图像(numpy数组) :param quality: 压缩质量(0-100) :return: 失真后的图像 """ encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality] _, encimg = cv2.imencode('.jpg', img, encode_param) distorted = cv2.imdecode(encimg, 1) return distorted

关键参数控制

  • quality=30/50/70/90:对应4个失真等级
  • 典型表现:产生块效应和振铃伪影
2.2.2 高斯模糊失真
from skimage import filters def add_gaussian_blur(img, sigma=1.5): """ 生成高斯模糊失真 :param img: 输入图像 :param sigma: 高斯核标准差 :return: 失真后的图像 """ return filters.gaussian(img, sigma=sigma, multichannel=True)

参数建议

  • sigma=0.5/1.0/1.5/2.0:模糊程度递增
  • 注意处理RGB三通道图像时的multichannel参数
2.2.3 加性高斯白噪声
def add_white_noise(img, sigma=0.05): """ 添加高斯白噪声 :param img: 归一化到[0,1]范围的图像 :param sigma: 噪声标准差 :return: 含噪图像 """ noise = np.random.normal(0, sigma, img.shape) distorted = img + noise return np.clip(distorted, 0, 1)

噪声水平控制

  • sigma=0.02/0.05/0.08/0.11
  • 注意先对图像进行归一化处理
2.2.4 运动模糊模拟
def add_motion_blur(img, size=15, angle=45): """ 模拟运动模糊效果 :param img: 输入图像 :param size: 核大小 :param angle: 运动角度(0-180) :return: 模糊后的图像 """ kernel = np.zeros((size, size)) kernel[size//2, :] = np.ones(size) kernel = kernel / size # 归一化 # 旋转核 rotation_matrix = cv2.getRotationMatrix2D((size/2-0.5, size/2-0.5), angle, 1) kernel = cv2.warpAffine(kernel, rotation_matrix, (size, size)) distorted = cv2.filter2D(img, -1, kernel) return distorted

运动参数组合

核大小角度(°)视觉效果描述
1130轻微斜向模糊
1545中度对角线模糊
2160显著运动痕迹
2.2.5 色度失真模拟
def add_chromatic_aberr(img, shift=2): """ 模拟色差效果 :param img: 输入图像 :param shift: 像素偏移量 :return: 失真图像 """ b, g, r = cv2.split(img) # 对红色和蓝色通道进行位移 M = np.float32([[1, 0, shift], [0, 1, shift]]) r_shifted = cv2.warpAffine(r, M, (r.shape[1], r.shape[0])) M = np.float32([[1, 0, -shift], [0, 1, -shift]]) b_shifted = cv2.warpAffine(b, M, (b.shape[1], b.shape[0])) distorted = cv2.merge([b_shifted, g, r_shifted]) return distorted

2.3 批量生成与元数据管理

创建控制脚本自动生成多级失真:

# config.py DISTORTION_TYPES = { 'jpeg': [30, 50, 70, 90], 'gaussian_blur': [0.5, 1.0, 1.5, 2.0], 'white_noise': [0.02, 0.05, 0.08, 0.11], 'motion_blur': [(11,30), (15,45), (21,60)], 'chromatic': [1, 2, 3] } # generate_distortions.py from utils import * from config import DISTORTION_TYPES import os def generate_all_distortions(src_dir, output_dir): if not os.path.exists(output_dir): os.makedirs(output_dir) metadata = [] for img_name in os.listdir(src_dir): img_path = os.path.join(src_dir, img_name) img = cv2.imread(img_path) img_id = os.path.splitext(img_name)[0] # 处理每种失真类型 for dist_type, levels in DISTORTION_TYPES.items(): for level_idx, level in enumerate(levels): if dist_type == 'jpeg': distorted = add_jpeg_compression(img, quality=level) elif dist_type == 'gaussian_blur': distorted = add_gaussian_blur(img, sigma=level) # 其他失真类型处理... # 保存图像 save_name = f"{img_id}_{dist_type}_l{level_idx}.png" save_path = os.path.join(output_dir, save_name) cv2.imwrite(save_path, distorted) # 记录元数据 metadata.append({ 'src_img': img_id, 'dist_type': dist_type, 'level': level_idx, 'level_value': str(level), 'file_path': save_name }) # 保存元数据CSV pd.DataFrame(metadata).to_csv(os.path.join(output_dir, 'metadata.csv'), index=False)

3. 主观评分实验设计

3.1 主流主观评价方法对比

方法名称评价方式适用场景优缺点分析
单刺激连续质量分级法单独显示图像,直接评分(1-5分)快速大规模评测简单但易受主观偏差影响
双刺激损伤分级法并排显示原始/失真图像,比较评分实验室精确评测结果可靠但耗时
双刺激连续质量分级法顺序显示图像对,分别评分后计算DMOS学术研究平衡效率与准确性

3.2 双刺激连续质量分级法实现方案

实验设计要点

  1. 显示设置

    • 使用校准过的显示器(建议sRGB色域,亮度120cd/m²)
    • 观察距离为屏幕高度的3-4倍
    • 环境光照约300lux
  2. 实验流程

    graph TD A[欢迎界面] --> B[练习阶段] B --> C[正式实验] C --> D[问卷调查] D --> E[结束]
  3. 评分界面设计建议

    # 使用PsychoPy构建的简化示例 from psychopy import visual, core, event def run_subjective_test(image_pairs): win = visual.Window(size=(1920, 1080), color='gray') rating_scale = visual.RatingScale(win, low=1, high=5, labels=['Bad', 'Poor', 'Fair', 'Good', 'Excellent']) for pair in image_pairs: # 显示参考图像 ref_stim = visual.ImageStim(win, image=pair['reference']) ref_stim.draw() win.flip() core.wait(1.0) # 显示失真图像 dist_stim = visual.ImageStim(win, image=pair['distorted']) dist_stim.draw() rating_scale.reset() while rating_scale.noResponse: dist_stim.draw() rating_scale.draw() win.flip() pair['score'] = rating_scale.getRating() win.close() return image_pairs

3.3 参与者筛选与数据清洗

参与者筛选标准

  1. 视力或矫正视力正常(Snellen视力表20/30以上)
  2. 通过色觉测试(如Ishihara色盲测试)
  3. 完成培训且练习阶段评分与预设标准相关系数>0.7

异常评分检测方法

def detect_outlier_scores(scores_df): """ 检测异常评分 :param scores_df: 包含所有评分的DataFrame :return: 异常评分索引列表 """ # 计算每个参与者的评分分布 user_stats = scores_df.groupby('user_id')['score'].agg(['mean', 'std']) outliers = [] for idx, row in scores_df.iterrows(): user_mean = user_stats.loc[row['user_id'], 'mean'] user_std = user_stats.loc[row['user_id'], 'std'] # 标记超过3个标准差的评分 if abs(row['score'] - user_mean) > 3 * user_std: outliers.append(idx) return outliers

4. 数据集验证与分析

4.1 客观指标验证

计算生成图像与参考图像的常用指标:

def calculate_metrics(ref_img, dist_img): """ 计算全参考图像质量指标 :return: 各指标值的字典 """ # PSNR mse = np.mean((ref_img - dist_img) ** 2) psnr = 10 * np.log10(255**2 / mse) # SSIM ssim = compare_ssim(ref_img, dist_img, multichannel=True) # VIF vif = vifp_mscale(ref_img, dist_img) return {'PSNR': psnr, 'SSIM': ssim, 'VIF': vif}

4.2 主观数据统计分析

DMOS计算方法

  1. 对每位观察者$i$和图像$j$,计算差分评分: $$ d_{i,j} = r_{i,ref(j)} - r_{i,j} $$

  2. 线性归一化到[0,100]范围: $$ d'{i,j} = \frac{d{i,j} - \min_i}{\max_i - \min_i} \times 100 $$

  3. 计算平均DMOS: $$ DMOS_j = \frac{1}{M} \sum_{i=1}^{M} d'_{i,j} $$

一致性分析

from scipy import stats def evaluate_consistency(scores): """ 计算评分一致性指标 :return: ICC, Cronbach's alpha """ # 转换为评分者×图像的矩阵 rating_matrix = scores.pivot(index='user_id', columns='image_id', values='score') # 计算组内相关系数(ICC) icc = ICC(rating_matrix.values) # 计算Cronbach's alpha n = rating_matrix.shape[1] item_var = rating_matrix.var(axis=0, ddof=1).sum() total_var = rating_matrix.values.var(ddof=1) alpha = n / (n-1) * (1 - item_var/total_var) return {'ICC': icc, 'Cronbach_alpha': alpha}

5. 高级技巧与扩展

5.1 基于深度学习的失真生成

使用GAN生成更真实的复合失真:

# 基于CycleGAN的复合失真生成 def train_cyclegan(real_images, distorted_images): # 初始化生成器和判别器 G_A2B = ResnetGenerator(input_nc=3, output_nc=3) G_B2A = ResnetGenerator(input_nc=3, output_nc=3) # 定义损失函数 criterion_GAN = torch.nn.MSELoss() criterion_cycle = torch.nn.L1Loss() # 训练循环 for epoch in range(num_epochs): for real_A in real_images: # 生成失真图像 fake_B = G_A2B(real_A) # 计算对抗损失 pred_fake = D_B(fake_B) loss_GAN = criterion_GAN(pred_fake, torch.ones_like(pred_fake)) # 计算循环一致性损失 recovered_A = G_B2A(fake_B) loss_cycle = criterion_cycle(recovered_A, real_A) # 总损失 loss_total = loss_GAN + 10 * loss_cycle loss_total.backward()

5.2 自适应失真强度控制

根据图像内容自动调整失真强度:

def adaptive_distortion(img, dist_type, target_metric): """ 自适应生成达到特定质量指标的失真图像 :param target_metric: 目标PSNR/SSIM值 :return: 优化后的失真图像 """ low = 0 high = 100 # 参数最大值 for _ in range(10): # 二分搜索迭代 mid = (low + high) / 2 distorted = apply_distortion(img, dist_type, mid) current_metric = calculate_metric(img, distorted) if current_metric < target_metric: high = mid else: low = mid return apply_distortion(img, dist_type, (low+high)/2)

6. 实际应用案例

6.1 手机摄像头评测数据集构建

特殊考虑因素

  • 模拟真实拍摄场景:低光照、运动模糊、色差等复合失真
  • 设计专用主观实验:
    • 显示设备:使用实际手机屏幕
    • 评分标准:重点关注纹理保持和色彩还原

6.2 医学图像质量评估

专业要求

  • 失真类型:模拟CT噪声、MR伪影等医学特有失真
  • 评分人员:需放射科医师参与
  • 评价标准:强调诊断信息保留度而非美观性

提示:构建专业领域数据集时,建议与领域专家深度合作,确保失真类型和评价标准符合实际需求