見出し画像

【Pythonコード】画像認識で動く障害物避けゲームをプレイしてみよう

フリーランスエンジニアのベナオです。

最近あった悲しいことはフォロワー2000人いたTwitterアカウントが謎の凍結を食らったことです。

この記事を読んでいる優しいそこのあなた、フォローボタンを押してくれると非常に嬉しいです。

というわけで失ったフォロワーを取り戻すべく、当分はnote強化月間になります。

今回はタイトルにもあります通り、Pythonの画像認識で動くゲームの解説になります。この記事を書くきっかけとなったツイートがこちら↓

デンマークの機械学習エンジニアの方のツイートですね。

Pythonの画像認識でカメラに写っている人のまぶたの動きを検知して、それに連動してブラウザ上の恐竜がジャンプして障害物を超えていくゲームです。

画像認識とは機械学習の1種で、膨大な画像データとタグ(画像にの被写物名などの情報)をセットでプログラムに与える(教師あり学習)ことで、新たな画像データの被写物が何であるか認識できるようになる技術のことです。

今回プレイしてみるゲームの場合で言えば、人間の顔が写った画像が学習テータとなり、カメラに映った人のまぶたの位置を学習してその動きを検知できるようにしています。

日本のサービスだと、メルカリで出品物の写真から商品情報を自動表示する機能などに使われていたり、身近なところで言えばiPhoneのカメラの自動ピント調整などに使われています。

詳しくは以下のnoteでも説明してますのでよろしければどうぞ。

というわけで今回は、ゲームで遊びながらこの画像認識技術に触れてみようというテーマになります。

手順自体はそこまで複雑ではなく、GitHubからプロジェクトファイルを落としてライブラリをインストールし、Pythonファイルを実行するだけでGoogle chrome上でゲームが動くのでぜひ試してみてください!

1.  Pythonの画像認識ゲーム立ち上げの準備

MacのPython3.7を前提に説明していきます(Windowsでもおおよそ同じになると思います)。

Pythonがインストールされていない方は以下からどうぞ。


まず以下のリポジトリからプロジェクトファイルをダウンロードします。

緑色の「code」ボタンを選択→「Download Zip」からZipファイルを落として解凍します。

以下のようなファイル構成になっています。

スクリーンショット 2020-09-19 15.18.22

Chromedriverは、SeleniumでChromeを自動化するのに必要となります。DLするとすでに入っているのですが、こちらはお使いのGoogle chromeのバージョンと合わせる必要があります。

Chromeを開いてブラウザ上部の「Chrome」を選択→「Google chromeについて」からバージョンを確認し、同じバージョンのChromeDriverをダウンロードして既存のファイルと入れ替えてください(でないとおそらくエラーになります)。

次にプログラムの実行に必要なライブラリをpipを使ってインストールするので、まずpip自体をインストールします。


ターミナル(Windowsであればコマンドプロンプト)を開いて以下のコマンドでライブラリを5つインストールします。

pip3 install keyboard
pip3 install dlib
pip3 install selenium
pip3 install opencv-python
pip3 install scipy

dlibが今回使う機械学習のためのライブラリです。

これで準備はOKです。

2. Pythonによる画像認識ゲームのプログラムコード

主な処理が記述してあるmain.pyファイルのみ解説します。

まず必要なライブラリをインポートします。

from selenium import webdriver
import json
import time
import keyboard
import cv2
import dlib
import numpy as np
from scipy.spatial import distance as dist

使用するChromeDriverのファイルパスを指定します。(Macの場合はFinder上でChromeDriverを右クリックした状態で「option」キーを押すと「パス名をコピー」ボタンが現れるのでそこからコピーできます。)

GoogleのWebページのURLを入力して移動できるようにします。

class TRex():

   def __init__(self,data):
       """Parameter initialization"""

       self.driver = webdriver.Chrome(r'ChromeDriverのパス')

   def open_game(self):
       """Go to Google.com"""

       self.driver.get("https://www.google.com")

次に瞬きの判定ですが、下の記事でも言われている通り目のアスペクト比を算出する方法で行います。

スクリーンショット 2020-09-19 17.53.49

スクリーンショット 2020-09-19 17.54.38

簡単にいうと、瞬きするときは目の横の長さに対して縦の長さが瞬間的に短くなることに着目します。これをコードに起こすと以下のようになります。

def eye_aspect_ratio(self,eye):
       """Computes the EAR"""
       A = dist.euclidean(eye[1], eye[5])
       B = dist.euclidean(eye[2], eye[4])
       C = dist.euclidean(eye[0], eye[3])

       ear = (A + B) / (2 * C)
       return ear

次に両目の位置を決定、各種変数の設定を行い、顔の認識と学習モデルの読み込みをdlibライブラリで行います。

shape_predictor_68_face_landmarks.datというファイルが今回の学習済みモデルファイルです。これはプロジェクトファイルの中に含まれています。

def play(self):
       """Play game"""

       RIGHT_EYE_POINTS = list(range(36, 42))
       LEFT_EYE_POINTS = list(range(42, 48))

       EYE_AR_THRESH = 0.22
       EYE_AR_CONSEC_FRAMES = 3
       EAR_AVG = 0

       # detection of the facial region
       detector = dlib.get_frontal_face_detector()
       predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')


       COUNTER = 0
       TOTAL = 0

次にOpenCVによるカメラ映像のキャプチャを行います。

# OpenCV - live video frame
       cap = cv2.VideoCapture(0)
       while True:
           ret, frame = cap.read()
           if ret:
               # convert the frame to grayscale
               gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
               rects = detector(gray, 0)
               for rect in rects:
                   x = rect.left()
                   y = rect.top()
                   x1 = rect.right()
                   y1 = rect.bottom()

そして認識した両目の位置から、先ほどの計算式で左右の目のアスペクト比を算出して平均を取ります。

landmarks = np.matrix([[p.x, p.y] for p in predictor(frame, rect).parts()])

                   left_eye = landmarks[LEFT_EYE_POINTS]
                   right_eye = landmarks[RIGHT_EYE_POINTS]

                   left_eye_hull = cv2.convexHull(left_eye)
                   right_eye_hull = cv2.convexHull(right_eye)

                   ear_left = self.eye_aspect_ratio(left_eye)
                   ear_right = self.eye_aspect_ratio(right_eye)

                   ear_avg = (ear_left + ear_right) / 2.0

​アスペクト比の平均が、先ほど設定したEYE_AR_THRESH=2.2以下になった時に瞬きをしたと判定してCOUNTERを1増やします。

両目をどちらもつぶらないと瞬き判定されないようになっています。

# detect of the blink
                   if ear_avg < EYE_AR_THRESH:
                       COUNTER += 1
                   else:
                       if COUNTER >= EYE_AR_CONSEC_FRAMES:
                           TOTAL += 1
                           # press space bar when blinked
                           keyboard.press_and_release('space')
                           print("Eye blinked")
                       COUNTER = 0

カメラの画面に瞬きの回数を表示します。qキーでゲームを終了します。

cv2.putText(frame, "Blinks{}".format(TOTAL), (10, 30), cv2.FONT_HERSHEY_DUPLEX, 0.7, (0, 0, 255), 1)

               cv2.imshow("Winks Found", frame)
               key = cv2.waitKey(1) & 0xFF

               if key is ord('q'):
                   break

       cap.release()
       cv2.destroyAllWindows()

最後に設定ファイルの読み込みとゲーム開始の処理を記述して完成です。

if __name__ == "__main__":

   with open('config.json') as config_file:
       data = json.load(config_file)

game = TRex(data)
game.open_game()
time.sleep(1)
game.play()

3. Pythonの画像認識ゲームを遊んでみよう

お使いのPCのWifi接続を落としてからPythonファイルをプロジェクトファイルのmain.pyが置いてあるディレクトリまで移動して以下のコマンドで起動します。

python3 main.py

こんな感じで遊べます。

(かなり難しい・・・)

ここまで読んで少しでも参考になったという方はTwitterによる引用RT、フォロー、スキをよろしくお願いします!

Twitterをフォローするとnoteの更新や月1のエンジニア交流イベントの告知をしてますのでオススメです!

またエンジニア志望の方の相談を1日無料で受ける #1日無料メンター という活動をやっていますので、エンジニアに興味があるという方はお気軽に以下のTwitterアカウントまでダイレクトメールでお尋ねください!

https://twitter.com/benao_python

過去に100人以上のエンジニア志望の方の相談をチャット・zoomで受けており、中には未経験からエンジニアに転職できたという方もいらっしゃいます!

それではここまで読んでいただきありがとうございました!


サポートは料理好きなのでの食材費にさせていただきます。