【個人開発】AI技術を用いたジョジョ立ち検定サービスをリリースしましたッ！

[![ogp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/77a9d63b-51e9-04ee-c7b7-4c70be1ba7ef.png)](https://jojo-pose-exam.jp)
AI技術を用いてユーザーのジョジョ立ち画像を解析し、
ジョジョ立ちの合否を判定する検定サービスをリリースいたしました！

**▼ サービスURL**
~~[https://jojo-pose-exam.jp](https://jojo-pose-exam.jp)~~ 
=> **現在サービス停止中🙇‍♂️**

**▼ リポジトリ**
[https://github.com/kohei-yamamoto1012/jojo-pose-exam](https://github.com/kohei-yamamoto1012/jojo-pose-exam)

# はじめに
はじめまして、ジョジョ好きエンジニアのやまもとと申します。

- **「ジョジョ好きが楽しめるサービスを開発したい」**
- **「多く人がジョジョ立ちに挑戦する姿を見てみてたい！」**

そのような思いから、ジョジョ立ちの検定サービスを開発いたしました！

:::note
ジョジョ立ちとは、漫画『ジョジョの奇妙な冒険』にて
キャラクターたちが行う独特なポーズのことであるッ！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/9b0ce31c-87a3-5937-141a-3251ecf73f77.jpeg" width="300">

:::

# 受検手順
ジョジョ立ち検定は以下の3ステップで受検することができます。

1. **検定選択**
検定一覧から受検したいジョジョ立ちの検定を選択します。
リリース時点では、以下の検定がライナップされています。
   - ジョナサン立ち検定
   - ジョルノ立ち検定
   - 承太郎立ち検定

1. **ジョジョ立ち撮影**
検定を選択したら「お手本となるモデル」と「各検定ごとに設定されたチェックポイント」
を参考にジョジョ立ちを行い、スマホカメラ等で撮影します。

1. **ジョジョ立ち画像のアップロード**
撮影したジョジョ立ち画像をアップロードします。
アップロードした画像の解析後に、受検結果が表示されます！
また、受験結果ページへの遷移時には評価コメントのカットイン演出が行われます。

|検定選択ページ|検定詳細ページ|検定結果ページ|
|:---:|:---:|:---:|
|![検定選択.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/ac25e592-9eb8-cb2b-3a90-6e2a7f25b2fc.gif)|![検定詳細.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/1e5958bc-f3c2-22b8-8510-4c680040838e.gif)|![検定結果.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/48b383a3-66d4-4433-a381-6235498f9500.gif)|


# 合否判定処理
ジョジョ立ち検定の要となる合否判定処理について、解説したいと思います。
合否判定処理は、以下の**事前準備**と**処理フロー**によって構成されています。

## 事前準備
合否判定処理中では、機械学習モデルを用いた**姿勢推定(pose detection)** を行います。

:::note
**姿勢推定**とは、画像や映像中の人物の関節(=キーポイント)
を座標データで検出する技術です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/8222d5c9-a644-1025-f2e8-1704ef514c9d.png" width="300">
:::

ジョジョ立ち検定では、姿勢推定用の機械学習モデルとして
**TensorFlow[^1]のMoveNet[^2]を採用しました。**

MoveNetの採用にあたり、１つの課題が発生します。
それは「TensorFlowがRubyをサポートしていない」という事です。

ジョジョ立ち検定はバックエンドにRuby on Railsを採用しているため、
MoveNet(=TensorFlow形式の学習モデル)を実行することができませんでした。

この課題を解決するために、事前準備としてMoveNetをTensorFlow形式から、
Ruby上で実行可能なONNX形式[^3]への変換を行いました。

ONNX形式への変換には、tensorflow-onnx[^4]を用います。
以下のようにtensorflow-onnxを実行して、変換を行います。
```
python -m tf2onnx.convert --opset 15 \
	--saved-model movenet_singlepose_lightning_4 \
	--output movenet_singlepose_lightning_4.onnx
```
※ 各パラメータの詳細については、[tensorflow-onnx公式](https://github.com/onnx/tensorflow-onnx)をご参照ください。


 [^1]: TensorFlowとは、Googleが開発しオープンソースで公開している、機械学習に用いるためのソフトウェアライブラリです。
https://www.tensorflow.org/?hl=ja

[^2]: MoveNetとは、TensorFlow公式によって公開されている、高速・高精度な姿勢推定用の学習済みモデルです。
https://www.tensorflow.org/hub/tutorials/movenet?hl=ja

[^3]: ONNXとは、Open Neural Network Exchangeの略称で、機械学習モデルを様々なフレームワーク間で共有するためのフォーマットです。
https://onnx.ai

[^4]: tensorflow-onnxとは、TensorFlow形式の学習モデルをONNX形式に変換するためのPythonパッケージです。
https://github.com/onnx/tensorflow-onnx

## 処理フロー
### 1. アップロード画像の加工
アップロードされたジョジョ立ち画像をMoveNetの入力形式に変換します。

- **MoveNetの入力形式**
   | 画像サイズ | チャンネル数 | 色順 | 画素値の範囲 |
   | :---: | :---: | :---: | :---: |
   | 192x192 | 3 | RGB | 0 ~ 255 |

- **加工処理**
ruby-vips[^5]を用いて、アップロード画像をMoveNetの入力形式に変換します。
```ruby
  INPUT_IMG_WIDTH = 192
  INPUT_IMG_HEIGHT = 192

  def preprocess(img)
    img = img[0..2] if img.bands > 3
    img_resize = img.thumbnail_image(INPUT_IMG_WIDTH, height: INPUT_IMG_HEIGHT) # アスペクト比を維持しつつリサイズ
    img_resize = img_resize.embed(0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT, extend: :black) # 正方形に0埋め
    img_resize.to_a # Arrayに変換
  end

```

[^5]: ruby-vipsとは、画像処理ライブリラリlibvipsのrubyバインディングです。
https://github.com/libvips/ruby-vips 

### 2. 姿勢推定の実行
画像の加工が完了したら、姿勢推定を実行します。
事前準備にて、ONNX形式に変換したMoveNetをonnxruntime-ruby[^6]を用いて実行します。
```ruby
  ML_MODEL_PATH = 'app/ml_models/movenet_singlepose_lightning_4.onnx'.freeze

  def estimate_pose(img)
    input = preprocess(img) # アップロード画像の加工処理
    model = OnnxRuntime::Model.new(Rails.root.join(ML_MODEL_PATH)) # 学習モデル(MoveNet)の読み込み
    model.predict({ 'input' => [input] })['output_0'][0][0] # 姿勢推定処理の実行
  end
```

- **姿勢推定の結果について**
MoveNetでは出力として、17の検出部位(=キーポイント)の姿勢推定結果が得られます。

  - **17の検出部位**
    鼻、両目、両耳、両肩、両肘、両手首、両腰、両膝、両足首

  - **姿勢推定結果の内容**
    | 値              | 単位 |
    |----------------|----------------| 
    | 検出部位の画像内でのx座標    |(元画像の幅を1とした)割合|
    | 検出部位の画像内でのy座標    |(元画像の高さを1とした)割合|
    | 検出結果の確からしさ |割合|


[^6]: onnxruntime-rubyとは、ONNX形式の学習モデルをRuby上で実行するためのgemパッケージです。
https://github.com/ankane/onnxruntime-ruby

### 3.合否判定 
姿勢推定結果を用いて、以下の手順で合否判定を行います。

1. **各チェックポイントのクリア判定**
各検定ごとに複数のチェックポイントが設定されており、それらのクリア判定を行います。
チェックポイントの一例として、以下のようなものがあげられます。
   |検定名|チェックポイント|クリア条件|配点|
   |---|---|---|---:|
   |ジョナサン立ち検定|右肩を上げる|右肩y座標 > 左肩y座標 > 右肘y座標|20点|
   |承太郎立ち検定|左肘を直角に曲げ、左手を腰の横に|75度 < 左肘の角度 < 105度|20点|

2. **得点の計算**
上記の例のように、各チェックポイントには配点が設定されており、
条件クリアしたチェックポイントの合計点を得点とします。

3. **合否判定**
得点が80点以上となった場合に **「合格」**。
得点が79点以下となった場合に **「不合格」** と判定します。
<br>

:::note
ちなみに関節の角度の計算は、ベクトルを用いて行っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/cc37c909-59df-90f2-ffa7-457920163b88.png)
引用元: https://w3e.kanazawa-it.ac.jp/math/category/vector/henkan-tex.cgi?target=/math/category/vector/naiseki-wo-fukumu-kihonsiki.html
:::
 
# 使用技術
- **Ruby 3.0.2**
- **Rails 6.1.4**
- **Vue.js 2.6.14**
- **主要gemパッケージ**
  - [onnxruntime](https://github.com/ankane/onnxruntime-ruby)
  - [ruby-vips](https://github.com/libvips/ruby-vips)
  - [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby)
  - [alba](https://github.com/okuramasafumi/alba)
  - [seed-fu](https://github.com/mbleigh/seed-fu)
  - [meta-tags](https://github.com/kpumuk/meta-tags)
  - [rubocop](https://github.com/rubocop/rubocop)

- **主要npmパッケージ**
  - [vuetify](https://github.com/vuetifyjs/vuetify)
  - [vue-router](https://github.com/vuejs/vue-router)
  - [vuex](https://github.com/vuejs/vuex/tree/3.x)
  - [vee-validate](https://github.com/logaretm/vee-validate)
  - [vue-typer](https://github.com/cngu/vue-typer)
  - [eslint](https://github.com/eslint/eslint)

- **インフラ**
  - Heroku
  - PostgreSQL
  - Amazon S3

- **姿勢推定**
  - TensorFlowの[MoveNet](https://www.tensorflow.org/hub/tutorials/movenet?hl=ja)

# ER図
![ER図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/c1c2a489-107a-2a85-15ad-1258f3c2994a.png)

# おわりに
最後までご覧いただきありがとうございました！
もし興味を持っていただけましたら、ぜひジョジョ立ち検定に挑戦していただけると嬉しいです。
それでは、**アリーヴェデルチ！(さよならだ)**

▼ Twiiterアカウント
https://twitter.com/koheiyamamoto26

<br>