スプラトゥーン3に登場するナワバトラーのカード画像の素材を収集します。
ネットにも素材は転がってはいるのですが、公式PVから切り取ったものが多くサイズが異なったり角が透過されていなかったりで不満があったため自分で収集することにしました。
はじめに
本記事ではカード画像全体の素材を収集します。
収集の際の要件
- カード画像全体が写っている
- 全カードの画像サイズが統一されている
- 角が透過されている
手順
1. ゲーム画面のキャプチャ
最初に、スプラトゥーン3のゲーム画面をキャプチャします。
なるべく大きいカード画像が欲しいため、ゲーム内でカード画像が大きく映る場面にいきます。
- ゲーム起動後、Xボタンを押下しメニューを開く
- メニュー>プレイヤー情報>ナワバトラー>カードリスト、と遷移
- 現在手持ちにあるカード一覧が出るのでAボタンを押下
上記手順で以下のような1280×720pの画像が出ます。
このキャプチャを全カード分集めます。
2. カード画像のクロップ
次に、カード画像を検出するために画像処理を行います。
ディレクトリに保存したキャプチャ画像を適当なサイズにクロップします。
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です。
3. カード画像の角を透過
次に、不要な角の透過を行います。
マスクを使って角のアルファチャンネルの値を0にすることで透過を行います。
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つの角が上記の画像と異なり透過されています。
(若干ジャギってますが、まあ拡大しない限り気にならないでしょう。)
4. ファイル名変更
Switchからキャプチャした画像のファイル名は
「2023040112000000-XXXXXXXXXXXXXXXXXXXXXXXXX.png」
みたいな名前になっているため、このファイル名をカード名に変更します。
手作業で行ってもいいですが、数が多く面倒なためOCRでテキスト抽出しファイル名に使います。
チャットGPTによるとGoogleのVision APIが現在一番精度が高いようなので今回はそれを用いました。
詳しい実装方法は他の記事でも書かれているため省略し、インプット画像と結果だけ紹介します。
結果例
元画像 | 画像処理後(インプット) | OCR結果(アウトプット) |
---|---|---|
わかばシューター | ||
LACT-450 | ||
スクイックリンα | ||
クアッドホッパーブラック | ||
スプラシューターコラボ | ||
大容量タコポッド | ||
マメブキチ&ツブブキチ |
いい感じにカード名を抽出してくれました。
ただ上手くいかなかったものもありまして…
うまくいかなかった例
元画像 | 画像処理後(インプット) | OCR結果(アウトプット) |
---|---|---|
ジャケ | ||
.96 toy | ||
.52tioy | ||
T-E-F | ||
14式竹節・甲 | ||
YDALE | ||
ワーゲルシュライバー | ||
* 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