2. 黑电平校正与动态范围优化

任务目标

针对不同ISO下传感器黑电平(Black Level)偏移问题,设计自适应校正算法,在RAW域完成黑电平扣除,并通过统计验证校正前后动态范围的变化。

核心原理与实现思路

CMOS传感器在无光照时仍存在暗电流导致的非零输出,即黑电平。黑电平随ISO升高而增大,若固定扣除一个常数,高ISO下会残留偏置或过度扣除,压缩可用动态范围。本案例采用分段线性插值方法:标定若干ISO档位的黑电平值,运行时根据当前ISO实时插值得到校正量,逐像素扣除,并做饱和保护。

完整实现代码

import numpy as np

# ========== 1. 黑电平标定表(典型值,单位:DN/16bit) ==========
# 实际项目中由暗场拍摄统计得到
CALIB_ISO = np.array([100, 200, 400, 800, 1600, 3200])
CALIB_BLACK = np.array([64, 68, 76, 92, 124, 180])

# ========== 2. 自适应黑电平插值 ==========
def get_black_level(iso):
    """根据ISO线性插值得到黑电平值"""
    if iso <= CALIB_ISO[0]:
        return float(CALIB_BLACK[0])
    if iso >= CALIB_ISO[-1]:
        return float(CALIB_BLACK[-1])
    # 线性插值
    idx = np.searchsorted(CALIB_ISO, iso, side='right') - 1
    iso0, iso1 = CALIB_ISO[idx], CALIB_ISO[idx+1]
    bl0, bl1 = CALIB_BLACK[idx], CALIB_BLACK[idx+1]
    return float(bl0 + (bl1 - bl0) * (iso - iso0) / (iso1 - iso0))

# ========== 3. 黑电平校正 ==========
def black_level_correction(raw_img, iso, bit_depth=16):
    """
    raw_img: 2D numpy数组,RAW域数据
    iso: 当前传感器增益对应的ISO值
    bit_depth: RAW位深,默认16bit
    返回: 校正后的RAW图像(浮点型)
    """
    max_val = (1 << bit_depth) - 1
    bl = get_black_level(iso)
    # 逐像素扣除黑电平,并做饱和保护
    corrected = raw_img.astype(np.float32) - bl
    corrected = np.clip(corrected, 0, max_val)
    return corrected

# ========== 4. 动态范围统计验证 ==========
def calculate_dynamic_range(img, bit_depth=16, percentile=99.9):
    """
    用percentile分位点估计有效信号上限,噪声底用暗区标准差
    简化动态范围 = 20*log10(信号上限/噪声底)
    """
    max_val = (1 << bit_depth) - 1
    # 信号上限:取高百分位(避免单点过曝干扰)
    signal_high = np.percentile(img, percentile)
    # 噪声底:取图像底部1%像素的标准差(近似暗区噪声)
    low_mask = img < np.percentile(img, 1)
    if np.sum(low_mask) < 10:
        noise_floor = 1.0
    else:
        noise_floor = np.std(img[low_mask])
    noise_floor = max(noise_floor, 1e-6)
    dr_db = 20 * np.log10(signal_high / noise_floor)
    return dr_db, signal_high, noise_floor

# ========== 5. 模拟验证流程 ==========
def run_validation():
    """模拟不同ISO下的RAW数据,验证校正效果"""
    np.random.seed(42)
    H, W = 512, 512
    results = []
    
    for iso in [100, 400, 1600, 3200]:
        # 模拟RAW:真实信号 + 黑电平偏移 + 噪声
        true_signal = np.random.uniform(0, 20000, (H, W))  # 模拟真实光信号
        bl_true = get_black_level(iso)  # 真实黑电平
        # 噪声随ISO增大(简化模型)
        noise_std = 2.0 * (iso / 100) ** 0.7
        noise = np.random.normal(0, noise_std, (H, W))
        raw = true_signal + bl_true + noise
        raw = np.clip(raw, 0, 65535).astype(np.uint16)
        
        # 校正前(用ISO100的固定黑电平扣除,模拟错误校正)
        bl_fixed = CALIB_BLACK[0]
        before = raw.astype(np.float32) - bl_fixed
        before = np.clip(before, 0, 65535)
        
        # 校正后(自适应)
        after = black_level_correction(raw, iso)
        
        # 计算动态范围
        dr_before, _, _ = calculate_dynamic_range(before)
        dr_after, _, _ = calculate_dynamic_range(after)
        
        results.append({
            'iso': iso,
            'dr_before_db': dr_before,
            'dr_after_db': dr_after,
            'improvement_db': dr_after - dr_before
        })
    
    return results

if __name__ == '__main__':
    # 打印标定插值验证
    print(f"ISO 800 插值黑电平: {get_black_level(800):.1f} (标定值: {CALIB_BLACK[3]})")
    
    # 运行验证
    res = run_validation()
    print("\n=== 动态范围对比 (dB) ===")
    print(f"{'ISO':>6} | {'校正前':>10} | {'校正后':>10} | {'提升':>8}")
    print("-" * 42)
    for r in res:
        print(f"{r['iso']:>6} | {r['dr_before_db']:>10.2f} | "
              f"{r['dr_after_db']:>10.2f} | {r['improvement_db']:>+8.2f}")

运行与验证方法

  1. 环境依赖:Python 3.8+,NumPy库(pip install numpy)
  2. 运行方式:将代码保存为 black_level_correction.py,执行 python black_level_correction.py
  3. 预期输出(基于代码逻辑可唯一推出):
    • ISO 800插值结果应为92.0(与标定值一致,验证插值正确性)
    • 动态范围对比表中,高ISO下校正后动态范围应高于校正前(因固定黑电平扣除导致低光区域被过度扣除或残留偏置,压缩了有效信号范围)

判定标准

  • 插值函数在标定点输出与标定值完全一致(误差<0.01)
  • 校正后图像最小值不低于0(无过度扣除)
  • ISO≥400时,校正后动态范围比校正前提升至少1dB,证明自适应校正对高ISO动态范围有改善作用
说明:本案例使用模拟RAW数据验证算法逻辑。实际工程中,标定表需通过暗场拍摄(遮盖镜头)统计均值获得,且需针对传感器通道(R/Gr/Gb/B)分别标定。动态范围计算采用简化模型(百分位信号上限/暗区噪声底),实际评测可使用ISO 12233标准测试卡或脉冲光源法。