コンピュータビジョン向けのレイトレーサを作った

３DコンピュータビジョンをやっているとC++で書いたアルゴリズムの中で自前のカメラのイントリンシックと推定したカメラポーズを使ってメッシュをレンダリングしたくなることがある。
例えばモデルベースのトラッキングをする際のエラー評価とか。
こういうことをしたいときの流派は二つあるようである。

一つはfaceの重心やvertexといったメッシュ表面のスパースな点群をCPUで画像座標にプロジェクションしまくるというもの。
実装が簡単でOpenMPで容易に並列化できるが得られるのは穴だらけの画像になる。
しかも大規模なジオメトリだとリニアに遅くなるしオクルージョンハンドリングを考え出すと面倒くさくなる。

二つ目はOpenGLを使う。
こちらは正攻法で密な普通の画像が得られるしGPUなので速い。
しかしまず環境依存の問題がある。
OS依存の問題がよく起こる。Ubuntuで動いてたのにWindowsでは動かないとかが代表的である。
またOSが同じでもハードウェアでサポートされてるOpenGLのバージョンが違うことによって動かないことがある。
クラッシュすることなく動いたとしてもカメラパラメータがハードコーディングされていることは珍しくない。手元のカメラパラメータからOpenGLのProjection Matrixへの変換を正しく実装するのは骨が折れる。
以上を正しく実装したとしても環境依存の部分を切り分けるifdefが大量に含まれて可読性が悪くなる。
さらに悪いことにはコンテクストの作成などでメイン関数やOpenGLに依存する他のモジュールと密に結合した再利用性が悪いコードになってしまう。
というかそもそもコンピュータビジョン屋なのでOpenGLは勉強したくない。

私が欲しいのは

- メッシュとカメラパラメータをセットしてRender関数を呼ぶと画像がでてくるシンプルなAPIのC++ライブラリで、
- 環境に依存しなくて、
- 疎結合で、
- 30fpsくらいの速度がでて、
- 色は凹凸がわかるように陰影をつけるくらい、もしくは陰影全くなしで
- 描いた画は画面に表示しなくてよい

というコンピュータビジョンのアルゴリズム開発用のレンダラである。

しかし上記の条件を全て満たすデファクトのレンダラはないようである。
そこで自分で作った。
[**Currender: A CPU renderer for computer vision**](https://github.com/unclearness/currender)

OpenCVと同じ座標系のカメラポーズやピンホールカメラモデルのイントリンシックをAPIで直接設定できるのでコンピュータビジョンの人は使いやすいと思う。
必須依存ライブラリも[Eigen](https://github.com/eigenteam/eigen-git-mirror)と[NanoRT](https://github.com/lighttransport/nanort)だけにしてなるべくコンパクトにした。画像の入出力に[stb](https://github.com/nothings/stb)、OBJファイルの読み込みに[tinyobjloader](https://github.com/syoyo/tinyobjloader)を使用したI/OもサポートしているがI/O用の依存ライブラリはCMakeのオプションで使わないように設定することも可能。

デフォルトでは色画像に加えて、コンピュータビジョンで使いそうなデプス画像、ノーマル画像、マスク画像、そしてface id画像をレンダリングする。

|color|depth|
|---|---|
|![color.png](https://qiita-image-store.s3.amazonaws.com/0/250024/48d25cd3-8c30-2e59-a779-0d25b1e62a1e.png)|![vis_depth.png](https://qiita-image-store.s3.amazonaws.com/0/250024/f02e020a-2a33-10b0-98cc-3a1a4dba14af.png)|

|normal|mask|face id|
|---|---|---|
|![vis_normal.png](https://qiita-image-store.s3.amazonaws.com/0/250024/2aeadea6-4453-f53c-d159-0bd6630cd2ae.png)|![mask.png](https://qiita-image-store.s3.amazonaws.com/0/250024/a9fbb9c8-3bc0-7785-b227-5e3a6ff2e2f8.png)|![vis_face_id.png](https://qiita-image-store.s3.amazonaws.com/0/250024/8e8e70f3-bbec-c533-5d1f-62d43369749a.png)|

この立方体をレンダリングするほぼミニマムのサンプルコードのメイン関数はこちら。

```C++
int main() {
  // make an inclined cube mesh with vertex color
  auto mesh = MakeExampleCube();

  // initialize renderer enabling vertex color rendering and lambertian shading
  currender::RendererOption option;
  option.diffuse_color = currender::DiffuseColor::kVertex;
  option.diffuse_shading = currender::DiffuseShading::kLambertian;
  currender::Renderer renderer(option);

  // set mesh
  renderer.set_mesh(mesh);

  // prepare mesh for rendering (e.g. make BVH)
  renderer.PrepareMesh();

  // make PinholeCamera (perspective camera) at origin.
  // its image size is 160 * 120 and its y (vertical) FoV is 50 deg.
  int width = 160;
  int height = 120;
  float fov_y_deg = 50.0f;
  Eigen ::Vector2f principal_point, focal_length;
  CalcIntrinsics(width, height, fov_y_deg, &principal_point, &focal_length);
  auto camera = std::make_shared<currender::PinholeCamera>(
      width, height, Eigen::Affine3d::Identity(), principal_point,
      focal_length);

  // set camera
  renderer.set_camera(camera);

  // render images
  currender::Image3b color;
  currender::Image1f depth;
  currender::Image3f normal;
  currender::Image1b mask;
  currender::Image1i face_id;
  renderer.Render(&color, &depth, &normal, &mask, &face_id);

  // save images
  SaveImages(color, depth, normal, mask, face_id);

  return 0;
}
```

ごちゃごちゃしているので重要な行だけ見ていく。
メッシュをセットし

```C++
  renderer.set_mesh(mesh);
```

メッシュの準備をして

```C++
  renderer.PrepareMesh();
```

カメラをセットし

```C++
  renderer.set_camera(camera);
```

レンダリングする。

```C++
  renderer.Render(&color, &depth, &normal, &mask, &face_id);
```
基本的にはこれだけ。

より詳しい使い方は[minimum_example.cc](https://github.com/unclearness/currender/blob/master/minimum_example.cc)と[examples.cc](https://github.com/unclearness/currender/blob/master/examples.cc)を参照。

欠点はレイトレーシングベースなので画像サイズが大きくなると遅くなってしまうこと。
物理コア4つのCore i7-8550Uのラップトップで15258頂点のbunnyのレンダリングにかかる時間はVGAサイズで30msくらい。処理時間はここからピクセル数に比例してリニアに増える。
しかしコンピュータビジョンだと処理時間やロバスト性の観点からVGA以下の小さい画像サイズを使うことが多いので速度面で困ることはあまりないと思う。

なおface idはpixel-to-faceの対応関係なのでジオメトリ的に見えている全てのface idが描き込まれている保証はない。画像サイズに比べてジオメトリが細かく1ピクセルに複数のfaceが対応してしまう場合にはいずれか一つがそのピクセルに描き込まれる。