4、线性化基础:传感器响应模型、光子转移曲线、线性度的定义与测量

好,我们进入第四章。前面聊了黑电平,那是图像质量的“地基”。现在要聊的线性化,是地基上的第一层钢筋。说白了,传感器到底怎么把光变成电信号的?这个转换过程有多“诚实”?这就是本章要解决的问题。

我个人习惯,在调任何ISP pipeline之前,第一件事就是先把传感器的线性响应摸清楚。为什么?因为后面所有的增益、校正、色彩矩阵,全都建立在“输入和输出是线性关系”这个假设上。如果传感器本身就不线性,后面再怎么调也是白搭。

4.1 传感器响应模型:从光子到数字

一个典型的CMOS图像传感器,它的响应过程可以拆成三步:

  1. 光子收集:入射光子打到光电二极管,产生电子-空穴对。
  2. 电荷积累:电子被收集在势阱里,曝光时间越长,积累的电子越多。
  3. 电压转换与量化:积累的电荷被转换成电压,再经过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的测量步骤很简单:

  1. 用均匀光源照射传感器,拍一系列不同亮度的平场图像。
  2. 对每张图像,计算平均信号值(Mean)和方差(Variance)。
  3. 以Mean为横轴,Variance为纵轴,画曲线。

在理想情况下,这条曲线分为三个区域:

区域 特征 主导噪声
读出噪声区 方差基本不变 读出噪声、量化噪声
散粒噪声区 方差与信号成正比(斜率=1) 光子散粒噪声
固定噪声区 方差与信号平方成正比(斜率=2) 像素响应非均匀性(PRNU)

从PTC上,我们可以直接读出几个关键参数:

  • 转换增益:散粒噪声区斜率的倒数
  • 满阱容量:曲线开始偏离线性时的信号值
  • 读出噪声:信号为0时的方差
  • 动态范围:满阱容量除以读出噪声

实战技巧:我建议在测量PTC时,至少采集20个不同亮度等级,每个等级拍10帧以上取平均。我曾经偷懒只拍了5帧,结果方差波动大得根本没法拟合。

下面这张图展示了典型的光子转移曲线结构:

光子转移曲线(PTC)结构图 平均信号(Mean, DN) 方差(Variance, DN²) 读出噪声区 散粒噪声区(斜率=1) 固定噪声区(斜率=2) 读出噪声拐点 满阱容量 从PTC可以提取:转换增益、满阱容量、读出噪声、动态范围

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 线性度的测量方法:动手做一遍

好了,理论说完了,咱们来点实际的。怎么在实验室里测线性度?我一般按这个流程走:

  1. 准备均匀光源:积分球或者均匀背光板,保证传感器整个靶面照度均匀。
  2. 设置曝光时间:固定光圈和增益,只改变曝光时间或光源亮度。
  3. 采集数据:从暗场开始,逐步增加亮度,直到传感器饱和。每个亮度拍10帧。
  4. 计算平均值:对每帧图像,取中心区域(避开边缘暗角)的像素平均值。
  5. 减去黑电平:用暗场图像的平均值作为黑电平,从所有数据中减去。
  6. 拟合直线:用最小二乘法拟合信号值与曝光量的关系。
  7. 计算残差:计算每个数据点与拟合直线的偏差,取最大偏差除以满量程。

下面是一个简单的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合成,我们甚至需要传感器在接近饱和区时有一个平滑的“软饱和”曲线,这样反而更容易做多帧融合。所以,理解线性度的本质,比死记硬背指标更重要。