satofumi のすべての投稿

「赤城さんのお風呂タイマー」の仕組み

赤城さんのお風呂タイマーとは

「赤城さんのお風呂タイマー」は「艦隊コレクション -艦これ-」用のタイマーツールです。タイマーの値をこのツールから確認できるため、ゲーム時にタイマーの値を見に行く手間を減らすことができます。「赤城さんのお風呂タイマー」では、タイマー値の取り込みをキャプチャした画像の OCR で行なっています。

処理の概要

赤城さんのお風呂タイマーでの処理の概要は、以下のような流れになります。

    1. ユーザがキャプチャボタンを押したら処理を開始する。
    2. ゲーム画面位置を検出する。
    3. タイマー値の OCR 処理を行う。
    4. 取り込んだタイマー値をファイルに保存し表示に反映させる。

empty_capture wind_0_90 value_captured

詳しい処理は、以降で順に説明していきます。

ゲーム画面位置の検出

キャプチャボタンが押されたときに画面のスクリーンショットを GUI ライブラリの Qt の機能で取得し、その画像内からゲーム画面の位置推定を行います。

具体的には、あらかじめゲーム画面とマッチする画像を用意しておき、画像処理ライブラリの OpenCV のテンプレートマッチングでゲーム画面の位置を推定しています。マッチングに用いる画像はゲーム画面の拡大率が 100 % 用のものと、100 % 以外用の 2種類あります。

ゲーム画面の拡大率が 100 % のときの処理

ゲーム画面と同じ大きさの画像を用意し、OpenCV の matchTemplate() を用いてマッチングを行なっています。マッチング用の画像はツールの data フォルダにあり、何かあった場合にユーザでも変更できるようになっています。

frame
テンプレート画像
frame_100_matched
マッチング結果を赤色で表示

それなりに良い精度で認識してくれます。ありがとう OpenCV !

ゲーム画面の拡大率が 100 % 以外のときの処理

この処理は 100 % 用の画像でのマッチングに失敗した場合に行われます。処理の概要としては4すみ用に L 字型のマッチング画像を用意しておいてマッチングさせ、結果として、ゲーム画像の1辺が推定できればその大きさをゲーム画像として処理します。

frame_success
揃っている辺があれば認識できたとみなす
frame_fail
揃っている辺がなければ認識失敗とみなす
推定した位置の扱い

ここで推定したゲーム画面の位置は、以降のタイマー値の読み込みに成功した場合にキャッシュされます。つまり、タイマー値が適切に読み込めた場合は、次回のキャプチャ時にはこれらのテンプレートマッチングの処理は省略されます。
デバッグ動作時に出力される profile_log.txt によると、この推定には 300 ms 程度の時間がかかっているため、2回目以降はその時間だけツールのレスポンスが早くなります。

タイマー値の OCR 処理

ゲーム画面の位置推定が終わったら、タイマー値がある位置を切り取り Tesseract OCR にて処理します。

OCR 処理結果の取り込み

タイマー値の種類は「遠征」「入渠」「建造」の3種類がありますが、それらのタイマー文字列の場所と大きさはツールの data フォルダにある number_positions.yaml で管理しています。タイマー位置の定義をファイルにしているのは、いずれ UI が微調整されたときに各ユーザでも対応できるようするためです。
data/number_positions.yaml の全文

# Mission
- 1:
? - size: 88x17
? - positions:
??? - 0: +710+391

# Repair
- 2:
? - size: 102x23
? - positions:
??? - 0: +610+160
??? - 1: +610+241
??? - 2: +610+322
??? - 3: +610+403

# Create
- 3:
? - size: 100x26
? - positions:
??? - 0: +394+181
??? - 1: +394+259
??? - 2: +394+337
??? - 3: +394+415

# Mission name
- 4:
? - size: 128x1
? - positions:
??? - 0: +584+118

このツールでは、このファイルで定義された位置の矩形画像に Tesseract OCR を用いた OCR 処理を行います。OCR 処理で得られた文字列が「01:23:45」のように、数値とコロンから構成されていた場合のみ、読み取りが成功したとみなしています。

target_2.0

現状では OCR は tesseract.exe をパッケージに含めて実行してますが、これは Windows の MinGW でのビルドでは Tesseract OCR をライブラリとしてリンクすると実行ファイルである bath_timer.exe の実行に失敗するようになったためです。今でもこの問題の解決方法は調査できていません。

OCR 処理の順番決め

タイマー値には「遠征」「入渠」「建造」の種類がありますが、これらが同時に表示されることはなく、ゲーム画面には1種類しか表示されていません。なので、このツールでは現在のシーンの推定を行い、タイマー値がない場所への OCR 処理を減らすようにしています。

  • やっていること
    • ゲーム画面の画素の色から現在のシーンを推定する。
    • あるシーンのタイマー値が取得できたら、他のシーンのタイマー値の OCR 処理は行わない。
    • 推定したシーンのタイマー値が取得できなかったら、他のシーンのタイマー値について OCR 処理を行う。

scene_estimate_mark? 推定に用いる画素の位置

1回の OCR 処理には 30 ms から 80 ms 程度の時間がかかるため、このような対処を行うことで、(それなりには) ツールのレスポンスを改善しています。

「遠征」の OCR 処理について

遠征以外については、タイマー値の場所が固定なのですが、遠征のみ「遠征の種類」「その遠征のタイマー値」といった構成になっています。

「今のタイマー値がどの遠征名のものなのか」を処理するために、このツールでは遠征名の画像に対応するキーを生成し、そのキーにタイマー値を対応させています。

具体的には、遠征名から 128 x 1 サイズの画像を切り取り、それの CRC 結果をキーとして利用しています。これにより「遠征1」のタイマー値と「遠征2」のタイマー値を区別しています。

mission_rects

取り込んだタイマー値の保存

取り込んだタイマー値は、タイマー値を PC が取り込んだ時刻と取り込んだ値をペアで timer_items.csv というファイルに出力して保存しています。これにより任意の時刻にけるタイマーの残り時間が一意に決まります。

将来的なゲーム UI 変更への対処

私がプログラマでいる限り必要だと思ったメンテナンスは行うつもりですが、私のメンテナンスに我慢できない人がいるかもしれないので、このツールはオープンソースで開発しています。

また、ゲーム UI 変更に伴ってゲーム画面のサイズ変更やタイマー値の位置変更がなされた場合のために、ユーザでも対処できるようにゲーム画面認識用のテンプレート画像やタイマー文字列位置の定義ファイルはツールの data フォルダに配置してあります。(もちろん、このツールが必要なくなる UI 変更は大歓迎です!)

最後に

この記事において誤記、およびわかりにくい箇所があればお知らせ下さい。
修正いたします。

SICK LMS5xx 用の C++ ドライバの作成 (強度情報を取得できるようにした)

暇なわけではないが、メイン仕事をする気が起きなかったので Lms_driver.cpp に強度情報を取得するあたりの機能を実装した。
https://bitbucket.org/satofumi/hobby_robot_sdk/src/b5734dc0ba50bf78957748e978cf05223d2d1f3d8/lib/lidar/Lms_driver.cpp?at=default

lms_viewer_2013_1210
オレンジ色のプロットが強度情報

正面に回帰反射版を置いたところ、そこの値が 16 進数の 8 bit 表記で 0xFE になっているのが確認できた。(0xFF には、ならないようだ)

強度データが 0xFE になる特性を利用すれば、反射版かどうかの判定が簡単にできるはず。是非、次回の実験時にこのあたりの情報をうまく使っていきたい。

残るは複数エコーを取得するあたりだが、その実装はまた今度にしたい。

 

Android ゲームアプリを SDL を使って作ろう (最初の調査)

手元に Nexus 7 があるので、それで動くゲームアプリを作ろうと思った。
SDL が好きなので SDL を使うことにする。まずは SDL チュートリアルのリンク先にある Android 関連の資料を読む。

SDL のチュートリアル: http://wiki.libsdl.org/Tutorials

で、SDL 1.2 までしか使ったことがなかったので Android の資料を読む前に SDL 2.0 についてのチュートリアルを読み進めた。
SDL 1.2 の頃からは、それなりに変わってるのが理解できた。

先は長そうだ。

SICK LMS5xx 用の C++ ドライバ作成 (Scale Factor の修正)

作成中の Lms_driver で取得した距離が 1/2 になっていたので修正した。
LMDscandata コマンド応答の Scale Factor が x2 なのに、取得した距離データをそのまま表示していたのが原因だった。

lms_viewer_2013_1207
修正前

lms_viewer_2013_1207_2nd
修正後

引き続き、強度情報を取得したりするあたりを実装したい。

SICK LMS5xx 用の C++ ドライバ作成

仕事に関係して SICK 社の LMS511 という距離センサを買ってもらったので、それを使うための C++ クラスを作成した。
https://bitbucket.org/satofumi/hobby_robot_sdk/src/0916bb0ee31d6aa207749dfab44581e53ebea180/lib/lidar/Lms_driver.cpp?at=default

lms_511

単に「作成した」とか書くと簡単に作ったように思えるが、使う日まで時間がないのに同僚からの「時間ないんだし付属してきたライブラリがあるなら、それ使えば?」というコメントに、「Java とか使いたくないし、自分で C++ で書いた方が早く使える気がする」とか返したりしたが…、作り始めたら割と面倒だわ出発の時刻が迫ってくるわで大変だった。みなさん、準備はちゃんとしましょう。

作ったライブラリは以前に作成した URG 用のクラス Urg_driver と同様 Lidar クラスを継承しているので、URG 用に作ったツールなんかがそのまま利用できるといいなー、と思っている。

ただ、未実装な機能が多いので引き続き実装していきます。

はじめに

このブログは、会社のサービス用に借りたサーバ上に設置しています。最初は仕事の進捗などをそれっぽく書こうかとも思ったのですが、守秘義務とかいろいろ面倒そうなので、会社でやった「仕事とは関係ないかもしれない記事」を好き勝手に書いていこうと思います。

どうぞ、よろしくお願いいたします。
がんばります!