リアルタイム物体検出システムdarknetをpythonでサーバ化

# やりたいこと
`darknet` というリアルタイムで物体検出できるオープンソースがあって、
これがけっこうすごい！
これをpythonでサーバ化して、イメージファイルを送ったら、
とりあえず、オブジェクトの名前とスコアが返ってくるようにしたい

`https://pjreddie.com/darknet/yolo/`

上記にチュートリアルとかあるので、まずはそちらをごらんください

# 前提
1. darknetが動作すること
2. ~~python 2系統であること~~　-> python3系統であること(18/08/11)

# 動作環境
- ~~Ubuntu 14.04~~ -> Ubuntu 16.04.4(18/08/11)
- ~~python 2.7.13~~ -> python 3.5.2 
- darknetのリビジョン ~~`1b001a7f58aacc7f8b751332d3a9d6d6d0200a2d`~~ 
   -> `9a4b19c4158b064a164e34a83ec8a16401580850`
    
    最新のソースではバグが出る可能性はあります

# ソースおよび動作方法
## ソース
1. `https://github.com/komorin0521/darknet_server`

## 動作方法
1. `darknet`のチュートリアルを動作させれるようgit cloneして`make`する
    できれば、darknet単独で予測が動くようにしてください
    (チュートリアルで利用する~~`yolo.weights`~~ `yolov3.weights`を利用します）
2. ソースって書いてあるソース一式を`darknet/python`のフォルダにコピーしてください
   (~~`libdarknet.so`と同階層に`darknet_server.py`など全てのファイル・フォルダをおいてください~~）
3. 必要なモジュールを`pip`でインストールしてください
    `(sudo) pip install -r requirements.txt`
    著者は`pyenv + pyenv-virtualenv`環境を利用してます。
    `sudo`権限が必要な場合は、つけてください

4. `PYTHONPATH=${darknetPATH}/python python3 darknet_server.py -cf ./cfg/yolov3.cfg -df ./cfg/coco.data -wf ./yolov3.weights -ud ./upload` を実行してください
   うまくいけけば`darknet`を単独で動作させた場合と同様にネットワークがロードされます

## 確認方法
`curl`で以下のように画像ファイルをアップロードしてください

`curl -XPOST -F file=@./data/person.jpg http://localhost:8080/detect`

jsonで下記のように結果が返ってこればOKです！

```
{
  "result": [
    {
      "name": "dog",
      "score": 0.8622361421585083
    },
    {
      "name": "person",
      "score": 0.8603283762931824
    },
    {
      "name": "horse",
      "score": 0.8156660199165344
    }
  ],
  "status": "200"
}
```

# いろいろと補足
githubの`darknet`のところに、`python/darknet.py`があったので、それを参考にしました。
この中身を見てみると、`config`ファイルと`weight`を`load_net`でロードしていて、`load_meta`にてメタ情報（といってもこの場合は、クラス名に一致するオブジェクト名(`cfg/coco.data`の`names`)を引っ張ってきているようでした。

さらに、ネットワークをロードしておいた上で、`detect`している

また、結果を見てみると、N個の物体検出結果の配列で、
個々の検出結果はどうやら名前、スコア、バウンディングボックスの位置になっているっぽい

上記を前提に~~`yolo.py`~~の`darknet.py`の中に定義した`Yolo`クラスの初期化メソッドで
ネットワークをロードしておいて、
`/detect`が呼ばれたら`detect`を呼ぶようにすればよいのかと思い、そのようにしています。


なお、サーバからのレスポンスについては、記事を書いた当初(2017年10月9日)では、名前とyoloのスコアを返していましたが、2017年10月11日にバウンディングボックスの値も返すようにしました。

また、記事を書いた当初はHOSTやポートをハードコーディングしていましたが
引数で受け取れるように変更してます。

`Flask`も（無駄に？）class化したら、
ちょっとはまったけど、stackoverflowにて解決！
はまったポイントは、FlaskではURIの定義を`@app.route('/hoge')`で定義するけれど、`@self.app.route('/detect')`ってしたら、エラーになった。
解決策を探してみると、どうやら'add_url_rule`（初めて知った！）にてURIを定義すればよいとのこと。
めでたしめでたし。

# 2017/11/1 APIの仕様追加
yolo本家では、確信度はdetectionを行う際に指定していたので、
APIでも指定できるように変更しました。
なお、デフォルト値は、（本家と同様なはずの）0.25にしてます。

`curl -XPOST -F thresh=0.1 -F file=@./data/person.jpg http://localhost:8080/detect`

と、`-F`オプションで指定可能で、値を小さくするとより多くの物体検出結果が帰ってきますが、その分ご認識も多くなるので、ご注意ください。

＃ 2018/08/11 更新
1. python3対応しました。python2とpython3で、ctypesの定義が変わっていたことに気づきました。参考：[darknet yolo v3をpython3(.5.2)で試す ](https://qiita.com/komorin0521/items/460fe61b45ae6d3b7228)
2. yolov3対応.darknet側が更新されたので、現時点で最新版で動作することを確認しました。
3. 予測埋め込み画像の更新しました。完璧ではないですが、予測結果画像を少しyolo本家に似せるように修正しました。
    ![20180811_180947_person_pred.jpg](https://qiita-image-store.s3.amazonaws.com/0/114699/66fc463e-ef01-e004-9c20-0fc105db0466.jpeg)


# 参考
- darknet
 https://pjreddie.com/darknet/yolo/   

- Flask
    * http://flask.pocoo.org/docs/0.12/api/#url-route-registrations
    * https://stackoverflow.com/questions/40460846/using-flask-inside-class
    * https://stackoverflow.com/questions/15421193/using-defaults-with-app-add-url-rule-in-flask
    * https://stackoverflow.com/questions/39985064/view-multipart-form-request-parameter-values-using-flask
 
- curl
    http://www.wagavulin.jp/entry/2015/10/18/060938