3. 传统 ISP Pipeline:黑电平校正、镜头阴影校正、坏点校正、去马赛克
好,我们正式开始进入 ISP 的核心环节。传统 ISP Pipeline 里,前四个模块——黑电平校正、镜头阴影校正、坏点校正、去马赛克——是 RAW 图进入后的第一道关卡。说白了,它们负责把传感器送来的“原始信号”变成“可用的图像数据”。
我刚开始接触 ISP 时,总觉得这些模块太基础,没什么技术含量。后来在项目里吃过亏,才明白:前处理做不好,后面 AI 再强也救不回来。你想想看,如果 RAW 图里带着固定的黑电平偏移,或者有几十个坏点没处理,后面的白平衡、降噪都会跟着跑偏。
核心观点:ISP 前处理的质量,决定了整个图像链路的“底子”。底子脏了,后面再怎么美化也是徒劳。
3.1 黑电平校正(Black Level Correction)
先聊黑电平。CMOS 传感器有个特性:即使完全遮光,每个像素也会输出一个非零的电压值。这个值就是黑电平。原因嘛,主要是 sensor 本身的暗电流和读出电路噪声。
黑电平校正的目的很简单:把 RAW 数据中这个固定的偏移量减掉,让“纯黑”对应到数字 0。
3.1.1 校正公式
假设 sensor 输出的 RAW 数据是 I_raw,黑电平值是 B,校正后的数据就是:
I_corrected = max(I_raw - B, 0)
嗯,这里要注意:不能减成负数。所以一般会用 max() 做截断。
3.1.2 黑电平的获取方式
| 方式 | 说明 | 优缺点 |
|---|---|---|
| OB(Optical Black)行/列 | sensor 边缘有被遮挡的像素,直接读它们的值作为黑电平 | 实时性好,但受温度影响大 |
| 固定值 | sensor 厂商在 datasheet 里给出的典型值 | 简单,但不同芯片有差异 |
| 动态统计 | 对全图暗部区域做统计,动态更新黑电平 | 精度高,但计算量大 |
我个人习惯用 OB 行 + 动态统计做融合。之前在某个安防项目里,sensor 温度从 -20°C 升到 60°C,黑电平漂了将近 10 个 DN(数字码值)。如果只用固定值,暗部偏色会非常明显。
小技巧:黑电平校正最好在 RAW 域做,而且要在增益之前做。否则增益会把黑电平误差一起放大,暗部噪声会变得很夸张。
3.2 镜头阴影校正(Lens Shading Correction)
镜头阴影,也叫亮度衰减。说白了,就是镜头中心比边缘亮。原因有两个:一是镜头的光学特性(cos⁴ 定律),二是 sensor 的微透镜阵列对边缘入射光的收集效率下降。
校正方法:给每个像素乘一个增益系数,边缘的增益大一些,中心的增益小一些。
3.2.1 增益网格
实际工程里,不会给每个像素单独算增益,那样太费资源。一般用 增益网格(Gain Grid):
- 把图像分成 M×N 个网格
- 每个网格存一个增益值
- 像素点的增益通过双线性插值得到
// 伪代码:双线性插值计算增益
float gain = bilinear_interp(gain_grid, x, y);
pixel_out = pixel_in * gain;
3.2.2 网格标定
增益网格怎么来的?标定。拍一张均匀亮度的灰卡,然后反推每个位置需要的增益。
我曾经遇到过一个坑:标定时用了低色温光源,结果在户外高色温下,镜头阴影的 RGB 通道衰减不一致,导致边缘偏色严重。后来我改用多色温标定,再根据当前色温做插值,才把问题解决。
注意:镜头阴影校正是乘性操作,会放大暗部噪声。边缘区域的 SNR 本来就低,增益一乘,噪声更明显。所以有些方案会在校正后加一道轻度的边缘降噪。
3.3 坏点校正(Bad Pixel Correction)
坏点,就是 sensor 上那些“不听话”的像素。要么一直亮(白点),要么一直暗(黑点),要么响应异常。原因嘛,制造缺陷、灰尘、老化都有可能。
坏点校正分两步:检测和替换。
3.3.1 坏点检测
常用的检测方法:
- 静态坏点表:出厂时标定好坏点坐标,直接查表。简单,但无法处理使用过程中新出现的坏点。
- 动态检测:比较当前像素和周围邻域的差异。如果差异超过阈值,就认为是坏点。
动态检测的经典做法:
// 以当前像素为中心,取 3x3 邻域
// 计算邻域内同色通道的中值
// 如果当前像素与中值的差 > 阈值,标记为坏点
if (abs(pixel_center - median_neighbor) > threshold) {
pixel_center = median_neighbor; // 替换
}
3.3.2 坏点替换
替换策略也有讲究:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 邻域均值 | 用周围像素的平均值替换 | 平坦区域效果好 |
| 邻域中值 | 用周围像素的中值替换 | 边缘区域更鲁棒 |
| 梯度导向插值 | 沿边缘方向做插值 | 纹理区域保真度高 |
我个人倾向用中值替换。原因很简单:均值容易被坏点“污染”,而中值对异常值不敏感。在某个手机项目里,我们试过均值替换,结果坏点周围的像素也被拉偏了,形成一圈“光晕”。换成中值后,问题立刻消失。
避坑指南:我曾经在 Bayer 格式下直接做坏点校正,没考虑同色通道。结果把 G 通道的坏点用 R 通道的像素去替换,颜色直接乱掉。记住:坏点校正必须在同色通道内做。
3.4 去马赛克(Demosaicing)
终于到了重头戏。Bayer 格式的 RAW 图,每个像素只有一个颜色通道(R、G 或 B)。去马赛克的任务就是:通过插值,给每个像素补全另外两个通道的值,得到全彩图像。
3.4.1 经典插值方法
- 最近邻插值:最简单,但锯齿严重。基本没人用。
- 双线性插值:取周围同色通道的平均值。速度快,但边缘模糊。
- 边缘导向插值:先检测边缘方向,再沿边缘方向插值。效果好很多。
边缘导向插值的核心思路:
// 以 G 通道为例,在 Bayer 格式中,G 像素占一半
// 对于 R 或 B 位置的 G 值,先计算水平和垂直方向的梯度
grad_h = abs(G_left - G_right);
grad_v = abs(G_up - G_down);
if (grad_h < grad_v) {
// 水平方向更平滑,用水平方向插值
G = (G_left + G_right) / 2;
} else if (grad_v < grad_h) {
// 垂直方向更平滑,用垂直方向插值
G = (G_up + G_down) / 2;
} else {
// 两个方向差不多,用双线性
G = (G_left + G_right + G_up + G_down) / 4;
}
3.4.2 高级方法
现在主流方案已经不用纯规则插值了,而是用 迭代优化 或 深度学习。但我觉得,理解经典方法很重要。你想想看,如果连边缘导向插值都搞不明白,怎么去调一个 AI 去马赛克网络?
我记得在某个项目中,我们尝试用 CNN 做去马赛克,效果确实比传统方法好,但计算量太大,跑不到 30fps。后来我们改用“传统方法 + 轻量 CNN 后处理”的混合方案,在效果和速度之间找到了平衡。
关键指标:去马赛克的质量通常用 PSNR 和 SSIM 衡量。但实际项目中,我更关注 伪色(False Color) 和 锯齿(Zipper Artifact)。这两个问题在文字、线条边缘特别明显,人眼非常敏感。
3.5 本章小结:前处理流程全景
这四个模块的顺序是固定的:黑电平校正 → 镜头阴影校正 → 坏点校正 → 去马赛克。为什么?因为黑电平校正是减性操作,必须在乘性操作(镜头阴影校正)之前做。坏点校正需要在 RAW 域做,去马赛克之后再做坏点校正就晚了。
下面这张图展示了整个流程:
嗯,到这里,传统 ISP 的前四个模块就讲完了。这些内容看起来基础,但每个模块都有不少工程细节。我建议你在实际项目中,多花点时间调校这些模块,把“底子”打好。后面我们会进入更复杂的模块:白平衡、去噪、锐化……到时候你会发现,前处理做得好,后面真的省心很多。