4. 传统ISP Pipeline:去马赛克、白平衡、色彩校正矩阵

各位同学,今天我们聊聊ISP Pipeline里最核心的三个模块——去马赛克、白平衡、色彩校正矩阵。这三个模块,说白了就是让Raw图变成人眼看着舒服的彩色图像的关键步骤。

我刚开始做ISP时,总觉得这些模块都是现成的算法,调调参数就行了。后来踩了不少坑才明白——每个模块背后都有物理原理和数学逻辑,不懂原理,调参就是瞎蒙。

4.1 去马赛克(Demosaic)

先说说去马赛克。为什么需要这步?因为CMOS传感器上每个像素只能感知一种颜色——要么红、要么绿、要么蓝。最常见的Bayer模式,就是RGGB排列。

你想想看,每个像素只有一种颜色信息,但我们要输出三通道的彩色图。怎么办?就得靠周围像素猜出缺失的颜色。这就是去马赛克。

核心思路: 利用颜色通道之间的空间相关性,插值出缺失的像素值。

4.1.1 最基础的插值方法

最简单的做法是双线性插值。比如G通道,直接取周围四个G像素的平均值。R和B通道也类似。但这种方法有个大问题——边缘处会出现伪彩色和锯齿。

我记得第一次用双线性插值做测试,拍了一张黑白条纹的测试卡,结果边缘全是彩色条纹。当时我还以为是传感器坏了,后来才知道这就是经典的“拉链效应”。

4.1.2 边缘导向插值

为了解决边缘伪色,业界提出了边缘导向插值。核心思想是:先判断当前像素是水平边缘还是垂直边缘,然后沿着边缘方向插值,不跨边缘。

// 边缘导向插值伪代码
// 判断水平梯度和垂直梯度
grad_h = |G(i,j-1) - G(i,j+1)|
grad_v = |G(i-1,j) - G(i+1,j)|

if (grad_h < grad_v) {
    // 水平边缘,沿水平方向插值
    G(i,j) = (G(i,j-1) + G(i,j+1)) / 2
} else if (grad_v < grad_h) {
    // 垂直边缘,沿垂直方向插值
    G(i,j) = (G(i-1,j) + G(i+1,j)) / 2
} else {
    // 平坦区域,用四个方向平均
    G(i,j) = (G(i,j-1) + G(i,j+1) + G(i-1,j) + G(i+1,j)) / 4
}

这种方法比双线性好很多,但在纹理复杂区域还是会有瑕疵。后来出现了自适应插值、基于残差插值等方法,效果越来越好,但计算量也上去了。

我的经验: 在嵌入式平台上,边缘导向插值是个不错的折中方案。如果算力够,可以试试基于导向滤波的去马赛克,效果接近顶级,但实现起来要小心内存占用。

4.2 白平衡(AWB)

白平衡,说白了就是让白色物体在任何光源下都呈现白色。人眼有色彩恒常性,但传感器没有。同一个白纸,在日光灯下发蓝,在白炽灯下发黄,传感器拍出来就是偏色的。

白平衡的核心就是估计光源色温,然后做通道增益补偿。

4.2.1 灰度世界假设

最经典的算法是灰度世界假设——认为一幅彩色图像中,R、G、B三个通道的平均值应该相等。如果不等,就说明有偏色,需要调整。

// 灰度世界算法
avg_r = mean(R_channel)
avg_g = mean(G_channel)
avg_b = mean(B_channel)

// 计算增益
gain_r = avg_g / avg_r
gain_b = avg_g / avg_b

// 应用增益
R_out = R_in * gain_r
B_out = B_in * gain_b

这个方法简单,但有个前提——场景中必须有足够多的颜色。如果场景本身就是单一色调(比如一片蓝天),灰度世界假设就会失效。

4.2.2 白点检测法

更靠谱的方法是白点检测。先找出图像中可能是白色的区域,然后用这些区域来估计光源色温。

怎么判断白点?一般用色温曲线。在色度图上,不同光源下的白色点会落在一条曲线上。我们只需要找到落在曲线附近的像素,认为它们是白点,然后计算增益。

避坑指南: 我曾经在一个项目中,白平衡在室内正常,一到室外就偏蓝。查了好久才发现,是白点检测的阈值设得太严,把室外高色温下的白点都过滤掉了。后来我把阈值放宽,并加入了置信度权重,问题才解决。

4.2.3 色温估计与增益计算

实际工程中,我们通常用查表法。预先标定好不同色温下的R/G和B/G比值,然后根据检测到的白点,找到最接近的色温,再查表得到增益。

色温(K) R/G比值 B/G比值 应用场景
2500 2.10 1.20 白炽灯
4000 1.50 1.40 荧光灯
5500 1.00 1.00 日光
6500 0.85 1.15 阴天

嗯,这里要注意——查表法需要针对每个传感器模组单独标定。不同传感器、不同镜头、甚至不同批次,色温曲线都会有差异。

4.3 色彩校正矩阵(CCM)

白平衡做完后,颜色基本不偏了,但还不够准。为什么?因为传感器的色彩响应和人眼不一样。传感器滤色片的光谱响应和人眼LMS锥细胞的响应曲线不同,拍出来的颜色饱和度、色调都有偏差。

色彩校正矩阵,就是用一个3x3的矩阵,把传感器颜色空间映射到标准颜色空间(比如sRGB)。

4.3.1 CCM的数学形式

说白了就是线性变换:

[R_out]   [a11 a12 a13]   [R_in]
[G_out] = [a21 a22 a23] * [G_in]
[B_out]   [a31 a32 a33]   [B_in]

矩阵系数怎么来的?通过标定。拍一张标准色卡(比如Macbeth色卡),记录传感器输出的RGB值,再和标准值做最小二乘拟合,就能算出矩阵系数。

4.3.2 矩阵系数的约束

CCM不是随便算的。有几个约束必须满足:

  • 中性轴约束: 灰色输入必须输出灰色。也就是说,R=G=B时,输出也必须是R=G=B。
  • 行和约束: 每行系数之和必须为1,否则会改变亮度。
  • 非负约束: 系数最好非负,否则可能出现负值,导致颜色溢出。
关键点: CCM的精度直接影响色彩还原度。但矩阵系数太强,会导致噪声放大。这是个trade-off。

4.3.3 多色温CCM

不同色温下,传感器的色彩响应也会变化。所以实际产品中,我们通常准备多组CCM,对应不同色温区间。

比如低色温一组、中色温一组、高色温一组。AWB估计出色温后,再选择对应的CCM。有些方案还会做线性插值,让色温过渡更平滑。

我记得有一次,客户反馈说某个场景下颜色突然跳变。查了半天,发现是色温刚好在两个CCM的切换点附近,导致颜色突变。后来改成线性插值,问题就解决了。

4.4 三者的流水线关系

这三个模块在Pipeline中的顺序是固定的:先做去马赛克,再做白平衡,最后做色彩校正矩阵。

为什么是这个顺序?因为去马赛克需要在Raw域做,白平衡也需要在Raw域或线性域做,而CCM必须在白平衡之后做——否则白平衡的增益会破坏CCM的标定结果。

我的建议: 调试时,先调好去马赛克,再调白平衡,最后调CCM。不要跳着调,否则出了问题很难定位。
传统ISP Pipeline(去马赛克→白平衡→CCM) Raw输入 去马赛克 Demosaic 白平衡 AWB 色彩校正 CCM 说明: 去马赛克在Raw域完成,输出RGB三通道;白平衡在RGB线性域做通道增益; CCM在白平衡之后做色彩空间转换,三者顺序不可颠倒。

4.5 工程实践中的注意事项

最后,分享几个我在项目中踩过的坑:

  • 去马赛克和降噪的顺序: 先降噪再去马赛克,还是先去马赛克再降噪?我建议先做轻量降噪,再去马赛克,最后做精细降噪。因为去马赛克会放大噪声,先降噪可以减少伪色。
  • 白平衡的收敛速度: AWB不能跳变太快,否则视频会闪烁。一般会用低通滤波,让色温平滑过渡。
  • CCM的精度与噪声: 矩阵系数越大,颜色越饱和,但噪声也越明显。需要根据场景动态调整——低照度下用弱CCM,高照度下用强CCM。
曾经踩过的坑: 有一次,我把CCM系数调得太强,结果在暗光下拍出来的照片全是彩色噪点。后来加了照度自适应,低照度时自动切换到弱CCM,问题才解决。

好了,这一章的内容就到这里。去马赛克、白平衡、CCM这三个模块,是传统ISP Pipeline的基石。理解透了,后面学AI ISP时才能知道哪些地方可以改进、哪些地方必须保留。