2018/10/31 updated by

YOLO-v3 on Keras を使ってみる

論文

github で公開されているKeras実装

https://github.com/qqwweee/keras-yolo3

  1. github からcloneする
  2. $ git clone https://github.com/qqwweee/keras-yolo3.git
    
  3. cloneでできた keras-yolo3 フォルダに移動する
  4. $ cd keras-yolo3
    
  5. README.md をよく読む。
  6. ## Quick Start
    1. Download YOLOv3 weights from [YOLO website](http://pjreddie.com/darknet/yolo/).
    2. Convert the Darknet YOLO model to a Keras model.
    3. Run YOLO detection.
    
  7. 学習済みモデルをダウンロードする
  8. $ wget https://pjreddie.com/media/files/yolov3.weights
    
  9. 学習済みモデルを keras 用に変換する。
  10. $ python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
    
  11. 画像ファイルを用意する
  12. フリー素材で試してみるので、今回は https://www.pakutaso.com/person/ から 人物写真を選び image001.jpg として保存した。

  13. 実行する
  14. $ python yolo_video.py --image
     ...(略)...
    Input image filename: image001.jpg
    (416, 416, 3)
    Found 2 boxes for img
    person 1.00 (4771, 870) (5800, 3643)
    person 1.00 (3635, 785) (4762, 3708)
    3.8203338981396944
    Input image filename:  
    
  15. オブジェクト認識された画像が表示されるので保存する。
  16. オリジナル画像YOLO_v3 の認識結果画像

動画像ファイルをYOLO-v3で処理をする。

  1. フリー素材のサイトから動画をダウンロードする。
  2. http://mazwai.com/#/grid/videos/173 by DAVIDE QUATELA から動画をダウンロードして、ファイル名を videos/video001.mp4 とした。

    Filename: videos/video001.mp4
    Author: davide quatela
    Author webpage: http://davidequatela.de/ 
    Licence: ATTRIBUTION LICENSE 3.0 (http://creativecommons.org/licenses/by/3.0/us/)
    Downloaded at Mazwai.com
    
  3. yolo_video.py はビデオファイルを読み込んで、yoloでオブジェクト認識するように書かれている
  4. $ python yolo_video.py --input 動画ファイル名
    または
    $ python yolo_video.py --input 動画ファイル名  --output 保存ファイル名
    
  5. yolo_video.py で動画を処理してみる。
  6. $ python yolo_video.py --input videos/video001.mp4 --output videos/video001_yolo3.mp4
    

カメラ画像をYOLO_v3でリアルタイムに処理をする

yolo_video.py を参考にして yolo_cam.py を作成した。

  detect_cam(yolo, cam_id, output_path="",count=20, imshow='cv')
    yolo: (YOLO class object)
    cam_id: (int) , camera id
    output_path: (string) mp4 path to output
    count:  (int) number of pictures to take
    imshow: (string) how to show image. ('cv', '', or 'pil')
In [ ]:
# %load qqwweee/keras-yolo3/yolo_cam.py
# yolo_cam.py
from timeit import default_timer as timer
from PIL import Image, ImageFont, ImageDraw
import matplotlib.pyplot as plt
import numpy as np
import argparse
import cv2

from yolo import YOLO

def detect_cam(yolo, cam_id, output_path="",count=20, imshow='cv'):
    cam = cv2.VideoCapture(cam_id)
    if not cam.isOpened():
        raise IOError("Couldn't open webcam or video")
    #video_FourCC    = int(cam.get(cv2.CAP_PROP_FOURCC))
    video_FourCC    = cv2.VideoWriter_fourcc('F','M','P','4')
    video_fps       = cam.get(cv2.CAP_PROP_FPS)
    video_size      = (int(cam.get(cv2.CAP_PROP_FRAME_WIDTH)),
                        int(cam.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    isOutput = True if output_path != "" else False
    if isOutput:
        print("!!! TYPE:", type(output_path), type(video_FourCC), type(video_fps), type(video_size))
        out = cv2.VideoWriter(output_path, video_FourCC, video_fps, video_size)
    accum_time = 0
    curr_fps = 0
    fps = "FPS: ??"
    prev_time = timer()
    for i in range(count):
        return_value, frame = cam.read()
        image = Image.fromarray(frame)
        image = yolo.detect_image(image)
        result = np.asarray(image)
        curr_time = timer()
        exec_time = curr_time - prev_time
        prev_time = curr_time
        accum_time = accum_time + exec_time
        curr_fps = curr_fps + 1
        if accum_time > 1:
            accum_time = accum_time - 1
            fps = "FPS: " + str(curr_fps)
            curr_fps = 0
        cv2.putText(result, text=fps, org=(3, 15), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=0.50, color=(255, 0, 0), thickness=2)
        result_rgb = result[:,:,::-1]
        if imshow == 'cv':
            cv2.imshow("result", result)
        elif imshow == 'pil':
            plt.imshow(Image.fromarray(result_rgb))
            plt.show()
        else:
            pass
        if isOutput:
            out.write(result)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    if isOutput:
        out.release()

if __name__ == '__main__':
    FLAGS = None
    parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
    parser.add_argument(
        "--camera", type=int, required=False,default=0,
        help = "Camera id number"
    )
    parser.add_argument(
        "--output", nargs='?', type=str, default="",
        help = "[Optional] Video output path"
    )
    parser.add_argument(
        "--count", type=int, default=20,
        help = "[Optional] Max capture count"
    )
    parser.add_argument(
        "--imshow", type=str, default='cv',
        help = "[Optional] How to show the result image"
    )
    FLAGS = parser.parse_args()   # FLAGS is argparse.Namespace object
    print(vars(FLAGS))

    yolo = YOLO(**vars(FLAGS))
    detect_cam(yolo, FLAGS.camera, FLAGS.output, FLAGS.count, FLAGS.imshow)
    #yolo.close_session()

keras-yolo3/ フォルダで yolo_cam.py を実行する。

$ python yolo_cam.py --camera 0 --output video002.mp4 --count 20

作成されるビデオのフォーマットは mp4 のみに対応している。 したがって、--output で指定するファイル名の拡張子は .mp4 にすること。 --count で指定した枚数だけ画像を撮影して動画ファイルを作成する。 opencvが作成したプレビュー・ウィンドウにフォーカスがあるときに、 キーボードで'q'キーを押すとそこまでの動画ファイルを作り、終了する。

[注意] できあがった mp4 ファイルはWindows Media Playerなどのアプリでは再生できるものの、HTML5のvideo要素ではなぜか再生できなかった。そのため、上記の動画はXMedia Recordを用いてフォーマット変換している。

jupyter 内で動画像を処理する

この ipynb ファイルがあるフォルダからの相対パスが VOLOv3_RPATH = 'qqwweee/keras-yolo3' であるので、YOLOクラスのオブジェクトを生成する前に カレント・ワーキング・ディレクトリを移動し、 実行が終わったら元のフォルダに戻る。

detect_cam() 関数は、--imshowオプションの値が 'pil' のときは、 できあがった認識画像をPILとmatplotlibを使って jupiterの結果画面に表示する。 OpenCVの画像は BGR 形式だが、PILをはじめとする他のライブラリは RGB 形式なので、 画像を変換している。

In [2]:
import os
import sys
import pprint

YOLOv3_RPATH = 'qqwweee/keras-yolo3'
YOLOv3_PATH = os.path.join(os.getcwd(), YOLOv3_RPATH)
sys.path.append(YOLOv3_PATH)

print(YOLOv3_PATH)
print(sys.path)
D:\Users\nitta\Documents\jupyter\keras\YOLO_v3\qqwweee/keras-yolo3
['', 'D:\\sys\\Anaconda3\\envs\\gpu\\python36.zip', 'D:\\sys\\Anaconda3\\envs\\gpu\\DLLs', 'D:\\sys\\Anaconda3\\envs\\gpu\\lib', 'D:\\sys\\Anaconda3\\envs\\gpu', 'D:\\sys\\Anaconda3\\envs\\gpu\\lib\\site-packages', 'D:\\sys\\Anaconda3\\envs\\gpu\\lib\\site-packages\\IPython\\extensions', 'C:\\Users\\nitta\\.ipython', 'D:\\Users\\nitta\\Documents\\jupyter\\keras\\YOLO_v3\\qqwweee/keras-yolo3']
In [5]:
import os
import sys
from yolo import YOLO
from yolo_cam import detect_cam

parameters = ['model_path', 'anchors_path', 'classes_path', 'gpu_num']
yolo_params = {}

for name in parameters:
    yolo_params[name] = YOLO.get_defaults(name)

print("CWD = ", os.getcwd())

print(yolo_params)

CWD = os.getcwd()
try:
    os.chdir(YOLOv3_RPATH)
    yolo = YOLO(**yolo_params)
    detect_cam(yolo, 0, '', 2, 'pil')
finally:
    os.chdir(CWD)
    #yolo.cllose_session()
CWD =  D:\Users\nitta\Documents\jupyter\keras\YOLO_v3
{'model_path': 'model_data/yolo.h5', 'anchors_path': 'model_data/yolo_anchors.txt', 'classes_path': 'model_data/coco_classes.txt', 'gpu_num': 1}
model_data/yolo.h5 model, anchors, and classes loaded.
(416, 416, 3)
Found 21 boxes for img
book 0.46 (7, 362) (27, 419)
book 0.48 (29, 199) (60, 244)
book 0.51 (101, 116) (132, 175)
book 0.53 (311, 122) (344, 177)
book 0.54 (88, 115) (121, 176)
book 0.55 (52, 204) (82, 246)
book 0.55 (141, 301) (162, 339)
book 0.56 (43, 281) (84, 341)
book 0.59 (21, 113) (427, 448)
book 0.60 (114, 117) (145, 176)
book 0.63 (65, 115) (109, 175)
book 0.64 (76, 284) (108, 340)
book 0.65 (104, 294) (131, 339)
book 0.66 (40, 203) (70, 245)
book 0.69 (342, 123) (361, 180)
book 0.70 (32, 120) (79, 177)
book 0.70 (5, 117) (56, 174)
book 0.71 (92, 292) (119, 339)
book 0.78 (131, 301) (154, 339)
book 0.78 (116, 297) (142, 340)
person 0.99 (55, 114) (416, 438)
3.2661976232305676
(416, 416, 3)
Found 21 boxes for img
book 0.42 (7, 363) (28, 419)
book 0.43 (153, 103) (177, 178)
book 0.45 (52, 202) (83, 249)
book 0.47 (361, 116) (392, 180)
book 0.49 (141, 114) (167, 174)
book 0.58 (115, 274) (156, 340)
book 0.60 (39, 202) (70, 245)
book 0.60 (139, 284) (166, 340)
book 0.62 (65, 273) (106, 342)
book 0.62 (57, 54) (570, 437)
book 0.62 (64, 207) (96, 253)
book 0.62 (90, 272) (133, 341)
book 0.63 (88, 115) (121, 176)
book 0.64 (102, 116) (132, 175)
book 0.68 (45, 279) (85, 342)
book 0.69 (5, 117) (56, 174)
book 0.69 (32, 120) (79, 176)
book 0.70 (70, 207) (117, 257)
book 0.74 (65, 115) (110, 175)
book 0.75 (114, 116) (146, 176)
person 1.00 (135, 107) (469, 436)
0.16302689855727692

Training (学習) させてみる

  1. annotation ファイルと class names ファイルを作る。

class name ファイルは1行に1つ文字列が並んでいればよい

name1
name2
...

annotationファイルは、各行が

    image_file_path   box1  box2  ...  boxN
という形式で、boxi の部分はスペース無しで詰めて
    x_min,y_min,x_max,y_max,class_id
という形式で記述する。 次の例では、最初の行の img1.jpgにはクラスは0と3の認識物体が1個ずつ合計2個写っていて、それぞれのbounding_boxが(50,100,150,200), (30,50,200,120)ということになる。
    path/to/img1.jpg 50,100,150,200,0  30,50,200,120,3
    path/to/img2.jpg 120,300,250,600,2
  1. convert.py を用いて yolov3.weights を model_data/yolo_weights.h5 に変換しておく。 -wオプションは、モデル全体ではなくと重だけをファイルに保存するためである。
    $ python convert.ps -w yolov3.cfg yolov3.weights model_data/yolo_weights.h5 
    
  2. train.py を変更してから python train.ps とやってtrainingする。--model --classes --anchors オプションを使って正しいファイルを使うように指定すること。

  3. original pretrained weights for YOLO3 を使う方法。

    $ wget https://pjreddie.com/media/files/darknet53.conv.74
    $ mv darknet53.conv.74 darknet53.weights
    $ python convert.py -w darknet53.cfg darkent53.weights model_data/darknet53_weights.h5
    $ python train.ps --model model_data/darknet53_weights.h5  --classes your_class.txt --anchors your_anchor.txt
    

学習結果

メモリ 8GB の PC ではメモリ不足で training できなかった。 メモリ 16BG のPCで training すると epoch 50回走った後(25分×50回)で、さらに100回のtrainingを始めようとするところでメモリ不足で止まった。

keras-yolo3/logs/000/trained_weights_stage_1.h5 ができていたので これを model_data/yolo.h5 としてyolo_cam.py動かしてみた。