3、图像传感器与AE的接口:传感器曝光时序

好,我们进入第三章。前面两章我们把AE的宏观架构和核心指标聊完了。这一章,得往底层走一走,看看AE到底是怎么跟图像传感器“对话”的。

说白了,AE算法最终要控制的是传感器。你算出来目标亮度是120,增益该设多少,曝光时间该设多少,这些指令最终要变成传感器能理解的寄存器配置。而传感器怎么执行这些配置,就取决于它的曝光时序。

我个人习惯,在开始调AE之前,一定会先把传感器的datasheet翻烂,尤其是时序图那几页。为什么?因为时序决定了你的曝光时间能设多短、帧率能跑多高、以及会不会出现画面撕裂。这些坑,我在项目里都踩过。

3.1 滚动快门 vs 全局快门

先讲最基础的概念:传感器的曝光方式。目前主流有两种:滚动快门(Rolling Shutter)和全局快门(Global Shutter)。

特性 滚动快门 (Rolling Shutter) 全局快门 (Global Shutter)
曝光方式 逐行曝光,行与行之间有延迟 所有像素同时开始、同时结束
帧率上限 较高,受限于行时间 较低,受限于像素存储单元
动态范围 通常更高 受限于存储电容,通常较低
果冻效应 明显,拍摄快速运动物体时变形 无果冻效应
噪声 行噪声较明显 固定模式噪声(FPN)更易校正
成本 低,主流消费级传感器 高,工业/专业级传感器
AE适配难度 中等,需考虑行时间对齐 简单,曝光时间直接对应帧长

在手机、安防、行车记录仪这些消费级领域,99%用的是滚动快门。为什么?成本低、帧率高、动态范围好。但代价就是——你得处理果冻效应,以及AE时序上的那些麻烦事。

我的经验:做AE算法,滚动快门是主战场。全局快门虽然时序简单,但动态范围受限,在强光场景下容易饱和,AE反而更难收敛。我在一个工业检测项目里用过全局快门传感器,最后不得不加了一路ND滤镜才搞定。

3.2 行时间(Line Time)——AE的“心跳”

行时间,英文叫Line Time,或者Row Time。它是什么?就是传感器读完一行像素所需要的时间。

你想想看,滚动快门是一行一行读的。读一行需要时间,这个时间就是行时间。它由两部分组成:

  • 像素积分时间(Integration Time):这一行真正在收集光子的时间
  • 读出时间(Readout Time):把这一行的电荷转换成数字信号的时间

行时间 = 积分时间 + 读出时间。但注意,在滚动快门里,积分时间和读出时间是流水线重叠的。也就是说,第N行在读出的同时,第N+1行已经在积分了。

这个流水线结构,对AE有什么影响?

关键点:行时间决定了AE能设置的最小曝光步长。比如行时间是10μs,那你的曝光时间就只能以10μs为粒度调整。你不能设成15μs,因为传感器只能按整行时间对齐。

我在一个项目中遇到过这样的问题:AE算法算出来需要把曝光时间从20ms调到20.5ms,结果传感器根本不认这个值,直接给你截断到20ms或者21ms。AE就在这两个值之间来回跳,永远收敛不了。后来我把行时间从10μs改成了5μs,问题就解决了。

3.3 帧长(Frame Length)——帧率的“天花板”

帧长,就是一帧图像包含的总行数。注意,这个总行数包括有效像素行和消隐区(Blanking)行。

帧长 = 有效行数 + 垂直消隐行数

帧率 = 1 / (帧长 × 行时间)

举个例子:

  • 行时间 = 10μs
  • 有效行数 = 1080行(比如1080p分辨率)
  • 垂直消隐行数 = 120行
  • 帧长 = 1080 + 120 = 1200行
  • 帧率 = 1 / (1200 × 10μs) = 83.3 fps

帧长对AE的影响,主要体现在两个方面:

  1. 曝光时间上限:曝光时间不能超过帧长。因为滚动快门里,所有行的曝光必须在同一帧内完成。如果曝光时间超过帧长,就会出现上一帧的尾行和下一帧的首行重叠,画面直接乱掉。
  2. 帧率稳定性:AE调整曝光时间时,如果改变了帧长(比如为了降低帧率来增加曝光时间),帧率就会变化。帧率变化会影响运动物体的流畅度,也会影响AE的统计周期。

避坑指南:我曾经在一个项目中,为了在暗光下提升亮度,把曝光时间设到了接近帧长。结果发现画面顶部和底部亮度不一致,顶部偏暗。查了半天才发现,滚动快门最后几行的曝光时间被截断了,因为下一帧的读出已经开始。后来我强制留了至少5%的帧长余量,问题才解决。

3.4 滚动快门下的AE时序约束

好了,我们把行时间和帧长串起来,看看滚动快门下AE到底要遵守哪些约束。

滚动快门AE时序约束关系 行时间 (Line Time) 积分 + 读出 帧长 (Frame Length) 有效行 + 消隐行 帧率 (FPS) 1 / (帧长 × 行时间) 曝光时间 必须 ≤ 帧长 曝光步长 必须是行时间的整数倍 消隐区 (Blanking) 调节帧长的缓冲 AE时序约束总结 1. 曝光时间 ≤ 帧长(否则画面错乱) 2. 曝光时间 = N × 行时间(N为整数) 3. 帧长 = 有效行数 + 消隐行数 4. 帧率 = 1 / (帧长 × 行时间)

从这张图可以看得很清楚:行时间是基础粒度,帧长是时间窗口,曝光时间必须在这个窗口内,并且以行时间为步长。消隐区是留给AE调节的缓冲空间。

3.5 工程落地:如何配置AE的时序参数

好了,理论讲完了。我们来看看实际代码里怎么配。以我常用的一个Sensor驱动为例:

// 传感器寄存器配置示例
// 假设行时间 = 10μs,有效行数 = 1080

// 1. 设置行时间(通常通过PLL分频和像素时钟配置)
sensor_write(REG_LINE_LENGTH_PCK, 0x0A);  // 行长度 = 10像素时钟周期

// 2. 设置帧长(有效行 + 消隐行)
uint16_t frame_length = 1080 + 120;  // 120行消隐
sensor_write(REG_FRAME_LENGTH_LINES, frame_length);

// 3. AE算法计算目标曝光时间
float target_exposure_ms = 16.67f;  // 目标60fps下的最大曝光时间

// 4. 将曝光时间转换为行数(必须整数)
uint16_t exposure_lines = (uint16_t)(target_exposure_ms * 1000.0f / line_time_us);
// 约束:exposure_lines <= frame_length
if (exposure_lines > frame_length) {
    exposure_lines = frame_length - 5;  // 留5行余量
}

// 5. 写入曝光行数寄存器
sensor_write(REG_EXPOSURE_LINES, exposure_lines);

小技巧:我习惯在AE算法里维护一个“时序约束表”,把当前分辨率下的行时间、帧长、最大曝光行数都算好。每次AE调整前,先查表,再计算。这样可以避免运行时反复读寄存器,也防止算错。

3.6 常见问题与避坑

最后,总结几个我在项目中遇到过的坑:

  • 曝光时间超过帧长:画面出现横纹或撕裂。解决:AE算法里加硬限幅,曝光时间 ≤ 帧长 - 安全余量。
  • 行时间设置错误:导致实际帧率和预期不符,AE统计周期乱掉。解决:上电后先读回行时间寄存器,确认实际值。
  • 消隐区太小:AE在暗光下想增加曝光时间,但帧长已经满了,只能降帧率。解决:预留足够的消隐行数,或者支持动态调整帧长。
  • 滚动快门果冻效应:AE在快速运动场景下,不同行的亮度不一致。解决:如果硬件支持,可以开启“行交织”模式;否则AE要降低对单帧统计的依赖,增加时域滤波。

我曾经踩过的坑:在一个行车记录仪项目里,AE在隧道出入口频繁跳变。查了很久发现,是因为传感器在切换曝光时间时,需要几个帧周期的稳定时间。AE算法每帧都在调,传感器根本来不及响应。后来我加了“防抖”逻辑,每次调整后至少等3帧再继续调,问题就解决了。