OpenCVとdarknetを連携させてみた

![predictions1.png](https://qiita-image-store.s3.amazonaws.com/0/105738/e4871488-1682-a997-f38d-420b952c75b4.png)
train1.png: Predicted in 13.032408 seconds.
train: 58%
train: 85%
car: 43%
car: 45%
car: 43%

# １ はじめに
これは、OpenCV Advent Calendar 2016 18日目の記事です。関連記事は[目次](http://qiita.com/advent-calendar/2016/opencv)にまとめられています。
本記事は、一時期その性能だけではなく、名前とWebサイトのデザインで有名になった[darknet](http://pjreddie.com/darknet/)を連携させる方法に関しての記事です。
darknetがどのようなものかは、[この記事](http://ch.nicovideo.jp/akiba-cyberspacecowboys/blomaga/ar1116319)に記載されています。

## 1.1 darknet
darknetは、You only look once (YOLO)　と呼ばれる、高速高性能の物体検出と識別を行うことができるAPIを提供していますが、ライブラリはC言語で記述されており、OpenCVのC++になれた身には、使い勝手が良いとは言えません。そこで今回は、OpenCVからdarknetを呼び出すことを試してみたことをまとめてみました。

## 1.2 連携の仕方
[7日目の記事](http://qiita.com/TaroYamada/items/e3f3d0ea4ecc0a832fac)で取り上げたDlibも、静止画の読み書き、ストリームの扱いは、OpenCVを利用することを前提としており、cv::MatデータとDlibのデータとの変換用のAPIが準備されています。一方darknetでは、OpenCVの低レベルのCの関数を呼び出すことで、それらの機能を実現しています。
今回は、darknetビルド時にOpenCVのオプションを使用せず、OpenCVのMatとdarknetのimage構造体との間でデータの変換を行うことで、双方を行き来できるようにしています。なお、darknetのビルドに仕方や使用方法は、darknetのWebサイトに書いてあり、非常に簡単でビルド時間も短いので、この記事では省略しています。

## 1.3 darknet内部の概略
darknetをビルドすると、そのディレクトリにdarknetのバイナリファイルが作成され、コマンドラインから、使用したい機能と、引数を与えることでプログラムの実行を指示します。

例
./darknet detect cfg/yolo.cfg yolo.weights data/dog.jpg

　　　　detect : 使用する機能
　　　　cfg/yolo.cfg : ネットワーク構成データ
　　　　yolo.weights : 辞書の重み
　　　　data/dog.jpg : ターゲットファイル

GitHubの[darknet.c](https://github.com/pjreddie/darknet/blob/master/src/darknet.c)のソースを見てみると、中身は単なるパーサの機能しか有してなく、与えられてコマンド対応した関数を呼び出しているだけであることが確認できます。

OpenCV　オプションを使用した場合は、
./darknet imtest data/eagle.jpg
が実行し、ウィンドウが表示されることで、そのビルドの可否を判定しているという説明があります。

そこで、imtestの場合を追ってみると、最終的には、image.c に行き付き、このファイルで基本的な画像の扱いが定義されています。また、image構造体は、[image.h](https://github.com/pjreddie/darknet/blob/master/src/image.h)に以下の用に記載されている。

```
typedef struct {
    int h;
    int w;
    int c;
    float *data;
} image;
```

細かい説明は省略するが、image.cの内容を読んでいくとimage構造体は、floatの型であり、データ並びはRGBを想定しており、かつ配列はピクセルごとではなく、チャネルごとになっています。（OpenCVでは、BGRの順でデータが格納されているが、darknetでは、RRR... , GGG..., BBB...の順で配列に格納されています。）

# 2 Matからimageへの変換
下記に変換を試したコードを記載ます。手抜き感満載ですが、気にしないでください。
image.cの402行からの

```
void show_image_cv(image p, const char *name)
```
の関数では、image構造体からIplImageへの変換方法が記載されています。これの反対の手順を行えば、Matからimage構造体への変換ができますが、１画素ごとのアクセスになりますので、ここでは、memcpyを使用しています。また、BGRからRGBに変換には、memcpyの順番を入れ替えています。

```
  cv::Mat im1, im2;
  im1 = cv::imread(argv[1]);

  im1.convertTo(im2, CV_32FC3, 1.0/255);
  std::vector<cv::Mat> tmp;
  cv::split(im2, tmp);
  
  int size = im2.size().width * im2.size().height;
  int fsize = size * sizeof(float);

  image im3 = make_image(im2.size().width, im2.size().height, 3);
  float*p = im3.data;
  memcpy((unsigned char*)p, tmp[2].data, fsize);
  p+= size;
  memcpy((unsigned char*)p, tmp[1].data, fsize);
  p+= size;	
  memcpy((unsigned char*)p, tmp[0].data, fsize);
  save_image(im3, "darknet");

```

## 2.1 動作環境
 ubuntu 14.04 64bit + OpenCV 2.4.13.1
上のサンプルを実行させると、指定したファイルをdarknet.pngで保存します。

## 2.2 Yolo サンプル
本当は、Deeplearningまで動作させたかったのですが、時間切れとなりました。参考までにdarknet単体でのYoloの実行結果の画像を上げておきます。CPUで実行させていますので、処理時間は800x450画素画像で約13秒かかっています。

![predictions2.png](https://qiita-image-store.s3.amazonaws.com/0/105738/c974efa0-7833-1144-3d6a-0622f4c071af.png)
train2.png: Predicted in 13.029776 seconds.
train: 49%
train: 80%

![predictions3.png](https://qiita-image-store.s3.amazonaws.com/0/105738/f2f58719-6e1d-6d1f-a8a3-3fff61d46018.png)
train3.png: Predicted in 13.021452 seconds.
train: 30%
train: 80%

# 3 終わりに
今回も手抜き感満載の内容になりました。実は、Cの関数をC++からの呼び出し方法につまづき、コードの確認に手間取りました。こちらに関しては、メインの[Blog](https://iwaki2009.blogspot.jp/)の方に記載する予定ですので、気になる人はそちらを見てください。

明日は、peisukeさんの最適化手法周りについてです。

以上