简介:个人学习分享,如有错误,欢迎批评指正。
一、什么是颜色校正矩阵(Color Correction Matrix)?
颜色校正矩阵(CCM) 是一个 3×3矩阵,其作用是将图像中每个像素的RGB通道值通过矩阵运算调整为目标颜色空间中的RGB值。简单来说,CCM将图像从传感器输出的原始颜色空间转换为标准色彩空间(如sRGB、XYZ或Rec.2020),或者对颜色进行校正,以达到色彩还原的目标。
CCM 的基本形式
假设图像的每个像素的RGB值为:
(RinGinBin)\begin{pmatrix} R_{in} \\ G_{in} \\ B_{in} \end{pmatrix} RinGinBin
经过颜色校正矩阵 C\mathbf{C}C 变换后,输出的RGB值为:
(RoutGoutBout)=C×(RinGinBin)\begin{pmatrix} R_{out} \\ G_{out} \\ B_{out} \end{pmatrix} = \mathbf{C} \times \begin{pmatrix} R_{in} \\ G_{in} \\ B_{in} \end{pmatrix} RoutGoutBout =C× RinGinBin
其中, C\mathbf{C}C 就是 颜色校正矩阵(CCM),它定义了如何将原始的RGB通道映射到目标颜色空间。CCM矩阵的系数决定了颜色的转换、色调调整、色彩饱和度等因素。
二、为什么需要颜色校正矩阵(CCM)?

CCM之前和之后对比图
1. 传感器和显示设备之间的颜色差异
不同的图像传感器和显示设备(如LCD、OLED显示屏)使用不同的颜色空间。这意味着即使传感器捕捉到了图像,颜色也可能和实际场景有所偏差。CCM的作用就是校正这些偏差,确保输出图像的颜色符合标准的视觉要求。
2. 去除色偏和调整白平衡
色偏(color cast)是指图像在某种光照条件下表现出不自然的颜色。CCM通过调整矩阵系数,帮助去除色偏,恢复真实色彩。尤其是在低光照或高感光条件下,传感器可能会出现明显的色偏,CCM能够有效修正这些问题。此外,白平衡校正也是CCM的一个重要应用,帮助图像中的白色物体恢复自然的白色,避免偏蓝或偏红的现象。
3. 多摄像头一致性
现代智能手机通常配备多个摄像头(如广角、长焦、微距摄像头),它们可能使用不同厂商的传感器,导致颜色表现的差异。CCM的应用能够统一各摄像头的色彩表现,确保同一场景拍摄的多张图片色彩一致,避免多摄像头间的颜色不匹配问题。
三、颜色校正矩阵的计算方法
关于如何计算CCM矩阵的三种常见方法:通过标定板数据计算、通过算法优化计算、以及基于机器学习的自动化计算方法。
1. 通过标定板数据计算
通过使用已知的标准色卡(例如X-Rite ColorChecker),我们可以得到传感器的实际输出值,并将其与色卡上的真实颜色进行对比。这种方法是获取CCM矩阵最直接和最常见的方式。具体步骤如下:
1.1 标定板和色卡
在标定过程中,我们使用的标准色卡,如X-Rite ColorChecker,包含了已知的标准颜色。这些颜色的RGB值是预先定义和标准化的,通常是通过严格的色彩校准设备测量得到的。标定过程依赖于已知的这些标准颜色值。
1.2 拍摄标定图像
首先,我们通过图像传感器(例如相机)拍摄一张标准色卡的图像。传感器捕捉到的每个像素的RGB值(称为原始RGB值)通常会有所偏差。为了校正这个偏差,我们需要计算一个映射矩阵,即颜色校正矩阵,将传感器输出的RGB值转化为真实的标准颜色。
1.3 计算颜色误差
通过对比标准色卡的RGB值和传感器拍摄得到的RGB值,我们可以计算出图像传感器的误差。例如,如果标准色卡上的某个颜色的RGB值是 (Rs,Gs,Bs)(R_s, G_s, B_s)(Rs,Gs,Bs),而传感器捕捉到的颜色是 (Ri,Gi,Bi)(R_i, G_i, B_i)(Ri,Gi,Bi),则误差为:
ΔC=(Ri Gi Bi)−(Rs Gs Bs)\Delta C = \begin{pmatrix} R_i \ G_i \ B_i \end{pmatrix} - \begin{pmatrix} R_s \ G_s \ B_s \end{pmatrix} ΔC=(Ri Gi Bi)−(Rs Gs Bs)
1.4 使用最小二乘法进行矩阵优化
计算误差后,下一步是通过最小二乘法 来优化颜色校正矩阵,使得误差最小。最小二乘法的目标是最小化所有颜色误差的平方和,并计算出使得该误差最小的矩阵系数。
为了实现这一点,我们可以将颜色校正问题转化为一个线性系统的求解问题。假设传感器捕捉到的原始RGB值为 (Ri Gi Bi)\begin{pmatrix} R_i \ G_i \ B_i \end{pmatrix}(Ri Gi Bi),而标准色卡中的真实RGB值为 (Rs Gs Bs)\begin{pmatrix} R_s \ G_s \ B_s \end{pmatrix}(Rs Gs Bs)。我们希望找到一个矩阵 C\mathbf{C}C,使得:
C×(Ri Gi Bi)=(Rs Gs Bs)\mathbf{C} \times \begin{pmatrix} R_i \ G_i \ B_i \end{pmatrix} = \begin{pmatrix} R_s \ G_s \ B_s \end{pmatrix}C×(Ri Gi Bi)=(Rs Gs Bs)
通过最小二乘法来优化该矩阵,使得误差最小化,得到一个最优的颜色校正矩阵 C\mathbf{C}C。
2. 通过算法优化计算
除了使用标定板数据来直接计算CCM矩阵外,还可以通过一些算法优化方法来求解最优矩阵,尤其是通过图像数据进行自动调整。这种方法依赖于在不同的图像条件下,通过计算传感器输出和目标颜色之间的差异,自动优化CCM矩阵。
2.1 基于误差最小化的优化方法
通过误差最小化,我们可以利用不同图像数据集,求解一个最优的CCM矩阵。这种方法通常使用最小二乘法或梯度下降法等优化算法,利用传感器的实际输出数据和目标标准数据之间的差异来调整CCM矩阵。
步骤:
- 选择训练数据集:通过选择多组具有已知颜色目标的图像数据集来进行训练。通常需要涵盖多种光照条件、环境变化和传感器特性。
- 计算误差:在每组数据中,计算实际传感器输出的RGB与目标标准RGB之间的误差。常见的误差度量方式是计算均方误差(MSE)或者色差(如CIEDE2000)。
- 优化矩阵:通过最小化所有误差的平方和,使用优化算法(如最小二乘法或梯度下降法)来优化矩阵系数,得到最优的CCM。
2.2 使用目标函数优化
通过定义一个目标函数来优化CCM矩阵的系数,使得最终计算出的RGB图像与标准色卡颜色尽可能匹配。常见的目标函数可以是误差函数:
E=∑i=1N(∣C×Ri−Rs∣2)E = \sum_{i=1}^{N} \left( | \mathbf{C} \times \mathbf{R_i} - \mathbf{R_s} |^2 \right)E=i=1∑N(∣C×Ri−Rs∣2)
其中, NNN 是训练样本的数量, Ri\mathbf{R_i}Ri 是传感器的RGB值, Rs\mathbf{R_s}Rs 是标准RGB值, C\mathbf{C}C 是待求解的颜色校正矩阵。
3. 基于机器学习的自动化计算
随着机器学习技术的进步,神经网络逐渐被应用于自动计算CCM矩阵,尤其是在多传感器、多场景条件下。通过大量的训练数据,神经网络可以学习到如何从传感器输出的RAW图像生成标准色彩空间的RGB图像,并自动估算出最优的CCM矩阵。
3.1 神经网络训练方法
使用卷积神经网络(CNN)或其他深度学习模型来学习从传感器图像到目标色彩空间的映射。训练过程中,网络会根据输入的RAW图像和目标RGB图像之间的差异,自动优化矩阵中的权重系数。
训练步骤:
- 准备训练数据集:收集大量有标定图像的训练数据集,包括各种光照、场景、传感器的颜色输出。每一组数据包括传感器拍摄的RAW图像和其对应的标准RGB图像。
- 网络模型设计:设计一个卷积神经网络或其他深度学习模型,通过模型学习RGB与标准颜色之间的映射关系。
- 训练网络:使用梯度下降法或其他优化算法,训练神经网络,使得其输出与标准RGB图像尽可能接近,从而自动推导出最优的CCM矩阵。
3.2 自动化和实时调节
神经网络还可以应用于实时调节CCM矩阵,尤其是在动态环境中。当光照条件或传感器参数发生变化时,神经网络可以根据新的输入图像自动调整CCM矩阵,使得图像颜色始终保持真实和一致。
通过上述三种方法,我们可以获得 Color Correction Matrix (CCM):
- 通过标定板数据计算:通过拍摄标准色卡图像,比较传感器输出和标准色卡之间的误差,通过最小二乘法等优化算法计算得到CCM矩阵。
- 通过算法优化计算:通过误差最小化和算法优化(如梯度下降法),自动调整矩阵系数,使得传感器输出的颜色接近标准色卡。
- 基于机器学习的自动化计算:使用深度学习技术,通过训练神经网络自动计算CCM矩阵,适应不同传感器和场景条件。
通过这些方法,我们可以为图像信号处理(ISP)链路中的颜色校正提供高效且精确的解决方案,确保图像输出的色彩准确性和一致性。
四、颜色校正矩阵(CCM)值的含义
颜色校正矩阵(CCM)本质上是一个 3×3 的线性变换矩阵。
它里面的 9 个值,每一个都表示:
- 输入的某个颜色通道,对输出的某个颜色通道贡献多少。
也就是说,CCM 不是简单地"R 调 R、G 调 G、B 调 B",而是允许 三个输入通道互相混合,从而把传感器原始颜色空间映射到目标颜色空间。
1. 先写出 CCM 的标准形式
设输入颜色是:
RinGinBin\begin{bmatrix} R_{in}\\ G_{in}\\ B_{in} \end{bmatrix} RinGinBin
经过 CCM 之后输出为:
RoutGoutBout=m11m12m13m21m22m23m31m32m33RinGinBin\begin{bmatrix} R_{out}\\ G_{out}\\ B_{out} \end{bmatrix}= \begin{bmatrix} m_{11} & m_{12} & m_{13}\\ m_{21} & m_{22} & m_{23}\\ m_{31} & m_{32} & m_{33} \end{bmatrix} \begin{bmatrix} R_{in}\\ G_{in}\\ B_{in} \end{bmatrix} RoutGoutBout = m11m21m31m12m22m32m13m23m33 RinGinBin
展开后就是:
Rout=m11Rin+m12Gin+m13BinGout=m21Rin+m22Gin+m23BinBout=m31Rin+m32Gin+m33BinR_{out}=m_{11}R_{in}+m_{12}G_{in}+m_{13}B_{in}\\ G_{out}=m_{21}R_{in}+m_{22}G_{in}+m_{23}B_{in}\\ B_{out}=m_{31}R_{in}+m_{32}G_{in}+m_{33}B_{in}Rout=m11Rin+m12Gin+m13BinGout=m21Rin+m22Gin+m23BinBout=m31Rin+m32Gin+m33Bin
2. 矩阵里每个值到底表示什么
下面逐个解释。
第一行:决定输出红色 RoutR_{out}Rout
Rout=m11Rin+m12Gin+m13BinR_{out}=m_{11}R_{in}+m_{12}G_{in}+m_{13}B_{in}Rout=m11Rin+m12Gin+m13Bin
这三个系数的含义是:
- m11m_{11}m11:输入红色 RinR_{in}Rin 对输出红色 RoutR_{out}Rout 的贡献
- m12m_{12}m12:输入绿色 GinG_{in}Gin 对输出红色 RoutR_{out}Rout 的贡献
- m13m_{13}m13:输入蓝色 BinB_{in}Bin 对输出红色 RoutR_{out}Rout 的贡献
也就是说,输出红色不一定只来自输入红色,还可能吸收一部分绿色和蓝色的信息。
第二行:决定输出绿色 GoutG_{out}Gout
Gout=m21Rin+m22Gin+m23BinG_{out}=m_{21}R_{in}+m_{22}G_{in}+m_{23}B_{in}Gout=m21Rin+m22Gin+m23Bin
- m21m_{21}m21:输入红色对输出绿色的贡献
- m22m_{22}m22:输入绿色对输出绿色的贡献
- m23m_{23}m23:输入蓝色对输出绿色的贡献
第三行:决定输出蓝色 BoutB_{out}Bout
Bout=m31Rin+m32Gin+m33BinB_{out}=m_{31}R_{in}+m_{32}G_{in}+m_{33}B_{in}Bout=m31Rin+m32Gin+m33Bin
- m31m_{31}m31:输入红色对输出蓝色的贡献
- m32m_{32}m32:输入绿色对输出蓝色的贡献
- m33m_{33}m33:输入蓝色对输出蓝色的贡献
3. 从"位置"上怎么理解这 9 个值
你可以记一个规律:
第 i 行第 j 列的元素 mijm_{ij}mij,表示"输入第 j 个通道,对输出第 i 个通道的贡献系数"。
也就是:
- 行:决定"输出谁"
- 列:表示"来自谁"
所以: - 第一行:输出 R 怎么组成
- 第二行:输出 G 怎么组成
- 第三行:输出 B 怎么组成
而: - 第一列:输入 R 分别对输出 R/G/B 的影响
- 第二列:输入 G 分别对输出 R/G/B 的影响
- 第三列:输入 B 分别对输出 R/G/B 的影响
4. 对角线元素和非对角线元素分别代表什么
这是理解 CCM 最关键的一点。
4.1 对角线元素
对角线是:
m11,m22,m33m_{11}, m_{22}, m_{33}m11,m22,m33
它们表示:
- 红到红
- 绿到绿
- 蓝到蓝
也就是"本通道对本通道"的保留或缩放。
通常它们是最主要的项,往往数值较大。
例如:
- m11m_{11}m11 大,表示输出红色主要由输入红色决定
- m22m_{22}m22 大,表示绿色主要保留原来的绿色
- m33m_{33}m33 大,表示蓝色主要保留原来的蓝色
4.2 非对角线元素
非对角线元素表示通道之间的交叉混合。
例如:
- m12m_{12}m12:绿色混到红色里多少
- m13m_{13}m13:蓝色混到红色里多少
- m21m_{21}m21:红色混到绿色里多少
- m23m_{23}m23:蓝色混到绿色里多少
- m31m_{31}m31:红色混到蓝色里多少
- m32m_{32}m32:绿色混到蓝色里多少
这些项的存在非常重要,因为真实相机传感器的 RGB 光谱响应并不理想,三个通道之间常常"串色",所以必须通过交叉混合来修正。
5. 为什么会出现负数
CCM 里很多元素并不是全是正数,出现负数是非常正常的 。
比如一个矩阵可能是:
1.6−0.4−0.2−0.21.4−0.20.0−0.51.5\begin{bmatrix} 1.6 & -0.4 & -0.2\\ -0.2 & 1.4 & -0.2\\ 0.0 & -0.5 & 1.5 \end{bmatrix} 1.6−0.20.0−0.41.4−0.5−0.2−0.21.5
这里的负数表示:
- 某个输入通道会被"减掉一部分",用来抵消串色或色偏。
例如:
- m12=−0.4m_{12}=-0.4m12=−0.4
表示在生成输出红色时,要减去一部分输入绿色
这通常意味着传感器红通道里混入了过多"绿成分",所以要扣掉一些 - m23=−0.2m_{23}=-0.2m23=−0.2
表示输出绿色时减去一些输入蓝色
这说明蓝色对绿色有串扰,需要纠正
所以负数不是异常,而是 颜色解耦和纠偏 的体现。
6. 举一个具体例子来理解
假设 CCM 为:
M=1.5−0.3−0.2−0.11.3−0.20.0−0.41.4\mathbf{M}= \begin{bmatrix} 1.5 & -0.3 & -0.2\\ -0.1 & 1.3 & -0.2\\ 0.0 & -0.4 & 1.4 \end{bmatrix}M= 1.5−0.10.0−0.31.3−0.4−0.2−0.21.4
输入一个像素:
RinGinBin=10012080 \begin{bmatrix} R_{in}\\ G_{in}\\ B_{in} \end{bmatrix}= \begin{bmatrix} 100\\ 120\\ 80 \end{bmatrix} RinGinBin = 10012080
则:
输出红色
Rout=1.5×100+(−0.3)×120+(−0.2)×80R_{out}=1.5\times100+(-0.3)\times120+(-0.2)\times80Rout=1.5×100+(−0.3)×120+(−0.2)×80
Rout=150−36−16=98R_{out}=150-36-16=98Rout=150−36−16=98
意思是:
- 先保留较强的红色
- 再减去部分绿色
- 再减去部分蓝色
说明原始红通道可能被 G/B 污染了,所以要净化。
输出绿色
Gout=−0.1×100+1.3×120+(−0.2)×80G_{out}=-0.1\times100+1.3\times120+(-0.2)\times80Gout=−0.1×100+1.3×120+(−0.2)×80
Gout=−10+156−16=130G_{out}=-10+156-16=130Gout=−10+156−16=130
输出蓝色
Bout=0×100+(−0.4)×120+1.4×80B_{out}=0\times100+(-0.4)\times120+1.4\times80Bout=0×100+(−0.4)×120+1.4×80
Bout=0−48+112=64B_{out}=0-48+112=64Bout=0−48+112=64
这个过程本质上就是把"传感器看到的颜色"变成"更接近标准颜色空间的颜色"。
7. 这些值在视觉上分别会影响什么
虽然 9 个值是整体联动的,但从直觉上可以这样理解:
1. 对角线项过大或过小
m11m_{11}m11 变大
输出红色更强,画面会更偏暖、偏红。
m22m_{22}m22 变大
绿色更强,画面可能发绿,亮度结构也可能更明显。
m33m_{33}m33 变大
蓝色更强,画面会更冷、偏蓝。
2. 非对角项变化
m12m_{12}m12 变化
改变绿色向红色的混合,常影响黄、橙、肤色区域。
m13m_{13}m13 变化
改变蓝色向红色的混合,常影响紫、洋红、冷暖平衡。
m21、m23m_{21}、m_{23}m21、m23
影响绿色纯度,常影响草地、植物、肤色过渡。
m31、m32m_{31}、m_{32}m31、m32
影响蓝色纯度,常影响天空、水面、阴影区颜色。
8. 能不能把 CCM 看成"颜色旋转+拉伸"
从线性代数角度看,CCM 这个 3×3 矩阵,本质上对 RGB 颜色空间做了:
- 缩放
- 旋转
- 剪切
- 通道混合
也就是说,它不是简单地把每个通道单独拉亮,而是在三维颜色空间中重新调整颜色坐标的位置。
所以 CCM 的作用不是"调亮一点红色"这么简单,而是:
把传感器原始颜色空间整体变换到目标颜色空间。
CCM 中每个元素,表示某个输入颜色通道对某个输出颜色通道的线性贡献系数。
行看输出,列看输入。对角线是本通道保留,非对角线是通道间混合,正值表示增加,负值表示抵消。
五、CCM 的 9 个值是怎么通过色卡拟合出来
1. 为什么可以用色卡拟合 CCM
CCM 的任务是:把相机传感器测到的颜色,变换到目标标准颜色空间。
也就是说,相机看到的颜色和"真实标准颜色"之间,存在一个映射关系。
如果我们假设这个映射在某个工作条件下可以近似成线性变换 ,那么就有:
y=Mx\mathbf{y} = \mathbf{M}\mathbf{x}y=Mx
其中:
- x\mathbf{x}x:传感器测到的 RGB
- y\mathbf{y}y:目标标准 RGB(或者 XYZ)
- M\mathbf{M}M:待求的 3×3 CCM
而色卡的作用就是:
给你很多组"已知输入"和"已知正确输出"的配对样本。
比如一张 ColorChecker 有 24 个色块,那么你就有 24 组:
- 输入:相机拍到的某个色块 RGB
- 输出:这个色块标准的参考值
这样, M\mathbf{M}M 就不是拍脑袋设的,而是可以从这些样本里"拟合"出来。
2. CCM 的数学形式
设 CCM 为:
M=m11m12m13m21m22m23m31m32m33\mathbf{M}= \begin{bmatrix} m_{11} & m_{12} & m_{13}\\ m_{21} & m_{22} & m_{23}\\ m_{31} & m_{32} & m_{33} \end{bmatrix}M= m11m21m31m12m22m32m13m23m33
对于某一个色块,假设传感器测到的 RGB 为:
x=RsGsBs\mathbf{x}= \begin{bmatrix} R_s\\ G_s\\ B_s \end{bmatrix}x= RsGsBs
它对应的标准目标值为:
y=RtGtBt\mathbf{y}= \begin{bmatrix} R_t\\ G_t\\ B_t \end{bmatrix}y= RtGtBt
则有:
RtGtBt=m11m12m13m21m22m23m31m32m33RsGsBs \begin{bmatrix} R_t\\ G_t\\ B_t \end{bmatrix}= \begin{bmatrix} m_{11} & m_{12} & m_{13}\\ m_{21} & m_{22} & m_{23}\\ m_{31} & m_{32} & m_{33} \end{bmatrix} \begin{bmatrix} R_s\\ G_s\\ B_s \end{bmatrix} RtGtBt = m11m21m31m12m22m32m13m23m33 RsGsBs
展开就是:
Rt=m11Rs+m12Gs+m13BsGt=m21Rs+m22Gs+m23BsBt=m31Rs+m32Gs+m33BsR_t = m_{11}R_s + m_{12}G_s + m_{13}B_s\\ G_t = m_{21}R_s + m_{22}G_s + m_{23}B_s\\ B_t = m_{31}R_s + m_{32}G_s + m_{33}B_sRt=m11Rs+m12Gs+m13BsGt=m21Rs+m22Gs+m23BsBt=m31Rs+m32Gs+m33Bs
这就是一组三元线性方程。
3. 为什么需要很多色块
因为 CCM 一共有 9 个未知数:
m11,m12,m13,m21,m22,m23,m31,m32,m33m_{11},m_{12},m_{13},m_{21},m_{22},m_{23},m_{31},m_{32},m_{33}m11,m12,m13,m21,m22,m23,m31,m32,m33
理论上,3 个色块就可以提供 9 个方程:
- 每个色块提供 3 个方程
- 3 个色块一共 9 个方程
似乎可以解 9 个未知数。
但是工程上几乎不会只用 3 个点,原因有三个:
1. 噪声
相机拍出来的 RGB 有噪声。
2. 色卡测量误差
色块均值不可能完全精确。
3. 真实颜色映射并非完全线性
CCM 只是线性近似,不可能对所有颜色都绝对精确。
所以工程上会用很多色块,比如 24 色卡、96 色卡,甚至更多采样点,然后做:
最小二乘拟合
即:求一个最优矩阵,让总体误差最小。
4. 如何把很多色块写成矩阵方程
假设我们有 N 个色块。
把相机测得的 RGB 组成一个矩阵:
X=Rs1Gs1Bs1Rs2Gs2Bs2⋮⋮⋮RsNGsNBsN(N×3)X= \begin{bmatrix} R_{s1} & G_{s1} & B_{s1}\\ R_{s2} & G_{s2} & B_{s2}\\ \vdots & \vdots & \vdots\\ R_{sN} & G_{sN} & B_{sN} \end{bmatrix} \quad (N\times 3)X= Rs1Rs2⋮RsNGs1Gs2⋮GsNBs1Bs2⋮BsN (N×3)
把目标标准 RGB 组成矩阵:
Y=Rt1Gt1Bt1Gt2Gt2Bt2⋮⋮⋮RtNGtNBtN(N×3)Y= \begin{bmatrix} R_{t1} & G_{t1} & B_{t1}\\ G_{t2} & G_{t2} & B_{t2}\\ \vdots & \vdots & \vdots\\ R_{tN} & G_{tN} & B_{tN} \end{bmatrix} \quad (N\times 3)Y= Rt1Gt2⋮RtNGt1Gt2⋮GtNBt1Bt2⋮BtN (N×3)
注意这里更标准地写应该是:
Y=Rt1Gt1Bt1Rt2Gt2Bt2⋮⋮⋮RtNGtNBtNY= \begin{bmatrix} R_{t1} & G_{t1} & B_{t1}\\ R_{t2} & G_{t2} & B_{t2}\\ \vdots & \vdots & \vdots\\ R_{tN} & G_{tN} & B_{tN} \end{bmatrix}Y= Rt1Rt2⋮RtNGt1Gt2⋮GtNBt1Bt2⋮BtN
那么拟合关系可写成:
Y=XMTY = X M^TY=XMT
因为每一行都是一个样本:
Rt,;Gt,;Bt=Rs,;Gs,;BsMTR_t,;G_t,;B_t = R_s,;G_s,;B_s M^TRt,;Gt,;Bt=Rs,;Gs,;BsMT
也有人写成列向量形式:
YT=MXTY^T = M X^TYT=MXT
本质完全一样。
5. 为什么能用最小二乘解
因为真实情况下不可能有一个矩阵让所有色块都精确满足:
Y=XMTY = X M^TY=XMT
于是我们只能找一个让误差最小的矩阵:
minM∣XMT−Y∣F2\min_M |XM^T - Y|_F^2Mmin∣XMT−Y∣F2
这里 ∣∣⋅∣∣F||\cdot||_F∣∣⋅∣∣F 表示 Frobenius 范数,本质就是:所有色块、所有通道的平方误差总和最小
这是标准的线性最小二乘问题。
其解析解是:
MT=(XTX)−1XTYM^T = (X^T X)^{-1}X^T YMT=(XTX)−1XTY
所以:
M=YTX(XTX)−1M = Y^T X (X^T X)^{-1}M=YTX(XTX)−1
更常见的工程实现是直接写成:
bash
M_T = np.linalg.lstsq(X, Y, rcond=None)[0]
M = M_T.T
6. 一个最简单的"刚好可精确求解"的手算例子
先举一个 3 个色块,刚好 9 个方程解 9 个未知数 的例子。
例子设定
假设相机拍到 3 个色块的传感器 RGB(输入)分别是:
色块 1
x1=1005020\mathbf{x}_1= \begin{bmatrix} 100\\ 50\\ 20 \end{bmatrix}x1= 1005020
色块 2
x2=3012040\mathbf{x}_2= \begin{bmatrix} 30\\ 120\\ 40 \end{bmatrix}x2= 3012040
色块 3
x3=2040150\mathbf{x}_3= \begin{bmatrix} 20\\ 40\\ 150 \end{bmatrix}x3= 2040150
对应的标准目标 RGB(输出)分别是:
色块 1
y1=1104818\mathbf{y}_1= \begin{bmatrix} 110\\ 48\\ 18 \end{bmatrix}y1= 1104818
色块 2
y2=2812535\mathbf{y}_2= \begin{bmatrix} 28\\ 125\\ 35 \end{bmatrix}y2= 2812535
色块 3
y3=1838160\mathbf{y}_3= \begin{bmatrix} 18\\ 38\\ 160 \end{bmatrix}y3= 1838160
7. 把问题拆成三个独立的小问题
这是最容易理解的方式。
因为:
Rt=m11Rs+m12Gs+m13BsR_t = m_{11}R_s + m_{12}G_s + m_{13}B_sRt=m11Rs+m12Gs+m13Bs
所以第一行的三个未知数 (m11,m12,m13) (m_{11},m_{12},m_{13})(m11,m12,m13) 可以单独求。
同理:
- 第二行求 (m21,m22,m23)(m_{21},m_{22},m_{23})(m21,m22,m23)
- 第三行求 (m31,m32,m33)(m_{31},m_{32},m_{33})(m31,m32,m33)
7.1 先求第一行:输出红色
根据 3 个色块列方程:
色块 1
100m11+50m12+20m13=110100m_{11} + 50m_{12} + 20m_{13} = 110100m11+50m12+20m13=110
色块 2
30m11+120m12+40m13=2830m_{11} + 120m_{12} + 40m_{13} = 2830m11+120m12+40m13=28
色块 3
20m11+40m12+150m13=1820m_{11} + 40m_{12} + 150m_{13} = 1820m11+40m12+150m13=18
写成矩阵:
100502030120402040150m11m12m13=1102818 \begin{bmatrix} 100 & 50 & 20\\ 30 & 120 & 40\\ 20 & 40 & 150 \end{bmatrix} \begin{bmatrix} m_{11}\\ m_{12}\\ m_{13} \end{bmatrix}= \begin{bmatrix} 110\\ 28\\ 18 \end{bmatrix} 100302050120402040150 m11m12m13 = 1102818
记:
A=100502030120402040150A= \begin{bmatrix} 100 & 50 & 20\\ 30 & 120 & 40\\ 20 & 40 & 150 \end{bmatrix}A= 100302050120402040150
则:
Am11m12m13=1102818A\begin{bmatrix}m_{11}\\m_{12}\\m_{13}\end{bmatrix}= \begin{bmatrix} 110\\28\\18 \end{bmatrix}A m11m12m13 = 1102818
这个目标红色大致表现为:
- 第一块红多一点:110 对应原来 100
- 第二、三块红都略减:30→28,20→18
所以你大概能猜到这第一行接近:1.1,0,0\]\[1.1,0,0\]\[1.1,0,0
再验证一下: - 色块1: 1.1×100=1101.1\times100=1101.1×100=110
- 色块2: 1.1×30=331.1\times30=331.1×30=33,但目标是 28,说明还得减一些 G/B
- 色块3: 1.1×20=221.1\times20=221.1×20=22,目标是 18,也得减一些 G/B
所以更合理地设:
Rt≈1.1Rs−0.02Gs−0.01BsR_t \approx 1.1R_s - 0.02G_s - 0.01B_sRt≈1.1Rs−0.02Gs−0.01Bs
验证:
色块1
1.1×100−0.02×50−0.01×20=110−1−0.2=108.81.1\times100 - 0.02\times50 - 0.01\times20 =110-1-0.2=108.81.1×100−0.02×50−0.01×20=110−1−0.2=108.8
还差一点。
把系数微调一下,设第一行为:
1.15,−0.08,−0.03\]\[1.15,-0.08,-0.03\]\[1.15,−0.08,−0.03
验证:
色块1
1.15×100−0.08×50−0.03×20=115−4−0.6=110.41.15\times100 - 0.08\times50 - 0.03\times20 =115-4-0.6=110.41.15×100−0.08×50−0.03×20=115−4−0.6=110.4
色块2
1.15×30−0.08×120−0.03×40=34.5−9.6−1.2=23.71.15\times30 - 0.08\times120 - 0.03\times40 =34.5-9.6-1.2=23.71.15×30−0.08×120−0.03×40=34.5−9.6−1.2=23.7
偏低。
所以这说明真实解还是需要正规线性求解,不适合全靠猜。下面我给你走"正规可手算"路径。
8. 用"矩阵求逆"手算思路讲清楚
对于第一行,有:
Amr=brA\mathbf{m}_r=\mathbf{b}_rAmr=br
其中:
mr=m11m12m13,br=1102818 \mathbf{m}r=\begin{bmatrix}m{11}\\m_{12}\\m_{13} \end{bmatrix}, \quad \mathbf{b}_r= \begin{bmatrix} 110\\28\\18 \end{bmatrix} mr= m11m12m13 ,br= 1102818
理论上:
mr=A−1br\mathbf{m}_r=A^{-1}\mathbf{b}_rmr=A−1br
同理:
mg=A−1bg,mb=A−1bb\mathbf{m}_g=A^{-1}\mathbf{b}_g,\qquad \mathbf{m}_b=A^{-1}\mathbf{b}_bmg=A−1bg,mb=A−1bb
其中:
bg=4812538,bb=1835160\mathbf{b}_g= \begin{bmatrix} 48\\125\\38 \end{bmatrix}, \quad \mathbf{b}_b= \begin{bmatrix} 18\\35\\160 \end{bmatrix}bg= 4812538 ,bb= 1835160
所以一旦你把 A−1A^{-1}A−1 求出来,三个通道都能解。
9. 真正适合手算的完整例子
我们换一组色块,让输入更容易算,但仍然符合 CCM 拟合原理。
假设相机测到 3 个色块输入为:
X=100000100000100X= \begin{bmatrix} 100 & 0 & 0\\ 0 & 100 & 0\\ 0 & 0 & 100 \end{bmatrix}X= 100000100000100
即:
- 第一个色块主要是红
- 第二个色块主要是绿
- 第三个色块主要是蓝
对应目标标准值为:
Y=110−50 10955 08120Y= \begin{bmatrix} 110 & -5 & 0\ 10 & 95 & 5\ 0 & 8 & 120 \end{bmatrix}Y=110−50 10955 08120
这意味着:
- 纯红输入,目标变成 (110,-5,0)
- 纯绿输入,目标变成 (10,95,5)
- 纯蓝输入,目标变成 (0,8,120)
这里虽然有负值,数学上是允许的;实际后面会 clamp。
9.1 建模
有:
Y=XMTY = X M^TY=XMT
由于
X=100000100000100=100IX= \begin{bmatrix} 100 & 0 & 0\\ 0 & 100 & 0\\ 0 & 0 & 100 \end{bmatrix} =100IX= 100000100000100 =100I
所以:
Y=100MTY = 100 M^TY=100MT
因此:
MT=1100YM^T = \frac{1}{100}YMT=1001Y
即:
MT=1.10−0.0500.100.950.0500.081.20M^T= \begin{bmatrix} 1.10 & -0.05 & 0\\ 0.10 & 0.95 & 0.05\\ 0 & 0.08 & 1.20 \end{bmatrix}MT= 1.100.100−0.050.950.0800.051.20
所以:
M=1.100.100−0.050.950.0800.051.20M= \begin{bmatrix} 1.10 & 0.10 & 0\\ -0.05 & 0.95 & 0.08\\ 0 & 0.05 & 1.20 \end{bmatrix}M= 1.10−0.0500.100.950.0500.081.20
这就是拟合出来的 CCM。
9.2 验证这 9 个值是什么意思
这个 CCM 为:
M=1.100.100−0.050.950.0800.051.20M= \begin{bmatrix} 1.10 & 0.10 & 0\\ -0.05 & 0.95 & 0.08\\ 0 & 0.05 & 1.20 \end{bmatrix}M= 1.10−0.0500.100.950.0500.081.20
展开后:
Rout=1.10Rin+0.10Gin+0BinR_{out}=1.10R_{in}+0.10G_{in}+0B_{in}Rout=1.10Rin+0.10Gin+0Bin
Gout=−0.05Rin+0.95Gin+0.08BinG_{out}=-0.05R_{in}+0.95G_{in}+0.08B_{in}Gout=−0.05Rin+0.95Gin+0.08Bin
Bout=0Rin+0.05Gin+1.20BinB_{out}=0R_{in}+0.05G_{in}+1.20B_{in}Bout=0Rin+0.05Gin+1.20Bin
含义就是:
- 输出红色主要来自输入红色,但也吸收一点绿色
- 输出绿色主要来自输入绿色,但要减一点红色、加一点蓝色
- 输出蓝色主要来自输入蓝色,同时吸收少量绿色
9.3 代一个实际像素进去算
假设某个像素输入为:
RinGinBin=8010060 \begin{bmatrix} R_{in}\\G_{in}\\B_{in} \end{bmatrix}= \begin{bmatrix} 80\\100\\60 \end{bmatrix} RinGinBin = 8010060
则:
输出红色
Rout=1.10×80+0.10×100+0×60=88+10=98R_{out}=1.10\times80+0.10\times100+0\times60=88+10=98Rout=1.10×80+0.10×100+0×60=88+10=98
输出绿色
Gout=−0.05×80+0.95×100+0.08×60=−4+95+4.8=95.8G_{out}=-0.05\times80+0.95\times100+0.08\times60=-4+95+4.8=95.8Gout=−0.05×80+0.95×100+0.08×60=−4+95+4.8=95.8
输出蓝色
Bout=0×80+0.05×100+1.20×60=5+72=77B_{out}=0\times80+0.05\times100+1.20\times60=5+72=77Bout=0×80+0.05×100+1.20×60=5+72=77
所以校正后像素为:
9895.877\begin{bmatrix} 98\\95.8\\77 \end{bmatrix} 9895.877
10. 再讲真实工程里怎么做最小二乘拟合
上面那个例子是为了让你手算彻底理解。
但真实工程里不是 3 个色块,而是很多个,比如 24 个色块。
这时通常这样做。
1. 采集数据
对每个色块,先取中心区域均值,得到传感器线性 RGB:
(Rs1,Gs1,Bs1),...,(RsN,GsN,BsN)(R_{s1},G_{s1},B_{s1}),\dots,(R_{sN},G_{sN},B_{sN})(Rs1,Gs1,Bs1),...,(RsN,GsN,BsN)
同时查标准参考值,得到目标值:
(Rt1,Gt1,Bt1),...,(RtN,GtN,BtN)(R_{t1},G_{t1},B_{t1}),\dots,(R_{tN},G_{tN},B_{tN})(Rt1,Gt1,Bt1),...,(RtN,GtN,BtN)
注意这里一般要求:
- 去黑电平
- 去白平衡前或白平衡后要定义清楚
- 在线性域做
- 不要在 gamma 后拟合
2. 写成矩阵
X=Rs1Gs1Bs1Rs2Gs2Bs2⋮⋮⋮RsNGsNBsNX= \begin{bmatrix} R_{s1} & G_{s1} & B_{s1}\\ R_{s2} & G_{s2} & B_{s2}\\ \vdots & \vdots & \vdots\\ R_{sN} & G_{sN} & B_{sN} \end{bmatrix}X= Rs1Rs2⋮RsNGs1Gs2⋮GsNBs1Bs2⋮BsN
Y=Rt1Gt1Bt1Rt2Gt2Bt2⋮⋮⋮RtNGtNBtNY= \begin{bmatrix} R_{t1} & G_{t1} & B_{t1}\\ R_{t2} & G_{t2} & B_{t2}\\ \vdots & \vdots & \vdots\\ R_{tN} & G_{tN} & B_{tN} \end{bmatrix}Y= Rt1Rt2⋮RtNGt1Gt2⋮GtNBt1Bt2⋮BtN
3. 最小二乘解
求:
minM∣XMT−Y∣F2\min_{M}|XM^T-Y|_F^2Mmin∣XMT−Y∣F2
解析解:
MT=(XTX)−1XTYM^T=(X^TX)^{-1}X^TYMT=(XTX)−1XTY
4. 为什么分三次回归也可以
因为每个输出通道都可以单独拟合:
拟合输出 R
minmr∣Xmr−yr∣2\min_{\mathbf{m}_r}|X\mathbf{m}_r-\mathbf{y}r|^2mrmin∣Xmr−yr∣2
拟合输出 G
minmg∣Xmg−yg∣2\min{\mathbf{m}_g}|X\mathbf{m}_g-\mathbf{y}g|^2mgmin∣Xmg−yg∣2
拟合输出 B
minmb∣Xmb−yb∣2\min{\mathbf{m}_b}|X\mathbf{m}_b-\mathbf{y}_b|^2mbmin∣Xmb−yb∣2
最后把三组系数组起来,就是 CCM。
这也是你最容易理解的方式。
11. 真实工程里还会加哪些约束
真实 ISP 里,CCM 拟合通常不只是裸最小二乘,还会加一些工程约束。
11.1 行和约束(neutral preservation)
为了让中性灰尽量保持中性,常要求每一行系数和接近 1:
m11+m12+m13≈1m_{11}+m_{12}+m_{13}\approx 1m11+m12+m13≈1
m21+m22+m23≈1m_{21}+m_{22}+m_{23}\approx 1m21+m22+m23≈1
m31+m32+m33≈1m_{31}+m_{32}+m_{33}\approx 1m31+m32+m33≈1
这样灰色 (k,k,k) 经过变换后不容易明显偏色。
11.2 权重拟合
有些色块比别的更重要,例如:
- 肤色
- 中性灰
- 高饱和主色
那么目标函数会写成加权形式:
minM∑i=1Nwi∣Mxi−yi∣2\min_M \sum_{i=1}^N w_i |\mathbf{M}\mathbf{x}_i-\mathbf{y}_i|^2Mmini=1∑Nwi∣Mxi−yi∣2
11.3 在 XYZ 或 Lab 空间优化
实际工程中,经常不是直接最小化 RGB 误差,而是:
- 先把目标标准值转到 XYZ
- 或再转到 Lab
- 再最小化色差 ΔE\Delta EΔE
因为人眼对颜色误差的感知不是简单的 RGB 欧氏距离。
在实际相机标定中,通常先拍摄标准色卡,提取每个色块在线性 RAW 域中的平均 RGB 值,作为传感器颜色响应;再根据色卡在目标光源下的标准参考值构建目标颜色数据。设传感器响应矩阵为 XXX,目标颜色矩阵为 YYY,则通过最小化整体颜色误差
minM∣XMT−Y∣F2\min_M |XM^T-Y|_F^2Mmin∣XMT−Y∣F2
求得最优解
MT=(XTX)−1XTYM^T=(X^TX)^{-1}X^TYMT=(XTX)−1XTY
其转置 MMM 即为颜色校正矩阵 CCM。矩阵中的 9 个系数分别表示输入 RGB 三通道对输出 RGB 三通道的线性贡献关系。
六、24 色 ColorChecker 做 CCM 标定
6 步展开:
- 拍图准备与采集规范
- 色卡检测与 24 个色块取值
- RAW 线性化与预处理
- 构建目标参考值
- 最小二乘拟合 CCM
- 用 ΔE 做验证、诊断与回调
1. 先明确:CCM 标定到底在标什么
CCM 标定的本质是:
在某个给定光源条件下,找到一个 3×3 矩阵,把相机传感器的线性 RGB 响应,映射到目标标准颜色空间,使 24 个色块尽量接近标准参考颜色。
所以它不是"任意场景万能矩阵",而是:
- 与 传感器光谱响应 有关
- 与 镜头透过率 有关
- 与 光源 SPD 有关
- 与 RAW 预处理方式 有关
这也是为什么工程里通常不是一个 CCM 走天下,而是:
- A 光一个 CCM
- D65 一个 CCM
- TL84 一个 CCM
- 中间色温靠插值
2. 第一步:拍图准备与采集规范
这一步非常关键。
很多人后面拟合得不好,不是最小二乘有问题,而是前面的拍摄数据就不干净。
2.1 使用的色卡
常见是 X-Rite / Calibrite ColorChecker Classic 24,共 24 个色块,包括:
- 6 个中性灰阶块
- 6 个典型彩色块
- 12 个常见自然/高饱和颜色块
它的价值在于:
- 有中性灰,可检查中性轴
- 有肤色、天空蓝、叶绿色等典型自然色
- 有高饱和颜色,可看 gamut 边界表现
2.2 光源条件
CCM 必须在已知、稳定、均匀的光源下标定。常见标准光源有:
- D65:日光
- A:钨丝灯,偏暖
- TL84:商场荧光灯
- D50:印刷常用
要求:
- 光源稳定,不闪烁
- 光照尽量均匀打在色卡上
- 尽量避免环境杂散光污染
- 色卡表面不要有高光反射
工程建议
色卡最好与镜头光轴基本垂直,轻微倾角避开镜面反光。
如果色卡上有明显 specular highlight,这张图最好不要拿来做 CCM。
2.3 曝光设置
为了做 CCM,曝光不能乱设。
原则是:
- 不过曝
- 不欠曝太多
- 色卡亮块有足够 SNR
- 暗块不被噪声主导
通常做法:
- 使用固定 ISO
- 固定曝光时间
- 固定增益
- 尽量让白块/亮灰块接近但不达到饱和
一个经验点
不要把最亮白块打到饱和边缘,否则高光 roll-off 或 clipping 会破坏线性关系。
2.4 RAW 采集要求
必须尽量拿到接近传感器线性响应的 RAW ,不要用已经做过强 ISP 的 JPEG。
理想情况:
- Bayer RAW
- 保留 black level 信息
- 保留 sensor metadata
- 关闭自动增强
- 不要 gamma
- 不要 tone mapping
- 不要 sharpen / saturation / local contrast
因为 CCM 是在线性域工作的。
如果你拿的是已经 gamma 过的 RGB,再去拟合 CCM,结果会很差。
3. 第二步:色卡检测与 24 个色块取值
这一步的目标是:
从拍到的 RAW 图里,稳定地提取每个色块对应的相机响应值。
3.1 色卡定位
有两种做法:
做法 A:手工标定
人工点击四角点,然后透视矫正。
做法 B:自动检测
通过边缘、轮廓、矩形检测、模板匹配等自动找到色卡,再做 homography 对齐。
工程上常见流程是:
- 检测色卡外框
- 做透视变换,把色卡拉正
- 分成 4×6 网格
- 对每个色块提取中心 ROI
3.2 为什么不能取整块平均
因为色块边缘容易受这些因素影响:
- 色块边界印刷过渡
- 透视误差
- 镜头模糊
- 去马赛克/插值混色
- 阴影或高光边缘
所以通常不会取整块,而是取每个色块的中心区域,例如:
- 中间 40%
- 中间 50%
- 中间 60%
这样更稳。
3.3 取 RAW 还是取 demosaic 后 RGB
做 CCM 标定时,更推荐:
在线性 RGB 域取值,而不是在 Bayer 单通道直接取值,也不是在 gamma RGB 上取值。
常见做法是:
- 先对 Bayer 做基础预处理
- 再 demosaic 成线性 RGB
- 再在每个色块中心取均值
原因是 CCM 本身定义在 RGB 三通道空间里。
如果你直接在 Bayer 格式取值,需要先把 CFA 通道关系处理清楚,更麻烦。
3.4 每个色块得到什么数据
最后你会得到 24 组相机测量值:
(Rs1,Gs1,Bs1),...,(Rs24,Gs24,Bs24)(R_{s1},G_{s1},B_{s1}),\dots,(R_{s24},G_{s24},B_{s24})(Rs1,Gs1,Bs1),...,(Rs24,Gs24,Bs24)
这里下标 sss 表示 sensor / source。
通常这些值应是:
- black level corrected
- demosaiced
- linear
- white-balanced 或明确说明未 AWB
这个"是否已白平衡"非常重要,后面我单独说。
4. 第三步:RAW 线性化与预处理
这一步最容易被忽视,但它其实决定了 CCM 是否能拟合得像样。
4.1 黑电平校正(Black Level Correction)
传感器 RAW 一般不是从 0 开始,存在黑电平偏置。
假设某像素原始值为 xrawx_{raw}xraw,黑电平为 b,则应先做:
xlin=xraw−bx_{lin} = x_{raw} - bxlin=xraw−b
如果不减黑电平:
- 暗色块会整体偏高
- 中性灰轴会歪
- 线性关系被破坏
- CCM 拟合出的矩阵会补偿错误偏置
4.2 饱和点归一化
如果希望不同 bit depth 或不同曝光条件下可比,通常还会归一化:
xnorm=xraw−bw−bx_{norm} = \frac{x_{raw}-b}{w-b}xnorm=w−bxraw−b
其中:
- b:black level
- w:white level / saturation level
这样数据会落在 0,1 左右范围。
4.3 去白平衡还是加白平衡?
这是一个关键问题。
CCM 一般有两种标定口径:
口径 A:AWB 后再拟合 CCM
即先对白卡或灰卡做通道增益校正,再拟合 CCM。
流程:
RGBsensor→AWBRGBwb→CCMRGBtargetRGB_{sensor} \xrightarrow{AWB} RGB_{wb} \xrightarrow{CCM} RGB_{target}RGBsensorAWB RGBwbCCM RGBtarget
这也是最常见的 ISP 链路方式。
口径 B:不做 AWB,直接把 AWB 和 CCM 一起吸收到矩阵里
不太常见,因为会让矩阵与 illuminant 耦合更重,插值和维护更麻烦。
工程上更推荐
先做 AWB,再拟合 CCM。
因为这样 CCM 主要负责"色彩空间校正",AWB 负责"中性轴拉正",职责更清晰。
4.4 去 gamma / tone mapping
如果图像经过了 gamma:
xgamma=xlin1/γx_{gamma} = x_{lin}^{1/\gamma}xgamma=xlin1/γ
那通道关系已经不是线性的。
此时再拟合 3×3 线性矩阵,会把非线性问题错误地塞给 CCM。
所以 CCM 标定必须尽量在线性域做。
4.5 demosaic 的影响
理论上 CCM 作用于 RGB,所以你常常要先 demosaic。
但要注意:
- demosaic 本身会引入通道混合
- 色卡边缘可能受插值影响
- 所以一定取色块中心区域
如果用的是低噪、高质量 RAW,demosaic 对色块中心均值的影响通常可接受。
5. 第四步:构建目标参考值
5.1 目标值从哪里来
ColorChecker 的每个色块都有标准参考值,但这个"标准值"要注意它的定义空间,常见有:
- XYZ
- Lab
- 某标准 illuminant 下的反射参考
- 有时也提供 sRGB 参考值
工程上最稳妥的方法通常是:
先拿到色卡的标准 XYZ 参考值,再根据目标颜色空间转换。
因为 XYZ 更接近颜色测量基础空间。
5.2 为什么很多流程喜欢拟合到 XYZ
因为 XYZ 是设备无关色空间。
传感器 RGB 是设备相关的,目标如果直接用某一套 sRGB 数值,有时会把显示端假设也混进来。
所以常见流程是:
RGBsensor→CCMXYZRGB_{sensor} \xrightarrow{CCM} XYZRGBsensorCCM XYZ
然后再通过标准矩阵:
XYZ→sRGBXYZ \rightarrow sRGBXYZ→sRGB
当然,也可以直接拟合到目标线性 sRGB,工程上两种都能做,只要口径统一。
5.3 如果目标是 sRGB,要注意什么
必须是线性 sRGB,不是 gamma 编码的 sRGB。
也就是要先把标准 sRGB 反 gamma,得到线性值,再拟合。
否则:
- 左边是线性 sensor RGB
- 右边是非线性 gamma RGB
矩阵很难拟好。
6. 第五步:最小二乘拟合 CCM
这一步就是数学核心。
1. 构建输入矩阵 X
把 24 个色块相机测得的 RGB 组成矩阵:
X=Rs1Gs1Bs1Rs2Gs2Bs2⋮⋮⋮Rs24Gs24Bs24X= \begin{bmatrix} R_{s1} & G_{s1} & B_{s1}\\ R_{s2} & G_{s2} & B_{s2}\\ \vdots & \vdots & \vdots\\ R_{s24} & G_{s24} & B_{s24} \end{bmatrix}X= Rs1Rs2⋮Rs24Gs1Gs2⋮Gs24Bs1Bs2⋮Bs24
维度是:24×324 \times 324×3
2. 构建目标矩阵 Y
把 24 个色块的参考目标值组成矩阵:
Y=Rt1Gt1Bt1Rt2Gt2Bt2⋮⋮⋮Rt24Gt24Bt24Y= \begin{bmatrix} R_{t1} & G_{t1} & B_{t1}\\ R_{t2} & G_{t2} & B_{t2}\\ \vdots & \vdots & \vdots\\ R_{t24} & G_{t24} & B_{t24} \end{bmatrix}Y= Rt1Rt2⋮Rt24Gt1Gt2⋮Gt24Bt1Bt2⋮Bt24
也是:24×324 \times 324×3
这里下标 t 表示 target。
3. 建立模型
我们假设存在一个 3×3 矩阵 MMM,满足:
Y≈XMTY \approx X M^TY≈XMT
也就是每一个色块:
Rt,Gt,Bt≈Rs,Gs,BsMTR_t,G_t,B_t \approx R_s,G_s,B_sM^TRt,Gt,Bt≈Rs,Gs,BsMT
4. 最小二乘目标
因为 24 个色块不可能被一个线性矩阵完美拟合,所以求:
minM∣XMT−Y∣F2\min_M |XM^T - Y|_F^2Mmin∣XMT−Y∣F2
即总平方误差最小。
解析解:
MT=(XTX)−1XTYM^T = (X^TX)^{-1}X^TYMT=(XTX)−1XTY
所以:
M=YTX(XTX)−1M = Y^T X (X^TX)^{-1}M=YTX(XTX)−1
在代码里通常直接写:
bash
M_T, _, _, _ = np.linalg.lstsq(X, Y, rcond=None)
M = M_T.T
5. 为什么分三次回归更容易理解
因为 3 个输出通道可以分别拟合。
输出 R
minmr∣Xmr−yr∣2\min_{\mathbf{m}_r}|X\mathbf{m}_r-\mathbf{y}r|^2mrmin∣Xmr−yr∣2
输出 G
minmg∣Xmg−yg∣2\min{\mathbf{m}_g}|X\mathbf{m}_g-\mathbf{y}g|^2mgmin∣Xmg−yg∣2
输出 B
minmb∣Xmb−yb∣2\min{\mathbf{m}_b}|X\mathbf{m}_b-\mathbf{y}_b|^2mbmin∣Xmb−yb∣2
其中:
- mr\mathbf{m}_rmr 是 CCM 第一行转置
- mg\mathbf{m}_gmg 是第二行转置
- mb\mathbf{m}_bmb 是第三行转置
最后把三组系数拼起来,就是 CCM。
6. 是否要加约束
真实 ISP 里通常会加一些约束或先验。
中性约束
希望中性灰尽量保持中性,常要求每行和接近 1:
m11+m12+m13≈1m_{11}+m_{12}+m_{13}\approx 1m11+m12+m13≈1
m21+m22+m23≈1m_{21}+m_{22}+m_{23}\approx 1m21+m22+m23≈1
m31+m32+m33≈1m_{31}+m_{32}+m_{33}\approx 1m31+m32+m33≈1
加权拟合
有些色块更重要,比如:
- 灰阶
- 肤色
- 天空蓝
- 叶绿
则可做加权最小二乘:
minM∑i=124wi∣Mxi−yi∣2\min_M \sum_{i=1}^{24} w_i |M x_i - y_i|^2Mmini=1∑24wi∣Mxi−yi∣2
去异常点
如果某个色块取值被反光污染、SNR 太差、ROI 错了,可以剔除或降权。
7. 第六步:如何把 CCM 用起来
得到矩阵 MMM 后,对于任一线性 RGB 像素:
RoutGoutBout=MRinGinBin \begin{bmatrix} R_{out}\\G_{out}\\B_{out} \end{bmatrix}= M \begin{bmatrix} R_{in}\\G_{in}\\B_{in} \end{bmatrix} RoutGoutBout =M RinGinBin
这里的 Rin,Gin,BinR_{in},G_{in},B_{in}Rin,Gin,Bin 一般是:
- 已黑电平校正
- 已归一化
- 已白平衡
- 线性 RGB
输出是:
- 目标线性 RGB
- 或 XYZ
- 再接后续 gamma / tone mapping / display transform
8. 第七步:用 ΔE 验证效果
这一步特别重要。
因为拟合出一个矩阵,不代表就真的"颜色准"。
工程上一定要做:
把 24 个色块经过 CCM 后,与标准参考做色差比较。
8.1 为什么不用只看 RGB 误差
因为 RGB 空间中的欧氏距离,不等于人眼感知的颜色差异。
例如:
- 某些颜色在 RGB 上差一点点,人眼很敏感
- 某些颜色 RGB 差不少,人眼不一定觉得大
所以更常用的是 CIE Lab 空间的 ΔE。
8.2 验证流程
对于每个色块:
第一步
用拟合得到的 CCM 把相机色块 RGB 变换到目标空间。
第二步
把结果转到 XYZ,再转到 Lab。
第三步
把标准参考值也转到同样的 Lab。
第四步
计算色差:
- ΔE*ab(CIE76)
- ΔE94
- ΔE00(CIEDE2000,更常用)
8.3 ΔE 的直觉理解
一般可以粗略理解为:
- ΔE < 1:几乎看不出差异
- 1 ~ 2:非常小
- 2 ~ 3:可接受
- 3 ~ 5:明显可见
- 5:偏差较大
工程上不能只看最大值,还要看:
- 平均 ΔE
- 最大 ΔE
- 灰阶平均 ΔE
- 肤色块 ΔE
- 饱和色块 ΔE
8.4 验证时要看的统计量
常见指标包括:
平均色差 ΔEmean\Delta E_{mean}ΔEmean
最大色差 ΔEmax\Delta E_{max}ΔEmax
中位数色差
更抗异常点。
分类统计
分别看:
- 灰阶块
- 肤色块
- 高饱和块
- 蓝绿色块
因为 CCM 常见问题是:
- 灰阶准,但高饱和炸
- 高饱和准,但肤色偏
- 平均不错,但个别块特别差
9. 第八步:如何根据 ΔE 结果诊断问题
这一步很像真实调参。
9.1 如果灰阶 ΔE 大
优先检查:
- AWB 是否做对
- 行和是否偏离 1 太多
- black level 是否准
- 目标白点口径是否一致
灰阶不好,往往说明中性轴没立住。
9.2 如果肤色块偏差大
优先检查:
- 红绿通道混合比例
- 第一行、第二行的交叉项
- 权重是否对肤色不够重视
工程里肤色通常要单独照顾,因为用户最敏感。
9.3 如果蓝色/绿色饱和块偏差大
常见原因:
- 传感器光谱响应与理想基色差异较大
- 单个 3×3 CCM 无法完全覆盖 gamut 边缘
- 目标色空间超出线性可拟合范围
这时可能需要:
- 调整权重
- 降低过饱和期望
- 用更复杂的 3D LUT / Hue-Sat 映射补偿
9.4 如果平均 ΔE 不错,但最大 ΔE 很大
说明整体拟合还行,但某些块是异常点或特殊颜色拟合失败。
这时要检查:
- ROI 是否取错
- 是否有反光污染
- 某色块是否接近饱和
- 是否需对个别块降权
10. 工程里 CCM 标定的完整链路可以概括成这样
你可以把整个流程记成这条线:
- 拍图
在标准光源下拍 24 色 ColorChecker RAW - 预处理
黑电平校正、归一化、demosaic、线性化、AWB - 取块
检测色卡,提取每个色块中心 ROI 均值 - 构建样本
得到 24 组相机 RGB 与 24 组标准参考值 - 拟合
用最小二乘求 3×3 CCM - 应用
把 CCM 作用到线性 RGB - 验证
转 Lab,计算每块 ΔE00,分析平均/最大/分组表现 - 回调
根据灰阶、肤色、饱和色的偏差重新调权重或加约束,再拟合
11. 为什么实际手机 ISP 往往不止一个 CCM
因为单个 CCM 往往只对某个 illuminant 最优。
真实工程里一般会做:
- A 光一套 CCM
- D65 一套 CCM
- TL84 一套 CCM
然后根据 AWB 估计的 CCT,在两套或多套 CCM 之间插值:
M(T)=αM1+(1−α)M2M(T) = \alpha M_1 + (1-\alpha)M_2M(T)=αM1+(1−α)M2
这样才能覆盖不同光源下的颜色变化。
在 ColorChecker 24 色卡的 CCM 标定中,首先在标准光源下采集相机 RAW 图像,并完成黑电平校正、归一化、去马赛克和线性化处理;随后在每个色块中心区域提取稳定的线性 RGB 均值,形成相机颜色响应矩阵 X。与此同时,根据色卡在对应光源下的标准参考数据构建目标颜色矩阵 Y。在 AWB 之后,采用最小二乘方法求解
minM∣XMT−Y∣F2\min_M |XM^T - Y|_F^2Mmin∣XMT−Y∣F2
得到 3×3 颜色校正矩阵 CCM。为评价标定效果,将校正结果转换至 Lab 空间,并计算各色块与标准值之间的 ΔE,综合分析平均色差、最大色差以及灰阶、肤色和高饱和色块的表现,据此对矩阵权重、约束条件和异常样本进行进一步优化。
七、如何调试CCM
调试CCM的目标是通过调整矩阵系数来获得图像准确的颜色表现。为了实现这一目标,调试时需要考虑图像传感器的特性、光照条件、色彩误差、设备差异等因素。调试过程可以分为 手动调节 和 自动化调节,并且通常会结合以下几种方法。
1. 使用标准色卡进行手动调节
标准色卡(Color Checker) 是调试CCM的一个重要工具。常见的标准色卡包括 X-Rite ColorChecker 和 Macbeth ColorChecker,这些色卡上包含了多种标准颜色,它们的RGB值是已知的,并且经过严格测量。通过将拍摄的标准色卡图像与色卡的标准颜色进行比对,可以计算出图像的颜色误差,并通过调整 CCM矩阵 来修正这些误差。
1.1 步骤:
1. 拍摄色卡图像:
使用摄像头或图像传感器拍摄色卡,记录标准色卡上的每个颜色的RGB值。
2. 计算颜色误差:
比较传感器输出的RGB值(例如通过RAW图像获取的RGB值)与色卡上已知的标准RGB值之间的差异。例如,对于某个颜色,标准色卡上的RGB值是 (Rs,Gs,Bs)(R_s, G_s, B_s)(Rs,Gs,Bs),而传感器输出的RGB值是 (Ri,Gi,Bi)(R_i, G_i, B_i)(Ri,Gi,Bi),误差可以通过如下公式计算:
ΔC=(RiGiBi)−(RsGsBs)\Delta C = \begin{pmatrix} R_i \\ G_i \\ B_i \end{pmatrix} - \begin{pmatrix} R_s \\ G_s \\ B_s \end{pmatrix}ΔC= RiGiBi − RsGsBs
3. 调节CCM矩阵:
使用最小二乘法(Least Squares)等优化算法,根据计算出的误差调整 CCM矩阵。这个优化的目标是最小化误差,使得传感器输出的RGB值尽可能接近标准色卡的RGB值。
4. 检查调节结果:
完成初步调节后,再次拍摄色卡图像,并计算新的误差。如果误差变小且色彩还原准确,说明调节成功。否则,重复调节过程。
1.2 注意事项:
- 在调节过程中,必须确保传感器和标准色卡的光照条件相似,避免环境光源的变化引起不必要的误差。
- 一些高级的色卡(如 X-Rite ColorChecker)会提供较为复杂的色彩标准,确保图像的RGB表现与这些标准尽量一致。
2. 逐通道调节
逐通道调节方法是针对 红色通道(R)、绿色通道(G) 和 蓝色通道(B) 分别进行细致调节,直到图像颜色表现准确。调节过程通常需要借助测试图案或者特定的场景,确保每个通道的颜色表现符合预期。
2.1 步骤:
1. 选择测试图案:
使用标准的测试图案,如 灰阶图、色轮、人脸图像等,来测试图像中的颜色还原和色调准确性。
2. 单独调节每个通道:
- 调节 红色通道(R):例如,如果图像偏红,可以减小矩阵中对应的红色分量。
- 调节 绿色通道(G):通常,绿色通道对于色彩的准确性至关重要,因为人眼对绿色较为敏感。如果图像偏绿,可以调整绿色分量。
- 调节 蓝色通道(B):如果图像偏蓝或色彩显得过冷,可以调整蓝色通道的系数。
3. 评估图像颜色:
每次调节后,都要评估图像中的色彩表现,检查是否出现色偏、色彩失真等现象。通常,可以通过查看图像中白色物体的颜色是否正确,或者观察人脸色调的准确性来判断。
4. 反复调整:
根据调节结果继续调整矩阵中的系数,直到图像中的颜色达到理想状态。调节通常需要一些反复试验,特别是在多个摄像头之间进行一致性调节时。
2.2 注意事项:
- 绿色通道的重要性:由于绿色通道对亮度信息的贡献最大,调节时需要特别注意绿色通道的表现。特别是在低光环境下,绿色通道的误差可能导致整个图像的颜色失衡。
- 避免过度调节:过度调节可能导致图像产生明显的色偏,尤其是在饱和色调较强的区域。需要保持调节的精度和细致。
3. 自动化调节
随着图像处理技术的不断进步,自动化调节CCM 已成为现代ISP系统的主流方案。通过机器学习、图像处理和优化算法,自动化调节可以大大提高调节效率和准确性。
3.1 自动化调节流程:
1. 数据收集:
通过拍摄多个不同光照、场景、传感器状态下的图像,收集各种可能的输入数据。这些数据将用于训练自动化算法。
2. 误差计算与优化:
自动化调节通过误差计算(如均方误差、色差等)来评估图像的颜色准确性。优化算法(如 最小二乘法、梯度下降法、遗传算法 等)用来调整 CCM矩阵 的系数,使其误差最小。
3. 使用深度学习:
近年来,深度学习已经被广泛应用于颜色校正。通过训练神经网络模型,输入图像的RAW数据,输出图像的RGB值,自动推算出最优的CCM矩阵。训练数据包括大量不同拍摄条件下的图像,神经网络可以根据这些数据学习出合适的色彩校正模型。
4. 实时调整:
在实际应用中,自动化调节CCM能够根据传感器的不同输入自动调整矩阵。特别是在动态光照、移动拍摄等场景下,自动化调节能够快速响应并调整颜色。
3.2 优势与挑战:
- 优势 :
- 高效率:自动化调节比手动调节更加高效,尤其在需要处理大量数据或多个摄像头时。
- 适应性强:自动化调节能够适应不同的传感器、光照条件、环境变化等。
- 精准度高:通过机器学习和优化算法,自动化调节可以减少人为错误,提高颜色还原的准确性。
- 挑战 :
- 计算复杂度:自动化调节通常需要较强的计算能力,特别是使用深度学习时,计算需求更高。
- 训练数据要求:需要大量标定数据来训练模型,这对于新设备或新传感器来说,可能需要额外的标定过程。
- 硬件限制:实时调节可能受到硬件计算能力和延迟的限制,尤其是在高分辨率和高帧率的视频应用中。
4. 综合调试与优化策略
调试和优化 CCM矩阵 时,综合考虑多个因素非常重要。以下是常见的优化策略:
4.1 综合误差度量
调试过程中不仅要关注图像的色彩准确性,还要综合考虑误差度量。例如:
- 均方误差(MSE):评估图像颜色的整体误差。
- 色差(CIEDE2000):考虑人眼对颜色差异的感知。
- 饱和度与亮度对比度:保持图像的自然感和清晰度。
4.2 各种光照条件下的测试
图像颜色会受到光照条件的显著影响。调试时需要确保 CCM矩阵 能适应不同光照条件下的变化。例如,低光照时可能需要增强绿色通道,而高光照时可能需要调整蓝色通道的增益。
4.3 跨摄像头一致性
当手机拥有多个摄像头时,需要确保每个摄像头的 CCM矩阵 一致性。通过自动化调节和优化算法,确保同一场景在不同摄像头下的颜色表现一致。
调试 CCM 的目标是让图像的颜色表现尽可能准确和自然,消除色偏并增强色彩还原。通过手动调节、逐通道调节和自动化调节等方法,图像的色彩校正可以得到精确优化。在多摄像头、多光照条件下,调节 CCM 也变得更加复杂,但通过自动化和机器学习技术,调节过程可以更加高效和准确。
八、总结
Color Correction Matrix 的挑战与优化
- 光照条件变化
光照条件的变化会显著影响图像的色彩表现。低光照环境可能会导致传感器捕获到的图像颜色失真,而强光环境下,图像的高光区域可能会出现颜色过度饱和的问题。CCM需要能够适应这些不同的光照条件,确保图像颜色的准确性。 - 跨传感器一致性
手机中的多个摄像头可能使用不同厂商的传感器,这会导致图像的颜色表现差异。如何在不同传感器之间保持一致的色彩表现是一个挑战。CCM能够帮助统一这些传感器的色彩输出,使得不同摄像头拍摄的图像具有一致的颜色。 - 高动态范围(HDR)
在HDR模式下,图像的动态范围非常宽。CCM的调整不仅需要关注整体颜色的准确性,还需要考虑高光和暗部的色彩表现。如何在保持细节的同时,避免颜色失真,是CCM调试中的一个挑战。
颜色校正矩阵(CCM) 是在图像信号处理中用于将传感器的RGB数据转换为标准颜色空间的关键工具。通过矩阵变换,CCM可以去除色偏、调整白平衡、色彩还原和增强图像的色彩表现。调试CCM时,我们需要使用标准色卡、逐通道调节和自动化调试等方法,以确保图像颜色的准确性和一致性。尽管CCM在实际应用中面临光照变化、跨传感器一致性以及HDR等挑战,但通过优化和调节,我们可以得到更加真实和自然的图像颜色。
结~~~