Colaboratoryで学習したモデル+CPU環境でハーフタレントの顔識別したときのメモ

# 概要
Colaboratoryで学習したモデルをローカルPCにダウンロードして、GPUの無いローカルPC上で動作させてみました。

# 関連記事
- [Keras+Colaboratoryでハーフタレントの顔識別したときのメモ](https://qiita.com/kiwsdiv/items/d5f6caf0f1a836b1bcf9)
- [顔認識のテストのためにスクレイピングでハーフタレントの画像収集してみた](https://qiita.com/kiwsdiv/items/8aa53e4afaf3b148ca3f)

# 目的
- 顔認識システムを作りたい。
- 過去に試した時の備忘録が消えてしまったので、再度やってみた。今回はメモを残す。

# 動作環境
- Windows10 Home 21H1
- Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz 1.80 GHz
- Python 3.8.6
- tensorflow 2.8.2
- keras 2.8.0
- etc...

私はvenvで環境構築しましたが、condaでもなんでもOKのはずです。tensorflowとkerasのバージョンだけ注意してインストールしました。

# やったこと
## テスト用のデータ準備

## 正規化用の関数
画像を学習時と同様に正規化する関数を定義します。

```python:helper.py
from __future__ import absolute_import
from __future__ import print_function
import numpy as np
import cv2

def normalized(rgb):
    norm=np.zeros((rgb.shape[0], rgb.shape[1], 3),np.float32)

    b=rgb[:,:,0]
    g=rgb[:,:,1]
    r=rgb[:,:,2]

    norm[:,:,0]=cv2.equalizeHist(b)
    norm[:,:,1]=cv2.equalizeHist(g)
    norm[:,:,2]=cv2.equalizeHist(r)

    return norm

def one_hot_it(labels):
    x = np.zeros([360,480,12])
    for i in range(360):
        for j in range(480):
            x[i,j,labels[i][j]]=1
    return x
```
## モデル定義
学習時と同じモデルを定義する関数。
```python:model.py
from keras.layers import Activation, Conv2D, Dense, Flatten, MaxPooling2D
from keras.models import Sequential

def model():
    # CNNで学習するときの画像のサイズを設定（サイズが大きいと学習に時間がかかる）
    ImgSize = (180, 180, 3)

    # モデルの定義
    model = Sequential()
    model.add(Conv2D(input_shape=ImgSize, filters=32, kernel_size=(3, 3), strides=(1, 1), padding="same"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding="same"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding="same"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(256))
    model.add(Activation("sigmoid"))
    model.add(Dense(128))
    model.add(Activation('sigmoid'))

    # 分類したい人数を入れる
    model.add(Dense(len(classes)))
    model.add(Activation('softmax'))

    # コンパイル
    print("Compilingmodel...")
    model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy'])
    print("done")
    model.summary()

    return model

if __name__ == '__main__':
    model = model()
```
## 推論テスト

推論して結果を返す関数を作ります。これもGoogle Colaboratoryと同様です。

```python:predict.py
from model import model
from helper import *
import glob
import time
import numpy as np
from keras.models import load_model

# 事前に設定するパラメータ
model = load_model('models/vgg16_model.hdf5') # load
model.load_weights("weights/weights.best.hdf5")
classes = ['Other', 'Joy', 'Harry', 'Uentsu', 'Raul', 'Yuji']

# CNNで学習するときの画像のサイズを設定（サイズが大きいと学習に時間がかかる）
ImgSize = (180, 180)
input_shape = (180, 180, 3)

# 画像を読み込んで予測する
def img_predict(img):
    # 画像を読み込んで4次元テンソルへ変換
    # normalized
    x = np.uint8(normalized(img))
    x = x[np.newaxis, :, :, :]

    # 指数表記を禁止にする
    np.set_printoptions(suppress=True)

    # 画像の人物を予測
    pred = model.predict(x)[0]

    # 結果を表示する
    """
    print(classes[np.argmax(pred)] + ':{:.2f}'.format(pred[np.argmax(pred)] * 100) + '%')
    print(classes)
    print(pred * 100)
    """
    return pred
```

ここでは、テスト画像をdataフォルダからランダムに選択し、画像を表示後、img_predict 関数に画像データを入力しています。

```python:predict.py
if __name__ == '__main__':

    who   = glob.glob('data/*')
    ih    = np.random.randint(len(who))
    files = glob.glob(who[ih]+'/*')
    i     = np.random.randint(len(files))
    fname = files[i]
    print('Test filename : ' + fname)

    # 画像表示
    orgimg = cv2.imread(fname)
    cv2.imshow("", orgimg)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # Prefict
    start_time = time.time()
    pred = img_predict(orgimg)
    end_time = time.time()
    proctime = end_time - start_time

    print(classes[np.argmax(pred)] + ':{:.2f}'.format(pred[np.argmax(pred)] * 100) + '%')
    print(classes)
    print(pred * 100)
    print('Erapsed time  : {:.4f}'.format(proctime), '[s]')
```

## 実行
コンソール上で `predict.py` 以下を実行すると、
```bash
$ python predict.py
: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found
: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'nvcuda.dll'; dlerror: nvcuda.dll not found
: W tensorflow/stream_executor/cuda/cuda_driver.cc:269] failed call to cuInit: UNKNOWN ERROR (303)
: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:169] retrieving CUDA diagnostic information for host: DESKTOPPC
: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:176] hostname: DESKTOPPC
: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
```

たくさんWarningが表示されますが「特にエラーなど問題があることを示しているわけではないので、無視してよいです。」とのことだったので、無視します。
> CPU で AVX2、FMA 命令が使えるけど、このバイナリはそれを使うようにコンパイルされていないので使えないというメッセージです。特にエラーなど問題があることを示しているわけではないので、無視してよいです。
参考：[tensorflowの動作確認で成功しているのかわからない](https://teratail.com/questions/193036)


テストする対象の画像がdataフォルダからランダムに選択され、ウィンドウが表示されます。

![face_wnd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599506/93ecc1c9-0c99-65b2-67c6-c6aaca10f496.png)

何かキーを押すとウィンドウが閉じて推定に進みます。
```
Test filename : data\Yuji\0b214320-ml_face_132.png
Yuji:99.19%
['Other', 'Joy', 'Harry', 'Uentsu', 'Raul', 'Yuji']
[ 0.04029212  0.6518975   0.00032707  0.10256486  0.01965701 99.18527   ]
Erapsed time  : 0.1875 [s]
```
推定結果と処理時間が表示されます。
ここではユージの画像をちゃんとユージと認識しているようです。推論には0.2秒程度かかっていることが分かります。

# 結論

学習さえできればGPUの無いローカルPC上でも動作することを確認できました。
顔識別を使ったアプリケーションに活用していきたいです。[ソースコード](https://github.com/iwatadive28/Face_Predict_Keras/tree/main/Local)