SyntaxHighlighter

2011-05-06

kinectで遊んでみる 再び

大型連休にも関わらず、自分は何やっているんだろうと思いつつ、またkinectを触っています。
今回はARっぽいものを作ろうとしたけど、結論から言うと、(例によって)あまり実りの無い実装をしてしまったようです。なので、反面教師的な意味を込めて記録しておきます。

内容は4月にあったkinectハッカソンvol.2 +そのとき構想だけのことろを実際に作ってみたらこうなった、という部分です。

やろうとしたこと
ちょっと前に(今も??)ARってのが流行ってたけど、基本、画像にとあるモデルをオーバーラップさせて表示させているけど、kinect使うと3次元空間上(細かい人だと2.5次元と言ったりするらしい。。。)に(3次元復元した)画像データとモデルを(比較的簡単に、というか、先人たちの情報を使って)表示出来そうなので、頑張ってみようと思いました。
従って、やるべきことは、RGB画像からモデルを表示する領域(以下対象領域)を取得して、その点を3次元上にプロットして、そこにOpenGLでモデルを表示させる、と言うことになります。

まあ、誰かがどこかでやってそうな内容だけど、気にしない、気にしない(あまりそういう調査をしない人なので)。。。

構成
流れはこんな感じ。
  1. RGB画像から、モデルを表示する対象領域を切り出す(手とか、方眼紙とか。。。)
  2. 1.で求めた領域に対応するdepth画像の領域を取り出す
  3. 2.で取り出した領域を3D点に復元する
  4. 3D点から平面を推定して、位置とx,y,z軸を頑張って計算する
  5. 4.で求めた座標上にモデルを表示する
  6. その他の領域を3Dに復元して表示する
使ったツールは、kinect制御にlibfreenect、画像処理ツールとしてOpenCV2.2、描写にOpenGLを使った。言語はCです。

RGB画像の領域を切り出す
調べていないので、とっても推測ですけど、OpenNIとかのボーン検出って、depth画像を元にやっていそうな気がするけど、あまのじゃくな自分としては、RGB画像から領域を取得することにしました。単純しごくな方法で、kinectのRGBカメラ画像から色情報(HSV)を使って切り出すことにしました。
  1. RGBからHSVに変換
  2. 各ピクセルについてしきい値内にあるものを取得
  3. オープニングとクロージングを使ってノイズを削減
  4. 輪郭抽出をして、最も面積が大きいものを対象領域とする
あまりに単純なため、色の明るさが自動で調節されてしまう上に、パラメータが職人的でそのわりに、精度も良くない。あくまでkinectのRGBカメラは表示用なんだなあと思います。
RGB画像
対象領域候補の抽出:上のパラメータを変えて色の範囲を決めます。。。

depth画像の領域を切り出す
RGB画像から取得した対象領域を3次元上に表示する為には、対象領域の対応するdepth画像上の点を取得する必要があります。
そのやり方は単純に、
img_rgb.x = img_depth.x, img_rgb.y = img_depth.y
では無いです。depth画像のある(dx, dy)に対応するRGB画像の(cx, cy)を求める計算方法は、http://nicolas.burrus.name/index.php/Research/KinectCalibration とか http://graphics.stanford.edu/~mdfisher/Kinect.html に詳しいです。これをパラメータ値も含めて、参考にしました。本当はキャリブレーションしないといけないところなんですが、、、

3次元点の復元
depth画像から3次元点の復元が出来ます。これも、上記の2つのリンクに詳しいです。libfreenectのサンプルコードや、ofxKinectにもやり方があります。

抜粋:
 img_depth : depth画像のなまデータ
 mask_hand : RGB画像から抽出した対象領域(名前は昔の名残。。。)
 img_targetarea : RGB画像から抽出した対象領域に対応したdepth領域
 map_3d : depth画像の各点の3次元点を記憶
 map_image : depth画像の各点に対応するRGB画像の座標値を記憶 

cvSetZero(img_targetarea);
 cvSetZero(map_3d);
 cvSetZero(map_image);
 for (int i=0; i<DISPLAY_SIZE_H; i++) {
  for (int j=0; j<DISPLAY_SIZE_W; j++) {
   float tx, ty, tz;
   float w, h;
   uint16_t d = CV_IMAGE_ELEM(img_depth, uint16_t, i, j);
   if (0 < d && d < 2047) {
    transformDepthTo3D(j, i, d, &tx, &ty, &tz);
    CV_IMAGE_ELEM(map_3d, float, i, j*3)     = tx;
    CV_IMAGE_ELEM(map_3d, float, i, j*3 + 1) = ty;
    CV_IMAGE_ELEM(map_3d, float, i, j*3 + 2) = tz;
    transform3DToRGBImage(tx, ty, tz, &w, &h);
    CV_IMAGE_ELEM(map_image, float, i, j*2)     = w;
    CV_IMAGE_ELEM(map_image, float, i, j*2 + 1) = h;
    int rw = cvRound(w);
    int rh = cvRound(h);
    if (0<= rw && rw<DISPLAY_SIZE_W && 0 <= rh && rh < DISPLAY_SIZE_H) {
     uint8_t c = CV_IMAGE_ELEM(mask_hand, uint8_t, rh, rw);
     if (c > 0) {
      // 対象領域
      CV_IMAGE_ELEM(img_targetarea, uint8_t, i, j) = 255;
     }
    }
   }
  }
 }

この辺のコードが出てきたから、自分のやり方がどうもうまく行かない気がしてきました。。。
四角い青いカード
四角い青いカードに対応するdepth画像を切り出したところ

前段の画像処理がうまくないせいか、プログラムが間違っているせいか、キャリブレーションをちゃんとやっていないせいか、はずれ値データが沢山取れてしまいます(そもそも構想から問題があった、が正解のような気がします)。

対象領域の位置とx,y,z軸を計算する
モデル表示の為に、対象領域の3次元上の位置と3軸が必要になります。そのために対象領域の3次元上の平面推定をします(平面が推定できればあとは適当な条件をつけてやれば、位置と3軸が求まります、きっと。。。)。
ところで、3次元点列からの平面推定ってどうやるんでしょう。私は一般的な方法を知りません。あったとしてもとても面倒そう。
z軸の誤差を最小にするする方法なら、例えばこんな感じらしいけど、ある点から平面への垂線の距離を最小にする場合、面倒らしい。このQAページによると、主成分分析を使うといいらしい、とのことで、OpenCVの cvCalcPCA 関数を使って、固有値の大きい順にx, y, z軸を決めることにしました(位置も一緒に求められます)。なので頑張らなくても計算できました。

でも、計算した軸はかなりふらついてしまって、その結果、きれいに物体が表示できないのが正直なところです(主成分分析のせいではなくて、前段の画像処理の部分の制度の問題と思われます)。あまりのふらつき具合に、座標軸なんて推定するだけ無駄のような気になってきます。

座標上にモデルを表示する
対象領域の3次元上の位置と軸を求めたので、そこから、OpenGL上でのMODEL_VIEW変換行列が計算できます。座標変換してお望みのモデルなり何なりを表示出来ます。
抜粋:
 hand_X_axis : 対象領域の各軸
 hand_center : 対象領域の中心

void loadHandPositionMatrix() {
 GLfloat mat[16] = {
  hand_x_axis[0], hand_x_axis[1], hand_x_axis[2], 0,
  hand_y_axis[0], hand_y_axis[1], hand_y_axis[2], 0,
  hand_z_axis[0], hand_z_axis[1], hand_z_axis[2], 0,
  hand_center[0], hand_center[1], hand_center[2], 1
 };
 glMultMatrixf(mat);

その他の領域を3次元に復元して表示
3次元上に生成した点にRGB画像をテクスチャとして貼る方法も、libfreenectのサンプルコードにあります。

結果
対象領域の3次元点
対象領域にモデル表示、その他領域も表示
ちなみに処理の速度を測ってみると、7FPSくらい。かなり微妙。。。

感想と反省
  • RGB画像上で画像処理するのはやはり、あまり賢い選択ではないようです。折角のdepth画像を有効に使って領域を取得した方がいいんだなあと思います。
  • となると、RGBの画像処理を使わずに、OpenNIのボーン検出をして、そこから特徴点の3次元を取得、3次元点を使ってモデルを表示した方が精度が高くなるような気がします。また、きっとそっちの方がkinectらしい気もします。。。
  • あくまでRGB画像処理派なら、ARToolkitを使うということも考えられます。それはどこかの誰かがやっていたような気がしますが。。。
  • OpenCV1.xを少し触ったことあるけど、2x、ヘッダファイルとか変わり過ぎでしょ。関数はそれほど変わってないけど、引数の増えた関数もあった。ウェブ上にもまだあまり日本語情報が無い。。。
  • OpenCVもC++の方が情報が多いような気がするので、C++使えるならそっちを使った方が良いのでしょう(今更勉強する気にはあまりなれないけど)。
  • まあOpenCVとかOpenGLの勉強になったので良しとしよう。。。
  • 例によって自己満足な誰得?な内容です。

0 件のコメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...