소량의 손라벨 데이터로 모델을 학습시키고 → 나머지 이미지를 자동 라벨링 → 웹에서 검수 → 검수분을 합쳐 다시 학습, 이 사이클을 반복하는 로컬 전용 파이프라인입니다. 인터넷으로 이미지를 보내지 않고 전부 이 PC에서 처리합니다.
시드 라벨(수동)
│
▼
[ train ] ──▶ models/best.pt
│ │
│ ▼
│ [ label ] 자동 라벨 생성 (신뢰도 포함)
│ │
│ ▼
│ [ review ] 웹 UI에서 검수·수정 → 승인
│ │
└────────────────┘ 승인분이 학습 데이터에 누적됨
이 PC 에는 이미 구성되어 있습니다. 다른 PC 에서는 아래 새 PC 에서 시작하기를 따르세요.
| 항목 | 값 |
|---|---|
| Python | 3.14.3 |
| PyTorch | 2.14.0+cu130 (CUDA 13.0) |
| Ultralytics | 8.4.142 (WSL 학습 환경도 같은 버전) |
| GPU | RTX 5060 Laptop 8GB, sm_120 네이티브 지원 |
| 검출 모델 | Ultralytics YOLO26 (yolo26m — 11-08 전에는 yolo26s, DFL-free) |
| 보조 모델 | SAM 2.1 tiny (검수 UI 의 클릭 한 번 박스) |
저장소를 다른 PC 에 클론했을 때의 순서입니다. git 에는 코드·설정·문서와 runs/ 의 텍스트 기록만
있고 이미지·DB·가중치는 없습니다 — 그것들은 git 이 아니라 백업으로 지킵니다(아래 4).
1. git 사용자. Claude Code 로 작업하면 턴이 끝날 때마다 .claude/hooks/autocommit.sh 가 바뀐 파일을
로컬에 커밋합니다(push 는 하지 않습니다). 이름·메일이 없으면 그 커밋이 실패해 턴마다 실패 알림만 뜨고
아무것도 커밋되지 않으니, 클론한 폴더에서 먼저 정해 두세요. 저장소가 공개되면 커밋 메일도 누구나 볼 수
있으니, 메일을 감추려면 GitHub 의 noreply 주소(GitHub 설정 → Emails 에 나옵니다)를 쓰세요.
git config user.name "<이름>"git config user.email "<메일>"2. 가상환경. Python 3.14 로 저장소 폴더 안에 .venv 를 만듭니다 — 3.14.3 에서 검증했고, 3.14.1 은
안 됩니다(torchvision 0.29.0 이 그 버전만 받지 않습니다). 이 문서의 python 명령은 모두 이 .venv 의 것
(.venv\Scripts\python.exe)으로 돌립니다 — 시스템 Python 에는 아무것도 깔지 않습니다. 명령은 PowerShell
(또는 cmd)에서 저장소 폴더를 기준으로 적었습니다. Git Bash 에서는 역슬래시가 지워지니
.venv/Scripts/python.exe 처럼 / 로 바꿔 쓰세요.
py -3.14 -m venv .venv3. 패키지 — PyTorch 를 먼저, cu130 인덱스에서. 이 저장소는 PyTorch 의 CUDA 13.0(cu130) 빌드로
검증했습니다. PyPI 의 Windows 빌드(torch 2.14.0 은 124MB 짜리 CPU 빌드)로는 GPU 를 못 쓰기 때문에
requirements.txt 는 torch==2.14.0+cu130 처럼 cu130 빌드를 콕 집어 고정해 두었습니다 — 순서를 바꿔
requirements.txt 부터 깔면 CPU 빌드가 조용히 들어오는 대신 'No matching distribution' 으로 멈춥니다. 버전도
빼지 마세요 — 빼면 인덱스의 최신(2026-10-31 에 2.14.1)이 깔려 다음 단계가 같은 오류로 멈춥니다.
.venv\Scripts\python.exe -m pip install torch==2.14.0+cu130 torchvision==0.29.0+cu130 --index-url https://download.pytorch.org/whl/cu130.venv\Scripts\python.exe -m pip install -r requirements.txt.venv\Scripts\python.exe -c "import torch; print(torch.__version__, torch.cuda.is_available())"2.14.0+cu130 True 가 나오면 됩니다. 2.14.0 False 처럼 +cu130 이 없으면 CPU 빌드가 깔린 것이니 첫 명령을
다시 돌리세요 — 버전을 +cu130 까지 적었기 때문에 깔려 있던 CPU 빌드를 cu130 빌드로 바꿉니다.
tools/label_qa_claude.py(Claude API 파일럿)를 돌릴 때만
anthropic==1.6.0 을 더 받습니다. WSL 학습 버튼을 쓰려면 WSL 안의 conda 환경이 따로 있어야 합니다
(CLAUDE.md 'WSL 학습').
4. 데이터·DB·가중치 되살리기. 아래 네 폴더는 git 에 없습니다. 다른 디스크 백업(CLAUDE.md '다른 디스크
백업' — 지금은 C:\local_auto_labeling_backup\20261031\)에서 이 폴더들만 저장소의 같은 자리로 복사하세요.
그 뒤에 더한 …\20261105\(그날의 DB·새 파일)가 있으면 그 폴더의 BACKUP_INFO.txt 대로 위에 덮어 넣습니다.
백업 폴더를 통째로 덮으면 그 안의 .git 과 옛 문서까지 덮여 저장소가 백업 시점으로 돌아갑니다.
되살리기 전에는 labeler.py 명령과 검수 UI 를 띄우지 마세요 — 빈 labeling.db 를 새로 만듭니다(UI 는 그
빈 DB 의 사본까지 backups/ 에 남깁니다). Claude Code 도 끈 채 복사하세요 — 켜 두면 턴이 끝날 때 자동 커밋
훅이 덮인 runs/ 기록을 커밋합니다.
| 폴더 | 들어 있는 것 |
|---|---|
data/visible/ |
이미지·라벨과 labeling.db(검수 상태·박스·변경 이력 — 작업물 그 자체), backups/(DB 사본) |
models/ |
학습한 가중치 — 운영 best.pt 와 따로 보관한 best_*.pt |
weights/ |
사전학습 가중치 (yolo26n/s/m/l.pt, sam2.1_t.pt) |
runs/ |
학습 체크포인트·그래프·로그 — 중단한 학습을 이어 하려면 필요합니다(저장소가 같은 경로일 때만 — 이어하기는 체크포인트에 적힌 데이터셋 절대경로를 봅니다) |
runs/ 의 텍스트 기록(results.csv 등)은 git 에도 있습니다. 백업의 옛 파일로 덮으면 다음 자동 커밋이
그 옛 내용을 커밋하니, 덮었다면 git checkout -- runs 로 되돌리세요.
5. 경로 맞추기. labeling.db 는 이미지를 절대경로로 들고 있습니다 — images.path 가
E:\local_auto_labeling\data\visible\… 꼴입니다(2026-10-31 기준 7,067장 전부). 그래서 저장소를 같은
경로 E:\local_auto_labeling 에 두면 그대로 동작하고, 다른 경로에 두면 모든 이미지가 '파일 없음' 이
됩니다(config.yaml 의 경로는 저장소 기준 상대경로라 고칠 것이 없습니다).
경로를 고치기 전에는 스캔(labeler.py scan, 검수 UI 의 스캔 버튼)·학습·prune·정리 버튼을 쓰지 마세요.
스캔은 시드 사진을 검수 전 라벨로 다시 등록하고(그래서 스캔은 DB 의 절반 넘게 '파일 없음' 이면 멈춥니다),
prune·정리는 '파일 없음' 인 항목을 라벨째 지웁니다(절반 넘게 없으면 멈추지만, 일부만 되살린 상태라면 그만큼
지웁니다).
다른 경로를 써야 한다면 검수 서버를 끈 채 DB 를 따로 복사해 두고, 저장소 폴더에서 .venv\Scripts\python.exe
를 띄워 아래를 붙여 넣습니다. 새 경로는 손으로 적지 않고 이 저장소의 실제 위치(ROOT)에서 가져옵니다 —
대소문자나 / 가 다르게 적히면 디스크와 표기가 어긋나기 때문입니다.
import sqlite3, sys
sys.path.insert(0, "src")
from autolabel.config import ROOT, Config
old = "E:\\local_auto_labeling\\"
new = str(ROOT) + "\\"
con = sqlite3.connect(Config.load().db)
n = con.execute("UPDATE images SET path = ? || substr(path, ?) WHERE substr(path, 1, ?) = ?",
(new, len(old) + 1, len(old), old)).rowcount
con.commit()
con.close()
print(n, "행을", new, "로 바꿨습니다")n 이 0 이면 DB 의 경로가 old 로 시작하지 않는 것입니다. UNIQUE constraint failed: images.path 가 나면
경로를 고치기 전에 스캔해 사진이 새 경로로 다시 들어간 것이니, 따로 복사해 둔 DB(또는 백업의
labeling.db)를 다시 가져와 처음부터 하세요. 앞부분만 바꾸므로 old 와 new 를 맞바꿔 다시 돌리면 원래대로
돌아옵니다. 그다음 확인합니다.
.venv\Scripts\python.exe labeler.py check'이상 없음.' 이 나오면 준비가 끝났습니다. '파일 없음' 이 남았는데 check 가 prune 을 권해도 아직 쓰지
마세요 — 덜 되살린 폴더가 있는지부터 봅니다. '경로 표기' 는 DB 에 적힌 경로가 디스크의 실제 경로와 달라(연결
폴더·다른 드라이브 이름 등) 스캔이 같은 사진으로 알아보지 못하는 항목입니다. 끝나면 labeler.py status 로
장수를 보고 labeler.py review 로 검수 UI 를 띄웁니다.
프로젝트마다 폴더가 따로입니다 (2026-09-21 정리). 지금 프로젝트는 가시광(config.yaml) 하나이고,
프로젝트를 늘리면 data/<이름>/ 안에 같은 모양으로 들어갑니다.
data/visible/seed/ ← 이미 라벨링된 데이터 (하위 폴더 재귀 탐색)
data/visible/unlabeled/ ← 자동 라벨링할 이미지
data/visible/labeled/ ← 검수를 마쳐 '정리'로 옮겨진 것
data/visible/dataset/ ← 학습할 때 자동으로 만들어지는 폴더 (건드릴 필요 없음)
data/visible/labeling.db ← 라벨과 검수 상태. 작업물 그 자체입니다
영상은 검수 UI 의 영상 캡처 탭(아래 '영상 캡처')에서 몇 초마다 한 장 뽑아 눈으로 골라 넣으면, 카메라 폴더·촬영 시각이
붙은 이름으로 unlabeled/<카메라>/ 에 들어가 곧바로 등록됩니다.
지금은 COCO 로 받습니다. Roboflow 내보내기를 회차별 폴더로 쌓고
(data/visible/seed/seed1, seed2, …, 각각 coco/*.json + images/), config.yaml 의
data.seed_coco: data/visible/seed 가 하위 json 을 전부 읽습니다. 다음 회차는 seed3 을 같은
모양으로 넣고 scan 만 돌리면 됩니다. Roboflow 의 YOLO 내보내기는 가려져 조각난 객체의
박스를 잘라 버리므로 쓰지 마세요(COCO 대비 IoU 0.7 미만이 6%).
휴지통을 비워 시드 사진을 지우면 json 에는 이름이 남아, scan 이 'JSON 에 있으나 파일이 없는 이미지 N장' 으로
알리고 건너뜁니다(다시 넣지는 않습니다). 지운 것이 맞으면 tools/prune_seed_json.py --apply 로 json 에서 그 항목을
뺍니다. 원본 json 은 seed_coco 폴더 밖의 seed_json_backup/<시각>/ 에 떠 두고, 기본은 보기만 합니다.
아래는 seed_coco 를 비웠을 때의 YOLO .txt 방식입니다. 경로에 images/ 폴더가 있으면
같은 위치의 labels/ 에서 같은 이름의 .txt 를 찾습니다.
data/visible/seed/images/x.jpg -> data/visible/seed/labels/x.txt
data/visible/seed/train/images/x.jpg -> data/visible/seed/train/labels/x.txt (Roboflow 내보내기)
valid/ · test/ 도 동일
train/valid/test 로 나뉜 내보내기를 그대로 넣으면 셋을 모두 학습 풀에 넣고, 이 도구의
규칙으로 train/val/test 를 다시 나눕니다(아래 '데이터 나누기'). 내보내기의 분할을 그대로
쓰는 기능은 없습니다. valid·test 를 아예 빼려면 data.seed_images 를 data/visible/seed/train 으로
좁히세요.
클래스 목록은 config.yaml 의 classes → data/visible/classes.txt → 시드 라벨 순으로
정합니다. 지금은 config.yaml 에 10개(bicycle, bus, car, motorcycle, person, scooter,
truck, pickup, responder, cone)가 적혀 있습니다 — 밴은 car 입니다(2026-11-02 에 van 을 car 로 합쳤습니다).
responder(출동 차량)·cone(라바콘)은 2026-11-09 에 붙였고 판정은 docs/label-rules.md 에 있습니다. 순서가 곧
클래스 번호라 뒤에만 붙이고 중간에 끼워 넣거나 빼지 마세요. 맨 끝 클래스를 다른 클래스에 합칠 때는
tools/merge_class.py 를 씁니다(config.yaml 에서 먼저 빼고, 기본은 보기만). .txt 라벨을 쓴다면
순서가 클래스 ID 와 맞아야 합니다.
data/visible/classes.txt 를 쓸 때는 한 줄에 하나씩 적습니다.
.venv\Scripts\python.exe labeler.py scan폴더를 훑어 DB에 등록하고 클래스를 확정합니다. 시드 라벨은 정답으로 보고 곧바로 승인 상태가 됩니다.
.venv\Scripts\python.exe labeler.py status지금 상태를 요약합니다 — DB 위치, 운영 가중치, 클래스 목록, 상태별 장수(미라벨·검수 대기·승인·건너뜀과 휴지통까지 더한 합계).
.venv\Scripts\python.exe labeler.py train승인된 라벨로 학습합니다. 결과는 models/best.pt 로 복사됩니다. epoch 수는 --epochs N 으로 바꿀 수
있습니다(기본은 config.yaml 의 train.epochs).
.venv\Scripts\python.exe labeler.py label미라벨 이미지에 자동 라벨을 붙입니다 (검수 대기 상태로).
label --all 은 조심하세요. 이미 검수 대기인 이미지까지 다시 추론해 그 라벨을 모델 예측으로
덮어씁니다. 검수 대기 중인 VIRAT 프레임(tools/import_virat.py 가 넣은 것, 2026-11-07)은 모델이 못 찾은
VIRAT 박스까지 지워집니다 — 그 프레임을 지금 모델로 다시 라벨링하려면 tools/import_virat.py --relabel 을 쓰세요.
옛 라벨은 변경 내역에 남지만 되돌리려면 이미지마다 해야 합니다. 검수 UI 의 자동 라벨링 버튼은 미라벨 이미지만
추론합니다.
타일 추론 (2026-11-10) — labeler.py label --tiles, 또는 검수 UI 모델 → 자동 라벨링 (타일). 이미지 통째 추론에 더해,
긴 변이 800px 넘는 이미지는 2×2 조각(겹침 20%)으로도 추론해 합칩니다. 조각 경계에 잘린 박스는 버리고, 조각과 통째가 같은
물체를 낸 중복은 IoU 0.3 으로 지웁니다. 멀리 있는 작은 물체를 더 잡는 대신 오검출이 늘고 시간이 약 2배입니다 — 옛 CCTV
test 에서 재현율 +1.8pt, 오검출 +22%, F1 은 같았습니다(docs/experiments.md '타일 추론'). 풀 해상도 CCTV 화면처럼 큰 이미지에서
작은 물체를 많이 놓칠 때 쓰세요. 지금 운영 모델로 옛 CCTV test 의 큰 이미지 40장을 견주어 보니(2026-11-10, CPU) 재현율
0.921 → 0.943, 오검출 24 → 29개였고 시간은 3.4배였습니다(GPU 로는 측정에서 약 2배). 설정은 config.yaml 의 predict.tiles
이고, 예측 비교(P)는 통째 그대로입니다. 타일 모드로 라벨링한 이미지는 조각을 안 쓴 작은 이미지까지 DB 의 model_version
끝에 +tiles2 가 붙습니다.
.venv\Scripts\python.exe labeler.py reviewhttp://127.0.0.1:7860 에서 검수합니다(--host·--port 로 바꿀 수 있고, 기본값은 config.yaml 의 server). 이 UI 안에서 스캔·자동 라벨링·학습·내보내기를 모두 상단 데이터·모델 메뉴로 실행할 수 있으므로, 실제로는 review 하나만 띄워두고 작업해도 됩니다.
프로젝트 탭. projects.yaml 에 프로젝트가 둘 이상이면 화면 위쪽 탭으로 데이터셋을 오갑니다(/p/<이름>/). 지금은 가시광(/p/visible/) 하나라 탭이 보이지 않습니다 — 열화상·화재 감지는 2026-09-28 에 뺐습니다. 프로젝트 하나는 설정 파일 하나이고, 이미지·라벨·클래스·학습 가중치가 완전히 따로라 서로 섞이지 않습니다. 탭이 여럿이어도 GPU 는 하나뿐이라 학습과 자동 라벨링은 프로젝트를 통틀어 한 번에 하나만 돕니다.
설정을 직접 지정하면 그 프로젝트 하나만 / 에 띄웁니다(labeler.py --config <설정> review).
.venv\Scripts\python.exe labeler.py export --with-images승인된 라벨을 YOLO 포맷(labels/*.txt + classes.txt + data.yaml)으로 내보냅니다. 기본 폴더는
프로젝트마다 따로인 export/<프로젝트>(예: export/visible)이고, --out 으로 바꿀 수 있습니다.
다시 내보내면 지난번 내보내기를 비우고 새로 씁니다. 다른 파일이 들어 있는 폴더에는 쓰지 않고
멈춥니다(엉뚱한 폴더를 지우지 않도록). 저장소 안의 다른 폴더로 내보내면 그 폴더에 .gitignore(*)를 함께
두어 자동 커밋에 담기지 않게 합니다.
.venv\Scripts\python.exe labeler.py tidy검수를 마친 이미지를 data/visible/unlabeled 밖으로 옮겨 정리합니다. UI의 정리(상단 데이터 메뉴)는 이것에 더해
원본이 사라진 DB 항목을 걷어내는 prune 까지 함께 합니다(아래 '파일을 탐색기에서 직접 지웠다면').
run.bat 을 더블클릭하면 검수 UI가 바로 뜹니다.
검수 UI 상단의 모델 메뉴에 있습니다(이어서 할 학습이 있으면 메뉴 단추에 주황 점이 붙습니다).
| 버튼 | 하는 일 |
|---|---|
| 학습 | Windows 에서 학습. 승인 라벨로 data/visible/dataset 을 새로 만든다 |
| WSL 학습 | WSL(Linux)에서 학습. 같은 설정에서 약 4배 빠르다 (s 기준 epoch 258초 → 62초. 지금 설정 m·100 epoch 은 WSL 에서 약 2.8시간) |
| 이어서 학습 / WSL 이어하기 | 중단한 학습이 있을 때만 나타난다 |
어느 쪽이든 끝나면(중간에 멈춰도) 결과가 models/best.pt 로 올라가고, 직전 것은
models/best.prev.pt 로 남습니다. 되돌리려면 그 파일을 best.pt 로 복사하세요.
멈출 때마다 이 교체가 일어납니다. 한 학습을 두 번 이상 끊으면 best.pt·best.prev.pt
둘 다 그 학습의 중간 결과로 채워지고, 그 전의 운영 가중치는 models/ 에서 사라집니다
(실제로 겪었습니다). 지키고 싶은 가중치는 학습 전에 best_<이름>.pt 처럼 다른 이름으로
복사해 두세요. 다른 이름의 파일은 건드리지 않습니다.
멈추기는 작업 창의 [중단] 버튼으로 합니다. 다음 배치에서 멈추고 검증·저장까지 한 뒤
이어할 수 있는 스냅샷(resume.pt)을 남깁니다. 명령줄로 돌린 학습은 runs/STOP 파일을
(WSL 에서 직접 돌렸다면 WSL 안의 ~/new_env/yolo/runs_wsl/STOP 을) 만들면 같은 효과입니다.
Ctrl+C 는 즉시 종료입니다(진행 중이던 epoch 하나만 잃습니다).
.venv\Scripts\python.exe labeler.py resume --check # 이어할 지점만 확인
.venv\Scripts\python.exe labeler.py resume # 이어서 학습
.venv\Scripts\python.exe labeler.py resume --batch 16 # 배치를 줄여 이어서 학습이어하기는 중단할 때의 설정을 그대로 씁니다 — config.yaml 을 고쳐도 대부분 무시되고,
data/visible/dataset 이 남아 있으면 그 사이 검수한 라벨도 들어가지 않습니다(반영하려면 새로 학습).
예외가 --batch 입니다. Windows 는 VRAM 이 모자라면 에러 없이 몇 배 느려지는데(CLAUDE.md 함정 9),
epoch 시간이 갑자기 늘면 멈춘 뒤 배치를 줄여 이어가세요. 손실 누적(nbs=64)이 유효 배치를 맞춰 줍니다.
개인정보 블러 등으로 원본을 손봤다면 DB 와 파일이 맞는지 확인합니다. 파일이 없어졌거나,
열리지 않거나, 픽셀 크기가 달라진 것을 찾습니다. 크기만 달라졌다면 --fix-size 로 맞춥니다.
.venv\Scripts\python.exe labeler.py checkscan 은 새 파일을 등록할 때 이미 있는 이미지와 내용(바이트)이 같으면 등록하지 않고
리포트에 남깁니다. 로보플로에서 프로젝트를 다시 내보내면 같은 사진이 새 이름으로 들어오는데,
그대로 등록하면 학습 데이터가 겹치고 같은 사진이 train 과 test 에 동시에 들어갈 수 있어서입니다.
리포트는 박스가 다른 중복을 따로 셉니다 — 로보플로에서 라벨을 고쳐 다시 받은 것일 수 있으니,
그 경우 어느 쪽을 쓸지 직접 정하세요. 막지 않으려면 config.yaml 의 data.allow_duplicates: true.
넣기 전에 미리 보거나, 이미 등록된 것 가운데 중복이 있는지 볼 때:
.venv\Scripts\python.exe labeler.py dupcheck # DB 안의 중복 (읽기만)
.venv\Scripts\python.exe labeler.py dupcheck --folder <새 내보내기> # 넣으려는 폴더 대 DB파일 크기가 같은 것만 내용 해시(md5)를 비교하므로 빠릅니다(6천여 장에 1초 안팎). 파일명이나 화면 모양이 비슷한 것으로는 가르지 않습니다 — 같은 CCTV 의 인접 프레임이 전부 걸리기 때문입니다.
이미지를 데이터셋에서 완전히 빼려면 두 단계를 거칩니다. 실수로 지우는 것을 막기 위해서입니다.
[휴지통] 클릭 → 상태만 '휴지통'으로 바뀜 (파일은 그대로)
목록 필터 '휴지통'에서 확인 가능, [검수 대기로]로 되돌리기
↓
[휴지통 비우기] → 원본 파일 + DB 항목 + 라벨 영구 삭제
1단계는 되돌릴 수 있습니다. 파일을 건드리지 않고 상태만 바꾸며, 휴지통에 있는 동안에는
학습 데이터에서 빠집니다. 검수 대기로를 누르면(한 번 묻습니다) 검수 대기로 돌아옵니다(아래 '검수 대기로 되돌리기').
2단계는 되돌릴 수 없습니다. 디스크의 원본 이미지가 삭제됩니다. 확인창에서 장수를 보여주고 한 번 더 묻습니다.
명령줄로도 됩니다.
.venv\Scripts\python.exe labeler.py purge --yesDB에는 항목이 남습니다. prune 이 그런 항목을 걷어냅니다.
.venv\Scripts\python.exe labeler.py prune --dry-run # 대상만 확인
.venv\Scripts\python.exe labeler.py prune # 실제 삭제UI의 정리(상단 데이터 메뉴)도 같은 일을 함께 합니다. 전체의 절반 넘게 사라진 것으로 나오면
드라이브 연결 문제일 수 있으므로 지우지 않고 멈춥니다(--force 로 강행 가능).
자동 라벨링을 반복하면 승인이 끝난 이미지가 data/visible/unlabeled 에 계속 쌓여, 폴더만 봐서는
남은 작업량을 알 수 없게 됩니다. 상단 데이터 메뉴의 정리(또는 labeler.py tidy)가 이를 옮겨 줍니다.
data/visible/unlabeled/사진.jpg (승인됨)
↓
data/visible/labeled/images/사진.jpg
data/visible/labeled/labels/사진.txt ← 라벨도 함께 저장
- 대상은
data/visible/unlabeled안의승인이미지뿐입니다. 시드 데이터는 건드리지 않습니다. - 파일을 실제로 이동하고 DB 경로도 함께 갱신하므로, 라벨·검수 이력이 그대로 따라갑니다.
- 같은 이름이 이미 있으면 뒤에 해시(그래도 겹치면 번호)를 붙여 구분하고, 어떤 경우에도 있는 파일을 덮어쓰지 않습니다. (2026-09-27 전에는 같은 이름으로 받은 사진을 두 번 정리하면 앞 사진을 덮어쓸 수 있었습니다.)
함께 쓰는 라벨 .txt 는 정리한 시점의 사본입니다 — 정리한 뒤 검수 UI 에서 고친 박스나 클래스 합치기(11-02
van → car)는 따라가지 않습니다(2026-11-05 실측: 11-03 전 정리분 legacy/labeled 716장 중 98장의 라벨이 DB 와 다르고, 지금은 없는
van(8번) 박스가 44개 남아 있습니다). 그래서 이 폴더로 DB 를 되살리지 마세요 — 되살리기는 DB 백업(DB 옆
backups/, 다른 디스크 백업)으로 하고, 지금 라벨이 필요하면 내보내기(labeler.py export)를 씁니다.
지금은 COCO 로 받으므로 해당하지 않습니다. .txt 시드를 쓰면 각 줄을 행 단위로 읽습니다 —
5열은 박스, 6열은 박스 + 신뢰도, 7열 이상은 세그멘테이션 폴리곤(정점의 외접 박스로 바꿈).
Ultralytics 는 한 파일에 박스 행과 폴리곤 행이 섞이면 그 이미지를 통째로 버리고 경고 한 줄만 남깁니다(예전 YOLO 내보내기에서 5,943장 중 1,085장이 그랬습니다). 이 도구는 행마다 따로 처리하므로 그 이미지들도 학습에 들어갑니다. 외접 박스 변환은 되돌릴 수 없으니, 세그멘테이션을 할 계획이면 원본 폴리곤 라벨을 따로 보존하세요(이 도구는 원본을 읽기만 합니다).
화면은 세 칸입니다 — 왼쪽 목록, 가운데 이미지, 오른쪽 클래스·태그·박스 (2026-11-04 에 정리했습니다).
- 위쪽 줄:
라벨링 | 이미지 보기 | 영상 캡처로 보기를 바꿉니다(V는 라벨링 ↔ 이미지 보기, 아래 '이미지 보기'· '영상 캡처'). 폴더 스캔·정리·내보내기는데이터메뉴, 자동 라벨링·학습·이어서 학습·WSL 학습은모델메뉴에 있습니다(자주 누르지 않고, 학습·정리는 잘못 누르면 오래 걸려서 묶었습니다).변경 기록만 밖에 둡니다. - 왼쪽 목록 폭: 목록과 가운데 이미지의 경계를 끌면 목록이 넓어집니다(긴 이름·태그가 덜 잘립니다). 가운데에
420px·오른쪽 칸 270px 은 늘 남기고, 처음 폭(250px)보다 좁히지는 않습니다. 넓히면 상태·정렬 옆에
기본 폭이 생겨 누르면 처음 폭으로 돌아갑니다(경계를 두 번 눌러도 됩니다). 정한 폭은 그 브라우저에 남고, 이미지 보기에서는 늘 처음 폭입니다. - 왼쪽 필터: 상태·정렬 아래의
필터줄을 누르면 클래스·카메라·도메인·분할·태그·목록 묶음이 펼쳐집니다. 접혀 있어도 고른 필터가 한 줄로 보이고(도메인 드론 · 태그 없음),모두 해제로 한 번에 풉니다. 태그 필터는 아래 '태그 필터 — 여러 개 넣고 빼기'. - 태그 필터 — 여러 개 넣고 빼기: 누르면 맨 위에 찾기 칸이 있어 글자를 치면 맞는 태그만 남습니다. 줄을 누르거나
Enter면 포함(파란+), 줄 끝−나Shift+Enter면 제외(빨간−, 그 태그가 붙은 이미지를 뺍니다), 다시 누르면 풀립니다. 여러 태그를 함께 걸 수 있고, 포함이 둘 이상이면 칸 아래에서모두 붙은 것(기본 — 클래스 필터처럼 교집합)·하나라도를 고릅니다. 예:#Korea포함 +#K-PM제외 = Korea 인데 K-PM 이 아닌 것.태그 없음은 다른 태그와 함께 걸리지 않습니다. 고른 것은 칸 아래에 칩으로 보이고(×로 하나씩 풀기),태그 조건 풀기로 한꺼번에 풉니다. 바꿀 때마다 목록을 다시 받고 칸은 열린 채로 남습니다(↑·↓로 줄 옮기기, 칸 밖을 누르거나Esc로 닫기). 칸 맨 아래에 지금 목록의 장수가 나옵니다. - 오른쪽 칸:
클래스·태그·박스머리를 누르면 접고 폅니다. 접은 것은 다시 열어도 남습니다. 태그 칸 위의태그 찾기에 글자를 넣으면 그 글자가 든 태그만 보입니다.
| 조작 | 동작 |
|---|---|
B |
새 박스 모드 (기본) |
C |
SAM 클릭 모드 — 객체를 한 번 찍으면 박스, 끌면 그 범위를 다듬음. 이 모드에서는 모든 좌클릭이 SAM 으로 감 |
R |
선택한 박스를 SAM 으로 다듬기 |
M |
가릴 영역 모드 — 끌어서 학습 때만 회색으로 칠할 사각형을 그림. 이 모드에서는 영역만 고르고 옮기며, 우클릭·Del 로 지움 (아래 '가릴 영역') |
D |
화면 끌기 모드 |
Ctrl (누르고 있는 동안) |
두 모드를 임시로 반대로 |
| 빈 곳 드래그 | 새 박스 그리기 (새 박스 모드) |
| 아무 곳 드래그 | 화면 이동 (화면 끌기 모드) |
| 클릭 | 박스 선택 (두 모드 모두) |
| 박스 안 드래그 | 이동 (새 박스 모드) |
| 모서리/변 핸들 드래그 | 크기 조절 (새 박스 모드). 1px 아래로는 줄지 않습니다 |
| 우클릭 | 그 박스 삭제 |
| 가운데 버튼 드래그 | 화면 이동 (모드 무관) |
Space / Enter |
승인 후 다음 이미지 — 누르고 있어도 한 장만. 사진이 아직 안 떴거나 못 받은 이미지, 휴지통 이미지(검수 대기로 먼저)는 승인하지 않습니다 |
S |
검수 대기 이미지는 '건너뜀'으로 표시하고 다음으로 — 한 번 묻습니다(Enter = 건너뛰기, Esc = 그대로). 그 외 상태에서는 묻지 않고, 상태를 바꾸지 않은 채 다음으로만 이동. 누르고 있어도 한 장만 |
↑ ↓ |
이전 / 다음 이미지 (11-03 전에는 ← →). 지금 이미지가 목록에 없으면 첫 줄로 |
Del / Backspace |
선택한 박스 삭제 |
1~9 |
클래스 선택 (박스 선택 중이면 그 박스의 클래스 변경) |
G |
고른 박스의 클래스를 같은 장면·같은 자리 박스에 한꺼번에 적용 (아래 참고) |
H |
이 이미지의 변경 내역 펼치기/접기 |
P |
예측 비교 켜기/끄기 — 모델이 라벨과 다르게 본 곳을 점선으로 |
A |
예측 비교 목록의 맨 위 항목 받기 |
Q / W / E |
이 이미지의 도메인을 CCTV / 비CCTV / 드론 으로 정하고 다음 이미지로 (이미 그 값이어도 풀리지 않고 넘어가기만 한다 — 바꾸려면 다른 키). 왼쪽 도메인 필터(미분류 등)에서 써도 그 줄은 목록에 남는다 — 아래 '목록 묶음 보기' |
T |
오른쪽 태그 목록에서 고른 태그를 이 이미지에 붙이고 다음으로 (이미 붙어 있으면 넘어가기만 한다). 아래 '태그' |
V |
라벨링 ↔ 이미지 보기 (아래 '이미지 보기') |
Ctrl+Z |
되돌리기 |
Esc |
선택 해제 |
F |
화면에 맞추기 |
| 마우스 휠 | 확대/축소 (커서 기준). 가로 스크롤(틸트 휠)은 아무것도 하지 않습니다 |
문자 단축키(B C R M D S G H P A Q W E T V F)와 Ctrl+Z 는 한글 입력 상태에서도 동작합니다.
자동 라벨링·휴지통 비우기·정리가 끝나면 보던 이미지를 다시 읽습니다 — 자동 박스가 들어왔으면 그려지고, 지워졌으면 첫 장으로 갑니다(학습이 끝날 때는 그대로). 자동 라벨링이 도는 동안 승인하면 서버의 박스를 한 번 더 보고, 그새 바뀌었으면 승인하지 않고 다시 읽습니다.
비슷한 이미지끼리(같은 카메라, 같은 방식으로 자른 크롭 등) 이름을 붙여 묶습니다. 한 이미지에 여러 개를 붙일 수 있고, 라벨이 아니라서 학습·변경 이력에는 들어가지 않습니다.
- 만들기: 오른쪽
태그아래 칸에 이름을 넣고Enter(또는추가). 같은 이름은 만들 수 없고 40자까지입니다. - 붙이기: 태그 줄을 눌러 고른 뒤(왼쪽에 파란 줄) 이미지마다
T— 붙이고 다음으로 넘어갑니다. 줄 앞의 칸을 누르면 지금 이미지에 붙이거나 뗍니다(넘어가지 않습니다). - 이름 바꾸기·지우기: 줄 끝의
✎·×. 지우면 붙어 있던 이미지에서도 떨어집니다(이미지·라벨은 그대로). 왼쪽 목록은 그 줄의 꼬리표만 바뀌고 움직이지 않습니다. 지운 태그로 거르던(포함·제외) 중이면 그 태그를 조건에서 빼고 다시 받되, 지금 이미지 줄은 화면의 같은 자리에 둡니다. - 보기: 왼쪽
태그필터로 고른 태그가 붙은 것만(여럿이면 모두 또는 하나라도), 고른 태그를 뺀 것, 또는태그 없음만 봅니다(위 '태그 필터 — 여러 개 넣고 빼기').태그 없음에서T로 붙여도 그 줄은 목록에 남습니다(아래 '목록 묶음 보기'). 목록 줄에는 가장 좁은 태그(붙은 이미지가 가장 적은 것 — 나라보다 도시·장소)가 이름 위에, 위쪽 정보 줄 옆에는 전부가 좁은 것부터 나옵니다. 여러 장을 한눈에 보며 한꺼번에 붙이고 떼려면 아래 '이미지 보기' 를 쓰세요. - 보이는 이름: 목록 줄·정보 줄·이미지 보기·변경 기록의 이름은 로보플로 꼬리(
_jpg.rf.<해시>)·정리 꼬리(__<해시>)와 확장자를 뗀 것입니다 — 파일 이름은 그대로고, 마우스를 올리면 원래 이름이 나옵니다. 태그가 붙은 줄은 이름의 끝부분 (시각·프레임 번호)이 보이게 앞을 줄입니다.
위쪽 이미지 보기(또는 V)를 누르면 가운데가 썸네일 그리드가 됩니다. 왼쪽 필터를 라벨링과 함께 쓰고,
돌아가면(라벨링 또는 V) 보던 이미지가 그대로입니다. 라벨링에서 보던 이미지가 처음 골라진 채로 열립니다.
칸마다 id·도메인·분할(test·val)·박스 수·태그가 나오고, 오른쪽에 고른 이미지를 크게 보여 줍니다.
| 조작 | 동작 |
|---|---|
클릭 / Ctrl+클릭 / Shift+클릭 |
그것만 고르기 / 하나 더하기·빼기 / 범위 고르기 |
← → ↑ ↓ (Shift 와 함께면 범위) |
옮기며 고르기 |
Ctrl+A / Esc |
지금 필터의 전부 고르기(화면 밖까지) / 모두 풀기 |
Space |
지금 칸을 고르거나 빼기 |
Q / W / E |
고른 이미지를 모두 CCTV / 비CCTV / 드론 으로 |
T |
오른쪽에서 고른 태그를 고른 이미지 모두에 붙이기 |
더블클릭 / Enter |
그 이미지를 라벨링에서 열기 / 고른 것만 라벨링 목록으로(지정한 id) |
- 태그 칸: 고른 이미지 모두에 붙어 있으면 ✓, 일부만이면 − 로 보입니다. 칸을 누르면 고른 이미지 모두에 붙이고,
모두에 붙어 있었으면 모두에서 뗍니다. 숫자는
고른 것 중 붙은 장수/고른 장수 · 전체 장수이고, 고른 이미지에 붙은 태그가 위로 올라옵니다. 줄을 누르면T로 붙일 태그로 고릅니다(라벨링과 같은 태그). - 30장 넘게 고른 채로 도메인·태그를 바꾸면 한 번 묻습니다(
Ctrl+A뒤 실수로Q를 누르는 일을 막으려고). - 바꿔서 필터에 맞지 않게 된 칸도 다시 받기 전까지 그 자리에 남습니다 — 왼쪽
↻ 새로 받기로 걷어 냅니다. - 썸네일은 서버가 줄여
data/visible/thumbs/에 남겨 다시 씁니다(지워도 다시 만들어집니다).
위쪽 영상 캡처 를 누르면 영상에서 프레임을 뽑아 넣는 화면이 됩니다 — tools/extract_frames.py 와 같은 방식으로 뽑고,
넣기 전에 눈으로 고릅니다.
영상 불러오기…로 영상 파일을 고르거나 화면에 끌어다 놓습니다(mp4·mkv·avi 등 OpenCV(FFmpeg)가 읽는 형식). 서버에 사본을 올려(data/visible/capture/, 같은 PC 라 1.46GB 가 4초) 그 사본에서 뽑습니다 — 원본 영상 파일은 건드리지 않고, 넣거나 닫으면 사본은 지웁니다. 올리는 중에는취소할 수 있습니다.N 초마다 한 장과 구간(90·1:30·1:02:03, 비우면 처음부터·끝까지)을 정하고캡처 시작. 영상 안 시각 0·N·2N초… 에 가장 가까운 프레임을 원본 해상도로 뽑습니다(JPEG 품질 95). 뽑히는 대로 오른쪽에 칸이 늘어나고, 그동안 다른 보기로 가도 서버는 계속 뽑습니다. 1080p 11.5분 영상을 10초마다 뽑는 데 44초였습니다(이 PC 의 CPU). 학습이 도는 중에도 됩니다(GPU 를 쓰지 않습니다). 여러 영상을 걸면 한 번에 하나씩 차례로 뽑습니다.- 고르기 — 처음엔 모두
넣기입니다. 칸을 누르면넣기↔빼기(빼기는 흐리게·✕),Shift+클릭은 앞서 누른 칸부터 그 칸과 같은 쪽으로, 더블클릭·Enter는 원본 크기로 크게 보기(←→로 넘기고Space로 넣기/빼기,Esc닫기). 칸과 크게 보기의 그림은 끌리지 않고, 눌러서 끈 것은 클릭으로 치지 않습니다(11-12 — 전에는 그림을 끌면 영상을 끌어온 줄 알고 그 그림을 올리려 했습니다). 칸의 오른쪽 숫자는 앞 장과 밝기가 크게 바뀐 픽셀 비율이고, 앞의 장과 픽셀까지 같은 장(멈춘 화면 — 웹 CCTV 에서 흔합니다)은= #3처럼 표시되고 처음부터 빼기입니다. 위쪽바뀐 픽셀 [0.05]% 미만 빼기는 거의 같은 화면을 한꺼번에 뺍니다(0.05% 아래는 사실상 같은 화면 — 올리면 사람 한 명이 지나간 장면도 빠질 수 있습니다). 고른 것은 서버에 남아 새로 고쳐도 이어집니다. - 넣기 — 카메라(
cameras.yaml에 등록된 것 — 없으면 옆의+ 새로, 아래. 모르면 비워 둡니다 — 아래 '자동 카메라')와 촬영 시작 시각(영상 첫 프레임의 현지 시각)을 정하고고른 N장 넣기. 고른 프레임을data/visible/unlabeled/<카메라>/<날짜>/<카메라>_<YYYYMMDD_HHMMSS>_<밀리초>.jpg로 옮기고(시각 = 시작 + 영상 안 시각 — 넣을 곳·첫·끝 이름이 미리 보입니다) 곧바로 스캔해 미라벨로 등록합니다. 그다음은 보통 수집분과 같습니다(라벨링에서 미라벨로 보고모델 → 자동 라벨링). 안 고른 프레임과 영상 사본은 지웁니다.- 카메라·시작 시각은 영상 이름이
hw-02_20261109_110901.mp4·2026-11-10 14-30-00.mkv처럼 생겼으면 이름에서 채웁니다. 이름에 시각이 없으면 직접 적습니다 —수정 시각 − 길이로 채우기는 OBS 녹화처럼 녹화가 끝날 때 닫힌 파일에서만 맞습니다(복사·내려받은 파일이면 틀립니다). - 촬영 시각을 모르면 비워 두고 넣어도 됩니다(2026-11-10). 그때 이름은 시각 대신 영상 지문(영상 내용에서 센 8자)과 영상 안
시각이 됩니다 —
unlabeled/<카메라>/v<지문>/<카메라>_v<지문>_t00h01m05s500.jpg(1분 5.5초 지점). DB 의 촬영 시각은 '모름' 으로 들어가고, 이름에 시각이 없으니 나중에 이름으로 채울 수 없습니다. 같은 영상을 다시 넣으면 같은 이름이 되어 건너뜁니다. - 자동 카메라 — 카메라를 모르면 고르지 않고 넣어도 됩니다(2026-11-11). 카메라 칸을
자동 — 이 영상용 카메라그대로 두고 넣으면 이 영상용 카메라를 다음 번호vid-01,vid-02… 로cameras.yaml에 등록해 넣습니다(종류 cctv · 장소모름 — 영상 <파일 이름>· 해상도 · 메모에 영상 지문). 같은 영상을 다시 넣으면 그 카메라에 다시 넣습니다(키를 바꿨어도). 카메라를 아예 비우지 않는 것은 분할이 카메라째라서입니다 — 출처 없이 넣으면 같은 영상의 프레임이 train 과 test 로 갈릴 수 있습니다. 넣을 곳 미리 보기와 확인 창에 어느 카메라로 가는지 나오고, 장소·종류·키는 알게 되면고치기…로 바꿉니다. - 같은 이름의 파일이 이미 있으면 덮지 않고 건너뜁니다(같은 구간을 두 번 넣어도 그대로). 내용이 이미 있는 이미지와 바이트까지 같은 프레임은 스캔의 중복 검사가 막습니다 — 결과 칸에 각각 몇 장인지 나옵니다.
- 정리·휴지통 비우기가 도는 동안에는 넣지 않습니다(파일을 옮기는 중이라).
- 새 카메라 등록(
+ 새로) — 키·장소·종류·설치·분할·화면·해상도·권리·시간대를 적어data/visible/cameras.yaml끝에 더합니다(이 PC 에만 있는 파일 — git 에 넣지 않습니다).다음 번호단추(예:hw-10)를 누르면 그 계열의 다음 번호와 종류·설치를 채우고, 해상도는 지금 영상에서 채웁니다. 시간대는 그 카메라 시각의 UTC 와의 차이로, 국내 CCTV 는 기본+09:00그대로 두면 됩니다 —KST·JST·UTC·GMT·UTC+9처럼 적어도+09:00꼴로 바꿔 저장합니다(뜻이 여럿인CST·IST, 여름에 바뀌는EST·PST는-05:00처럼 그 영상에 맞는 차이로 적습니다). 한국이 아닌 카메라는 목록에UTC-05:00처럼 표시됩니다. 키는 폴더·파일 이름과 문서·커밋에 그대로 남으니 지명이 드러나지 않게 번호로 짓습니다 — 처음 보는 계열이면 한 번 묻고,test로 고정할 때도 묻습니다. 파일의 주석·다른 카메라는 그대로 두고 맨 끝에 글자로 덧붙이며(그 위에 등록 시각 주석 한 줄), 바꾸기 전 파일은data/visible/backups/cameras_<시각>.yaml로 남습니다. - 카메라 고치기(
고치기…) — 같은 칸이 고치기 모드로 열립니다.고칠 것에서 카메라를 고르면(지금 영상이 고른 카메라가 먼저) 지금 값이 채워지고, 바꾼 칸만cameras.yaml의 그 카메라 줄에서 고칩니다 — 다른 카메라·주석·crop같은 다른 속성은 그대로이고 줄 끝 주석도 남습니다(바꾸기 전 파일은 위와 같이backups/).- 키는 이미지가 있는 카메라도 바꿀 수 있습니다(2026-11-11) — 그 카메라의 폴더(
unlabeled·labeled/images·labeled/labels의<키>/)와 파일 이름 앞머리(<옛 키>_→<새 키>_), DB 의 카메라·경로를 함께 바꿉니다(라벨·검수 상태·분할·가릴 영역은 그대로). 지명이 드러난 키를 번호로 바꾸거나 자동 카메라(vid-NN)에 이름을 붙일 때 씁니다. 바꾸기 전 DB 는data/visible/backups/labeling_camrename_<시각>.db에 남고, 키를 다시 바꾸면 이름·경로가 처음과 같아집니다. 학습·자동 라벨링·정리 같은 작업이 도는 중에는 할 수 없고(파일을 옮기는 일이라), 바꾸는 동안에는 다른 작업이 시작되지 않습니다. 중간에 실패하면 되돌립니다. 새 키의 카메라 폴더에 이미 파일이 있으면 막습니다. 바꾼 뒤 라벨링의 카메라 필터도 새 키로 옮겨 갑니다. - 분할 고정은 그 카메라의 이미지가 이미 분할(train·val·test)에 들어가 있으면 못 바꿉니다(칸이 잠기고 이유가 나옵니다) —
바꾸면 한 카메라가 두 분할로 갈립니다. 가릴 영역이 있는 이미지가 있으면
test로 고정할 수 없습니다. - 시간대를 바꾸면 그 카메라 이미지의 촬영 시각을 새 시간대로 다시 셉니다(파일 이름의 시각 기준 — 고친 수가 알림에 나옵니다).
- 키는 이미지가 있는 카메라도 바꿀 수 있습니다(2026-11-11) — 그 카메라의 폴더(
- 카메라 지우기(
지우기…) — 등록된 카메라 목록에서 골라(모두 고르기로 한꺼번에)cameras.yaml에서 뺍니다. 그 카메라의 줄(속성·안쪽 주석·바로 위 등록 주석)만 빠지고 다른 카메라와 묶음 설명 주석은 그대로이며, 바꾸기 전 파일은 위와 같이backups/에 남습니다(되살리려면 그 줄을 다시 붙여 넣습니다). 그 카메라의 이미지가 DB(휴지통 포함)나unlabeled·labeled의 카메라 폴더에 남아 있으면 고를 수 없습니다 — 등록이 빠진 폴더는 스캔을 멈추고, DB 의 이미지는 장소·고정 분할을 잃기 때문입니다. 비어 있는 카메라 폴더는 지우지 않고 둡니다.
- 카메라·시작 시각은 영상 이름이
- 넣지 않을 영상은
영상 닫기(뽑은 프레임과 사본을 지웁니다). 불러온 영상은 왼쪽 아래불러온 영상에 남아, 서버를 다시 켜도 이어집니다(뽑던 것은 '멈춤').
현재 모드는 하단 바의 색 배지로 표시됩니다 — 파란 새 박스, 초록 화면 끌기, 보라 SAM 클릭, 회색 가릴 영역.
Ctrl 로 임시 반전 중일 때는 테두리가 점선이 되어 구분됩니다. 마우스 커서도 함께
바뀝니다(십자 / 손 모양).
수정은 0.6초 뒤 자동 저장되고, 다른 이미지로 넘어가기 전에도 저장을 강제합니다. 저장이 도는 동안 더 고친 것은 그 저장이 끝난 뒤 이어서 저장합니다. 저장에 실패하면 다음 이미지로 넘어가지 않고 알림을 띄웁니다(넘어가면 그 수정이 사라지므로).
사람을 포함해 라벨하기 애매한 물체 — 한 명씩 가를 수 없는 무리, 조각만 보이는 사람, 우리 클래스 물체인지부터 알 수 없는
것처럼 박스를 짐작해 그릴 수도 없고 그냥 두면 모델이 '그 물체 아님'으로 배우는 곳에 씁니다(2026-11-09, 판정은
docs/label-rules.md). 몇 px 짜리 조각이라도 애매하면 덮습니다. 차종만 애매한 차는 덮지 않고 판정 순서대로
정합니다. M(또는 아래 줄의 가릴 영역 버튼)으로 바꾸고 박스처럼 끌어 사각형을 그리면 학습 세트를 만들 때만
그 자리를 회색(114 — 학습 때 여백 색)으로 칠합니다. 원본 그림은 그대로이고, 화면에는 빗금 친 반투명 회색으로 보입니다.
- 영역들이 박스 넓이의 반 이상을 덮으면 그 박스는 학습 라벨에서 빠집니다(박스 목록에
학습 제외, 캔버스에서는 흐린 점선). DB 의 박스는 그대로라 지우지 않아도 됩니다. - 라벨된 박스와 조금 겹치는 것은 괜찮습니다 — 그 물체도 그만큼 칠해지지만 조금 가려진 물체와 다를 게 없습니다. 겹친
몫은 박스 목록에
⚠ 23% 가림처럼 나옵니다. 물체를 크게 덮어야 하면 그 물체가 큰 회색 조각이 붙은 채로 학습되니, 영역을 좁히거나 그림 가장자리면 잘라 냅니다(tools/crop_images.py). - 이 모드에서는 영역만 고르고 옮기고 크기를 바꿉니다(박스를 고치려면
B). 우클릭·Del로 지우고Ctrl+Z로 되돌립니다. 영역은 박스 목록 맨 아래에가릴 영역 1줄로 나오고, 줄을 누르면 이 모드로 바뀌며 골라집니다. - 한 장에 500개까지 둘 수 있습니다(2026-11-11 에 50 → 500) — 넓게 한 번에 덮지 않고 애매한 물체마다 따로 촘촘히
덮어도 됩니다. 넘치면
가릴 영역은 한 장에 500개까지입니다가 뜨고 그리지 않습니다. - test 이미지에는 둘 수 없습니다(분할이 test 이거나 test 로 고정한 카메라) — 평가는 실제 화면 그대로여야 해서입니다.
- 영역을 바꾼 저장도 변경 내역에
가릴 영역 1→2처럼 남고,이 저장 전으로를 누르면 영역까지 그때로 돌아갑니다. - 예측 비교(
P)는 가려진 자리를 견주지 않습니다. 내보내기는 이미지를 함께 내보낼 때만 칠합니다(라벨만 내보내면 DB 박스 그대로). - 같은 자리를 여러 장에 넣을 때는 아래 가릴 영역 템플릿을 쓰거나, 명령으로:
python tools/mask_images.py --ids 8017-8029 --rect x1,y1,x2,y2(지금 화면 픽셀, 보기만 →--apply).
고정 카메라처럼 같은 자리를 여러 장에 덮을 때 씁니다. 한 장에 그린 가릴 영역을 이름(예: 카메라A_1)을 붙여 남겨 두고
다른 이미지에 더합니다. 적용하면 그 이미지의 영역으로 복사되므로, 템플릿을 고치거나 지워도 이미 적용한 이미지는 그대로입니다.
- 만들기 — 라벨링 화면 오른쪽
가릴 영역 템플릿칸 아래에 이름을 넣고지금 영역 저장(Enter). 지금 이미지의 영역 전부가 들어갑니다. 같은 이름이 있으면 바꿀지 묻습니다 — 바꾸면 이미 적용한 이미지의 옛 영역은 그대로 남고빼기로도 찾지 못하니, 옛 영역을 걷어 내려면 바꾸기 전에 이미지 보기에서빼기를 하세요(또는 다른 이름으로 저장). 줄의✎로 이름을 바꾸고×로 지웁니다. 적용할 때는 서버의 지금 템플릿을 다시 받아 넣습니다(다른 창에서 바꾸거나 지운 것도 맞게). - 한 장에 적용 — 줄에 마우스를 올리면 덮을 자리가 노란 점선으로 보이고,
적용을 누르면 지금 이미지에 더합니다. 이미 같은 자리에 있는 영역은 다시 넣지 않고, 평소 편집처럼 자동 저장되며Ctrl+Z로 되돌립니다. 템플릿을 만든 그림과 가로·세로 비율이 다르면 한 번 묻습니다(영역이 늘어나거나 줄어든 자리에 덮이므로). - 여러 장에 적용 —
이미지 보기에서 이미지를 고르고(카메라 필터 →Ctrl+A등) 오른쪽가릴 영역 템플릿에서 템플릿을 골라더하기. test 이미지, 화면 비율이 템플릿과 다른 이미지, 영역이 한 장 한도(500개)를 넘게 되는 이미지는 건너뛰고 그 장수를 알려 줍니다.빼기는 템플릿과 좌표가 같은 영역만 뺍니다(손으로 옮긴 영역은 남습니다). 여러 장 적용은 일괄 수정 묶음으로 남아, 상단변경 기록→일괄 수정 묶음에서 묶음째 되돌릴 수 있습니다. 되돌리기는 영역만 그 전으로 돌리고 박스는 지금 것을 그대로 둡니다(적용한 뒤 박스를 검수한 이미지도 되돌아갑니다). 그 뒤 가릴 영역을 또 고친 이미지, 그리고 되돌리면 영역이 다시 생기는데 그새 test 가 된 이미지는 건너뜁니다. - test 이미지에는 적용할 수 없습니다(위와 같은 이유). 템플릿은 DB(
mask_templates표)에 있어 DB 백업에 함께 들어갑니다.
박스를 저장할 때마다 바뀌기 전과 뒤가 DB 에 남습니다. 잘못 지웠거나 고친 것을 나중에 발견해도 되돌릴 수 있습니다. 같은 박스·영역 목록은 DB 에 한 번만 두어(2026-11-11) 저장이 잦아도 DB 가 크게 늘지 않습니다 — 박스 100개 장에 영역을 하나씩 300개까지 그리는 저장이 전에는 11MB, 지금은 2MB 안팎입니다. 저장마다 한 줄씩 남는 것과 되돌릴 수 있는 시점은 전과 같습니다.
- 이 이미지의 변경 내역 — 오른쪽 아래
변경 내역을 펼칩니다(H). 줄마다 언제, 무엇이 (car −1 · pickup +1처럼) 바뀌었는지 나오고, 줄에 마우스를 올리면 그 저장 직전의 박스가 흰 점선으로 겹쳐 보입니다.이 저장 전으로를 누르면 그 상태로 되돌립니다. 되돌린 것도 내역에 남으므로 다시 되돌릴 수 있습니다. - 박스가 줄면 표시합니다 — 저장 뒤 위쪽 표시가
저장됨 · 박스 5→4처럼 주황으로 바뀝니다 (이 이미지를 연 때보다 박스가 적을 때). 일부러 지운 것이면 그냥 넘어가면 됩니다. - 변경 기록 (상단 버튼) — 여러 이미지에 걸친 최근 변경입니다. 기본은 박스가 줄어든
저장만 보여 주므로, 여러 장을 연달아 고친 뒤 사라진 박스가 없는지 한 번에 확인할 수
있습니다. 줄을 누르면 그 이미지가 열리고,
목록으로 열기는 표의 이미지들을 왼쪽 목록으로 엽니다.일괄 수정 묶음에서는 아래 '같은 자리 일괄 수정' 을 묶음째 되돌립니다. - DB 자동 백업 — 검수 서버를 켤 때 하루 한 번
data/<프로젝트>/backups/에 DB 사본을 남깁니다. 가장 최근 14개만 남기고 오래된 사본은 지웁니다(config.yaml의backup.keep, 0 이면 끔).
고정 CCTV 영상에는 같은 차가 수백 프레임 같은 자리에 서 있습니다. 한 장에서 박스를 고르고
클래스를 바꾼 뒤 G(또는 박스 목록 위 같은 자리)를 누르면:
- 같은 장면 묶음(train/val/test 를 나눌 때 쓰는 묶음)의 다른 이미지에서, 고른 박스와 같은 자리(겹침 IoU ≥ 0.85, 바꿀 수 있음)에 있는 박스를 찾습니다. 이미지마다 가장 많이 겹치는 박스 하나만 봅니다.
- 먼저 무엇이 바뀔지 보여 줍니다 — 몇 장인지, 지금 클래스별·분할별 개수. val·test 가
섞여 있으면 경고합니다.
목록으로 보기로 그 이미지들을 직접 훑어볼 수 있습니다. 적용을 누르면 한꺼번에 바꿉니다. 되돌리려면변경 기록→일괄 수정 묶음→되돌리기. 그 뒤에 또 고친 이미지는 건드리지 않고 건너뜁니다.
기본으로는 원래 클래스가 같은 박스만 바꿉니다(오토바이 위의 사람처럼 크게 겹치는 다른
객체를 끌어오지 않게). 다른 클래스로 적힌 것까지 맞추려면 다른 클래스 박스도 포함 을 켭니다.
아직 묶음이 정해지지 않은 새 이미지(다음 학습 때 정해짐)에서는 쓸 수 없습니다.
운영 모델(models/best.pt)을 지금 이미지에 돌려, 라벨과 다르게 본 곳을 점선으로 겹쳐
보여 줍니다. DB 에는 아무것도 쓰지 않습니다. 오른쪽 예측과 다른 곳 목록에서 하나씩
받아들이거나(A = 맨 위 항목) 무시합니다.
| 종류 | 뜻 | 받기 |
|---|---|---|
| 빠짐 (분홍) | 모델이 확신(0.5 이상)하는데 라벨이 없다 | 그 박스를 추가 |
| 클래스 (주황) | 라벨과 크게 겹치는데 클래스가 다르다 | 라벨의 클래스를 바꿈 |
| 박스 (하늘) | 같은 클래스인데 겹침이 0.3~0.5 뿐이다 | 라벨을 예측 박스로 맞춤 |
| 못 봄 (빨강) | 라벨이 있는데 모델이 거기서 아무것도 못 본다 | (직접 확인) |
- 모델이 모르는 클래스의 라벨은 비교하지 않습니다(예: 7클래스 visible-3 의 pickup). 11-08 부터 운영 모델은 8클래스를 모두 배운 yolo26m 이라 pickup 도 비교합니다.
- 학습·자동 라벨링이 GPU 를 쓰는 중이면 CPU 로 돕니다(한 장 0.1~0.3초). 켠 뒤 첫 이미지는 모델을 올리느라 10초 넘게 걸릴 수 있습니다.
tools/label_qa.py가 뽑은 이미지 id 를id 로 열기에 넣고P를 켜 두면, 어긋난 곳을 바로 보면서 고칠 수 있습니다.
왼쪽 목록 위 입력칸에 이미지 id 를 쉼표·공백으로 구분해 넣고 Enter 를 누르면 그 이미지들만
넣은 순서대로 목록이 됩니다(필터가 지정한 id 로 바뀜). ↑ ↓ 와 승인도 이 목록 안에서
움직입니다. 이어진 id 는 범위로 씁니다 — 5532, 5493-5497, 4435(–·~ 로 써도 되고, 거꾸로 쓰면
거꾸로 엽니다. 한 번에 10,000개까지). 주소 끝에 붙여도 같습니다.
http://127.0.0.1:7860/p/visible/#ids=106,254,264
http://127.0.0.1:7860/p/visible/#ids=5493-5497,5532
없는 id 는 알림으로 알려주고 건너뜁니다. 원래 목록으로 돌아가려면 상태 필터를 다시 고르면
됩니다. tools/label_qa.py 결과의 image_id 를 그대로 넣어 고칠 때 씁니다.
왼쪽 필터 칸을 펼치고 클래스: 전체 단추를 누르면 클래스 목록이 펼쳐집니다. 체크한 클래스가
들어 있는 이미지만 목록에 남습니다.
- 둘 이상 고르면 그 클래스가 모두 있는 이미지만 남습니다. 예를 들어
car와person을 고르면 둘이 함께 있는 장면만 나옵니다(둘 중 하나가 아니라). - 박스 없음 (빈 이미지) 을 고르면 박스가 하나도 없는 이미지만 나옵니다. 배경 샘플을 확인하거나, 자동 라벨링이 아무것도 못 잡은 이미지를 찾을 때 씁니다. 클래스 선택과는 함께 쓸 수 없습니다(동시에 성립할 수 없는 조건이라 자동으로 해제됩니다).
- 단추 옆 해제 로 필터를 끕니다. 상태 필터(승인됨·검수 대기…)·정렬과 함께 걸립니다.
- 목록 개수와 묶음(100개씩)도 필터에 맞춰 다시 셉니다. 'id 로 열기' 를 쓰는 동안에는 꺼집니다.
그 아래 도메인·분할 필터도 같은 식으로 함께 걸립니다(괄호 안은 휴지통을 뺀 장수).
분할 필터는 train·val·test·미배정 을 고르기만 하고 분할을 바꾸지 않습니다 — 분할을
바꾸는 것은 labeler.py split 뿐입니다. 목록에는 test·val 이미지에만 꼬리표가 붙고
(train 은 대부분이라 표시하지 않습니다), 위쪽 이미지 정보 줄에는 늘 분할이 나옵니다.
오른쪽 클래스 목록의 각 행에 숫자 두 개가 나옵니다.
[■] person 12 21,921 5
이름 이 이미지 전체 단축키
- 파란 배지 = 지금 보고 있는 이미지의 해당 클래스 박스 수 (없으면
·) - 흐린 숫자 = 데이터셋 전체 박스 수
- 행 아래 비율 막대 = 가장 많은 클래스를 100%로 본 상대 비율
클래스 불균형이 한눈에 보입니다. 행에 마우스를 올리면 상태별(승인 / 검수 대기 / 건너뜀 / 미라벨) 내역까지 나옵니다.
박스를 추가·삭제하거나 클래스를 바꾸면 두 숫자가 즉시 갱신됩니다.
이미지가 많으면 왼쪽 목록이 1 – 500, 501 – 1000 처럼 묶음으로 접혀서 나옵니다.
헤더를 클릭하면 펼쳐지고, 그때 펼친 묶음들을 서버에서 새로 받아옵니다(아래처럼 남아 있던 줄은 그때
빠지고, 화면 맨 위 줄은 제자리에 둡니다). 여러 묶음을 동시에 펼칠 수 있고, 펼쳐진 것들이 이어져서
↑/↓ 탐색 대상이 됩니다.
묶음 크기는 왼쪽 필터 칸의 목록 묶음 에서 100 / 200 / 500 / 1000 / 전체 중 고를 수 있습니다(전체는
묶음 하나에 다 펼칩니다).
각 줄 앞의 숫자는 지금 필터·정렬에서 몇 번째인지입니다(묶음을 나눠도 이어서 셉니다 —
이름순 이면 이름순으로 몇 번째). ↑/↓·승인으로 이미지가 바뀌면 지금 이미지 줄(파란색)이
보이도록 목록이 따라 굴러갑니다 — 가는 방향으로 다음 3줄이 늘 보입니다.
Q·W·E·T·태그 칸으로 도메인·태그를 바꿔 지금 필터(미분류·태그 없음 등)에 더는 맞지 않게 된 줄도
목록을 다시 받을 때까지 그 자리에 남습니다 — 바로 빼면 줄이 빠지면서 목록이 움직이기 때문입니다. 남은 것만 보려면
목록 위 ↻ 새로 받기 를 누르세요. 지금 이미지 줄은 화면의 같은 자리에 두고, 그 이미지가 빠졌으면 바로 다음 줄로
갑니다(알림에 몇 장에서 몇 장이 됐는지 나옵니다). 승인처럼 목록을 다시 받는 동작에서도 그런 줄이 한꺼번에 빠지지만, 넘어갈
이미지는 원래 바로 아래에 있던 줄이고 파란 줄의 화면 위치도 그대로입니다(최근 수정순 에서 승인해 그 이미지가 맨 위로
옮겨 가도 마찬가지).
건너뛰기는 검수 대기(pending) 이미지에만 상태를 바꿉니다. 이미 승인했거나 시드로
들어온 이미지를 볼 때는 버튼이 다음 으로 바뀌고, 상태를 건드리지 않고 이동만 합니다.
승인된 데이터를 훑어보다 실수로 건너뜀 으로 떨어뜨리는 일이 없습니다.
건너뜀 으로 표시할 때는 한 번 묻습니다(11-11 — 잘못 눌러도 보던 이미지가 건너뜀이 되어 넘어가지 않게).
Enter 면 건너뛰고 Esc 면 그대로 둡니다. 상태를 바꾸지 않는 다음 은 ↓ 와 같아서 묻지 않습니다.
승인됨·건너뜀·휴지통 이미지를 보고 있으면 아래 줄에 검수 대기로 단추가 보입니다(11-11 — 전에는 휴지통의 복원 만
있어서, 건너뜀·승인됨은 휴지통을 거쳐야 검수 대기로 돌아갔습니다). 누르면 한 번 묻고, 상태만 검수 대기로 바꾼 뒤
다음 이미지로 넘어갑니다. 박스·가릴 영역은 그대로이고, 승인됨이었으면 다시 승인할 때까지 학습에 들어가지 않습니다.
건너뜀 이미지는 되돌리지 않고 바로 Space 로 승인해도 됩니다.
검수 대기 이미지는 labeler.py label --all(검수 대기도 다시 추론)이 예측 박스로 바꿉니다 — 되돌려 둔 이미지를 그 명령 전에
다시 승인하지 않았다면 박스가 바뀌니, 그때는 변경 내역 에서 그 이미지를 되돌리세요. 웹 UI 의 자동 라벨링은 미라벨만
다룹니다.
목록 위 정렬 상자의 기본값은 최근 수정순입니다. 자동 라벨을 검수할 때는 불확실한 순 으로 바꾸세요 — 모델이 헷갈려 하는 이미지를 먼저 보게 되므로 같은 시간에 더 많은 오류를 잡을 수 있습니다. 왼쪽의 색 막대가 빨갈수록 불확실합니다.
드문 클래스 먼저 는 자동 라벨이 드문 클래스를 확신하는 이미지부터 보여 줍니다. 검수할
이미지가 많을 때, 데이터가 모자란 클래스를 다른 장면에서 먼저 채우려는 것입니다. '드문' 은 박스
수가 아니라 그 클래스가 나온 장면 묶음 수로 셉니다 — 한 카메라가 같은 차를 수백 프레임 찍은
것은 박스가 많아도 묶음으로는 하나입니다. 이미지 점수 = 박스마다
(신뢰도 × 희소도) 중 가장 큰 값이고, 이름 옆에 그 클래스가 붙습니다(마우스를 올리면 묶음 수).
자동 라벨링을 돌린 뒤 검수 대기 에서 쓰세요 — 미라벨 이미지는 박스가 없어 순서가 안 매겨집니다.
태그 순 은 태그가 같은 이미지끼리 모아 보여 줍니다. 이미지에 붙은 태그를 넓은 것부터(붙은 이미지가 많은 태그부터) 견주므로 같은 나라 안에 같은 도시·장소가 모이고, 태그가 같으면 이름순(연번·촬영 시각)입니다. 태그가 없는 이미지는 맨 뒤에 옵니다. 목록 줄에는 좁은 태그가 먼저 보이니, 줄의 태그 글자가 가나다순으로 늘어서지는 않습니다.
- 신뢰도 0.5 미만 박스는 캔버스에 점선으로 그려집니다.
- 사람이 손댄 박스(옮기기·크기 조절·클래스 변경)는 신뢰도가
수동으로 바뀌고, 이후 우선순위 계산에서 확실한 것으로 취급됩니다. 박스를 고르기만 하는 클릭은 바꾸지 않습니다.
검수가 어느 정도 쌓이면 다시 학습합니다. 승인된 데이터가 늘어난 만큼 다음 자동 라벨링의 정확도가 올라가고, 검수할 것이 줄어듭니다.
train → label → review(수십~수백 장) → train → label → review → ...
학습은 매번 사전학습 가중치(model.base, 지금 weights/yolo26m.pt)에서 새로 미세조정합니다. 직전 모델에 이어서 학습하면
자기가 만든 오류를 스스로 굳히는 문제(confirmation bias)가 생기기 때문입니다.
승인된 이미지는 세 갈래로 나뉩니다 (기본 70 / 15 / 15).
| 쓰임 | |
|---|---|
| train | 학습 |
| val | 학습 중 어느 시점의 가중치를 남길지(best.pt) 고르는 데 사용 |
| test | 학습에도 best.pt 고르기에도 쓰지 않음. 모델끼리 비교할 때만 |
세 가지 규칙이 있습니다.
- 배정은 DB 에 저장되어 바뀌지 않습니다. 데이터가 늘어도 기존 이미지는 그 자리에 남아, 학습 사이클 간 비교가 의미를 가집니다.
- 같은 장면은 통째로 한쪽에만 들어갑니다. 같은 영상의 연속 프레임이나 연사 사진이 train 과 val 로 갈리면, 모델이 외운 배경을 그대로 다시 보는 셈이라 점수가 부풀려집니다.
- 클래스 비중을 맞춥니다(계층화). 새로 승인된 이미지는 같은 장면 묶음이 이미 배정돼 있으면 그쪽을 따라가고, 새 장면이면 모자란 쪽으로 들어갑니다.
새로 승인된 이미지는 학습을 시작할 때 배정됩니다. split 명령도 현황을 보여주기 전에
미배정 이미지를 배정해 DB 에 씁니다.
.venv\Scripts\python.exe labeler.py split # 미배정분을 배정하고 현황 표시
.venv\Scripts\python.exe labeler.py split --leak # val·test 가 train 과 얼마나 비슷한지
.venv\Scripts\python.exe labeler.py split --regroup # 같은 장면 묶음을 다시 계산 (이미 배정된 이미지는 그대로)test 점수는 tools/test_baseline.py 로 잽니다. DB 를 읽기만 하고, 지금 라벨로 test 를 따로
만들어 잽니다. --domains 를 주면 아래 도메인을 한 번에 재고(하나만은 --domain <이름>, 0장인 도메인은
건너뜁니다), 결과를 runs/test_baseline/history.jsonl 에 클래스별로 쌓은 뒤 같은 도메인의 직전 기록과
나란히 보여 줍니다(test 장수·박스 수가 달라졌으면 라벨이 바뀐 몫이 섞였다고 알립니다). 가중치의 클래스가 DB 클래스
목록의 앞부분과 번호까지 같지 않으면 재지 않고 멈춥니다(2026-11-05 — 클래스를 번호로 견주기 때문입니다).
| 도메인 (이름표) | 고르는 이미지 (기본은 test 분할) |
|---|---|
all 전체 |
Objects365(source='o365')를 뺀 전부 — 새 수집분(collected)도 담습니다 |
cctv CCTV(손 분류) |
도메인이 CCTV 인 것 |
drone 드론(손 분류) |
도메인이 드론인 것 |
noncctv 비CCTV(손 분류) |
도메인이 비CCTV 인 것 |
nodrone 드론 제외(손 분류) |
드론도 Objects365 도 아닌 것 (도메인이 비어 있는 것도 들어갑니다) |
undomain 도메인 미분류 |
도메인이 비어 있고 카메라 등록으로도 짐작하지 못한 것 |
legacy 출처 없음(legacy) |
source 가 seed·unlabeled — 출처 없이 들어온 이미지(11-03 전에 모인 것 등) |
collected 새 수집분 |
source='collected' — 카메라 폴더(unlabeled/<카메라키>/)로 들어온 이미지 |
o365 Objects365 |
source='o365' 만 |
all_o365 전체+O365 |
전부 |
cam:<키>·kind:<형태>·mount:<설치> |
그 분할에 카메라가 붙은 이미지가 있을 때만 — 카메라별, cameras.yaml 의 형태·설치별 |
CCTV·드론·비CCTV 는 사람이 검수 UI 에서 정한 images.domain(Q·W·E)으로 가릅니다(2026-11-04 부터). 비어 있으면
카메라 등록 파일(cameras.yaml)로 짐작합니다 — 설치가 drone 이면 드론, 형태가 cctv 면 CCTV, phone 이면
비CCTV, 그 밖은 미분류. 그 전에는 경로·출처로 갈랐습니다(드론 = 경로에 stabilized_, 비CCTV = source='unlabeled',
CCTV = 그 밖의 seed). 뜻이 바뀌었으므로 cctv·drone·noncctv·nodrone 은 이름표에 '(손 분류)' 가 붙고, 이 넷과
undomain 의 기록에는 domain_rule: images.domain 이 남습니다 — 직전 기록과 견줄 때도 --history 표에서도 옛
규칙으로 잰 기록과 섞이지 않습니다.
all·nodrone 이 Objects365 를 빼는 것은 2026-10-03 부터입니다. all 은 이름표가 그대로라 --history 에서 옛
기록과 이어지지만 새 수집분도 담으므로, 09-27 기준선과 같은 범위로 견주려면 legacy 를 봅니다. 2026-11-10 에 이미지를
전부 비우고 새로 모으기 시작했으므로(옛 데이터는 E:\local_auto_labeling_archive\20261110\), 그 뒤의 기록은 test 가 다른
이미지라 그 전 기록과 견주지 않습니다 — --history 에서는 시각으로 가릅니다.
.venv\Scripts\python.exe tools/test_baseline.py --domains --note "메모"
.venv\Scripts\python.exe tools/test_baseline.py --weights models/best_새것.pt --domains
.venv\Scripts\python.exe tools/test_baseline.py --history # 쌓인 기록을 표로data/visible/dataset 으로 직접 재도 되지만, 그 폴더는 마지막 학습 때 만든 것이라 그 뒤 승인된
이미지는 test 에 없습니다.
전체를 다시 나누려면 labeler.py split --reset --yes 를 씁니다. 이전에 학습한 모델들은
새 val/test 이미지를 학습에 썼을 수 있으므로, 재배정 후에는 그 모델들의 점수를 새 기준으로
비교할 수 없습니다. 지금의 분할(2026-09-16) 이전에 학습한 모델(visible-2 등)이 그렇습니다
— test 에서 0.88 이 나오지만 부풀려진 값입니다.
알아둘 비용. val·test 로 뗀 장면(30%)은 학습에 들어가지 않습니다. 분할 뒤 처음 학습한 모델은 두 모델 모두 처음 보는 장면에서 그 전 모델보다 mAP50-95 가 약 0.05 낮았습니다. 원인은 확인하지 않았습니다. 자세한 비교는 docs/experiments.md.
자주 건드릴 항목만:
| 키 | 설명 |
|---|---|
model.base |
weights/yolo26m.pt(지금, 11-08 부터) / yolo26s.pt·yolo26n.pt 등. 바꿔도 dfl 은 1.5 유지 (아래 참고) |
train.batch |
10 (yolo26m·640 — 8GB 에서 들어가는 최대치). s 는 20 이었고, 넘치면 에러 없이 몇 배 느려집니다 |
train.epochs |
100, 마지막 10 epoch 은 mosaic 끔(close_mosaic) — 운영 m 과 같은 설정(11-08, 그 전 s 는 300·30). patience 120 이라 조기 종료는 걸리지 않습니다 |
train.val_ratio / test_ratio |
0.15 / 0.15. 바꿔도 이미 배정된 이미지는 움직이지 않습니다 |
wsl.* |
WSL 학습 버튼의 WSL 쪽 경로·워커·캐시. 학습 인자는 위 train 값을 그대로 씁니다 |
predict.imgsz |
자동 라벨링 해상도. 가중치와 짝이 맞아야 합니다 (s 는 640, n 은 960 이 최선이었음 — m 은 640 으로만 학습·평가) |
train.overrides |
Ultralytics train() 인자를 그대로 전달 (손실 가중치·증강 등). |
predict.conf |
이 신뢰도 미만 예측은 버립니다. 낮추면 검출은 늘고 오검출도 늘어납니다. |
predict.tiles |
타일 추론(자동 라벨링 (타일)·label --tiles 에서만) — 조각 수 grid 2, 겹침 0.2, 이 긴 변(min_side 800px)을 넘는 이미지만, 조각↔통째 중복 지우기 cross_iou 0.3. 11-03 측정에서 고른 값입니다 |
review.low_band / high_band |
검수 우선순위 계산 구간. |
이름과 달리 DFL 전용 옵션이 아니라 박스 거리 손실 게인입니다 — DFL 을 쓰지 않는 YOLO26 에서도
ltrb 거리의 L1 손실에 곱해집니다. 0 으로 두면 에러 없이 학습되지만 박스 좌표 정밀도가 떨어집니다
(합성 데이터 A/B: mAP50 은 같고 mAP50-95 가 0.953 → 0.902). train 은 dfl 이 0 이면 경고를
냅니다. 자세한 근거는 CLAUDE.md 함정 1.
README.md 사용자 문서 (이 파일)
CLAUDE.md 작업자 인수인계 — 실제로 겪은 함정, 라벨 판정 기준, 결정 기록
LICENSE GNU AGPL-3.0 (아래 '라이선스')
requirements.txt Windows .venv 패키지 (torch 는 cu130 인덱스에서 먼저 — '새 PC 에서 시작하기')
projects.yaml 검수 UI 에 띄울 프로젝트 목록 (지금은 가시광 하나)
config.yaml 설정 (가시광)
labeler.py 진입점
run.bat 검수 UI 실행
.claude/ Claude Code 프로젝트 설정
settings.json 턴이 끝날 때 자동 커밋 훅을 부르는 Stop 훅
hooks/autocommit.sh 그 훅 — 바뀐 파일을 git add -A 로 커밋한다 (push 는 하지 않는다)
launch.json 데스크톱 앱 미리보기용 검수 서버 (포트 7861)
data/
visible/ 가시광 프로젝트
seed/seed1, seed2… 손라벨 시드 (Roboflow COCO 내보내기, 회차별)
unlabeled/ 자동 라벨링 대상 (입력)
labeled/ '정리'로 옮겨진 검수 완료 이미지 + 라벨
classes.txt 클래스 목록 (검수 UI 에서 클래스를 추가하면 여기 적힌다)
dataset/ 학습용으로 자동 빌드 (건드리지 않아도 됨)
labeling.db 라벨·검수 상태 (SQLite) — 작업물 그 자체. 박스 변경 이력도 여기 있다
backups/ 검수 서버를 켤 때 하루 한 번 뜨는 DB 사본 (최근 14개)
thumbs/ 이미지 보기의 썸네일 캐시 (지워도 다시 만들어진다 — 백업할 필요 없음)
capture/ 영상 캡처 탭의 임시 세션 (올린 영상 사본·뽑은 프레임 — 넣거나 닫으면 지운다, 백업할 필요 없음)
export/<프로젝트>/ '내보내기' 결과 (기본 폴더)
models/ 운영 가중치 best.pt (+ 교체 직전 것 best.prev.pt, 따로 보관한 best_*.pt)
weights/ 사전학습 가중치 (yolo26n/s/m/l.pt, sam2.1_t.pt)
runs/ Windows 학습 로그·그래프·체크포인트
(WSL 학습은 WSL 안의 ~/new_env/yolo/runs_wsl)
wsl/ WSL 학습 스크립트 (웹 UI 'WSL 학습' 버튼이 부른다)
docs/experiments.md 실험·비교 기록 (모델 선택 근거)
docs/label-rules.md 라벨 판정 기준 전문 (차종별 실물 예시·점검 기록)
src/autolabel/ 소스
tools/ 보조 도구 — 저장소 루트에서 `.venv\Scripts\python.exe tools\<이름>.py`
import_o365.py Objects365 의 Van·Pickup Truck 사진을 검수 대기로 넣는다 (기본은 보기만, --apply 로 넣음.
Objects365 를 푼 폴더는 --root, 기본 E:\datasets\objects365 — 11-02 부터 원본은
E:\datasets\objects365.tar 로 묶여 있어 먼저 풀어야 한다)
import_virat.py VIRAT 영상에서 장면마다 프레임을 뽑아 검수 대기로 넣는다 — 운영 모델 예측 + 모델이 못 찾은
VIRAT 박스 (기본은 보기만, --apply 로 넣음, --per-scene 을 올리면 모자란 만큼만 더,
--relabel 은 이미 넣은 검수 대기를 지금 모델로 다시(--model-only 면 VIRAT 박스 없이),
cameras.yaml 에 crop 을 적은 장면은
그 영역만 — 이미 넣은 것은 --recrop 뒤 검수 화면을 새로 고칠 것. docs/provenance.md)
merge_class.py 맨 끝 클래스를 다른 클래스에 합친다 (기본은 보기만, --apply 로 DB 를 떠 두고 합침)
finetune.py 해상도를 올려 짧게 미세조정
probe_scale.py 모델 크기 비교 실험
scheduled_stop.py 정해진 시각에 학습 정상 중단
test_baseline.py 지금 라벨로 test 점수를 다시 잰다 (DB 읽기 전용)
label_qa.py 라벨과 모델 예측이 어긋나는 곳을 뽑는다 (DB 읽기 전용)
label_qa_claude.py 위 후보를 Claude API 로 거르는 파일럿 (API 키와 anthropic 패키지 필요)
test_tiles.py 타일로 잘라 추론한 것과 통째 추론을 test 로 비교한다 (DB 읽기 전용)
extract_frames.py 영상에서 프레임을 뽑아 unlabeled/<카메라>/ 로 (docs/provenance.md — 눈으로 골라 넣으려면
검수 UI 의 '영상 캡처' 탭. 뽑는 방식·이름은 같다)
legacy_groups.py legacy 이미지의 장면 종류·카메라 후보·크롭 추정 초안과 대조 시트 (DB 읽기 전용)
domain_review.py 도메인(CCTV·비CCTV·드론)·태그 점검 — 의심 이미지 id 목록·대조 시트,
태그의 포함 관계·오타 후보·같은 카메라 누수 (DB 읽기 전용, data/visible/domain_review/)
— 다 본 뒤 --mark-seen 으로 적어 두면 다음부터 새로 걸린 것만 낸다 (--all 이면 전부)
— '일부가 빠진 태그' 표는 태그별로 다 본 뒤 --mark-tag <태그> (--mark-seen 은 이 표를 적지 않는다)
import_roadplus.py ROAD PLUS CCTV 를 OBS 로 녹화한 파일에서 전체 화면 구간만 골라 프레임으로
(--save-clips 면 구간마다 720p 영상도. docs/provenance.md)
ovd_propose.py OWLv2 로 출동 차량·라바콘 후보를 JSONL 로 (다른 가상 환경, DB 를 열지 않는다)
import_proposals.py 그 후보로 responder·cone 박스를 만들어 검수 대기로 (기본은 보기만, 묶음째 되돌릴 수 있다)
crop_images.py DB 이미지를 사각형 영역으로 잘라 바꾼다 — 박스를 옮기고 상태는 그대로
(기본은 보기만, --apply 로 DB 를 떠 두고 자름 — 뒤에 검수 화면을 새로 고칠 것)
mask_images.py 가릴 영역(검수 화면 M — 애매한 물체를 학습 때만 회색으로)을 명령으로 넣는다 — 같은 자리를 여러 장에
(원본·박스·상태는 그대로, test 이미지는 멈춤, 기본은 보기만, --preview 로 대조 그림, --apply)
data/·models/·weights/·export/·.venv/ 는 git 에 없습니다(데이터·가중치는 백업으로 지킵니다).
runs/ 는 텍스트 기록(results.csv·args.yaml·history.jsonl·스크립트)만 git 에 있습니다.
data/visible/dataset 은 학습할 때마다 새로 만들어집니다. 이미지는 복사합니다 — 하드링크로
걸면 Ultralytics 가 깨진 JPEG 을 고쳐 저장할 때 원본까지 덮어써져서 끄고 있습니다.
그래서 데이터만큼(지금 약 4GB) 디스크를 더 쓰고, Windows 학습은 캐시 .npy 를 약 14GB
더 얹습니다.
- 시드 데이터가 품질을 좌우합니다. 클래스당 최소 수십 장, 가능하면 100장 이상을 권장합니다. 시드가 너무 적으면 첫 자동 라벨링 결과가 검수 비용을 줄여주지 못합니다.
- 자동 승인은 넣지 않았습니다. 신뢰도가 높아도 사람이 눈으로 보고 승인해야 학습 데이터로 들어갑니다. 자동 라벨을 검수 없이 학습에 넣으면 오류가 누적됩니다.
- 라벨 .txt가 없는 시드 이미지는 "객체가 없는 배경 샘플"로 학습에 포함됩니다. 라벨을 빠뜨린
것이라면
scan출력의 경고를 확인하세요. - 학습(WSL 학습 포함)과 자동 라벨링은 동시에 실행되지 않습니다. 작업은 한 번에 하나씩 돕니다. 8GB VRAM 을 나눠 쓰면 Windows 에서는 에러 없이 몇 배 느려지기 때문입니다.
- 라벨링 화면(가운데 캔버스)만 검게 나오고 이미지·박스가 안 보이면 브라우저를 완전히 껐다 켜 보세요.
2026-11-09 Firefox 159 에서 겪었는데 다시 켜니 나왔습니다. 같은 Firefox 를 새 프로필로 띄워 재현해 보니 코드·설정·확장
문제는 아니었습니다. 그 브라우저 세션의 GPU 합성 상태로 보입니다(이 노트북은 GPU 가 둘이고 모니터가 둘).
다시 켜도 검으면
about:support의 '그래픽' 항목과 F12 콘솔을 볼 것.
이 저장소의 코드는 GNU AGPL-3.0 으로 공개합니다. 학습·자동 라벨링·SAM 을 Ultralytics 로 하는데, Ultralytics 가 AGPL-3.0 이기 때문입니다(나머지 의존성은 MIT·BSD·Apache 계열). 이 코드를 고쳐 배포하거나 고친 것을 네트워크 서비스로 제공하면, 그 소스도 AGPL-3.0 으로 공개해야 합니다.
이미지·라벨 DB·학습한 가중치는 저장소에 들어 있지 않습니다. 쓰는 데이터셋(Objects365 등)과 사전학습 가중치에는 각자의 이용 조건이 따로 있습니다.