【YOLOで見つけてARで空間配置】:「視覚」サポートテクノロジー試行シリーズ
週刊プレイグラウンズ第44号
iPad Swift Playgroundで「見る」をつくる
はじめに
これまで、YOLOを使ってカメラ映像の中からボッチャ球を見つける試行と、ARKitを使って床や空間を認識する試行を続けてきました。
第40号では、なぜYOLOとARKitという異なる技術を組み合わせるのかを説明しました。
簡単に振り返ると、
YOLOで「何がどこに写っているか」を見つける
ARKitで「現実空間」を認識
それぞれ異なる能力があります。
今回は、これらの二つをつなぎます。
YOLOがボッチャ球を見つけたら、その画面上の位置をARKitへ渡し、床の上の3D位置として仮想球を置きます。
そして、YOLOはを停止します。
ARKitの空間管理により、デバイスを動かしてボールが画面から見えなくなっても、ARKitは、仮想球の位置を覚えています。
今号で試すのは、この流れです。
「探して見つける」から「見つけて覚える」へ
YOLOだけを使っている場合、ボールを確認するには、そのたびにカメラ映像の中にボールが写っている必要があります。
画面から外れれば、当然YOLOからも見えなくなります。
今回のコードでは、まずYOLOにボールを見つけてもらいます。
カメラ映像
↓
YOLO
↓
ボール発見ここまではこれまでと同じです。
しかし、見つけた後は、
ボールの2D位置
↓
ARKit
↓
床の3D位置
↓
RealityKitで仮想球を配置と処理を引き継ぎます。一度AR空間に仮想球を置けば、その後はYOLOでボールを追い続ける必要はありません。
つまり、
YOLOは見つける。
ARKitは、その場所を覚える。
という役割分担です。
第40号で考えた二つの技術の組み合わせを、今回は実際のコードとしてつなぎます。
YOLO&ARKitの流れ
アプリを起動すると、まずARKitが床を探します。
床を認識したら、YOLOでボール候補を探します。
ボールが安定して認識されると、その位置を床面へ投影し、そこに半透明の緑色の仮想球を置きます。
床を認識
↓
YOLOでボールを探す
↓
一定回数、安定して検出
↓
ボールの画面上の位置を取得
↓
ARKitが認識した床へ投影
↓
仮想球を配置
↓
YOLO停止仮想球を配置した後に、デバイスを左右に動かしたり、少し離れたりしてみてください。実際のボールがカメラから外れても、仮想球は現実空間の同じ位置に残ります。
ここが今回のポイントです。
YOLOはARKitのカメラ画像を使う
今回、カメラをARKit用とYOLO用に二重に動かしているわけではありません。
ARKitが取得している、
frame.capturedImageを、そのままYOLOにも渡します。カメラを一本化することで、
YOLOが見ている映像と、ARKitが認識している空間を同じものにする
ことができます。
また、今回のコードでは、ARKitで利用可能な範囲から、できるだけ高い解像度の映像を使うようにしています。
今回の試行では、ボッチャ球をある程度近くから確認できることを前提として、実際に試した結果から現在の方法を選んでいます。
実装例によっては異なる工夫が必要になるかもしれません。
長方形の中央の正方形を採用
ARKitから得られるカメラ画像は長方形です。
一方、今回使っているYOLOモデルへの入力は416×416ピクセルの正方形です。そこで、長方形の画像を無理に正方形へ変形するのではなく、中央部分を正方形に切り出します。
YOLOは正方形
YOLOは、正方形の画像を扱う仕様です。しかし、iPadのカメラ画像は長方形です。ここで二つの考え方があります。
1.長辺で正方形を作るー>上下に黒い帯を足して正方形にする
2.短辺でくり抜くー>カメラ画像の左右を切り落とす
この号の統合以前から、2を採用しています。これによりカメライメージの左右を切り捨てることになります。しかし、実際に試すと、今回の目的では、上下に黒帯を足す方法では、カメラ映像内のボールが相対的に小さくなってしまい、認識率が低下し見つけられなくなってしまいました。
そこで、今回のような目的では、左右を切り落とすことにしました。
下部のスライダーでズーム倍率を変えられます。
その中央部分をより狭く切り出します。これは光学ズームではありません。元画像の中央を拡大してYOLOへ渡す、いわゆるデジタルズームです。
ですから、拡大すれば無限に認識精度が上がるわけではありません。
それでも、今回の用途では「ボールの近くまで移動して確認する」使い方を想定しているため、いくつか試した中では扱いやすい方法でした。
視覚サポートでは、常に最高精度のアルゴリズムだけを探すのではなく、実際の使い方も含めて成立する方法を探すことが重要だと考えています。
416×416で見つけた場所を、元の画像へ戻す
今回使っているYOLOは、切り出して416×416に変換した画像を見ています。しかしARKitが扱っているのは、元の長方形のカメラ画像です。
そのため、
YOLOの416×416
↓
切り出す前の正方形
↓
ARKitの元の長方形画像と、検出位置を戻す必要があります。
コードでは、正方形を切り出したときの、
元画像の幅と高さ
正方形の大きさ
左右のオフセット
上下のオフセット
を記録しています。YOLOが「ここにボールがあります」と返した位置を、この情報を使って元画像の位置へ戻します。
こうすることで、
YOLOが見つけた場所とARKitが見ている場所をつなぐ
ことができます。
ボールの中心ではなく下を取得する
YOLOが返すのは、ボールを囲む四角い枠です。
今回、その四角の中心ではなく、
下辺の中央
を使っています。
┌─────────┐
│ │
│ ● │
│ │
└────●────┘
↑
この位置ボールは床の上にあります。
ですから、ボールの中心を床へ投影するよりも、ボールと床が接していると考えられる位置を使った方が、今回の目的には適しています。
もちろんYOLOの矩形は完全ではありません。
それでも「床のどこにボールがあるか」を求める今回の試行では、下辺中央を使うのが分かりやすい方法です。
画面の一点を、床の一点へ変換する
YOLOから得られた位置は、まだ2Dです。
そこでARKitの、
unprojectPointを使います。ARKitはすでに床面を認識しています。
ARKitに「画面上のこの位置は、認識している床のどこに対応しますか?
と尋ねます。すると、画面上の2D位置から、現実空間にある床上の3D位置を得ることができます。
今回は、この床上の点にそのまま球の中心を置くのではありません。
ボッチャ球の半径に相当する約43mmだけ、床面から上へ持ち上げています。これで仮想球の底が床に接するように配置されます。
仮想球を置いたら、YOLOを止める
今回、もう一つ意識しているところがあります。
仮想球を配置した後は、
anchorLocked = trueとしてYOLO推論を停止します。これは単なる処理負荷軽減だけが理由ではありません。
今回確認したいのは、
YOLOが追跡し続けなくても、ARKitだけでその場所を保持できるか
だからです。YOLOの仕事は、
「このボールがここにあります」
とARKitへ教えるところまでです。
後はARKitとRealityKitに任せます。
YOLO
│
│ ボールはここ
↓
ARKit / RealityKit
│
│ ここですね
↓
YOLO停止このバトンタッチが、今回の試行の中心です。
動かしてみてください
床を認識したら、ボールをカメラ中央付近に捉えてみてください。安定して検出されると、ボールの位置に半透明の緑色の仮想球が配置されます。
ここからが実験です。
デバイスを少し横へ動かしてみてください。
実際のボールが画面から外れても構いません。
その後、もう一度ボールの方向へカメラを向けます。
仮想球が同じ場所に残っていれば、ARKitが現実空間の位置として保持しています。
「もう一度探す」を押すと仮想球を削除してYOLOが再開します。
場所や距離、床の模様、明るさなどを変えて試してみてください。
今回のコード
iPad Swift Playground、iOS 18以降、実機で動作します。
アプリ設定の「機能」から、カメラの利用許可を追加してください。
また、YOLOモデルとしてyoloV3.mlmodelをプロジェクトへ追加します。
次の号を参考に、
ContentView.swiftを更新すると良いでしょう。
今回試したこと
今回は、
YOLOが見つけたボール位置を、ARKitが認識した現実空間の位置へ渡す
ところまで進めました。
最初はカメラ画像の中にしか存在しなかった「ボールの位置」が、
画像の中の位置
↓
現実空間の位置へ変わります。
そして一度AR空間へ置けば、YOLOはもう必要ありません。
この変化は小さく見えるかもしれません。
しかし、「カメラに今何が見えているか」だけでなく、**「さっき見つけたものが、この空間のどこにあるか」**を扱えるようになったことになります。
第40号で触れた、
「ARKitは空間を覚えている」
という特徴を、今回は実際のボール検出につなげることができました。
次は
今回は、まず「見つけた場所をAR空間に固定する」ことに絞りました。
まだ試したいことはたくさんあります。
固定した位置が、デバイスを持って移動したときにどの程度安定するのか
複数のボールをどのように扱うのか。
そして、その位置関係を視覚に頼らずどのように伝えるのか。
一つずつ試していきます。
完成した答えを示すというより、
「この技術をこう使ったら、こんなサポートができるかもしれない」
を実際に動くコードで確かめていく。
引き続き、その試行錯誤を紹介していきます。
おわりに ── 応援について
このシリーズの記事も、コードも、添えている.mdファイルも、すべて無料で公開しています。これからも、そのつもりです。視覚サポートのための技術が、少しでも多くの人の手に届くように ── それがこの連載を続けている理由だからです。
そのうえで、もし記事が役に立った、続きを読んでみたいと感じてもらえたら、note のチップ(応援)で背中を押していただけると、とても励みになります。いただいた応援は、実機でのテストや、次の号の試行錯誤を続けていくための力になります。
もちろん、読んでいただくこと自体が、いちばんの応援です。ここまで目を通してくださって、ありがとうございました。次号でまたお会いしましょう。
