2. 去除模糊图像

任务说明

在AI训练数据集中,模糊图像会引入噪声标签,降低模型收敛速度与最终精度。本案例实现一个基于清晰度评分的图像筛选工具,使用拉普拉斯方差(Variance of Laplacian)与Tenengrad梯度两种方法计算图像清晰度,并依据阈值剔除模糊样本。

环境依赖

需要 Python 3.8+、OpenCV(opencv-python)、NumPy。安装命令:

pip install opencv-python numpy

完整实现

import cv2
import numpy as np
from pathlib import Path

def laplacian_variance(image_path):
    """拉普拉斯方差:值越小图像越模糊"""
    img = cv2.imread(str(image_path), cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")
    return cv2.Laplacian(img, cv2.CV_64F).var()

def tenengrad_score(image_path):
    """Tenengrad梯度:使用Sobel算子计算梯度幅值均值"""
    img = cv2.imread(str(image_path), cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")
    gx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
    gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
    magnitude = np.sqrt(gx**2 + gy**2)
    return magnitude.mean()

def filter_blurry_images(input_dir, output_dir, method="laplacian",
                         threshold=100.0, move=False):
    """
    筛选并剔除模糊图像。
    - method: "laplacian" 或 "tenengrad"
    - threshold: 清晰度阈值,低于该值判定为模糊
    - move: True 将模糊图移入 output_dir;False 仅打印结果
    """
    input_dir = Path(input_dir)
    output_dir = Path(output_dir)
    if not input_dir.exists():
        raise FileNotFoundError(f"输入目录不存在: {input_dir}")

    score_func = laplacian_variance if method == "laplacian" else tenengrad_score
    image_exts = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"}
    images = [p for p in input_dir.iterdir()
              if p.suffix.lower() in image_exts]

    if not images:
        print("输入目录中没有找到支持的图像文件")
        return

    if move:
        output_dir.mkdir(parents=True, exist_ok=True)

    blurry_count = 0
    for img_path in sorted(images):
        score = score_func(img_path)
        is_blurry = score < threshold
        status = "模糊" if is_blurry else "清晰"
        print(f"{img_path.name}: {method}得分={score:.2f} → {status}")

        if is_blurry and move:
            dest = output_dir / img_path.name
            img_path.rename(dest)  # 移动文件
            blurry_count += 1

    print(f"\n共检测 {len(images)} 张图像,判定模糊 {blurry_count} 张")

if __name__ == "__main__":
    # 示例调用:将 input_images 中拉普拉斯得分低于 100 的图像移入 blurry/
    filter_blurry_images(
        input_dir="input_images",
        output_dir="blurry",
        method="laplacian",
        threshold=100.0,
        move=True
    )

运行与验证

准备数据:创建 input_images/ 目录,放入若干清晰图像与明显模糊的图像(可用手机拍摄时抖动产生,或用高斯模糊处理清晰图生成)。

执行筛选:

python blur_detector.py

判定标准:

  1. 程序逐张输出每张图像的清晰度得分与“清晰/模糊”判定;
  2. 当 move=True 时,被判定为模糊的图像文件会从 input_images/ 移动到 blurry/ 目录;
  3. 肉眼检查 blurry/ 中的图像确实视觉模糊,而 input_images/ 中剩余图像视觉清晰,即说明筛选有效。

阈值调优建议:拉普拉斯方差通常以 100 为经验分界(低于100偏模糊),但受图像内容影响较大——纹理丰富的清晰图得分可能上千,而平滑背景为主的清晰图可能只有几十。建议先以 move=False 运行观察得分分布,再设定合理阈值。Tenengrad得分整体偏高,阈值需另行观察确定。

两种方法对比

清晰度评分方法对比 拉普拉斯方差 原理:对图像求拉普拉斯二阶导数, 计算结果的方差。边缘越锐利, 响应值波动越大,方差越高。 特点: • 计算快,单通道灰度即可 • 对噪声敏感(需先降噪) • 经验阈值:<100 偏模糊 Tenengrad梯度 原理:使用Sobel算子计算水平与 垂直方向的梯度幅值,取均值作为 清晰度指标。 特点: • 抗噪性优于拉普拉斯 • 能捕捉方向性边缘信息 • 得分范围大,阈值需实测 建议:先用两种方法分别输出得分分布,再结合人工抽检确定最终阈值与筛选方法

常见问题

  • 得分普遍偏低:若图像本身分辨率低或场景平滑(如天空、白墙),清晰图得分也可能低于阈值。此时应降低阈值或改用Tenengrad方法。
  • 读取失败:确认图像路径无中文或特殊字符,且OpenCV支持该格式。
  • 移动而非复制:代码使用 rename() 实现移动,若需保留原图请将 move 参数改为 False 并手动处理输出列表。