13.1.9. 通道视图#
帧缓冲区显示摄像头 看到 的内容;通道视图显示脚本 测量和判断 的内容。脚本通过调试协议发布命名的值——一个温度、一个检测计数、一段麦克风波形、一幅飞行时间深度图,或是它希望用户能够更改的控件——IDE 将每一个实时渲染为帧缓冲区下方窗格中的控件、图形或深度图。无需在图像上绘制任何东西,也无需从终端中解析。在摄像头端只需几行 protocol.CBORChannel 代码;在 IDE 端则是 窗格选择器 中的 通道 条目。

热成像摄像头脚本的控件:两个温度读数、测量模式的开关,以及温度范围的滑块。更改其中任何一个都会将新值发送给脚本。#
视图按通道组织。脚本注册的每个通道都成为一个以通道名称为标题的区段,通道中的每条记录都成为其下的一行,顺序与脚本添加它们的顺序一致。当脚本注册通道或更改某个通道的形态时,视图会重新构建自身;当脚本停止时,最后的值会保留在屏幕上,控件被禁用,直到下一个脚本启动。
13.1.9.1. 控件#
读数和控件采用显而易见的形式。label 是带单位的只读值;text 记录是用于标题和说明的静态富文本块。可写的类型会在你更改的瞬间将新值发回脚本,由脚本的 on_write 回调接收记录名称和值:toggle 是一个开关,slider 和 spinbox 在脚本的范围和步长内设置一个数字(微调框用于精确输入),radio 按钮和 select 从脚本的选项中选择一项,lineedit 是自由文本字段,pushbutton 触发一次瞬时动作。在写入进行期间,视图会将你的编辑保留在屏幕上,而不是让它弹回摄像头最近发布的值。
在脚本端,整套控件就是一个 protocol.CBORChannel,每个控件一次 add,外加一个处理写入的回调:
import time
import protocol
from protocol import CBORChannel
def on_write(ch, name, value):
print(name, "=", value) # every change lands here
ch = CBORChannel(on_write=on_write)
ch.add("Status", type="label", value="starting")
ch.add("Enable", type="toggle", value=True)
ch.add("Threshold", type="slider", min=0, max=100,
step=1, value=50, unit="%")
ch.add("Mode", type="select",
options=["Idle", "Track", "Record"], value="Idle")
ch.add("Calibrate", type="pushbutton")
protocol.register(name="controls", backend=ch)
while True:
if ch["Enable"]: # read a control back
ch["Status"] = "threshold %d%%" % ch["Threshold"]
time.sleep_ms(100)
运行它并将窗格切换到 通道:会出现一个名为 controls 的区段,其中包含这五个控件,移动滑块会在终端中打印输出。其他控件类型——spinbox、radio、lineedit、text——各自只需再多一个 add;主机协议章节 会逐一介绍它们。
13.1.9.2. 波形图#
波形 记录是一块带采样率的交错采样数据——音频、加速度计各轴、ADC 轨迹——视图将其绘制为滚动图形,每个轴一条序列,由脚本命名。图形控件位于其工具栏和图形本身上:
拖动可平移,滚动可缩放;双击可回到跟随实时边缘的状态。历史 设置保留多少过去的数据,以便你可以向回平移查看。
自动缩放 使纵轴适应数据;关闭时,纵轴为脚本声明的范围。
右键点击图形可放置一个 标记 以读取某个采样的精确时间和值,或设置 触发电平。预备 触发 后,图形会保持静止,直到信号以上升沿或下降沿穿过该电平——这是示波器捕捉瞬态的方式——而 重新预备 则等待下一次触发。
频谱 会将图形切换为同一数据的频率图,可选择窗函数(Hann、Hamming、Blackman、矩形),支持跨帧的 平均 和 峰值保持,以及 对数频率 坐标轴。图形下方的读数会标明峰值频率及其电平。
暂停 会冻结图形而不影响录制;统计 会添加一行最小值 / 最大值 / RMS;右键菜单可将图形保存为图像。

两条波形记录实时滚动:一条麦克风轨迹和一条三序列 IMU 轨迹。#
波形字段接受采样率,以及在同时传多个信号时交错 series 的数量及其名称;每次更新是以字段 typecode 表示的采样 array 的原始字节:
import math
import time
import protocol
from array import array
from protocol import CBORChannel
RATE, CHUNK = 1000, 100 # 1 kHz, 100 ms per update
ch = CBORChannel()
ch.add("IMU", type="waveform", sample_rate=RATE, series=3,
typecode="f", min=-2.0, max=2.0,
options=["X", "Y", "Z"], unit="g")
protocol.register(name="signals", backend=ch)
t = 0
while True:
block = array("f")
for i in range(CHUNK):
phase = 2 * math.pi * 2 * (t + i) / RATE
block.extend((math.sin(phase), math.cos(phase), 1.0))
ch["IMU"] = bytes(block) # interleaved X, Y, Z
t += CHUNK
time.sleep_ms(100)
13.1.9.3. 深度图#
深度 记录是一个距离网格——飞行时间传感器产生的帧——视图将其绘制为放大到图形高度的伪彩色图。工具栏用于选择渲染方式:调色板(摄像头自带的 Depth、Rainbow 和 Ironbow 调色板,外加面向事件摄像头的浅色和深色调色板)、放大小尺寸传感器网格的 缩放滤镜(双三次得到平滑表面,双线性,或最近邻使每个传感器像素对应一个色块),以及将距离转换为颜色的 色调映射:脚本范围 使用脚本声明的范围,自动范围 将颜色拉伸到每一帧中实际存在的距离上(忽略离群的少数百分比),均衡化 则按帧的分布展开颜色,使平坦的场景仍能显示细节。标题栏报告深度图的尺寸、声明的范围以及帧的最小值、最大值和平均值;将鼠标悬停在图上可读出光标下的距离,保存图像 则将渲染后的图写入 PNG。

传感器前方一只手的飞行时间深度图,工具栏中带有调色板、缩放和色调映射选择器。#
深度字段声明其网格尺寸和范围;每次更新是以紧凑 32 位浮点数表示的网格,这正是 tof 模块的读取器所产生的数据:
import struct
import time
import tof
import protocol
from protocol import CBORChannel
tof.init()
ch = CBORChannel()
ch.add("depth", type="depth", width=tof.width(),
height=tof.height(), min=0, max=1000)
protocol.register(name="ToF", backend=ch)
while True:
try:
d, dmin, dmax = tof.read_depth(vflip=True, hmirror=True)
except RuntimeError:
continue
ch["depth"] = struct.pack("<%df" % len(d), *d)
time.sleep_ms(50)
13.1.9.4. 录制#
每个图形和深度图都有一个 录制 按钮,而拥有多个图形或深度图的通道还有一个 录制全部 栏,可以同时启动它们。录制会将数据按到达的原样——而非渲染结果——写入文件,文件格式取决于你在保存对话框中选择的扩展名:CSV 适用于电子表格和绘图工具,WAV 适用于单通道音频波形,NumPy .npy 数组适用于 Python,而 Edge Impulse JSON 或 CBOR 可直接上传到 Edge Impulse 项目作为训练数据,并可选添加一个用于命名文件的标签。只会提供能够容纳该记录数据的格式;例如,深度图不能保存为 WAV 文件。长时间采集会拆分为带编号的分段,这样单个文件不会变得过于庞大,而且录制在脚本重新注册其通道后仍会继续。按钮旁的读数会跟踪已写入的采样数和文件大小。
窗格的 通道轮询 间隔位于首选项的协议部分,它设置 IDE 向摄像头请求新通道数据的频率;默认值适合每秒发布几次的脚本,而流式传输音频的脚本则会受益于更短的间隔。
参见
protocol.CBORChannel 介绍脚本端——记录类型、它们的参数以及 on_write 回调——而 File → Examples 下的 12-Protocol 示例则提供了发布读数、控件和深度图的可用脚本。