こちらは ドワンゴ Advent Calendar 2021 4日目の記事です。
はじめに
新型コロナウイルス対策の一環としてテレワークが急速に普及し、今はご自宅でお仕事をされている方も多いのではないでしょうか。
私が働く会社でも、感染対策として昨年2月という比較的早い時期から原則在宅勤務が導入され、昨年7月にその制度が恒久化されました。
さて突然ですが、みなさんの仕事部屋に防球ネットは置いてありますか?
私はあります。(マウント)
防球ネットが仕事部屋にあると非常に便利です。仕事で溜まったストレスをいつでも軟式M号球に込めて防球ネットにぶつけて発散することが出来ます。
ただ、仕事部屋の広さは約7帖で、日本の一般的な住宅の1室としては普通の広さかなと思いますが、軟式球を投げるにはあまりに狭過ぎます。
それでも防球ネットは安定して私の投げたボールを受け止めてくれるので素晴らしいのですが、逆に常に同じ手応えしかなく、今投げた球がどの程度の勢いだったのかも知ることが出来ないので、いまいち楽しさに欠けるなと思っていました。
ということで今回作ったものがこちらです。
この記事ではこれを作った過程について書いていきます。
方針
最初に思い浮かぶのは、一般的なスピードガンで用いられる、ドップラー効果による反射波の周波数変化を観測する方式です。
ドップラーセンサーというものがあるらしく、こいつを使えば良いのかなと思うのですが、私は信号処理に詳しくないので手に余りそうなのと、そもそも狭い室内だと乱反射して正しく計測出来ないのではないかという懸念(素人考えなので正しいかはわかりません)があったので、今回は採用しませんでした。
代案として思い付くのはカメラで撮影した映像を解析する方式です。
既にプロ野球での球速計測にもこちらの方式が採用されているらしく*1、簡単な画像処理程度なら扱ったことがあるので、今回はこちらを採用してみました。
カメラ
とりあえずカメラを用意します。fps は高ければ高いほど良いだろうということでこいつを買いました。260 fps らしいです。
試しにボールを投げた様子を撮影するとこんな感じ。
260fps を指定した場合には画像サイズは 640x480 固定になる上、映像もかなり暗くなります。ボールもかなりブレて写ってしまうのですが、お遊び程度の精度を目指すなら十分使えそうだなと思いました。
画像中の対象物検出
カメラから得られるコマ送りの画像それぞれから、ボールを検出していきます。
今回は最もシンプルに、色による検出を採用します。
先ほど撮影した画像だと比較的くっきりとボールが写っていたので輝度だけのフィルタでも何とかなるかと期待していたのですが、さすがに白色のボールを白色の壁の部屋で検出するのは難しいようです。代わりに、ちょうど蛍光色の軟式M号球が売ってたのでそれを買いました。夜間練習用らしいです。
記事冒頭の写真もそうですが、自室に置いてある物は赤・白・黒で統一されているので、黄緑色を検出すればちょうどボールだけが検出される状態になっています。最初に買ったいくつかの器具の配色が偶然同じだったことから生まれた謎のこだわりでしたが、まさかここで役に立つとは思いませんでした。
あとは以下のような流れでボールの中心座標を取得します。それぞれOpenCVにそのまま関数が用意されているようなお決まりの手法なので、何をしているのかを知りたい人はググって下さい。
原画像 | レンズ歪み補正*2 | HSV色空間変換 |
---|---|---|
色範囲による二値化*3 | メディアンフィルタ(ノイズ除去) | 重心計算 |
画像内の座標からの空間座標計算
さて、画像1枚のどこにボールがあるかわかったとしても、以下の図のようにボールが存在する可能性のある直線がわかるだけで、ボールの位置を確定させることは出来ません。
ここにもう1枚、別の角度から撮影した画像があれば、そこから得られる2直線の交点にボールがあることが確定するわけですね。
ということでカメラをさらにもう1つ購入し、自室の2箇所に設置しました。
まず必要なのは1台のカメラから1つの「ボールが存在する可能性のある直線」を得ることです。
その方法については様々なアプローチがあると思いますが、今回は室内に「ちょうどカメラにぴったり収まるように写る4:3の長方形」を考えることにします。(4:3は今回買ったカメラから得られる画像の縦横比です。)
ひとまずボールがその長方形上にあると仮定した場合、その位置は前述の「4:3の長方形」に求めた二値画像の重心座標を対応させてやると簡単に決まるので、あとはカメラとその点を結ぶ直線が「ボールが存在する可能性のある直線」になるわけです。
カメラの空間座標は、部屋のどこかを原点に座標系を定義すればそこからメジャーで計測するだけですし、この「4:3の長方形」の四隅の空間座標も、実際に4:3の板を用意してカメラにすっぽり収まるように配置すれば計測出来るので、それらの値を使えば例の直線を計算することが出来そうです。*4
そしてもう一方のカメラでも同じ直線を求めて、さらにそれら2直線の交点を求めれば、それがボールの空間座標です。実際には測定誤差もあって綺麗に交差するわけではないので、代わりに「2直線を最短で結ぶ線分の中点」を求めることにします。
あとはこれらの計算をプログラムにするだけです。あまり3D座標系に関する処理は扱ったことはなかったのですが、これまでの考え方は高校数学の「空間座標とベクトル」で習った内容がそのまま活用出来るので、当時の記憶を引っ張り出すだけで想定よりは簡単に書くことができました。
出来上がったものはこちらです。
実際に対象物の位置を測った値と比較しても、そこそこ正確な値になっているようでした。
画像の取得タイミングのズレの考慮
前述のように2台のカメラからの画像があれば対象の位置を求めることは出来るはずなのですが、実際には2台が完全に同じタイミングで撮影した画像を取得出来るわけではないため、対象物が高速で動くような状況では正しく計測出来ません。
一応それについての対策を入れてあります。あまり手法として洗練させたわけでないので詳細は省略しますが、ざっくり言うと一方のカメラは時間差で求めた2直線からなる平面で考えることにして、この平面と他方のカメラから求めた直線との交点をボールの位置としています。
速度の計算
ボールの空間座標は取得出来たので、速度を求めるには2つの空間座標の距離を求め、それを経過時間で割るだけです。
経過時間を計算するためにはボールがそれらの空間座標に存在した時刻が必要になるので、今回はカメラの画像が取得できた時のPC側の時刻を使っています。
伝送や処理の遅延等を考えると必ずしも正しいとは言えず、実際に後述するようにここらへんが怪しいことが原因で誤差が生じている可能性があるのですが、他に使える時刻情報もないので一旦そのままやってみます。
動作確認
実際に投げて測ってみた結果は冒頭の動画の通りです。
測定の過程を出力した画像はこちらです。
(測定には10フレーム使えていたので、そのうちの4フレームの抜粋です。)
ぱっと見た限りでは、ちゃんと動いてそう??
たまたま直径40mmほどのウレタン球を一定速度で連続発射する謎のマシンを持ってたので計測してみましたが、毎回大体同じ球速が表示されています。まあ、結局この商品のカタログに球速の記載はないのでこの値が正しいのかはわからないのですが、少なくとも低速で移動する対象においては安定した計測結果が出せていそうです。
ただ、今回の測定方法だと、球速が上がった場合にはボールの像がブレて位置の誤差が大きくなる上、計測に使えるフレーム数も減るので経過時間の誤差も大きくなるという、悪いことしかない状態になります。今回は私が野球経験も運動センスもなかったため球速も大したことなく、ギリギリ信頼出来そうな精度で計測が出来ていたのですが、これ以上球速が上がると厳しい気がします…。
課題など
ここまで書いた流れは概ね画像処理で球速測定しようと思い付いた時点で予想してた通りなのですが、実際にはいくつか想定外の問題もありました。いちいち書くと脱線だらけになるのでここにまとめて書きます。
フレームの取得タイミングが安定しない
稀にあまり信頼できそうにない球速が表示されてしまうことがあるのですが、画像を取得出来る間隔にムラがあることが原因のようでした。撮影間隔にムラがあるのか、PCまで伝送する時に詰まるのか、はたまたPCで取得する際に遅延してるのかはわかりませんが、今回はPC側の時刻情報しか使えない以上、稀に発生する誤差は避けられなさそうです。
計算に利用出来るフレーム数が思ったより少ない
自室の間取りで投球するとリリースポイントから防球ネットまでの距離は 1m くらいしか確保出来なさそうでした。例えば 80km くらいのボールを投げたとしても
1(m) / ( 80(km/h) * 1000(km -> m) / 3600 (h -> s) ) * 260 (frames/s) = 11.7 (frames)
ということで、11枚くらい。前述の通りカメラからフレームを取得するタイミングにはムラがあるものの、フレーム数が多ければ誤差は僅かな範囲に収まるだろうと期待していたのですが、この枚数だとちょっと心許ないです。
天井照明の近くを通るとつらい
投球動作にはある程度スペースが必要だし、防球ネットの後ろにも余裕が必要なので、ボールを検出するのは部屋の中央辺りになってしまうのですが、部屋の中央には天井に照明が設置されています。
上投げだとボールは床から2mくらいの位置から放たれるため、そのままボールが照明の下を通過するとかなりの近距離で強い光にさらされることになり、どんなにHSVの閾値を調整してもボールの全てを正しく検出することは出来ませんでした。
最後に
結局のところ本当に正確なのかという疑念が消えないので、球速が計測できるマシンが置いてあるバッティングセンターまで足を運びました。
結果としては大体同じくらいの球速で表示されている気がします。
まあ、そもそもバッティングセンターの球速測定は正確なのかという話もありますが、「バッセンに置いてあるスピードガンと同程度の球速測定が仕事部屋でいつでも出来ている(気がする)」ということで、とりあえずは満足です。
最後の最後に、一応今回作成したコードを GitHub に上げておきます。
完全に自分の環境だけで動かすつもりで書いており、そのまま他の人が動かすことは厳しいと思いますが、今回説明を省略した部分で実際に何をしているのか気になる方がいらっしゃれば、覗いてみてください。
*1:https://japanese.engadget.com/jp-2019-09-07-jiko.html
*2:実際にはレンズ歪み補正はこのタイミングで行うのではなく、最後に得られた重心座標のみに対して行います。(そっちの方が余計な計算をせずに済むので。)
*3:実はこの画像はわかりやすさのために意図的にHSVの範囲を広げてノイズを出しています。実際には適切なHSV範囲を設定すれば稀にごく僅かなノイズが発生するだけでした。
*4:さすがに面倒なので、「4:3の長方形」の四隅については実物の板を用意するようなことはなく、画像の決まった位置に写る壁・床などの計測しやすい箇所を二つだけ計測すれば残りは計算で求まるようにしたのですが、詳細の説明は省略します