2. 镜头阴影校正

任务目标

实现镜头阴影校正(Lens Shading Correction, LSC),对因镜头光学特性导致的径向亮度衰减进行补偿,使图像边缘亮度与中心区域趋于一致。输入为单张RAW或RGB图像及对应的阴影校正增益表,输出为校正后的图像。

核心原理

镜头阴影表现为图像亮度从中心到边缘逐渐降低,近似服从径向衰减模型。校正方法为逐像素乘以与半径相关的增益系数:

out(x, y) = in(x, y) × gain(r)

其中 r 为像素到图像中心的归一化距离,gain(r) 为预先标定的增益曲线。本案例采用多项式拟合增益曲线,避免逐点存储大表。

实现代码


import numpy as np
from scipy.interpolate import CubicSpline

def generate_gain_table(img_shape, center=None, gain_curve=None):
    """
    生成径向增益表。
    img_shape: (H, W)
    center: 光心坐标,默认图像中心
    gain_curve: 归一化半径(0~1)到增益的映射表,默认模拟衰减
    返回: (H, W) float32 增益矩阵
    """
    H, W = img_shape[:2]
    if center is None:
        cx, cy = (W - 1) / 2.0, (H - 1) / 2.0
    else:
        cx, cy = center

    max_r = np.sqrt(cx**2 + cy**2)
    yy, xx = np.mgrid[0:H, 0:W].astype(np.float32)
    r = np.sqrt((xx - cx)**2 + (yy - cy)**2) / max_r
    r = np.clip(r, 0.0, 1.0)

    if gain_curve is None:
        # 模拟典型衰减:边缘亮度为中心70%,增益为1/0.7≈1.43
        gain_curve = np.array([[0.0, 1.0],
                               [0.5, 1.08],
                               [0.7, 1.20],
                               [0.9, 1.35],
                               [1.0, 1.43]])
    # 使用三次样条插值获得连续增益曲线
    cs = CubicSpline(gain_curve[:, 0], gain_curve[:, 1])
    gain_map = cs(r).astype(np.float32)
    return gain_map

def apply_lsc(image, gain_map):
    """
    对图像应用镜头阴影校正。
    image: (H, W) 或 (H, W, C) uint8/uint16/float
    gain_map: (H, W) float32
    返回: 校正后图像,与输入同形状同类型
    """
    if image.ndim == 2:
        corrected = image.astype(np.float32) * gain_map
    else:
        corrected = image.astype(np.float32) * gain_map[..., np.newaxis]

    # 防止溢出并保持原数据类型
    if image.dtype == np.uint8:
        corrected = np.clip(corrected, 0, 255).astype(np.uint8)
    elif image.dtype == np.uint16:
        corrected = np.clip(corrected, 0, 65535).astype(np.uint16)
    else:
        corrected = corrected.astype(image.dtype)
    return corrected

# 最小验证示例
if __name__ == "__main__":
    # 构造模拟图像:中心亮度200,边缘亮度140(模拟径向衰减)
    H, W = 100, 100
    yy, xx = np.mgrid[0:H, 0:W].astype(np.float32)
    cx, cy = (W-1)/2.0, (H-1)/2.0
    r = np.sqrt((xx-cx)**2 + (yy-cy)**2) / np.sqrt(cx**2 + cy**2)
    # 模拟衰减:亮度 = 200 - 60*r^2
    img = (200 - 60 * np.clip(r, 0, 1)**2).astype(np.uint8)

    gain_map = generate_gain_table(img.shape)
    corrected = apply_lsc(img, gain_map)

    # 验证:比较中心与边缘区域的平均亮度
    center_region = corrected[45:55, 45:55]
    edge_region = np.concatenate([corrected[5:15, 5:15].ravel(),
                                  corrected[5:15, 85:95].ravel(),
                                  corrected[85:95, 5:15].ravel(),
                                  corrected[85:95, 85:95].ravel()])
    print(f"校正前中心均值: {img[45:55, 45:55].mean():.1f}")
    print(f"校正前边缘均值: {np.concatenate([img[5:15,5:15].ravel(), img[5:15,85:95].ravel(), img[85:95,5:15].ravel(), img[85:95,85:95].ravel()]).mean():.1f}")
    print(f"校正后中心均值: {center_region.mean():.1f}")
    print(f"校正后边缘均值: {edge_region.mean():.1f}")
    print(f"亮度差(中心-边缘): {abs(center_region.mean() - edge_region.mean()):.2f}")

运行与验证

依赖安装:

pip install numpy scipy

运行方式:直接执行脚本,观察终端输出的校正前后中心/边缘亮度均值。

通过条件:校正后中心与边缘的平均亮度差小于校正前差值的20%(即校正效果≥80%)。示例中校正前亮度差约60,校正后应小于12。

关键说明

  • 增益表可通过实际标定获得(拍摄均匀光源),本案例提供模拟曲线用于演示算法流程。
  • 实际ISP中增益表通常按网格存储(如17×17),本案例使用全分辨率样条插值,便于验证算法正确性。
  • 若输入为Bayer RAW,需先分通道处理(各通道增益不同),本案例适用于RGB或单通道灰度图像。
镜头阴影校正效果示意 校正前 边缘暗 增益曲线 归一化半径 r → 增益 → 1.43 1.0 校正后 亮度均匀 边缘增益 = 中心亮度 / 边缘亮度,逐像素乘上增益即可补偿衰减