13.1.13. 設定編輯器#

許多相機應用程式需要少數幾項非程式設計人員也能變更的設定 -- 例如閾值、模式、Wi-Fi 密碼 -- 而不必動到腳本。OpenMV Cam Settings Editor 就是 IDE 為此提供的工具:一個 JSON 檔案描述這些控制項,編輯器將該描述轉換成表單,你的腳本則以 json.load() 讀回已儲存的值。此檔案既是組態,也是其自身編輯器的定義,因此儲存數值的同一份組態也會安排出用於編輯這些數值的 GUI。

工具 → OpenMV Cam Settings Editor 子選單包含其三項動作。

  • Open OpenMV Cam Settings Config File 會從已連線相機的磁碟讀取組態並在編輯器中開啟;儲存時會將其寫回相機。

  • Open Config File 會對你電腦上的 .json 檔案執行相同操作,用於離線準備組態。

  • Create Default Config 會將一份入門設定寫入磁碟並開啟它 -- 這是一個演練了每一種控制項類型的範例,可隨時精簡為你自己應用程式所需的設定。

The OpenMV Cam Settings Editor showing the default demo configuration -- Camera, Processing, Network, and System tabs, with the Camera tab's exposure and gain sliders, resolution and pixel-format dropdowns, mirror and flip checkboxes, and a digital-zoom slider, above the Save and Cancel buttons

設定編輯器已開啟預設組態 -- JSON 檔案的各項控制項以表單形式呈現(分頁、滑桿、下拉選單、核取方塊),底部為 Save 與 Cancel。#

13.1.13.1. 設定檔的結構剖析#

設定檔是一個包含兩個鍵的單一 JSON 物件:

  • title(字串,選用)-- 編輯器視窗的標題。預設為 OpenMV Cam Settings Editor

  • controls(陣列,必填)-- 編輯器依序渲染為表單各列的控制項物件清單。這就是整個 GUI。

{
  "title": "Camera Settings",
  "controls": [
    { "type": "slider", "name": "threshold", "label": "Threshold", "value": 50 },
    { "type": "checkbox", "name": "draw_overlays", "label": "Draw Overlays", "value": true }
  ]
}

controls 中的每一個項目都是一個控制項 -- 文字欄位、數字、核取方塊、下拉選單、slider、group、分頁列、靜態 label 等等。其 type 決定要使用的小工具;物件的其餘部分則用來設定它。當檔案儲存時,編輯器無法辨識的任何鍵都會保持原封不動,因此你可以在檔案中將自己的中繼資料與控制項一併保留。

13.1.13.2. 每個控制項共用的鍵#

每一個控制項物件都接受以下這些鍵,無論其 type 為何:

  • type*(字串,必填)-- 要建立哪一種小工具,例如 ``"slider"`` 或 ``"combobox"``。缺少或無法辨識的 *type 會渲染成一個停用的 Unknown control 佔位符,而不會使整個檔案失敗,因此一個拼字錯誤只會讓你損失一個控制項,而非整個表單。

  • name*(字串)-- 供 *你 使用的識別碼。編輯器從不讀取它;只會保留它。為每個控制項指定一個唯一的 name,你的腳本便可用它來尋找該控制項所儲存的值。

  • label(字串)-- 顯示在控制項旁邊或其上的文字。對大多數控制項而言,這是豐富文字 -- HTML 標籤與 <a href> 連結皆可使用。

  • tooltip(字串)-- 控制項的滑鼠停留提示文字。

  • enabled(布林值,預設為 true)-- 設為 false 可讓控制項顯示為灰色且唯讀。

容器控制項(grouptabs)的標題使用 title 而非 label,且 tabs 的提示文字是按分頁分別設定的;相關差異已註記於各控制項的頁面上。

13.1.13.3. 儲存與讀取值#

會儲存值的控制項 -- 核取方塊、slider、文字欄位 -- 會將該值放在 value 鍵中。當你按下 Save 時,編輯器會將每個控制項目前的 value 就地直接寫回同一個控制項物件,並重寫檔案。檔案會保留其結構:沒有任何東西移動,只有 value 鍵改變。並沒有一份將 name 對應到值的獨立扁平對照表 -- 這些值就存放在控制項內部,正是它們被定義的地方。

這就是為什麼版面配置對你的腳本很重要。controlstabs 陣列會保留你建立它們時的順序,因此檔案無法像字典那樣以名稱建立索引 -- config["controls"]["threshold"] 是行不通的。這個小輔助函式彌補了這個落差:給它一條通往你想要的值的名稱路徑,它便會為你走遍整棵樹。

import json


def find(node, *path):
    """Return the control at a path of names; read its value with
    ["value"]. The tab strip and any unnamed group are transparent,
    so you list only the tabs and controls you gave a name."""
    for key in path:
        match = _child(node, key)
        if match is None:
            raise KeyError(key)
        node = match
    return node


def _child(node, key):
    for member in node.get("controls", []) + node.get("tabs", []):
        if member.get("name") == key or member.get("title") == key:
            return member
        if "name" not in member and ("controls" in member or "tabs" in member):
            match = _child(member, key)   # see through an unnamed group / the tab strip
            if match is not None:
                return match
    return None


with open("config.json") as file:
    config = json.load(file)

threshold = find(config, "threshold")["value"]

路徑中的每一個步驟都是一個控制項的 name,或是一個分頁的 title*(分頁本身不帶 *name,不過你可以自行加上一個,它會被保留)。分頁列以及任何你未命名的 group 都是透明的 -- find 會逐步穿越它們 -- 因此你只需列出分頁以及你所標記過的控制項,這與巢狀的 config[...][...] 查詢十分接近。已命名的 group 本身即為一個步驟,因此 find 也能回傳它的核取方塊值。為每個控制項指定一個唯一的 name,路徑便絕不會產生歧義。完整範例 會從巢狀的示範版面配置中讀出值。

由於設定是純 JSON,你的腳本中沒有任何部分依賴於編輯器 -- 編輯器只是寫入你的腳本本來就會讀取的檔案。裝置可以同時攜帶它的設定以及一個易於變更設定的方式,而腳本則始終只是單純的值讀取者。

有一種控制項類型會在 Save 時強制驗證其輸入:帶有 maskregexlineedit 會在其文字無效或不完整時拒絕讓編輯器儲存,並指明需要修正的欄位。其他每一種控制項都會在你編輯時就約束輸入 -- slider 無法超出其範圍,spinbox 會夾限在其界限之內 -- 因此已儲存的檔案永遠是有效的。

13.1.13.4. 設定檔參考#

各控制項類型記載於下方頁面中,並依其功能分組。