AndroidとunityでARアプリの原理を探る

 いろいろなAR用のライブラリが出ているのは知っているけれど、それを使ってしまうとARの仕組みが理解できないので、すべて自分で作ってみることにする。

デバイスの姿勢を取得する

 デバイスの傾きの検出を視覚的にわかる様にして見ることにする。
 カメラを中心にして、四方・上下にオブジェクトを置いた空間を作り、デバイスの傾きをカメラの傾きに変えて見えるようにする。

 gyroのQuaternionを、カメラのQuaternionに入れる。

 Quaternion gyro = Input.gyro.attitude;
 Quaternion camrotation=  new Quaternion(-gyro.x, -gyro.y, gyro.z, gyro.w);
 Camera.main.transform.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f) * camrotation;

 デバイスを立てた状態で正しい姿勢にするには、上記のように書くようだ。
 unityのgyroを有効にして、姿勢の値をとってみる。

 表示されたgyroの値に注目してみると、カメラを北向きにしたときに、x,y,zが0に近づいているのがわかる。この値は各軸の回転角だから、gyroは東向き、北向き、頭上方向をそれぞれ軸として座標系が作られているとわかる。以下、この座標系をデバイス座標系と呼ぼう。ARで画面に表示するには、デバイス座標系で目標物がどの方向にあるかを知る必要がある、ということなのだ。

位置情報から自分の姿勢を計算する

 このデバイス座標系の地球との関係を見てみる。
 地球中心から経度・緯度が0度へ向かう線をX軸、経度が90度、緯度が0度に向かう線をY軸、北極に向かう線をZ軸とする。ある地点では、このデバイス座標系は、北向きの軸は緯度、東向きの軸は経度により地球に対して傾いている。頭上向きの軸は地球中心とその地点を結ぶ線の延長ということもできる。

 地球上の地点は経度と緯度で表せるから、ある地点に置かれたデバイス座標系の軸も経度と緯度でで書き表すことができる。そうしてできた軸により作られた座標系をローカル座標系と呼ぶことにする。
 これがデバイス座標系の中でどこに位置するかを知るには、このベクトルにローカル座標系を示す行列の逆行列を掛けてやることで得られるとわかる。

 逆行列掛けてやると、その値はx軸、y軸、z軸で作られた空間に移される。ローカル空間もデバイス空間も東、北、頭上を軸としているので、逆行列で写される空間はデバイス座標系と同じであるということができる。
 つまり、手順としては、
 ①位置情報からその地点での座標系を作るベクトルを求める。
 ②求めたベクトルから、逆行列を求める。
 ③目標地点までの方向ベクトルを2つの座標値の差を取って求める。
 ④方向ベクトルに逆行列を掛けてデバイス空間での値を得る
 これにより、デバイスに対してどこに位置しているか知ることができるであろう。

カメラの視界に入っているか計算する

 デバイスと目標地点の関係は得られた。では、画面上のどこに位置しているか、どうやって知るのか?
 それを判定するために、unityのQuaternionにあるInverseメソッドで、カメラの回転を基に戻すQuaternionを使って判定することを考える。このQuaternionに得られた目標までの方向ベクトルを掛けてやると、カメラが前に向いた状態での方向に移される。

 こうしてやることで値が扱いやすくなる。その方向の上下左右の角度を計算して、カメラの画角と比較してやることで画面に入っているか、画面上のどこに位置するかが得られるのである。

地磁気は真北を指さない

 不便なことに、地球の地磁気は真っすぐに北極点に向かっているわけではなく少し北からずれたところへと向かっている。ついでに言うと、常に一定ではなくゆっくりと変化している。真北と地磁気のずれを磁気偏角という。数度のずれでも、目標までの距離が長いと正確に場所を示さなくなるから、注意が必要だ。これを得る近似式を国土地理院のこのページ公開されているので、得られた方位を磁気偏角で補正するようにする。
 残りの手順は
 ・デバイスの傾きから、Quaternionのinverseを計算する
 ・デバイス座標系の方向ベクトルを逆Quaternionで回転させて前に向いたときの方向に直す。磁気偏角を用いてずれを補正する。
 ・ベクトルの上下、左右角度を得る
  左右角φ = Math.Acos(x / Math.Sqrt(x * x + y * y)) (※yの値で前後のどちらか判別)
  上下角ψ= Math.Asin(z/ Math.Sqrt(x * x + y * y + z * z));
 ・カメラの画角と比較して、画面上に目標が入っているか判定する

画角を図る

 デバイスの画角を得なくては、計算できない。unityから呼び出してAndroidからCamera2などで必要な値を読むプラグインを作って画角を計算することも考えたが、unityのカメラ画像が、androidの仕様と合致するとは限らないとも思ったので、簡単な画角を測るシーンを入れて図ってやることにした。

目印になるようなものを画面上に赤い四角で示した場所に入れて、横回転、縦回転で姿勢を変えることで画角を測定する。簡易なつくりだが、Camera2などで取得した値で計算した画角と大きく違わないので、実用には耐えるだろう。

実際に試してみる

 それでは、上記の手順をコード化したものを作って、試したところをお見せしよう。近くの大きな目標物の経度、緯度を地図から読んで、その方向に印が出るようにしてみた。

 少しずれるけど、なかなか近い所に印が出た。
 しばらく試したところ、金属の近くだと正確な位置が示されないことに気づいた。当たり前か。金属の近くは地磁気が乱れるんだから。ある時なんか、なんだか全く出鱈目な位置を示すな~と思っていたら、橋の上だった。橋脚は大きな金属の塊だしね。
 画面を傾けたら、印はきちんとついて来るんだろうか?

 そこそこ追従する。でも、画面の端に近づくとずれが大きくなる。完全に正確なキャリブレーションは難しいし、カメラのレンズも端の方は歪みがある。広角になればなおさら。
 違った地点から目標を見ると、こんな感じ

 別の地点からも目標の方角に印が出ているから、追従は出来ているといっていいだろう。もちろん、もう少しずれるときもあるし、ぴったりと目標を指す時もある。最近のデバイスはGPSの精度が昔より上がっているので、こういったアプリが正確になるには、コンパスの精度次第、というところのように思われる。コンパスは、いまだにデバイスを8の字に動かしてやるくらいしか精度を上げる方法がないらしい。

地球上の位置を示すのは、実は難しい?

 作ったアプリは、目標地点の緯度、経度、高さを入力して現在地点から方向を示している。上の橋の頭頂部の高さを283メートルとして指定してやると、下の画像のようになる。

 本当は橋のてっぺんに印が来るはずなんだけど、合ってくれない。しかし、それには理由がある。高さが合わないのは、GPSから得られる標高は海面からの高さではなく、地球を楕円としたときのモデルからの高さである。でも、地球は凸凹なので、ジオイド高というものがあって、その上ある地面が標高なのだ。つまり、ジオイドの高さを考慮しないと、実際の高さにはならない。詳しくは、国土地理院のこのページを参照されたい。
 このあたりのジオイド高は37メートルくらいなので、その値を高さに加えてみると

 実際の橋の頂上付近を指している。このように、建物とか山とかを示すARアプリはこのジオイドを知らないと正確な場所を指さないはずで、作るには少しハードルが高い。
 以上がARアプリをライブラリを使わずに作ってみた概要である。少し回りくどい気もするが、原理としてはこんなところなのか、とも思える。
 最後に、今回作ったアプリのAPKファイルを添付しておこう。
 Androidをお持ちの方は、このAPKファイルをダウンロードしてインストールすると、上のアプリを試すことができます。対象バージョンは5.0以降のデバイスで、位置情報とカメラの許可が必要です。初期状態では富士山山頂が目標地点として設定されているので、ボタンを押して、新たな経度・緯度・高さを入力すれば希望の位置に向かって赤い印が表示される(はず)。                                                  今回のアプリを踏まえて、次の記事ではARを使った少し役に立つアプリを作ってみたい。
 ではまた。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする