13.1.9. Channels 檢視#

影格緩衝區顯示相機看到什麼;Channels 檢視則顯示指令碼測量與決定了什麼。指令碼透過除錯協定發布具名的數值——溫度、偵測計數、麥克風波形、飛時測距深度圖,或它希望使用者能夠變更的控制項——IDE 會在影格緩衝區下方的窗格中,將每一項即時呈現為小工具、圖表或深度圖。不需要在影像上繪製任何東西,也不需要從終端機中剖析任何內容。在相機端,只需用 protocol.CBORChannel 寫幾行程式;在 IDE 端,則是 窗格選擇器 的 Channels 項目。

The Channels view showing a thermal camera script's controls channel: FPA and AUX temperature readouts with their unit, Measurement and High Temp toggles, Temp Min and Temp Max sliders, and H-Mirror and V-Flip toggles

熱像儀指令碼的控制項:兩個溫度讀數、測量模式的切換開關,以及溫度範圍的滑桿。變更其中任何一項都會將新值傳送給指令碼。#

檢視依通道組織。指令碼註冊的每個通道都會成為一個以通道名稱為標題的區段,而通道中的每筆記錄都會成為其下的一列,順序依指令碼加入的順序。當指令碼註冊通道或變更通道的結構時,檢視會自行重建;當指令碼停止時,最後的數值會保留在畫面上,控制項則停用,直到下一個指令碼啟動為止。

13.1.9.1. 小工具#

讀數與控制項採用顯而易見的形式。label 是帶有單位的唯讀數值;text 記錄是一段靜態的 rich 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)

執行它並將窗格切換至 Channels:會出現一個名為 controls 的區段,包含五個小工具,而移動滑桿會在終端機中列印。其他小工具類型——spinbox、radio、lineedit、text——各只需多一個 add;主機協定章節 會逐一介紹它們。

13.1.9.2. 波形圖#

waveform(波形)記錄是一段具有取樣率的交錯取樣資料——音訊、加速度計軸、ADC 軌跡——檢視會將它繪製為捲動圖表,每個軸一條序列,名稱由指令碼指定。圖表的控制項位於其工具列與圖表本身上:

  • 拖曳可平移,捲動可縮放;按兩下可回到跟隨即時邊緣。History 設定保留多少過去的資料,讓你可以向後平移瀏覽。

  • Auto Scale 會讓垂直軸符合資料範圍;關閉時,軸的範圍即為指令碼宣告的範圍。

  • 在圖表上按右鍵可放置一個 marker(標記)以讀取某個取樣的精確時間與數值,或設定一個 trigger level(觸發位準)。Trigger 備妥後,圖表會保持靜止,直到訊號以上升或下降緣越過該位準為止——這是示波器捕捉暫態的方式——而 Re-arm 則等待下一次觸發。

  • Spectrum 會將圖表切換為同一資料的頻率圖,可選擇窗函數(Hann、Hamming、Blackman、矩形)、跨影格的 Averaging 與 Peak Hold,以及 Log Frequency 軸。圖表下方的讀數會標明峰值頻率及其位準。

  • Pause 會凍結圖表而不影響錄製;Stats 會加上一行最小/最大/RMS 值;右鍵選單則可將圖表儲存為影像。

The Channels view with two waveform records scrolling live: a 16 kHz microphone trace and a three-series accelerometer trace with its X, Y, and Z legend, each with its Record, Spectrum, Pause, and Stats buttons

兩個即時捲動的波形記錄:一條麥克風軌跡與一條三序列 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(深度)記錄是一個距離網格——飛時測距感測器所產生的影格——檢視會將它繪製為放大至圖表高度的偽色彩圖。工具列用來選擇呈現方式:palette(調色盤;相機自有的 Depth、Rainbow 與 Ironbow 調色盤,再加上供事件相機使用的淺色與深色調色盤)、放大小型感測器網格的 scaling filter(縮放濾鏡;bicubic 可得到平滑的表面、bilinear,或 nearest 讓每個感測器像素對應一個方塊),以及將距離轉換為色彩的 tone mapping(色調映射):Script Range 使用指令碼宣告的範圍,Auto Range 會將色彩拉伸至每張影格中實際存在的距離範圍(忽略離群的少數百分比),而 Equalize 則依影格的分布將色彩展開,使平坦的場景仍能顯現細節。標頭會回報深度圖的大小、宣告的範圍,以及影格的最小值、最大值與平均值;將滑鼠停留在圖上可讀出游標下的距離,Save Image 則會將呈現的圖寫入 PNG。

The Channels view showing an 8 x 8 time-of-flight depth map scaled up smoothly with the Depth palette, the palette, scaling, and tone-mapping selectors in its toolbar, and the header reporting the size, declared range, and the frame's minimum, maximum, and mean

感測器前方一隻手的飛時測距深度圖,工具列中有調色盤、縮放與色調映射選擇器。#

深度欄位宣告其網格大小與範圍;每次更新都是以緊密排列的 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. 錄製#

每個圖表與深度圖都有一個 Record 按鈕,而擁有多個圖表的通道則有一個 Record All 列可一起啟動它們。錄製會在資料抵達時寫入資料本身——而非呈現結果——寫入的檔案格式依你在儲存對話框中選擇的副檔名而定:CSV 適用於試算表與繪圖工具,WAV 適用於單通道音訊波形,NumPy .npy 陣列適用於 Python,而 Edge Impulse JSON 或 CBOR 則可直接上傳至 Edge Impulse 專案作為訓練資料,並可選擇性加上用來為檔案命名的標籤。只會提供能容納該記錄資料的格式;例如深度圖就不能是 WAV 檔案。長時間的擷取會分割為編號的分段,因此不會有單一檔案變得過於龐大,而錄製也能在指令碼重新註冊其通道後繼續。按鈕旁的讀數會追蹤已寫入的取樣數與檔案大小。

窗格的 Channel Polling 間隔位於偏好設定的協定區段中,用來設定 IDE 向相機要求新通道資料的頻率;預設值適合每秒發布幾次的指令碼,而串流音訊的指令碼則會受益於較短的間隔。

也參考

protocol.CBORChannel 說明指令碼端——記錄類型、其引數與 on_write 回呼——而 File → Examples 下的 12-Protocol 範例則提供可發布讀數、控制項與深度圖的可運作指令碼。