YOLOv5 (+ roboflow) で麻雀の点数計算をした話

タイトルまんまです。初心者なのでやばいところがあればご教示ください。

-----

牌の画像を撮り、麻雀牌のデータセットを自作しました。これをYOLOv5[^yolo]で学習し検出できるようになりました。また画像から牌の検出、上がり時の点数計算をできるようにしました。

![実際に画像から計算する様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/1ac1e129-8ec0-4e92-7c15-f999d4a7eb44.gif)

今まで同様の取り組みはたくさんあったよう[^kizi]なのですが、鳴きや赤ドラに対応していなかったので楽に学習して精度をあげて[^seido]鳴きと赤ドラに対応[^naki]してみました。

それをwebアプリにして写真を送ってドラ表示牌や親かどうかなどの状況を打ち込んだら計算してくれるようにもしました。

## データセットの作成

### 分類か物体検出か

分類とは、ある画像に写っているものが何なのかを判定します。

物体検出とは、画像の中から物体を検出・分類しバウンディングボックスと呼ばれる長方形で囲むことです。ここでは具体的なアルゴリズムには触れません。進化を重ね検出が高速化されることで動画上での検出も可能になっています。

今回は鳴きを見分けるために画像上での位置が欲しかったため物体検出を選択しました。

YOLOは物体検出の手法・ライブラリです。学習のための画像を突っ込めば良しなにしてくれそうかつ触りやすそうなので、このライブラリを使いました。他にもSSDという手法があったりします。

### 精度向上

事前に麻雀牌の認識をしているサイトをいくつか見ました。それらより精度をよくしようと思い何が必要かを考えましたが、初心者でアルゴリズムの良し悪しがわかりません。

先行例では1種類の牌に1つの画像をランダムな位置に配置したり、加工を加えて学習させていました。つまり元画像が少ない状態から学習していました。そのため精度向上の方法として画像データを増やすことにしました。

テストとして風牌と背面を撮影しぱっと学習させて認識できることを確認しました。

その後いそいそと麻雀牌撮影会を開催しざっと300枚。1枚の牌を含んでいるものと複数枚含んでいるものを用意してこれを学習のための画像データとしました。

![大量の麻雀牌の画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/43247c30-d40f-8f06-8d79-6cbef99d9bda.png)

### アノテーション

学習の前準備の一つにアノテーション、またの名をラベル付けという作業があります。これは画像内の物体を四角で囲み、それが何かを書き込むことです。言葉にするのは簡単ですし、実際簡単だろうと思っていました。

長い。ひたすらに時間がかかる。

300枚×牌数枚＝牌1000枚以上。これをマウスで囲っては九萬なのか中なのかとラベルを付けていきます。

アノテーションのためのソフトはいくつかあり、最近ではブラウザ上でこれを行えるアプリも出てきています。前者の例ではvott[^vott]、後者はroboflow[^roboflow]があります。初め、roboflowを使用していたのですが、UIが使いにくく、またスムーズに画像の切り替えができなかったりしました。そこで、vottでアノテーションをしてそのデータにroboflowの取り込み機能を使いました。

![vottのアノテーション画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/bc50606d-b281-0d3e-8bb7-d547842d1bcd.png)

![roboflowのアノテーション画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/f2e387e0-5df0-81be-ad5b-d301753311bc.png)

### データセット完成

roboflowに取り込む必要はというと、最新のvottではYOLOv5に対応したデータを出力することができないので変換を使うためです。

色々と触ってみた結果、出力時にサイズの変更、クロッピング、黒埋めなどに加えて、ノイズやランダムな回転などを掛けることでデータセットを増やす機能も付いていました。これは僥倖です。学習する画像を1000枚程度に増やし、これを学習データとしました。

![roboflowの出力画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/01eb8a6a-86be-e29e-7076-815324ad5394.png)

## 学習

学習はデータセットを配置しYOLOを適当に叩くだけ[^yolotataku]です。なんて楽なんでしょう。

ところが貧弱ノートPCのCPUではなかなか学習が進みませんし、メモリも足りません。そこで、Google Colab[^colab]上で学習を行いました。Colab上で動かすためのチュートリアルもYOLOが用意していたので、それを参考にしつつ実行。

### さらなる精度向上

作成してみたモデルでは全くと言っていいくらい検出できませんでした。これは学習した画像の解像度が小さすぎたことが原因でした。学習した画像の解像度を上げ、ついでに画像を200枚ほど増やし再度学習。

しかし問題が。元画像自体も増え、さらに画像を回転を掛けるなどして増やした結果、学習時間が伸びてしまいColabの使用制限に引っ掛かりました。

![colabの使用制限](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/866d2434-ee80-a2bd-68ef-0e7bd68995ad.png)

やはり実機でぶん回すしかない……ということで、大学の施設で3080をお借りし1日ほど回してモデルを作成しました。

## 検出結果

作成したモデルで検出してみた結果です。

![IMG_1296.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/a13f83d1-103a-99ea-30f9-6d3da197abd8.jpeg)

![IMG_0671.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/f33a127c-c252-f4a1-289f-f5b1c2ce5b23.jpeg)


![IMG_1301.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/e760e54c-0d1b-cabb-3974-51076fc8a789.jpeg)

上手くいくケースではすべての牌をうまく認識できています。しかし、上手くいっていないケースでは全く認識していなかったり、別の牌と誤認していたりします。

ある程度の解像度を確保し、また画像の種類を増やしてもこういった上手くいかないパターンが残っていました。

ぱっと思いつくのは、学習のための画像内での牌のサイズを出来るだけ一定にしようとカメラを固定して同じような距離感で撮影していたり、同じ部屋の同じ場所で撮影していたためノイズの乗り方が一定になってしまっていたり、といった原因がありそうです。

また、YOLO側のドキュメントでは、学習のためにクラスごとに1500枚以上の画像と10000個分以上の物体を用意することが推奨となっていましたからデータが足りないということも考えられます。1種類の麻雀牌しか持っていないので他の柄の麻雀牌では認識できないというのもあります。

ひとまず今回はこれで完成ということにしました。

## webアプリ化

ここからはおまけみたいなものです。実際は公開できればよかったのですが、精度がいまいちなのとweb周りも初心者なので公開はしません。

モデルに画像を与えれば座標と種類が返ってきます。これを使っていい感じに計算していきます。

まずは牌の座標から鳴きの位置を特定しポン・チー・カンのどれなのかも決定します。

これらをPythonで書かれた点数計算ライブラリmahjong.py[^mahjongpy]に与えることで実現しました。冒頭の画像が上手く動いたときの動作風景です。

![ApplicationFrameHost_gw23TUqFu0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603690/6b284e96-9b24-f706-eecf-33248e9a94cd.png)


## 感想

アプリにするというざっくりとした目標でしたが、無事完走できました。検出精度をもっと上げないと実践で使うのは難しいかな～と思ってますし、使わなかった結果先に点数計算を大体覚えてしまいました。

また、統一されたそこそこ大きなデータセットがないと精度の比較ができないなと感じました。mAPは～とか書きたいよね。誰か自動卓と色んな牌とカメラください。

これをしていたのは2021年の夏休み入る前後なのですが、手牌チューブさんの記事[^tehaitube]を読んで同じの(YOLO)使ってる！！！と思い書きました。調べてみると2020年から公開しているようなので後追い記事になりました。

おしまい。

## 謝辞

相談に乗ってくれた上に卓を囲んでくれた友人たち、機械学習について丁寧に教えてくださった先輩方、PCを貸してくださった大学の方々に感謝します。ありがとうございました。

[^kizi]: https://blog.brainpad.co.jp/entry/2017/11/07/140000
 https://qiita.com/smishima/items/4f79eeb98537bd133e2a など多数。
[^tehaitube]: https://note.com/tehaitube/n/n75082f4f58ba
[^yolo]: https://github.com/ultralytics/yolov5
[^seido]: 上手くいくときといかないときの差が激しい模様。
[^naki]: 三麻の抜きドラは非対応。
[^vott]: https://github.com/microsoft/VoTT
[^roboflow]: https://roboflow.com/
[^yolotataku]: とりあえずwikiを読むとよい。たまに引数が何を持っているかなどを見る必要がありコードを確認するときがある。https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data
[^colab]: https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja
[^mahjongpy]: 1.1.11でバグを踏み、プレリリース版を使用するという場面があった。https://pypi.org/project/mahjong/