Bayer格式与预处理:从RAW数据到可处理的图像
各位同学,欢迎来到第二章。上一章我们聊了ISP的整体架构,今天要深入的是ISP流水线的第一站——Bayer域预处理。说白了,就是传感器刚把光信号转成电信号,送出来的原始RAW数据该怎么收拾干净。
我个人习惯把这一章叫做“打扫卫生”阶段。为什么?因为传感器出来的数据,黑的不够黑、亮的不够亮、中间还有坏点和阴影。不把这些处理好,后面的去马赛克、白平衡全是白搭。
2.1 Bayer CFA模式详解
先说说Bayer格式。大部分单传感器相机都用Bayer滤色阵列,每个像素只采集红、绿、蓝中的一种。最常见的模式是RGGB,也就是一个2x2的单元里,两个绿色、一个红色、一个蓝色。
为什么绿色占两个? 因为人眼对绿色最敏感。传感器厂商这么设计,说白了就是模仿人眼的亮度感知特性。
常见的Bayer模式有四种:
| 模式 | 2x2单元排列 | 常见应用 |
|---|---|---|
| RGGB | R G / G B | 索尼、OmniVision主流 |
| BGGR | B G / G R | 部分安森美传感器 |
| GRBG | G R / B G | 三星部分型号 |
| GBRG | G B / R G | 较少见,但会遇到 |
在FPGA里,我们通常用两个bit来表示当前像素属于哪个通道。比如00=R, 01=G, 10=B。这样在流水线里,每个像素进来,先查一下它的通道标签,再决定后续处理路径。
我记得第一次做Bayer处理时,犯了个低级错误——把行列计数搞反了。结果出来的图像全是斜条纹,查了两天才发现是奇偶行判断写反了。嗯,这里要注意:行计数器要从0开始,偶数行和奇数行的G像素位置是错开的。
2.2 黑电平校正(BLC)
黑电平校正,名字听着高大上,其实原理很简单。传感器在没有光照时,理论上输出应该是0。但实际因为暗电流、电路噪声等原因,会有一个固定的偏移量,比如64。
校正公式就一行:
P_out = P_in - black_level
但实际项目里没这么简单。我遇到过一个问题:不同增益下的黑电平不一样。你想想看,ISO提高时,暗电流噪声也被放大了,黑电平偏移会跟着变。
工程经验: 建议在FPGA里实现一个查找表,根据当前增益值查对应的黑电平偏移量。不要用固定值,否则高ISO下暗部会偏色。
FPGA实现思路:
- 每个像素进来,先减去一个寄存器中存储的黑电平值
- 结果要做饱和处理,不能出现负数(小于0就钳位到0)
- 如果传感器支持动态BLC,需要预留I2C接口更新寄存器
我曾经在一个项目中,BLC没做饱和处理,结果暗部出现了负值,后面去马赛克直接算出了负数,整张图暗部全是紫色噪点。从那以后,我每个减法后面都加一级饱和钳位。
2.3 镜头阴影校正(LSC)
镜头阴影,也叫暗角。镜头中心光线直射,边缘光线斜射,导致边缘亮度比中心低。校正方法就是给边缘像素乘一个大于1的增益系数。
传统做法是存一张增益网格图,比如17x13个网格点,每个点存一个增益值。中间像素用双线性插值算出来。
FPGA实现要点:
- 网格增益表存在BRAM里,按行号列号查表
- 插值用两个乘法器+一个加法器,流水线设计
- 增益系数通常用Q8.8定点格式,精度够用
注意: 增益系数不能太大,一般不超过4倍。否则边缘噪声会被放大得很明显。我见过有人把增益设到8倍,结果边缘全是彩色噪点,比不校正还难看。
这里有个小技巧:LSC和BLC的顺序有讲究。我个人习惯先做BLC再做LSC。为什么?因为BLC减掉的是固定偏移,LSC乘的是增益。先减后乘,数学上更干净。如果先乘后减,暗电流噪声也被放大了,校正效果会变差。
2.4 坏点校正(DPC)
传感器生产过程中,难免有几个像素是坏的。坏点分两种:
- 亮点(Hot Pixel): 一直输出高值,像一颗星星
- 暗点(Dead Pixel): 一直输出低值,像一颗黑洞
校正方法:用周围像素的平均值替换坏点。
FPGA里怎么做?
- 需要缓存至少3行数据(Line Buffer)
- 对当前像素的3x3邻域做判断
- 如果当前像素与周围8个像素的差值超过阈值,判定为坏点
- 用周围同通道像素的中值或均值替换
// 伪代码示意
if (abs(P_center - median_of_neighbors) > threshold) {
P_out = median_of_same_channel_neighbors;
} else {
P_out = P_center;
}
关键点: 坏点检测必须在Bayer域做,不能等到去马赛克之后。因为Bayer域每个像素只有一种颜色,邻域统计更准确。一旦插值成RGB,坏点的影响会被扩散到周围像素,再检测就晚了。
阈值怎么设?我一般用动态阈值:根据当前区域的亮度均值,乘以一个比例系数。暗区阈值小一点,亮区阈值大一点。固定阈值在暗区容易误检,在亮区又容易漏检。
2.5 整体预处理流水线结构
把这三个模块串起来,就是ISP预处理的核心流水线:
这个流水线在FPGA里实现时,要注意几点:
- 每个模块都用valid-ready握手,方便级联
- Line Buffer用分布式RAM或BRAM,看资源情况
- 所有模块的延迟要对齐,否则数据会乱
我的习惯: 每个模块输出都带一个像素有效标志(pixel_valid),下游模块只在该标志有效时才处理。这样即使某个模块因为查表多等了一个周期,整个流水线也不会崩。