032、色彩校正核心:CCM矩阵标定、3D-LUT映射与色彩空间转换的精度控制
032、色彩校正核心CCM矩阵标定、3D-LUT映射与色彩空间转换的精度控制去年夏天某款旗舰机在印度市场翻车了。用户拍出来的肤色偏紫尤其是深色皮肤直接变成茄子色。我们连夜拉会议ISP团队说3A没问题sensor团队说raw数据干净最后定位到CCM矩阵——标定用的色卡是X-Rite的24色但印度人肤色落在色卡之外的区域矩阵外推时炸了。从那以后我对色彩校正的敬畏心就上来了。从sensor raw到sRGB中间藏着多少坑先捋清楚一件事sensor输出的raw数据经过黑电平校正、去马赛克、白平衡之后进入色彩校正模块。这个模块的核心任务是把sensor的“原生色彩空间”映射到标准色彩空间sRGB、BT.709、DCI-P3等。映射方式主要有两种CCM矩阵和3D-LUT。CCM矩阵是线性变换3x3或者4x3的矩阵乘上去就完事。3D-LUT是非线性映射本质是一个三维查表插值出结果。前者计算量小适合实时处理后者精度高能处理非线性色偏但占内存、费算力。手机ISP里CCM是标配3D-LUT通常用在高端机或者专业模式。安防和车载领域CCM够用但遇到特殊光源比如隧道里的钠灯、雪地反射的蓝光3D-LUT的优势就出来了。CCM标定别被24色卡骗了CCM标定的标准流程拍色卡 - 提取色块RGB值 - 和标准值做最小二乘拟合。听起来简单实操全是细节。色卡选择X-Rite的24色卡是行业基准但它的色块分布偏“标准”覆盖不了真实世界的极端色。比如深蓝色、高饱和红色、肤色渐变区。我见过有人只用24色卡标定结果拍蓝天时偏青拍红花时偏橙。后来我们加了自定义色卡包含50个色块专门覆盖肤色、植被、天空等常见场景。光源控制标定必须在D656500K标准光源下进行。但实验室的D65灯箱和户外自然光差远了。灯箱的色温均匀性、显色指数CRI必须达标否则标出来的矩阵在户外就失效。有一次我们用的灯箱CRI只有90标出来的矩阵在阳光下拍人脸偏黄后来换成CRI 95的灯箱才解决。拟合算法最小二乘是基础但容易过拟合。我习惯加正则项L2正则化让矩阵系数不要太大。代码里这样写# 这里踩过坑直接用np.linalg.lstsq会得到大系数噪声放大严重# 别这样写coeff np.linalg.lstsq(X, Y)[0]# 加正则项lambda取0.01左右coeffnp.linalg.solve(X.T Xlambda_*np.eye(3),X.T Y)正则化之后矩阵系数变小噪声抑制变好但精度会轻微下降。需要根据sensor的噪声水平调lambda高噪声sensor比如暗光场景lambda要大一些。验证环节标定完别急着烧固件。拿一组独立测试图不是标定用的色卡算色差Delta E。Delta E 3算及格 1算优秀。但注意Delta E是平均值要看最大值。有一次平均Delta E只有1.5但某个深蓝色块Delta E到了8拍夜空时直接翻车。3D-LUT映射精度和算力的博弈3D-LUT的输入是RGB值输出是校正后的RGB值。LUT的网格密度决定了精度17x17x17是入门33x33x33是主流65x65x65是发烧级。网格越密精度越高但内存占用指数级增长。插值算法三线性插值是标配但边缘色块容易出锯齿。我见过有人用四面体插值精度更高但计算量大。手机ISP里三线性插值够用配合dithering抖动可以掩盖量化误差。标定流程和CCM类似但需要更多色块。我们通常用1000色块覆盖整个色彩空间。标定数据来自两部分一是标准色卡比如ColorChecker SG二是合成数据在标准色彩空间里均匀采样然后模拟sensor响应。合成数据能补足色卡覆盖不到的角落。精度控制3D-LUT的精度瓶颈不在标定在量化。LUT的输入输出都是8bit或10bit量化误差不可避免。我习惯在标定后做一轮“误差补偿”对每个网格点计算实际输出和理想输出的偏差存成另一个小LUT。运行时先查主LUT再查补偿LUT精度能提升0.5个Delta E。代码里这样实现// 这里踩过坑直接查表后不做补偿高饱和区域色偏明显// 别这样写out_r lut[r][g][b].r;// 加补偿查主LUT再查补偿LUTuint16_tbase_rlut_main[r][g][b].r;int16_tdelta_rlut_delta[r][g][b].r;out_rclamp(base_rdelta_r,0,1023);色彩空间转换矩阵精度决定一切色彩空间转换比如sensor RGB - sRGB - DCI-P3本质是矩阵乘法。但这里的坑在于矩阵系数是浮点数硬件里只能用定点数实现。定点化精度不够转换后色偏。定点化策略16位定点数Q8.8格式8位整数8位小数精度够用。但要注意矩阵乘法后结果可能溢出需要做饱和处理。我习惯用Q10.6格式牺牲一点小数精度换取更大的动态范围。白点适配不同色彩空间的白点不同D65、D50等。转换时要做白点适应常用的有Bradford变换。但Bradford变换的矩阵系数是固定的实际应用中需要根据光源色温动态调整。我们做过一个自适应版本根据AWB输出的色温插值出Bradford矩阵精度提升明显。Gamma校正sRGB的Gamma是2.2但sensor的响应是线性的。转换时先做线性化再做Gamma。顺序不能错否则色偏。而且Gamma校正的LUT要精确8bit LUT不够至少10bit。有一次我们用了8bit LUT暗部色阶断裂拍夜景时天空出现条纹。实战经验从调试到量产场景覆盖标定数据要覆盖低照度、高照度、混合光源。低照度下sensor噪声大CCM矩阵会放大噪声需要做降噪预处理。高照度下sensor饱和色彩信息丢失矩阵会失效。混合光源比如室内日光灯窗外自然光最难搞3D-LUT比CCM更靠谱。温度漂移sensor的响应随温度变化色彩校正参数也要变。我们做过实验sensor温度从25°C升到60°CCCM矩阵系数变化了5%。量产时我们在sensor模组里加温度传感器根据温度查表切换CCM参数。模组一致性同一个型号的模组不同批次、不同产线色彩响应有差异。我们做过统计同一批次Delta E差异在0.5以内不同批次可能到2。量产时每批模组都要重新标定或者做“微调”用少量色卡比如6色做快速标定修正主矩阵。调试工具我习惯用Python写一个离线调试工具输入raw图输出校正后的图像实时显示Delta E分布。调试时先调CCM再调3D-LUT最后调色彩空间转换。每一步都要验证不要跳步。个人经验性建议别迷信24色卡。它只是起点不是终点。根据产品定位定制色卡覆盖真实场景的极端色。手机要加肤色和植被车载要加交通标志色和路面色安防要加低照度下的色彩。CCM和3D-LUT不是二选一。我习惯用CCM做粗调3D-LUT做精调。CCM负责把整体色偏拉回来3D-LUT负责局部色块的微调。这样既保证实时性又保证精度。量化误差是隐形杀手。硬件实现时定点化精度、LUT的bit数、插值算法每一步都影响最终效果。调试时用浮点模型跑一遍再和定点模型对比看Delta E差异。如果差异超过0.5说明量化有问题。温度补偿不是锦上添花。对于车载和安防产品温度范围宽-40°C到85°C温度漂移必须考虑。手机虽然温度范围窄但长时间录像时sensor发热也会漂移。加温度传感器成本不高效果明显。验证环节要“暴力”。不要只测标准色卡要测真实场景人脸、天空、草地、红色物体、蓝色物体、低照度、高照度、混合光源。每个场景拍100张算平均Delta E和最大Delta E。最大Delta E超过5的必须返工。最后说一句色彩校正没有“完美”只有“够用”。产品定位不同精度要求不同。手机要讨好眼球车载要准确识别安防要稳定可靠。搞清楚需求再决定用CCM还是3D-LUT用多少网格用多少bit。别为了追求理论精度把系统搞复杂了量产时出问题更麻烦。