2、色彩校正算法基础:白平衡、Gamma与色彩矩阵
色彩校正,说白了就是让摄像头拍出来的颜色,跟人眼看到的颜色尽量一致。我做了这么多年图像处理IP,发现很多刚入行的同学容易忽略算法层面的理解,一上来就撸RTL代码,结果调出来的颜色怎么看怎么别扭。这一章,咱们先把算法底子打牢。
色彩校正的核心,主要围绕三个问题:白平衡(解决色偏)、Gamma校正(解决亮度非线性)、色彩矩阵变换(解决色彩串扰)。下面我一个一个讲。
2.1 白平衡算法
白平衡要解决什么问题?你想想看,同一张白纸,在日光灯下偏蓝,在白炽灯下偏黄。人眼会自动适应,但传感器不会。所以我们需要算法把“白”给找回来。
业界常用的白平衡算法,主要有两种:灰度世界法和完美反射法。我个人的习惯是,在低端Sensor上用灰度世界法,在中高端Sensor上用完美反射法或者两者的融合。
2.1.1 灰度世界法
这个算法的假设很简单:一张色彩丰富的图片,所有像素的R、G、B平均值应该趋于相等(即灰色)。如果某个通道的平均值偏高,说明存在色偏,需要压低它。
具体步骤就三步:
- 计算整帧图像R、G、B三个通道的平均值:
R_avg、G_avg、B_avg - 计算三个平均值的总平均:
Gray = (R_avg + G_avg + B_avg) / 3 - 计算每个通道的增益:
Gain_R = Gray / R_avg,Gain_G = Gray / G_avg,Gain_B = Gray / B_avg
然后,每个像素乘以对应的增益,就完成了白平衡校正。
关键点:灰度世界法对场景有要求。如果画面里大面积是单一颜色(比如一片蓝天、一片绿草地),这个假设就不成立了。我曾经在一个项目中遇到过,用户拍了一整片蓝色的海,结果灰度世界法把蓝色通道增益压得很低,整张图变成了偏黄色。后来我加了一个场景检测逻辑,才解决了这个问题。
2.1.2 完美反射法
完美反射法的假设是:图像中最亮的点(R+G+B之和最大)应该是纯白色的。所以,我们找到这些“疑似白色”的点,用它们的RGB值来计算增益。
算法流程:
- 遍历所有像素,计算每个像素的亮度值:
L = R + G + B - 找出亮度最高的前1%~5%的像素(这个比例可以配置)
- 计算这些像素的R、G、B平均值:
R_max、G_max、B_max - 计算增益:
Gain_R = 255 / R_max,以此类推
我的经验:完美反射法对高光区域敏感。如果场景中有强光源(比如太阳、灯泡),这些区域会被误判为白色参考点,导致整体偏暗。我建议在硬件实现时,加一个亮度阈值上限,排除掉过曝的像素。
两种算法的对比,我整理了一个表格:
| 特性 | 灰度世界法 | 完美反射法 |
|---|---|---|
| 假设前提 | 图像平均色为灰色 | 最亮点为白色 |
| 计算复杂度 | 低(只需累加) | 中(需要排序或阈值比较) |
| 适用场景 | 色彩丰富的自然场景 | 有明确白色参考的场景 |
| 硬件资源 | 少量加法器和除法器 | 需要比较器和FIFO |
| 典型缺陷 | 大面积单色会失效 | 强光源会导致过暗 |
2.2 Gamma校正原理
Gamma校正,说白了就是“亮度映射”。人眼对暗部细节的敏感度远高于亮部,而传感器是线性响应的。如果不做Gamma校正,暗部细节会丢失,画面看起来灰蒙蒙的。
标准Gamma校正公式:
V_out = V_in ^ (1 / Gamma)
其中Gamma值通常取2.2。为什么是2.2?这是历史原因,CRT显示器的物理特性决定的。现在虽然不用CRT了,但这个标准保留了下来。
在硬件实现中,直接用乘方运算是不现实的。我们通常用分段线性逼近或者查找表(LUT)来实现。
避坑指南:我曾经在FPGA上实现Gamma校正时,直接用浮点运算,结果资源消耗巨大,时序还跑不过。后来改用512条目的LUT,用8位输入查10位输出,效果几乎一样,资源却省了90%。记住,硬件里能用查表解决的问题,就别用计算。
Gamma校正的典型曲线(输入8bit,输出10bit):
| 输入值 | 线性输出 | Gamma校正后 | 效果 |
|---|---|---|---|
| 0 | 0 | 0 | 纯黑不变 |
| 64(暗部) | 256 | ~512 | 暗部被提亮 |
| 128(中间) | 512 | ~724 | 适度提升 |
| 192(亮部) | 768 | ~887 | 轻微压缩 |
| 255 | 1023 | 1023 | 纯白不变 |
你看,暗部(输入64)被映射到了更高的值,细节就出来了。亮部被轻微压缩,但人眼对亮部不敏感,所以感知上没问题。
2.3 色彩矩阵变换
色彩矩阵变换,解决的是传感器色彩空间到标准色彩空间的转换问题。传感器的RGB跟标准sRGB的色域不一样,直接显示会偏色。
标准形式是一个3x3矩阵:
[ R_out ] [ a11 a12 a13 ] [ R_in ]
[ G_out ] = [ a21 a22 a23 ] * [ G_in ]
[ B_out ] [ a31 a32 a33 ] [ B_in ]
这个矩阵的系数,通常由Sensor厂商提供,或者通过拍摄标准色卡(比如X-Rite ColorChecker)标定得到。
在RTL实现时,我一般用流水线结构:
- 第一级:并行计算三个乘法(比如R_out的a11*R_in、a12*G_in、a13*B_in)
- 第二级:加法器累加三个乘积
- 第三级:饱和截位(防止溢出)
我的建议:矩阵系数用定点数表示,比如Q8.8格式(8位整数,8位小数)。这样乘法结果用16位,加法用18位,最后截位到8位输出。精度够用,资源也省。
下面这张图,展示了本章三个算法的整体流程关系:
嗯,到这里,色彩校正的三大算法基础就讲完了。白平衡负责把颜色拉回中性,Gamma校正负责把亮度映射到人眼敏感区,色彩矩阵负责把传感器颜色转换到标准色域。这三个模块在硬件流水线中是串联的,顺序不能乱。