CHAPTER 4

객체 탐지

이미지에서 객체의 위치와 클래스 식별

객체 탐지 개요

객체 탐지는 이미지 내에서 여러 객체의 위치(바운딩 박스)와 클래스를 동시에 식별하는 작업입니다. 분류(무엇인가)와 위치 파악(어디 있는가)을 결합한 복합 작업입니다.

주요 구성 요소

탐지 방식 분류

YOLO (You Only Look Once)

YOLO는 객체 탐지를 회귀 문제로 처리하여 실시간 성능을 달성한 혁신적인 모델입니다.

YOLO 발전 과정

버전연도주요 혁신FPSmAP
YOLOv12016단일 단계 탐지4563.4
YOLOv32018다중 스케일 예측3057.9
YOLOv52020자동 앵커 학습14067.3
YOLOv72022학습 가능한 bag-of-freebies16069.7
YOLOv82023앵커 프리, 정확도 향상28072.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()

성능 최적화 팁

요약

  • 객체 탐지는 이미지에서 객체의 위치와 클래스를 동시에 식별합니다
  • YOLO는 1단계 구조로 실시간 탐지(280+ FPS)를 달성합니다
  • Faster R-CNN은 2단계 구조로 높은 정확도를 제공합니다
  • NMS는 IoU 기준으로 중복 탐지를 제거합니다
  • mAP는 객체 탐지의 표준 평가 메트릭입니다
  • DETR, EfficientDet 등 현대 아키텍처가 등장했습니다
  • TensorRT, 양자화로 실시간 배포가 가능합니다
  • 弘益人間 - 탐지 기술로 안전, 접근성, 자동화에서 인류에 기여합니다

복습 문제

  1. 1단계 탐지기와 2단계 탐지기의 주요 차이점은 무엇이며, 각각 언제 사용하나요?
  2. NMS가 어떻게 작동하며 왜 필요한가요? 전통적 NMS의 한계는 무엇인가요?
  3. IoU 메트릭을 설명하고 객체 탐지 평가에서의 중요성을 논하세요.
  4. mAP@0.5:0.95가 무엇이며, COCO가 mAP@0.5 대신 이것을 사용하는 이유는?
  5. YOLO가 Faster R-CNN보다 빠른 이유는 무엇인가요?
  6. YOLOv1부터 YOLOv8까지의 발전 과정과 주요 혁신을 설명하세요.
  7. 모바일 기기에 탐지 모델을 배포할 때 사용할 수 있는 최적화 기법은?