객체 탐지 개요
객체 탐지는 이미지 내에서 여러 객체의 위치(바운딩 박스)와 클래스를 동시에 식별하는 작업입니다. 분류(무엇인가)와 위치 파악(어디 있는가)을 결합한 복합 작업입니다.
주요 구성 요소
- 백본 - 특징 추출기 (ResNet, VGG, EfficientNet)
- 넥 - 특징 집계 (FPN, PANet, BiFPN)
- 헤드 - 탐지 예측 (바운딩 박스, 클래스, 신뢰도)
- 후처리 - NMS로 중복 박스 제거
탐지 방식 분류
- 2단계 탐지기 - 먼저 영역 제안, 그 다음 분류 (Faster R-CNN)
- 1단계 탐지기 - 한 번에 박스와 클래스 예측 (YOLO, SSD)
YOLO (You Only Look Once)
YOLO는 객체 탐지를 회귀 문제로 처리하여 실시간 성능을 달성한 혁신적인 모델입니다.
YOLO 발전 과정
| 버전 | 연도 | 주요 혁신 | FPS | mAP |
|---|---|---|---|---|
| YOLOv1 | 2016 | 단일 단계 탐지 | 45 | 63.4 |
| YOLOv3 | 2018 | 다중 스케일 예측 | 30 | 57.9 |
| YOLOv5 | 2020 | 자동 앵커 학습 | 140 | 67.3 |
| YOLOv7 | 2022 | 학습 가능한 bag-of-freebies | 160 | 69.7 |
| YOLOv8 | 2023 | 앵커 프리, 정확도 향상 | 280 | 72.4 |
from ultralytics import YOLO
# YOLOv8 모델 로드
model = YOLO('yolov8n.pt') # n(nano), s, m, l, x 변형
# 이미지에서 객체 탐지
results = model('street.jpg')
# 결과 처리
for result in results:
boxes = result.boxes
for box in boxes:
# 바운딩 박스 좌표
x1, y1, x2, y2 = box.xyxy[0]
# 신뢰도 점수
conf = box.conf[0]
# 클래스 ID
cls = box.cls[0]
print(f"클래스: {cls}, 신뢰도: {conf:.2f}, 박스: ({x1}, {y1}, {x2}, {y2})")
# 배치 추론
results = model(['img1.jpg', 'img2.jpg', 'img3.jpg'], batch=8)
# 비디오 추론
results = model('video.mp4', stream=True)
for result in results:
annotated_frame = result.plot()
# 추적 기능
results = model.track('video.mp4', persist=True, tracker='botsort.yaml')
Faster R-CNN
2단계 탐지기로 높은 정확도를 제공하지만 YOLO보다 느립니다. Region Proposal Network (RPN)을 도입했습니다.
import torchvision
from torchvision.models.detection import fasterrcnn_resnet50_fpn
# 사전 학습된 Faster R-CNN 로드
model = fasterrcnn_resnet50_fpn(pretrained=True)
model.eval()
# 추론
import torch
from PIL import Image
import torchvision.transforms as T
image = Image.open('image.jpg')
transform = T.Compose([T.ToTensor()])
img_tensor = transform(image)
with torch.no_grad():
predictions = model([img_tensor])
# 예측 추출
boxes = predictions[0]['boxes']
scores = predictions[0]['scores']
labels = predictions[0]['labels']
# 신뢰도 기준으로 필터링
threshold = 0.5
keep = scores > threshold
boxes = boxes[keep]
labels = labels[keep]
scores = scores[keep]
커스텀 Faster R-CNN 학습
from torchvision.models.detection import FasterRCNN
from torchvision.models.detection.rpn import AnchorGenerator
# 커스텀 백본
backbone = torchvision.models.mobilenet_v2(pretrained=True).features
backbone.out_channels = 1280
# 커스텀 앵커 생성기
anchor_generator = AnchorGenerator(
sizes=((32, 64, 128, 256, 512),),
aspect_ratios=((0.5, 1.0, 2.0),) * 5
)
# ROI 풀러
roi_pooler = torchvision.ops.MultiScaleRoIAlign(
featmap_names=['0'],
output_size=7,
sampling_ratio=2
)
# 모델 생성
model = FasterRCNN(
backbone,
num_classes=91, # COCO 클래스
rpn_anchor_generator=anchor_generator,
box_roi_pool=roi_pooler
)
# 학습 루프
optimizer = torch.optim.SGD(model.parameters(), lr=0.005, momentum=0.9, weight_decay=0.0005)
for epoch in range(num_epochs):
for images, targets in data_loader:
images = list(image.to(device) for image in images)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
# 순전파 (학습 모드에서는 손실 반환)
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
# 역전파
optimizer.zero_grad()
losses.backward()
optimizer.step()
Non-Maximum Suppression (NMS)
중복되는 바운딩 박스를 제거하는 후처리 기법입니다.
import torch
def nms(boxes, scores, iou_threshold=0.5):
"""
boxes: [N, 4] 텐서 (x1, y1, x2, y2)
scores: [N] 텐서
"""
# 점수로 정렬
sorted_indices = torch.argsort(scores, descending=True)
keep = []
while len(sorted_indices) > 0:
# 가장 높은 점수의 박스 선택
current = sorted_indices[0]
keep.append(current.item())
if len(sorted_indices) == 1:
break
# 나머지 박스와 IoU 계산
current_box = boxes[current]
other_boxes = boxes[sorted_indices[1:]]
ious = box_iou(current_box.unsqueeze(0), other_boxes).squeeze(0)
# IoU가 임계값보다 낮은 박스만 유지
sorted_indices = sorted_indices[1:][ious < iou_threshold]
return torch.tensor(keep)
def box_iou(box1, box2):
"""두 박스 집합 간의 IoU 계산"""
area1 = (box1[:, 2] - box1[:, 0]) * (box1[:, 3] - box1[:, 1])
area2 = (box2[:, 2] - box2[:, 0]) * (box2[:, 3] - box2[:, 1])
# 교집합 좌표
lt = torch.max(box1[:, None, :2], box2[:, :2])
rb = torch.min(box1[:, None, 2:], box2[:, 2:])
wh = (rb - lt).clamp(min=0)
inter = wh[:, :, 0] * wh[:, :, 1]
iou = inter / (area1[:, None] + area2 - inter)
return iou
평가 메트릭
IoU (Intersection over Union)
def compute_iou(box1, box2):
"""
box: [x1, y1, x2, y2]
"""
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
# 교집합 영역
intersection = max(0, x2 - x1) * max(0, y2 - y1)
# 각 박스 영역
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
# 합집합 영역
union = area1 + area2 - intersection
return intersection / union if union > 0 else 0
mAP (Mean Average Precision)
객체 탐지의 표준 메트릭입니다. mAP@0.5는 IoU 임계값 0.5, COCO는 mAP@[0.5:0.95]를 사용합니다.
커스텀 탐지기 학습
데이터셋 준비
# YOLO 형식 dataset.yaml
# 구조:
# dataset/
# images/
# train/
# val/
# labels/
# train/
# val/
# dataset.yaml
path: /path/to/dataset
train: images/train
val: images/val
nc: 3 # 클래스 수
names: ['사람', '자동차', '트럭']
# 레이블 형식 (이미지당 하나의 txt 파일):
# class x_center y_center width height (정규화 0-1)
# 예: 0 0.5 0.5 0.3 0.4
YOLOv8 커스텀 학습
from ultralytics import YOLO
# 베이스 모델 로드
model = YOLO('yolov8n.pt')
# 커스텀 데이터셋으로 학습
results = model.train(
data='dataset.yaml',
epochs=100,
imgsz=640,
batch=16,
device=0, # GPU
workers=8,
optimizer='AdamW',
lr0=0.01,
lrf=0.01,
momentum=0.937,
weight_decay=0.0005,
warmup_epochs=3,
box=7.5, # 박스 손실 가중치
cls=0.5, # 분류 손실 가중치
dfl=1.5, # DFL 손실 가중치
augment=True,
hsv_h=0.015, # HSV 색조 증강
hsv_s=0.7, # HSV 채도 증강
hsv_v=0.4, # HSV 명도 증강
degrees=0.0, # 회전
translate=0.1, # 이동
scale=0.5, # 스케일
mosaic=1.0, # 모자이크 증강
)
# 검증
metrics = model.val()
print(f"mAP@0.5: {metrics.box.map50}")
print(f"mAP@0.5:0.95: {metrics.box.map}")
# ONNX 내보내기
model.export(format='onnx', dynamic=True, simplify=True)
현대 탐지 아키텍처
DETR (Detection Transformer)
NMS 없이 end-to-end로 학습하는 트랜스포머 기반 탐지기입니다.
EfficientDet
복합 스케일링으로 백본, BiFPN, 박스/클래스 네트워크를 효율적으로 확장합니다.
RetinaNet
Focal Loss를 도입하여 1단계 탐지기의 클래스 불균형 문제를 해결합니다.
import torch.nn.functional as F
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2.0):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, inputs, targets):
# inputs: [N, num_classes]
# targets: [N] (클래스 인덱스)
ce_loss = F.cross_entropy(inputs, targets, reduction='none')
pt = torch.exp(-ce_loss)
focal_loss = self.alpha * (1-pt)**self.gamma * ce_loss
return focal_loss.mean()
실시간 배포
모델 최적화
# TensorRT 최적화 (NVIDIA GPU)
model.export(format='engine', device=0, half=True)
# CoreML (iOS)
model.export(format='coreml')
# TFLite (모바일)
model.export(format='tflite')
# OpenVINO (Intel 하드웨어)
model.export(format='openvino')
실시간 비디오 처리
import cv2
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
# 웹캠 캡처
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
while True:
ret, frame = cap.read()
if not ret:
break
# 추론
results = model(frame, verbose=False)
# 시각화
annotated_frame = results[0].plot()
# FPS 계산
fps = 1.0 / (time.time() - start_time)
cv2.putText(annotated_frame, f'FPS: {fps:.1f}', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('탐지', annotated_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
성능 최적화 팁
- 작은 모델 사용 - YOLOv8n은 YOLOv8x보다 4배 빠름
- 입력 크기 축소 - 320x320은 640x640보다 4배 빠름
- Half Precision - FP16은 최신 GPU에서 2배 빠름
- 배치 처리 - 여러 이미지를 함께 처리
- TensorRT - NVIDIA GPU에서 2-3배 가속
요약
- 객체 탐지는 이미지에서 객체의 위치와 클래스를 동시에 식별합니다
- YOLO는 1단계 구조로 실시간 탐지(280+ FPS)를 달성합니다
- Faster R-CNN은 2단계 구조로 높은 정확도를 제공합니다
- NMS는 IoU 기준으로 중복 탐지를 제거합니다
- mAP는 객체 탐지의 표준 평가 메트릭입니다
- DETR, EfficientDet 등 현대 아키텍처가 등장했습니다
- TensorRT, 양자화로 실시간 배포가 가능합니다
- 弘益人間 - 탐지 기술로 안전, 접근성, 자동화에서 인류에 기여합니다
복습 문제
- 1단계 탐지기와 2단계 탐지기의 주요 차이점은 무엇이며, 각각 언제 사용하나요?
- NMS가 어떻게 작동하며 왜 필요한가요? 전통적 NMS의 한계는 무엇인가요?
- IoU 메트릭을 설명하고 객체 탐지 평가에서의 중요성을 논하세요.
- mAP@0.5:0.95가 무엇이며, COCO가 mAP@0.5 대신 이것을 사용하는 이유는?
- YOLO가 Faster R-CNN보다 빠른 이유는 무엇인가요?
- YOLOv1부터 YOLOv8까지의 발전 과정과 주요 혁신을 설명하세요.
- 모바일 기기에 탐지 모델을 배포할 때 사용할 수 있는 최적화 기법은?