画像認識で自作データのサイズ加工

こんにちは．
初記事なので，ざっくりです．
練習も兼ねて書いてみようと思いました．

画像認識やる際に一番問題なのって元の画像データじゃないですか．
それをサイズそろえたり，枚数が少なければ水増ししたり，ぼやかしたり，，，

ってなわけで，誰にでもできる方法で，自分の備忘録としてもまとめるぞ！！！

また，今回の目的はLEDで表示される文字を認識するという目的で行います．
なぜなら，私の研究テーマだからです．

一眼レフカメラで電光掲示板を撮影してきた画像をPC上に表示させて，
それをまた様々な角度から
撮影するという古典的な方法を取りましたが，
先にこの水増し方法を知っていれば要らない作業だったかもしれません．
ですが，研究にこういった無駄はつきものなのでこのまま付き進めていきます．




#目次
1. 画像のサイズ合わせ
2. 画像の水増し
3. データセット作成


# １．画像サイズ合わせ
まずカメラを使って文字を撮影しました．
新宿の「新」と「宿」をそれぞれ100枚撮りました．
一例を載せます．
![sfjhk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256435/8cd2eebb-688e-941e-1aad-4388ff6df76a.png)



ここから周りの余計なところを取り除きながらなんとなく正方形ぽくします．
しかしながらwindows標準のペイントなど，通常jpgを加工すればするほど
画質は落ちていってしまうので，以下のようなフリーソフトを使いました．

[JPEGの画質を劣化させずにトリミングするソフト](https://forest.watch.impress.co.jp/library/software/buff/)

手作業で大変ではありますが，片手間にやればすぐ終わります．
![kakakak.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256435/3e7d08b9-1b56-2c8d-0371-4d8dbc4063c6.png)


ここからサイズが大きすぎるのと，ぴったり縦横を1:1にするための作業をします

```python:resize.py
import os
import glob
from PIL import Image

files = glob.glob('./shin/*.jpg')

for f in files:
    img = Image.open(f)
    img_resize = img.resize((300 ,300), Image.LANCZOS)
    ftitle, fext = os.path.splitext(f)
    img_resize.save(ftitle + '_300' + fext)
```
データ構造は以下の通りです

```
data
  |――shin
  |――shin_300
  |
  |――resize.py

```

# 2.画像の水増し
参考にさせて頂いたURLはこちら
[Keras の ImageDataGenerator を使って学習画像を増やす](http://pynote.hatenablog.com/entry/keras-image-data-generator#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9)
[画像ファイルからの機械学習用データセット作成を、OpenCVとnumpyを用いて行う方法](https://qiita.com/ufoo68/items/69b855b3b757c65ac139)
[自前のDeep Learning用のデータセットを拡張して水増しする](https://qiita.com/halspring/items/7692504afcba97ece249)

Kerasを使って簡単に回転やせん断，縦横にずらした画像をつくりましょうという目的です．

```python:dataset_generator.py
import os
import glob
import numpy as np
from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img

def draw_images(generator, x, dir_name, index):
    # 出力ファイルの設定
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir, save_prefix=save_name, save_format='jpg')

    # 1つの入力画像から何枚拡張するかを指定
    # g.next()の回数分拡張される
    for i in range(10):
        bach = g.next()


if __name__ == '__main__':

    # 出力先ディレクトリの設定
    output_dir = "./extend/shin"
    if not(os.path.exists(output_dir)):
        os.mkdir(output_dir)

    # 拡張する画像群の読み込み
    images = glob.glob(os.path.join('./shin_300', "*.jpg"))

    # 拡張する際の設定
    generator = ImageDataGenerator(
                    rotation_range=45, # 90°まで回転
                    width_shift_range=0.1, # 水平方向にランダムでシフト
                    height_shift_range=0.1, # 垂直方向にランダムでシフト
                    #channel_shift_range=50.0, # 色調をランダム変更
                    shear_range=0.39, # 斜め方向(pi/8まで)に引っ張る
                    brightness_range=[0.3, 1.0] #明度を変える　0が暗く1が明るく
                    #horizontal_flip=True, # 垂直方向にランダムで反転
                    #vertical_flip=True # 水平方向にランダムで反転
                    )

    # 読み込んだ画像を順に拡張
    for i in range(len(images)):
        img = load_img(images[i])
        # 画像を配列化して転置a
        x = img_to_array(img)
        x = np.expand_dims(x, axis=0)
        # 画像の拡張
        draw_images(generator, x, output_dir, i)
```


# 3.データセット作成

[画像認識で「綾鷹を選ばせる」AIを作る](https://qiita.com/tomo_20180402/items/e8c55bdca648f4877188)
あとは綾鷹先輩(勝手にそう呼んでいます)の記事を参考にしているんですが，
学習用とテスト用にデータを分けることと，
1ofK符号化法を実現するためにto_categorical()関数を使っている点に注意です．

1ofK符号化法とは
例えばクラスが0~9あったときに
２というクラスは
[0 0 1 0 0 0 0 0 0 0]
みたいな感じでベクトル化するといった作業が必要になります

```python:test.py
#ラベリングによる学習/検証データの準備

from PIL import Image
import os, glob
import numpy as np
import random, math

#画像が保存されているルートディレクトリのパス
root_dir = "./extend"
# フォルダ名
categories = ["shin","juku"]

# 画像データ用配列
X = []
# ラベルデータ用配列
Y = []

#画像データごとにadd_sample()を呼び出し、X,Yの配列を返す関数
def make_sample(files):
    global X, Y
    X = []
    Y = []
    for cat, fname in files:
        add_sample(cat, fname)
    return np.array(X), np.array(Y)

#渡された画像データを読み込んでXに格納し、また、
#画像データに対応するcategoriesのidxをY格納する関数
def add_sample(cat, fname):
    img = Image.open(fname)
    img = img.convert("L") #grayscale読み込みのはず
    data = np.asarray(img)
    X.append(data)
    Y.append(cat)

#全データ格納用配列
allfiles = []

#カテゴリ配列の各値と、それに対応するidxを認識し、全データをallfilesにまとめる
for idx, cat in enumerate(categories):
    image_dir = root_dir + "/" + cat
    files = glob.glob(image_dir + "/*.jpg")
    for f in files:
        allfiles.append((idx, f))

#シャッフル後、学習データと検証データに分ける 8割学習用
random.shuffle(allfiles)
th = math.floor(len(allfiles) * 0.8)
train = allfiles[0:th]
test  = allfiles[th:]
X_train, Y_train = make_sample(train)
X_test, Y_test = make_sample(test)
xy = (X_train, X_test, Y_train, Y_test)
#データを保存する（データの名前を「shinjuku_data.npy」としている）
np.save("shinjuku_data.npy", xy)

#データの準備
import numpy as np
from keras.utils import np_utils


categories = ["shin","juku"]
nb_classes = len(categories)

x_train, x_test, y_train, y_test = np.load("./shinjuku_data.npy")

x_train = x_train.reshape(1600, 300, 300, 1)
x_test = x_test.reshape(400, 300, 300, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
num_classes = 2
y_train = np_utils.to_categorical(y_train, num_classes)
y_test = np_utils.to_categorical(y_test, num_classes)

#モデル準備
import numpy as np
np.random.seed(1)
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.optimizers import Adam
import time

model = Sequential()
model.add(Conv2D(8, (3, 3), padding='same',
           input_shape=(300, 300, 1), activation='relu'))  # (A)
model.add(Flatten())                                    # (B)
model.add(Dense(2, activation='softmax'))
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(),
              metrics=['accuracy'])
startTime = time.time()
history = model.fit(x_train, y_train, batch_size=200, epochs=10,
                    verbose=1, validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
print("Computation time:{0:.3f} sec".format(time.time() - startTime))

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline


#正答率を確認する関数
def show_prediction():
    n_show = 40
    y = model.predict(x_test)  # (A)
    plt.ﬁgure(2, ﬁgsize=(12, 8))
    plt.gray()
    for i in range(n_show):
        plt.subplot(8, 12, i + 1)
        x = x_test[i, :]
        x = x.reshape(300, 300)
        plt.pcolor(1 - x)
        wk = y[i, :]
        prediction = np.argmax(wk)
        plt.text(250, 250, "%d" % prediction, fontsize=12)
        if prediction != np.argmax(y_test[i, :]):
            plt.plot([0, 299], [1, 1], color='cornflowerblue', linewidth=5)
        plt.xlim(0, 299)
        plt.ylim(299, 0)
        plt.xticks([], "")
        plt.yticks([], "")

show_prediction()
plt.show()
```



```
Train on 1600 samples, validate on 400 samples
Epoch 1/10
1600/1600 [==============================] - 12s 7ms/step - loss: 7.1159 - acc: 0.5119 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 2/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5062 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 3/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5063 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 4/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5062 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 5/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5062 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 6/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5062 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 7/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5063 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 8/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5063 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 9/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5063 - val_loss: 8.4620 - val_acc: 0.4750
Epoch 10/10
1600/1600 [==============================] - 11s 7ms/step - loss: 7.9583 - acc: 0.5062 - val_loss: 8.4620 - val_acc: 0.4750
Test loss: 8.462000122070313
Test accuracy: 0.475
Computation time:113.399 sec

```
正答率は47%でした．低いですね．
![result001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256435/04f3fd0e-bb0e-bf8d-e0b5-8017e909ddbc.png)

「新」は0「宿」は1に対応しているので，誤っているものが青線が引いてあります．
なかのパラメータをいじったり，モデルを変えたりして試行錯誤していこうと思います．
ご一読ありがとうございました！