hello, robot

색 기반 검출은 시작하기 쉬워요. HSV 범위 하나만 정하면 파란 상자를 찾을 수 있으니까요. 대신 조명이 바뀌거나 비슷한 색이 배경에 있으면 바로 흔들리고, 무엇보다 “이 색”이 아니라 “이 물체”를 찾고 싶을 때 확장이 막혀요.

검출기를 YOLO로 바꾸면서 두 가지 접근을 차례로 시험했어요.

대상3 cm 큐브 3색 · 화면에서 15~40 px 1차YOLO-World (open-vocabulary zero-shot) 2차YOLOv8n 파인튜닝 · 자동 라벨 진리값시뮬레이터가 아는 물체 위치·색
zero-shot 신뢰도
0.08~0.15
사실상 노이즈
파인튜닝 신뢰도
0.79~0.94
같은 장면 · 주행 중
mAP50
0.98
정밀도 0.99
데이터 생성
5분
150장 · 사람 라벨 0장
학습
3분
GPU · 60에폭

zero-shot은 합성 장면에서 약했어요

먼저 시도한 건 open-vocabulary 계열인 YOLO-World예요. 학습 없이 텍스트로 클래스를 지정할 수 있어서, blue box 같은 문장만 주면 바로 검출이 되는 모델이에요.

m = YOLO('yolov8s-worldv2.pt')
m.set_classes(['blue box', 'red box', 'green box'])
r = m.predict(img, conf=0.05)[0]
같은 장면, 두 모델
zero-shot은 큐브를 놓치고 로봇의 노란 팔을 blue box로 잡았어요.
0.00.20.40.60.81.0검출 신뢰도YOLO-World (zero-shot)같은 장면 · 노란 팔을 blue box로0.08 ~ 0.15YOLOv8n 파인튜닝자동 라벨 150장 · 60에폭0.79 ~ 0.94검증 지표mAP50 0.98 · 정밀도 0.99 · 학습 125장 / 검증 25장3 cm 큐브화면에서 15~40픽셀 — 작은 객체 검출은 원래 어려운 축이에요

이유는 학습 분포예요. 이런 모델은 웹 사진으로 학습해서 실사 질감과 맥락에 맞춰져 있는데, 게임 엔진 렌더링에 가까운 합성 이미지는 텍스처도 그림자도 달라요. 작은 객체 검출이 원래 어려운 문제라는 점까지 겹치면 신뢰할 수 없는 출력이 나와요.

zero-shot은 “무엇이든 찾아준다”기보다 **“학습 분포 안에서 이름표를 유연하게 붙여준다”**에 가까워요.

시뮬레이터는 정답을 알고 있어요

여기서 시뮬레이션 환경의 이점이 드러나요. 실제 세계에서 학습 데이터를 만들려면 사람이 이미지마다 상자를 그려야 하는데, 시뮬레이터에서는 물체의 위치와 색을 시스템이 이미 알고 있어요.

라벨을 만드는 루프
큐브가 단색이라 HSV 마스크가 사실상 진리값 역할을 했어요.
큐브 3개 무작위 배치set_pose · 로봇도 회전화면 캡처같은 프레임HSV 마스크 → 경계 상자연결 성분YOLO 라벨로 저장cls cx cy w h150장 만드는 데 5분 · 사람이 상자를 그리는 단계가 없어요시뮬레이터는 물체의 위치와 색을 이미 알고 있어요. 그게 진리값이 돼요.주의 — 차체 상판이 빨간색이라 red_box 라벨에 로봇 자신이 섞여요. 화면 하단에 닿고 폭이 큰 성분은 제외해요.
for name in ('pick_blue', 'pick_red', 'pick_green'):
    set_pose(name, random.uniform(0.40, 1.35), random.uniform(-0.55, 0.55), 0.015)
img = capture()
for cls in (0, 1, 2):
    for x, y, w, h in hsv_bboxes(img, cls):
        lines.append(f'{cls} {(x+w/2)/W:.6f} {(y+h/2)/H:.6f} {w/W:.6f} {h/H:.6f}')

150장을 만드는 데 5분, YOLOv8n 파인튜닝에 GPU로 3분이 걸렸어요. 학습은 125장으로 하고 25장을 검증에 남겨 60에폭을 돌렸어요.

                   all         25         58      0.991      0.919      0.981      0.799

무엇을 보고 판단했는지 로그로 남기기

검출기를 바꾸면서 같이 넣은 게 근거 로그예요. 이전에 색 필터를 쓸 때, 파란색 지정과 빨간색 지정이 서로 다른 실행인데도 완전히 같은 검출 좌표를 내는 일이 있었어요. 두 색 범위는 겹치지 않으니 원래 불가능한 결과였고, 실제로 그 검출은 색 필터가 만든 게 아니었어요.

선택된 영역의 실제 픽셀 값을 한 줄 찍어두면 이런 착각이 바로 드러나요.

검출 근거: HSV평균=(113,163,206) 면적=223px     ← 파랑(H 113)
검출 근거: HSV평균=(0,163,206) 면적=230px       ← 빨강(H 0)
검출 근거(YOLO): green_box conf=0.90 d=0.73

검출기가 블랙박스일수록 “무엇을 보고 그 판단을 했는지”를 로그에 남겨야 결과를 신뢰할 수 있어요.

분포 밖에서는 여전히 약해요

파인튜닝 모델이 만능이 되는 건 아니에요. 이 모델은 원거리에서 작게 보이는 큐브로만 학습했기 때문에, 손목 카메라가 물체를 가까이서 크게 담는 장면은 학습 분포 밖이에요.

단계카메라물체 크기쓰는 검출기
접근전방 카메라15~40 pxYOLO 파인튜닝
근접 정렬손목 카메라크게색 기반 (그대로 둠)

시뮬레이션 데이터로 학습한 모델을 실물에 옮길 때 성능이 떨어지는 문제를 도메인 갭(domain gap)이라고 부르는데, 여기서 확인한 건 그 갭이 실물과 시뮬 사이에만 있는 게 아니라 같은 시뮬 안에서도 카메라와 거리 조건이 바뀌면 생긴다는 점이에요.

자동 라벨링의 진짜 이점은 이 갭이 보일 때마다 그 조건으로 데이터를 다시 뽑아 몇 분 만에 재학습할 수 있다는 데 있어요. 새 물체를 추가하는 비용도 같은 이유로 낮아요. 색이 단색이 아니어서 마스크로 진리값을 만들 수 없는 물체라면, 시뮬레이터가 아는 물체 자세와 카메라 파라미터로 경계 상자를 직접 투영하는 방법으로 바꿔야 해요.

설치 단계에서 만난 함정

패키지 설치도 그냥 지나가지 않았어요. ultralytics를 설치하면 의존성으로 NumPy 2.x가 따라 들어오는데, ROS의 cv_bridgeNumPy 1.x ABI로 빌드돼 있어서 이후 모든 이미지 콜백이 깨져요.

A module that was compiled using NumPy 1.x cannot be run in NumPy 2.5.1

numpy<2로 되돌리면 해결되지만, PyTorch를 다시 설치하면 NumPy가 또 올라오기 때문에 순서에 주의해야 해요. 딥러닝 패키지는 최신 NumPy를 원하고 ROS 배포판 바이너리는 빌드 시점 버전에 묶여 있어서, 두 생태계를 한 인터프리터에 얹는 순간 이 충돌은 예정된 것에 가까워요.

import numpy, torch
from cv_bridge import CvBridge     # 설치 후 매번 확인하는 한 줄