4. 原始图像数据采集与预处理
拿到一颗 onsemi 图像传感器(例如 AR0234、AR0521、AR0821 这类常见型号)后,真正决定后续噪声与坏点治理效果的,往往不是算法本身,而是原始数据在进入算法之前是否被正确解析和预处理。本节围绕一条完整链路展开:从传感器输出的 raw 数据格式,到 Bayer 阵列的排布还原,再到黑电平校正与线性化,最后用 Python 或 MATLAB 把数据读成可用的二维数组。
一、先弄清 onsemi 传感器输出的是什么
onsemi 的 CMOS 图像传感器通常通过 MIPI CSI-2 或并行接口输出原始 Bayer 数据。以 10-bit 输出为例,每个像素的有效数据占 10 位,但传输时一般按 16 位对齐存放,即每个像素占 2 字节,高 10 位有效、低 6 位补零或填充。这一点非常关键:如果直接按 8 位读取,图像会完全错乱。
常见的几种存放方式:
- MIPI RAW10:4 个 10-bit 像素打包成 5 字节,传输效率高,但解析时需要按位重组。
- MIPI RAW12:2 个 12-bit 像素打包成 3 字节。
- 16-bit 对齐:每个像素占 2 字节,低 6 位为 0,解析最简单,常用于调试阶段。
此外,onsemi 传感器在有效像素区之外通常还有嵌入式数据行(embedded data lines),里面包含曝光、增益、温度、帧计数等信息。这些行不属于图像内容,读取时必须跳过,否则会整体错位。
二、Bayer 阵列:颜色从哪来
Bayer 阵列的本质是每个像素只感应一种颜色,通过 2×2 的重复单元恢复彩色信息。onsemi 传感器常见的起始相位有四种:GRBG、RGGB、BGGR、GBRG。相位判断错误,会导致红蓝通道互换或整体偏色。
以 RGGB 为例,2×2 单元为:
R G
G B
因此第 0 行第 0 列为 R,第 0 行第 1 列为 G,第 1 行第 0 列为 G,第 1 行第 1 列为 B。解析时,只要知道起始相位,就能把 raw 数组拆成 R、G、B 三个子平面。G 通道数量是 R、B 的两倍,这也是后续做白平衡和去马赛克时要特别注意的地方。
三、黑电平校正:把“零”找回来
传感器输出的 raw 值并不是从 0 开始的。由于暗电流、读出电路偏置等原因,即使完全没有光,像素也会输出一个非零的基底值,这就是黑电平(black level)。onsemi 传感器通常会在寄存器中给出一个标称黑电平,例如 10-bit 下为 64 或 128,但实际值会随增益和温度变化。
黑电平校正的做法是:从每个像素值中减去当前条件下的黑电平。更精细的做法是使用光学黑区(optical black,OB)行/列——这些像素被遮光,只输出黑电平,可以逐行或逐帧估计真实黑电平。
corrected = raw - black_level
corrected = max(corrected, 0)
注意减完后要截断到 0,避免出现负值。如果黑电平估计偏低,暗部会残留偏色;估计偏高,暗部会被压死、丢失细节。
四、线性化:让数值与光强成正比
理想情况下,传感器输出与入射光强成线性关系。但实际传感器在接近饱和区会压缩,在极暗区也可能有非线性。线性化就是通过查找表(LUT)或分段线性拟合,把 raw 值映射回线性响应。
对于大多数 onsemi 传感器,如果工作在线性区,线性化可以简化为归一化:
linear = (corrected) / (max_value - black_level)
其中 max_value 为满量程,例如 10-bit 为 1023。这样得到的值落在 [0,1] 区间,便于后续噪声建模和坏点检测统一处理。如果传感器启用了 HDR 或压缩曲线,则必须使用厂商提供的线性化 LUT,不能简单归一化。
五、用 Python 读取 onsemi raw 数据
假设我们拿到一帧 1920×1080、10-bit、16 位对齐、RGGB 相位的 raw 文件,文件名为 frame.raw。用 NumPy 读取并完成预处理:
import numpy as np
W, H = 1920, 1080
BLACK = 64
MAXV = 1023
# 16-bit 对齐,小端
raw = np.fromfile("frame.raw", dtype=np.<u2)
raw = raw[:W*H].reshape(H, W).astype(np.float32)
# 黑电平校正
corr = np.clip(raw - BLACK, 0, None)
# 线性化归一化
linear = corr / (MAXV - BLACK)
# 按 RGGB 拆通道
R = linear[0::2, 0::2]
Gr = linear[0::2, 1::2]
Gb = linear[1::2, 0::2]
B = linear[1::2, 1::2]
print(R.shape, Gr.shape, Gb.shape, B.shape)
如果文件是 MIPI RAW10 打包格式,则需要先按 5 字节一组解包:
def unpack_raw10(data):
data = data.reshape(-1, 5).astype(np.uint16)
out = np.zeros((data.shape[0], 4), dtype=np.uint16)
out[:,0] = (data[:,0] << 2) | (data[:,4] & 0x03)
out[:,1] = (data[:,1] << 2) | ((data[:,4] >> 2) & 0x03)
out[:,2] = (data[:,2] << 2) | ((data[:,4] >> 4) & 0x03)
out[:,3] = (data[:,3] << 2) | ((data[:,4] >> 6) & 0x03)
return out.ravel()
六、用 MATLAB 读取与验证
MATLAB 中可以用 fread 直接读取,并快速可视化 Bayer 相位是否正确:
fid = fopen('frame.raw','r');
raw = fread(fid, W*H, 'uint16=>double');
fclose(fid);
raw = reshape(raw, [W, H])';
corr = max(raw - 64, 0);
linear = corr / (1023 - 64);
% 查看 2x2 单元,确认相位
disp(linear(1:2, 1:2));
% 简单可视化
imagesc(linear); colormap(gray); axis image;
判断相位是否正确,可以观察图像中天空、白墙等中性区域:如果 R 和 B 平面均值差异过大,而 G 平面明显偏高,通常说明相位判断有误,需要把起始坐标偏移一个像素再试。
七、预处理结果如何判断
完成上述步骤后,建议检查三个指标:
- 暗区均值:遮光区域校正后应接近 0,若明显大于 0,说明黑电平估计偏低。
- 饱和区:亮部归一化后应接近 1,若大量像素被截断在 1,说明曝光过度或满量程设置错误。
- 通道均值比:在均匀白光下,R、G、B 均值应大致接近,偏差过大提示 Bayer 相位或白平衡问题。
把 raw 数据正确读成线性、已校正的浮点数组,是后续所有噪声建模与坏点治理工作的起点。这一节的处理链路虽然不长,但每一步的相位、位深、黑电平和满量程参数都必须与具体 onsemi 型号严格对应,建议在正式分析前先用一帧均匀光照图像做一次完整验证。