4. 坏点检测与校正

任务目标

开发坏点检测与校正算法,识别图像传感器中的固定坏点(包括死点与热噪点),并通过邻域插值完成校正,消除图像中的固定噪点伪影。

核心原理

坏点表现为与邻域灰度差异极大的固定位置像素。检测策略:对多帧静态场景图像取中值或均值作为基准帧,计算每个像素与邻域中值的偏差,超过自适应阈值则判定为坏点。校正采用3×3邻域中值替换,保留边缘细节。

完整实现

import numpy as np
from scipy import ndimage

def detect_bad_pixels(frames, k=3, threshold_ratio=3.0):
    """
    基于多帧静态场景检测坏点
    frames: list/np.array, 形状 (N, H, W) 的灰度帧序列
    k: 邻域窗口大小(奇数)
    threshold_ratio: 偏差阈值倍数(相对于邻域标准差)
    返回: 坏点掩码 (H, W),True表示坏点
    """
    frames = np.asarray(frames, dtype=np.float32)
    N, H, W = frames.shape

    # 1. 多帧中值合成基准帧(抑制随机噪声)
    base = np.median(frames, axis=0)

    # 2. 邻域中值与标准差(排除中心像素)
    footprint = np.ones((k, k), dtype=bool)
    footprint[k//2, k//2] = False

    neigh_median = ndimage.median_filter(base, size=k, mode='nearest')
    # 邻域标准差:用每个像素与邻域中值的偏差的局部中值近似(鲁棒)
    abs_dev = np.abs(base - neigh_median)
    local_std = ndimage.median_filter(abs_dev, size=k, mode='nearest') * 1.4826

    # 3. 自适应阈值判定
    threshold = threshold_ratio * local_std + 1e-6
    mask = abs_dev > threshold

    # 4. 边缘保护:排除图像边界2像素(邻域不完整)
    border = k // 2
    mask[:border, :] = False
    mask[-border:, :] = False
    mask[:, :border] = False
    mask[:, -border:] = False

    return mask

def correct_bad_pixels(image, bad_mask, k=3):
    """
    对坏点进行邻域中值校正
    image: (H, W) 灰度图
    bad_mask: (H, W) bool掩码
    返回: 校正后图像
    """
    image = np.asarray(image, dtype=np.float32)
    corrected = image.copy()

    # 仅对坏点位置计算邻域中值(排除坏点自身)
    footprint = np.ones((k, k), dtype=bool)
    footprint[k//2, k//2] = False

    # 计算全图邻域中值(不含中心)
    neigh_median = ndimage.median_filter(
        image, size=k, footprint=footprint, mode='nearest'
    )

    # 替换坏点
    corrected[bad_mask] = neigh_median[bad_mask]
    return corrected

# ========== 最小验证闭环 ==========
if __name__ == "__main__":
    # 生成模拟数据:100x100 灰度图,含5个坏点
    H, W = 100, 100
    rng = np.random.default_rng(42)
    clean = rng.integers(50, 200, size=(H, W), dtype=np.uint8).astype(np.float32)

    # 注入坏点(固定位置)
    bad_positions = [(20, 30), (45, 67), (80, 15), (10, 90), (60, 50)]
    for y, x in bad_positions:
        clean[y, x] = 255.0  # 死点(过亮)

    # 生成8帧静态场景(含轻微随机噪声)
    frames = []
    for _ in range(8):
        noisy = clean + rng.normal(0, 2.0, size=(H, W))
        frames.append(np.clip(noisy, 0, 255))

    # 检测
    mask = detect_bad_pixels(frames, k=3, threshold_ratio=3.0)

    # 校正
    corrected = correct_bad_pixels(clean, mask)

    # 验证:检测准确率
    true_mask = np.zeros((H, W), dtype=bool)
    for y, x in bad_positions:
        true_mask[y, x] = True

    tp = np.sum(mask & true_mask)
    fp = np.sum(mask & ~true_mask)
    fn = np.sum(~mask & true_mask)

    print(f"检测到坏点: {np.sum(mask)} 个")
    print(f"真阳性(正确检出): {tp} / {len(bad_positions)}")
    print(f"误检: {fp} 个")
    print(f"漏检: {fn} 个")

    # 校正效果:坏点位置校正前后对比
    print("\n坏点校正值对比(原值 -> 校正值):")
    for y, x in bad_positions:
        print(f"  ({y},{x}): {clean[y,x]:.1f} -> {corrected[y,x]:.1f}")

运行方式

pip install numpy scipy
python bad_pixel_correction.py

验证方法与通过标准

  • 检测准确率:程序输出中“真阳性”应等于5(全部检出),误检与漏检为0或极小值(受噪声影响可能有个别误检,但应远小于正确检出数)。
  • 校正效果:坏点位置校正值应接近周围邻域灰度(约50~200区间),不再保持255的异常值。
  • 视觉确认:将校正前后图像并排显示或保存,坏点位置不再出现固定亮点。

关键参数说明

  • threshold_ratio=3.0:偏差超过邻域中值偏差的3倍(对应约3σ)判为坏点。若误检多可调至3.5~4.0;若漏检多可调至2.0~2.5。
  • frames数量建议≥5帧,帧间噪声独立时中值合成可有效抑制随机噪声,避免将噪声误判为坏点。
  • 实际相机场景中,应拍摄同一静态物体(如均匀光照下的平坦表面)的多帧图像作为输入。