13.1.10. 獨立終端機視窗

Tools → Open Terminal 會開啟獨立的終端機視窗 -- 每一個都是在自己的視窗中執行的迷你 OpenMV IDE 工作階段,具備影格緩衝區檢視器、直方圖以及互動式終端機,並透過你選擇的傳輸方式連接。主視窗的連線不受影響,因此獨立終端機可讓你在第一台相機保持連線的同時觀看第二台相機,或是對網路另一端的相機進行除錯。

獨立終端機視窗:左側是互動式終端機及其工具列,右側是影格緩衝區與直方圖

透過序列埠連接的獨立終端機視窗:左側是互動式終端機及其 run / stop / reset 工具列,右側是影格緩衝區與直方圖 -- 當相機以帶內方式串流影格時,它們便會亮起作用。

New Terminal 會要求你選擇三種傳輸方式之一:

  • Serial port -- 任何鮑率(預設 115,200)下的任何序列埠。這涵蓋第二台相機的 USB 埠、透過 USB-to-UART 橋接器接線的相機、藍牙序列連線(會顯示為一般的序列埠),或任何需要終端機的非 OpenMV 序列裝置。在 USB 埠上,鮑率並不會限制速度 -- 資料一律以 USB 連線的速度傳輸 -- 但在相機的 USB 埠上應避免使用 921,600 與 12,000,000,因為這兩者會使相機從 REPL 切換到 IDE 的除錯協定。

  • TCP -- 連線到指定主機與連接埠上的伺服器,或在指定的連接埠上以伺服器身分監聽。

  • UDP -- 與上述相同的兩種角色,但透過 UDP。

IDE 會記住最近十組設定,並將它們列在 Open Terminal 子選單中以供一鍵重新開啟;Clear Menu 則會清除這些記錄。

與主視窗僅供輸出的窗格不同,獨立終端機是完全互動式的:它就是一個 REPL。在提示字元處輸入內容,Python 便會逐行在所連接的相機上執行,並由 MicroPython 本身提供歷史記錄與 Tab 自動補完。工具列另外提供常用控制序列的一鍵等效操作 -- 執行目前編輯器中的指令碼、停止執行中的指令碼,以及軟重置 -- 以及與主終端機窗格相同的清除、儲存與自動換行控制項。

終端機上方的影格緩衝區同樣是即時的。當所連接的相機以帶內方式串流壓縮影格時 -- 也就是嵌入在與其列印輸出相同的串流中 -- 終端機會解碼並顯示這些影格,且 Record 與 Zoom 按鈕的運作方式與主視窗中完全相同。這樣的組合正是 IDE 的網路除錯方案:一台透過 Wi-Fi 公開其 REPL 的相機,無需任何 USB 線即可享有完整的編輯-執行-預覽循環。

13.1.10.1. 以帶內方式串流影格

終端機所理解的編碼方式很簡單:一個 0xFE 位元組開啟一個影格,第二個 0xFE 將其關閉,而兩者之間的每一個酬載位元組最高位元皆設為 1,並攜帶壓縮影像的六個位元 -- 三個影像位元組會變成四個酬載位元組。純文字永遠不會用到那些位元組值,因此影格與 print() 輸出可共用同一串流而不會衝突:終端機會顯示文字並呈現影格。

以下指令碼會擷取影像、將每一影格轉換為 JPEG,並以該形式列印出來。位元封裝是透過 ulab 進行(它沒有位移運算子,因此位移運算改以乘法與除法表示),且速度快到足以跟上相機:

import csi
import sys
import time
from ulab import numpy as np

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()


def encode_for_ide(data):
    n = len(data)
    n3 = n - (n % 3)
    m = (n3 // 3) * 4
    out = bytearray(((n * 8) + 5) // 6 + 2)
    out[0] = 0xFE
    out[-1] = 0xFE
    if n3:
        src = np.frombuffer(data, dtype=np.uint8)
        dst = np.frombuffer(out, dtype=np.uint8)
        b0 = src[0:n3:3]
        b1 = src[1:n3:3]
        b2 = src[2:n3:3]
        dst[1:m + 1:4] = (b0 & 0x3F) | 0x80
        dst[2:m + 2:4] = (b0 // 64) | ((b1 & 0x0F) * 4) | 0x80
        dst[3:m + 3:4] = (b1 // 16) | ((b2 & 0x03) * 16) | 0x80
        dst[4:m + 4:4] = (b2 // 4) | 0x80
    if n % 3 == 2:
        x = data[n - 2] | (data[n - 1] << 8)
        out[m + 1] = 0x80 | (x & 0x3F)
        out[m + 2] = 0x80 | ((x >> 6) & 0x3F)
        out[m + 3] = 0x80 | ((x >> 12) & 0x3F)
    elif n % 3 == 1:
        out[m + 1] = 0x80 | (data[n - 1] & 0x3F)
        out[m + 2] = 0x80 | (data[n - 1] >> 6)
    return out


while True:
    clock.tick()
    img = csi0.snapshot().to_jpeg(quality=80)
    sys.stdout.write(encode_for_ide(img.bytearray()))
    print(clock.fps())

這在獨立終端機中可正常運作,因為 REPL 列印會等待:相機會一直阻塞,直到終端機取走資料為止,因此影格的每一個位元組都會送達,而 JPEG 在 USB 全速或高速連線上的傳輸都很迅速。同樣的指令碼透過 TCP 終端機也能原封不動地運作,這正是上文所述的網路除錯路徑。唯一無法運作的地方是 IDE 的主除錯連線,其輸出通道在溢位時會重置自身而非等待 -- 但在那裡,影格緩衝區檢視器原本就能原生顯示相機的影格,因此並不會遺漏任何內容。