优化LED 8bit信号驱动下CIE XYZ色彩的指数曲线拟合
背景与现有方案
我记录了LED色彩随8bit驱动信号(范围0-255)变化的数据,采用指数曲线拟合能较好表征LED特性,使用公式:
x * signal ** ex y * signal ** ey z * signal ** ez
在Python中通过scipy实现拟合:
from scipy.optimize import curve_fit def fit_func_xae(x, a, e): # Curve fitting function return a * x**e # X, Y, Z为物理仪器测得的真实色度值 (aX, eX), cov = curve_fit(fit_func_xae, signal, X) (aY, eY), cov = curve_fit(fit_func_xae, signal, Y) (aZ, eZ), cov = curve_fit(fit_func_xae, signal, Z)
注:色度学中采用CIE XYZ色彩空间表示LED颜色,该空间为线性空间,与线性RGB空间类似,可近似将XYZ视为线性RGB的同义词,颜色由三元组线性值X、Y、Z表示。
原始测量数据
每个8bit驱动信号对应3组测量值:
Signal [ 3. 3. 3. 5. 5. 5. 7. 7. 7. 10. 10. 10. 15. 15. 15. 20. 20. 20. 30. 30. 30. 40. 40. 40. 80. 80. 80. 160. 160. 160. 240. 240. 240. 255. 255. 255.] X, Y, Z [[9.93295448e-05 8.88955748e-04 6.34978556e-04] [9.66399391e-05 8.86031926e-04 6.24680520e-04] [1.06108685e-04 8.99010175e-04 6.41577838e-04] [1.96407653e-04 1.70210146e-03 1.27178991e-03] [1.84965943e-04 1.67927596e-03 1.24985475e-03] [1.83770476e-04 1.67905297e-03 1.24855580e-03] [3.28537613e-04 2.75382195e-03 2.14639821e-03] [3.17804246e-04 2.74152647e-03 2.11730825e-03] [3.19167905e-04 2.74977632e-03 2.11142769e-03] [5.43770342e-04 4.09314433e-03 3.33793380e-03] [5.02493149e-04 4.04392581e-03 3.24784452e-03] [5.00712102e-04 4.03456071e-03 3.26803716e-03] [1.48001671e-03 1.09367632e-02 9.59283037e-03] [1.52082180e-03 1.09920985e-02 9.63624777e-03] [1.50153844e-03 1.09623592e-02 9.61724422e-03] [3.66206564e-03 2.74730946e-02 2.51982924e-02] [3.64074861e-03 2.74283157e-02 2.52187294e-02] [3.68719991e-03 2.75033778e-02 2.51691331e-02] [1.50905917e-02 1.06056566e-01 1.06534373e-01] [1.51370269e-02 1.06091182e-01 1.06790424e-01] [1.51654172e-02 1.06109863e-01 1.06943957e-01] [3.42912601e-02 2.30854413e-01 2.43427207e-01] [3.42217124e-02 2.30565972e-01 2.43529454e-01] [3.41486993e-02 2.30807320e-01 2.43591644e-01] [1.95905112e-01 1.27409867e+00 1.37490536e+00] [1.94923951e-01 1.26934278e+00 1.37751808e+00] [1.95242984e-01 1.26805844e+00 1.37565458e+00] [1.07931878e+00 6.97822521e+00 7.49602715e+00] [1.08944832e+00 7.03128378e+00 7.54296884e+00] [1.07994964e+00 6.96864302e+00 7.44011991e+00] [2.95296087e+00 1.90746191e+01 1.99164655e+01] [2.94254973e+00 1.89524517e+01 1.98158118e+01] [2.95753358e+00 1.90200667e+01 1.98885050e+01] [3.44049055e+00 2.21221159e+01 2.29667049e+01] [3.43817829e+00 2.21225393e+01 2.29363833e+01] [3.43077583e+00 2.21158929e+01 2.29399652e+01]]
问题描述
上述指数曲线拟合整体效果较好,但放大后发现低信号区间精度不足:高信号时Z分量(蓝色点)高于Y分量(绿色点),低信号时Y分量高于Z分量,说明LED色彩随PWM变化(或因功率提升致温度上升),现有模型无法表征该特性。
我尝试用双指数项公式优化:
x1 * signal ** ex + x2 * signal ** fx
替代原公式:
x * signal ** ex
但导致scipy溢出。需要简洁优雅的方案,满足:
- 信号为0时输出为0
- 低信号区间增长快于单一指数
- 保留高信号拟合精度
1. 修正双指数模型的数值稳定性
双指数溢出的核心原因是signal ** fx在fx为极端值时,数值会趋近于0或无穷大。可以通过以下方式解决:
- 参数约束:给
curve_fit添加bounds参数,限制指数范围(比如0.5到3之间),避免极端值导致溢出。 - 对数转换计算:先计算
log(signal),再乘以指数后取指数,避免直接计算大幂次:import numpy as np def fit_func_double_exp(x, a1, e1, a2, e2): log_x = np.log(x + 1e-8) # 加极小值避免log(0)报错 term1 = a1 * np.exp(e1 * log_x) term2 = a2 * np.exp(e2 * log_x) return term1 + term2 - 初始化合理参数:用单指数拟合的结果作为初始值(比如
p0=[aX, eX, 0.1*aX, eX+0.5]),让拟合从合理起点开始,减少溢出概率。
2. 采用带偏移的指数模型
使用a * (signal + b) ** e - a * b ** e,这个模型满足signal=0时输出为0,通过调整b的值可以改变低信号区间的增长速率:当b>0时,低信号部分的增长会比单一指数更快;高信号时signal + b近似等于signal,不会影响高区间的拟合精度。
def fit_func_offset_exp(x, a, b, e): return a * (x + b)**e - a * (b)**e
拟合时给b设置下界(比如b>0),避免出现不合理的负值。
3. 分段拟合+平滑过渡
将数据分为低信号(比如signal<50)和高信号(signal>=50)两段,分别用不同的指数模型拟合,再用Sigmoid函数实现平滑过渡:
- 低信号段:用
a1 * signal ** e1,保证低区间增长速率 - 高信号段:用
a2 * signal ** e2,保留高区间精度 - 过渡权重:
weight = 1 / (1 + np.exp(-k*(signal - threshold))),最终输出为(1-weight)*low_fit + weight*high_fit
这种方法逻辑清晰,能针对性优化低信号区间,同时不影响高信号拟合效果,也不会出现数值溢出问题。
4. 引入温度相关修正项
如果色彩变化确实由温度导致,可以用PWM信号间接表征温度(比如temp = c * signal + d),给每个分量的指数添加温度依赖:
def fit_func_temp_exp(x, a, e0, k, c, d): temp = c * x + d e = e0 + k * temp return a * x**e
这个模型能直接表征温度对LED发光特性的影响,更贴合物理本质,但需要确保温度与PWM的相关性合理。
内容的提问来源于stack exchange,提问作者adrienlucca.net

