3. 黑电平校正参数标定
任务目标
拍摄全黑图像(盖上镜头盖),统计RAW图像各颜色通道(R/Gr/Gb/B)的暗电流响应均值,计算黑电平校正值并写入ISP寄存器,最终验证暗部区域无偏色。
环境依赖
- 瑞芯微平台(RK3568/RK3588等)Linux系统,已接入Camera Sensor
- 工具:
rkisp_demo(ISP调试工具)、v4l2-ctl(抓帧)、Python3 + numpy(离线计算) - Sensor RAW输出格式:需支持RAW10/RAW12(本例以RAW10为例)
实现步骤
- 遮挡镜头并抓取全黑RAW帧
- 解析RAW数据,统计各通道像素均值
- 将均值写入ISP黑电平校正寄存器
- 抓取暗部图像验证无偏色
关键完整代码
#!/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排列