4、线性化基础:传感器响应模型、光子转移曲线、线性度的定义与测量
好,我们进入第四章。前面聊了黑电平,那是图像质量的“地基”。现在要聊的线性化,是地基上的第一层钢筋。说白了,传感器到底怎么把光变成电信号的?这个转换过程有多“诚实”?这就是本章要解决的问题。
我个人习惯,在调任何ISP pipeline之前,第一件事就是先把传感器的线性响应摸清楚。为什么?因为后面所有的增益、校正、色彩矩阵,全都建立在“输入和输出是线性关系”这个假设上。如果传感器本身就不线性,后面再怎么调也是白搭。
4.1 传感器响应模型:从光子到数字
一个典型的CMOS图像传感器,它的响应过程可以拆成三步:
- 光子收集:入射光子打到光电二极管,产生电子-空穴对。
- 电荷积累:电子被收集在势阱里,曝光时间越长,积累的电子越多。
- 电压转换与量化:积累的电荷被转换成电压,再经过ADC变成数字码值。
这个过程的理想模型,就是一条直线:
Digital_Code = G × (Photo_Electrons + Dark_Signal) + Offset
其中:
- G 是转换增益(Conversion Gain),单位是 [DN/e⁻]
- Photo_Electrons 是光生电子数
- Dark_Signal 是暗电流产生的电子数
- Offset 是黑电平偏移
嗯,这里要注意。实际传感器里,这个模型只在“线性区”成立。到了接近满阱的时候,曲线会开始弯曲,这就是饱和。我在项目中遇到过一款传感器,标称满阱容量是10000e⁻,结果在8000e⁻就开始明显非线性了。所以,别太相信数据手册上的数字。
核心要点:传感器响应模型的核心假设是“光生电子数与入射光子数成正比”。这个假设一旦被打破,线性化就无从谈起。
4.2 光子转移曲线:测量传感器特性的金标准
怎么测量传感器的真实响应?业界标准方法就是光子转移曲线(Photon Transfer Curve, PTC)。你想想看,这其实是个很聪明的办法——利用光子散粒噪声的统计特性来反推传感器的参数。
PTC的测量步骤很简单:
- 用均匀光源照射传感器,拍一系列不同亮度的平场图像。
- 对每张图像,计算平均信号值(Mean)和方差(Variance)。
- 以Mean为横轴,Variance为纵轴,画曲线。
在理想情况下,这条曲线分为三个区域:
| 区域 | 特征 | 主导噪声 |
|---|---|---|
| 读出噪声区 | 方差基本不变 | 读出噪声、量化噪声 |
| 散粒噪声区 | 方差与信号成正比(斜率=1) | 光子散粒噪声 |
| 固定噪声区 | 方差与信号平方成正比(斜率=2) | 像素响应非均匀性(PRNU) |
从PTC上,我们可以直接读出几个关键参数:
- 转换增益:散粒噪声区斜率的倒数
- 满阱容量:曲线开始偏离线性时的信号值
- 读出噪声:信号为0时的方差
- 动态范围:满阱容量除以读出噪声
实战技巧:我建议在测量PTC时,至少采集20个不同亮度等级,每个等级拍10帧以上取平均。我曾经偷懒只拍了5帧,结果方差波动大得根本没法拟合。
下面这张图展示了典型的光子转移曲线结构:
4.3 线性度的定义:到底多直才算“直”?
线性度,说白了就是传感器的输出信号与输入光强之间的吻合程度。我们通常用两个指标来衡量:
1. 积分非线性(INL, Integral Non-Linearity)
这是最常用的指标。它测量的是实际响应曲线与理想直线之间的最大偏差,通常用满量程的百分比表示:
INL = max(|实际值 - 理想值|) / 满量程 × 100%
2. 微分非线性(DNL, Differential Non-Linearity)
这个指标更精细,它测量的是相邻两个码值之间的步长是否均匀。对于理想传感器,每增加一个光子,输出应该增加固定的码值。如果步长忽大忽小,DNL就会超标。
避坑指南:我曾经在一个项目中,INL测出来只有0.5%,看起来很好。但实际图像上出现了明显的条纹噪声。后来一查,是DNL在某个区间跳了2个LSB。所以,INL好不代表DNL好,两个都要看。
工业界对线性度的要求因应用而异:
| 应用场景 | 典型INL要求 | 典型DNL要求 |
|---|---|---|
| 消费电子(手机) | < 1% | < 0.5 LSB |
| 工业机器视觉 | < 0.5% | < 0.3 LSB |
| 科学/医疗成像 | < 0.1% | < 0.1 LSB |
4.4 线性度的测量方法:动手做一遍
好了,理论说完了,咱们来点实际的。怎么在实验室里测线性度?我一般按这个流程走:
- 准备均匀光源:积分球或者均匀背光板,保证传感器整个靶面照度均匀。
- 设置曝光时间:固定光圈和增益,只改变曝光时间或光源亮度。
- 采集数据:从暗场开始,逐步增加亮度,直到传感器饱和。每个亮度拍10帧。
- 计算平均值:对每帧图像,取中心区域(避开边缘暗角)的像素平均值。
- 减去黑电平:用暗场图像的平均值作为黑电平,从所有数据中减去。
- 拟合直线:用最小二乘法拟合信号值与曝光量的关系。
- 计算残差:计算每个数据点与拟合直线的偏差,取最大偏差除以满量程。
下面是一个简单的Python示例,演示如何计算INL:
import numpy as np
def calculate_inl(signal, exposure):
"""
计算积分非线性(INL)
signal: 传感器输出信号(已减黑电平)
exposure: 曝光时间或光源亮度
"""
# 最小二乘拟合
coeffs = np.polyfit(exposure, signal, 1)
ideal = coeffs[0] * exposure + coeffs[1]
# 计算偏差
deviation = np.abs(signal - ideal)
inl = np.max(deviation) / np.max(signal) * 100
return inl, ideal
# 示例数据
exposure = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
signal = np.array([0, 98, 201, 305, 402, 498, 601, 698, 795, 890, 980])
inl, ideal = calculate_inl(signal, exposure)
print(f"INL = {inl:.2f}%")
个人经验:测量时一定要确保光源的稳定性。我见过有人用LED灯直接测,结果LED本身就有温度漂移,测出来的非线性其实是光源的非线性。建议用卤素灯或经过稳流的LED光源。
最后,我想强调一点。线性度不是越高越好。对于某些应用,比如HDR合成,我们甚至需要传感器在接近饱和区时有一个平滑的“软饱和”曲线,这样反而更容易做多帧融合。所以,理解线性度的本质,比死记硬背指标更重要。