Table of Contents

3DエンジンでのEasyARの使用

3DエンジンでEasyARを使用する場合、カメラ映像と仮想オブジェクトのレンダリングが必要です。仮想オブジェクトのレンダリングにはカメラ映像との位置合わせが求められます。カメラ映像のレンダリング時には、画像生成時と表示時のパラメータが一致しない可能性があります。例えば物理カメラの位置、向き、画面サイズ、アスペクト比などがディスプレイと異なる場合があり、レンダリング時に考慮する必要があります。EasyARをサポートされていない3Dエンジンに接続する場合は、以下の詳細に特に注意が必要です。

カメラ画像のパディング部分のクロップ

画像のクロッピング、転置、エンコードは計算負荷が高いため、一般的にはより原始的な形式が使用されます。ビデオエンコーディングを容易にするため、物理カメラの出力画像は8x8、16x16、32x32、64x64などのグリッドに揃えられることがよくあります。例えば1920x1080解像度を選択しても、出力画像が1920x1088になる場合があります(1080が64の倍数ではないため)。

image with padding

レンダリング時にはこれらの余分なパディング部分を除去する必要があります。実装方法として複数のアプローチがあります:OpenGLではglPixelStorei(GL_PACK_ROW_LENGTH, ...)を使用して画像をGPUメモリに転送する際に幅を指定する方法や、フラグメントシェーダーでUV座標を手動計算し、画像サンプリング時に超過部分をクリップする方法などがあります。

画面の回転方向に合わせてレンダリング

モバイル端末では、物理カメラが記録する画像は通常、端末本体に対して固定されており、画面表示方向の変化に伴って回転することはありません。しかし端末本体の向きの変化は、画像の上下左右の定義に影響します。レンダリング時には、現在の画面表示方向も表示画像の方向に影響を与えます。

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

$\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} \]
注記

画面画像が回転する場合、回転発生後の最初のフレームで直ちに$\theta$を再計算する必要があります。さもないと一瞬画面の方向が不正になる可能性があります。

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

モバイル端末で仮想オブジェクトをレンダリングするには、仮想オブジェクトとカメラ画像を位置合わせする必要があります。これには仮想空間内のレンダリングカメラとオブジェクトを現実空間と完全に対応させ、物理カメラと同じ視野角とアスペクト比を使用する必要があります。カメラ画像と仮想オブジェクトが通過する透視投影変換はほぼ同一ですが、一点異なります。カメラ画像の透視投影変換の大部分は物理カメラ内で発生するのに対し、仮想オブジェクトの透視投影変換は完全に計算プロセスである点です。

以下ではOpenGLの慣例を使用します。他の慣例を使用する場合は対応する座標軸マッピングが必要です。カメラ座標系を次のように定義:x軸右方向、y軸上方向、z軸画面手前から奥方向。クリップ座標系を次のように定義:x軸右方向、y軸上方向、z軸画面手前から奥方向、w軸仮想軸。

この場合、カメラ画像レンダリングに必要な透視投影変換行列は:

\[ 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$は$\theta$に応じて変化する拡大縮小係数(等倍拡大縮小または等倍パディング用)です。この変換行列は、カメラ画像を拡大縮小→回転→反転の順で処理します。レンダリング時には画面全体を覆う矩形を使用します。例えばOpenGLでは、矩形の頂点を$(-1, -1, 0)\(、\)(1, -1, 0)\(、\)(1, 1, 0)\(、\)(-1, 1, 0)$に配置し、対応する四隅にUV座標を設定してこの透視投影行列でレンダリングします。

仮想オブジェクトレンダリングに必要な透視投影行列は:

\[ 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レンダリングの透視投影行列で使用されるニアクリップ・ファークリップパラメータ、\(w\)、$h$はカメラ画像のピクセル幅・高さ、\(f_x\)\(f_y\)\(c_x\)、$c_y$はカメラモデルで一般的な内部パラメータ(\(f_x\)、$f_y$はピクセル単位焦点距離、\(c_x\)、$c_y$は主点ピクセル位置)です。この投影行列は以下の変換を順次実行します:カメラ内部パラメータの透視投影変換(OpenCVの画像座標系y/z軸方向がOpenGLカメラ座標系と逆のため2回の座標系変換)、画像ピクセル座標系から画像矩形座標系への変換、ニア/ファークリップの変換、カメラ画像レンダリング時の透視投影変換。

整理すると:

\[ 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回(カメラ画像→仮想オブジェクト)実行され、仮想オブジェクトはカメラ画像上にオーバーレイされます。

一部の3Dエンジンでは透視投影行列を水平視野角・アスペクト比等で表現します。回転・反転を無視し主点オフセットを考慮しない場合、水平視野角$\alpha=2 \arctan{\frac{w}{2 f_x}}$、アスペクト比$r=\frac{w}{h}$として計算可能です。

現在のほとんどのモバイル端末カメラでは歪みが非常に小さいため、このプロセスではカメラ歪みを考慮していない点に注意が必要です。