4.14. CSI 基础¶
csi 模块是 Python 代码驱动摄像头传感器的途径。每一个采集帧的脚本都遵循同样的三段式结构:顶部的导入、中间的一次性配置,以及底部的一个 while True 循环,该循环逐个从摄像头拉取帧。
4.14.1. 典型的循环¶
import csi, image, time
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
clock = time.clock()
while True:
clock.tick()
img = csi0.snapshot()
# process img here
print(clock.fps())
4.14.2. 每个调用的作用¶
import csi, image, time引入三个模块。
csi控制传感器,image定义snapshot()返回的Image类,time提供用于测量每秒帧数的time.clock()辅助工具。csi.CSI()构造一个
CSI实例,它封装一个物理摄像头传感器。构造函数会占用摄像头外设并记录每个传感器的配置。带单个传感器的摄像头有一个CSI实例;带两个传感器的摄像头(彩色加热成像、彩色加事件)有两个实例,各自通过构造函数的cid参数选择。csi0.reset()给传感器上电并配置传感器。默认情况下它会脉冲拉动传感器的复位引脚,然后写入传感器的 I2C 寄存器,使其进入已知的起始状态。后续的配置调用——
pixformat、framesize以及各种自动控制旋钮——会通过同一条 I2C 控制总线推送更多寄存器写入。csi0.pixformat(csi.RGB565)写入用于选择输出像素格式的传感器寄存器。可用选项就是 像素格式 页面介绍过的那些格式:
RGB565、GRAYSCALE、BAYER、YUV422,以及在支持的传感器上的JPEG。csi0.framesize(csi.QVGA)写入用于选择输出分辨率的寄存器。
QVGA是 320 × 240;在支持的传感器上,命名尺寸最高可达WQXGA2(2592 × 1944,约 5 MP)。自定义的(width, height)元组也可以使用,只要它与传感器的输出能力相匹配即可。clock = time.clock()创建一个时钟辅助工具。循环内每次调用
clock.tick()都会记录该次迭代的开始时间;time.clock.fps()以每秒帧数报告最近的循环速率。img = csi0.snapshot()从传感器采集一帧,并将其作为
Image返回。这一帧最终如何进入内存的机制值得更仔细地了解。
4.14.3. snapshot 如何填充内存¶
传感器以每秒数百兆字节的速率,通过 传感器总线 中描述的像素数据总线传送像素——这对于 CPU 用软件逐像素复制而言实在太快了。
取而代之的是,MCU 把传输工作卸载给直接内存访问(DMA)——一个独立于 CPU 的硬件引擎,它在 MCU 内部把字节从一处复制到另一处,全程完全不涉及 CPU。摄像头输入外设把每个传入的像素字节捕获到一个小型片上 FIFO 中;在 MCU 端运行的那些 ISP 阶段会在数据通过时对其进行处理;然后 DMA 引擎把成品像素按对应的像素偏移写入 RAM 中的帧缓冲区。一旦 DMA 通道被编程配置好,这条链路中就没有任何环节需要 CPU。
当调用 snapshot() 时:
CSI 驱动用帧缓冲区的地址、传输长度(一帧像素的数据量)以及一个用于 DMA 完成中断的回调来对 DMA 引擎进行编程。
驱动启用摄像头输入外设,并等待传感器发出下一帧开始的信号。
当传感器把该帧流式输出时,外设把每个像素字节传过 ISP,再送往 DMA 引擎,DMA 引擎把结果写入 RAM 中的下一个帧缓冲区偏移处。在传输期间 CPU 可以自由运行其他代码。
当该帧的最后一个像素到达时,DMA 触发其完成中断,驱动把帧缓冲区封装进一个
Image,snapshot()随即将其返回给用户代码。
返回的 Image 并不拥有像素数据的副本——它指向摄像头在 RAM 中的某个帧缓冲区。摄像头保留多少个帧缓冲区,以及它们在每次调用 snapshot() 时如何在 DMA 和用户代码之间传递,取决于应用通过 framebuffers() 选择的缓冲模式。