ISP 流水线深度解析:各模块功能与性能开销
ISP(图像信号处理器)Pipeline,说白了就是相机从「看到光」到「出图」之间的那套魔法。我做了这么多年嵌入式相机,见过太多人把 ISP 当黑盒用——调个参数就完事,结果性能一塌糊涂。
今天咱们把这层窗户纸捅破。从 BLC 到 Sharpen,每个模块到底在干什么?吃多少算力?怎么优化?我一个一个讲清楚。
1. BLC(黑电平校正)—— 最不起眼的「底噪杀手」
BLC 做的事很简单:把传感器在无光条件下的输出归零。你想想看,CMOS 传感器即使全黑,也会有个基础电压输出,一般在 64~256 之间(12bit 数据)。
数学上就是减法:
// 伪代码示例
for each pixel:
pixel_out = pixel_raw - black_level
if pixel_out < 0: pixel_out = 0
性能开销: 极低。单次减法 + 饱和判断,每像素约 2~3 个时钟周期。
我踩过的坑: 曾经有一款 sensor,黑电平会随温度漂移。我偷懒用了固定值,结果暗光下偏色严重。后来加了温度查表校正,问题才解决。
2. LSC(镜头阴影校正)—— 边缘亮度救星
镜头天然有「中心亮、边缘暗」的特性,这叫 vignetting。LSC 就是给边缘像素「补光」。
实现方式: 通常用二维增益网格。每个像素根据坐标查表,乘以一个增益系数。
// 双线性插值示例
gain = bilinear_interp(gain_table, x, y)
pixel_out = pixel_in * gain
性能开销: 中等。查表 + 插值 + 乘法,每像素约 8~12 个时钟周期。如果增益表精度高(比如 17x17 网格),内存占用也不小。
我建议: 增益表用定点数,不要用浮点。我见过有人用 float 算,结果 ARM 平台直接卡成 PPT。
| 增益表精度 | 内存占用(12bit 数据) | 每像素周期数 |
|---|---|---|
| 9x9 网格 | ~162 bytes | ~6 |
| 17x17 网格 | ~578 bytes | ~10 |
| 33x33 网格 | ~2.2 KB | ~15 |
3. AWB(自动白平衡)—— 颜色还原的灵魂
AWB 要回答一个问题:当前场景的「白色」是什么?不同色温下,白色在 RGB 通道的响应不同。AWB 就是找到 R/G/B 的增益,让白色恢复成白色。
经典方法: 灰度世界法 + 色温曲线。
// 灰度世界法简化版
R_gain = G_avg / R_avg
B_gain = G_avg / B_avg
性能开销: 分两部分:统计阶段(遍历图像算均值)和增益应用(每像素乘法)。统计阶段开销大,但只做一次;增益应用是逐像素的。
避坑指南: 我曾经在低照度场景下,AWB 把红色增益拉到 4.0,结果噪声被放大到没法看。后来加了「增益上限钳位」,R/G/B 增益不超过 2.5,效果好了很多。
4. Demosaic(去马赛克)—— 从 Bayer 到全彩
传感器每个像素只采集一种颜色(R/G/B),Demosaic 要猜出缺失的两种颜色。这是 ISP 里最吃算力的模块之一。
常见算法对比:
| 算法 | 质量 | 每像素周期数 | 适用场景 |
|---|---|---|---|
| 双线性插值 | 低(有锯齿) | ~4 | 低功耗、预览 |
| 边缘导向插值 | 中 | ~15 | 通用 |
| 自适应插值(如 AMaZE) | 高 | ~40+ | 高画质、拍照 |
我个人的习惯: 预览用双线性,拍照用边缘导向。AMaZE 虽然画质好,但 1080P 下跑 30fps 都吃力,除非你有硬件加速。
5. CCM(色彩校正矩阵)—— 让颜色「准」起来
传感器光谱响应和人眼不一样,拍出来的红色可能偏紫、绿色可能偏黄。CCM 用 3x3 矩阵把 RGB 转换到标准色彩空间。
// 3x3 矩阵乘法
R_out = m00*R_in + m01*G_in + m02*B_in
G_out = m10*R_in + m11*G_in + m12*B_in
B_out = m20*R_in + m21*G_in + m22*B_in
性能开销: 每像素 9 次乘法 + 6 次加法,约 15~20 个时钟周期。不算重,但要注意数据精度。
我建议: CCM 矩阵系数用 Q8.8 定点格式,精度够用,速度比 float 快 3~5 倍。
6. Gamma(伽马校正)—— 亮度的「翻译官」
人眼对暗部变化敏感,对亮部变化迟钝。Gamma 校正就是把线性光信号映射到非线性空间,让暗部细节更丰富。
标准公式: out = in ^ (1/2.2)
实现方式: 查表法。预计算 256 或 1024 个点的 LUT,运行时直接查。
// 查表实现
uint8_t gamma_lut[256];
for (int i = 0; i < 256; i++) {
gamma_lut[i] = (uint8_t)(pow(i/255.0, 1/2.2) * 255);
}
// 运行时
pixel_out = gamma_lut[pixel_in];
性能开销: 极低。一次查表 + 一次赋值,约 1~2 个时钟周期。
7. Sharpen(锐化)—— 双刃剑
锐化通过增强边缘对比度,让图像看起来更清晰。但做过头了,就会出现「光晕」和噪声放大。
经典方法: Unsharp Mask(USM)。
// USM 简化版
blur = gaussian_filter(image)
edge = image - blur
sharpened = image + alpha * edge
性能开销: 高。高斯滤波本身就要 5x5 或 7x7 卷积,每像素约 25~49 次乘法加法。
避坑指南: 我曾经在夜景模式下开满锐化,结果星星周围全是白边。后来加了「边缘阈值」——只对梯度大于某个值的区域做锐化,暗部不动,效果好了很多。
ISP Pipeline 整体性能模型
下面这张图展示了 ISP 各模块的典型流水线结构,以及每个模块的相对计算开销。
性能优化核心策略
看完每个模块的开销,你应该能看出门道了。我总结几条实战经验:
- 抓大放小: Demosaic 和 Sharpen 是算力大头,优先优化这两个。其他模块再优化也省不了多少。
- 查表为王: Gamma、LSC、CCM 都可以用 LUT 加速。一次查表比一次浮点运算快 10 倍。
- 精度换速度: 定点数代替浮点数,8bit 代替 12bit(如果画质允许)。我见过有人用 32bit float 跑整个 Pipeline,结果帧率只有目标的一半。
- 流水线并行: 如果硬件支持,把 BLC、LSC、AWB 的统计阶段和像素处理阶段拆开,用双缓冲并行执行。