13.1.13. 설정 편집기#
많은 카메라 애플리케이션에는 프로그래머가 아닌 사람이 스크립트를 건드리지 않고 변경할 수 있는 몇 가지 설정 – 임계값, 모드, Wi-Fi 비밀번호 – 이 필요합니다. OpenMV Cam Settings Editor가 바로 그것을 위한 IDE의 도구입니다. JSON 파일이 컨트롤을 기술하고, 편집기가 그 기술을 양식으로 바꾸며, 스크립트는 json.load()로 저장된 값을 다시 읽습니다. 이 파일은 구성이면서 동시에 자체 편집기의 정의이기도 하므로, 값을 저장하는 바로 그 구성이 그 값을 편집하기 위한 GUI 레이아웃도 구성합니다.
도구 → OpenMV Cam Settings Editor 하위 메뉴에 그 세 가지 동작이 들어 있습니다.
OpenMV Cam 설정 구성 파일 열기는 연결된 카메라의 드라이브에서 구성을 읽어 편집기에서 엽니다. 저장하면 카메라에 다시 기록됩니다.
구성 파일 열기는 오프라인에서 구성을 준비하기 위해, 컴퓨터에 있는
.json파일에 대해 같은 작업을 수행합니다.Create Default Config 는 시작용 설정을 디스크에 작성하고 엽니다 – 모든 컨트롤 유형을 시연하는 예제로, 자신의 애플리케이션에 필요한 설정만 남기고 줄일 수 있도록 준비되어 있습니다.

기본 구성이 열려 있는 설정 편집기 – 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 의 각 항목은 하나의 컨트롤입니다 – 텍스트 필드, 숫자, 체크박스, 드롭다운, 슬라이더, 그룹, 탭 막대, 정적 레이블 등입니다. 그 type 이 위젯을 선택하고, 객체의 나머지가 위젯을 설정합니다. 편집기가 인식하지 못하는 키는 파일을 저장할 때 그대로 유지되므로, 컨트롤과 함께 파일에 자신만의 메타데이터를 보관할 수 있습니다.
13.1.13.3. 값 저장 및 읽기#
값을 가지는 컨트롤 – 체크박스, 슬라이더, 텍스트 필드 – 은 그 값을 value 키에 담습니다. Save 를 클릭하면 편집기는 각 컨트롤의 현재 value 를 동일한 컨트롤 객체 안 제자리에 그대로 기록하고 파일을 다시 씁니다. 파일은 구조를 유지합니다: 아무것도 이동하지 않고 value 키만 바뀝니다. name 을 값에 대응시키는 별도의 평면 맵은 없습니다 – 값은 컨트롤이 정의된 바로 그 안에 존재합니다.
바로 이 때문에 레이아웃이 스크립트에 중요합니다. controls 와 tabs 배열은 만든 순서를 유지하므로 파일을 딕셔너리처럼 이름으로 인덱싱할 수 없습니다 – 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 시 입력을 강제합니다: mask 또는 regex 가 있는 lineedit 은 텍스트가 유효하지 않거나 미완성인 동안 편집기가 저장하지 못하게 막고, 수정해야 할 필드를 지목합니다. 다른 모든 컨트롤은 편집하는 동안 입력을 제약합니다 – 슬라이더는 범위를 벗어날 수 없고, 스핀박스는 경계 값으로 제한됩니다 – 그러므로 저장된 파일은 항상 유효합니다.
13.1.13.4. 설정 파일 레퍼런스#
컨트롤 유형은 하는 일에 따라 묶여 아래 페이지들에 문서화되어 있습니다.