无论是手机拍照、监控相机还是工业视觉检测,色彩偏差都是最容易被用户察觉的画质问题。明明是白墙,拍出来却泛着蓝光;本该红润的肤色,成像后灰蒙蒙发绿。要解决这类问题,通常要从两个环节入手:一是拍摄端的白平衡,二是传感器数据到显示色彩空间的色彩矩阵转换。这两个环节一个决定整体色调是否准确,另一个决定各种颜色的相对关系是否还原到位,缺一不可。

白平衡为什么能纠正偏色
要理解白平衡,先要理解色温这个概念。光源的色温用开尔文(K)表示,数值越低光越偏暖(偏黄红),数值越高光越偏冷(偏蓝)。白炽灯的色温大约在2800K,正午日光约5500K,阴天可以到7000K以上。人眼有很强的色彩恒常性,无论在什么光源下看白纸,大脑都会自动把它“脑补”成白色。但图像传感器没有这种能力,它只是忠实地记录到达每个像素的光电信号,所以在白炽灯下拍的白纸会整体偏黄,在阴天户外拍的会整体偏蓝。
白平衡的作用,就是估计出当前场景光源的色温,然后对R、G、B三个通道施加不同的增益,把中性色(本应是灰白的区域)拉回灰色。假设估计出的光源偏暖,算法就会降低红色通道增益、提高蓝色通道增益,让画面整体色调恢复正常。这个过程可以简单理解为在RGB三通道上乘一个对角矩阵,R和G的增益比决定了色温校正量。
常见的自动白平衡算法
最经典的是灰世界算法(Gray World)。它的假设是:一幅内容足够丰富的图像,其R、G、B三个通道的平均值应该趋于相等,也就是整体趋近于灰色。算法实现非常简单,分别计算三个通道的均值,然后以绿色通道为基准,用均值比对红蓝通道做增益补偿。
import numpy as np
def gray_world_awb(img):
# img 为 float32 格式,取值范围 0~1
r_mean = img[..., 0].mean()
g_mean = img[..., 1].mean()
b_mean = img[..., 2].mean()
# 以绿通道为参考,计算红蓝通道增益
gain_r = g_mean / (r_mean + 1e-6)
gain_b = g_mean / (b_mean + 1e-6)
img[..., 0] = np.clip(img[..., 0] * gain_r, 0, 1)
img[..., 2] = np.clip(img[..., 2] * gain_b, 0, 1)
return img</code>灰世界算法的优点是计算量极小、实现容易,但局限也很明显:当画面大面积被单一颜色占据时,比如一片草地或一面红墙,算法会误判光源,把绿色或红色当成偏色强行“纠正”,结果反而造成严重失真。
另一种常用思路是完美反射法(White Patch),基于的假设是:场景中总存在高光反射点,这些点的亮度接近光源本身,应当是白色。算法找到图像中最亮的一批像素,把它们当作白点来计算增益。这个方法对存在镜面高光的场景效果不错,但遇到强逆光或画面中本身有高饱和亮色区域时容易出错。工程上更常见的做法是把多种算法的结果做加权融合,再结合人脸检测、色温传感器数据做修正,甚至建立色温-增益查找表,在不同光源环境下用标定数据插值,稳定性比单一算法好得多。
色彩矩阵:从传感器色彩到标准色彩空间
白平衡解决的是整体色调问题,但即使白平衡完全准确,其他颜色仍可能偏色。原因在于,相机传感器的光谱响应曲线和人眼完全不同。传感器上的 Bayer 滤色片对红绿蓝光的透过特性,与CIE标准观察者光谱响应曲线存在明显差异,导致传感器输出的RGB值不是“标准RGB”,比如拍一片纯红色物体,传感器输出的R值可能偏高,G和B也不是零,各种颜色的相对比例都和人眼感知有偏差。
色彩校正矩阵(Color Correction Matrix,简称CCM)就是为了解决这个问题。它用一个3x3矩阵对白平衡后的RGB向量做线性变换,把传感器自己的色彩空间映射到sRGB等标准色彩空间。矩阵的每一个系数都需要通过标定获得:在标准光源下拍摄包含多个色块的色卡(如经典的24色卡),用色度计或分光测色仪测量色卡的真实色度值,再与相机拍到的值建立对应关系,通过最小二乘法拟合出最优矩阵。
import numpy as np
def fit_ccm(sensor_rgb, target_rgb):
# sensor_rgb: 形状 (N, 3),色卡上 N 个色块的传感器测量值
# target_rgb: 形状 (N, 3),对应的标准色彩空间目标值
# 求解 sensor_rgb @ M = target_rgb 的最优 M
M, _, _, _ = np.linalg.lstsq(sensor_rgb, target_rgb, rcond=None)
return M
def apply_ccm(img, M):
shape = img.shape
flat = img.reshape(-1, 3)
corrected = flat @ M
return np.clip(corrected, 0, 1).reshape(shape)标定得到的3x3矩阵中,对角线元素一般大于1,非对角元素是负数或小于1的正数,起到交叉补偿的作用。举个例子,若传感器对红色的响应里混入了过多的绿色成分,矩阵中对应R行的G系数就会是负值,把多余的绿色扣回来。实际ISP流程中,CCM通常作用在白平衡之后、伽马校正之前,因为矩阵是在线性域标定的,如果先做了伽马压缩再乘矩阵,结果会严重偏离标定预期,这是新手调色时最常见的错误之一。
工程调校中的取舍与建议
纯数学最优的CCM不一定带来最好的观感。如果只追求色差最小化,把24色卡的误差全部压到最低,画面可能显得平淡、饱和度不足,用户反而觉得“不好看”。因此很多厂商会有意在标定目标之外加入偏好调整,比如轻微提升饱和度、让肤色偏向暖红、让天空更蓝,这些都是在准确与观感之间做平衡。肤色还原尤其关键,人眼对肤色的容忍度远低于对其他颜色,肤色偏绿或偏灰几乎立刻会被察觉,所以不少方案会在CCM之外单独做肤色检测与肤色专用校正。
实际排查色彩偏差问题时,建议按链路顺序逐段验证:先确认光源和AWB是否准确,用灰卡或中性物体检查白平衡;再检查CCM配置是否与当前传感器匹配,别把A型号的矩阵用到B型号上;最后核对伽马、色调映射等后处理环节是否引入了额外偏色。链路中任何一环出错,最终色彩都会崩掉,分段定位比整体盲调效率高得多。掌握白平衡与色彩矩阵这两把钥匙,绝大多数色彩偏差问题都能迎刃而解。