13.3.1.3. 流式传输帧¶
在摄像头上采集帧的脚本可以通过 USB 将每一帧流式传输回主机。其模式是在 openmv.Camera 实例上进行两次调用:用 streaming() 打开或关闭流,用 read_frame() 从通道中取出下一帧。
13.3.1.3.1. 一个最简流式传输与显示循环¶
摄像头端脚本就是常见的快照循环;新增的部分是主机打开流并将结果读取回来:
from openmv import Camera
script = """
import csi
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
while True:
csi0.snapshot()
"""
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(script)
cam.streaming(True)
while True:
if frame := cam.read_frame():
print(f"{frame['width']}x{frame['height']}, "
f"{frame['raw_size']} bytes")
摄像头持续采集帧;主机在每一帧到达时将其从流缓冲区中取出。摄像头在每次新的快照时都会覆写流缓冲区,因此轮询速度慢于摄像头采集速度的主机会悄无声息地丢帧——对于查看器类的使用场景而言,这正是正确的行为。
13.3.1.3.2. 帧字典¶
read_frame() 返回 None(没有等待中的帧)或包含五个条目的 dict:
键 |
含义 |
|---|---|
|
帧宽度,以像素为单位。 |
|
帧高度,以像素为单位。 |
|
摄像头声明的像素格式标识符(来自摄像头 |
|
对于压缩格式(JPEG、PNG),表示压缩图像的字节大小。未压缩格式不使用该字段。 |
|
以 RGB888 格式表示的帧,存放在 |
|
摄像头在解码前通过 USB 发送的字节数。对于实际吞吐量的计算很有用。 |
该软件包在返回前会将摄像头的原生格式(GRAYSCALE、RGB565、JPEG)转换为 RGB888,因此主机无需自行处理位打包的 RGB565 或 JPEG 解压路径。灰度帧返回时会将其亮度值复制到全部三个通道中。
data 缓冲区按行排列,从上到下;将其直接送入显示库或保存为原始 RGB 文件均无需任何进一步的重排。
13.3.1.3.3. 原始流式传输模式¶
默认情况下,摄像头会先对每个采集到的帧进行 JPEG 压缩,再将其放入流通道,然后 read_frame() 在主机端进行解压。在不支持硬件 JPEG 的摄像头上,软件压缩是循环中最慢的步骤。传入 raw=True 可跳过这一步:
cam.streaming(True, raw=True, resolution=(320, 240))
随后摄像头将以未压缩方式发送像素缓冲区。未压缩帧比其 JPEG 对应版本大得多,因此摄像头会在发送前将每个采集到的帧缩小以适应流通道;resolution=(width, height) 参数用于设置该目标尺寸。主机在 data 字段中仍然接收 RGB888——该软件包会从摄像头在 format 中报告的任何像素格式进行转换。
13.3.1.3.4. 让事件驱动循环¶
一个调用 read_frame() 的速度快于摄像头产生帧速度的轮询循环,会把大部分时间花在拿回 None 上。当主机还有其他工作要做时(更新 UI、轮询其他通道),read_status() 是更经济的检查方式:它返回一个字典,将每个已注册的通道名称映射到表示“数据已就绪”的布尔值:
while True:
status = cam.read_status()
if status.get('stream'):
frame = cam.read_frame()
# ... process the frame ...
if status.get('stdout'):
text = cam.read_stdout()
print(text, end='')
if status.get('my_channel'):
data = cam.channel_read('my_channel')
# ... process custom-channel data ...
这正是 CLI 查看器本身所使用的循环形式。
13.3.1.3.5. 停止流¶
以 enable=False 调用 streaming() 即可停止。摄像头仍会继续运行其脚本,但不再填充流缓冲区;从此刻起 read_frame() 只会返回 None。调用 stop() 会通过停止脚本隐式地达成同样的效果。