传统 ISP Pipeline 解析:从 RAW 到人眼可见

大家好,我是你们的老朋友。今天咱们来聊聊传统 ISP 里最核心的那几个模块。说实话,我入行那会儿,整个 ISP 流程就是这几板斧,现在 AI 来了,很多模块被颠覆了,但基础的东西你还是要懂。你想想看,不懂 RAW 域处理,你怎么理解 AI 降噪的输入?不懂 Demosaic,你怎么知道 AI 去马赛克到底在解决什么问题?

好,咱们直接进入正题。这一章,我会带着大家把 RAW 域处理、Demosaic、去噪、白平衡、Gamma 校正、色彩空间转换这几个环节,一个一个掰开揉碎了讲清楚。

传统 ISP Pipeline 核心流程 RAW 域处理 Demosaic 去噪 白平衡 Gamma 校正 色彩空间转换 关键说明: • RAW 域处理:黑电平校正、线性化、坏点校正,为后续处理打好基础。 • Demosaic:从 Bayer 模式恢复全彩,是 ISP 中最具挑战的环节之一。 • 去噪:在 RAW 域或 RGB 域进行,直接影响最终画质。 • 白平衡:校正色温,让白色物体在不同光源下看起来都是白的。 • Gamma 校正:非线性映射,适配显示设备的亮度响应特性。 • 色彩空间转换:从 RAW 的传感器色彩空间转换到 sRGB/Adobe RGB 等标准空间。

1. RAW 域处理:一切的基础

说白了,RAW 域就是传感器直接输出的原始数据。它还没经过任何处理,是最“诚实”的数据。我习惯把 RAW 域处理比作“打地基”——地基没打好,后面盖什么楼都白搭。

RAW 域处理通常包括以下几个步骤:

  • 黑电平校正:传感器在完全无光的情况下也会有微弱的输出,这叫黑电平。我们需要把这个底噪减掉。我记得有一次项目,黑电平没校准好,整个画面偏紫,折腾了两天才找到原因。
  • 线性化:有些传感器在低光下响应是非线性的,需要做线性化处理。这一步在高端相机里比较常见。
  • 坏点校正:传感器上总会有几个“坏像素”,要么一直亮,要么一直暗。我们需要用周围像素的值把它们替换掉。我建议用中值滤波来做,效果比较稳健。

核心要点:RAW 域处理的目标是让数据“干净”且“线性”,为后续的 Demosaic 和去噪做好准备。这一步做不好,后面再怎么调也是事倍功半。

2. Demosaic:从黑白到彩色

传感器上的每个像素只能感知亮度,不能感知颜色。那彩色是怎么来的?靠的是 Bayer 模式——每个像素上覆盖一个滤光片,只让红、绿或蓝光通过。Demosaic 的任务,就是从这种“马赛克”数据中恢复出每个像素的 RGB 值。

常见的 Demosaic 算法有:

  • 最近邻插值:最简单,但效果最差,边缘会有明显的锯齿。
  • 双线性插值:用周围像素的平均值来填充,效果稍好,但边缘还是模糊。
  • 方向自适应插值:根据边缘方向选择插值方向,效果明显提升。我个人比较推荐这种方法,性价比很高。
  • 基于深度学习的 Demosaic:这是目前最前沿的方法,效果最好,但计算量也最大。我在一个手机项目里用过,效果确实惊艳,但功耗也让人头疼。

避坑指南:我曾经在一个项目中,为了追求速度用了最简单的最近邻插值,结果画面边缘全是彩色伪影。后来换成了方向自适应插值,效果好了很多,速度也没慢多少。所以,别偷懒,选个合适的算法很重要。

3. 去噪:画质的守护神

去噪是 ISP 里最核心的模块之一。噪声主要来自传感器本身,尤其是在低光环境下。去噪做得好不好,直接决定了画面的纯净度。

去噪可以在 RAW 域做,也可以在 RGB 域做。我个人的经验是:RAW 域去噪效果更好,因为噪声在 RAW 域里是独立的,没有经过任何处理,更容易建模。

常见的去噪方法:

  • 空域去噪:比如高斯滤波、双边滤波、非局部均值去噪。双边滤波在保边去噪方面表现不错,我经常用它。
  • 变换域去噪:比如小波去噪、BM3D。BM3D 效果很好,但速度慢,适合离线处理。
  • 深度学习去噪:比如 DnCNN、FFDNet。效果最好,但需要大量训练数据和算力。

注意:去噪是一把双刃剑。去噪太强,画面会变模糊,细节丢失;去噪太弱,噪声又会影响观感。我建议根据场景动态调整去噪强度,比如低光场景强去噪,白天场景弱去噪。

4. 白平衡:让白色回归白色

你有没有发现,在日光灯下拍的照片会偏绿,在白炽灯下会偏黄?这是因为不同光源的色温不同。白平衡的任务,就是校正这种色偏,让白色物体在任何光源下看起来都是白的。

白平衡的核心是估计光源的色温,然后调整 R、G、B 三个通道的增益。常用的方法有:

  • 灰度世界假设:假设场景中所有颜色的平均值是灰色,然后据此调整增益。这个方法简单,但容易受大块纯色物体影响。
  • 完美反射假设:假设场景中最亮的点就是白色,然后据此调整增益。这个方法在大多数场景下效果不错。
  • 基于学习的白平衡:用深度学习模型来估计色温,效果最好,但需要大量标注数据。

我的经验:在实际项目中,我通常会把灰度世界和完美反射两种方法结合起来,取长补短。比如,先用灰度世界做一个初步估计,再用完美反射做精细调整。这样效果比较稳健。

5. Gamma 校正:适配人眼和显示设备

人眼对亮度的感知是非线性的——对暗部变化更敏感,对亮部变化不敏感。而显示设备的亮度响应也是非线性的。Gamma 校正的作用,就是让图像在显示设备上看起来更自然。

Gamma 校正的公式很简单:V_out = V_in ^ (1/γ)。标准的 γ 值是 2.2,但不同设备可能不同。

// 一个简单的 Gamma 校正函数
float gamma_correct(float value, float gamma) {
    return powf(value, 1.0f / gamma);
}

// 应用 Gamma 校正到整个图像
for (int i = 0; i < width * height; i++) {
    image[i].r = gamma_correct(image[i].r, 2.2f);
    image[i].g = gamma_correct(image[i].g, 2.2f);
    image[i].b = gamma_correct(image[i].b, 2.2f);
}

小技巧:Gamma 校正通常在色彩空间转换之前做,这样可以避免在非线性空间里做色彩处理,减少误差。我习惯把 Gamma 校正放在 Pipeline 的靠后位置。

6. 色彩空间转换:从传感器到标准空间

传感器的色彩空间是设备相关的,不同传感器的色彩空间不同。为了让图像在不同设备上显示一致,我们需要把它转换到标准色彩空间,比如 sRGB、Adobe RGB 或 DCI-P3。

色彩空间转换通常是一个 3x3 的矩阵乘法:

// 从传感器色彩空间到 sRGB 的转换矩阵
float matrix[3][3] = {
    {1.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f},
    {0.0f, 0.0f, 1.0f}
};

// 应用色彩空间转换
for (int i = 0; i < width * height; i++) {
    float r = image[i].r;
    float g = image[i].g;
    float b = image[i].b;
    image[i].r = matrix[0][0] * r + matrix[0][1] * g + matrix[0][2] * b;
    image[i].g = matrix[1][0] * r + matrix[1][1] * g + matrix[1][2] * b;
    image[i].b = matrix[2][0] * r + matrix[2][1] * g + matrix[2][2] * b;
}

这个矩阵怎么来的?通常是通过标定得到的。你拿一个标准色卡,在传感器下拍一张,然后跟标准值对比,就能算出这个矩阵。我在项目中做过好几次标定,说实话,挺枯燥的,但这一步不能省。

总结一下:色彩空间转换是 ISP Pipeline 的最后一步,它决定了图像最终的色彩表现。一个好的转换矩阵,能让图像色彩更准确、更自然。