かみのメモ

コンピュータビジョン・プログラムな話題中心の勉強メモ(記事一覧は https://kamino.hatenablog.com/archive へ)

OpenCVを使ってグレイコードパターンを投影する

よく忘れるので、自分用にOpenCVを使ったグレイコードパターン投影のテンプレートを作りました。 ついでなので解説記事を書いてみます。 サンプルプログラムの言語はC++Pythonです。

github.com

目次

1. グレイコードパターン投影とは?

MRの分野でいうグレイコードパターン投影は空間コード化法の一種です。

空間コード化法とは、プロジェクタの投影像をカメラで撮影しているときに、あるカメラの画素にどのプロジェクタ画素が写り込んでいるかを分析するためのアルゴリズムです。 プロジェクタ・カメラのキャリブレーションプロジェクションマッピングをする際のキャリブレーション、3Dオブジェクトの形状計測などに利用されます。

グレイコードパターン投影はこれをグレイコード(gray code)を利用して行う手法を指します。

1.1. 基本的な原理

グレイコードパターン投影では、プロジェクタ画素(もしくは画素ブロック)の座標をグレイコードに変換して、それに応じた白黒パターンを画像として表示します。 これをカメラから撮影し、カメラの各画素にどのようなパターンが写り込んだかを分析することでカメラ画素とプロジェクタ画素の対応関係を取得します。

例えば横幅を10ブロックに分割するとき、X座標は4ビットのグレイコードで表現できます(24 = 16)。

 0: 0000     4: 0110     8: 1100    12: 1010
 1: 0001     5: 0111     9: 1101    13: 1011
 2: 0011     6: 0101    10: 1111    14: 1001
 3: 0010     7: 0100    11: 1110    15: 1000

つまりX座標を判別するためには、以下のような4枚のパターン画像を投影してやればよいです。

f:id:kamino-dev:20180921141751p:plain

これをカメラで撮影した時、ある画素に「黒黒白白」というようなパターンが写り込んだなら0011 <-> 2より投影した画像のX=2のブロックが写り込んでいるということが判別できます。

バイナリコード(2進数)ではなくグレイコードを使う理由は、パターン画像の縞模様の最小幅が2ブロックになりカメラから撮影したときにモアレが生じにくくなるからです。

1.2. ネガポジパターン

上で述べた方法の課題として、どうやって白黒を判定するのかという問題があります。 例えば白と黒のタイルが交互に貼ってあるような壁にパターンを投影したとき、白い部分に黒パターンを投影したものと黒い部分に白パターンを投影したものはどちらも灰色になるので、白黒どちらのパターンが投影されているのかを区別できなくなります。

これを解決する方法として、投影の際に元のパターン画像(ポジティブ)と色を反転させたパターン画像(ネガティブ)の2枚を投影するというものがあります。 こうすれば、ポジティブとネガティブどちらの方が明るいかを調べることでもとの壁の色の影響を受けずに白黒を判別できるようになります。

1.3. 利点

  • 原理が単純
  • 実装がラク
  • セッティングに気をつければそこそこロバスト
    • 間違った対応関係を検出することがほとんどない

1.4. 欠点

  • プロジェクタ画素の座標をピクセル精度(整数精度)でしか取得できない
  • パターンの境目部分のコードの読み取りに失敗することが多い
  • カメラの焦点ぼけに弱い
    • ある位置に焦点を合わせると、他の焦点が合っていない場所のコードがうまく分析できなくなったりする

※この辺りの問題点が気になる人は位相シフト法(phase shifting method)について調べましょう

2. グレイコードパターン投影の実装

2.1. OpenCVを用いた実装

OpenCV3系以降では、外部モジュールであるopencv_contrib内のstructured_light::GrayCodePatternにネガポジを利用したグレイコードアルゴリズムが実装されています。 三次元形状計測に利用することを前提に実装されているので取り回しが少し面倒ですが、これを流用すれば比較的ラクにプログラムを書くことができます。

※ 三次元形状計測については公式がサンプルコードを公開してくれています

2.2. サンプルプログラム

以上を踏まえてサンプルプログラムを作成しました。 ソースコードは記事冒頭のGitHubリンクからご覧ください。 なお実行するにはopencvopencv_contribモジュールが必要です。

opencv_contribのインストールについては以前書いた記事が参考になると思います

kamino.hatenablog.com

処理の流れは次のようになっています。

  1. グレイコードパターン画像を生成
  2. カメラを初期化
  3. パターンの1つを投影してキーボード入力を待機
    • この間にカメラの位置やピントを調節してください
  4. パターンを順に投影・カメラから撮影
  5. カメラの終了処理
  6. カメラ画素ごとにグレイコードを分析
    1. 白色投影時と黒色投影時の輝度値の差がBLACKTHRESHOLDより大きいならグレイコードが写っていると判断する
    2. 各ビットについて、ポジティブ投影時とネガティブ投影時の輝度値の差がWHITETHRESHOLDより大きいなら白黒を判別する
    3. 全てのビットで白黒判定に成功した画素のみ対応関係を取得する
  7. 結果を可視化して画像として表示
  8. 取得できた対応関係をCSVに保存

2.3. サンプルプログラムの実装の欠点

OpenCVを使ったこの実装にはいくつか欠点があります。

本来、グレイコードは上位何ビットかの白黒判別に成功していれば、下位の何ビットかの判別に失敗しても座標の範囲を絞り込むことができます。 例えば0101**のように下位2ビットが判別できなかったとしても、010100010111つまり20〜23の範囲のどれかであることはわかります。

この原理を利用すれば、カメラ画素に対してプロジェクタ画素の解像度が高くしすぎた場合、つまりカメラ画像上でパターン画像の1ブロックが1画素より小さくなるほどパターンを細かくしてしまった場合でも可能な範囲でプロジェクタ画素の座標を計算することができます。 要は「プロジェクタで表示できる限界まで細かさのパターン画像を投影しておいて、カメラ側で可能な限りの値を推定しよう」という作戦が通用するのです。

しかしOpenCVの実装では全てのビットで白黒判別に成功した画素のみ成功と見なすので、上記のような範囲の絞り込みはしてくれません。 そのため、最も細かいパターンをカメラ側で判別できるよう(モアレが発生しないよう)にパターンの幅を調整する必要があります。

またOpenCVで画像をフルスクリーン表示する場合、環境によっては画面の端にウィンドウの枠が表示されてしまい、思い通りの画素数で表示できないことがあります。 これはOpenCVの実装が原因なので根深い問題です。 stack overflowのこちらの記事などで議論されているので、どうしてもこの問題を修正したいという場合は確認してみてください。


以上、OpenCVを使ったグレイコードパターン投影の実装について紹介しました。