4. 镜头阴影校正标定

任务目标

针对因镜头光学特性导致的图像四角亮度衰减( vignetting),通过拍摄均匀灰卡计算每个像素位置所需的增益补偿值,生成LSC(Lens Shading Correction)增益矩阵并配置到ISP,最终验证校正后图像四角与中心亮度差在可接受范围内。

环境与依赖

  • 硬件:瑞芯微RV1126/RV1109或RK3588平台Camera模组
  • 软件:Rockchip ISP SDK(含rkisp_aiq工具链)、OpenCV(用于图像分析)
  • 输入:均匀光照下拍摄的灰卡RAW图(建议灰卡亮度均匀性>98%,光照均匀性>95%)

实现原理

LSC校正的核心是计算每个像素位置的增益值。设中心区域平均亮度为Y_center,某像素位置亮度为Y(x,y),则该点增益为G(x,y) = Y_center / Y(x,y)。实际ISP硬件通常使用网格化增益表(如17x13网格),每个网格点存储一个增益值,像素间通过双线性插值获得连续增益。

关键完整代码

import cv2
import numpy as np
import json

def load_raw_to_bayer(raw_path, width, height, bayer_pattern='BGGR'):
    """加载RAW图并返回Bayer格式数组"""
    raw = np.fromfile(raw_path, dtype=np.uint16).reshape(height, width)
    return raw

def demosaic_to_gray(bayer, pattern='BGGR'):
    """简单Bayer转灰度(用于亮度分析)"""
    h, w = bayer.shape
    gray = np.zeros((h//2, w//2), dtype=np.float32)
    # 按BGGR排列提取G通道
    if pattern == 'BGGR':
        g1 = bayer[0::2, 1::2].astype(np.float32)
        g2 = bayer[1::2, 0::2].astype(np.float32)
        gray = (g1 + g2) / 2.0
    return gray

def compute_lsc_gain(gray_img, grid_cols=17, grid_rows=13):
    """计算LSC增益矩阵"""
    h, w = gray_img.shape
    # 计算中心区域(取图像中央10%区域)的平均亮度
    ch0, ch1 = int(h*0.45), int(h*0.55)
    cw0, cw1 = int(w*0.45), int(w*0.55)
    center_mean = np.mean(gray_img[ch0:ch1, cw0:cw1])
    
    # 生成网格坐标
    x_coords = np.linspace(0, w-1, grid_cols).astype(int)
    y_coords = np.linspace(0, h-1, grid_rows).astype(int)
    
    # 计算每个网格点的增益
    gain_grid = np.zeros((grid_rows, grid_cols), dtype=np.float32)
    for i, y in enumerate(y_coords):
        for j, x in enumerate(x_coords):
            # 取网格点周围5x5区域均值,避免单点噪声
            y0, y1 = max(0, y-2), min(h, y+3)
            x0, x1 = max(0, x-2), min(w, x+3)
            local_mean = np.mean(gray_img[y0:y1, x0:x1])
            gain = center_mean / max(local_mean, 1.0)
            # 限制增益范围,防止过曝
            gain_grid[i, j] = np.clip(gain, 1.0, 4.0)
    
    return gain_grid

def save_lsc_table(gain_grid, output_path):
    """保存为ISP需要的JSON格式"""
    # 转换为ISP期望的定点格式(Q4.8,即乘以256)
    gain_fixed = (gain_grid * 256).astype(np.uint16)
    table = {
        "grid_cols": gain_grid.shape[1],
        "grid_rows": gain_grid.shape[0],
        "gain_table": gain_fixed.tolist()
    }
    with open(output_path, 'w') as f:
        json.dump(table, f, indent=2)
    print(f"LSC表已保存至 {output_path}")

def verify_correction(raw_path, width, height, gain_grid):
    """验证校正效果"""
    bayer = load_raw_to_bayer(raw_path, width, height)
    gray = demosaic_to_gray(bayer)
    h, w = gray.shape
    
    # 对图像应用增益(双线性插值)
    grid_cols, grid_rows = gain_grid.shape[1], gain_grid.shape[0]
    x_coords = np.linspace(0, w-1, grid_cols)
    y_coords = np.linspace(0, h-1, grid_rows)
    
    # 使用OpenCV重映射实现插值
    map_x, map_y = np.meshgrid(np.arange(w), np.arange(h))
    gain_map = cv2.remap(gain_grid, 
                         (map_x * (grid_cols-1) / (w-1)).astype(np.float32),
                         (map_y * (grid_rows-1) / (h-1)).astype(np.float32),
                         cv2.INTER_LINEAR)
    
    corrected = gray * gain_map
    
    # 计算四角与中心亮度比
    corners = {
        'top_left': corrected[0:50, 0:50].mean(),
        'top_right': corrected[0:50, -50:].mean(),
        'bottom_left': corrected[-50:, 0:50].mean(),
        'bottom_right': corrected[-50:, -50:].mean(),
        'center': corrected[h//2-25:h//2+25, w//2-25:w//2+25].mean()
    }
    
    center_val = corners['center']
    ratios = {k: v/center_val for k, v in corners.items() if k != 'center'}
    return ratios

if __name__ == "__main__":
    # 配置参数
    RAW_PATH = "gray_card.raw"
    WIDTH, HEIGHT = 2592, 1944  # 根据实际sensor修改
    OUTPUT_JSON = "lsc_table.json"
    
    # 1. 加载并分析灰卡图
    bayer = load_raw_to_bayer(RAW_PATH, WIDTH, HEIGHT)
    gray = demosaic_to_gray(bayer)
    
    # 2. 计算增益矩阵
    gain_grid = compute_lsc_gain(gray)
    print("增益矩阵形状:", gain_grid.shape)
    print("增益范围: {:.3f} - {:.3f}".format(gain_grid.min(), gain_grid.max()))
    
    # 3. 保存LSC表
    save_lsc_table(gain_grid, OUTPUT_JSON)
    
    # 4. 验证校正效果
    ratios = verify_correction(RAW_PATH, WIDTH, HEIGHT, gain_grid)
    print("\n校正后四角/中心亮度比:")
    for pos, ratio in ratios.items():
        print(f"  {pos}: {ratio:.3f}")
    
    # 5. 判定是否通过
    max_deviation = max(abs(r-1.0) for r in ratios.values())
    passed = max_deviation < 0.05  # 5%偏差阈值
    print(f"\n最大偏差: {max_deviation*100:.1f}%")
    print("判定:", "通过" if passed else "未通过")

运行与配置

  1. 拍摄灰卡:将灰卡置于镜头前,确保灰卡充满画面且光照均匀,使用手动曝光模式拍摄RAW格式图像。
  2. 准备环境:安装OpenCV(pip install opencv-python numpy),将RAW图重命名为gray_card.raw。
  3. 执行脚本:python lsc_calibration.py,脚本会输出增益矩阵统计信息和校正后亮度比。
  4. 配置ISP:将生成的lsc_table.json通过Rockchip ISP工具(如rkisp_aiq)导入,或手动写入sensor驱动对应的LSC寄存器表。

验证方法与通过标准

  • 离线验证:脚本输出的四角/中心亮度比应接近1.0。若最大偏差<5%(即比值在0.95~1.05之间),判定校正有效。
  • 实拍验证:配置LSC后重新拍摄灰卡,用图像分析软件(如Imatest或OpenCV)测量四角与中心亮度差,确认亮度差从校正前的10%~30%降至5%以内。
  • 主观检查:拍摄自然场景,观察画面四角不应出现明显偏暗或偏色(若R/G/B通道分别标定则需检查各通道均匀性)。

常见问题

  • 增益过大导致噪声放大:若四角增益超过2.0,说明镜头衰减严重,建议先检查镜头安装或考虑光学方案优化。
  • 灰卡反光或脏污:会导致局部亮度异常,应使用哑光灰卡并保持清洁。
  • RAW图黑电平未扣除:计算增益前需先减去黑电平值(通常从sensor寄存器读取),否则暗部增益计算不准。