Table of Contents

3D エンジンで EasyAR を使用する

3D エンジンで EasyAR を使用するには、カメラ画像と仮想オブジェクトをレンダリングする必要があります。レンダリングされる仮想オブジェクトはカメラ画像と位置合わせされている必要があります。カメラ画像をレンダリングするとき、画像生成時と表示時の一部のパラメータが一致しない場合があります。たとえば、物理カメラの位置、向き、フレームサイズ、aspect ratio などのパラメータがディスプレイ画像と異なる場合があり、レンダリング時に考慮する必要があります。EasyAR を未対応の 3D エンジンに接続する必要がある場合は、次の詳細に特に注意してください。

カメラ画像境界 padding のクロップ

画像のクロップ、転置、エンコードはいずれも大きな計算量を必要とします。計算量と latency を減らすため、通常は比較的 raw な形式が使用されます。video encoding を容易にするため、物理カメラが出力する画像は 8x8、16x16、32x32、64x64 などのグリッドにアラインされることがよくあります。たとえば、一部のスマートフォンで 1920x1080 の解像度を選択すると、1080 が 64 の倍数ではないため、出力画像が 1920x1088 になることがあります。

image with padding

そのため、レンダリング時にこれらの余分な padding 部分を取り除く必要があります。方法はいくつかあります。1 つは画像をビデオメモリへアップロードするときに幅を指定する方法で、たとえば OpenGL では glPixelStorei(GL_PACK_ROW_LENGTH, ...) を使用できます。もう 1 つは fragment shader で UV 座標を手動計算し、画像から sampling するときに超過部分を切り捨てる方法です。

画面回転方向に従ってレンダリングする

スマートフォンでは、物理カメラが記録する画像は通常デバイス本体に対して固定され、画面表示方向の変化に伴って変化しません。しかし、スマートフォン本体の向きの変化は、画像の上下左右方向の定義に影響します。レンダリング時には、現在の画面表示方向も表示される画像の向きに影響します。

通常、レンダリング時には、画面表示方向に対するカメラ画像の回転角を決定する必要があります。

\(\theta_{screen}\) を画面画像が画面の自然方向に対して時計回りに回転したラジアン、\(\theta_{phycam}\) を物理カメラ画像が自然方向の画面上で正しく表示されるために時計回りに回転する必要があるラジアン、\(\theta\) を物理カメラ画像が現在の画面上で表示されるために時計回りに回転する必要があるラジアンとします。

背面カメラの場合:

\[ \theta = \theta_{phycam} - \theta_{screen} \]

たとえば Android スマートフォンで、自然方向で使用している場合、\(\theta_{screen} = 0, \theta_{phycam} = \frac{\pi}{2}\) なので、\(\theta = \frac{\pi}{2}\) です。

前面カメラの場合、回転完了後に左右反転を行うなら:

\[ \theta = \theta_{phycam} + \theta_{screen} \]
注記

画面画像が回転したときは、回転が発生した直後の最初の frame でただちに \(\theta\) を再計算する必要があります。そうしないと、画面画像の方向が瞬間的に不正になることがあります。

カメラ背景と仮想オブジェクトのレンダリング

スマートフォン上で仮想オブジェクトをレンダリングするには、仮想オブジェクトをカメラ画像に合わせる必要があります。そのため、render camera とオブジェクトの両方を、実空間に完全に対応する仮想空間内に配置し、物理カメラと同じ field of view と aspect ratio を使用してレンダリングする必要があります。カメラ画像と仮想オブジェクトに適用される perspective projection transforms はほぼ同一です。唯一の違いは、カメラ画像の perspective projection transform の大部分が物理カメラ内で発生するのに対し、仮想オブジェクトの perspective projection transform は完全に計算処理である点です。

以下では OpenGL の慣例を使用します。その他の慣例を使用する場合は、対応する座標軸マッピングが必要です。カメラ座標系の座標軸を次のように定義するとします。x 軸は右、y 軸は上、z 軸は画面の外側を向きます。clip coordinate system の座標軸は次のように定義します。x 軸は右、y 軸は上、z 軸は画面の外側を向き、w 軸は仮想軸です。

このとき、カメラ画像のレンダリングに必要な perspective projection transform matrix は次のとおりです。

\[ P_i=\left( \begin{array}{cccc} (-1)^{\text{flip}} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} \cos (-\theta ) & -\sin (-\theta ) & \phantom{0} & \phantom{0} \\ \sin (-\theta ) & \cos (-\theta ) & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} s_x & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & s_y & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

ここで、flip は画像を左右反転するかどうかを示し、反転する場合は 1、反転しない場合は 0 です。\(\theta\) は画像の時計回り回転角で、単位はラジアンです。\(s_x\)\(s_y\) は proportional scaling または proportional filling に使用するスケール係数で、\(\theta\) に応じて変化します。この transform matrix はまずカメラ画像をスケーリングし、次に回転し、最後に反転します。レンダリング時には画面全体を覆う矩形を使用します。たとえば OpenGL では、矩形の頂点を \((-1, -1, 0)\)\((1, -1, 0)\)\((1, 1, 0)\)\((-1, 1, 0)\) に配置し、UV 座標を対応する 4 つの角に設定して、この perspective projection matrix でレンダリングできます。

仮想オブジェクトのレンダリングに必要な perspective projection matrix は次のとおりです。

\[ P=P_i\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right)\left( \begin{array}{cccc} \frac{2}{w} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & \frac{2}{h} & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} f_x & \phantom{0} & c_x & \phantom{0} \\ \phantom{0} & f_y & c_y & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

ここで、\(n\)\(f\) は通常 3D レンダリングの perspective projection matrix で使用される near clipping と far clipping のパラメータです。\(w\)\(h\) はカメラ画像の pixel 幅と高さです。\(f_x\)\(f_y\)\(c_x\)\(c_y\) はカメラモデルで一般的に使用される内部パラメータで、\(f_x\)\(f_y\) は pixel 焦点距離、\(c_x\)\(c_y\) は principal point の pixel 位置です。この projection matrix は次の変換を順に行います。カメラ内部パラメータの perspective projection transform(OpenCV の画像座標系では y、z 軸方向が OpenGL カメラ座標系と逆であるため、2 回の座標系変換を行います)、画像 pixel 座標系から画像矩形座標系への変換、near clipping と far clipping の変換、カメラ画像をレンダリングするときの perspective projection transform です。

整理すると、次のようになります。

\[ P=P_i\left( \begin{array}{cccc} \frac{2 f_x}{w} & \phantom{0} & 1-\frac{2 c_x}{w} & \phantom{0} \\ \phantom{0} & \frac{2 f_y}{h} & -1+\frac{2 c_y}{h} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right) \]

上記のプロセスから、レンダリングは通常 2 回に分けて行う必要があることがわかります。1 回はカメラ画像をレンダリングし、もう 1 回は仮想オブジェクトをレンダリングし、仮想オブジェクトをカメラ画像の上に重ねます。

一部の 3D エンジンでは、perspective projection matrix を horizontal field of view や aspect ratio などのパラメータで表します。回転と反転を考慮せず、principal point offset を無視する場合、これらは計算可能で、horizontal field of view は \(\alpha=2 arctan{\frac{w}{2 f_x}}\)、aspect ratio は \(r=\frac{w}{h}\) です。

なお、このプロセスでは camera distortion は考慮していません。現在のほとんどのスマートフォンでは camera distortion が非常に軽微だからです。