BETA

HOG特徴量を用いたポケモンのアイコン画像判別

投稿日:2018-11-13
最終更新:2018-11-13
※この記事は外部サイト(https://qiita.com/hrs1985/items/118710a673...)からのクロス投稿です

目的

  1. ポケモンの対戦ログツールは色々とあるんだけど、相手パーティの内容を自分で入力しないといけないのがかったるすぎるので自動で判別してくれるようなものを作りたかった。
  2. HOG特徴量を使ってみたかった。

実装済みのものは記事の一番下に置いてあります。 ※今回のバージョンは偽トロキャプチャなどを使ってモニタなどに映された画面を対象としてます。

HOG特徴量について

HOG (Histgram Of Gradient) は画像中の輝度勾配の分布みたいな感じです。 輝度が大きく変化する場所を検出できるので、おおまかに言って画像のエッジ分布を取得できます。 ここの説明がわかりやすかった。

画像で表現すると、

こんな感じになります。 (画像の出典は琴葉姉妹 立ち絵素材(各30種))

  1. 利用できるデータの背景色と判別対象となるゲーム画面での背景色が異なる
  2. ゲーム画面の方では位置によって背景色が異なる

の2点に影響されづらいのが今回は特に重要です。

HOG抽出メソッドは色々なライブラリに実装されています。 試した範囲では、

  • scikit-imageのhog
  • OpenCV (Python用) のHOGDescriptor
  • OpenCV (java用) のHOGDescriptor

の三種類を試しました。 恐ろしいことに、 これらに実装されているメソッドは (次元数だけは等しい) 異なる結果を返してきます。 アルゴリズムの大枠は同等ですが計算手順などの細かい実装が異なっているものと思われます。

アルゴリズム

手順の概要

  1. 画像からのパーティ枠の抽出
  2. パーティ枠部分からのポケモンアイコン部分の切り出し
  3. 各アイコン部分に対する名前の推定

パーティ枠の抽出

Cannyフィルタでエッジを取ってきてその内側をくり抜くだけ。 ほぼこれです。

元画像

エッジ抽出画像

切り抜き画像

※トレーナー名は一応伏せました

アイコン部分の切り出し

アイコン画像部分が枠のどこにあるかは決まっているので、位置を決め打って切り出すだけです。 実験しつつ適当に調整しました。

こんな感じの画像が取れます。

HOG特徴量を用いた名前の推定

  1. 30x30画像をBGRの3色のチャンネルに分割
    1. 各画像についてガウシアンフィルタでノイズ除去
    2. セルサイズ5ピクセル、ブロックサイズ30ピクセル(6セル分)でHOGを計算(324次元)
  2. 3色分のHOGを結合して972次元のベクトルにする
  3. 各教師画像について同様に抽出されたベクトルとのマンハッタン距離を計算する
  4. 最も距離の近い画像を正解とする

セルサイズやブロックサイズは何種類か試しましたがこの組み合わせがよかったです。この組み合わせを上手く使うために判定画像のサイズは30x30としました。 Pythonのコード(推定部分)を貼っておきます。 最初に貼り付けたバージョンが古かったので、HOG計算に使うハイパーパラメータだけ差し替えました(2017/3/4)

#ポケモン名を推定するメソッド。引数のdataはポケモン名とそのHOGを組にしたものです。
def estimate_poke_index(image, data):
    hog = calculateHOG(image)
    distance_list = calculate_manhattan_distance(hog, data)
    return np.argmin(np.array(distance_list))

# 画像を読み込み、HOG特徴量を計算して返すメソッド
def calculateHOG(image, orient=9, cell_size=5, block_size=6):
    # 画像を読み込む
    number_color_channels = np.shape(image)[2]
    if number_color_channels > 3:
        mask = image[:, :, 3]
        image = image[:, :, :3]
        for i in range(len(mask)):
            for j in range(len(mask[0])):
                if mask[i][j] == 0:
                    image[i, j, :] = 255
    # 画像を30x30にリサイズする
    resized_image = cv2.resize(image, (30, 30))
    images = cv2.split(resized_image)
    fd = []
    for monocolor_image in images:
        blur_image = cv2.GaussianBlur(monocolor_image, (3,3),0)
        fd.extend(hog(blur_image, orientations=orient,
                      pixels_per_cell=(cell_size, cell_size), cells_per_block=(block_size, block_size)))
    return fd

# HOG特徴量間の距離計算メソッド
def calculate_distance_HOG(target, data):
    distance_list = []
    rows, columns = np.shape(data)
    for i in range(rows):
        distance_list.append(np.linalg.norm(data[i, :] - target))
    return distance_list

# マンハッタン距離の計算メソッド
def calculate_manhattan_distance(target, data):
    distance_list = []
    rows, columns = np.shape(data)
    for i in range(rows):
        distance_list.append(np.sum(np.abs(data[i, :] - target)))
    return distance_list

感想

雑なアルゴリズムながら体感7~8割は当たります。本当はテスト結果の話が書ければよいのでしょうけど、一貫したテストはしてないのでしょうがない。 (2017/3/4追記) 19試合分のデータをもらって誤認識率を調べたところ、20体/114体で間違えていました。8割程度は当てられるようですが、「ゲッコウガ→フクスロー」「ガルーラ→チルット」のようなKPの高いポケモンの誤認識はなんとかしたいところです。

  1. 配色や形が似ている(ガバイトとガブリアスなど)
  2. アイコンが小さいポケモン(ガルーラ、ルカリオ、メタモン、ラッキーなど)は配置される位置が想定より右下に寄っている。

の2つがおそらく主な誤認識要因です。 2番目についてはアルゴリズムをちょっと改良すれば当てられるようになるかも?

ツール開発者向けですが、Javaで実装して固めたものを置いときます。自由に使っていただいて大丈夫ですが、一報いただけるとうれしいです。(できあがったものとかを見に行きたいので)

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@kiyoの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!