3. 黑电平校正参数标定

黑电平校正参数标定 学习卡片

任务目标

拍摄全黑图像(盖上镜头盖),统计RAW图像各颜色通道(R/Gr/Gb/B)的暗电流响应均值,计算黑电平校正值并写入ISP寄存器,最终验证暗部区域无偏色。

环境依赖

  • 瑞芯微平台(RK3568/RK3588等)Linux系统,已接入Camera Sensor
  • 工具:rkisp_demo(ISP调试工具)、v4l2-ctl(抓帧)、Python3 + numpy(离线计算)
  • Sensor RAW输出格式:需支持RAW10/RAW12(本例以RAW10为例)

实现步骤

  1. 遮挡镜头并抓取全黑RAW帧
  2. 解析RAW数据,统计各通道像素均值
  3. 将均值写入ISP黑电平校正寄存器
  4. 抓取暗部图像验证无偏色

关键完整代码

#!/usr/bin/env python3
# black_level_calib.py - 黑电平标定与验证
import numpy as np
import subprocess, sys, time

RAW_WIDTH, RAW_HEIGHT = 1920, 1080   # 根据sensor实际分辨率修改
BIT_DEPTH = 10                        # RAW10
BLC_REG_BASE = 0x1000                 # 示例寄存器基址,按平台手册修改

def capture_raw(frames=1):
    """抓取全黑RAW帧,返回bytes"""
    cmd = f"v4l2-ctl --set-fmt-video=width={RAW_WIDTH},height={RAW_HEIGHT},pixelformat=RG10 --stream-mmap --stream-count={frames} --stream-to=black.raw"
    subprocess.run(cmd, shell=True, check=True)
    with open("black.raw", "rb") as f:
        return f.read()

def parse_raw10(data):
    """解析RAW10 packed格式为numpy数组 (H,W)"""
    img = np.frombuffer(data, dtype=np.uint8).reshape(-1, 5)
    # 每5字节存4个10bit像素
    px = np.zeros((img.shape[0]*4,), dtype=np.uint16)
    px[0::4] = (img[:,0].astype(np.uint16) & 0xFF) | ((img[:,4] & 0x03) << 8)
    px[1::4] = (img[:,1].astype(np.uint16) & 0xFF) | ((img[:,4] & 0x0C) << 6)
    px[2::4] = (img[:,2].astype(np.uint16) & 0xFF) | ((img[:,4] & 0x30) << 4)
    px[3::4] = (img[:,3].astype(np.uint16) & 0xFF) | ((img[:,4] & 0xC0) << 2)
    return px.reshape(RAW_HEIGHT, RAW_WIDTH)

def calc_channel_means(raw_img):
    """按Bayer RGGB排列计算各通道均值"""
    h, w = raw_img.shape
    r = raw_img[0:h:2, 0:w:2].mean()
    gr = raw_img[0:h:2, 1:w:2].mean()
    gb = raw_img[1:h:2, 0:w:2].mean()
    b = raw_img[1:h:2, 1:w:2].mean()
    return {"R": r, "Gr": gr, "Gb": gb, "B": b}

def write_blc_regs(offsets):
    """写入ISP黑电平寄存器(示例:rkisp通过ioctl写isp参数)"""
    # 实际平台通过rkisp_demo -i black.raw --blc-offset R,Gr,Gb,B 或直接写寄存器
    cmd = f"rkisp_demo -i black.raw --blc-offset {offsets['R']:.1f},{offsets['Gr']:.1f},{offsets['Gb']:.1f},{offsets['B']:.1f}"
    subprocess.run(cmd, shell=True, check=True)
    print(f"[OK] BLC offsets set: {offsets}")

def verify_no_color_cast():
    """抓取暗部图像,检查R/G/B通道均值差异"""
    # 抓一帧正常曝光但暗部场景(如室内阴影区)
    subprocess.run("v4l2-ctl --set-fmt-video=width=1920,height=1080,pixelformat=RG10 --stream-mmap --stream-count=1 --stream-to=verify.raw", shell=True, check=True)
    with open("verify.raw", "rb") as f:
        data = f.read()
    img = parse_raw10(data)
    means = calc_channel_means(img)
    # 扣除黑电平后,暗部R/G/B应接近
    corrected = {k: v - offsets[k] for k, v in means.items()}
    spread = max(corrected.values()) - min(corrected.values())
    print(f"暗部校正后通道均值: {corrected}")
    print(f"通道间最大差值: {spread:.2f} LSB")
    return spread < 8   # 判定阈值:8个LSB内视为无偏色

if __name__ == "__main__":
    print("=== 步骤1: 抓取全黑帧 ===")
    raw_data = capture_raw()
    img = parse_raw10(raw_data)
    
    print("=== 步骤2: 计算各通道黑电平 ===")
    offsets = calc_channel_means(img)
    print(f"黑电平值(LSB): {offsets}")
    
    print("=== 步骤3: 写入ISP配置 ===")
    write_blc_regs(offsets)
    
    print("=== 步骤4: 验证暗部无偏色 ===")
    time.sleep(1)  # 等待ISP生效
    if verify_no_color_cast():
        print("[PASS] 暗部无偏色验证通过")
    else:
        print("[FAIL] 暗部存在偏色,需检查BLC配置或sensor暗电流特性")

运行方式

# 1. 确保sensor已输出RAW格式
v4l2-ctl --list-formats-ext | grep RG10

# 2. 执行标定脚本(需root权限)
sudo python3 black_level_calib.py

# 3. 若需持久化配置,将输出的offsets写入dts或ISP tuning xml

验证方法与判定标准

  • 黑电平合理性:RAW10下典型黑电平值在30~80 LSB之间(sensor型号相关),若超出0~255范围需检查抓帧格式或sensor配置
  • 偏色判定:脚本输出暗部校正后R/Gr/Gb/B四通道均值,最大差值<8 LSB即通过;若差值较大,检查Bayer通道顺序是否匹配(RGGB/BGGR等)
  • 目视确认:拍摄暗部灰卡(反射率~2%),在显示器上观察无红/绿/蓝染色

常见问题

  • 全黑帧出现条纹:确认镜头盖完全遮光,避免漏光;检查sensor是否处于长曝光模式
  • 寄存器写入不生效:确认ISP pipeline中BLC模块位于去噪之前,且未与其他模块(如LSC)冲突
  • 通道顺序不匹配:若R/B通道均值明显互换,修改calc_channel_means中的Bayer排列