예제: YOLO 영상 데모
영상에 YOLO 검출을 실행하는 데모 앱으로 장비 연결, 추론 결과 전송, 프레임 수집, 모델 교체 확인
필요 권한: 설정ADMIN
영상에 YOLO 객체 검출을 실행하고 결과를 중앙에 보고하는 데모 앱 mlops-edge-demo. 에이전트(geo-mlops-edge)를 따로 실행하지 않고 애플리케이션 안에 SDK 런타임(EdgeRuntime)을 포함하는 구성의 예. 소스는 공개 저장소 https://github.com/osom8979/mlops-edge.
구성
| 데모 코드가 하는 일 | SDK 런타임이 하는 일 |
|---|---|
| 영상 읽기, YOLO 추론, 창에 결과 표시 | 등록, 하트비트, 명령 롱폴, 로컬 API(8600) |
| 추론 결과를 1초마다 큐에 추가 | 로컬 큐 저장, 연결 시 업로드, 연결 중단 시 누적 |
수집 대상 프레임을 JPEG 로 인코딩해 frames 수집기에 전달 | LabelMe 사전 라벨 생성, 중복 방지, 청크 업로드 |
| 모델 활성화 이벤트를 받아 재생 중인 모델 교체 | pull_model 처리(내려받기, 캐시, 활성화) |
중앙과 주고받는 데이터는 다음과 같음.
| 방향 | 내용 | 중앙에서 확인하는 곳 |
|---|---|---|
| 장비 → 중앙 | 등록, 하트비트(10초) | Edge Fleet 목록과 디바이스 상세 |
| 장비 → 중앙 | 추론 결과 1초에 1건 | 디바이스 상세 추론 결과 탭 |
| 장비 → 중앙 | 30프레임마다 JPEG 1장 + LabelMe JSON 1개 | 지정한 데이터셋의 파일 탭. 지정하지 않으면 수집 데이터 |
| 중앙 → 장비 | 명령 ping, restart, resync, set_policy, pull_model | 디바이스 상세 명령 탭 |
준비
- uv. Python 3.12 는 저장소의
.python-version에 맞춰 uv 가 설치. - 창을 표시할 화면. 화면이 없는 장비는
--no-show로 실행. - (선택) NVIDIA GPU. 없으면 CPU 로 추론.
- 장비에서 중앙 API 주소로 향하는 HTTPS 연결, 디바이스 등록용 테넌트 ADMIN 계정( 설정ADMIN).
git clone https://github.com/osom8979/mlops-edge.git
cd mlops-edge
uv sync
Linux 에서는 CUDA 빌드 PyTorch 와 GPU 라이브러리(합계 약 3 GB)를 함께 설치. 회선이 느려 Failed to download distribution due to network timeout 이 발생하면 UV_HTTP_TIMEOUT=600 uv sync 로 재실행.
1. 디바이스와 데이터셋 생성
- Edge Fleet 의 디바이스 등록 에서 디바이스 ID 입력(예:
edge-yolo-01). 스코프는 기본값(모두 활성화) 유지. 추론 결과에inference:write, 프레임 업로드에data:write, 모델 교체에models:read필요. 화면 절차는 5분 시작 참고. - 등록 완료 창의 토큰 복사. 창을 닫으면 다시 확인 불가.
- (선택) 프레임을 받을 데이터셋 생성. 데이터셋 의 새 데이터셋 에서 직접 업로드 를 선택하고 모달리티를 이미지로 지정. 상세 화면 주소의 ID(
ds-…) 복사. 생략하면 프레임은 수집 데이터 에 저장.
2. 설정
저장소의 edge.yaml 에서 세 곳 변경.
central:
base_url: https://mlops.example.com # 중앙 API 주소
device:
id: edge-yolo-01 # 1단계의 디바이스 ID
collectors:
- type: frames
name: demo-frames
priority: 30 # 추론 결과(50)보다 낮게. 회선이 좁으면 결과를 먼저 전송
options:
every_n_frames: 30 # 30 fps 영상이면 초당 1장
dataset_id: "ds-0123456789ab" # 1단계의 데이터셋 ID. 비우면 수집 데이터
labels: true # LabelMe 사전 라벨 동반
label_conf: 0.5 # 이 신뢰도 이상인 검출만 사전 라벨로 사용
토큰은 설정 파일이 아니라 .env 에 저장.
cp .env.example .env
# .env 에 입력: GEO_EDGE_CENTRAL__TOKEN=<device-token>
- 설정 우선순위는 환경 변수 >
.env>edge.yaml> 기본값(SDK 규칙과 동일). 전체 항목은 설정 레퍼런스 참고. - 데모의
edge.yaml은 화면에서 빨리 확인하도록 하트비트를 10초로 지정(기본 30초). - 토큰이 없어도 실행 가능. 결과는 로컬 큐(
data/edge/)에만 누적되고, 토큰 지정 후 재실행하면 전송.
3. 실행
uv run mlops-edge-demo
인자를 생략하면 기본 영상(도심 주행 17초)을 cache/videos/ 에 한 번 내려받아 반복 재생. 가중치 yolo26n.pt(COCO 사전학습)도 cache/weights/ 에 내려받음. 창에서 q 또는 ESC 로 종료.
창 왼쪽 위 상태 표시줄의 의미는 다음과 같음.
| 줄 | 내용 |
|---|---|
| 1 | MLOps Edge Demo 와 디바이스 ID |
| 2 | 모델 이름:버전 과 프레임당 추론 시간. 시작 시 버전은 pretrained |
| 3 | FPS, 검출 수와 클래스별 개수 |
| 4 | 중앙 연결 상태 / 등록 여부 / 대기 건수 / 동기화 상태 / 이번 실행에서 수집한 프레임 수. 색은 초록 online, 노랑 offline·probing, 빨강 auth_failed |
다른 영상과 실행 방식:
uv run mlops-edge-demo ./line-camera.mp4 # 로컬 파일 (내려받지 않고 바로 열기)
uv run mlops-edge-demo rtsp://192.0.2.10/stream # RTSP 스트림
uv run mlops-edge-demo --no-show # 화면 없는 장비. 5초마다 상태를 로그로 출력
| 옵션 | 기본값 | 설명 |
|---|---|---|
SOURCE | 기본 영상 URL | HTTP(S) URL 은 내려받아 캐시. 그 외(파일, 스트림 주소)는 OpenCV 로 직접 열기 |
--weights | yolo26n.pt | YOLO 가중치. 경로 없이 이름만 지정하면 COCO 사전학습 모델을 캐시에 내려받음 |
--conf | 0.25 | 검출 신뢰도 임계값(화면 표시와 추론 결과 기준) |
--config | edge.yaml | 런타임 설정 파일 |
--env-file | .env | 토큰 등 비밀값 파일. 없으면 무시 |
--cache-dir | cache | 영상·가중치 캐시 |
--report-interval | 1.0 | 추론 결과 전송 간격(초). 0 이면 매 프레임 |
--once | 반복 재생 없이 영상 끝에서 종료 | |
--no-show | 창 없이 실행 |
데모 안의 런타임이 로컬 API(127.0.0.1:8600)를 열기 때문에 SDK CLI 로 상태 조회 가능(CLI).
uv run geo-mlops-edge status --config edge.yaml # 연결, 대기 건수, 모델, 조치 필요 사항
uv run geo-mlops-edge queue --config edge.yaml --limit 5
같은 PC 에서 geo-mlops-edge 에이전트가 이미 8600 포트를 사용 중이면 데모는 로컬 API 만 비활성화(local API disabled 경고)하고 중앙 연동은 유지.
4. 중앙에서 확인
- Edge Fleet 에서 디바이스 열기. ① SDK 버전과 위치 ② 쌓인 데이터 의 대기 건수가 0 이면 수집분 전송 완료 ③ 수집기
demo-frames가running.데모 장비의 상세: ① SDK 버전과 위치 ② 대기 0 이면 수집분 전송 완료 ③ frames 수집기 demo-frames 의 상태 - 추론 결과 탭에 1초마다 1건 기록. ① 모델은 가중치 파일 이름과 버전
pretrained. 중앙에서 받은 모델로 교체하면 그 모델의 이름과 버전 ② 자세히 로 결과 JSON 펼치기. 검출마다 클래스 번호·이름, 신뢰도, 0~1 로 정규화한 박스[x1, y1, x2, y2].추론 결과 탭: 데모가 1초마다 보낸 YOLO 검출 결과. ① 모델 이름과 버전 ② 결과 JSON(클래스·신뢰도·정규화 박스) 펼치기 - 데이터셋 파일 탭에 30프레임마다 이미지 1장과 같은 이름의 LabelMe JSON 등록. 기본 영상은 한 바퀴에 17장. ① 라벨과 짝을 이룬 이미지 수가 학습 가능 여부에 반영 ② 종류 필터를 이미지로 지정하면 프레임만 표시 ③ 카드의 "라벨 있음" 이 짝이 맞은 표시.
수집 데이터셋의 파일 탭: ① 이미지 17장이 라벨과 짝을 이뤄 학습 가능 ② 종류 필터를 이미지로 ③ 프레임 이미지(같은 이름의 LabelMe 사전 라벨이 짝) - 이미지를 열면 모델이 그린 사전 라벨(사각형) 표시. ① 주석 숨김 으로 원본 확인 ② 메타데이터의 라벨 짝에 같은 이름의
.json.수집 프레임 미리보기: 모델이 그린 사전 라벨(사각형) 표시. ① 주석 표시 전환 ② 메타데이터(라벨 짝, 크기)
- 파일 이름은
<영상 이름>_<프레임 번호 6자리>.jpg. URL 로 받은 영상은 캐시 파일 이름(URL 해시 12자 + 원래 이름)이 영상 이름. 예:c5144a2b5077-kitti-inference-vid_000420.jpg. - 사전 라벨은 현재 모델의 예측. 그대로 학습하면 모델이 자기 오류를 다시 학습하므로 학습 전 사람의 검수 필요.
- 반복 재생하거나 재실행해도 이미 큐에 넣은 프레임은 다시 전송하지 않음. 기록은
data/edge/collectors/demo-frames.seen.
5. 학습한 모델로 교체
수집한 프레임으로 학습한 모델을 이 장비로 다시 전송하는 절차.
- 수집 데이터셋 상세의 이 데이터셋으로 학습 으로 학습 생성. 마지막 단계에서 모델 레지스트리 등록 을 활성화하고 등록 모델 이름 지정(예:
edge-demo-yolo). 등록하지 않은 모델은 장비가 내려받을 수 있는 목록에 없음. 이름에/와'사용 금지(장비의 내려받기 경로와 레지스트리 조회 오류). - 학습 완료 후 디바이스 상세 명령 탭에서 모델 내려받기 를 선택하고 모델과 버전 지정 후 명령 보내기.
- 장비가 다음 명령 확인 때 모델을 내려받아
data/edge/models/에 캐시하고 활성화. 데모는 활성화 이벤트를 받아 재생 중인 모델을 교체하고switched model: yolo26n:pretrained -> edge-demo-yolo:1로그 출력. 이후 추론 결과와 사전 라벨은 새 모델 이름과 버전으로 기록.
- 화면에 반영하는 것은 YOLO 프레임워크 모델만. 다른 프레임워크 모델은 경고 로그만 남기고 기존 모델로 계속 재생.
- 캐시한 모델은 다음 실행 시 다시 활성화. 활성화 규칙은 모델과 추론 참고.
- 승인(Production 승격)이나 배포 화면은 장비로 모델을 전송하지 않음. 장비로 보내는 방법은
pull_model명령, 장비 CLI, 로컬 API 의 세 가지.
재시작과 종료
q·ESC·Ctrl-C·SIGTERM으로 종료하면 런타임이 큐 정리와 DB 닫기를 마친 뒤 종료.- 디바이스 상세의 재시작 이나
restart명령을 받으면 종료 코드 3 으로 종료. systemd 같은 감시 프로그램 아래에서 실행하면 재기동(배포, 명령·정책·보존).
코드 구성
mlops-edge/edge.yaml런타임 설정 (중앙 주소, 디바이스 ID, 주기, 보존, 수집기).env.example토큰 템플릿src/mlops_edge_demo/cli.py명령행 인자, 설정 로드, 조립video.pyURL 영상 내려받기와 캐시detector.pyYOLO 추론 결과를 SDK 추론 계약(InferenceOutput)으로 변환edge.pyEdgeRuntime 을 별도 스레드에서 실행. 결과 보고, 프레임 수집, 모델 교체 연결player.pyOpenCV 재생 루프와 상태 표시줄애플리케이션에 런타임을 포함할 때의 핵심은 스레드 분리. OpenCV 창은 메인 스레드에서 동작하고 EdgeRuntime 은 asyncio 이벤트 루프가 필요하므로, 런타임은 별도 스레드의 루프에서 실행하고 영상 루프는 asyncio.run_coroutine_threadsafe 로 작업만 넘긴 뒤 기다리지 않음.
# edge.py 의 요약. 런타임 스레드에서 실행
runtime = EdgeRuntime(settings)
runtime.events.add_listener(on_event) # pull_model 로 모델이 활성화되면 알림
await runtime.start() # 등록·하트비트·명령·업로더·수집기 시작
await serve(runtime, settings, stop) # 로컬 API (geo_mlops_sdk.edge.daemon.serve)
# 영상 루프(메인 스레드)에서: 수집 대상 프레임만 인코딩해 frames 수집기로 전달
if frames.wants(source, index, seekable=True):
ok, jpeg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
asyncio.run_coroutine_threadsafe(
frames.collect(source, index, jpeg.tobytes(), output, model=model_ref), loop
)
frames 수집기 API 는 데이터 수집기, 추론 결과 형식(InferenceOutput)은 모델과 추론 참고.