2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Splatoon3】ナワバトラーのカード画像収集

Last updated at Posted at 2023-04-13

スプラトゥーン3に登場するナワバトラーのカード画像の素材を収集します。
ネットにも素材は転がってはいるのですが、公式PVから切り取ったものが多くサイズが異なったり角が透過されていなかったりで不満があったため自分で収集することにしました。

はじめに

本記事ではカード画像全体の素材を収集します。

収集の際の要件

  • カード画像全体が写っている
  • 全カードの画像サイズが統一されている
  • 角が透過されている

手順

1. ゲーム画面のキャプチャ

最初に、スプラトゥーン3のゲーム画面をキャプチャします。
なるべく大きいカード画像が欲しいため、ゲーム内でカード画像が大きく映る場面にいきます。

  1. ゲーム起動後、Xボタンを押下しメニューを開く
  2. メニュー>プレイヤー情報>ナワバトラー>カードリスト、と遷移
  3. 現在手持ちにあるカード一覧が出るのでAボタンを押下

上記手順で以下のような1280×720pの画像が出ます。

2023062411284700-4CE9651EE88A979D41F24CE8D6EA1C23.jpg

このキャプチャを全カード分集めます。

2. カード画像のクロップ

次に、カード画像を検出するために画像処理を行います。
ディレクトリに保存したキャプチャ画像を適当なサイズにクロップします。

crop.py
import os
from PIL import Image

# クロップする範囲
left, right, upper, lower = 469, (1280-469), 121, (720-121)

# 処理するフォルダのパス
folder_path = 'cardimg_raw'
save_folder_path = 'cardimg_cut'

# 保存するフォルダが存在しない場合は作成する
if not os.path.exists(save_folder_path):
    os.makedirs(save_folder_path)

# フォルダ内のファイルを処理する
for filename in os.listdir(folder_path):
    # ファイル名が.jpgまたは.pngで終わる場合のみ処理する
    if filename.endswith(('.jpg', '.png')):
        # 画像を開く
        image_path = os.path.join(folder_path, filename)
        image = Image.open(image_path)
        # 画像をクリッピングする
        cropped_image = image.crop((left, upper, right, lower))
        # 画像を保存する
        save_path = os.path.join(save_folder_path, os.path.splitext(filename)[0] + ".png")
        cropped_image.save(save_path)

以下のような結果が得られます。画像サイズは342×478pです。

2023062411284700-4CE9651EE88A979D41F24CE8D6EA1C23.png

3. カード画像の角を透過

次に、不要な角の透過を行います。
マスクを使って角のアルファチャンネルの値を0にすることで透過を行います。

edge_remove.py
import os
import numpy as np
from PIL import Image

# フォルダのパスを設定する
folder_path = 'cardimg_cut'
save_folder_path = 'cardimg_noedge'

# 保存するフォルダが存在しない場合は作成する
os.makedirs(save_folder_path, exist_ok=True)

# マスク画像用の配列
np_edge=np.array([[.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [.2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, .2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, .2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, .2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .5, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .2, .5, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .2, .5, 1, 1, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .2, .5, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .2, .5]])
                   
# フォルダ内のファイルを取得する
for filename in os.listdir(folder_path):
    # 拡張子が '.jpg' または '.png' の場合のみ処理する
    if filename.endswith(('.jpg', '.png')):
        # 画像のパスを取得する
        image_path = os.path.join(folder_path, filename)
        # 画像を開く
        image = Image.open(image_path)
        # 画像のサイズを取得する
        width, height = image.size

        # マスク画像を作成する
        bin_mask = np.ones((height, width))
        bin_mask[height-20:height,0:20] = np_edge
        bin_mask[height-20:height,width-20:width] = np.rot90(np_edge, 1)
        bin_mask[0:20,0:20] = np.rot90(np_edge, 3)
        bin_mask[0:20,width-20:width] = np.rot90(np_edge, 2)

        # マスク画像の値を変換する (ジャギー対策にアンチエイリアシング処理を行う)
        bin_mask[bin_mask == 1] = 255
        bin_mask[bin_mask == .5] = 127
        bin_mask[bin_mask == .2] = 80
        bin_mask = bin_mask.astype(np.uint8)
        # マスク画像を Image オブジェクトに変換する
        mask_image = Image.fromarray(bin_mask)

        # 画像にアルファチャンネルを追加する
        image.putalpha(mask_image)

        # 保存するファイルのパスを作成する
        save_path = os.path.join(save_folder_path, os.path.splitext(filename)[0] + ".png")
        # 画像を保存する
        image.save(save_path)

以下のような結果が得られます。
4つの角が上記の画像と異なり透過されています。
(若干ジャギってますが、まあ拡大しない限り気にならないでしょう。)

2023062411284700-4CE9651EE88A979D41F24CE8D6EA1C23.png

4. ファイル名変更

Switchからキャプチャした画像のファイル名は
「2023040112000000-XXXXXXXXXXXXXXXXXXXXXXXXX.png」
みたいな名前になっているため、このファイル名をカード名に変更します。
手作業で行ってもいいですが、数が多く面倒なためOCRでテキスト抽出しファイル名に使います。

チャットGPTによるとGoogleのVision APIが現在一番精度が高いようなので今回はそれを用いました。
詳しい実装方法は他の記事でも書かれているため省略し、インプット画像と結果だけ紹介します。

結果例

元画像 画像処理後(インプット) OCR結果(アウトプット)
2023062411284700-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062411284700-4CE9651EE88A979D41F24CE8D6EA1C23.png わかばシューター
2023062412165400-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412165400-4CE9651EE88A979D41F24CE8D6EA1C23.png LACT-450
2023062412154100-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412154100-4CE9651EE88A979D41F24CE8D6EA1C23.png スクイックリンα
2023062412164200-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412164200-4CE9651EE88A979D41F24CE8D6EA1C23.png クアッドホッパーブラック
2023062412203800-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412203800-4CE9651EE88A979D41F24CE8D6EA1C23.png スプラシューターコラボ
2023062412185800-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412185800-4CE9651EE88A979D41F24CE8D6EA1C23.png 大容量タコポッド
2023062804175100-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062804175100-4CE9651EE88A979D41F24CE8D6EA1C23.png マメブキチ&ツブブキチ

いい感じにカード名を抽出してくれました。
ただ上手くいかなかったものもありまして…

うまくいかなかった例

元画像 画像処理後(インプット) OCR結果(アウトプット)
2023062412192200-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412192200-4CE9651EE88A979D41F24CE8D6EA1C23.png ジャケ
2023062411290400-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062411290400-4CE9651EE88A979D41F24CE8D6EA1C23.png .96 toy
2023062411285700-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062411285700-4CE9651EE88A979D41F24CE8D6EA1C23.png .52tioy
2023062412173000-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412173000-4CE9651EE88A979D41F24CE8D6EA1C23.png T-E-F
2023062412160900-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412160900-4CE9651EE88A979D41F24CE8D6EA1C23.png 14式竹節・甲
2023062412192800-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412192800-4CE9651EE88A979D41F24CE8D6EA1C23.png YDALE
2023062412162900-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023062412162900-4CE9651EE88A979D41F24CE8D6EA1C23.png ワーゲルシュライバー
2023070212331500-4CE9651EE88A979D41F24CE8D6EA1C23.png 2023070212331500-4CE9651EE88A979D41F24CE8D6EA1C23.png * CX=¹ *

こういった誤認識のカードも1割ほどありました。これらは手修正で直しました。

5. カード格納

収集したカードは以下に格納しました。
https://drive.google.com/drive/folders/1_ei17gsYbdFBqdgvT5UsvjWx9_yt8Mki?usp=share_link

「.52ガロン」「.96ガロン」「.96ガロンデコ」は名前の都合上、隠しファイル扱いになっているため注意してください。

おわりに

【2023/04/16 追記】
以下のカードはデビルスコーピオン氏(@debirusukoupion)からご提供いただきました。
多数のご支援ありがとうございました。

  • アオリ
  • フウカ
  • ウツホ
  • ふしぎせいぶつマンタロー
  • トリプルトルネード
  • ホップソナー
  • ヤシガニさん
  • アナアキ
  • ジモン
  • シグレニ
  • タタキケンサキ
  • クマサン
  • ビートタコスタンプ
  • タコゾネス
  • ハコビヤ
  • テイオウイカ
  • ダウニー
  • ジェットスイーパー
  • もみじシューター
  • N-ZAP89
  • ジェットスイーパーカスタム
  • ラピッドブラスターデコ
  • L3リールガンD

【2023/04/18 追記】
以下のカードはデュアル園長氏(@usiusi0d)からご提供いただきました。
ご支援ありがとうございました。

  • オームラ
  • パル子
  • ロッケンベルグ
  • エンぺリー
  • シチリン

【2023/04/19 追記】
以下のカードはこひつじ†氏(@UnXnonGfOb8l2LQ)からご提供いただきました。
ご支援ありがとうございました。

  • アタリメ
  • ウルトラショット
  • アネモ
  • ノヴァブラスターネオ
  • カーボンローラーデコ
  • R-PEN/5H
2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?