귀퉁이 서재

컴퓨터 비전 - 11. YOLO v1, v2, v3 개요와 실습 본문

컴퓨터 비전

컴퓨터 비전 - 11. YOLO v1, v2, v3 개요와 실습

Baek Kyun Shin 2023. 5. 6. 15:25

객체 탐지에 혁신을 가져다준 모델인 YOLO 계열의 초기 모델인 YOLO v1, v2, v3 모델의 전반적인 특징을 알아보고, 이 모델을 활용해 간단한 객체 탐지 실습까지 해보겠습니다.


YOLO v1, YOLO v2, YOLO v3 소개

우선 YOLO v1, YOLO v2, YOLO v3를 알아봅시다. 이름을 보면 알겠지만 시리즈로 이어진 모델입니다. 버전 숫자가 클수록 개선된 모델이죠.

YOLO v1 이후로 등장한 객체 탐지 모델을 간략히 살펴보죠. 

  • YOLO v1 : 2-stage 검출기를 1-stage 검출기로 바꾸는 데 지평을 연 모델입니다. 1-stage 검출기란 영역 추정(region proposal)과 이미지 분류(classification)를 동시에 수행하는 모델을 말합니다. YOLO v1은 객체 탐지 수행 속도는 굉장히 빠르지만 정확도가 낮다는 단점이 있습니다.
  • SSD : 속도와 성능 모두 뛰어난 모델입니다.
  • YOLO v2 : 성능은 SSD와 비슷하지만 속도는 SSD보다 빠릅니다. 반면, SSD에 비해 작은 물체를 찾는 성능을 좀 떨어집니다.
  • Retinanet : YOLO v2보다 속도는 느리지만 성능은 더 좋습니다. Retinanet에는 FPN(Featrue Pyramid Network)를 적용합니다.
  • YOLO v3 : YOLO v2 대비 속도는 조금 느리지만 성능은 크게 좋아졌습니다. YOLO v3에는 FPN을 포함해 여러 객체 탐지 기술을 적용했습니다.
  • EfficientDet : EfficientDet은 D0, D1, D2 등 숫자가 높을수록 더 큰 모델입니다. 가장 작은 모델인 D0가 YOLO v3보다 수행 속도도 빠르고 성능도 조금 좋습니다. 

2020년 4월에 나온 YOLO v4는 EfficientDet보다 속도도 빠르고 성능도 좋습니다.

YOLO v1

가장 먼저 YOLO v1을 살펴보겠습니다.

<NOTE> 논문 리뷰 - YOLO(You Only Look Once) 톺아보기에서 YOLO v1 논문 전체를 번역/정리해놨습니다. 자세한 내용이 궁금한 분은 해당 글을 참고해주세요.

YOLO v1은 객체 탐지의 개별 요소를 단일 신경망(single neural network)으로 통합한 모델입니다. 먼저 YOLO v1은 입력 이미지(input images)를 S x S 그리드(S x S grid)로 나눕니다. 각 그리드의 셀(cell)마다 객체 하나를 탐지합니다. 예를 들어 만약 어떤 물체 중심이 특정 그리드 셀(grid cell) 안에 있다면, 그리드 셀이 해당 물체를 검출합니다.

각각의 그리드 셀(grid cell)은 경계 박스 B개와 그 경계 박스의 신뢰도 점수(confidence score)를 예측합니다. 신뢰도 점수는 경계 박스가 물체를 포함한다는 점이 얼마나 믿을만한지, 예측한 경계 박스가 얼마나 정확한지를 나타냅니다. 신뢰도 점수는 다음과 같이 정의합니다.

신뢰도 점수 수식

여기서 IoU는 intersection over union의 약자로 객체의 실제 경계 박스와 예측 경계 박스의 합집합 면적 대비 교집합 면적의 비율을 뜻합니다. 즉, IoU = (실제 경계 박스와 예측 경계 박스의 교집합) / (실제 경계 박스와 예측 경계 박스의 합집합)입니다.

만약 그리드 셀에 아무 물체가 없다면 Pr(Obejct)=0입니다. 아무 물체가 없다는 건 배경이라는 뜻이죠. 그러므로 신뢰도 점수도 0입니다. 그리드 셀에 어떤 물체가 확실히 있다고 예측했을 때, 즉 Pr(Object)=1일 때가 가장 이상적입니다. 따라서 신뢰도 점수가 IoU와 같다면 가장 이상적인 점수입니다.

각 경계 박스는 5개의 예측치로 이루어져 있습니다. 물체의 중심 x좌표, 물체의 중심 y좌표, 너비(w), 높이(h), 신뢰도 점수가 있죠. 여기서 (x, y) 좌표 쌍은 그리드 셀 안에서의 상대적인 물체 경계 박스 중심을 뜻합니다. 절대 위치가 아니라 그리드 셀 내의 상대 위치이므로 0~1 사이의 값을 갖습니다. 만약 경계 박스 중심인 (x, y)가 정확히 그리드 셀 중앙에 있다면 (x, y) = (0.5, 0.5)입니다. (w, h) 쌍은 경계 박스의 상대 너비와 상대 높이를 뜻합니다. 이때 (w, h)는 이미지 전체의 너비와 높이를 1이라고 했을 때 경계 박스의 너비와 높이가 몇인지를 상대값으로 나타냅니다. 마찬가지로 (w, h)도 0~1 사잇값을 갖습니다.

그리고 각 그리드 셀은 객체 클래스 조건부 확률(C)을 예측합니다. 객체 클래스 조건부 확률은 다음과 같이 계산합니다.

객체 클래스의 조건부 확률

그리드 셀 안에 물체가 있다는 조건 하에 그 물체가 어떤 클래스(class)인지를 나타내는 조건부 확률입니다. 그리드 셀에 경계 박스가 몇 개나 있는지와는 관계없이 그리드 셀 하나는 오직 하나의 클래스(class)에 대한 확률값만 구합니다. 하나의 그리드 셀은 경계 박스 B개를 예측한다고 했죠? 경계 박스는 B개 예측해도 결국엔 클래스는 하나만 예측하는 겁니다. 예측 경계 박스 가운데 실제 경계 박스와 가장 많이 겹치는 것을 대표 경계 박스로 정하는 것이죠.

테스트 단계에서는 객체 클래스 조건부 확률과 개별 경계 박스의 신뢰도 점수를 곱해주는데, 이를 각 경계 박스에 대한 class-specific 신뢰도 점수라고 부릅니다. class-specific 신뢰도 점수는 다음과 같이 계산합니다.

class-specific 신뢰도 점수

위에서 구한 객체 클래스 조건부 확률과 신뢰도 점수를 곱한 값이네요.

이 값은 '경계 박스에 특정 클래스가 있을 확률(=Pr(Class_i))'과 '예측한 경계 박스가 그 클래스에 얼마나 잘 들어맞는지(=IOU_pred^truth)'를 나타냅니다. 손실 함수에 쓰이는 값이죠.

<NOTE> YOLO v1의 손실 함수가 궁금한 분은 논문 리뷰 - YOLO(You Only Look Once) 톺아보기를 참고해주세요.

YOLO v1 연구진은 PASCAL VOC로 실험했습니다. S=7, B=2로 세팅했습니다. PASCAL VOC는 클래스가 총 20개니까 C=20입니다. S=7이면 입력 이미지가 7 x 7 그리드로 나뉩니다. B=2이므로 각 그리드 셀에서 경계 박스 2개를 예측합니다. 이렇게 하면 S x S x (B*5 + C)개의 텐서를 출력합니다. 따라서 최종 예측 텐서의 개수는 (7 x 7 x (2*5 + 20)) = 1,470개입니다. 여기서 B*5를 하는 이유는 각 경계 박스는 5개의 예측치로 이루어져 있기 때문입니다(x, y, w, h, 신뢰도 점수).

그런데 각 셀마다 경계 박스를 예측하면 최종적으로는 너무 많은 예측값이 있겠죠. 그래서 비최댓값 억제(Non-Maximum Suppression, NMS)를 적용해 경계 박스를 추린 뒤 예측합니다.

<NOTE> 비최댓값 억제(Non-Maximum Suppression, NMS)란 경계 박스 가운데 가장 확실한 경계 박스만 남기고 나머지 경계 박스는 제거하는 기법입니다.

YOLO v1은 객체 탐지 속도는 빠르지만 성능이 좀 떨어집니다. 게다가 크기가 작은 물체는 잘 탐지하지 못합니다. 왜냐하면 하나의 그리드 셀은 하나의 물체만 탐지하기 때문이죠. 하나의 그리드 셀에 작은 물체 여럿이 모여 있으면 그중 하나만 탐지합니다. 

YOLO v2

YOLO v1을 알아봤으니 이제 YOLO v2를 살펴보죠. YOLO v2를 알아보기 전에 YOLO v1부터 v3까지 간략히 비교해보겠습니다. 쓱 훑어만 보세요. 자세한 내용은 이후에 설명하겠습니다.

구분 YOLO v1 YOLO v2 YOLO v3
원본 이미지 크기 446 x 446 416 x 416 416 x 416
Feature Extractor
(Backbone 모델)
인셉션 변형 Darknet 19  Darknet 53
그리드 셀당 엥커 박스 개수 2개 5개 출력 피처 맵당 3개 / 서로 다른 스케일로 총 9개
앵커 박스 결정 방법   K-means 클러스터링 K-means 클러스터링
출력 피처 맵 크기 7 x 7 13 x 13 13 x 13, 26 x 26, 52 x 52
(총 3개의 피처 맵 사용)
피처 맵 스케일링 기법     FPN
(Feature Pyramid Network)

참고로, 원래 앵커 박스는 고정된 크기를 갖습니다. 따라서 원칙적으로 YOLO v1에서는 앵커 박스를 사용하지 않습니다. YOLO v1은 2개의 경계 박스를 예측하기 때문이죠. 고정된 크기의 앵커 박스를 사용하는 게 아니라 경계 박스를 그때그때 예측하는 겁니다. 하지만 여기서는 비교하기 쉽게 앵커 박스라 표현했습니다.

YOLO v2의 특징

YOLO v2는 다음과 같은 특징을 갖습니다.

  • Backbone 모델로는 Darknet-19를 사용합니다.

  • 배치 정규화 : 배치 정규화를 적용해주기 때문에 다른 규제를 적용할 필요가 없어서 더 빠르게 훈련할 수 있습니다. YOLO v1에서 Feature Extractor의 모든 합성곱 계층마다 배치 정규화를 적용하니 mAP가 2% 향상됐다고 합니다. 뿐만 아니라 배치 정규화는 모델 규제 효과도 있습니다. 규제 효과가 있으니 드롭아웃을 적용하지 않아도 모델이 과적합되지 않습니다. 

  • Hight Resolution Classifier : 크기가 448 x 448인 ImageNet 데이터를 활용해 분류기(classifier)를 파인 튜닝합니다. 총 10에폭으로 말이죠. 이렇게 하면 해상도가 높은 이미지에도 모델이 잘 적응하도록 만듭니다. 이 작업을 통해 maP를 4% 끌어올렸습니다.

  • 전결합 계층 대신 합성곱 계층 사용 : 분류 계층(Classification Layer)에서 전결합 계층을 제거했습니다. 대신 합성곱 피처 맵의 앵커 박스가 경계 박스를 예측하도록 합니다. 즉, 최종적으로 전결합 계층에서 예측을 수행하는 게 아니라 합성곱 피처 맵에서 예측을 수행하는 것입니다(최종 피처 맵 크기는 13 x 13). 전결합 계층은 고정된 크기의 이미지를 받아야 하지만, 합성곱 피처 맵을 사용한다면 다양한 크기의 이미지로 훈련을 할 수 있습니다.

  • 앵커 박스(anchor box) : 13 x 13 크기의 합성곱 피처 맵에서 개별 그리드 셀마다 5개의 앵커 박스로 객체 탐지를 수행합니다. 이때 앵커 박스의 크기와 비율은 K-means 클러스터링으로 결정합니다.

K-means 클러스터링을 활용한 앵커 박스 크기 및 비율 결정

  • Direct Location Prediction : 예측 경계 박스의 (x, y) 좌표가 셀에서 크게 벗어나지 않도록 Direct Location Prediction 적용합니다. Direct Location Prediction이란 그리드 셀 기준으로 상대적인 좌표로 (x ,y)를 구하는 방식입니다. 이 방식을 적용하면 (x, y)가 셀에서 크게 벗어나지 않습니다. 모델을 더 안정적으로 만들죠.

  • Fine-Grained Features : YOLO v2는 최종적으로 13 x 13 피처 맵을 만든다고 했습니다. 이 13 x 13 피처 맵을 기준으로 객체 탐지를 수행하죠. 피처 맵 크기가 이 정도면 큰 물체는 잘 예측할 수 있습니다. 반면 크기가 작은 물체는 너무 추상화해놓으면 제대로 찾지 못합니다. Faster R-CNN이나 SSD는 여러 합성곱 계층의 피처 맵마다 경계 박스를 예측해 이 문제를 해결했죠. YOLO v2는 다른 방법을 적용해서 해결합니다. 26 x 26 크기의 합성곱 피처 맵에 passthrough 모듈을 적용하는 방법입니다. (26 x 26 x 512) 계층을 (13 x 13 x 2048)로 크기를 조정한 뒤, 기존 (13 x 13 x 1024)와 결합합니다. 그러면 (13 x 13 x 3072) 크기의 계층이 나오겠죠. 이 방식으로 큰 물체와 더불어 작은 물체도 잘 식별할 수 있습니다. Fine-Grained Features를 적용하니 mAP가 1% 올랐다고 합니다.

Fine-Grained Features 방식

  • Multi-Scale Training : 분류 계층(classification layer)이 합성곱 계층(convolutional layer)으로 바뀌면서 입력 이미지 크기를 자유롭게 변경할 수 있다고 했습니다. 그래서 입력 이미지 크기를 320에서 608까지 동적으로 바꿔가며(무작위 추출해서) 훈련합니다. 이때 크기는 32의 배수로 설정합니다. 곧, 가장 작은 이미지의 크기는 (320 x 320)이고, 가장 큰 이미지의 크기는 (608 x 608)입니다. Multi-Scale Training을 적용하면 모델이 다양한 이미지 크기에 적응하도록 훈련할 수 있습니다. 

YOLO v3

YOLO v3는 YOLO v2보다 속도는 조금 느리지만 성능은 크게 좋은 모델입니다. YOLO v2의 특징을 대부분 그대로 사용하기 때문에 크게 바뀐 점은 없습니다. YOLO v3에서 바뀐 몇 가지 사항은 이렇습니다.

YOLO v3의 특징

  • Backbone 모델로 Darknet-53을 사용합니다. 아래 그림은 YOLO v3의 구조입니다. (13 x 13) 피처 맵, (26 x 26) 피처 맵, (52 x 52) 피처 맵을 활용해 객체 탐지를 수행합니다. 이때 각 피처 맵의 그리드 셀당 3개의 앵커 박스를 사용합니다. 따라서 사용하는 총 앵커 박스 개수는 {(13 x 13) + (26 x 26) + (52 x 52)} x 3이 되겠죠.

출처 : https://towardsdatascience.com/yolo-v3-object-detection-53fb7d3bfe6b

  • 3가지 피처 맵별로 예측 :
    앞서 말했듯 세 가지 피처 맵으로 객체 경계 박스를 예측합니다. 그리드 셀당 3개의 앵커 박스를 사용하는데, 이때 각 앵커 박스는 1) 객체 경계 박스 좌표(x, y, w, h), 2) 객체 여부 점수, 3) 클래스 확률을 예측합니다. 클래스 점수는 PASCAL VOC일 때는 20개, MS COCO일 때는 80개입니다. PASCAL VOC 데이터를 기반으로 객체 탐지를 한다면 모든 예측값 개수는 {(13 x 13) + (26 x 26) + (52 x 52)} x 3 x (4 + 1 + 20)개입니다. 여기서 3은 앵커 박스 개수, (4 + 1 + 20)은 (경계 박스 좌표 개수 + 객체 여부 점수 개수 + 클래스 확률 개수)를 뜻합니다.

출처 : https://blog.paperspace.com/how-to-implement-a-yolo-object-detector-in-pytorch/

  • 최종 예측 함수로 소프트맥스를 사용하지 않고, 개별 클래스별로 시그모이드를 활용한 이진 분류(binary classification)를 적용했습니다.

아쉽게도 YOLO의 원저자인 Redmon는 이러한 연구가 군사적 목적으로 사용될 수 있다는 점을 깨닫고 컴퓨터 비전 연구를 그만두겠다고 발표합니다. 이후에는 다른 개발자가 이어받아 YOLO 연구를 진행했죠. 이상으로 YOLO v1, v2, v3의 개요 설명을 마칩니다.


YOLO를 활용한 객체 탐지 실습

코드 링크 : https://github.com/BaekKyunShin/Computer-Vision-Basic/blob/main/Project5-Object_Detection/Object_Detection_with_YOLO.ipynb

YOLO에 관한 내용을 알아봤으니, 이제는 실습을 해보겠습니다.

먼저, Darknet으로 사전 훈련된 YOLO 가중치(weights)와 config를 따로 다운로드해야 합니다.

아래 코드는 구글 코랩(Google Colab)을 바탕으로 설명합니다.

1. 다크넷(Darknet) 다운로드

다크넷(Darknet)은 C와 CUDA로 작성된 오픈소스 신경망 프레임워크입니다. 설치가 쉽고, 빠르다는 장점이 있습니다. CPU와 GPU 연산을 모두 지원합니다. 다음 코드로 다크넷 깃헙을 클론합니다.

!git clone https://github.com/pjreddie/darknet

클론한 다크넷 폴더에 들어가서 어떤 파일이 있는지 출력해보죠.

cd darknet/
ls

다음으로 컴파일을 합니다.

# compile
!make

컴파일을 했으니 이제 YOLO로 객체 탐지를 해볼 차례입니다.

다크넷 프레임워크에서 YOLO를 사용해 객체 탐지를 하려면 세 가지가 필요합니다. 필요한 세 가지는 ① config 파일, ② 사전 훈련된 YOLO 가중치(weights), ③ 객체 탐지할 이미지입니다. 하나씩 알아보죠.

  • config 파일 : config 파일이란 모델에 관한 세부적인 명세를 뜻합니다. 가령, 배치 크기, 학습률, 필터 크기, 스트라이드, 패딩, 활성화 함수 등을 말합니다. darknet/cfg/ 디렉터리 안에 여러 가지 모델별 config 파일이 있습니다. 사용한 모델에 맞는 config 파일을 사용해서 객체 탐지를 수행하면 됩니다.

  • 사전 훈련된 YOLO 가중치 : YOLO로 객체 탐지를 하려면 YOLO 모델이 이미 훈련되어 있어야 합니다. 사전 훈련 모델을 사용해야 한다는 뜻이죠. 사전 훈련한 모델 전체를 가지고 오는 게 아니라 사전 훈련 모델의 가중치만 들고 오면 됩니다. 왜냐하면 우리는 config 파일을 사용하기 때문이죠. config 파일을 통해 모델의 구조를 알기 때문에 굳이 사전 훈련된 모델 전체를 들고 올 필요가 없습니다. config 파일로 모델 설계를 하고, 사전 훈련된 모델의 가중치를 우리가 사용할 모델의 가중치로 업데이트하면 되기 때문이죠. 다만, 가중치는 다운로드해서 사용해야 합니다.
  •  객체 탐지할 이미지 : 마지막으로 객체 탐지할 이미지를 준비하면 됩니다.

config 파일과 샘플 이미지는 darknet 디렉토리 안에 있습니다. 사전훈련된 YOLO의 가중치(weights)는 다운로드해야 합니다. 아래와 같이 YOLO v3의 가중치를 다운로드해봅시다. 여러 YOLO 모델이 있지만 여기서는 YOLO v3를 사용해보겠습니다. 

!wget https://pjreddie.com/media/files/yolov3.weights

앞서 현재 디렉터리를 darknet 디렉토리로 바꿨으므로, 위 코드를 실행하면 darknet 디렉토리 안에 yolo v3의 가중치인 yolov3.weights가 생깁니다.

2. YOLO를 활용한 객체 탐지

이제 객체 탐지를 해보죠. ./darknet detect 뒤에 세 가지 파라미터를 전달하면 됩니다. 순서대로 config 파일, 사전 훈련된 가중치, 샘플 이미지입니다.

!./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

...생략...
dog: 100% 
truck: 92% 
bicycle: 99%

객체 탐지가 완료됐습니다. 출력 로그 마지막을 보면 세 가지 물체를 탐지했음을 알 수 있습니다. 개, 트럭, 자전거죠. 각각 100%, 92%, 99%의 신뢰도를 보입니다. 객체 탐지 결과는 darknet 디렉터리 안에 predictions.jpg라는 이미지로 저장됩니다. 함수를 정의해서 객체 탐지 결과를 출력해보죠.

import cv2
import matplotlib.pyplot as plt

def show_detection_result(path):
    image = cv2.imread(path)
    fig = plt.gcf()
    fig.set_size_inches(18,10)
    plt.axis('off')
    plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

3. GPU를 활용한 객체 탐지

좀 더 빠른 탐지를 위해 GPU를 활용해보겠습니다. 먼저 구글 코랩에서 GPU를 실행한 뒤, 몇 가지 시드값을 설정한 Makefile 파일을 만듭니다.

!sed -i 's/OPENCV=0/OPENCV=1/' Makefile
!sed -i 's/GPU=0/GPU=1/' Makefile
!sed -i 's/CUDNN=0/CUDNN=1/' Makefile

이어서 컴파일을 해줍니다.

!make

객체 탐지 후 결괏값을 실행해보죠.

!./darknet detect cfg/yolov3.cfg yolov3.weights data/eagle.jpg
show_detection_result('predictions.jpg')

새를 잘 검출했네요. 다른 이미지도 해보죠.

!./darknet detect cfg/yolov3.cfg yolov3.weights data/horses.jpg
show_detection_result('predictions.jpg')

임계값을 바꾸면서도 실행해봅시다.

!./darknet detect cfg/yolov3.cfg yolov3.weights data/horses.jpg -thresh 0.001
show_detection_result('predictions.jpg')

임계값을 낮추니 신뢰도가 낮은 결괏값도 상당히 검출이 됐네요.

지금까지 YOLO에 관한 소개와 실습을 다뤄봤습니다.


참고 자료

Ayoosh Kathuria - "How to implement a YOLO (v3) object detector from scratch in PyTorch: Part 1"

Ayoosh Kathuria - "What’s new in YOLO v3?"

Jones Granatyr(Udemy) - "Computer Vision: Master Class"

YOLO 논문

권철민 - "딥러닝 컴퓨터 비전 완벽 가이드"

Comments