2015年05月30日

PCLと深度バッファを用いたポリゴンモデルのボクセル化

とりあえずボクセル関係で出来そうなこととして、深度バッファを用いたポリゴンのボクセル化を試してみました。

流れ
1. モデル読込み
2. 最大長を指定して、読込んだモデルを囲むボクセルを生成(PCL)
3. Octreeを生成(PCL)

4. 1パスでカラー値と深度値をFBOに出力(OpenGL)
5. 2パスで、OctreeとRayが衝突した表面ボクセルのみを描画(PCL、OpenGL)
6. 3パスで1. と2. をMixして表示(OpenGL)

ここまでの結果
voxelize_001.png
カラーバッファだけの表示も可能
voxelize_002.png

7. 視点からRayを飛ばし、FBOの深度値を参照して深度値より手前にある色のついていないボクセルを削除、深度値付近のボクセルを、FBOのカラー値の色に設定。色を付けたことを示すためにラベルをインクリメント。(PCL)
8. OctreeとCloudの再構築(PCL)

正面から削除/色付けした結果
voxelize_003.png
前後方向から削除/色付けした後横から見た結果
voxelize_004.png

9. いくつかの視点から削除(PCL、OpenGL)

視点を変更することで、前後上下左右で隠れていた部分も削除/色付け可能
voxelize_006.pngvoxelize_007.png

10. 削除されていないかつ色がついていないボクセルを、近傍ボクセルに色がついていればその色に設定し、色がついたボクセルがひとつも見つからなければGUIで設定した基本色を設定(PCL)
(時間がかりすぎるので要最適化)
11. MagicaVoxelに対応している.voxもしくは.slab.voxファイルとして出力
 (.voxファイルは最大256色なので、パレットから似た色に変換)

実行結果
テストとして、[2]〜[4]のモデルを使用させていただき、ボクセル化してMagicaVoxelのレンダラで表示してみました。すべて最大長のボクセル数を512とした結果です。これより小さくするとナンバープレートが潰れたり微妙でした。計算はボクセル数多めにしておいて、出力時にうまく縮小するなどできるといいのかもしれません。

snap0000.png snap0001.png

snap0003.png snap0004.png

snap0002.png snap0005.png

ボクセル数が増えると使用メモリ量・処理時間ともに増えてなかなかつらいところです。

MagicaVoxelの.voxは8ビットと書いているように、ボクセルの座標も8ビット(0〜255)でしか指定できないので、最大でも255x255x255のサイズのボクセルしか構築できません。それ以上のサイズを使用する場合は、生データでサイズが大きくなりますがslabというファイルフォーマットを使用する必要があります。.voxのエクスポータを書いているとき、一部分しか出力されていなくて無駄に悩みました。

MagicaVoxelのレンダラで表示できるのはいいのですが、よく考えるとあまり使い道がないですね…。ポリゴンの方がきれいで軽いですし3Dプリンタなども使う予定はありませんし。マインクラフトとかに取り込めるんですかね。

メモ:マウス操作の操作性が悪いので改善する(距離に応じて移動/回転量を変更)

[1] http://www.naturalsoftware.jp/entry/2012/11/06/161132
[2] 軽自動車:http://seiga.nicovideo.jp/seiga/im4099340
[3] 頭蓋骨:http://flower-dam.com/wp/?p=3824
[4] らば式利根改二:http://www.nicovideo.jp/watch/sm24544789

AABBがちょうど収まるカメラパラメータの決定(修正版)
以前の記事で、Z軸方向から見た場合にAABBをピッタリ収めるカメラパラメータの計算をしていたのですが、横に長すぎる場合の対応をしていませんでした。そこで、横にはみ出るかどうか判定し、はみ出る場合にはlyの変わりにlを使用するように修正しました。後、Z軸方向からだけではなく、上下左右前後から見る場合に対応しました。今後もうひとやる気がでれば、任意の視点と注視点から計算します(ボリュームレンダリングかCSMの時に計算した気もするのですが…)。

aabb_001.png

PCLを使用した点の削除とOctreeの更新
手法としては単純で、7.に書いてあるとおりです。削除するボクセルが決まったら、filterを使用して削除する[1]ようです。
filterを使用するために、以下のライブラリを追加しました。commonを追加したせいか、実行時に必要なDLLが結構増えました。

ライブラリ(リリース)
pcl_octree_release.lib;
pcl_common_release.lib;
pcl_filters_release.lib;

実行時に必要なDLL
$(PCL_ROOT)\bin\pcl_octree_release.dll
$(PCL_ROOT)\bin\pcl_common_release.dll
$(PCL_ROOT)\bin\pcl_filters_release.dll
$(PCL_ROOT)\bin\pcl_kdtree_release.dll
$(PCL_ROOT)\bin\pcl_search_release.dll
$(PCL_ROOT)\bin\pcl_sample_consensus_release.dll

以下ソースコードの一部

#include <pcl/point_cloud.h>
#include <pcl/octree/octree.h>
#include <pcl/filters/extract_indices.h>

pcl::PointCloud<pcl::PointXYZRGBL>::Ptr cloud;
pcl::octree::OctreePointCloudSearch<pcl::PointXYZRGBL> octree;
pcl::PointIndices::Ptr extract_indices;
std::vector<int> k_indices;

int deleteVoxels()
{
  extract_indices->indices.clear();
  double worldPos[4];
  int col[3];
  // 視点位置の設定
  Eigen::Vector3f org(eye[0], eye[1], eye[2]);
  Eigen::Vector3f dir2, dir, target, pPos;
  int hit = 0;
  float depth;
  unsigned int label;
  for (int y=0; y<=endSY; y++)
  {
    for (int x=startSX; x<=endSX; x++)
    {
      // fboから深度値を取得してdepthに格納(省略)
      // 深度値(スクリーン座標)をワールド座標に変換(省略)
      int ret = 0;
      target.x() = worldPos[0];
      target.y() = worldPos[1];
      target.z() = worldPos[2];
      dir = target - org;
      // octreeの関数は方向のみで長さは関係ない?引数の問題?
      float len = dir.norm();

      ret = octree.getIntersectedVoxelIndices(org, dir, k_indices, 1024);
      // 衝突した場合
      if (ret > 0)
      {
        // 全てのボクセルについて削除 or 色づけ or 何もしない
        for (int i=0; i<k_indices.size(); i++)
        {
          // 位置とラベルを取得
          pPos.x() = cloud->points[k_indices[i]].x;
          pPos.y() = cloud->points[k_indices[i]].y;
          pPos.z() = cloud->points[k_indices[i]].z;
          label = cloud->points[k_indices[i]].label;
          dir2 = pPos - org;
          float len2 = dir2.norm();
          // ポリゴンの表面より手前にあり、かつ、
          // 一度も色付けされていない場合だけ削除する
          if ((len2+0.5f) < len && label == 0)
          {
            // クラウドから削除するとともに、Octreeからも取り除かないと
            // その後の検索がおかしくなる?
            extract_indices->indices.push_back(k_indices[i]);
            octree.deleteVoxelAtPoint(k_indices[i]);
            hit++;
          }
          // 削除されずに距離が近いボクセルであれば、カラーバッファの色を塗る
          else if (fabs(len-len2) < 2.0f)
          {
            // カラーバッファから色を取得して設定(省略)
            cloud->points[k_indices[i]].r = col[0];
            cloud->points[k_indices[i]].g = col[1];
            cloud->points[k_indices[i]].b = col[2];
            // 色を塗られた回数を記録しておく
            cloud->points[k_indices[i]].label = min((int)label+1, 255);
          }
        }
      }
    }
  }
  // Cloudから点を消去
  pcl::ExtractIndices<pcl::PointXYZRGBL> extract;
  extract.setInputCloud(cloud);
  extract.setIndices(extract_indices);
  // trueにするとinliersを消去、falseにするとそれ以外を消去
  extract.setNegative(true);
  extract.filterDirectly(cloud);
  return hit;
}
OctreeとCloudの更新方法が良く分からないのでかなり適当です。この方法だと何か色々問題がありそうなのですが、とりあえずうまくいったのでいいかな程度なのでちゃんとした方法がわかりましたら教えてください。
Cloudのデータの参照などがうまく行かない場合があって、Octree経由でアクセスしています。
posted by シンドラー at 13:07 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック