WindowsでCaffeを使う：識別

前回は学習による識別器生成について解説しました。
今回はその識別器を用いて画像識別を行います。

#識別ツールのコード
Windows版Caffeでは識別ツールが用意されてないため、以下のように識別のコードを作成しました。

```lang:classify_c_image.cpp
//Copyright 2014 BVLC and contributors.

//This is a simple script that allows one to classify a color scale image by the trained network model.
//Mean image file is optional.
//Usage:
//   classify_c_image layer_prototxt classification_caffe_model cpu_gpu_mode target_image

#include <cuda_runtime.h>
#include <iostream>
#include <cstring>

#include "caffe/caffe.hpp"

using namespace caffe;  // NOLINT(build/namespaces)

int main(int argc, char** argv) {
  ::google::InitGoogleLogging(argv[0]);
  ::google::SetLogDestination(0, "../tmp/");

  //////////////// color image ver//////////////////
  if (argc < 5 ) {
    LOG(ERROR) << "Usage: classify_c_image layer_prototxt classification_caffe_model cpu_gpu_mode target_image [mean_file] ";
    return 1;
  }

  // 各種引数を渡す
  const string layer_deploy = argv[1];
  const string caffe_model = argv[2];
  const string cpu_gpu_mode = argv[3];
  const string target_image = argv[4];

  // CPU,GPUモード選択
  if(cpu_gpu_mode=="GPU")
	  Caffe::set_mode(Caffe::GPU);
  else 
	  Caffe::set_mode(Caffe::CPU);

  //// ソルバファイルでNetを初期化
  Net<float> net(layer_deploy);
  //// モデルファイルを読み込み
  net.CopyTrainedLayersFrom(caffe_model);

  float* image_mean;
  if (argc == 6)
  {
	  const string mean_file = argv[5];
	  //// meanファイルの読み込み
	  BlobProto mean_blobproto;
	  ReadProtoFromBinaryFileOrDie(mean_file, &mean_blobproto);

	  Blob<float> data_mean;
	  data_mean.FromProto(mean_blobproto);

	  const float* mean = data_mean.cpu_data();

	  image_mean = new float[data_mean.count()];
	  for (int i = 0; i < data_mean.count(); ++i) {
		  image_mean[i] = mean[i];
	  }
  }

  //// モデル対応の画像サイズ取得(inputは一つとする)
  vector<Blob<float>*> net_input_blobs = net.input_blobs();
  int model_width = net_input_blobs[0]->width();
  int model_height = net_input_blobs[0]->height();
  int model_channels = net_input_blobs[0]->channels();
  int model_num = net_input_blobs[0]->num();

  //// 画像ファイルの読み込み→Datum→Blob
  Datum* input_image_datum = new Datum();
  // BGR
  ReadImageToDatum(target_image, 0, model_height, model_width, input_image_datum);


  // 最新Caffeの DataTransformer<Dtype>::Transformを参考
  const string& data = input_image_datum->data();
  const int channels = input_image_datum->channels();
  const int height = input_image_datum->height();
  const int width = input_image_datum->width();
  const int size = input_image_datum->channels() * input_image_datum->height() * input_image_datum->width();

  Blob<float> input_blob(1, channels, height, width);
  float* input_blob_data = input_blob.mutable_cpu_data();

  for (int j = 0; j < size; ++j) {
	  float datum_element = static_cast<float>(static_cast<uint8_t>(data[j]));
	  // mean fileあり
	  if (argc == 6)
	  {
		  input_blob_data[j] = (datum_element - image_mean[j]);
	  }
	  // mean fileなし
	  else
	  {
		  input_blob_data[j] = datum_element;
	  }
  }

  // Forwardの形式上vectorに格納
  vector<Blob<float>*> bottom;
  float* loss = new float();
  bottom.push_back(&input_blob);
  
  // 識別(順伝播)
  net.Forward(bottom, loss);

  // 識別結果。出力Blobは一つとする。
  Blob<float> prediction_result;
  vector<Blob<float>*> output_blobs_vec = net.output_blobs();

  std::cout << "prediction result:" << std::endl;
  for(int i = 0; i < output_blobs_vec[0]->channels(); i++)
  {
	  // Blob->data_at 二番目の引数がアウトプットクラスのインデックス。他は全て0で固定。
	  std::cout << i <<"カテゴリ = " << output_blobs_vec[0]->data_at(0, i, 0, 0) << std::endl;
  }

  delete loss;
  delete input_image_datum;

  return 0;
}
```

#識別ツールのビルド
- 上記の`classify_c_image.cpp`を`.\tools\`に配置します。
- `.\examples\MainCaller.cpp`の中身を

```lang:MainCaller.cpp
#include "../../tools/classify_c_image.cpp"
```

とします。

-  ビルドし、`./bin`に吐かれたMainCaller.exeをclassify_c_image.exeにリネームします。本実行ファイルが識別ツールとなります。

#識別ツールの使い方
`classify_c_image.exe [net file] [model file] [mode] [image file] [mean file]`
-[net file]： 識別用のレイヤ定義ファイル(.prototxt)を指定します。
-[model file]： 学習したモデルファイルを指定します。
-[mode]： CPUかGPUを指定します。
-[image file]： 識別対象画像(.jpg等)を指定します。
-[mean file]： 学習に平均画像を使用した場合は指定します。(省略可)

前回学習したcifar10の例なら
`classify_c_image.exe cifar10_full.prototxt cifar10_full_iter_60000 GPU test.jpg mean.binaryproto`
のように実行します。

レイヤ定義ファイルとなるcifar10_full.prototxtは`.\examples\cifar10\`に置いてある、識別用のレイヤ定義ファイルとなります。学習、テストに使用したものと比べて、データレイヤや初期化パラメータがなくなってるだけのものです。

#識別コードの概要
以下ではコードの具体的な概要を解説します。識別ツールを各自が使いやすくカスタマイズできるよう参考になればと思います。


##1. 初期パラメータ設定

- レイヤ定義ファイルでNetクラスインスタンスを初期化します。
レイヤ定義ファイルに記載されたネット構成が読み込まれ、識別を行うメインのクラスインスタンスNetが生成されます。

```　
  //// ソルバファイルでNetを初期化
  Net<float> net(layer_deploy);
```
- モデルファイルをNet::CopyTrainedLayersFromで読み込みます。
これでNetクラスインスタンスにネットワークの重みがモデルファイルから書き込まれます。

```  
  //// モデルファイルを読み込み
  net.CopyTrainedLayersFrom(caffe_model);
```

##2. 平均画像読み込み
- binaryprotoファイルからBlobProto構造体に読み込みます。Caffeの関数ReadProtoFromBinaryFileOrDieを使用します。

```
  BlobProto mean_blobproto;
  ReadProtoFromBinaryFileOrDie(mean_file, &mean_blobproto);
```

- Blob構造体の関数FromProtoでBlobProto構造体を読み込みます。

```
  Blob<float> data_mean;
  data_mean.FromProto(mean_blobproto);
```

- Blog構造体から平均画像の実データを取得します。

```
  const float* mean = data_mean.cpu_data();

  image_mean = new float[data_mean.count()];
  for (int i = 0; i < data_mean.count(); ++i) {
       image_mean[i] = mean[i];
  }
```
##3. 識別対象画像読み込み

- Datum構造体に画像ファイルから読み込みます。先ずNetクラスのinput_blobs関数で規定の入力画像サイズを取得し、Caffe関数ReadImageToDatumで読み込んだ画像ファイルを規定サイズにリサイズして、Datum構造体に格納します。


```
  //// モデル対応の画像サイズ取得(inputは一つとする)
  vector<Blob<float>*> net_input_blobs = net.input_blobs();
  int model_width = net_input_blobs[0]->width();
  int model_height = net_input_blobs[0]->height();

  Datum* input_image_datum = new Datum();

  ReadImageToDatum(target_image, 0, model_height, model_width, input_image_datum);
```

- Datum構造体から画像データに相当する文字列を取得します。

```
  const string& data = input_image_datum->data();
```

- 入力となるBlobの実データメンバに、入力画像と平均画像の差分値を格納します。

```
  Blob<float> input_blob(1, channels, height, width);
  float* input_blob_data = input_blob.mutable_cpu_data();

  for (int j = 0; j < size; ++j) {
      float datum_element = static_cast<float>(static_cast<uint8_t>(data[j]));
      // mean fileあり
      if (argc == 6)
      {
          input_blob_data[j] = (datum_element - image_mean[j]);
      }
      // mean fileなし
      else
      {
          input_blob_data[j] = datum_element;
      }
  }
```

##4. 識別、結果表示
- 入力画像を順伝播します。Caffeでは複数の入力Blobも想定しているため、Blobのvector配列となっています。ここでは入力は一つですがvector配列に入力Blobを格納します。loss変数は学習時に使用するものなのでここでは関数の形式上入れておくだけです。あとはNetクラスのFoward関数で順伝播が行えます。

```
  // Forwardの形式上vectorに格納
  vector<Blob<float>*> bottom;
  float* loss = new float();
  bottom.push_back(&input_blob);

  // 識別(順伝播)
  net.Forward(bottom, loss);
``` 

- 出力Blob配列を取得し、各カテゴリの識別スコアを読み出します。出力も入力同様にBlobのvector配列となっており、出力Blobも一つだけなので0インデックスを指定して出力を取り出します。識別スコアはBlobのchannel次元（二番目の次元）から読み出します。つまり、channel数がカテゴリ数に割り当たります。

```
 // 識別結果。出力Blobは一つとする。
  Blob<float> prediction_result;
  vector<Blob<float>*> output_blobs_vec = net.output_blobs();

  std::cout << "prediction result:" << std::endl;
  for(int i = 0; i < output_blobs_vec[0]->channels(); i++)
  {
      // Blob->data_at 二番目の引数がアウトプットクラスのインデックス。他は全て0で固定。
      std::cout << i <<"カテゴリ = " << output_blobs_vec[0]->data_at(0, i, 0, 0) << std::endl;
  }
```

***
以上で、識別の解説を終わります。これで、Caffeに関する主要な解説は完了となります。

次回以降は補足的な内容を書いていければと思います。