CPU環境でYOLOv3をpythonで動かしてリアルタイム画像認識してみた

#背景
以前Yoloをpythonで動かすための記事を書きました。

>[YOLOをpythonで動かしてリアルタイム画像認識をしてみた](https://qiita.com/daiarg/items/a922c242af9bb76eb470)

Yoloよりもさらに高速かつ精度が上がったと言われるYolov3にトライしようとしたら、
どうやら前回記事で挙げたTensorflowベースのdarkflowは未対応らしいことがわかった・・・。

調べてみると、PyTorchというpythonの機械学習ライブラリを使ったやり方があるらしいことがわかったので、
pythonの統合開発環境であるanacondaを使ってトライしてみましたので、
そのやり方を残しておきます。

#参考サイト
参考にしたのは以下のサイト。
ここを参考にさせていただくだけでそのままできました！！ありがとうございます。
>http://starpentagon.net/analytics/yolo_v3_on_mac/

以下に、実際に行った作業を記載していきます。
上記サイトをほぼ引用させてもらっています:sweat_smile:

#PyTorchって？
参考サイトより　
>YOLO V3の公式サイトのコードで物体検知をできますが、PyTorchを使った実装「A PyTorch implementation of a YOLO v3 Object Detector」が公開されており

>公式コードから不要なコードが削除されておりシンプル
ドキュメントが丁寧
動画の物体検知、内蔵カメラを使ったリアルタイム物体検知のスクリプトも提供
と扱いやすい実装になっているので、今回はこちらを利用しました。

これを受けて改めてPyTorchを調べてみると、

・Facebookが開発を主導
・Python向けのオープンソース機械学習ライブラリ
・Numpyに似た扱いができる
・最近注目されているらしい
・TensorflowやKerasと比較しても後発のフレームワーク

ふむふむ。
numpyと似た操作ができるというのは入りやすくていいですね。
以下のサイトがわかりやすかったです。
とりあえず実装を進めてみます。

>参考サイト：
[https://www.sejuku.net/blog/64175](https://www.sejuku.net/blog/64175)
[https://www.codexa.net/pytorch-python/](https://www.codexa.net/pytorch-python/)

#開発環境
MacBook Air (13-inch, Early 2015)
macOS Mojave 10.14.2
python 3.6.3

#環境構築
anacondaの環境構築は調べると色々出てくるので、
ここでは省略します。参考までに以下のサイトをご覧ください。

>[Anaconda で Python 環境をインストールする](https://qiita.com/t2y/items/2a3eb58103e85d8064b6)



###仮想環境Yolov3の作成
Yolov3用の環境をcondaを使って作成します。

```
$ conda create -n yolo_v3 python=3.6 pip
```

これで仮想環境を構築できます。
anaconda navigatorでも仮想環境ができていることがわかります。
![スクリーンショット 2019-02-22 0.13.02.png](https://qiita-image-store.s3.amazonaws.com/0/193530/fe2d747a-4dce-5076-57c2-c9f7939765ae.png)

```
$ source activate yolo_v3
```

と打つことで、作成した仮想環境(yolo_v3)に入ることができます。
ちなみに、仮想環境から抜けたいときは下記で可能です。

```
(yolo_v3) $ source deactivate
```

###必要なライブラリのインストール
次いで、必要なライブラリのインストールを進めます。

```
(yolo_v3) $ conda install pandas opencv
(yolo_v3) $ conda install pytorch torchvision -c pytorch
(yolo_v3) $ pip install matplotlib cython
```

ちなみにMacの場合、matplotlibのバックエンドの影響でエラーが出てしまうようなので、
下記フォルダ内にあるmatplotlibrcファイルをテキストなどで開き、

backend : macosx
を
backend : Tkagg
に変更して保存します。
![スクリーンショット 2019-02-22 0.51.20.png](https://qiita-image-store.s3.amazonaws.com/0/193530/d9c1cad0-a71e-7a69-f10d-8b040b514d18.png)
参考サイトにあるように、もちろんviコマンドなどで編集してもOKです。


###Yolov3のインストールと設定
下記を実行してPyTorchでyolov3を実行する環境と、yolov3の学習の重みファイルをダウンロード。

```
(yolo_v3) $ git clone https://github.com/ayooshkathuria/pytorch-yolo-v3.git
(yolo_v3) $ cd pytorch-yolo-v3
(yolo_v3) $ wget https://pjreddie.com/media/files/yolov3.weights
```

###内蔵カメラ or USBカメラを使ったリアルタイム認識
ここまでできれば、あとはサンプルコードを実行するだけでyolov3を動かせます！

```
(yolo_v3) $ python3 cam_demo.py
```

![スクリーンショット 2019-02-23 13.17.46.jpg](https://qiita-image-store.s3.amazonaws.com/0/193530/180c1886-771e-da50-9408-d20940e59a78.jpeg)

Macの内蔵カメラでリアルタイム認識できました！

・keyboard
・tvmonitor
・cup
・mouse
・cell phone
・口から下しか写っていないのにperson
・ほぼ写っていないのにchair

を認識できました！

```
FPS of the video is  1.27
FPS of the video is  1.53
FPS of the video is  1.60
FPS of the video is  1.65
FPS of the video is  1.70
FPS of the video is  1.73
FPS of the video is  1.73
・
・
・
FPS of the video is  1.92
FPS of the video is  1.92
FPS of the video is  1.92
FPS of the video is  1.92
```

2fps弱くらいですね。
CPUでこれだけの画像認識実行してくれるのはスゴイ。

実行したサンプルコードを参考に挙げておきます。
Videocapture(0)を(1)にするとMacに接続したUSBカメラを入力にできますね。


```python:cam_demo.py
from __future__ import division
import time
import torch
import torch.nn as nn
from torch.autograd import Variable
import numpy as np
import cv2
from util import *
from darknet import Darknet
from preprocess import prep_image, inp_to_image
import pandas as pd
import random
import argparse
import pickle as pkl

def prep_image(img, inp_dim):
    """
    Prepare image for inputting to the neural network.
    Returns a Variable
    """
    orig_im = img
    dim = orig_im.shape[1], orig_im.shape[0]
    img = cv2.resize(orig_im, (inp_dim, inp_dim))
    img_ = img[:,:,::-1].transpose((2,0,1)).copy()
    img_ = torch.from_numpy(img_).float().div(255.0).unsqueeze(0)
    return img_, orig_im, dim

def write(x, img):
    c1 = tuple(x[1:3].int())
    c2 = tuple(x[3:5].int())
    cls = int(x[-1])
    label = "{0}".format(classes[cls])
    color = random.choice(colors)
    cv2.rectangle(img, c1, c2,color, 1)
    t_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_PLAIN, 1 , 1)[0]
    c2 = c1[0] + t_size[0] + 3, c1[1] + t_size[1] + 4
    cv2.rectangle(img, c1, c2,color, -1)
    cv2.putText(img, label, (c1[0], c1[1] + t_size[1] + 4), cv2.FONT_HERSHEY_PLAIN, 1, [225,255,255], 1);
    return img

def arg_parse():
    """
    Parse arguements to the detect module
    """
    parser = argparse.ArgumentParser(description='YOLO v3 Cam Demo')
    parser.add_argument("--confidence", dest = "confidence", help = "Object Confidence to filter predictions", default = 0.25)
    parser.add_argument("--nms_thresh", dest = "nms_thresh", help = "NMS Threshhold", default = 0.4)
    parser.add_argument("--reso", dest = 'reso', help =
                        "Input resolution of the network. Increase to increase accuracy. Decrease to increase speed",
                        default = "160", type = str)
    return parser.parse_args()

if __name__ == '__main__':
    cfgfile = "cfg/yolov3.cfg"
    weightsfile = "yolov3.weights"
    num_classes = 80

    args = arg_parse()
    confidence = float(args.confidence)
    nms_thesh = float(args.nms_thresh)
    start = 0
    CUDA = torch.cuda.is_available()

    num_classes = 80
    bbox_attrs = 5 + num_classes

    model = Darknet(cfgfile)
    model.load_weights(weightsfile)

    model.net_info["height"] = args.reso
    inp_dim = int(model.net_info["height"])

    assert inp_dim % 32 == 0
    assert inp_dim > 32

    if CUDA:
        model.cuda()

    model.eval()

    videofile = 'video.avi'

    cap = cv2.VideoCapture(0)

    assert cap.isOpened(), 'Cannot capture source'

    frames = 0
    start = time.time()
    while cap.isOpened():

        ret, frame = cap.read()
        if ret:

            img, orig_im, dim = prep_image(frame, inp_dim)

#            im_dim = torch.FloatTensor(dim).repeat(1,2)

            if CUDA:
                im_dim = im_dim.cuda()
                img = img.cuda()

            output = model(Variable(img), CUDA)
            output = write_results(output, confidence, num_classes, nms = True, nms_conf = nms_thesh)

            if type(output) == int:
                frames += 1
                print("FPS of the video is {:5.2f}".format( frames / (time.time() - start)))
                cv2.imshow("frame", orig_im)
                key = cv2.waitKey(1)
                if key & 0xFF == ord('q'):
                    break
                continue

            output[:,1:5] = torch.clamp(output[:,1:5], 0.0, float(inp_dim))/inp_dim

#            im_dim = im_dim.repeat(output.size(0), 1)
            output[:,[1,3]] *= frame.shape[1]
            output[:,[2,4]] *= frame.shape[0]

            classes = load_classes('data/coco.names')
            colors = pkl.load(open("pallete", "rb"))

            list(map(lambda x: write(x, orig_im), output))

            cv2.imshow("frame", orig_im)
            key = cv2.waitKey(1)
            if key & 0xFF == ord('q'):
                break
            frames += 1
            print("FPS of the video is {:5.2f}".format( frames / (time.time() - start)))

        else:
            break
```