【AI/実践編】WebカメラとAIを連携【衝撃簡単③】

WebカメラとAIを連携

対象読者

AIで何をつくっていいかわからない。そんな人に『衝撃簡単シリーズ』はおすすめです。今回はその3回目です。

前回はCNNを使ってよりディープラーニングらしいものを作りました。今回はそこから少しだけレベルアップしてリアルタイムでカメラに映し出された画像データを分析するということをやっていきます。

もし分からないところがあっても、そのうち絶対分かるので気長に気楽にやっていきましょう!

前前回↑
前回↑
今日のゴール

今日はカメラで数字を認識させて、その数字を学習したモデルに通して数字を当てさせるところをゴールとしてやっていきます。

カメラ分析
CMD

また、今回もこちらのサイト(https://qiita.com/Ka-k/items/b9da86a3dfaac104aa02)を参考に進めさせてもらいます。こちらはAI初心者にはとても参考になるサイトですが、少し古い部分があったりします。なので、本ページでは今風にアレンジしつつ誰でも実装できるように解説していきます。

今日使うコード

これが今日の全コードです。理解のために、コードと説明の順番が上下するので、もし迷ったらこちらを参考にしてください。

Python
import cv2
from keras.models import load_model
import numpy as np

cap = cv2.VideoCapture(0)

while(True):
    ret, frame = cap.read()

    h, w, _ = frame.shape[:3] 

    w_center = w//2 
    h_center = h//2 

    cv2.rectangle(frame, (w_center-71, h_center-71),  
                 (w_center+71, h_center+71),(255, 0, 0)) 
    cv2.imshow("frame",frame) 

    k =  cv2.waitKey(1) & 0xFF
    prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

    if k == ord("q") or (prop_val < 0):
        break
    elif k == ord("s"):
        im = frame[h_center-70:h_center+70, w_center-70:w_center+70] 
        im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 

        _, th = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU) 
        th = cv2.bitwise_not(th)
        th = cv2.GaussianBlur(th,(9,9), 0) 
        cv2.imwrite("capture.jpg", th)
        break

cap.release()
cv2.destroyAllWindows()


#-----------------------------------------


Xt = []
img = cv2.imread("capture.jpg", 0)
img = cv2.resize(img,(28, 28), cv2.INTER_CUBIC)

Xt.append(img)
Xt = np.array(Xt)/255




model = load_model("ai-third.h5")

result = model.predict(Xt)#モデルの予測を抽出
top3_probs = np.sort(result, axis=1)[:, ::-1][:, :3]
top3_classes = np.argsort(result, axis=1)[:, ::-1][:, :3]

# 結果を出力
for i in range(len(top3_probs)):
    print(f"サンプル {i+1} の上位3つのクラスと確率:")
    for j in range(3):
        print(f"クラス {top3_classes[i][j]}: 確率 {top3_probs[i][j]}")

カメラ入力の設定

前提として、今回はMNISTデータで学習したモデルを使います。そのため、学習モデルを適応するには入力をMNISTと同じ形式にする必要があります。ということで、MNISTの形式に直しつつ、カメラの入力設定をやっていきたいと思います。

カメラの映像を映し出す

  • 映し出した映像に正方形の領域を描画する
  • 描画した領域内に手書きの数字を映す
  • 撮影する
  • 撮影した画像の正方形部分(数字が映ってる部分)だけを切り取る
  • 切り取った画像を白黒にする
  • 白黒にした画像の白と黒を反転させる(黒背景に白文字にする)
  • 28×28サイズに圧縮する
カメラを起動させる
Python
import cv2

cap = cv2.VideoCapture(0) 

while(True):
    ret, frame = cap.read()

    cv2.imshow("frame",frame)

    k =  cv2.waitKey(100) & 0xFF

    if k == ord("q"): #qを押したら終了
        break

cap.release()
cv2.destroyAllWindows()

このコードは、OpenCVを使用してウェブカメラからビデオをキャプチャし、フレームをリアルタイムで表示するプログラムです。以下に、コードの各部分の解説をします。
import cv2
#OpenCVライブラリをcv2という名前でインポートします。

cap = cv2.VideoCapture(0)
VideoCaptureクラスを使用して、ウェブカメラからのビデオキャプチャを開始します。引数の0は、デフォルトのカメラデバイスを指定しています。複数のカメラが接続されている場合は、0以外の番号を指定して切り替えることができます。

while(True):
#無限ループを開始します。

ret, frame = cap.read()
cap.read()メソッドを使用して、カメラから1フレームをキャプチャします。retは、フレームの取得が成功したかどうかを示すブール値です。frameは、キャプチャされたフレームの画像データです。

cv2.imshow("frame", frame)
imshow()関数を使用して、キャプチャされたフレームをウィンドウに表示します。第1引数はウィンドウの名前を指定し、第2引数には表示する画像データを指定します。

k = cv2.waitKey(100) & 0xFF
waitKey()関数を使用して、ユーザーからのキーボード入力を待ちます。引数は、入力を待つ時間(ミリ秒)です。ここでは100ミリ秒です。& 0xFFは、64ビットマシンでの処理のために追加されたビットマスクです。

if k == ord("q"):
#ユーザーがキーボードのqを押すと、無限ループを抜けてプログラムを終了します。qキーを押さないと閉じれないので、しっかりとqキーを押しましょう

cap.release()
#カメラリソースを解放します。

cv2.destroyAllWindows()
#すべてのOpenCVウィンドウを閉じます。


キャプチャ機能を追加

sキーを押すと現在のフレームが保存されてプログラムが終了するようにしましょう。

p.s.コメント(#)は既に書いたコードを表します。

Python
# import cv2

# cap = cv2.VideoCapture(0) 
# while(True):
#     ret, frame = cap.read()
#     cv2.imshow("frame",frame)
#     k =  cv2.waitKey(100) & 0xFF 

#    if k == ord("q"):
#      break
    elif k == ord("s"): 
        cv2.imwrite("./frame.jpg", frame) 
        break 

# cap.release()
# cv2.destroyAllWindows()

elif k == ord("s"):
 cv2.imwrite("./frame.jpg", frame)
 break

#もしsキーが押されたら、フレームを保存し、ループを抜けてプログラムを終了します。保存するファイル名は”frame.jpg”です。

無限ループ対策を追加

プログラム内で無限ループを使用するときは、しっかりとした終了操作も追加しておく必要があります。これをしないと、コンピュータに過度な負荷がかかってしまいます。

カメラからの映像をリアルタイムで表示し、qを押すかウィンドウのアスペクト比が変更された場合に終了できるようにします。

Python
# import cv2

# cap = cv2.VideoCapture(1) 

# while(True):
#     ret, frame = cap.read()
#     cv2.imshow("frame",frame)
#     k =  cv2.waitKey(100) & 0xFF
     prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

      if k == ord("q") or (prop_val < 0):
#         break
#     elif k == ord("s"): 
#         cv2.imwrite("frame.jpg", frame)
#         break
# cap.release()
# cv2.destroyAllWindows()

prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)
#ウィンドウのアスペクト比を取得します。ウィンドウを閉じると-1が返される。

if k == ord("q") or (prop_val < 0):
#もしqが押されたか、ウィンドウのアスペクト比が負の値になった場合に、無限ループを抜けてプログラムを終了します。

対象領域をカメラ内に表示

フレームの中心に青い矩形を描画して、数字を読み込むための対象領域を明確化しましょう。

Python
# import cv2

# cap = cv2.VideoCapture(0) 


# while(True):
#   ret, frame = cap.read()
    h, w, _ = frame.shape[:3]
    w_center = w//2
    h_center = h//2
    cv2.rectangle(frame, (w_center-71, h_center-71), (w_center+71, h_center+71),(255, 0, 0))
    cv2.imshow("frame",frame)

#     k =  cv2.waitKey(100) & 0xFF 
#     prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

#     if k == ord("q") or (prop_val < 0):
#         break
#     elif k == ord("s"): 
#         cv2.imwrite("frame.jpg", frame)
#         break

# cap.release()
# cv2.destroyAllWindows()

h, w, _ = frame.shape[:3]
#フレームの高さと幅を取得します。OpenCVのshapeメソッドは、画像の高さ、幅、チャンネル数などの情報を返します。ここでは、高さと幅のみを取得しています。

w_center = w//2
#フレームの中心の横座標を計算します。

h_center = h//2
#フレームの中心の縦座標を計算します。

cv2.rectangle(frame, (w_center-71, h_center-71), (w_center+71, h_center+71), (255, 0, 0))
cv2.rectangle()関数を使用して、フレームに矩形を描画します。矩形の左上の座標と右下の座標を指定し、色を指定します。ここでは青色((255, 0, 0))の矩形を描画しています。順番は(青,緑,赤)を表します。

cv2.imshow("frame", frame)
#キャプチャされたフレームをウィンドウに表示します。

画像データの前処理

では、画像データの前処理をしていきましょう。

ここではキャプチャされたフレームに描画された短形を切り取って白黒反転させた後、二値化ガウシアンブラーを適用していきます。

Python
# import cv2

# cap = cv2.VideoCapture(0) 


# while(True):
#     ret, frame = cap.read()

#     h, w, _ = frame.shape[:3]
#     w_center = w//2
#     h_center = h//2
#     cv2.rectangle(frame, (w_center-71, h_center-71), (w_center+71, h_center+71),(255, 0, 0))


#     cv2.imshow("frame",frame)

#     k =  cv2.waitKey(100) & 0xFF
#     prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

#     if k == ord("q") or (prop_val < 0):
#         break
#     elif k == ord("s"): 
        im = frame[h_center-70:h_center+70, w_center-70:w_center+70]
        im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 
        _, th = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)
        th = cv2.bitwise_not(th)
        th = cv2.GaussianBlur(th,(9,9), 0)
        cv2.imwrite("capture.jpg", th)
#         break

# cap.release()
# cv2.destroyAllWindows()

im = frame[h_center-70:h_center+70, w_center-70:w_center+70]
frameから、縦方向には中央から±70ピクセル、横方向には中央から±70ピクセルの領域を切り取ります。h_centerw_centerは、フレームの高さと幅の中心座標です。

im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
#切り取られた領域をカラーからグレースケールに変換します。グレースケールに変換することで、画像の明るさ情報のみを残します。

_, th = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)
大津の二値化法を使用して、画像を二値化します。大津の二値化法は、画像の輝度ヒストグラムから自動的に適切な閾値を計算し、画像を黒と白の二値画像に変換します。この関数の返り値は、使用された閾値と二値化された画像ですが、このコードでは_に代入しているため、閾値は使用されません。

th = cv2.bitwise_not(th)
#二値化された画像を反転させます。つまり、黒い部分と白い部分が入れ替わります。

th = cv2.GaussianBlur(th, (9,9), 0)
#二値化された画像にガウシアンフィルタを適用します。ガウシアンフィルタは、画像のノイズを軽減するために使用され、画像の平滑化を行います。(9,9)はカーネルサイズを示し、それぞれの次元におけるフィルタの幅と高さを指定します。0はX軸方向の標準偏差を示しています。0の場合、カーネルサイズから自動的に計算されます。

cv2.imwrite(“capture.jpg”, th)
#もろもろの処理を施した、thをcapture.jpgとして保存します。

入力サイズの変形
Python
# import cv2
import numpy as np

# cap = cv2.VideoCapture(0) 
# while(True):
#     ret, frame = cap.read()

#     h, w, _ = frame.shape[:3]
#     w_center = w//2
#     h_center = h//2
#     cv2.rectangle(frame, (w_center-71, h_center-71), (w_center+71, h_center+71),(255, 0, 0))

#     cv2.imshow("frame",frame)

#     k =  cv2.waitKey(100) & 0xFF
#     prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

#     if k == ord("q") or (prop_val < 0):
#         break
#     elif k == ord("s"): 
#         im = frame[h_center-70:h_center+70, w_center-70:w_center+70]
#         im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

#         _, th = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)
#         th = cv2.bitwise_not(th) 
#         th = cv2.GaussianBlur(th,(9,9), 0) 
#         cv2.imwrite("./capture.jpg", th)
#         break

# cap.release()
# cv2.destroyAllWindows()

Xt = []
img = cv2.imread("capture.jpg", 0)
img = cv2.resize(img,(28, 28), cv2.INTER_CUBIC)

Xt.append(img)
Xt = np.array(Xt)/255

import numpy as np
#NumPyライブラリをnpとしてインポートします。NumPyはPythonで数値計算を行うための基本的なライブラリです。

Xt = []
: 画像データを格納するための空のリストXtを作成します。

img = cv2.imread("capture.jpg", 0)
#”capture.jpg”というファイルをグレースケールで読み込みます。cv2.imread()関数は、指定したファイルから画像を読み込みます。2番目の引数で0を指定することで、グレースケールとして読み込みます。

img = cv2.resize(img, (28, 28), cv2.INTER_CUBIC)
#読み込んだ画像を28×28ピクセル(MNISTのサイズ)にリサイズします。cv2.resize()関数は、画像を指定したサイズにリサイズします。cv2.INTER_CUBICは、リサイズ時の補間方法を指定します。

Xt.append(img)
#リサイズされた画像をリストXtに追加します。

Xt = np.array(Xt) / 255
#リストXtをNumPy配列に変換し、それぞれの要素を0から1の範囲に正規化します。画像のピクセル値は通常、0から255の範囲になりますが、ニューラルネットワークに入力する際には0から1の範囲に正規化することが一般的です。


モデルをロード

前回作って保存したモデルのニューラルネットワークモデルロードして、使用して画像データの予測を行い、上位3つのクラスとその確率を出力します。

p.s.前回のモデルがない場合は、ここを一旦飛ばして次の「前回のモデルがない場合」のコードを実行してください。

Python

# import cv2
# import numpy as np
from keras.models import load_model

# cap = cv2.VideoCapture(0)

# while(True):
#     ret, frame = cap.read()

#     h, w, _ = frame.shape[:3] 

#     w_center = w//2 
#     h_center = h//2 

#     cv2.rectangle(frame, (w_center-71, h_center-71),  
#                  (w_center+71, h_center+71),(255, 0, 0)) 
#     cv2.imshow("frame",frame) 

#     k =  cv2.waitKey(1) & 0xFF
#     prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO)

#     if k == ord("q") or (prop_val < 0):
#         break
#     elif k == ord("s"):
#         im = frame[h_center-70:h_center+70, w_center-70:w_center+70] 
#         im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 

#         _, th = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU) 
#         th = cv2.bitwise_not(th)
#         th = cv2.GaussianBlur(th,(9,9), 0) 
#         cv2.imwrite("capture.jpg", th)
#         break

# cap.release()
# cv2.destroyAllWindows()

# Xt = []
# img = cv2.imread("capture.jpg", 0)
# img = cv2.resize(img,(28, 28), cv2.INTER_CUBIC)

# Xt.append(img)
# Xt = np.array(Xt)/255

model = load_model("ai-third.h5")

result = model.predict(Xt)#モデルの予測を抽出

#ここ↓はお好みでどうぞ。面倒くさかったらprint(result[0])だけでも出力できます。
top3_probs = np.sort(result, axis=1)[:, ::-1][:, :3]
top3_classes = np.argsort(result, axis=1)[:, ::-1][:, :3]
for i in range(len(top3_probs)):
    print(f"サンプル {i+1} の上位3つのクラスと確率:")
    for j in range(3):
        print(f"位 {top3_classes[i][j]}: 確率 {top3_probs[i][j]}")

model = load_model("ai-third.h5")
load_model()関数を使用して、指定されたファイル(”ai-third.h5″)から事前にトレーニングされたモデルをロードします。このファイルは、Kerasで保存されたモデルの重みやアーキテクチャなどの情報を含んでいます。

result = model.predict(Xt)
model.predict()メソッドを使用して、ロードしたモデルを使って入力データ Xt の予測を行います。このメソッドは、入力データをモデルに渡し、各クラスに属する確率の配列を返します。

top3_probs = np.sort(result, axis=1)[:, ::-1][:, :3]
np.sort()関数を使用して、各サンプルのクラスに属する確率をソートします。
axis=1は行方向(各サンプル)に対してソートを行うことを意味し、
:,-1は降順でソートすることを示します。
:,:3で各サンプルの上位3つの確率を取得します。

top3_classes = np.argsort(result, axis=1)[:, ::-1][:, :3]
np.argsort()関数を使用して、各サンプルのクラスに属する確率のインデックスをソートします。
axis=1は行方向(各サンプル)に対してソートを行うことを意味します。
:,-1は降順でソートすることを示します。
:,:3で各サンプルの上位3つのクラスのインデックスを取得します。

forループを使用して、各サンプルの上位3つのクラスと確率を出力します。各サンプルに対して、上位から3つのクラスとそれに対応する確率が出力されます。

p.s.正解率が良くなかったら、前回のモデルのエポック数を調整してみてください。

前回のモデルがない場合

このコードを実行することでai-third.h5にモデルの訓練データが格納されます。
これが実行し終わったら、上の『モデルをロードする』のコードを実行してください。

Python
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.layers import Conv2D,Reshape, MaxPooling2D,Dropout

from keras.datasets import mnist
from keras.utils import to_categorical
import numpy as np

(X_train, y_train), (X_test, y_test) = mnist.load_data()

X_train = np.array(X_train)/255
X_test = np.array(X_test)/255

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)



model = Sequential()

model.add(Reshape((28,28,1), input_shape=(28,28)))
model.add(Conv2D(32,(3,3)))
model.add(Activation("relu"))

model.add(Conv2D(32,(3,3)))
model.add(Activation("relu"))
model.add(MaxPooling2D((2,2)))
model.add(Dropout(0.5))

model.add(Conv2D(16,(3,3)))
model.add(Activation("relu"))
model.add(MaxPooling2D((2,2)))
model.add(Dropout(0.5))

model.add(Flatten())
model.add(Dense(784))
model.add(Activation("relu"))
model.add(Dropout(0.5))

model.add(Dense(10))
model.add(Activation("softmax"))


model.compile(loss="categorical_crossentropy", optimizer="sgd", metrics=["accuracy"])


hist = model.fit(X_train, y_train, batch_size=200, verbose=1, 
                 epochs=15, validation_split=0.1)

score = model.evaluate(X_test, y_test, verbose=1)

model.save("./ai-third.h5")

おわりに

これで、今回の学習は終わりです。参考書でせっせこやるよりも、やっぱり実践的に手を動かした方が楽しいですよね。

また、今回で最初の1歩シリーズは完成になります。意外と、自分でも作れるんだと思ってもらえたら幸いです。

これからも、誰でも簡単に作れるAIをモットーにどんどん記事を更新していくので、AIで何か作ってみたい場合は是非、当サイトを利用してみてください。

次回
最初の1歩 ①
最初の1歩 ②


タイトルとURLをコピーしました