画像処理でダーツの軌道を描画してみた話

##はじめに
どうも．Life is Tech ! 名古屋 iPhoneメンターのちゃーりーです．ずっとスマブラで遊んでます．
昨日の谷口君の記事，面白かったですね．さすが！僕も負けないように頑張ります．
今日はAdvent Calendar 16日目！クリスマスまであと1週間！
残りのメンターの記事も楽しみですね！！
Advent Calendarお決まりの挨拶をしたところで早速ばちイケな記事をどうぞ．

##そもそもなぜこの記事を書いたか
僕は普段大学院で画像処理に関する研究をしています．
画像処理って言うと難しく聞こえるので，身近な例であげると
![300x0w.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/47ce3618-8cf8-8493-cee2-188dee9f2229.jpeg)![IMG_9787.JPG] (https://qiita-image-store.s3.amazonaws.com/0/173720/37d28e96-01bf-da0c-e8d7-1e9d64e2b34a.jpeg)
SNOWのこういうやつとか．
![color.gif](https://r-chapter.com/wp-content/uploads/2018/04/Hair-Color-Change-1-e1524893984812.gif)
あとは，Photoshopで写真を加工したりとかも例としてあげられます．
さらには，自動車の[自動ブレーキシステム](https://toyota.jp/safety/tss/)にも使われています．
どうですか？意外とみなさんの身の回りは画像処理で溢れていませんか？
少なからずその恩恵を受けているわけです．

そして今回の記事で，画像処理のさらなる楽しさと魅力に気づいてもらえたらなと思っています．
画像処理に全く触れたことのない方のために，難しい説明を省きながら図やイラストを使って説明していきます．


##今回の実施内容
今回は，僕の趣味の一つである**ダーツ**に画像処理を落とし込もうと思います．
ダーツ楽しいですよね．この記事を機にダーツの人口が増えることも期待しています．
では，**ダーツにどうやって画像処理を落とし込むか**ですが，今回は，**ダーツの軌道を映像上に描画**することで，自分の投げたダーツがどんな軌道を描いてターゲットに飛んでいくのかをユーザに提供します．そして，その情報を基にユーザがスロー（投げる動作）を修正することで練習の効率化を図ります．


> 下手くその 上級者への 道のりは 己が 下手さを 知りて一歩目 
安西監督 - 井上雄彦『SLAM DUNK（スラムダンク）』

例えばこちらは何も処理を施していない映像です．

![ezgif-2-ac8ff51b24e2.gif](https://qiita-image-store.s3.amazonaws.com/0/173720/d954196e-ab99-f08e-cd4e-3d7c832ebc90.gif)

ダーツがどのように飛んでいるかは分からなくはないです．
でも，それがこんなふうに見えたらもっと分かりやすくないですか？
![スクリーンショット 2018-12-05 15.51.28 2.png](https://qiita-image-store.s3.amazonaws.com/0/173720/4416f956-d88f-be75-f169-4d169860737a.png)
(この軌道は筆者のイメージです)

ちなみに本記事で利用した映像は全て，Bull（まとの真ん中）を狙ったものになります．

![IMG_9810.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/72ea8166-dc35-b286-17b9-0e34c18a3193.jpeg)

今回は画像処理ライブラリの**OpenCV**を利用して，上記の画像のような処理の実装を目指します．
**うーん・・・画像処理って面白そうだね！！！！！！**


##提案手法
処理の流れはこのようになります．

1. 映像の入力
2. フレームの分割
3. ダーツのフライト領域検出
4. フライトの座標検出
5. 座標を前フレームと比較（進行方向の判定）
6. 映像の出力

こんな感じです！順を追って説明していきます．

###1. 映像の入力
そのままです．プログラムに映像を入力します．
今回はこのような映像を入力します．
![ezgif-2-ac8ff51b24e2.gif](https://qiita-image-store.s3.amazonaws.com/0/173720/d954196e-ab99-f08e-cd4e-3d7c832ebc90.gif)
先ほどにも載せましたね．
30fps程度の映像だと，ダーツの速度が速すぎて捉えきれないので，iPhoneXSで撮影したスローモーション映像を利用します．こちらはgifですが，実際に利用した映像はmp4の映像です．

###2. フレームの分割
映像に対して画像処理を施す際，映像を1枚1枚の画像として切り出します．
![フレーム分割図.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/4a2cda97-1dc0-d82d-cd35-77510f6081b5.jpeg)
実は動画というのはこのように，**静止画の集まり**なのです．
パラパラマンガをイメージしてもらえると分かりやすいと思います．
切り出した1枚1枚の静止画に対して処理を行うため，フレームの分割を行います．

###3. ダーツのフライト領域検出
さて，次は切り出したフレームから，**どうやってダーツを検出するか** です．
実はこれ，めちゃくちゃ簡単にできちゃうんですよ！！
その方法がこちら　
![IMG_9793.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/7f2bae72-d54f-6139-ce0c-afaf9d40a89e.jpeg)
**ダーツのフライト（羽部分）を緑にする**です．
なぜ，これだけでダーツが認識できるかというと・・・
クロマキー合成って知ってます？映像に携わっている方には身近かもしれません．よく映画とかで使われるあれです．

![greenback.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/06c62ade-c9bb-c861-c653-65e0c77dbbed.jpeg)![GREEN_hashi_TP_V.png](https://qiita-image-store.s3.amazonaws.com/0/173720/5b92e1d3-3729-33d8-55f1-6a13c04a4232.png)

緑の領域だけを切り抜くことで
手だけの画像にすることができます．

この技術の応用でダーツの位置が認識できてしまいます．
つまり**映像上の緑の領域はダーツのフライトの部分だよね**とあらかじめ決めてしまうのです．

そして，ダーツのフライトの領域のみを抽出した画像がこちらになります．
![img157.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/520de66b-b8bb-ee92-70e5-4debda2c316a.jpeg)![img157color.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/de7623c8-001a-d795-8170-a4fbee0c235b.jpeg)
どうですか？ちょっと見えづらいですけど，しっかりと飛んでいるダーツのフライトを検出できていますよね．
ちなみに，フライト部分以外はわざと真っ黒に塗りつぶしてあります．
###4. フライトの座標検出
先ほどのフライト領域のみを抽出した画像を二値化します．
二値化画像とは，画素の色が二種類の画像のことです．基本的には関心を当てたい部分を白，それ以外の部分を黒とします．
今回の場合は
![img157.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/977b1d7d-5f43-f3e4-edf8-30995ef2ac69.jpeg)![img157bin.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/d6ac9a2a-b36b-f8fc-9702-8d1983b52b70.jpeg)

このようにフライトの領域を白，それ以外の部分を黒としました．

しかし，このままではフライトの座標は特定できません．
なぜなら，現状では**フライトのある白い部分を塊とは認識していないから**です．
そこで，二値化画像に対してラベリング処理を施します．
ラベリング処理とは，画像中の隣合った画素を抽出する解析手法です．
![ラベリングイメージ図.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/24dc50b2-1edd-d378-c755-cd6f26ca4c74.jpeg)
するとこちらのような特定の領域（今回は白い部分）を一つの塊として認識してくれます．
そうすれば，OpenCVの関数で特定領域の重心座標を検出することができます．
あとはその座標を配列に加えてあげることで，フレームごとのフライトの座標が配列に記録されていきます．

###5. 座標を前フレームと比較
次は前後のフレームで座標を比較します．ターゲットの方向を正，反対を負とした時，**ダーツが正の向きに動いているか負の向きに動いているかを判定**します．この処理は実装しなくてもシステムとして成り立ちますが，描画した時に分かりやすいと思うので実装してみます．

では具体的にどうやって実装するかですが，あるフレーム*n*に着目して説明します．

フレーム*n*にあるフライト座標はこうなっています．
![フレームn.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/aa3dee69-4eaa-c312-b807-06f5df17b72c.jpeg)
そして，フレーム*n*の前フレームにあたるフレーム*n*-1のフライト座標はこうです
![フレームn-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/757d77da-91b1-112c-0dca-956e790598a0.jpeg)
では，フライトの座標を記録している配列の中身はどうなっているでしょうか．
![配列.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/aaab7ca3-0f3b-0143-c792-d60323c6a418.jpeg)
このようになっているはずですね．
なので

```cpp
// indexには現在のフレーム番号が入っている
if ( フライト座標配列[index - 1].x < フライト座標配列[index].x ) {
// 正の向きに動いている
} else {
// 負の向きに動いている
}

```
と比較してあげれば前後の位置関係から描画の処理を分岐することができます．
後は，描画する色を変更すれば視覚的にも進行方向の違いを提示することができます．

###6. 映像の出力
最後に**一連の処理を施したフレームを映像として記録**していきます．
![映像出力図.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/0b6b9327-669b-0eae-a5c7-d9da8a8e55bb.jpeg)

こういうイメージです．

これでダーツの軌道を描画した映像ファイルが出来上がるわけです．

##実験結果
では，これらの処理を施した映像がどうなったかというと・・・
![resultVideo.gif](https://qiita-image-store.s3.amazonaws.com/0/173720/d13c240e-61aa-0e36-a5b5-4203fa614d41.gif)
見事！ダーツの軌道を描画することに成功しています．
![result 2.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/2692aa81-61fc-31c0-9556-fffeb90d1277.jpeg)![スクリーンショット 2018-12-05 15.51.28 2 2.png](https://qiita-image-store.s3.amazonaws.com/0/173720/f2cb37ec-e18d-247d-7d6e-c45aee9fa826.png)

最初に予測して書いてみた画像に比べると，違った軌道で飛んでいることが分かりますね．
![result 2.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/bc45c09b-4aef-ce6e-9960-82a595e51a17.jpeg)![out 2.jpg](https://qiita-image-store.s3.amazonaws.com/0/173720/f6979bb7-8cad-a148-8c77-9b72ebb7795c.jpeg)
また，狙ったターゲットよりも少し上に外してしまった時（右側画像）と比べてみると，投げ始めの軌道が若干高い位置にあることがダーツの軌道から分かります．

**いや〜！すごい！ダーツ × 画像処理を実現することができました！
まさかプログラミングでスポーツが上手くなるキッカケを作れるとは！！！！
どうです？画像処理ワクワクしませんか？**

##今後の課題(解決したい優先順位順)
実装から実験まで行い，思ったことです．結構真面目な話なので読み飛ばしてもらって構いません．
###動画分割の自動化
現状，撮影した動画のスローイングシーンをトリミングしてプログラムに入力しています．
ぶっちゃけ，それがめちゃくちゃ手間です．
なので，オプティカルフロー（物体の動きをベクトルで表したもの）を利用して，スローイング動作，ダーツの飛行の特性を明らかにすることで動画の分割を自動化できないかと思っています．
###描画できていない時がある
結果画像からも分かる通り，軌道が一部歯抜けになっているところがあります．
原因は部屋の照明によって，フライトの色が白とび，もしくは緑以外になっていることが挙げられます．
これは割とどうしようもない外的要因です．
解決するとしたら色のみではなく，フレーム間差分や背景差分を利用して動的物体を抽出し，その上で色情報を基にダーツを検出するのがいいのではないのかと思います．
その場合，スローイングしている人をどう考慮して例外処理するかが難しそうです．

##終わりに
ここまで読んでくださり，ありがとうございます．
画像処理って簡単で楽しいものとわかっていただけたと思います．
明日はおしゃれイケメンスーパーデザイナーりゅーじん君の記事です！

> 高まる期待 明るい未来 見ろよ絶対 はやく読みたい りゅーじんQiita チェケ
> MC ちゃーりー

それでは．