VGG16で画像認識してみた

#はじめに
せっかくPythonを始めたのだから機械学習で画像認識をしてみようと思った。
今回は、画像を集めてCNN(畳み込みニューラルネットワーク)で学習して～という手法は取らずに、VGG16という学習済みのモデルを使って画像認識を行います。
#VGG16とは
全16層の畳み込みニューラルネットワークで、1000クラスを学習したモデルです。
認識できるのは学習した1000クラスに限られます。
1000クラス（犬,猫,...など）以外の、たとえば人間の画像を認識させるとガスマスクと認識したりします（人間だと認識できていない）
#使ったもの
・Python 3.6
・numpy 1.16.4
・tensorflow 1.3.0
・keras 2.2.4
・OpenCV 4.1.0
#やったこと
手順としては以下のようになります。
　　入力画像の前処理 → 学習済みモデルの読み込み → 画像の判定 → 結果の出力
##入力画像の前処理
以下は入力画像のpathを受け取って前処理を行う関数。

```python
import numpy as np
import cv2
from keras.applications.vgg16 import preprocess_input

def img_preprocess(img_path):
    img = cv2.imread(img_path)              #読み込み
    size = (224,224)
    img = cv2.resize(img, size)             #サイズ変更
    img = np.array(img)                     #np.arrayにする
    img = np.expand_dims(img, axis=0)       #入力用に次元を一つ追加する
    img = preprocess_input(img)             #VGG16に入力するための前処理を行う
    return img
```
画像の読み込みはOpenCV(cv2)のimreadで行う。（入力された画像は配列で受け取られる）
VGG16への入力画像サイズは224×224なのでcv2.resizeでサイズを変更。(ここで、imgは(224, 224, 3)の配列となる。最後の3はカラーで入力しているのでRGBの3層あることを示している。)
入力はnp.arrayで行うので変換。preprocess_input()を用いてVGG16に入力するための前処理を行う。

##学習済みモデルの読み込み
VGG16は簡単に呼び出すことができる。

```python
model = VGG16()
```
全結合層を外すか、重みの種類はどうするかなどを選べるが、重みは1種類しか提供されていないし今回はモデルをそのまま使うので全結合層もそのままにする。
最初に実行するときはデータをダウンロードするので時間がかかる。
####学習済みモデルを出力するには
pickleを使う。

```python
import pickle
#モデルの保存
model_filename = 'model.sav'
pickle.dump(model, open(model_filename, 'wb')
#モデルの読み込み
loaded_model =  pickle.load(open(model_filename, 'rb'))
```
##画像の判定
1行でできる！便利！

```python
pred = model.predict(img)
```
predに対してdecode_prediction()を適応する。
decode_predictionしたpredは[(class1, description1, probability1), (class2, description2, probability2), ...]のタプルで与えられる。
top=nと指定すると、probability(確率)が最も高いものからn個のデータを取得できる。

```python
result = decode_predictions(pred, top=1)[0]
```
##コード
main.pyと同じディレクトリにあるimgフォルダから入力画像を取得する。認識結果の出力を整える＆翻訳するといった細かいところまで含んだものを下に示す。

```python:main.py
import utils
import pickle
import numpy as np
from keras.applications.vgg16 import preprocess_input, decode_predictions

#画像のpathを取得
img_path = utils.get_img_path()
#入力画像の前処理
img = utils.img_preprocess(img_path)
#学習済みモデルの読み込み
model_filename = 'model.sav'
model = pickle.load(open(model_filename, 'rb'))
#画像の判定
pred = model.predict(img)
#最もスコアの高いものを取得
result = decode_predictions(pred, top=1)[0]
name_en = result[0][1]
score = round(result[0][2]*100, 1)
#翻訳
name_ja = utils.name_trans(name_en)
print("この画像に映っているのは: {0}({1}) {2}%" .format(name_ja, name_en, score))
print(result)
```

```python:utils.py
import os, glob
import numpy as np
import cv2
from keras.applications.vgg16 import preprocess_input
from requests import get

#入力画像のpathを取得する
def get_img_path():
    path = './img/'
    img_name_arr = os.listdir(path)
    img_name = img_name_arr[0]
    return path + img_name

#入力画像の前処理を行う
def img_preprocess(img_path):
    img = cv2.imread(img_path)              #読み込み
    size = (224,224)
    img = cv2.resize(img, size)             #サイズ変更
    img = np.array(img)                     #np.arrayにする
    img = np.expand_dims(img, axis=0)       #入力用に次元を一つ追加する
    img = preprocess_input(img)             #VGG16に入力するための前処理を行う
    return img

#predictから得られた名前が_(アンダースコア)付きの英単語なので翻訳する
def name_trans(name):
    #'_'を取り除く
    if '_' in name:
        name_arr = name.split('_')
        name = ''
        for i in range(len(name_arr)):
            if i == (len(name_arr) - 1):
                name += name_arr[i]
            else:
                name += (name_arr[i] + " ")
    #GASで作ったAPIで翻訳
    trans_from = 'en'
    trans_to = 'ja'
    trans_url = (
        'https://script.google.com/macros/s/AKfycbzPH7k0wm5QqP78MtGXt2cOZ_dR0X0G-jMxJODwZvQM3hG89Cct/exec'
         + '?text=' + name + '&source=' + trans_from + '&target=' + trans_to
    )
    res = get(trans_url)
    name_ja = res.text
    return name_ja
```

翻訳については前回の記事を参照してください。
[Google Apps Scriptで作った無料翻訳APIを使う](https://qiita.com/umyomyomyon/items/ecf41f0111a65bfe2d65)
#実行してみる
入力する画像はこれ。
![airplane-1554870_960_720.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468937/835f2e1b-c062-3d6a-cb5d-2be6cbe75d6e.jpeg)
####結果
```
この画像に映っているのは: 旅客機(airliner) 84.8%
```
正解！
#おわりに
学習済みモデルを使った画像認識は画像データの用意、データの整理、ラベル付けといっためんどくさい作業がないので非常に楽。
しかもVGG16に関してはkerasが色々やってくれる。
次はVGG16の全結合層を外してFine-Tuningを試してみようと思う。

##参考
[Applications - Keras](https://keras.io/ja/applications/#vgg16)
[Python scikit-learnで機械学習モデルを保存＆ロードする](https://localab.jp/blog/save-and-load-machine-learning-models-in-python-with-scikit-learn/#toc1
)
[KerasでVGG16を使う - 人工知能に関する断創録](http://aidiary.hatenablog.com/entry/20170104/1483535144)