作成したCNNモデルを使用して、リアルタイムでマスクの有無を判定

f:id:hanamichi_sukusuku:20210305175648p:plain

f:id:hanamichi_sukusuku:20210305175715p:plain

実行結果

マスクあり

f:id:hanamichi_sukusuku:20210305181606p:plain


マスクなし

f:id:hanamichi_sukusuku:20210305182026p:plain

 

hanamichi-sukusuku.hatenablog.com

このプログラムでは、上記で作成したモデルを使用してリアルタイムのマスクあり、なしの判定を行っていく。

 

必要な変数の定義とモデル読み込み、dlibのインスタンス生成

import keras
import cv2, dlib, pprint, os
import numpy as np
from keras.models import load_model

# 結果ラベル
res_labels = ['NO MASK!!', 'ok']
save_dir = "./live"

# 保存した学習データを読む --- (※1)
model = load_model('mask_model.h5')

# Dlibをはじめる --- (※2)
detector = dlib.get_frontal_face_detector()
 
red = (0,0,255)
green = (0, 255, 0)
fid = 1
cap = cv2.VideoCapture(0)

 

cv2.VideoCapture()で動画処理のためにVideoCaptureオブジェクトを生成。内臓カメラからのデータを取得するために引数に0を指定。USBなどで追加のカメラを接続する場合は0から順番に番号が割り当てられる。

 

while文でカメラから取得した画像データを繰り返し読み込み処理していく

ok, frame = cap.read()
if not ok: break
# 画像を縮小表示する --- (※5)
frame = cv2.resize(frame, (500,300))
# 顔検出 --- (※6)
dets = detector(frame, 1)

cap.read()でカメラから取得した画像データを取得。

返り値はbool型のオブジェクト(取得できればTrue、できなければFalse),画像データの二つが返されるためif文で変数okがFalseであれば処理を中止するようにしている。

 

dlibを使用して顔検出を行う。第二引数の1は顔と認識する最小領域を指定している。1は40✖️40を最小領域として指定。

 

ウィンドウに描画する内容を記述

f:id:hanamichi_sukusuku:20210305185535p:plain



enumerate(dets)で顔検出した座標データを元に画像に枠とテキストを描画していく。

pprint.pprint()では複数の配列を改行し、見やすくして表示してくれる。

x1~y2にそれぞれの座標を格納。

frame[y1:y2, x1:x2]で顔の部分を切り取る。

入りとったデータをcv2.resize()でモデルが学習できるサイズに変換し、reshape()で三次元の配列に変換することでCNNでのモデルで扱えるようにする。

変換した画像データをmodel.predict()で予測。

argmax()で最も大きな値を持つインデックス番号を取得。

 

if文でv == 1の時、つまりマスクをしている時、描画する枠の色をグリーン、ボーダーの太さを2、マスクをしていない時、枠の色を赤、ボーダーの太さを7にするための変数を定義している。

 

cv2.rectangle()で顔検出した座標に枠を描画。

cv2.putText()でテキストを描画。引数には9個の引数を指定することができる。

第一引数から

  1. cv2.imread()で読み込んだ画像データ
  2. 書き込む文字列
  3. 書き込む位置(文字列の左下の角が指定の位置に配置される。
  4. フォントスタイル(今回はcv2.FONT_HERSHEY_SIMPLEXを指定している。
  5. フォントサイズ
  6. フォントの色
  7. フォントの太さ
  8. ラインタイプ

第三引数の位置の指定ではy1から-7を引くことで枠の大きさより上にテキストを表示できる。

 

画像の保存

f:id:hanamichi_sukusuku:20210305191205p:plain

len(dets) > 0はつまり顔検出できた配列が存在する時に以下の処理を実行するようにしている。

cv2.imwrite()で任意のファイルに画像データを保存。

 

ウィンドウに表示

cv2.imshow('Mask Live Check', frame)

cv2.imshow()でウィンドウを表示し、描画した画像を表示する。第一引数はウィンドウの名前なので任意のもの。

 

任意のキーが入力された時、処理を中断する

k = cv2.waitKey(1) # 1msec確認
if k == 27 or k == 13: break

cv2.waitKey()でキーボードからの入力を処理する。引数は指定した数値ミリ秒の間入力を受け付けるというもの。

escは27、enterキーは13となるのでescとenterキーが押されると処理を中断するようにしている。

 

カメラを閉じ、開いているウィンドウを全て閉じる

cap.release() # カメラを解放
cv2.destroyAllWindows() # ウィンドウの破棄

 

 

作成したCNNモデルを使用して、リアルタイムでマスクの有無を判定

f:id:hanamichi_sukusuku:20210305175648p:plain

f:id:hanamichi_sukusuku:20210305175715p:plain

実行結果

マスクあり

f:id:hanamichi_sukusuku:20210305181606p:plain


マスクなし

f:id:hanamichi_sukusuku:20210305182026p:plain

 

hanamichi-sukusuku.hatenablog.com

このプログラムでは、上記で作成したモデルを使用してリアルタイムのマスクあり、なしの判定を行っていく。

 

必要な変数の定義とモデル読み込み、dlibのインスタンス生成

import keras
import cv2, dlib, pprint, os
import numpy as np
from keras.models import load_model

# 結果ラベル
res_labels = ['NO MASK!!', 'ok']
save_dir = "./live"

# 保存した学習データを読む --- (※1)
model = load_model('mask_model.h5')

# Dlibをはじめる --- (※2)
detector = dlib.get_frontal_face_detector()
 
red = (0,0,255)
green = (0, 255, 0)
fid = 1
cap = cv2.VideoCapture(0)

 

cv2.VideoCapture()で動画処理のためにVideoCaptureオブジェクトを生成。内臓カメラからのデータを取得するために引数に0を指定。USBなどで追加のカメラを接続する場合は0から順番に番号が割り当てられる。

 

while文でカメラから取得した画像データを繰り返し読み込み処理していく

ok, frame = cap.read()
if not ok: break
# 画像を縮小表示する --- (※5)
frame = cv2.resize(frame, (500,300))
# 顔検出 --- (※6)
dets = detector(frame, 1)

cap.read()でカメラから取得した画像データを取得。

返り値はbool型のオブジェクト(取得できればTrue、できなければFalse),画像データの二つが返されるためif文で変数okがFalseであれば処理を中止するようにしている。

 

dlibを使用して顔検出を行う。第二引数の1は顔と認識する最小領域を指定している。1は40✖️40を最小領域として指定。

 

ウィンドウに描画する内容を記述

f:id:hanamichi_sukusuku:20210305185535p:plain



enumerate(dets)で顔検出した座標データを元に画像に枠とテキストを描画していく。

pprint.pprint()では複数の配列を改行し、見やすくして表示してくれる。

x1~y2にそれぞれの座標を格納。

frame[y1:y2, x1:x2]で顔の部分を切り取る。

入りとったデータをcv2.resize()でモデルが学習できるサイズに変換し、reshape()で三次元の配列に変換することでCNNでのモデルで扱えるようにする。

変換した画像データをmodel.predict()で予測。

argmax()で最も大きな値を持つインデックス番号を取得。

 

if文でv == 1の時、つまりマスクをしている時、描画する枠の色をグリーン、ボーダーの太さを2、マスクをしていない時、枠の色を赤、ボーダーの太さを7にするための変数を定義している。

 

cv2.rectangle()で顔検出した座標に枠を描画。

cv2.putText()でテキストを描画。引数には9個の引数を指定することができる。

第一引数から

  1. cv2.imread()で読み込んだ画像データ
  2. 書き込む文字列
  3. 書き込む位置(文字列の左下の角が指定の位置に配置される。
  4. フォントスタイル(今回はcv2.FONT_HERSHEY_SIMPLEXを指定している。
  5. フォントサイズ
  6. フォントの色
  7. フォントの太さ
  8. ラインタイプ

第三引数の位置の指定ではy1から-7を引くことで枠の大きさより上にテキストを表示できる。

 

画像の保存

f:id:hanamichi_sukusuku:20210305191205p:plain

len(dets) > 0はつまり顔検出できた配列が存在する時に以下の処理を実行するようにしている。

cv2.imwrite()で任意のファイルに画像データを保存。

 

ウィンドウに表示

cv2.imshow('Mask Live Check', frame)

cv2.imshow()でウィンドウを表示し、描画した画像を表示する。第一引数はウィンドウの名前なので任意のもの。

 

任意のキーが入力された時、処理を中断する

k = cv2.waitKey(1) # 1msec確認
if k == 27 or k == 13: break

cv2.waitKey()でキーボードからの入力を処理する。引数は指定した数値ミリ秒の間入力を受け付けるというもの。

escは27、enterキーは13となるのでescとenterキーが押されると処理を中断するようにしている。

 

カメラを閉じ、開いているウィンドウを全て閉じる

cap.release() # カメラを解放
cv2.destroyAllWindows() # ウィンドウの破棄

 

 

CNNでマスクつけてるかどうかの画像判定

f:id:hanamichi_sukusuku:20210304102751p:plain

f:id:hanamichi_sukusuku:20210304102815p:plain

実行結果

f:id:hanamichi_sukusuku:20210304102837p:plain

このプログラムではCNNモデルを使用してマスクをつけている画像かそうでない画像かの判定を行う。

 

画像形式の指定

in_shape = (50, 50, 3)
nb_classes = 2

 

in_shapeは入力値。

nd_classは出力値。

 

モデル構築

model = Sequential()
model.add(Conv2D(32,
  kernel_size=(3, 3),
  activation='relu',
  input_shape=in_shape))
model.add(Conv2D(32, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_classes, activation='softmax'))

# モデルをコンパイル --- (※3)
model.compile(
  loss='categorical_crossentropy',
  optimizer=RMSprop(),
  metrics=['accuracy'])

 

畳み込み、畳み込み、プーリング、ドロップアウトを2回繰り返し、平滑化、全結合層、ドロップアウト、出力層、という流れでCNNモデルの定義し、コンパイルする。

 

read_files()関数を呼び出し、学習用、テスト用のデータに変換

read_files("imageset/mask_off/*.jpg", [1,0])
read_files("imageset/mask_on/*.jpg", [0,1])
x_train, y_train = (np.array(x), np.array(y))
# テスト用の画像をNumpy形式で得る
x, y = [, ]
read_files("imageset/mask_off_test/*.jpg", [1,0])
read_files("imageset/mask_on_test/*.jpg", [0,1])
x_test, y_test = (np.array(x), np.array(y))

テスト用の画像データをread_filesで処理する際、x,y = [,]と定義し直しているのは中身を空にするため。

それぞれ最後にnumpy形式に変換し学習用、テスト用の変数に格納。

 

read_files()関数、画像データとラベルデータに分割し新たな配列を作成

f:id:hanamichi_sukusuku:20210304104721p:plain

引数に画像データが格納されているパスとラベルデータを渡す。

opencvでデータを読み込んで、画像データ、ラベルデータを空のリストに追加していく。

 

学習

f:id:hanamichi_sukusuku:20210304105000p:plain

 

テストデータを評価しモデルの保存

score = model.evaluate(x_test, y_test, verbose=1)
print("正解率=", score[1], 'loss=', score[0])
# モデルを保存 --- (※6)
model.save('mask_model.h5')

 

学習の様子を描画

plt.plot(hist.history['accuracy'])
plt.plot(hist.history['val_accuracy'])
plt.title('Accuracy')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

 

 

 

 

 

Dlibを使用して顔検出 python

f:id:hanamichi_sukusuku:20210303201544p:plain

実行結果

f:id:hanamichi_sukusuku:20210303201612p:plain

captureディレクトリの画像データから顔検出して、faceディレクトリに検出した顔画像データを保存している。

 

DlibとはC++言語で書かれた汎用目的のクロスプラットフォームのライブラリで機械学習、画像処理、データマイニングなど幅広い分野の処理が可能。今回は顔検出に利用するが、opencvを用いた方法よりも誤検出が少ないのが特徴。

 

Dlibの顔検出に使用するインスタンス生成

detector = dlib.get_frontal_face_detector()

get_frontal_face_detector()を実行することでインスタンスを生成できる。

 

captureディレクトリに用意した画像ファイル名を全て取得し、顔検出を行う

files = glob.glob(indir+"/*")
for f in files:
 print(f)
 get_face(f)

get_face()関数に画像ファイル名を渡すことで、その画像から顔検出を行う。

 

get_face()関数、dlibのインスタンスを利用して顔検出を実行

f:id:hanamichi_sukusuku:20210303203516p:plain

golbal fidで保存するファイル名の変数をグローバル変数にする。今回は1000を定義しているので処理を行うごとにファイル名が1増えていく。

cv2.imread()で画像データ読み込み。

if flag_resize: では扱う画像データがデジカメなどの大きいものを使用する場合サイズをリサイズするための記述。今回はflag_resizeにFalseを定義しているので実行されない。

detector(img, 1)ではdlibのインスタンスを利用して顔の輪郭の座標を取得している。第一引数には顔検出する画像データ、第二引数には検出する最小領域のパラメーターを指定。デフォルトは0で、この場合80✖️80を最小領域として検出を行う。1の場合は40✖️40、2の場合は20✖️20となる。

pprint.pprintを使用すると、要素ごとに改行して見やすく表示してくれる。

dlibのインスタンスを利用して取得した座標データ(dets)の要素には左端からの座標、一番上からの座標、右側の座標、一番下からの座標が格納されており、left()、top()...とすることでそれぞれの値を取得できる。

img[y1:y2, x1:x2]それぞれ検出した顔の座標を元の画像から切り抜く。

try~except文でtryに例外が起きるかもしれないが実行したい処理、exceptに例外時の処理を記述。tryでは画像を50✖️50の画像データに変換している。exceptではcontinueで処理を飛ばしている。

最後にcv2.imwrite()が検出した画像データを保存し、次のファイル名に使用する変数fidの値を1加えて終了。

 

 

 

 

 

 

オリジナルの写真を作成したCNNモデルで判定

f:id:hanamichi_sukusuku:20210302165245p:plain

実行結果

f:id:hanamichi_sukusuku:20210302165325p:plain

上手く出力できた。

 

プログラムを見ていく。

 

必要な変数の定義

im_rows = 32 # 画像の縦ピクセルサイズ
im_cols = 32 # 画像の横ピクセルサイズ
im_color = 3 # 画像の色空間
in_shape = (im_rows, im_cols, im_color)
nb_classes = 3

LABELS = ["寿司", "サラダ", "麻婆豆腐"]
CALORIES = [588, 118, 648]

im_rows、im_clos、im_colorには使用する画像の情報を定義。

in_shapeには入力層で使用する入力を定義。

今回使用するラベルデータは0,1,2の順番で寿司、サラダ、麻婆豆腐なのでLABELSにはその順番になる様に定義。

 

保存したCNNモデルの読み込み

model = cnn_model.get_model(in_shape, nb_classes)
model.load_weights('./image/photos-model.hdf5')

hanamichi-sukusuku.hatenablog.com

cnn_modelは上記リンクで作成したCNNのモデルを構築するための関数を使用するために冒頭でインポートしている。cnn_model.pyのget_model()関数に入力値、出力値を引数に渡すことでCNNモデルを返り値として受け取ることができる。

hanamichi-sukusuku.hatenablog.com

model.load_weightsで上記で作成した重みデータを取得。

 

テストしたい画像で関数の実行

if __name__ == '__main__':
 check_photo_str('test-sushi.jpg')
 check_photo_str('test-salad.jpg')

 

check_photo_str()関数

f:id:hanamichi_sukusuku:20210302170944p:plain

この関数ではcheck_photo()関数に画像のパスを渡して返り値として最も確率の高いラベルデータ(idx)とその確率(per)を取得して、モデルの予測結果がどの料理を指しているのかとカロリー数、確率を出力している。

 

check_photo()関数、テスト画像を予測し、呼び出し元に結果を返す

f:id:hanamichi_sukusuku:20210302171611p:plain

Image.open()でPillowを使用して画像を読み込む。

生成したImgaeオブジェクトをconvertで色空間変換。

resize()でサイズ変更。

plt.imshow()でプロットに描画、plt.show()で表示。

 

モデルで使用できる様に、画像データをnumpy形式に変換、三次元配列に変換、255で割ることで0.0~1.0で表現できる様に変換。

model.predict()で変換したデータの結果を予測。

argmax()で予測結果から最も値の大きいインデックス番号を出力。

int()では引数に渡した値を整数に変換して出力する。

 

 

 

 

 

水増しした画像データを使って再度CNNでデータを学習

f:id:hanamichi_sukusuku:20210301140333p:plain

f:id:hanamichi_sukusuku:20210301140357p:plain

実行結果

f:id:hanamichi_sukusuku:20210301140529p:plain

f:id:hanamichi_sukusuku:20210301140545p:plain

hanamichi-sukusuku.hatenablog.com

実行結果としては上記で水増しせずに学習を行った場合の正解率は0.816...ほどだったので少し精度をあげることができた。

 

プログラムを見ていく。

 

必要な変数の定義

import cnn_model
import keras
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
import cv2

# 入力と出力を指定
im_rows = 32 # 画像の縦ピクセルサイズ
im_cols = 32 # 画像の横ピクセルサイズ
im_color = 3 # 画像の色空間
in_shape = (im_rows, im_cols, im_color)
nb_classes = 3

 

im_rows、im_colsでは学習に使用する縦、横のピクセルサイズを指定。

im_colorではRGB色空間を指定。白黒なら1。

in_shapeではCNNのモデルの入力層に渡す入力データ数を定義。

nd_classesは出力数。今回はsushi,salad,tohuの3種類なので3。

 

画像データ読み込み

photos = np.load('image/photos.npz')
x = photos['x']
y = photos['y']

imageディレクトリのphotos.npzはFlickr APIで取得した画像データをnumpy形式に変換したデータが格納されている。

x = photos['x'], y = photos['y']ではnumpy形式でデータを保存するときに配列名をそれぞれx,yと指定したため、['配列名']として取り出すことができる。

 

各データをCNNで学習できるように変換

# 読み込んだデータをの三次元配列に変換
x = x.reshape(-1, im_rows, im_cols, im_color)
x = x.astype('float32') / 255
# ラベルデータをone-hotベクトルに直す
y = keras.utils.to_categorical(y.astype('int32'), nb_classes)

# 学習用とテスト用に分ける
x_train, x_test, y_train, y_test = train_test_split(
x, y, train_size=0.8)

 

画像データ(x)は一つの画像を3つの要素を32列保有した行列を32行で表すことができるのでreshapeで三次元の配列に変換し、astype('float32') でndarrayの配列が保有しているデータ型を変換している。float32で小数点第八位までの値を扱うように指定。小数点第九位の値を四捨五入して第八位の値を決定する。255で割ることで0.0~1.0でデータを表現するようにしている。

 

ラベルデータ(y)ではkeras.utils.to_categorical()でone-hotベクトルに変換する。

train_test_split()で学習用、テスト用にデータを分割。

 

学習データの水増し

f:id:hanamichi_sukusuku:20210301143151p:plain

画像データを回転させ、学習用のデータを水増しする。

for i, xi in enumerate(x_train):

学習データを一つずつ取り出す。

for ang in range(-30, 30, 5):

range()に3つの引数を渡した時は(start, stop, step)の様に指定することでstepの値だけ飛ばした等差数列が生成される。

今回で言えば

(-30, 30, 5)なので[-30,-25, -20, -15. -10, -5, 0, 5, 10, 15, 20, 25]という数列が生成される。この時、start<= i < stopなので気を付ける。

 

cv2.getRotation Matrix2D(回転の中心点, 角度, 拡大比率)でアフィン変換行列を生成。

cv2.warpAffine(元画像データ, アフィン変換行列, ピクセルサイズ)でアフィン変換。

アフィン変換を行ったデータをx_newに追加しデータを作り直していく。

ラベルデータも忘れずに。

 

cv2.flip()はopencvで画像を上下左右に反転させる関数。第一引数に元となるndarrayのデータ、第二引数に反転の方向を指定。

flipcode = 0上下反転、flipcode > 0左右反転、flipcode < 0上下左右反転。0か1か-1を目的に応じて指定すれば良い。今回は1なので左右反転。

反転させた画像もx_newに追加。

 

水増しした画像をnumpy形式に変換して学習用に置き換える

print('水増し前=', len(y_train))
x_train = np.array(x_new)
y_train = np.array(y_new)
print('水増し後=', len(y_train))

 

モデルの読み込みと学習、評価

f:id:hanamichi_sukusuku:20210301150911p:plain

別ファイルに作成したCNNモデルをcnn_modelファイルのget_model()関数を呼び出し、返り値としてCNNモデルを取得。

model.fit()で学習。

modelevaluate()で評価。

 

学習の様子をグラフで表示、モデルの重みデータを保存

# 正解率の推移をプロット
plt.plot(hist.history['accuracy'])
plt.plot(hist.history['val_accuracy'])
plt.title('Accuracy')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

# ロスの推移をプロット
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Loss')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

model.save_weights('./image/photos-model.hdf5')

 

 

 

 

アフィン変換 opencv

アフィン変換とは画像を回転させたり、拡大縮小させたり、平行移動をまとめて3✖️3の行列を使って変換することをアフィン変換という。

opencvのcv2.getRotaionMatrix2D()ではアフィン変換に使用する、画像の回転に必要なアフィン行列を作成している。引数には(画像の中心の座標,回転させたい角度,拡大比率)を指定する。

cv2.getRotationMatrix2D()で作成したアフィン行列をアフィン変換するのが、cv2.warpAffine(元画像, アフィン変換行列, ピクセルサイズ)。

これらを行うことで画像の回転や、拡大縮小などの処理を画像に加えることができる。