【PIFu】3Dモデリングで体脂肪率を出したらとんでもないことになった【Python】

# はじめに
こんにちは。
アメフトチーム、[ノジマ相模原ライズ](http://www.sagamihara-rise.com/)の姫野です。
コーチの傍ら、アメフト界のデータ利用を推進するべく活動しています。
↓前回記事です。
> [スポーツコーチがチームで使える検温アプリをDjangoで作ってみた](https://qiita.com/ryojihimeno/items/73c3a201375aaa419492)

今回は、選手の全身写真から3Dモデリングを生成し、体脂肪率を算出しました。
結論から言うと実用レベルには至らない精度でしたがご紹介します。
# 課題
スポーツチームとなると選手の身体が資本なので、選手のコンディションチェックは重要なわけです。
体重が一番重要な要素の一つではありますが、その次に大事になるのは体脂肪率です。
ただ、体脂肪率を管理しようとしても、市販の数千~数万円の体組成計は精度が悪く、同じ日に測定しても5%とかは普通にズレます。
なにかいい方法がないかな～と探していたところに見つけたのが、コチラ！
>[3Dボディスキャニング・システム『BODYGEE』の日本語版がリリース](https://prtimes.jp/main/html/rd/p/000000010.000048337.html)
>『BODYGEE』最大の強みは、iPadと特殊な3Dカメラのみで、極めて精密なリアル3Dアバターを作成することができる、その”手軽さ”です。
>「体組成」の重要な一要素である体脂肪率は、スキャンした3Dモデルとターンテーブルに内蔵された体重計から取得されるデータから算出されます。この3Dデータを活用した体脂肪率測定アプローチは、現在一般的なインピータンス方式を採用する体組成計に対して精度で優っていることが、大学の研究で明らかになっています。

月額数万円とお高くて手が出せませんが、手法を真似て3Dモデリングを自作していきます。(自分で作ってない)

# 3Dモデルの生成(PIFu)
### PIFu
PIFuとは、FacebookとUSC(南カリフォルニア大)との共同研究で開発された、全身写真から3Dモデルを生成する技術です。([元論文](https://shunsukesaito.github.io/PIFu/))
最近[PIFuHD](https://shunsukesaito.github.io/PIFuHD/)という高解像度化されたものが新しく開発されたので、使っていきます。
ありがたいことに、写真を用意してコチラの[Google Colab](https://colab.research.google.com/drive/11z58bl3meSzo6kFqkahMa35G5jmh2Wgt)をポチポチするだけで3Dモデルができてしまいます。

### やってみた
協力してくれたのは背番号4番の[星野選手](http://www.sagamihara-rise.com/roster/player/p04.html)！
![p04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/227c96d7-b071-715f-8102-006b17b24c70.jpeg)
LBという守備の要のポジションで、声がやたらでかいチームのムードメーカーです。
できたモデルがこちら！
(元写真は半裸で~~きたない~~エロいので載せてません)
<img width=300 alt="hoshino3d.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/75367ca8-a0aa-6a1c-6159-fee49441cfef.png">
足元がちょっと変ですが簡単に3Dモデルができました。
モデリングに使ったのは前からの写真ですが背中側もキレイにできています。
<img width=300 alt="hoshino_back.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/b99f9e51-a7f5-1502-8a2d-763763e22c24.png">
目がキュートですね。
<img width=300 alt="S__5726214.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/e0933803-2723-a5a4-8ba2-f944ef0b0f9f.jpeg">
ここで出来たobjファイルをBlenderで体積に換算していきます。
# Blenderで体積に換算
Blenderとはフリーの3Dモデルソフトです。
体積を計算する機能があるので使っていきます。
こちらの[サイト](http://silverspirecg.blog119.fc2.com/blog-entry-43.html)を参考にしました。
![blender_v.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/48354366-97d7-99b2-fdbe-0a4447e167d6.png)
Blender上では91706cm3と算出されました。
ここでBlender上では縮尺が実世界とは違うので補正する必要があります。
![blender_h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/236fb611-5336-e371-a08c-00bf84883322.png)
blenderのメジャーで測ると星野選手は184.4cmだそうです。
ホントは星野選手は171cmなので、Blenderの縮尺は184.4/171となります。
縮尺の逆数の3乗をBlenderでの体積にかけて補正します。

```体積計算.py
blender_v = 91706
blender_h = 184.42
real_h = 171

calc_v = blender_v * (real_h/blender_h)**3

print('星野選手の体積: {} cm3'.format(round(calc_v)))
```

```実行結果.py
星野選手の体積: 73108 cm3
```

体積に換算できたので、次にいよいよ体脂肪率の計算を行います。

# 体脂肪率の計算
体脂肪率の算出には、市販の体重計に付属されている測定法であるインピーダンス法のほかに、密度法があります。
[現実ではプールに沈めて水中での体重を測ることで換算可能です。](https://www.mhlw.go.jp/content/000656488.pdf)
密度から体脂肪率に変換するには以下の経験式が知られています。

```math
fat = \frac{4.570}{D} - 4.124 \hspace{50pt} Brozek.et.al.\hspace{4pt}1963
```

この場合のDは密度(g/cm3)です。
星野選手の体重は87kgなので、こちらの式を使って体脂肪率を計算していきます。
### 結果

```体脂肪率計算.py
blender_v = 91706
blender_h = 184.42
real_h = 171
real_w =  87

calc_v = blender_v * (real_h/blender_h)**3
D = real_w / calc_v * 1000

fat = 4.570 / D - 4.124

print('星野選手の体脂肪率: {} %'.format(round(fat*100)))
```

```実行結果.py
星野選手の体脂肪率: -28 %
```

ん？

![星野体脂肪.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/ab3fce80-7049-6773-e693-08586802c7a6.png)
人間の限界超えました。

### 星野選手の反応
<img width=300 alt="星野ライン.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/d90ae65c-769f-5f87-682f-6544e90f6f71.png">
気に入ってくれました。

# なぜうまくいかなかったか
今回やったことは、
①写真から3Dモデリング生成
②Blenderで実体積に換算
③密度法で体脂肪率に換算
です。

どこかのプロセスに原因がありそうですが、①では見た目でそれほど違和感ないモデルできているし(足元は汚いけどマイナスになる程か？)、③に至っては医科学会の権威？のような経験則ですので文句のつけようがありません。
②が怪しそうです。
### 髪の毛を除いて再計算
Blenderの身長をもう一度測ります。
髪の毛がもっこりしてるので目視で目減らすと、182.16cmとなりました。
![星野アタマ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/983155/7c45372b-a8ad-8815-6b6a-28ae24f7e925.png)
もう一度計算してみます。

```体脂肪率計算.py
blender_v = 91706
blender_h = 182.16
real_h = 171
real_w =  87

calc_v = blender_v * (real_h/blender_h)**3
D = real_w / calc_v * 1000

fat = 4.570 / D - 4.124

print('星野選手の体脂肪率: {} %'.format(round(fat*100)))
```

```実行結果.py
星野選手の体脂肪率: -14 %
```

なんと-28%から-14%まで変化しました。
依然としてマイナスですが、身長が大きく効いているのは間違いなさそうです。
縮尺の3乗で掛け算してくるのでちょっとのズレが影響大きいんですね。
縮尺のずれが精度不足になっていたようです。
髪の毛の考慮でも大きく変わりますが、足元はうまく撮れてませんし、写真の画角などでも大きく変わるので原理的には難しそうです。。。

# 今後
選手にはキャップをかぶせて定点カメラで写真を撮り、絶対尺度となるルーラーなどを用意しようと思います。

また、ノジマ相模原ライズでは一緒に活動してくれるエンジニアを募集中です！
スタッフ申し込みは[こちらのフォーム](http://www.sagamihara-rise.com/recruit/recruit_staff.html)からお願いします。