catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

ARの原理実験

ARの原理について色々と調べてみました。
結論からいうと、ライブラリを2個組み合わせるだけでそれらしいことはできそうです。
この原理試験を簡単ではありますが試してみたいと思います。
世間ではOpenGVとかvuforiaとかAR向けのフレームワークが出てきていますが、
今回は原理試験が目的なので、フレームワークは使わずにOpenCVOpenGLを使ってみたいと思います。


ARを実現するための基礎技術として

  1. カメラ内部パラメータの推定
  2. カメラ外部パラメータの推定(物体の姿勢推定)

が大きな課題だと思います。
検出対象物の姿勢と位置さえわかれば、あとは3Dグラフィックエンジンを叩いてレンダリングするだけです。
今回はこの2つの基礎技術について実験をしてみました。

実験の目的

今回の実験の目的ですが、
カメラキャリブレーション(カメラ内部パラメータ推定)を行わずに手軽にARを実現できないか?
と思ったのが事の発端です。
例えばARアプリを作ったとしてもキャリブレーション用の器具(チェスボードとか)をユーザーに持ち歩けというのはあまりに無茶ですし、
何かしらの既知の大きさをもった物体(例えば後述のカード)を机に並べるとかでできなくはないですが、手間がかかってしまいます。
色々と調べていくうちに、そもそもキャリブレーションしなくてもいいんじゃないか?という結論に至ったので、
こんな理屈で本当にARらしいことができるのか?を確認することが目的です。

理論的なもの

ARにおける物体の姿勢推定の前に基本的なカメラモデルの理論です。
多くの場合では問題を簡単にするためにピンホールカメラモデルが利用されているようです。
ピンホールカメラモデルは次のような式で表されます。
式:撮影画像上の点 = 内部パラメータ行列 x 外部パラメータ行列 x マーカー座標系の点
論文やwebサイトをあちこち見て回りましたが、最終的にはこのような式で表現されます。
この式には含まれていませんが、実際には歪み補正パラメータというものもあるのですが、次の2つの理由から無視することにしました

  1. 広角レンズでも使っていない限りはそれほど大きくない
  2. 最近の一般的なwebカメラ(特に手元で実験に使うwebカメラ)はそこまで歪みがない

なお、OpenCVで提供されているカメラモデルも同様のものです。
カメラキャリブレーションと3次元再構成 — opencv 2.2 documentation

上記式のパラメータの詳細は論文をあたってもらったほうが正確な情報が得られるので、ここでは深く説明はしません。
(私自身も突っ込んだ質問されても正確に答えられる自信がないというのもあります。)
この式で示されている各パラメータをソフトウェア上で求める方法について軽く見ていきます。
「内部パラメータ行列」ですが、これを求める(推定する)行為がカメラキャリブレーションと呼ばれています。
「撮影画像上の点」はカメラから取り込んだ映像フレームに対して物体検出などの画像処理を行うことで求めることができます。
「マーカー座標系の点」も同様です。

残ったの「外部パラメータ行列」は今回のエントリの主な着目点です。
マーカー座標系の点と撮影画像上の点を正しく対応付けられているとき、上記式のうち未知である外部パラメータ行列を推定することができます。
この行為をARの世界ではマーカーの姿勢推定などと呼ばれています。(アルゴリズム的にはPNP問題を解くとも呼ばれます)
ここでのPNP問題とは「Perspective-n-Pont Problem」のことで、P≠NP問題のことではないです。

今回は平面マーカーを利用するので、全ての点が平面上にあるという前提のもとに姿勢推定を行います。
そのためには4つの対応点が必要とされるようですが、まだ詳しい理屈がわかっていないです。
この点についても追々調べていきたいですね。

さて、画像処理に対するお話は一旦おいて、3Dグラフィックスの概念レベルのお話です。
3Dグラフィックスでジオメトリをレンダリングするための頂点情報の変換手順は概ね
クリッピング座標系 = 射影変換行列 x ビュートランスフォーム x ワールドトランスフォーム x ジオメトリ座標の点
となっています。
実際のレンダリングパイプラインではこのクリッピング座標系から正規化デバイス座標系を経て、スクリーン座標へと変換されます。

この3Dグラフィックレンダリングの手順とカメラモデル、どちらも「スクリーン投影までの変換手順」を定義しているといえます。

座標変換の手順は表現方法や途中で経由する座標系こそ違いますが、
ピンホールカメラモデルも3Dレンダリングパイプラインも目的は同じです。

本題のARですが、ARとは「実世界から取り込んだ映像上に任意の3Dモデルをオーバーレイする」と考えたとき、カメラモデルとレンダリングパイプラインで同一の変換が行えるようになっていれば、話は簡単になりそうです。
具体的には
映像入力について

映像出力について

  • ソフトウェア内の3Dモデル->変換(3Dレンダリング)->オーバーレイ表示する画像

の「変換」にあたる部分が同一の変換を行うのであれば、簡単にARが実現できそうです。


数式的な証明はできていませんが、おいおいやっていきたいと思います。(ARには魅力を感じているので、基礎は押さえておきたい。)
大昔に3Dグラフィックスやり始めた頃にスクリーン投影に関する部分は深くは追っていなかったのが悔やまれます。
やっぱり基礎って大事ですね。

さて、ここまでの考えが正しいとした場合、

などが考えられます。

実世界のカメラをプレビューのみに利用することになるので多少の誤差が出ることは想定されますが、エンターテインメントとしてARアプリを作るなら多少の誤差であれば許容されそうです。(手軽に使えるほうが大事)
逆にミッションクリティカルな分野ではもっと正確なアプローチが求められると思っています。

というわけで本題の実験です。
姿勢推定の実験が目的なので、まずはそれ以外の部分は適当にでっちあげます。

物体検出

本格的に作るのであれば適切な特徴量の検出器などの採用などを検討するところですが、今回の実験でそこまですると本題から逸れてしまいます。
というわけで、実験環境を限定して目的の機能を作ることにします。

  • カメラは手元のwebカメラ
  • 机の真上から、真下に向かって撮影する
  • 検出対象は机の上に転がってたMTG(MagicTheGatharing)の黒枠カード
  • ノイズが邪魔なので、白の厚紙の上にカードを置く

この限定条件下であれば、次のような簡易的な手順でカードを識別できます

  1. カードのイメージを事前にスキャナで取り込む
  2. 入力映像フレームに対する事前処理
  3. 輪郭抽出
  4. スキャナ取り込み画像と入力映像、それぞれの輪郭のモーメントの比較

若干の誤検出はありますが、とりあえずの実験としては目的を充分に果たせます。

物体の点を求める

物体の輪郭のままではマーカー座標系の点との対応付けが困難なので、適当にそれっぽくなるようにしました

  1. 輪郭を長方形に近似し、4つの点を求める
  2. 4つの点をマーカー座標系と対応付けるため、並び順を決める(右上から時計回りに4つ)

これに対応するマーカー座標上の点は物理的に対処します。

  1. 道具箱を開ける
  2. ノギスもしくは物差しなどの測定器を取り出す
  3. 測定(物理)
  4. 物体の点と同様の並び順で座標をソースコード中に定義する

これでマーカーとの点の対応付けもできました。(画像中の座標、物理座標どちらも右上から時計回りに4点あります。)
とりあえずの実験らしく超乱暴な逃げ方ですね。


やっと実験の目的のところまで来ました。

OpenGLレンダリングした結果を使ってキャリブレーションする

キャリブレーションの関数自体はOpenCVに用意されているのでそれを使います。
方法は何でもいいのですが、よく使われているZhangの手法を使います。

  1. チェスボードのイメージを作る
  2. チェスボードのイメージをテクスチャとしてOpenGLに転送する
  3. 板ポリゴンに上記テクスチャを貼ったものをレンダリングして、その結果を取り出す
  4. 取り出したレンダリング結果の画像を使ってキャリブレーションを行う

ここまでで、以下のパラメータがすべてそろいました。

  • 撮影画像上の物体の点(物体検出)
  • カメラ内部パラメータ(キャリブレーション)
  • マーカー上の点(物理的に測定)

カメラからの入力画像中に含まれる物体の姿勢推定を行う

これらを使ってマーカーの位置・姿勢推定を行います。
OpenCVの関数でいうとsolvePnpです。歪み補正パラメータは無視したいのでcv::Mat()を与えておけばいいです。
推定結果が正しいかをとりあえず確認してみると、どうやらそれっぽい推定はできています。
こういったものの確認にはOpenCV3.0のarucoにあるDrawaxis関数がとても便利です。

推定した姿勢情報、位置情報をもとに3Dオブジェクトをレンダリングする

あとは簡単!
とか思っていたら足元をすくわれました。
しくじったポイントをメモに残しておきます。

回転の定義がOpenCVOpenGLで違う

solvePnPが返してくる姿勢推定結果の回転の情報は、xyzの回転ベクトルであり、ベクトルの向きが回転軸、ベクトルの大きさが回転量を表します。
対するopenGLのglRotatefなんかは回転軸と角度を度数で指定します。
変換方法を考えましたが、ちょっと面倒ですね。

そもそも私がやりたいのは姿勢推定で得られた結果をOpenGLでのレンダリングに反映したいだけです。
OpenGL1.x系のAPIをわざわざ叩く必要はないということに気付いたので、glLoadMatrixで行列を直接投げ込むことにしました。
回転行列の生成はopenCVロドリゲスの公式によるものが実装されているので、コレを使います。
これで3x3の回転行列が生成できました。
行列が完成したら、OpenCVからopenGLの行列表現に変換するために転置してあげればOKです。
ついでに、OpenGLの行列は4x4なので、定義に従って残りの成分に平行移動成分などを設定します。

OpenCVOpenGLで座標系と行列の定義が違う

OpenCVのカメラキャプチャの映像をそのままOpenGLレンダリングバッファに転送してしまうと上下反転します。
座標系違いますもんね。。。

  • OpenCVはX+は画面右方向、Y+は画面下方向
  • OpenGLはX+は画面右方向、Y+は画面上方向

です。
カメラからの入力画像自体を毎フレーム上下反転するのはコスト高なので、OpenGLのカメラ行列で解決します。
カメラ行列の生成時にカメラの上向き方向を定義するとき、Y+が画面下方向になるように指定します。
カメラを逆さまにするってことですね。

カメラからの入力画像しかレンダリングされない

実験ではOpenGL1.x系を使っているのですが、レンダリングバッファに対してカメラからの映像を書き込んでしまうと、深度バッファも更新されてしまいます。
そんな深度バッファのところに3Dオブジェクトをレンダリングを試みても期待した結果は得られません。
カメラ映像を書き込んだ後で深度バッファのみクリアしておく必要があります。
ARでは奥から順に

  1. 現実世界の映像
  2. オーバーレイする3Dオブジェクト

となるので、普通のARの使い方であればこれで事足りそうです。

魔法っぽいものをレンダリングしてみた

3Dモデリングは苦手なので、なんとかARらしいことをしてみました。
ARって魔法みたいですよね。カードゲームの世界も剣と魔法のファンタジーですよね。
それっぽくなるようにレンダリングしてみました。

  • 板ポリゴンに六芒星を描いたα付きイメージ(inkscapeで作った)
  • パーティクル用のフレア画像α付き(gimpのグラデーションフレアで作った)
  • 六芒星、パーティクルともに加算半透明
  • 目立つように画面全体の輝度レベルを半分に落としています

f:id:Catalina1344:20160303172057p:plain

静止画なので分かりにくいですが、カードの姿勢推定も動いているので、傾けたり回転させたりするとそれに反応します。
(物体検出がただの輪郭抽出アルゴリズムのせいで、カードを直接手で持ってしまうと検出できなくなります。姿勢は台紙を傾けて確認しました。)

感想・今後の展望

妙なところで躓きましたが、今回の手法であればそれっぽくARできました。
まだ懸念点・疑問点が残っているので、ここに記しておきます

  • 使用したwebカメラは固定焦点だが、オートフォーカスなカメラではこの手法は通用するのだろうか?
  • solvePnp(PNP問題を解く関数)の具体的なアルゴリズムと特性を知っておきたい
  • レンダリングパラメータから一意にカメラ内部パラメータを算出できるのではないか?
  • 手などで遮蔽されるとカードが検出できなくなるのはかっこ悪いので、なんとかしたい。

キャリブレーション結果のカメラ内部パラメータをメモしておきます。しっかりと理論を理解してから計算で求めたほうがよさそうですね。

[580.5345906650882, 0, 320.354109100925;
 0, 580.5217311914437, 239.9422018792191;
 0, 0, 1]


参考にさせていただいたサイト、文献など

http://qiita.com/akira108/items/e5b43810b64cd921a947
http://www.cyber.t.u-tokyo.ac.jp/~tani/class/mech_enshu/enshu2011mi2.pdf
http://www.wakayama-u.ac.jp/~chen/education/cv/pinpara.pdf