2019.06.28

イマイチ使えなかったスピードガンアプリを実用的にするたったひとつのアイディア

こんにちは。F.S.です。
今年も野球少年にとって忙しい季節がやって来ました。
ということで、今回は2年前に実験開発したスピードガンアプリをなんとか使えるようにできないかと思案した話です。一応、来たるMR時代に向けて古典的手法(OpenCV)ではありますがコンピュータービジョンを勉強しているつもりです・・・

前作のおさらい


iPhoneカメラ&画像認識で球速計測(いわゆるスピードガン)が実現できるか?

前作の仕組みをおさらいすると、次のようなものでした。
  • 1. 投球を240fpsのスローモーションで撮影
  • 2. 1フレームずつ画像認識で球体検出
  • 3. 画像における球体の2次元座標を3次元実空間に変換
  • 4. 3次元実空間におけるボール位置の変化量から速度を算出
  • 5. 結果を表示
iphone-speedgun-03
この仕組み、内部では測定値から近似式を算出したりと複雑な上に、球体の誤認識やいくつかの精度が低いパラメータの影響を多分に受けます。

結果、計測できたりできなかったり・・・
計測できてもその多くがイマイチな結果でしばらくほったらかしにしてました。

時は流れ、最近子供の投球フォームを分析し始めたこともあり、ふとスピードガンアプリを改良できないかと思い立ちました。

改善アイディア


できるだけシンプルにして精度を損ねる計算をやめれば良いのではないかと考えました。
ポイントは下記です。
  • 3次元空間への変換をやめて、2次元で計算する
    →投球を正面や背後からではなく、横から撮影することを前提とする
  • 画像内にある計測の対象物(ボール)を特定しやすくするために、移動物体を検出する
    →フレーム間の画像の差分から移動物体を判断し、移動距離から速度を求める
これでなんだか実現できそうな気がしてきました。
ただし、ボールの他に動くものがカメラに写り込まないように撮影しなければなりません。

そこでひらめいたたったひとつのアイディアがこちら


「スマホを地面に置く」


結果


こちらの動画をご覧ください。



地面に置いて上空を撮影することで余計な移動物体が写り込まないどころか、ボールの陰影がしっかり撮影できています。カメラが動かないので映像がブレないのも良いところです。

動画後半に出てくる計測結果チェック画像。これを見る限りしっかりとボールを捉えられているのがわかります。


実際に少年野球チームの子供達の投球を計測したところ、6年生エースで 90km/h 強、5年生で 70〜80km/h 強と妥当なレンジの数値が出ました。また、同じ人の投球を計測すると数値がほぼ一緒という安定した結果になりました。

これはもう実用的と言って良いのではないでしょうか。

実装の解説


実装について少しだけ解説します。

インターフェース


前作は普通にカメラプレビュー画面を表示して動画を撮るように投球直前にタップするというものでしたが、地面に置くために音声ガイドに従って投球するように刷新されました。

アプリ起動後の画面。スタートボタンをタップしたらあとは置くだけ。


置きっぱなしで継続して計測できるように、STARTボタンを押した後は下記の一連をタイマー処理で繰り返すようにしています。
  • 1. 投球合図の音声出力 「投球してください」
  • 2. 動画撮影(2秒間)
  • 3. 画像解析・計算
  • 4. 結果の音声出力 「〇〇キロです」
STOPボタンで繰り返しを停止できます。
合図から投球までのタイミングが人それぞれなので、撮影時間は2秒間と長めにとっています。

動画でわかるように合成音声はちょっと微妙・・ですが、これが今の AVSpeechSynthesizer の実力です。

なお、繰り返し中は操作がなくなるので、スリープしないように AppDelegate にて UIApplication.shared.isIdleTimerDisabled = true を入れています。

動画撮影


動画を撮影してフレームごとに画像処理するというのは前作と基本的に同じですが、下記の変更点があります。

・フレーム画像のキャプチャ処理


前作は AVCaptureVideoDataOutput を用いてキャプチャイメージバッファのデータを逐次メモリにコピーしていましたが、バッファの処理落ちが多いのとメモリ不足によるクラッシュを回避するために AVCaptureMovieFileOutput でテンポラリファイルに落とすようにしました。テンポラリファイルからフレーム画像をキャプチャし、必要な画像(移動物体が写っている画像)だけをメモリに保持するようにしました。

※保持した画像データはチェック画像表示のためだけに使うので、球速を計算するだけなら保持は不要です。
※音声ガイドのインターフェースになって前作よりも動画撮影時間を長めにとっているため、メモリ不足の考慮が必要になっています。


・フレームレート


前作は240fps(1/8倍速)でしたが、撮影時間が長くなった分処理量を減らしたいので60fps(1/2倍速)または120fps(1/4倍速)の選択制にしました。

iPhoneの背面カメラの画角はほとんどが 75°(水平:63° 垂直:50°)なので、被写体から1メートル距離をとると水平方向でだいたい1.2mくらいの幅が収まることになります。目安として、球速が100km/sの場合、120fpsでは1フレームで約23cm進み、5フレーム程度が画像処理に使えることになります。(最低で連続した2フレーム分の有効な画像があれば計算ができます)

実験した限りでは、投球が低すぎなければ球速90km/hでも60fps(1/2倍速)で結果が出せています。

ちなみに、今回の実験機種における画像解像度は、60fps, 120fps ともに 1080p です。

フレーム画像処理


画像処理で画像の差分領域を抽出し、結果を回転矩形(cv::RotatedRect)で取得します。OpenCVを用いるのは前作同様ですが、手法は刷新しています。

1. 直前のフレーム画像との差分抽出


cv::absdiff() を用いてグレースケール画像のピクセルの差分を計算します。その後、cv::threshold() で二値化します。

フレーム前後の差分画像の例。ボールが重ならない場合(左)と重なる場合(右)がある。


無駄な処理を省くため、この時点で cv::countNonZero() により差分(ゼロでないピクセル)がなければ次のフレーム処理に移ります。

2. ノイズ除去


cv::morphologyEx() でモフォロジーのオープニング処理を行い、微細な差分を検出してしまった場合のノイズを除去します。

ノイズ除去後でもゼロピクセルの判定を行い、処理継続を判断します。

(参考)モフォロジー – IGUNOSS,Inc.

3. 差分領域の輪郭がフィットする回転矩形を算出


cv::findContours() で取得される差分領域の輪郭情報を cv::minAreaRect() に渡して回転矩形を算出します。

フレーム前後でボールが重ならない場合(左)と重なる場合(右)の領域輪郭(赤色)および回転矩形(黄色)


回転矩形を採用した理由は、フレーム前後でボールが重なってもボールの位置と直径が推定できるようにするためです。

(参考)領域(輪郭)の特徴 — OpenCV-Python Tutorials 1 documentation

球速計算


フレームごとに算出された差分領域の回転矩形をもとに球速を計算します。

1. フレームごとのボール位置の推定


矩形情報から、当該フレームではどの位置にボールが存在するのかを推定します。
適当なフレーム前後で矩形の座標を比較し、進んでいる方向を判断します。

緑の円が推定されたボールの位置


また、差分領域が画像の端で見切れている場合は適切に処理できないため、領域の中心座標が画像の端よりにあるものは無視しています。

2. 球速算出


ここまできたらあとは座標の距離計算をしてフレームの時間差で割るだけです。
ピクセルを実距離に換算する必要がありますが、矩形の狭い方の幅をボールの直径とみなし、その幅を実際の直径約 7cm として計算します。

補足ですが、iPhoneのバックカメラは広角カメラなので撮影される画像にはレンズ歪みがあります。試しにメジャーを撮影すると画像両端および中央で実距離とピクセル幅に差があるのがわかります。キャリブレーションして補正パラメーターを算出する方法がありますが、球速の最終結果にメジアンを採用しているので歪みの影響は小さいものと考えています。実際に何度か補正を試してみましたが、最終結果のズレは0.1km/h以内に収まるものでした。

(参考)OpenCV: カメラキャリブレーション

最後に


前述の結果の通り、思ったよりもうまく行きました。

当初は夜間に家の中で実験していたので、明かりのちらつきノイズがあったりフォーカスが天井にあたることでボールがはっきり写らない状況でした。

屋内で試している時のボール映像。ボールがかなりぼやけています。


そのためオープニングによるノイズ除去とクロージングによる点群の結合を多重にかけており処理時間を要していましたが、屋外だとそこまでの必要はありませんでした。

ということで、屋外での使用を推奨しています。

手持ちのiPhoneがちょっと古いというのもありますが、もう少し早く結果が出ると嬉しいので、処理にかける画像の解像度をどこまで落とせるかチューニングできると良いです。
また、仕組み上バッテリー消費は高めです。それはしょうがないですかね。


それではまた。


次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事