ディスクアップの青7を自動認識

今回はディスクの青7を自動認識させてみたいと思います。
というのも、スマホ等でカメラを構えると、勝手に人間の顔を認識して追いかけてくれるのを見たときに、「これってスロットの目押しもできるんじゃない?」と思ったのがきっかけです。

残念ながら、私にはスマホアプリを作成するスキルはないので、
この記事を元に、どなたかビタ押しのタイミングを教えてくれるスマホアプリを作成していただけたら感謝感激です。

ということで、早速ディスクの青7を認識させる方法を考えていきます。

本日のアジェンダはこちら。

1. どうやって図柄を認識させるか?

まずは、私たちが普段目押しを行う時に、どの部分に注目しているかを考えてみます。
ディスクアップのリール配列はこちら。

サミーさんのHPから引用


人によって違いはあると思いますが、個人的に一番目につくのは「色」ですね。
(リプレイ図柄が水色で色が近いのが気になりますが、「大きさ」でフィルタすればなんとかなりそうです。)
今回のように、「色」によって物体を追跡することを「カラートラッキング」といいます。

2. カラートラッキングとは

「カラートラッキング」は、直訳すると「色追跡」となるように、色で物体を追跡する手法です。
コンピュータでは、色を赤・青・緑それぞれの数値で定義しています。(光の三原色というやつですね。)
例えば、WindowsのPaintで「色の編集画面」を開くと、R(Red:赤)・G(Green:緑)・B(Blue:青)の値を直接指定して好きな色を作ることができます。
下図のように、R=0・G=0・B=255とすると、「青」の色が作成できます。

実際のカメラの画像では、ホールの明るさやカメラのスペック、撮影方法によって色が変わってくるので、ある程度の幅以内に含まれる色を判定するコードを記載します。
Pythonでは、↓のコードを用いることで、指定した色部分だけを抽出することができます。
色の指定は、RGBの順番でなくBGRの順番であることに注意が必要です。
cv2.inRange(画像ファイル, 色の下限, 色の上限)

3. 動画とは

コーディングに移る前に、もう一つ確認しておかなければならないことがあります。
それは、動画がどのようにできているのかということです。
簡単に言うと動画は「パラパラ漫画」です。
時系列順に静止画をたくさん撮った上で時間順にぱらぱらめくっていくと動画になります。
より細かい時間間隔で静止画を撮影するとなめらかな動画になりますが、この細かさのことをfps(frame per second:単位時間当たりの静止画数)と呼びます。

つまり、この静止画1枚1枚に対して、「青7」を検出していくことで、「青7」をトラッキングすることができます。

4. Pythonでのコーディング

青7を検出する範囲を設定

ここから、Pythonでプログラムを組んでいきます。
ちなみに今回処理する動画はこちら。

まずは、青7を検出する範囲を指定します。
今回は、中リールに限定して処理を行っていくので、先ほどの動画から中リール部分の位置を確認し、設定します。
# 中リールの座標を入力
xmin = 724
xmax = 1264
ymin = 144
ymax = 898

動画の読み込みと処理準備

次に、動画を読み込んでいきます。
また、読み込んだ動画に対して、処理に必要となる情報を取得していきます。
最後の行「video = ...」は、処理結果を保存するためのものです。
# 動画の場所指定
videofile_path = r"D:\Test\disc.MOV"

# 動画の読み込み
cap = cv2.VideoCapture(videofile_path)

# 動画ファイルの情報を取得
# 動画のFPSを取得
fps = int(cap.get(cv2.CAP_PROP_FPS))
# 動画の横幅を取得
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
# 動画の縦幅を取得
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 動画保存時のfourcc設定(mp4用)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
# 動画の仕様(ファイル名、fourcc, FPS, サイズ, カラー)
video = cv2.VideoWriter(r"D:\Test\disc-out1.MOV", fourcc, fps, (w, h), True)

カラートラッキング処理の実装

カラートラッキングの準備が整ったので、ここから青7をカラートラッキングする処理をコーディングしていきます。
まずは、青色部分の抽出処理を行います。
動画を1フレームずつ読み込んだ上で、「中リールだけ指定」→「青色部分抽出」を繰り返していきます。
# 動画が読み込めたらループ処理を行う
while (cap.isOpened()):
    # フレームを取得
    ret, frame = cap.read()

    # フレームが取得できない場合はループを抜ける
    if not ret:
        break

    # 色検出処理
    # 中リールの部分だけ処理するために、範囲を指定する
    bgr = frame[ymin:ymax, xmin:xmax]

    # 青色のRGB値を設定
    # 多少色が異なっても良いように、マージンを設けておく
    r = 0
    g = 0
    b = 255
    mergin = 50

    bgr_min = np.array([b - mergin, g - mergin, r - mergin])
    bgr_max = np.array([b + mergin, b + mergin, r + mergin])

    # 青色部分だけ抽出
    mask = cv2.inRange(bgr, bgr_min, bgr_max)

ちなみに、中リールの青色部分だけ抽出した動画はこちら。
やはり、リプレイ部分も抽出されてしまいますね。

もうちょっと色の指定を微調整しても良いですが、ここは元の予定通り大きさでフィルタリングしていきます。

まずは、先ほど「mask = cv2.inRange(bgr, bgr_min, bgr_max)」で取得した部分の情報を取得していきます。
↑の動画で抽出された青色領域の数や座標、面積等は、「cv2.connectedComponentsWithStats」を使用することで、結果を配列形式で取得できます。
青7図柄はリプレイ図柄より大きいので面積が最大になると想定して、「np.argmax(coord[:, 4])」を用いて必要なデータが格納されている配列の場所を取得します。
さらに、リール上にリプレイ図柄しか見つからなかった場合も想定して、取得した連結領域の「幅」がリールの半分以上のときだけ「青7」と認識することにします。
抽出した「青7」の場所を変数に保存すれば処理は完了です。
    # 抽出した画像を解析
    if mask.any():
        # 抽出した画像から連結領域を取得
        area = cv2.connectedComponentsWithStats(mask)

        # 連結領域の情報を取得
        # 連結領域の数
        n = area[0] - 1
        # 連結領域の座標情報(先頭は背景の情報なので削除)
        coord = np.delete(area[2], 0, 0)
        # 連結領域の重心情報(先頭は背景の情報なので削除)
        center = np.delete(area[3], 0, 0)

        # 面積が最大の連結領域の情報の位置取得
        max_index = np.argmax(coord[:, 4])

        # 面積最大の領域の情報を入れる箱準備
        maxarea = {}

        # 面積最大の領域の各種情報を取得
        # 幅
        maxarea["width"] = coord[:, 2][max_index]
        # 中心座標
        maxarea["center"] = center[max_index]

        # リール幅の半分以上の連結領域を検出した時だけ座標を取得
        if maxarea["width"] > (xmax - xmin) / 2:
            center_x = int(maxarea["center"][0])
            center_y = int(maxarea["center"][1])
        else:
            # リール幅の半分以下の連結領域は無視して、中リールの中心座標を入力。
            center_x = int((xmax - xmin) / 2)
            center_y = 0
    else:
        # 連結領域が見つからない場合は、中リールの中心領域を入力。
        center_x = int((xmax - xmin) / 2)
        center_y = 0

5. 今回作成したコード

今回作成したコードは、こちら。
結果を動画で保存して確認できるようにしています。
    # -*- coding: utf-8 -*-
    import cv2
    import numpy as np
    
    # 中リールの座標を入力
    xmin = 724
    xmax = 1264
    ymin = 144
    ymax = 898
    
    # 動画の場所指定
    videofile_path = r"D:\Test\disc.MOV"
    
    # 動画の読み込み
    cap = cv2.VideoCapture(videofile_path)
    
    # 動画ファイルの情報を取得
    # 動画のFPSを取得
    fps = int(cap.get(cv2.CAP_PROP_FPS))
    # 動画の横幅を取得
    w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    # 動画の縦幅を取得
    h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    # 動画保存時のfourcc設定(mp4用)
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    # 動画の仕様(ファイル名、fourcc, FPS, サイズ, カラー)
    video = cv2.VideoWriter(r"D:\Test\disc-out.MOV", fourcc, fps, (w, h), True)
    
    # 動画が読み込めたらループ処理を行う
    while (cap.isOpened()):
        # フレームを取得
        ret, frame = cap.read()
    
        # フレームが取得できない場合はループを抜ける
        if not ret:
            break
    
        # 色検出処理
        # 中リールの部分だけ処理するために、範囲を指定する
        bgr = frame[ymin:ymax, xmin:xmax]
    
        # 青色のRGB値を設定
        # 多少色が異なっても良いように、マージンを設けておく
        r = 0
        g = 0
        b = 255
        mergin = 50
    
        bgr_min = np.array([b - mergin, g - mergin, r - mergin])
        bgr_max = np.array([b + mergin, b + mergin, r + mergin])
    
        # 青色部分だけ抽出
        mask = cv2.inRange(bgr, bgr_min, bgr_max)
    
        # 抽出した画像を解析
        if mask.any():
            # 抽出した画像から連結領域を取得
            area = cv2.connectedComponentsWithStats(mask)
    
            # 連結領域の情報を取得
            # 連結領域の数
            n = area[0] - 1
            # 連結領域の座標情報(先頭は背景の情報なので削除)
            coord = np.delete(area[2], 0, 0)
            # 連結領域の重心情報(先頭は背景の情報なので削除)
            center = np.delete(area[3], 0, 0)
    
            # 面積が最大の連結領域の情報の位置取得
            max_index = np.argmax(coord[:, 4])
    
            # 面積最大の領域の情報を入れる箱準備
            maxarea = {}
    
            # 面積最大の領域の各種情報を取得
            # 左上座標
            # maxarea["upper_left"] = (coord[:, 0][max_index], coord[:, 1][max_index])
            # 幅
            maxarea["width"] = coord[:, 2][max_index]
            # 高さ
            # maxarea["height"] = coord[:, 3][max_index]
            # 面積
            # maxarea["area"] = coord[:, 4][max_index]
            # 中心座標
            maxarea["center"] = center[max_index]
    
            # リール幅の半分以上の連結領域を検出した時だけ座標を取得
            if maxarea["width"] > (xmax - xmin) / 2:
                center_x = int(maxarea["center"][0])
                center_y = int(maxarea["center"][1])
            else:
                # リール幅の半分以下の連結領域は無視して、中リールの中心座標を入力。
                center_x = int((xmax - xmin) / 2)
                center_y = 0
        else:
            # 連結領域が見つからない場合は、中リールの中心領域を入力。
            center_x = int((xmax - xmin) / 2)
            center_y = 0
    
        # 中リールの範囲に赤枠描画
        cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
        # 連結領域の中心位置に点を描画
        cv2.circle(frame, (center_x + xmin, center_y + ymin), 50, (0, 0, 255), thickness=-1, lineType=cv2.LINE_AA)
        cv2.rectangle(mask, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
    
        # 結果表示
        cv2.imshow("Frame", frame)
        cv2.imshow("Mask", mask)
    
        # 動画を保存
        video.write(frame)
    
        # qキーが押されたら途中終了
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break
    
    # 処理が終わったら、キャプチャを解放
    cap.release()
    # 処理が終わったら、ウィンドウを閉じる。
    cv2.destroyAllWindows()

6. 結果の確認

結果として出力された動画はこちら。
動画内の赤丸部分が「青7」の動きに合わせてちゃんと移動してくれています。


にほんブログ村 スロットブログ スロット情報へ
にほんブログ村

コメント

このブログの人気の投稿

【WEBアプリ】Sキングハナハナ-30のベル確率予想&ベル確率逆算ツール&設定判別ツール

【WEBアプリ】ハナハナホウオウ天翔のベル確率を差枚数から逆算するツール

【WEBアプリ】ハナハナホウオウ天翔の設定判別ツール