こんにちは。
前回は、麻雀CPU全体の設計について紹介しました。
設計が固まり、いよいよ実装に入ります。
「まずはGUIから作り始めたのでは?」と思われるかもしれませんが、実は最初に取り組んだのは画面作りではありませんでした。
最初に挑戦したのは、実物の麻雀牌をカメラで認識することです。
この麻雀CPUは、ゲーム画面の情報を読み取るものではありません。
実際の麻雀卓に牌を並べ、その牌をカメラで撮影し、CPUが次に何を切るかを考えるシステムです。
つまり、牌を認識できなければ、このプロジェクトそのものが成立しません。
今回は、実装で最初に取り組んだ牌認識について、どんなことを考え、どんな試行錯誤をしたのかを紹介します。
実物の牌を認識できるのか?
このプロジェクトで一番不安だったのは、
「本当に実物の牌を認識できるのか?」
ということでした。
CPUの思考はあとから改良できます。
GUIも何度でも作り直せます。
しかし、実物の牌を認識できなければ、その先へ進むことはできません。
スマホゲームであれば、画面の情報をそのまま利用できます。
一方、この麻雀CPUは実際の牌を相手にします。
照明の明るさ。
牌の向き。
影の入り方。
少し並び方が変わるだけでも、見え方は大きく変わります。
人間なら簡単に見分けられる牌でも、機械にとってはそう簡単ではありません。
だからこそ、設計が終わったあと最初に確認したかったのは、
「この方法で本当に牌認識ができるのか」
という一点でした。
最初に取り組んだのは牌認識でした
一般的なアプリ開発では、まず画面を作ることが多いと思います。
画面があれば動いている実感もありますし、開発のモチベーションにもつながります。
実際、私も普段は「まず動くものを作る」という進め方をすることが多いです。
しかし今回は違いました。
この麻雀CPUの特徴は、
「実物の牌を認識すること」
です。
ここが実現できなければ、どんなにGUIを作っても意味がありません。
そのため、まずは技術的に実現できることを確認し、そのあとでGUIやCPUの思考を作ることにしました。
今振り返っても、この順番にして正解だったと思います。
一つのAIですべて認識しようとしていました
最初は、とてもシンプルな構成を考えていました。
AIが画像の中から牌を見つけ、
そのまま
「一萬」
「九筒」
「東」
まで判定してくれればいい。
モデルも一つで済み、実装も分かりやすくなります。
今回採用したのは、「画像の中から物体の位置を見つける」ことが得意なYOLOというAIモデルです。
画像認識ではよく使われているモデル(簡単に説明すると画像内の「どこに」・「なにが」あるかを一回で判別できるモデル)で、まずはこれ一つで最後までできるのではないかと考えていました。
しかし、実際に学習させてみると、思うような結果にはなりませんでした。
認識精度が安定しないだけでなく、
もっと困ったことがありました。
「間違えがどこで起こっているのか判別しづらい」
のです。
牌を見つけられなかったのか。
違う牌を見ているのか。
見つけたけれど種類を間違えたのか。
間違える原因が多く、改善も思うように進みませんでした。
AIの役割を分けることにしました
そこで、一つのAIに全部任せることをやめました。
代わりに、
役割ごとにAIを分ける
という構成へ変更しました。
まず最初のAIは、
「牌がどこにあるか」
「牌が何枚あるか」
だけを担当します。
ここでは牌の種類までは判断しません。
検出した牌を一枚ずつ切り出し、その画像を画像分類用のAIへ渡します。
さらに画像分類も一度ではなく、
最初に
- 萬子
- 筒子
- 索子
- 字牌
の4種類へ分類し、
そのあとで
「一萬なのか二萬なのか」
「東なのか白なのか」
というように細かく判定する二段階構成にしました。
結果としてAIの数は増えましたが、
「どこで認識を間違えたのか」
が分かるようになり、改善もしやすくなりました。
今回は速度よりも確実さを優先しました
画像認識というと、「リアルタイムで高速に動くこと」が重要だと思われるかもしれません。
もちろん速いことは魅力です。
しかし、この麻雀CPUでは優先順位が少し違いました。
対局中、プレイヤーが牌を並べ終わってからCPUが考え始めるまで、数秒かかっても問題ありません。
それよりも大切なのは、
14枚すべての牌を正しく認識すること
でした。
例えば13枚しか認識できていない状態でCPUが思考を始めれば、当然正しい判断はできません。
だから最初のAIには、牌の種類を当てることよりも、
「牌を漏れなく見つけること」
を最優先の役割として持たせました。
役割を明確に分けたことが、結果としてシステム全体の安定につながっています。
学習すれば終わりではありませんでした
ある程度認識できるようになったとき、
「これで完成かな。」
と思っていました。
しかし、その考えはすぐに崩れます。
最初に学習用の画像を撮影したのは昼間でした。
部屋全体が明るく、光も十分あります。
ところが、そのモデルを夜に動かしてみると、認識率が大きく下がってしまいました。
照明が変わっただけで、ここまで精度が落ちるとは予想していませんでした。
そこで、認識ミスを改善するために、再学習用のPythonアプリを作ることにしました。
ゲーム中にサンプルとなる画像を保存し、
ある程度たまったら教師データへ追加して再学習する。
この流れを何度も繰り返しました。
最初は認識ミスも多かったのですが、遊べば遊ぶほど教師データが増え、認識精度も上がっていきました。
しかし、実環境で動作させると100%認識できるには至っておらず、実は今でも繰り返し再学習しています。
AIを作るというより、
AIを少しずつ育てている
そんな感覚です。
「これなら作れる」と思えた瞬間
初めて実物の牌をきちんと認識できた瞬間は、今でも印象に残っています。
もちろん、その時点ではまだ完成には程遠く、認識ミスもありました。
それでも、
「実物の牌をAIが認識した。」
その事実だけで十分でした。
初めて期待どおりに認識したときの画面がこちらです。

※ 開発初期の認識画面。数字は牌のID(0~36)。この時点では認識率はまだ十分ではありませんでしたが、実物の牌を認識できたことで、このプロジェクトを進められる手応えを感じました。
画面に認識結果が表示された瞬間、
「これなら本当に麻雀CPUを作れる。」
そう思えたことが、このプロジェクトの大きな転機だったように思います。
もちろん、このあとも認識率の改善には何度も苦労しました。
しかし、この最初の成功体験があったからこそ、「もっと精度を上げたい」「もっと実用的にしたい」という気持ちで開発を続けることができました。
おわりに
今回は、実装で最初に取り組んだ牌認識について、その考え方や試行錯誤を紹介しました。
実物の牌を認識することは、この麻雀CPUの土台となる機能です。
最初から思いどおりに認識できたわけではなく、一つのAIですべてを判断しようとして失敗したり、撮影環境の違いで認識率が大きく変わったりと、想像以上に試行錯誤の連続でした。
その過程で、AIの役割を分ける構成へ変更したり、再学習の仕組みを取り入れたりしながら、少しずつ精度を改善していきました。
この記事では開発の流れを中心に紹介しましたが、
- なぜYOLOを採用したのか
- AIを三つに分けた理由
- 学習データの作り方
- アノテーションやデータ拡張の工夫
- EfficientNetによる二段階画像分類
- ゲームをしながら精度を上げていく再学習の仕組み
といった内容は、それぞれ技術記事として詳しく紹介していく予定です。
興味のある方は、ぜひあわせて読んでいただけるとうれしいです。
そして次のストーリーでは、認識できるようになった牌を実際に操作するためのGUI作りへ進みます。
最初は一局終わるとアプリが終了する、とてもシンプルな画面からのスタートでした。
そこからどのようにページを増やし、現在のGUIへ発展していったのか、その開発過程を紹介していきます。



コメント