Aurebesh(スターウォーズ文字)を画像認識する
みなさん、「Aurebesh(オーラベッシュ)」って聞いたことありますか?
これはスター・ウォーズの世界で用いられている架空の文字で、スター・ウォーズシリーズの映画やディズニーパークのアトラクション内などに頻出します。
今回の記事では、このAurebesh文字を畳み込みニューラルネットワーク(CNN)を用いて画像から分類・認識する試みについて紹介します。
Aurebeshとは
「Aurebesh(オーラベッシュ)」は、スター・ウォーズ銀河で使用される標準的なアルファベットです。
実際には英語のA〜Zに1対1対応しており、英語の文字をAurebeshに置き換えることができます。
今回の課題
コアなスター・ウォーズのファンは対応表を見なくてもAurebeshを読むことができるらしいですが、私のようなにわかファンにはそれが難しいため、機械学習でAurebesh文字を自動で判別できる仕組みを作りたいと考えました。本記事では、Aurebesh文字を1文字単位で画像として切り出し、畳み込みニューラルネットワーク(CNN)によりA〜Zに分類するモデルを訓練します。
これにより、Aurebeshの各記号がどのアルファベットに対応するかを自動的に判断できるようにするのが目的です。
なお、実際に有用なモデルは「複数のAurebesh文字が記載された画像を入力とするモデル」だと思いますが、今回はあくまで足がかり的な実験としてこの「1文字単位の認識モデル」を試してみるという趣旨です。
データセット
今回の実験では以下のようなデータを使用しました。
データの種別 | 枚数 |
---|---|
学習用データ | A:54, B:34, C:43, D:42, E:78, F:31, G:40, H:38, I:62, J:28, K:29, L:51, M:39, N:51, O:57, P:30, Q:26, R:60, S:55, T:81, U:53, V:31, W:26, X:27, Y:38, Z:29(合計: 1,208枚) |
テスト用データ | A:3, B:3, C:3, D:3, E:3, F:2, G:3, H:2, I:3, J:1, K:1, L:3, M:2, N:3, O:3, P:1, Q:1, R:3, S:3, T:3, U:3, V:1, W:1, X:1, Y:3, Z:1(合計: 65枚) |
-
学習用データ
-
インターネット上に公開されているAurebesh文字画像からA〜Zをトリミングしたもの
-
有志の方が公開している手書きのAurebesh文字画像(https://github.com/rsanjabi/aurebesh/tree/master/Aurek-Besh)
-
私が東京ディズニーランドで直接撮影したAurebeshの写真からA〜Zをトリミングしたもの
-
-
テスト用データ
- 私が東京ディズニーランドで直接撮影したAurebeshの写真からA〜Zをトリミングしたもの(学習用データとは別のものを使用)
このように、A〜Z計26クラスの画像を、学習用、検証用それぞれに用意します。
なおAurebeshにはA〜Zの他に「Ae」、「Ch」といった合字(リガチャ)や、「,」、「!」、「?」、「.」といった句読点や記号類も存在しますが、これらはデータセット不足のため今回は対象外とします。
正直データセット収集は難航したため、データセットは前提的に少なめでです。とはいえ、Aurebeshは現実世界のアルファベットのようにフォントの多様性や手書き文字の揺らぎが存在しないため、比較的シンプルで難易度の低いタスクとなっており、この程度のデータ量でもそれなりに性能が出るはずです。
CNNモデルアーキテクチャ
今回は、Aurebesh文字をA〜Zの26クラスに分類するための、比較的シンプルな畳み込みニューラルネットワーク(CNN)を構築しています。
- 入力層(64×64ピクセルの白黒画像)
- 畳み込み層(32フィルター, カーネルサイズ3×3, ReLU活性化)
- MaxPooling層(2×2)
- 畳み込み層(64フィルター, カーネルサイズ3×3, ReLU活性化)
- MaxPooling層(2×2)
- Flatten層により1次元化
- Dense層(128ユニット, ReLU活性化)
- 出力Dense層(26ユニット, softmax活性化)
コード
先に書いたモデルを含めた全コードがこちらです。
- フレームワーク: Python + TensorFlow 2.16
- 前処理: 画像サイズ64x64、白黒画像
- データ分割: 学習用データのうち、80%を訓練用(train)、20%を検証用(validation)に自動で分割しています。ややこしいですが、この検証用(validation)データは事前に用意した「テスト用データ」とは別物です。
- データ拡張:
RandomRotation
で画像の左右回転、RandomZoom
で拡大・縮小を行い、データの多様性を高める - エポック数: 300
- 学習率スケジューラ: 200エポックまでは一定、それ以降指数関数的に減衰。
import tensorflow as tf
import datetime
BATCH_SIZE = 128 * 4
IMG_HEIGHT = 64
IMG_WIDTH = 64
EPOCHS = 300
TRAIN_DATA_DIR = 'dataset/train'
VAL_DATA_DIR = 'dataset/test'
VALIDATION_RATE = 0.2
def create_model(input_shape, num_classes):
"""
A~Z を分類する簡単なCNNモデル例(白黒画像対応)
"""
model = tf.keras.Sequential([
# 正規化
tf.keras.layers.Rescaling(1./255, input_shape=input_shape),
# データオーグメンテーション (オンザフライ)
tf.keras.layers.RandomRotation(factor=0.1), # ランダム回転
tf.keras.layers.RandomZoom(height_factor=0.1), # ランダム拡大縮小
tf.keras.layers.Conv2D(32, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(num_classes, activation='softmax') # A~Z (26クラス想定)
])
return model
def scheduler(epoch, lr):
if epoch < EPOCHS - 100:
return lr
else:
return float(lr * tf.math.exp(-0.01))
def main():
seed_value = 123
# クラス数 (A~Zの場合は26)
num_classes = 26
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
TRAIN_DATA_DIR,
validation_split=VALIDATION_RATE,
subset="training",
seed=seed_value,
image_size=(IMG_HEIGHT, IMG_WIDTH),
batch_size=BATCH_SIZE,
color_mode='grayscale' # 白黒モード
)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
TRAIN_DATA_DIR,
validation_split=VALIDATION_RATE,
subset="validation",
seed=seed_value,
image_size=(IMG_HEIGHT, IMG_WIDTH),
batch_size=BATCH_SIZE,
color_mode='grayscale' # 白黒モード
)
# パフォーマンス最適化 (キャッシュやプリフェッチ)
AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
# モデル作成
model = create_model((IMG_HEIGHT, IMG_WIDTH, 1), num_classes)
# モデルのコンパイル
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
log_dir = f"logs/{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
lr_scheduler_callback = tf.keras.callbacks.LearningRateScheduler(scheduler)
# 学習
model.fit(
train_ds,
validation_data=val_ds,
epochs=EPOCHS,
callbacks=[tensorboard_callback, lr_scheduler_callback]
)
# 学習後のモデルを保存する
model.save('trained_model.h5')
if __name__ == '__main__':
main()
モデルの学習推移
LossとAccuracyは下記のように推移しました。
【Lossの推移】
【Accuracyの推移】
100エポックぐらいでLoss、Accuracy共に収束しており、CNNの学習推移としては期待通りの動きです。終終盤はtrainとvalidationの差が開きやや過学習していますが、データセットが少ないことを考えると、むしろこの程度で済んで良かったと言えます。
モデル性能検証
最後にテスト用データをモデルに入力し、モデルの性能を検証しました。
結果として、テストデータ全体のLossは0.25
、Accuracyは0.92
と、高い性能を記録しました。
データセットが少ないながら、これくらいの性能が出るのは当然の結果とも言えます。なぜなら、Aurebeshにはフォントや手書きによる揺らぎがほとんど存在せず、視覚的に非常に一貫した文字だからです。現実世界のアルファベット認識のように、文字ごとのバリエーションに対応する必要がないため、比較的簡単に高精度な認識が可能となっています。
テスト画像1枚ずつに対する予測結果を掲載します。
青文字が正解、赤文字が不正解です。
次のステップ:単語単位での認識へ
今回のモデルは、1文字ずつ切り出された画像からAurebesh文字を認識するというものでした。しかし、実際の応用を考えると、これはあくまで“第一歩”にすぎません。現実には、看板やスクリーンに複数のAurebesh文字が連なって表示されるため、文字単位で認識するだけでは不十分です。
今後のステップとしては、
- 画像内からAurebeshの文字領域を自動検出
- 複数文字を検出・分割し、順に認識していくモデルの構築
といった技術が求められます。今回の検証はそのための足がかりとして活用していきます。