【Keras】Keras기반 YOLO3 - 오픈 소스 코드로 Inference

심플하면서 좋은 성능을 보유하는 YOLO - Keras open source 코드를 공부해보자

Keras기반 YOLO3 오픈 소스 코드 공부 및 활용

1. Keras 기반 YOLO

  • DarkNet은 c언어 기반의 하나의 딥러닝 프레임워크라고 할 수 있다.
  • 따라서 Yolo를 python에서 사용하기 위해서는 Tensorflow, keras, torch에서 Yolo모델을 지원해주어야 한다.
  • Tenworflow Object Detection API에서는 YOLO를 지원하지 않고, keras에서 위의 코드를 이용해서 구현할 수 있다.
  • keras를 사용함에 있어서 source 코드가 Tensorflow보다 훨씬 이해하기가 쉽다. 그래서 Customization도 상대적으로 쉽다.

  • keras 기반 유명한 Yolo 오픈소스 코드 비교

drawing

2. qqwweee/keras-yolo3 분석하기

drawing

  1. Yolo.py : train, inference 대부분이 이 파일에 있는 Yolo Class의 메소드 함수를 사용한다.
  2. yolo3 Directory : model.py와 utils.py에 필요한 함수가 정의되어 있고, Yolo.py 내부 함수들이 이것을 이용한다.
  3. train.py : 학습에 사용
  4. convert.py : darkNet weight를 받아서 Keras에서 사용할 수 있는 Weight 파일로 바꿔준다.
  5. coco_annotation.py : coco data format을 csv로 바꿔준다.
  6. voc_annotation.py : voc xml파일 포멧을 csv로 바꿔준다.
  7. yolo_video.py : Inferene 할 수 있따. 하기 전에 cumstomization이 필요하다.
  8. 이 코드는 Setup이 안돼 있어서 손수 몇가지 작업을 해주어야 한다.
  9. keras : 2.2 version. Tensorflow : 1.3 version
  10. model_data : 환경구성 파일 class id 와 mapping, yolo_anchors - Default anchors가 들어가 있다.
    csv 파일 format 예시
    path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3
    path/to/img2.jpg 120,300,250,600,2

3. keras-yolo3 패키지를 이용하여 Yolo 수행

  • 앞으로 우리는 오픈소스 패키지를 이용할 것이다. 이떄 패키지의 .py파일을 그대로 이용하는게 아니라, 파일 내부의 클래스와 맴버 함수를 하나하나 적극적으로 사용해 나갈 것이다.
  • 물론 맴버함수 하나하나 전부 알고 사용한다면 좋겠지만, 정말 필요한 핵심적인 맴버 함수(YOLO.detect_image)를 사용하면 이 함수가 알아서 주변의 맴버 함수 이것저것을 사용해서 결국 Detection을 수행한다.
  • 차근차근 코드를 처보고 익히면서 공부해보자.
  • 참고 자료 : /DLCV/Detection/yolo/KerasYolo_이미지와_영상_Detection.ipynb

  • Yolo를 사용하는 딥러닝 모든 딥러닝 프레임워크 들은, 다크넷에서 Pretrained된 yolo/tiny-yolo weights 모델을 다운로드해서 사용한다.
  • pytorch_YoloV3 package만 봐도 다크넷사이트에서 pretrained 모델을 다운로드해서 처리하는 것을 확인할 수 있다.
  • 다운로드한 다크넷 weight 파일을 기반으로 keras-yolo3에서 사용할 수 있는 weight 파일로 변환 후 이를 이용하여 Object Detection 수행할 것이다. 아래에 어떻게 그렇게 하는지 나와 있다.
import os
import sys
import random
import math
import time
import numpy as np
import tensorflow as tf
import matplotlib
import matplotlib.pyplot as plt
%cd ./keras-yolo3  
from yolo import YOLO  # yolo.py 파일의 YOLO class를 가져온다.
/home/sb020518/DLCV/Detection/yolo/keras-yolo3


Using TensorFlow backend.

3-1 Local Directory 상에서 yolo package를 import 함.

  • 위와 같이 파일이 있는 위치에 가서 import를 해도 되지만 아래와 같은 방법도 있다. 즉
  • keras-yolo3는 setup을 제공하지 않으므로 local directory상에서 바로 package를 import함.
  • 이를 위해 keras-yolo3를 system path에 추가. 그래서 아래 2줄이 필요한 것이다. 없다며 import 불가능
  • keras-yolo3 디렉토리에 있는 yolo.py에서 YOLO class를 import하여 사용.

  • 점프 투 파이썬 내용 참조하기(python package directory path add 하기)
    • import sys; sys.path’ [내부의 위치들이 파이썬 라이브러리가 설치되어 있는 디렉터리들의 목록을 보여준다.]
    • sys.path.append(“모듈이 있는 절대 위치 ex) C:/doit/mymod”) 그 후 import mod3를 하면 정상적으로 import되는 것을 확인할 수 있다.!
print(sys.path)  # 파이썬 라이브러리가 설치되어 있는 디렉터리 확인 가능
LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)

from yolo import YOLO  # yolo.py 파일의 YOLO class를 가져온다.
# YOLO 클래스는 model_path, achors_path, classes_path를 model_data 밑에 파일로 가짐. 변경을 위해서는 yolo.py 파일에서 YOLO 클래스코드를  직접 변경 필요. 

print(LOCAL_PACKAGE_DIR)
!ls /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3
!cat /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3/yolo.py
/home/sb020518/DLCV/Detection/yolo/keras-yolo3/keras-yolo3
ls: cannot access '/home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3': No such file or directory
cat: /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3/yolo.py: No such file or directory

YOLO 객체를 사용하기 위한 모델 파일 설정 및 소스 코드 변경

  • 다크넷에서 Yolo V3 Weight 모델 파일을 다운로드 받은 뒤 이를 keras-yolo3용으로 모델 파일 변경
  • convert.py 파일을 이용해서 아래와 같은 작업을 거치면 된다. GPU를 사용하기도 하지만 나는 CPU밖에 없으므로 CPU로도 처리가 가능하다. 대략 5분정도 걸린다.
  • model_data 디렉토리 밑에 yolo_anchors.txt, coco_classes.txt 가 있는지 확인.

  • keras-yolo3 디렉토리 밑에서 아래 명령어로 다크넷에서 Yolov3 weight를 다운로드 받고 이를 keras-yolo3용으로 모델 파일 변경
  • cfg 파일은 이미 git 레포지토리에 있었다. pjreddie(YOLO 저자)사이트에서 받은 파일과 거의 똑같다. batch등 값만 아주 조금 다르다.
$ wget https://pjreddie.com/media/files/yolov3.weights
$ python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
  • convert.py를 수행하면 yolo anchor값이 yolo_anchors.txt 파일이 자동으로 생성됨.
  • coco label과 클래스 매핑은 0부터 매핑함.(0 => person)
  • yolo_anchors : Default anchor box정보가 들어가 있다.(9개) - 너비 x 높이, 너비 x 높이…

아래 코드 설명

  • YOLO 객체 생성. config는 default로 keras-yolo3 디렉토리에 있는 yolov3.cfg를 적용
  • 중요한 인자를 YOLO 생성자 인자로 넣어준다. 그래서 yolo class가 생성된다.
  • 인자를 아무것도 넣지 않아도, _default에 의해서 맴버변수가 정의가 자동으로 정의되지만, 우리에게 맞게 아래와 같이 설정

drawing

  • self.__dict__.update에 의해서 yolo 생성자의 값이 자동으로 분배 및 저장된다.
    예를 들어서 self.model_path, self.anchors_path, self.classes_path, self.score 가 위에 정의되어 있는 dictionary 변수를 읽어서 자동으로 맴버변수를 정의해준다.
  • 새로 넣어주는 생성자 인자에서 path값을 절대경로로 넣어줘야 한다. 상대경로 안된다!! 아래의 코드들을 보면 왜 안되는지 알 수 있다. (os.path.expanduser 라는 API를 이용하기 때문이다. 이 모듈은 home directory 부터 시작하는 ~부터 시작하는 path를 적어줘야 한다.)
  • yolo class 생성자에 의해서 yolo.py의 _get_class, _get_anchors, generate 맴버함수를 사용한다.
  • 이미지를 PIL을 사용한다. 지금 PIL은 거의 죽은 패키지이다. 근데 이 패키지는 PIL을 사용하기 때문에 우리도 어쩔 수 없이 이것을 사용해야 한다. 보통은 openCV등으로 img read를 해서 array형태로 받아와야하는데…
import sys
import argparse
from yolo import YOLO, detect_video
#keras-yolo에서 image처리를 주요 PIL로 수행. 
from PIL import Image


config_dict = {}
yolo = YOLO(model_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo.h5',
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/coco_classes.txt',
           score=0.55)
# score=0.55를 지우면 원래 _default대로 0.3으로 지정된다.
/home/sb020518/DLCV/Detection/yolo/keras-yolo3/model_data/yolo.h5 model, anchors, and classes loaded.

단일 이미지 Object Detection

# 원본 이미지 보기 # Path 설정 조심하기 ~/DLCV로 하면 안되더라.
from PIL import Image
img = Image.open(os.path.join('/home/sb020518/DLCV/data/image/beatles01.jpg'))

plt.figure(figsize=(12, 12))
plt.imshow(img)

drawing

  • yolo.detect_image() 메소드는 PIL package를 이용하여 image 작업 수행. keras-yolo3/font 디렉토리를 상위 디렉토리로 복사 해야함. (나는 아무 문제 없었음)
  • 만약 font문제가 있다면, 해결방법은 다음과 같다.
// detect_image 메소드의 ImageFont.truetype에러. 
~~yolo/keras-yolo3 $ cp -rf font ..
// keras-yolo3 패키지 내부의 font 폴더를 아래의 디렉토리(지금 나의 주피터 노트북파일(KerasYolo_이미지와_영상_Detection)이 있는 위치로 내려준다.)

img = Image.open(os.path.join('/home/sb020518/DLCV/data/image/beatles01.jpg'))
detected_img = yolo.detect_image(img)

plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
(416, 416, 3)
Found 11 boxes for img
car 0.56 (314, 233) (351, 267)
car 0.70 (502, 230) (572, 291)
car 0.79 (471, 235) (503, 261)
car 0.93 (383, 222) (400, 238)
car 0.94 (432, 225) (452, 242)
car 0.96 (138, 246) (256, 334)
person 0.71 (606, 228) (627, 293)
person 0.98 (395, 274) (551, 554)
person 0.98 (588, 272) (735, 557)
person 1.00 (48, 254) (189, 556)
person 1.00 (260, 263) (377, 537)
9.930654634023085

drawing

4. Video Object Detection 수행

  • yolo_video.py를 사용하면 inference를 이미지든 비디오든 쉽게 수행할 수 있지만, 이것은 cv2.imshow를 사용한다. 이것은 jupyter에서 보이지 않는다. (OpenCV Windows Frame 인터페이스 - 이전 게시물)
  • 따라서 우리는 새롭게 우리가 다시 정의해서 사용해보자.
  • 나는 CPU만을 사용하기 때문에 하나의 이미지당 2초가 걸린다. GPU를 쓰는 사람들은 0.05초 정도 걸린다.

drawing

import cv2
import time

def detect_video_yolo(class_yoloModel, input_path, output_path=""):
    
    start = time.time()
    cap = cv2.VideoCapture(input_path)
    
    #codec = cv2.VideoWriter_fourcc(*'DIVX')
    codec = cv2.VideoWriter_fourcc(*'XVID')
    vid_fps = cap.get(cv2.CAP_PROP_FPS)
    vid_size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    vid_writer = cv2.VideoWriter(output_path, codec, vid_fps, vid_size)
    
    frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print('총 Frame 갯수:', frame_cnt, '원본 영상 FPS:',vid_fps)
    index = 0
    while True:
        hasFrame, image_frame = cap.read()
        if not hasFrame:
            print('프레임이 없거나 종료 되었습니다.')
            break
        start = time.time()
        # PIL Package를 내부에서 사용하므로 cv2에서 읽은 image_frame array를 다시 PIL의 Image형태로 변환해야 함.  
        image = Image.fromarray(image_frame)
        # 아래는 인자로 입력된 yolo객체의 detect_image()로 변환한다.
        detected_image = class_yoloModel.detect_image(image)
        # cv2의 video writer로 출력하기 위해 다시 PIL의 Image형태를 array형태로 변환 
        result = np.asarray(detected_image)
        index +=1
        if index%5 == 0:
            print('#### frame:{0} 1장 이미지 처리시간:{1}, 진행률:{2:4.2f}%'.format(index, round(time.time()-start,3), index/frame_cnt*100 ))
        
        vid_writer.write(result)
    
    vid_writer.release()
    cap.release()
    print('### Video Detect 총 수행시간:', round(time.time()-start, 5))
detect_video_yolo(yolo, '/home/sb020518/DLCV/data/video/Night_Day_Chase.mp4', '/home/sb020518/DLCV/data/output/Night_Day_Chase_yoloKeras_01.avi')
총 Frame 갯수: 1383 원본 영상 FPS: 28.282227867912656
(416, 416, 3)
Found 2 boxes for img
motorbike 0.99 (559, 177) (862, 459)
person 0.87 (549, 113) (870, 451)
2.561595087056048
2.6086239949800074
#### frame:5 5장 이미지 처리시간:13.065, 진행률:0.36%
(416, 416, 3)

drawing

5. tiny Yolo를 이용하여 이미지와 영상 object detection 수행.

  • tiny yolo weights파일은 https://pjreddie.com/media/files/yolov3-tiny.weights 에서 다운로드 받을 수 있음.
  • 다운로드 받은 tiny-yolo weight파일을 keras-yolo3에서 사용할 수 있게 Convert 수행 후 YOLO객체에서 로딩하여 사용
  • 확실히 tiny Yolo는 이미지에 정말 확실한!! 객체만 찾는다.
  • 그럼에도 불구하고 PIL을 사용해서 그런지 (?) 수행속도가 0.03정도 나온다. (논문 공식 속도인 220 FPS 까지는 안된다.)
$ wget https://pjreddie.com/media/files/yolov3-tiny.weights
$ !python convert.py yolov3-tiny.cfg ./model_data/yolov3-tiny.weights model_data/yolo-tiny.h5
  • tiny yolo weight 파일과 tiny와 맞는 anchor 파일, coco 클래스 파일을 YOLO 객체 생성 시 인자로 입력
tiny_yolo = YOLO(model_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo-tiny.h5',
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/tiny_yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/coco_classes.txt')
  • 단일 이미지 Object Detection
img = Image.open(os.path.join('../../data/image/beatles01.jpg'))
detected_img = tiny_yolo.detect_image(img)

plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
  • Video Object Detection
detect_video_yolo(tiny_yolo, '../../data/video/secretlife_pet.mp4', '../../data/output/secretlife_pet_yolo01.avi')

© All rights reserved By Junha Song.