딥러닝 기반 객체 탐지
1. 객체 탐지 개요
- 인간의 시각을 모방하는 AI
- 우리가 주변 환경을 인식할 때, 단순히 ‘무엇인가 있다’고만 생각하지 않음
- ‘앞에 자동차가 한 대 있고, 길가에는 보행자 두 명이 걸어가고 있다’와 같이 무엇이(객체 종류), 어디에(위치) 있는지를 동시에 파악
- 객체 탐지(Object Detection)는 이처럼
- 이미지나 영상 내에서 특정 객체의 클래스(Class)를 식별하고,
- 해당 객체의 위치(Localization)를
- 사각형 형태의 바운딩 박스(Bounding Box)로 나타내는
- 컴퓨터 비전 기술
- 자율주행(Mobility AI)에서의 중요성
- 자율주행차에게 ‘보는’ 것은 곧 ‘이해하는’ 것
- 객체 탐지는 자율주행 시스템의 인지(Perception) 단계에서 가장 핵심적인 역할 수행
- 안전 운전
- 도로 위의 다른 차량, 보행자, 자전거, 오토바이 등 움직이는 모든 객체를 정확히 탐지하여
- 충돌을 피하고 안전한 주행 경로 확보
- 교통 상황 판단
- 신호등, 도로 표지판 등 고정된 객체를 인식하여 ➜ 주행 규칙 준수
- 주변 차량의 밀집도를 파악하여 ➜ 교통 흐름 이해
- 환경 이해
- 낙석, 구덩이, 건물, 교차로 등 다양한 환경 요소를 탐지하여
- 차량의 위치를 정확하게 파악하고
- 주행 전략을 수립
- 판단 및 제어의 입력
- 탐지된 객체의 종류, 위치, 크기, 그리고 시계열 데이터를 통한 움직임 예측 정보는
- 판단(Decision-making) 및 제어(Control) 모듈의 주요 입력으로 활용
- 안전 운전
2. 객체 탐지의 주요 개념 및 평가 지표
- 바운딩 박스 (Bounding Box)
- 탐지된 객체의 위치를 나타내는 직사각형
- 주로 좌상단 좌표 \((x_1, y_1)\)와 우하단 좌표 \((x_2, y_2)\) 또는 중심 좌표 \((x, y)\), 너비 \((w)\), 높이 \((h)\)로 표현
- 모델은 객체의 클래스를 예측함과 동시에 이 바운딩 박스의 좌표도 예측함
- 클래스 분류 (Classification) & 신뢰도 점수 (Confidence Score)
- 모델은 각 바운딩 박스 내부에 어떤 종류의 객체가 있는지를 예측(예:
car,person,traffic light)하고, - 그 예측이 얼마나 정확하다고 확신하는지 신뢰도 점수(0~1)를 함께 출력
- 보통 이 신뢰도 점수가 특정 임계값(예: 0.5) 이상인 탐지만 유효하다고 간주함
- 모델은 각 바운딩 박스 내부에 어떤 종류의 객체가 있는지를 예측(예:
- IoU (Intersection over Union)
- 예측된 바운딩 박스(Predicted Box)와 실제 객체의 바운딩 박스(Ground Truth Box)가 얼마나 잘 겹치는지 측정하는 지표
- 계산식
\(IoU = (두\ 박스의\ 교집합\ 영역) / (두\ 박스의\ 합집합\ 영역)\) - 의미
- IoU 값이 0.5 이상이면 일반적으로 ‘올바르게 탐지되었다(True Positive)’고 판단
- IoU가 클수록 예측이 정확함
- NMS (Non-Maximum Suppression)
- 객체 탐지 모델은 하나의 객체에 대해 여러 개의 바운딩 박스를 중복하여 예측하는 경우가 많음
NMS는 이러한 중복된 바운딩 박스들을 제거하고 가장 적절한 하나의 박스만을 남기는 후처리 과정
- 작동 방식:
- 모든 예측 박스 중에서 신뢰도 점수가 가장 높은 박스를 선택
- 선택된 박스와 IoU가 특정 임계값(예: 0.5) 이상인 다른 박스들을 제거
- 남은 박스들 중에서 다시 신뢰도 점수가 가장 높은 박스를 선택하고
- 위 과정을 반복
- mAP (mean Average Precision)
- 객체 탐지 모델의 전반적인 성능을 평가하는 가장 중요한 지표
여러 클래스에 대한 Average Precision(AP) 값을 구하고, 이들의 평균을 계산한 값
- AP
- 특정 IoU 임계값에서 Precision-Recall 곡선 아래의 면적을 나타냄
- Precision: ‘탐지된 것 중 진짜’ 비율, Recall: ‘실제 객체 중 탐지된’ 비율
- 특정 IoU 임계값에서 Precision-Recall 곡선 아래의 면적을 나타냄
- mAP
- 모델이 여러 종류의 객체를 얼마나 정확하고 효율적으로 탐지하는지를 종합적으로 보여줌
3. 딥러닝 기반 객체 탐지 모델의 종류
3.1. 2단계 탐지기
- 객체 탐지 과정을
객체 후보 영역 제안(Region Proposal)과클래스 분류 및 바운딩 박스 미세 조정의 두 단계로 나누어 수행 (Two-Stage Detectors)
- 특징
- 높은 정확도
- 각 단계가 독립적으로 최적화될 수 있으므로 일반적으로 탐지 정확도가 매우 높음
- 느린 속도
- 두 단계를 순차적으로 거치므로 상대적으로 처리 속도가 느림
- 높은 정확도
- 주요 모델
- R-CNN (Region-based Convolutional Neural Network)
- Selective Search로 후보 영역을 제안하고
- CNN으로 특징 추출
- SVM으로 분류
- Fast R-CNN
- R-CNN의 속도 개선 모델
- 특징 추출을 한 번만 하고
- RoI Pooling으로 각 후보 영역의 특징을 얻음
- Faster R-CNN
- Fast R-CNN의 속도 개선 모델
- Selective Search 대신 RPN(Region Proposal Network)이라는 딥러닝 기반 네트워크로 후보 영역을 제안
- 전체 과정이 딥러닝 파이프라인 안에서 수행됨
- R-CNN (Region-based Convolutional Neural Network)
3.2. 1단계 탐지기
- 객체 후보 영역 제안과 분류 및 바운딩 박스 예측을 하나의 네트워크에서 동시에 수행함
(One-Stage Detectors)
- 특징
- 빠른 속도
- 단일 네트워크에서 모든 연산이 이루어져 처리 속도가 매우 빠름
- 자율주행과 같은 실시간 응용 분야에 적합
- 상대적으로 낮은 정확도 (초기)
- 초기에는 2단계 탐지기보다 정확도가 떨어졌으나,
- 지속적인 발전으로 성능 차이가 많이 줄어듦 ➜ 현재는 사실상 거의 차이가 없다고 해도 좋은 수준
- 빠른 속도
- 주요 모델
- YOLO (You Only Look Once)
- 이미지를 그리드로 나누고,
- 각 그리드 셀이 바운딩 박스와 클래스를 직접 예측
- 탐지 속도가 매우 빠름
- YOLOv1 ~ YOLOv8 등 계속 발전 중
- SSD (Single Shot Detector)
- 다양한 스케일의 특징 맵에서 바운딩 박스와 클래스를 예측하는 기법 사용
- YOLO와 더불어 대표적인 1단계 탐지기
- RetinaNet
- Focal Loss를 도입하여 클래스 불균형 문제를 해결
- Focal Loss: 주로 객체 탐지(Object Detection) 모델, 특히 밀집 객체 탐지(Dense Object Detection)에서 발생하는 클래스 불균형(Class Imbalance) 문제를 해결하기 위해 고안된 손실 함수
- 1단계 탐지기의 성능을 크게 끌어올린 모델
- Focal Loss를 도입하여 클래스 불균형 문제를 해결
- YOLO (You Only Look Once)
- 자율주행을 위한 모델 선택
- 자율주행과 같은 실시간 응용 분야에서는 속도(FPS, 초당 프레임 수)가 매우 중요함
- 일반적으로 YOLO나 SSD 계열의 1단계 탐지기가 선호됨
- 최신 YOLO 모델들은 정확도와 속도 모두에서 훌륭한 성능을 보여줌
4. 모빌리티 AI에 특화된 고려사항 및 데이터셋
자율주행 환경의 특수성으로 인해 객체 탐지 모델을 개발할 때 몇 가지 추가적인 고려사항이 있습니다.
4.1. 주요 탐지 객체
- 차량 (Cars, Trucks, Buses): 크기, 속도, 차종 다양성
- 보행자 (Pedestrians): 다양한 자세, 군집, 부분 가려짐
- 이륜차 (Bicycles, Motorcycles): 작고 민첩함, 예측 어려운 움직임
- 교통 신호/표지판 (Traffic Lights, Traffic Signs): 작고 색상, 형태 중요
- 차선 (Lane Lines): 일반적으로 세그멘테이션으로 처리되지만, 일부 시스템에서는 객체로 간주하기도 함
4.2. 주요 도전 과제
- 다양한 조명 조건: 낮/밤, 터널, 역광 등 조명 변화에 강건해야 함
- 기상 조건: 비, 눈, 안개 등 악천후에도 안정적인 탐지
- 부분 가려짐 (Occlusion): 다른 차량이나 장애물에 의해 객체의 일부가 가려진 경우
- 작은 객체 탐지 (Small Object Detection): 멀리 있는 차량이나 작은 표지판을 정확히 탐지하는 것은 고난도
- 실시간 성능: 높은 정확도를 유지하면서도 초당 최소 30프레임 이상의 처리 속도 필요
4.3. 대표적인 모빌리티 AI 데이터셋
- BDD100K
- Berkeley DeepDrive 100K
- 10만 장 이상의 도로 주행 이미지/영상
- 10가지 클래스에 대한 객체 탐지, 세그멘테이션, 차선 마스크 등 다양한 어노테이션을 포함
- 날씨, 시간, 지역 등이 다양하게 포함되어 자율주행 연구에 널리 사용됨
- Waymo Open Dataset
- Waymo가 공개한 대규모 데이터셋
- HD 카메라, 라이다 센서 데이터 포함
- nuScenes
- 자율주행 차량용 센서(카메라, 라이다, 레이더) 데이터를 포함하는 데이터셋
- COCO (Common Objects in Context)
- 일반적인 객체 탐지 벤치마크 데이터셋
car,person,bicycle,traffic light등 자율주행과 관련된 클래스도 다수 포함하고 있어 전이 학습에 유용함
5. 실습 예제
- BDD100K 샘플 이미지에 대한 객체 탐지 추론
- 실제 자율주행 모델을 처음부터 학습시키는 것은 매우 많은 시간과 컴퓨팅 자원을 요구함
- 여기서는 사전 학습된(Pre-trained) 객체 탐지 모델을 사용하여 실습 진행
- BDD100K 데이터셋의 샘플 이미지에 대한 추론(Inference)을 수행하고 결과를 시각화
- 라즈베리파이와 같은 임베디드 환경에서도 효율적인 실시간 탐지를 위해 자주 사용됨
준비물
- 샘플 이미지
- BDD100K 데이터셋에서 ‘차량’, ‘보행자’ 등이 포함된 몇 가지 샘플 이미지를 준비하거나,
- 임의의 도로 이미지를 사용
- 예:
sample_image.jpg,sample_image2.jpg등
- 딥러닝 프레임워크
- TensorFlow 또는 PyTorch 설치
5.1. TensorFlow 버전
- 사용 모델
- TensorFlow Hub 또는 Keras Applications에서 제공하는 사전 학습된 MobileNetV2-SSD (Single Shot Detector) 모델
- 속도와 정확도 사이의 균형이 좋아 임베디드 장치에 적합함
#//file: "mobile_net_v2_ssd.py"
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import cv2 # 이미지 처리 및 드로잉에 여전히 사용
import matplotlib.pyplot as plt # Colab 시각화를 위해 필요
import os
# --------------------------------------------------------------------------
# TensorFlow Hub 모델 로드
# --------------------------------------------------------------------------
model_url = "https://tfhub.dev/tensorflow/ssd_mobilenet_v2/fpnlite_320x320/1"
print(f"TensorFlow Hub 모델을 로드 중입니다: {model_url}")
detector = hub.load(model_url)
print("TensorFlow Hub 모델 로드 완료.")
# 모델이 학습된 COCO 데이터셋의 클래스 이름 (실제 모델 출력을 기반으로)
COCO_CLASSES = [
'__background__', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A',
'N/A', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table',
'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard',
'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book',
'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]
# 클래스 ID와 이름을 매핑하는 딕셔너리 생성 (1-based index)
category_index = {i + 1: {'id': i + 1, 'name': name} for i, name in enumerate(COCO_CLASSES[1:])}
# --------------------------------------------------------------------------
# 이미지 로드 및 전처리 함수
# --------------------------------------------------------------------------
def load_and_preprocess_image_tf(image_path, target_size=(320, 320)):
# tf.io와 tf.image를 사용하여 이미지 로드 및 전처리
img = tf.io.read_file(image_path)
img = tf.image.decode_jpeg(img, channels=3) # JPEG 디코딩, TensorFlow는 기본적으로 RGB 순서
# 원본 이미지를 numpy 배열로 복사 (시각화를 위해)
# tf.Tensor는 기본적으로 RGB 채널 순서로 읽어오므로, original_image_rgb_tf도 RGB
original_image_rgb_tf = np.copy(img.numpy())
# 모델 입력 형식에 맞게 이미지 크기 조정
# 모델은 uint8 입력을 기대하므로, float32 변환을 제거합니다.
img = tf.image.resize(img, target_size) # (height, width)
img = tf.cast(img, tf.uint8) # 모델이 uint8을 기대하므로 명시적으로 캐스팅
# 모델은 [batch, height, width, channels] 형태의 텐서를 기대하므로 배치 차원 추가
input_tensor = img[tf.newaxis, ...]
return input_tensor, original_image_rgb_tf
# --------------------------------------------------------------------------
# 검출 결과 시각화 함수 (OpenCV 그리기 -> Matplotlib으로 최종 출력)
# --------------------------------------------------------------------------
def visualize_detections_tf(image_np_rgb, detections, category_index, threshold=0.5):
# image_np_rgb는 원본 이미지 (RGB numpy array)
image_with_detections = image_np_rgb.copy() # 원본 이미지 복사 (RGB 유지)
h, w, _ = image_with_detections.shape
# detections 딕셔너리에서 필요한 정보 추출
num_detections = int(detections['num_detections'][0].numpy())
detection_boxes = detections['detection_boxes'][0].numpy() # (ymin, xmin, ymax, xmax) (0.0~1.0)
detection_classes = detections['detection_classes'][0].numpy().astype(np.int32)
detection_scores = detections['detection_scores'][0].numpy()
for i in range(num_detections):
score = detection_scores[i]
if score > threshold:
box = detection_boxes[i] # [ymin, xmin, ymax, xmax]
class_id = detection_classes[i]
# 바운딩 박스 좌표를 원본 이미지 크기에 맞게 스케일링
# Tensorflow 모델은 일반적으로 (ymin, xmin, ymax, xmax) 순서 사용
ymin, xmin, ymax, xmax = int(box[0] * h), int(box[1] * w), int(box[2] * h), int(box[3] * w)
# 클래스 이름 매핑
class_name = category_index[class_id]['name'] if class_id in category_index else f'Unknown({class_id})'
label_text = f'{class_name}: {score:.2f}'
# 바운딩 박스 및 텍스트 색상 (image_with_detections가 RGB이므로, 색상도 RGB로 지정)
color_rgb = (int(np.random.randint(0, 255)), int(np.random.randint(0, 255)), int(np.random.randint(0, 255)))
# 바운딩 박스 그리기 (OpenCV 함수는 BGR을 기대하지만, RGB 이미지에 RGB 색상을 넘겨도 내부적으로 처리함)
# image_with_detections가 RGB이므로 color_rgb를 그대로 사용합니다.
cv2.rectangle(image_with_detections, (xmin, ymin), (xmax, ymax), color_rgb, 2)
# 라벨 텍스트 배경 그리기
(text_width, text_height), baseline = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv2.rectangle(image_with_detections, (xmin, ymin - text_height - baseline - 5), (xmin + text_width, ymin), color_rgb, -1)
# 라벨 텍스트 쓰기 (검은색 글씨는 (0,0,0) RGB이므로 문제 없음)
cv2.putText(image_with_detections, label_text, (xmin, ymin - baseline - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
return image_with_detections # RGB numpy 배열 반환
# --------------------------------------------------------------------------
# 메인 실행 블록
# --------------------------------------------------------------------------
if __name__ == '__main__':
# Colab에서 이미지를 업로드하거나 GDrive를 마운트하여 사용할 수 있습니다.
# 여기서는 예시로 로컬 경로를 사용하지만, Colab에서는 직접 업로드해야 할 수 있습니다.
sample_image_path_tf = './images/road_image.jpg' # 실제 이미지 경로로 변경 필요
# Colab에서 이미지를 업로드할 경우 '/content/' 경로에 저장될 수 있습니다.
# if not os.path.exists(sample_image_path_tf):
# # Colab 환경에서 이미지 파일이 없다면, 사용자에게 업로드 지시
# from google.colab import files
# uploaded = files.upload()
# for fn in uploaded.keys():
# sample_image_path_tf = '/content/' + fn # 업로드된 파일 경로 사용
# print(f"'{fn}'이(가) '{sample_image_path_tf}' 경로에 업로드되었습니다.")
# break # 첫 번째 업로드된 파일만 사용
# 더미 이미지 생성 (Colab에서 파일이 없을 경우)
if not os.path.exists(sample_image_path_tf):
print(f"이미지 파일 '{sample_image_path_tf}'이(가) 없습니다. 더미 이미지를 생성합니다.")
# Colab 환경은 matplotlib이 주로 RGB를 기대하므로, 더미 생성 시 RGB 컬러를 사용
dummy_img = np.zeros((480, 640, 3), dtype=np.uint8) # RGB 형태의 검은색 이미지
cv2.putText(dummy_img, "Dummy Image for Object Detection (TensorFlow)", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(dummy_img, "Please replace with a real image!", (100, 300), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.rectangle(dummy_img, (300, 50), (350, 150), (255, 0, 0), 2) # 파란색 사각형 (BGR) -> 여기서는 (R,G,B)=(0,0,255)인 파란색으로 그려짐
cv2.imwrite(sample_image_path_tf, cv2.cvtColor(dummy_img, cv2.COLOR_RGB2BGR)) # 저장 시에는 BGR로 변환
print(f"더미 이미지 '{sample_image_path_tf}'를 생성했습니다.")
print("\n--- TensorFlow 기반 객체 탐지 시작 ---")
try:
# load_and_preprocess_image_tf 함수는 original_image_rgb_tf를 RGB로 반환합니다.
input_tensor_tf, original_image_rgb_tf = load_and_preprocess_image_tf(sample_image_path_tf, target_size=(320, 320))
# 실제 모델 추론 (TensorFlow Hub 모델)
detections_tf = detector(input_tensor_tf)
# visualize_detections_tf 함수에 RGB 이미지를 전달하고, RGB 이미지를 반환받습니다.
result_image_tf_rgb = visualize_detections_tf(original_image_rgb_tf, detections_tf, category_index, threshold=0.5)
# Matplotlib 시각화
plt.figure(figsize=(10, 8))
# 이미 RGB이므로 cv2.cvtColor(..., cv2.COLOR_BGR2RGB) 변환이 필요 없습니다.
plt.imshow(result_image_tf_rgb)
plt.title('TensorFlow Object Detection Results')
plt.axis('off') # 축(axis) 표시 제거
plt.show() # 이미지 출력
print("--- TensorFlow 기반 객체 탐지 완료 ---")
except FileNotFoundError as e:
print(e)
except Exception as e:
print(f"오류 발생: {e}")
5.2. PyTorch 버전
- 사용모델
- PyTorch
torchvision.models.detection에서 제공하는 사전 학습된 ResNet50-FPN 기반의 Faster R-CNN (Region-based Convolutional Network) 모델 - 뛰어난 정확도를 가지며, ResNet50-FPN (Feature Pyramid Network) 백본을 사용하여 다양한 크기의 객체 탐지에 강함
- PyTorch
#//file: "object_detection_torch.py"
import torch
from torchvision import transforms
from torchvision.models.detection import fasterrcnn_resnet50_fpn_v2, FasterRCNN_ResNet50_FPN_V2_Weights
import numpy as np
import cv2
import matplotlib.pyplot as plt
import os
from PIL import Image
# --------------------------------------------------------------------------
# 1. GPU (CUDA) 사용을 위한 device 정의 및 확인
# --------------------------------------------------------------------------
if torch.cuda.is_available():
device = torch.device('cuda')
print(f"CUDA (GPU)를 사용합니다: {torch.cuda.get_device_name(0)}")
else:
device = torch.device('cpu')
print("CUDA (GPU)를 사용할 수 없어 CPU를 사용합니다.")
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# 2. 모델 로드 및 device 이동, eval 모드 설정
# --------------------------------------------------------------------------
# 가중치 객체 생성
weights = FasterRCNN_ResNet50_FPN_V2_Weights.DEFAULT
# 모델 로드 (pretrained=True는 weights=weights와 동일하며, progress=True로 다운로드 진행 상황 표시)
model_pt = fasterrcnn_resnet50_fpn_v2(weights=weights, progress=True)
# !!! 중요: 모델을 정의된 device로 이동해야 함
# 모델 정의 후 eval() 모드 설정 전에 이동하는 것이 일반적
model_pt.to(device)
# 모델을 추론 모드로 설정
model_pt.eval()
# --------------------------------------------------------------------------
# COCO 클래스 이름 로드
coco_class_names = weights.meta["categories"]
print(f"COCO 클래스 수: {len(coco_class_names)}")
print(f"일부 COCO 클래스: {coco_class_names[0]}, {coco_class_names[1]}, {coco_class_names[2]}, {coco_class_names[3]}, {coco_class_names[5]}, {coco_class_names[7]}")
# 이미지 전처리를 위한 변환 파이프라인 로드
preprocess = weights.transforms()
# --------------------------------------------------------------------------
# 3. 이미지 로드 및 전처리 함수
# --------------------------------------------------------------------------
def load_and_preprocess_image_pt(image_path):
img_bgr = cv2.imread(image_path)
if img_bgr is None:
raise FileNotFoundError(f"이미지 파일을 찾을 수 없습니다: {image_path}")
# OpenCV는 BGR, torchvision은 RGB를 기대하므로 변환
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 원본 이미지는 시각화를 위해 저장 (np.copy()를 사용하여 원본 보존)
original_image_np = np.copy(img_rgb)
# numpy.ndarray를 PIL.Image 객체로 변환 (transforms가 PIL Image를 기대)
img_pil = Image.fromarray(img_rgb)
# PyTorch 모델 입력 형식으로 변환 (PIL Image -> Tensor)
input_tensor_pt = preprocess(img_pil)
return input_tensor_pt, original_image_np
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# 4. 검출 결과 시각화 함수
# --------------------------------------------------------------------------
def visualize_detections_pt(image_np, detections_output, class_names, threshold=0.7):
# image_np는 RGB 포맷의 numpy 배열이어야 합니다.
image_with_detections = image_np.copy()
# 모델 출력에서 결과 추출
# detections_output은 list[dict] 형태이며, 우리는 batch_size=1이므로 첫 번째 dict를 사용함
boxes = detections_output['boxes'].cpu().numpy()
labels = detections_output['labels'].cpu().numpy()
scores = detections_output['scores'].cpu().numpy()
# OpenCV의 바운딩 박스 그리기
for i in range(len(boxes)):
score = scores[i]
if score > threshold:
box = boxes[i].astype(int) # 바운딩 박스 좌표 [xmin, ymin, xmax, ymax]
class_id = labels[i]
xmin, ymin, xmax, ymax = box[0], box[1], box[2], box[3]
# 클래스 이름 확인 (안전한 인덱싱)
class_name = class_names[class_id] if class_id < len(class_names) else f'Unknown({class_id})'
label_text = f'{class_name}: {score:.2f}'
# 랜덤 색상 (매번 동일한 색상을 사용하려면 np.random.seed()를 설정하거나 미리 정의된 색상을 사용)
color = (int(np.random.randint(0, 255)), int(np.random.randint(0, 255)), int(np.random.randint(0, 255)))
# 바운딩 박스 그리기
cv2.rectangle(image_with_detections, (xmin, ymin), (xmax, ymax), color, 2)
# 라벨 텍스트 배경 그리기
# 텍스트 크기를 미리 계산하여 배경 사각형 크기를 맞춤
(text_width, text_height), baseline = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv2.rectangle(image_with_detections, (xmin, ymin - text_height - baseline - 5), (xmin + text_width, ymin), color, -1)
# 라벨 텍스트 쓰기 (배경색과 반대되는 색상이 좋음)
cv2.putText(image_with_detections, label_text, (xmin, ymin - baseline - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
return image_with_detections
# --------------------------------------------------------------------------
if __name__ == '__main__':
# BDD100K 샘플 이미지 또는 기타 도로 이미지 경로
sample_image_path_pt = './images/road_image.jpg' # 이미지 이름을 변경했습니다 (bdd10k_sample.jpg -> road_image.jpg)
# 더미 이미지 생성 (파일이 없을 경우)
if not os.path.exists(sample_image_path_pt):
print(f"이미지 파일 '{sample_image_path_pt}'이(가) 없습니다. 더미 이미지를 생성합니다.")
dummy_img = np.zeros((480, 640, 3), dtype=np.uint8)
cv2.putText(dummy_img, "Dummy Image for Object Detection", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(dummy_img, "A traffic light here!", (100, 300), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.rectangle(dummy_img, (300, 50), (350, 150), (255, 0, 0), 2) # 파란색 상자 (신호등 역할)
cv2.imwrite(sample_image_path_pt, dummy_img)
print(f"더미 이미지 '{sample_image_path_pt}'를 생성했습니다.")
print("\n--- PyTorch 기반 객체 탐지 시작 ---")
try:
input_tensor_pt, original_image_np_pt = load_and_preprocess_image_pt(sample_image_path_pt)
# 추론 실행 (입력 텐서를 배치 차원과 함께 디바이스로 이동)
with torch.no_grad():
# input_tensor_pt는 [C, H, W] 형태
# 모델은 배치 차원을 [N, C, H, W] 형태로 기대하므로 unsqueeze(0)를 통해 추가해줌
detections_pt = model_pt([input_tensor_pt.to(device)]) # 수정: input_tensor_pt를 [input_tensor_pt.to(device)]로 감쌈
# 결과 시각화
# detections_pt는 모델의 출력으로, list[dict] 형태 (배치 크기만큼).
# 우리 배치는 1이므로, detections_pt[0]이 실제 이미지의 결과 딕셔너리임
result_image_pt = visualize_detections_pt(original_image_np_pt, detections_pt[0], coco_class_names, threshold=0.7)
plt.figure(figsize=(10, 8))
# 이미 RGB로 변환되어 있으므로 Matplotlib에서 바로 표시 가능
plt.imshow(result_image_pt)
plt.title('PyTorch Object Detection Results')
plt.axis('off')
plt.show()
print("--- PyTorch 기반 객체 탐지 완료 ---")
except FileNotFoundError as e:
print(e)
except Exception as e:
print(f"오류 발생: {e}")
- 객체 탐지는 단순히 이미지를 ‘이해’하는 것을 넘어, 컴퓨터가 현실 세계와 상호작용할 수 있게 하는 핵심 기술임
- 자율주행에서는 매 순간 정확하고 빠르게 주변 객체를 파악하는 것이 안전과 직결되기 때문에, 객체 탐지 모델의 성능 향상은 곧 자율주행 기술의 발전을 의미함
- 객체 탐지의 기본적인 원리부터 딥러닝 기반 모델의 종류, 그리고 실제 코드에서의 적용 방법을 이해할 것