1. 基于中值滤波的ToF深度图去噪

基于中值滤波的ToF深度图去噪 学习卡片

任务说明

本案例处理ToF(Time-of-Flight)相机输出的原始深度图,目标是通过中值滤波去除因多路径反射、边缘混叠和低信噪比区域产生的孤立噪点,同时保留深度边缘的清晰度。输入为单通道16位深度图(单位:毫米),输出为滤波后的同尺寸深度图。有效像素值为正值,无效像素(测量失败)标记为0,滤波时需跳过无效像素。

核心实现

import numpy as np
from scipy import ndimage
import cv2

def median_filter_depth(depth_raw, kernel_size=5, max_valid_range=(0, 8000)):
    """
    对ToF深度图执行中值滤波,自动处理无效像素(值为0)。
    
    参数:
        depth_raw: np.uint16数组, 原始深度图 (H, W), 单位mm
        kernel_size: int, 滤波窗口边长(奇数)
        max_valid_range: tuple, 有效深度范围(最小, 最大), 超出视为无效
    
    返回:
        depth_filtered: np.uint16数组, 滤波后深度图
    """
    if kernel_size % 2 == 0:
        kernel_size += 1
    
    # 构建有效掩码: 深度值在有效范围内且非0
    min_val, max_val = max_valid_range
    valid_mask = (depth_raw >= min_val) & (depth_raw <= max_val) & (depth_raw > 0)
    
    # 将无效像素替换为极大值,使其在排序时被排除
    depth_work = depth_raw.astype(np.float32)
    depth_work[~valid_mask] = np.inf
    
    # 使用scipy的generic_filter实现中值滤波(忽略inf)
    from scipy.ndimage import generic_filter
    
    def _median_ignore_inf(window):
        valid_vals = window[~np.isinf(window)]
        if len(valid_vals) == 0:
            return 0.0
        return np.median(valid_vals)
    
    depth_filtered = generic_filter(
        depth_work, 
        _median_ignore_inf, 
        size=kernel_size, 
        mode='constant', 
        cval=np.inf
    )
    
    # 恢复原始无效像素位置为0
    depth_filtered[~valid_mask] = 0
    
    return depth_filtered.astype(np.uint16)

def generate_sample_depth_map():
    """生成带噪点的模拟ToF深度图 (H=8, W=8)"""
    # 真实深度平面: 左侧2000mm, 右侧3000mm, 中间垂直边缘
    depth = np.zeros((8, 8), dtype=np.uint16)
    depth[:, :4] = 2000
    depth[:, 4:] = 3000
    
    # 添加孤立噪点(模拟多路径/飞点)
    noise_positions = [(1, 1), (3, 6), (5, 2), (6, 5), (2, 7)]
    for r, c in noise_positions:
        depth[r, c] = 500  # 明显偏离真实值的噪点
    
    # 添加一个无效像素
    depth[4, 4] = 0
    
    return depth

if __name__ == "__main__":
    # 生成样例数据
    sample = generate_sample_depth_map()
    print("原始深度图:\n", sample)
    
    # 执行中值滤波
    filtered = median_filter_depth(sample, kernel_size=3)
    print("\n滤波后深度图:\n", filtered)
    
    # 验证: 检查噪点是否被消除
    noise_positions = [(1, 1), (3, 6), (5, 2), (6, 5), (2, 7)]
    all_removed = all(filtered[r, c] in (2000, 3000) for r, c in noise_positions)
    invalid_preserved = (filtered[4, 4] == 0)
    
    print(f"\n噪点全部消除: {all_removed}")
    print(f"无效像素保持为0: {invalid_preserved}")
    print(f"边缘保留(第4列仍为2000, 第5列仍为3000): "
          f"{np.all(filtered[:, 3] == 2000) and np.all(filtered[:, 4] == 3000)}")

关键设计说明

上述实现的核心在于无效像素感知的中值计算:将无效像素(值为0或超出有效范围)替换为无穷大,在滑动窗口内计算中值时忽略这些无效值,仅对有效深度值排序取中。若窗口内全部为无效像素,则输出0。这种设计避免了无效像素对中值结果的污染,同时保留了深度边缘——因为中值滤波本身对边缘附近的异常值具有天然鲁棒性。

落地与运行方式

# 安装依赖
pip install numpy scipy opencv-python

# 运行案例
python median_filter_tof.py

验证方法与判定标准

运行上述脚本后,观察输出:

  • 原始深度图中5个噪点位置(深度值500)在滤波后应变为所在区域的真实深度(2000或3000),即噪点被消除;
  • 无效像素位置(4,4)滤波后仍为0,未被周围有效值填充;
  • 深度边缘(第3列与第4列之间)保持清晰,左侧2000mm与右侧3000mm区域内部无串扰。

满足以上三点即判定滤波算法实现正确。若需处理实际ToF相机数据,将depth_raw替换为相机输出的uint16深度图即可,无需修改核心函数。

中值滤波处理流程与验证观测点 输入深度图 (8×8) ■ 2000mm ■ 3000mm 噪点(500mm) 无效像素(0) 3×3中值滤波核 中心 ∞ ∞ 2000 2000 3000 2000 3000 3000 排序: [2000,2000,2000,2000, 3000,3000,3000,3000] 中值 = (2000+3000)/2 = 2500 输出深度图 (8×8) ✓ ✓ ✓ ✓ ✓ ✓ 噪点已消除 ■ 无效像素保持0 边缘清晰 验证观测点 ① 噪点位置(500mm) → 滤波后变为2000或3000 ② 无效像素(0) → 滤波后仍为0 ③ 边缘(第3/4列间) → 左侧2000/右侧3000无串扰