Raspberry Pi 深層学習で「赤りんご」と「青りんご」を見分ける（Keras・Open CV）

「赤りんご」と「青りんご」を見分けるだけの簡単なものですが、はじめて自分の用意した画像で深層学習をしてみました。

## こんな感じ

[![Deep Learning Object Recognition ](http://img.youtube.com/vi/6zkL_qifX2k/0.jpg)](https://www.youtube.com/watch?v=6zkL_qifX2k)

[Deep Learning Apple Recognition    - YouTube](https://www.youtube.com/watch?v=6zkL_qifX2k)

「赤りんご」と「青りんご」を見分けます。

![2017-01-07 19_08_25.gif](https://qiita-image-store.s3.amazonaws.com/0/47128/82b215ad-782c-6a75-a02b-f7813863c9cb.gif)

まずは「青りんご」から、確率91.4%と推測しました。

![2017-01-07 19_09_04.gif](https://qiita-image-store.s3.amazonaws.com/0/47128/f93dfe99-8166-4660-bc1e-b7fb17aac8fc.gif)

続いて「赤りんご」、確率91.1%と推測しています。

今回は推測結果が90%以上で「赤りんご」と「青りんご」を判定しています。りんごがない時に（例えば私の丸顔を写した場合）80%の推測がされた際は、判定結果が出ない様にしています。

## 環境

[前回記事](http://qiita.com/PonDad/items/c5419c164b4f2efee368)と同様です。

## 学習の流れ

1. Web上から画像をダウンロードする
2. ダウンロードした画像を`NumPy`配列にし、訓練データとテストデータに分ける
3. 畳み込みニューラルネットワークで学習する
4. OpenCVで撮影した画像を`NumPy`配列にし推測データとして使用する

基本的な流れは、書籍「 [Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみよう - クジラ飛行机(著)](https://www.amazon.co.jp/exec/obidos/ASIN/4802610793/pondadamazoz-22/ref=nosim/) 」の第７章「7-3牛丼屋のメニューを画像判定しよう」を参考にさせて頂きました。

1〜3はMacで行い、4はRaspberryPiで行います。さて、少し詳しくまとめてみます。

## 1. Web上から画像をダウンロードする

![green_apple 2017-01-07 22-01-24.png](https://qiita-image-store.s3.amazonaws.com/0/47128/866a7e2d-e20d-08b3-41d2-c146abae7357.png)

Web上からのクローリングはこちらのコードを利用させて頂きました。

[TomoProg/HatenaBlog/python/web_crawler.py - GitHub](https://github.com/TomoProg/HatenaBlog/blob/master/python/web_crawler.py)

クローリング先に負担をかけない様に3秒おきに実行するようになっています。特に画像の大きさの変更などは行わず`red_apple`フォルダに100枚、`green_apple`フォルダに100枚保存しました。

一部りんごが複数あったり学習に向かないものは手作業で削除しました。

## 2. ダウンロードした画像を`NumPy`配列にし、訓練データとテストデータに分ける

ダウンロードした画像は今回は32×32ピクセルに縮小して畳み込みニュラルネットワークで学習させます。

![winter1516_lecture7.pdf 2017-01-07 22-30-07.png](https://qiita-image-store.s3.amazonaws.com/0/47128/42bcf948-5d90-0c58-8ac7-a2a595844c34.png)

[CS231n: Convolutional Neural Networks for Visual Recognition, Lecture7, p16](http://cs231n.stanford.edu/slides/winter1516_lecture7.pdf)

ちょうどこの図が同じ大きさなので引用させて頂きます。カラー画像(RGB3チャンネル)を維持したまま学習させなければなりません。

最初ここで躓いてしまったんですが、ここでカラー画像の3チャンネルを1チャンネルにしてしまうと、カラー画像の特徴量が消えてしまうんですね。

そこで、RGB３チャンネルを維持したまま配列に格納する必要があります。今回は画像が200枚あるので、200×32×32×3の４次元配列にしなければならないという訳です。

[apple_makedata.py - Gist](https://gist.github.com/PonDad/e47ebc44f84cb6aae46e63e0028c437b#file-apple_makedata-py)

上述の書籍では、`enumerate()`メソッドを使って`for`ループで画像データを格納していく方法が紹介されていました。

サンプルでは`PIL`を利用して`NumPy`配列に変換していましたが、`Keras`のメソッドで`keras.preprocessing.image`という便利なものがあるので、今回はそちらを使いました。

格納後の`NumPy`配列はこんな風になっています。

```bash
[[[[ 255.  255.  255.]
   [ 255.  255.  255.]
   [ 255.  255.  255.]
   ..., 
   [ 255.  255.  255.]
   [ 255.  255.  255.]
   [ 255.  255.  255.]]

   略

  [[ 255.  255.  255.]
   [ 255.  255.  255.]
   [ 255.  255.  255.]
   ..., 
   [ 255.  255.  255.]
   [ 255.  255.  255.]
   [ 255.  255.  255.]]]]
```
4次元配列になっているのが分かると思います。

これを`scikit-learn`の`cross_validation`メソッドを利用して訓練データとテストデータに分けます。今回は訓練ラベル、テストラベルは`red_apple`と `green_apple`の2種類だけです。

ここでは一旦分けた訓練データ、テストデータ、訓練ラベル、テストラベルを`apple.npy`というファイルにしています。

## 3. 畳み込みニューラルネットワークで学習する

さて、学習する前にもう一つ事前準備が必要になります。

[apple_keras.py - Gist](https://gist.github.com/PonDad/e47ebc44f84cb6aae46e63e0028c437b#file-apple_keras-py)

`float`型に変換したのち、RGBの色調(0〜255)を正規化します。

```bash
[[[[ 0.9921875   0.9921875   0.9921875 ]
   [ 0.99609375  0.99609375  0.99609375]
   [ 0.9921875   0.9921875   0.9921875 ]
   ..., 
   [ 0.99609375  0.99609375  0.99609375]
   [ 0.99609375  0.99609375  0.99609375]
   [ 0.99609375  0.99609375  0.99609375]]

   略

  [[ 0.9921875   0.984375    0.99609375]
   [ 0.9921875   0.984375    0.99609375]
   [ 0.99609375  0.98828125  0.99609375]
   ..., 
   [ 0.99609375  0.98828125  0.99609375]
   [ 0.99609375  0.98828125  0.9921875 ]
   [ 0.99609375  0.98828125  0.9921875 ]]]]
```

これにより推測結果を割合で得ることが出来ます。（詳細後程）

さて、これで準備はOKです。`Keras`のメイン開発者（たまに日本語で呟くイカした人）のサンプルコードを利用して畳み込みニューラルネットワークで学習させます。

[fchollet/keras/examples/cifar10_cnn.py](https://github.com/fchollet/keras/blob/master/examples/cifar10_cnn.py)

今回は画像が単純なこともあり`nb_epoch=10`で設定しました。学習結果はiMacで10秒程です。

![figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/47128/06b1bc55-2f8a-7fc0-0753-d25a82a1ddc7.png)

![figure_2.png](https://qiita-image-store.s3.amazonaws.com/0/47128/e769f0ac-83c8-fd56-1a4a-50ff68601486.png)

```bash
$ python3 apple_keras.py
Using TensorFlow backend.
loss= 6.48597096733e-05
accuracy= 1.0
```
正解率100%になりました。まあ、今回は赤と緑のりんごを分けるだけなので、間違えようもないのかなと思います。ちょっと不安になりますが、過学習も起きていないのでこのまま進めます。

学習結果を`h5`ファイルに保存できたら、MacからRaspberryPiにデータを移動します。

## 4. OpenCVで撮影した画像を`NumPy`配列にし推測データとして使用する

今回は、RaspberryPiで撮影した画像を、学習した時と同じように`NumPy`配列に変換することで、推測データとして利用することにします。

[apple_checker_cv.py](https://gist.github.com/PonDad/e47ebc44f84cb6aae46e63e0028c437b#file-apple_checker_cv-py)

「青りんご」を撮影した画像はこのような`NumPy`配列に格納されます。

```bash
[[[[ 0.0625      0.078125    0.09375   ]
   [ 0.140625    0.05859375  0.0859375 ]
   [ 0.203125    0.1328125   0.11328125]
   ..., 
   [ 0.9140625   0.984375    0.96875   ]
   [ 0.9140625   0.9765625   0.97265625]
   [ 0.84765625  0.93359375  0.96875   ]]

  　　略

  [[ 0.2734375   0.25390625  0.390625  ]
   [ 0.51953125  0.15625     0.2578125 ]
   [ 0.0390625   0.0390625   0.0390625 ]
   ..., 
   [ 0.          0.0078125   0.00390625]
   [ 0.015625    0.01171875  0.0390625 ]
   [ 0.09765625  0.0859375   0.1171875 ]]]]
```

これを学習済みデータを元に、`Keras`の`model.predict(X)`メソッドを使いラベルを推測します。

```bash
$ python3 apple_checker_cv.py
Using TensorFlow backend.
[[ 0.00830923  0.99169075]]
青りんご
```

今回ラベルは２種類だけ`["赤りんご", "青りんご"]`なので、学習結果は

```bash
"赤りんご" = [1, 0]
"青りんご" = [0, 1]
```
の２次元配列で判定出来ます。学習させる時に`softmax`関数を利用しているので、推測結果が正規化された形で返ってきます。

青りんごを撮影した際は`[[ 0.00830923  0.99169075]]`こんな配列が返ってきます。「赤りんご0.8%」、「青りんご99.1%」と推測出来るわけですね。

これを利用して推測した値が0.9以上であれば「赤りんご」「青りんご」であると判定する様にしてみました。前回と同じくOpenJTalkで話させて完成です。

## まとめ

画像の大きさを32ピクセル、枚数を100枚ということで、本当に特徴量が抽出出来るのか謎だったのですが、対象が単純だったこともあり予想以上の精度で分類・認識することが出来ました。

同じパターンで顔識別も出来そうなので、今度試してみたいと思います。ではまた。