ARアプリとカメラの画角について
フロントエンドエンジニアのらくさんです。ソニックムーブ Advent Calendar 2013 24日目の記事です。
ARアプリの開発では、カメラの画角に合わせて描画内容を調整することが重要になります。これを行わないと、カメラで撮影された画像・映像とアプリが描画したコンテンツが正しく合成されず、おかしな結果になってしまいます。
画角とは
次の図のa, bの角度のことを画角といいます。
この図ではaよりbの角度が大きくなっているため、被写体から同じだけ離れた場所で撮影してもbの方が映る範囲が広くなります。画角は、アナログカメラではフィルムの種類によって、デジタルカメラでは撮像素子の種類によって異なります。またレンズによっても変わります。したがって、スマートフォンの場合は端末の種類毎に画角が異なり、その違いを考慮せずにアプリのコンテンツを合成してしまうと、現実世界の中でコンテンツが出現する場所や、現実世界の物体とコンテンツの相対的な大きさが端末毎に異なってしまいます。
画角を考慮して合成した場合
画角を考慮しないで合成した場合
目次
スマートフォンのカメラの画角
実測により画角を求める
大きさが正確にわかっている被写体をフレームいっぱいに収まるように撮影し、被写体とカメラの距離を計測することで、画角を計算することができます。
a = 2 * atan(w / (2 * z))
35mm判換算焦点距離から画角を計算する
スマートフォンのカメラやデジタルカメラなどの画角は「35mm判換算焦点距離」という値から計算することができます。35mm判換算焦点距離は、iPhone4S以降では撮影した画像のExif情報から知ることができます。iPhone 5sの場合30mm、iPhone 5で33mm、iPhone 4Sで35mmのようです。
英語版Wikipediaの 35 mm equivalent focal length によると、「35mm判換算焦点距離(f35)」と「レンズの焦点距離(f)」および「撮像素子の長辺(w)または対角(d)」の関係式は次のようになります。
f35 = 36.0 * f / w [長辺基準の35mm判換算焦点距離]
f35 = 43.3 * f / d [対角基準の35mm判換算焦点距離]
(36.0と43.3は、35mm判の撮像素子の長辺と対角)
iPhoneの35mm判換算焦点距離は、長辺基準と思われます(後述)。
iPhoneの「撮像素子の長辺」が何mmなのかわかりませんが、撮影した画像のアスペクト比が4:3なので仮に長辺を4mm(短辺3mm)として上記の式に当てはめると、iPhone5sのレンズの焦点距離は3.33mmとなります。
f = f35 * w / 36.0 = 30 * 4 / 36.0 = 3.33
これは、撮像素子のサイズを「仮に」長辺4mmとした場合なので実際の値とは違いますが、画角を求めるには長さの「比」がわかればよいので、これで十分です。次の図と式により、iPhone5sの画角は、長辺方向が61.93°となります(短辺方向は48.46°)。
a = 2 * atan(w / (2 * f)) = 2 * atan(4 / (2 * 3.33)) = 1.081ラジアン = 61.93°
同様にiPhone 5は長辺方向57.22°/短辺方向44.50°、iPhone 4Sは長辺方向54.43°/短辺方向42.18°となります。
なお、実際の焦点距離はExif情報から得ることができ、撮像素子のサイズもGoogleで検索すると見つけることができます。iPhone5の実際の焦点距離は4.13mmで撮像素子のサイズは4.54×3.42mmのようです。これらの値を使って35mm判換算焦点距離を計算してみると、iPhone5の35mm判換算焦点距離(33mm)は長辺基準の値であることがわかります。
f35 = 36.0 * f / w = 36.0 * 4.13 / 4.54 = 32.75 ≒ 33mm
(対角基準で計算すると、43.3 * f / d = 43.3 * 4.13 / sqrt(4.54^2 + 3.42^2) = 31.46)
AndroidのCamera APIから画角を取得する
Androidの場合、カメラAPIから画角を取得することができます。
Camera.Parameters#getHorizontalViewAngle
Camera.Parameters#getVerticalViewAngle
しかし、これらのAPIが正しい値を返さない端末もあるそうです。
http://www.j7lg.com/archives/1547
画角からOpenGLのProjection行列を求める
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
[sourcecode lang="c" highlight="5,6,21,22,23,24,25,26"] GLfloat viewWidth; // 画面(または表示領域)の幅 GLfloat viewHeight; // 画面(または表示領域)の高さ GLfloat cameraWidth; // カメラ画像の幅 GLfloat cameraHeight; // カメラ画像の高さ GLfloat fieldOfViewX; // カメラの幅方向の画角 GLfloat fieldOfViewY; // カメラの高さ方向の画角 GLfloat near; // ニアクリップ GLfloat far; // ファークリップ GLfloat viewAspect = viewWidth / viewHeight; CGfloat cameraAspect = cameraWidth / cameraHeight; GLfloat top, right; if (viewAspect <= cameraAspect) { top = near * tanf(fieldOfViewY/2); right = top * viewAspect; } else { right = near * tanf(fieldOfViewX/2); top = right / viewAspect; } GLfloat projectionMatrix[] = { near/right, 0, 0, 0, 0, near/top, 0, 0, 0, 0, -(far+near)/(far-near), -1, 0, 0, -2*far*near/(far-near), 0 }; [/sourcecode] |
これは、カメラ画像をアスペクト比固定で画面全域に表示する場合です(iOSではAVLayerVideoGravityResizeAspectFillの場合)。他の場合には適宜アレンジしてください。
まとめ
ARアプリの内容によっては、カメラの画角とコンテンツの画角が一致していなくてもそれほど問題にならないものもあります。しかし、画角を一致させることでよりクオリティの高いARアプリを作ることができます。