2. 传统ISP Pipeline解析
大家好,我是你们的老朋友。今天咱们来聊聊传统 ISP 里最核心的那几个模块。说实话,我入行那会儿,整个 ISP 流程就是这几板斧,现在 AI 来了,很多模块被颠覆了,但基础的东西你还是要懂。你想想看,不懂 RAW 域处理,你怎么理解 AI 降噪的输入?不懂 Demosaic,你怎么知道 AI 去马赛克到底在解决什么问题?
好,咱们直接进入正题。这一章,我会带着大家把 RAW 域处理、Demosaic、去噪、白平衡、Gamma 校正、色彩空间转换这几个环节,一个一个掰开揉碎了讲清楚。
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 的最后一步,它决定了图像最终的色彩表现。一个好的转换矩阵,能让图像色彩更准确、更自然。