3、去马赛克(Demosaic):Bayer格式详解、经典插值算法、边缘导向插值
好,咱们进入ISP Pipeline里最核心、也最考验功底的一步——去马赛克。说白了,就是把传感器上那个花花绿绿的Bayer图案,还原成我们肉眼看着舒服的全彩图像。
我刚开始接触ISP时,觉得这步不就是插值嘛,简单。后来被一个低照度场景的伪彩色折磨了整整两周,才明白这里面的门道有多深。你想想看,每个像素点只采集了一种颜色,另外两种颜色全靠“猜”,这个“猜”的水平,直接决定了画质的上限。
3.1 Bayer格式:传感器的“原罪”
大部分CMOS传感器,每个像素只能感知光的强度,分不清颜色。那怎么办?在像素表面盖一层彩色滤光片阵列(CFA),最常用的就是Bayer模式。
Bayer模式的核心思想很简单:人眼对绿色最敏感,所以绿色像素占一半;红和蓝各占四分之一。典型的排列是RGGB。
| 像素位置 | R | G | B |
|---|---|---|---|
| 奇数行奇数列 | R | — | — |
| 奇数行偶数列 | — | G | — |
| 偶数行奇数列 | — | G | — |
| 偶数行偶数列 | — | — | B |
嗯,这里要注意,Bayer格式不是唯一的。还有BGGR、GBRG、GRBG等变种。我在项目中遇到过最坑的一次,就是sensor驱动配置错了Bayer相位,结果整张图偏色到没法看。排查了半天,最后发现是初始化时少写了一个寄存器。
3.2 经典插值算法:从简单到靠谱
有了Bayer数据,怎么恢复全彩?最直接的想法就是——邻居有啥,我借啥。
3.2.1 最近邻插值
最简单粗暴。缺失的像素值,直接用最近的同色像素值填充。速度快,但效果嘛……马赛克感十足,边缘锯齿严重。现在基本没人用了,除非你对性能要求极端苛刻。
3.2.2 双线性插值
这个就靠谱多了。取周围同色像素的平均值。比如在R像素点恢复G值,就取上下左右四个G像素的平均。
// 双线性插值示例:在R位置恢复G值
G_at_R = (G_up + G_down + G_left + G_right) / 4;
效果比最近邻好不少,但问题也很明显——它不分方向。一旦遇到图像边缘,就会把边缘两侧的颜色混在一起,产生模糊和伪彩色。
3.2.3 色比恒定法
这个思路有点意思。它利用了图像的一个特性:在局部区域内,不同颜色通道的比值变化比较平缓。先插值G通道,然后用R/G和B/G的比值来恢复R和B。
我个人习惯在低ISO场景下用这个方法,色彩还原比较自然。但高ISO时噪声会放大比值,效果反而变差。
3.3 边缘导向插值:真正能打的方案
前面那些方法,说白了都是“无脑平均”。真正让去马赛克效果产生质变的,是引入边缘检测。
核心思想:插值应该沿着边缘方向走,而不是跨过边缘。你想想看,如果一条水平边缘穿过一个像素,那用左右两边的像素来插值,肯定比用上下像素更准。
3.3.1 经典方法:Hamilton-Adams算法
这是ISP领域一个里程碑式的算法。它用二阶梯度来判断边缘方向,然后沿着梯度小的方向做插值。
// Hamilton-Adams 边缘判断(简化版)
// 计算水平和垂直方向的梯度
delta_H = |G_left - G_right| + |2*R_center - R_left2 - R_right2|
delta_V = |G_up - G_down| + |2*R_center - R_up2 - R_down2|
if (delta_H < delta_V) {
// 水平方向更平滑,用水平像素插值
G_at_R = (G_left + G_right) / 2 + (2*R_center - R_left2 - R_right2) / 4
} else if (delta_V < delta_H) {
// 垂直方向更平滑,用垂直像素插值
G_at_R = (G_up + G_down) / 2 + (2*R_center - R_up2 - R_down2) / 4
} else {
// 无明显边缘,用双线性
G_at_R = (G_up + G_down + G_left + G_right) / 4
}
我在一个安防项目中用过这个算法。在1080P分辨率下,纯软件实现跑不到30fps。后来用NEON指令集做了向量化,才勉强达标。但效果确实比双线性好一大截,边缘的伪彩色明显减少了。
3.3.2 自适应插值
Hamilton-Adams虽然好,但在纹理复杂区域还是会翻车。更高级的方案是自适应插值——不只用两个方向,而是把多个方向的插值结果加权融合。
权重的计算方式有很多种,我比较常用的是基于局部梯度能量的方法:
- 计算每个方向的梯度能量
- 梯度能量越小,说明该方向越平滑,权重越大
- 最终结果是各方向插值的加权和
这样做的好处是,在边缘区域几乎完全沿着边缘插值,在平坦区域又退化成双线性,平滑噪声。
3.4 去马赛克的知识体系
我把整个去马赛克的知识脉络整理成了一张图,方便你理解各个模块之间的关系。
3.5 硬件加速要点
去马赛克是ISP Pipeline中计算量最大的模块之一。在硬件实现时,有几个关键点需要特别注意:
- Line Buffer设计: 边缘导向插值至少需要5行数据。用Line Buffer做行缓存,比用Frame Buffer省带宽得多。
- 梯度计算并行化: 水平和垂直梯度的计算没有数据依赖,可以完全并行。我建议用两个独立的运算单元同时算。
- 查表法优化: 如果权重计算涉及除法或非线性函数,提前做好查找表(LUT),一个时钟周期就能出结果。
我记得有一次做FPGA原型验证,去马赛克模块的时序一直跑不满。最后发现是梯度计算路径上多了一级不必要的流水线寄存器。去掉之后,频率直接从150MHz提到了200MHz。有时候,少就是多。
好了,去马赛克这块的内容就到这里。从Bayer格式的排列,到经典插值算法,再到真正能打的边缘导向插值,每一步都有它的物理意义和工程考量。你在实际项目中,可以根据画质要求和硬件资源,选择合适的方案。