OpenCVを使ってグレイコードパターンを投影する
よく忘れるので、自分用にOpenCVを使ったグレイコードパターン投影のテンプレートを作りました。 ついでなので解説記事を書いてみます。 サンプルプログラムの言語はC++とPythonです。
スポンサーリンク
もくじ
1. グレイコードパターン投影とは?
AR/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枚のパターン画像を投影してやればよいです。
これをカメラで撮影した時、ある画素に「黒黒白白」というようなパターンが写り込んだなら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リンクからご覧ください。
なお、実行にはopencvとopencv_contribモジュールが必要です。 opencv_contribのインストールについては以前書いた記事が参考になると思います
処理の流れは次のようになっています。
- グレイコードパターン画像を生成
- カメラを初期化
- パターンの1つを投影してキーボード入力を待機
- この間にカメラの位置やピントを調節してください
- パターンを順に投影・カメラから撮影
- カメラの終了処理
- カメラ画素ごとにグレイコードを分析
- 白色投影時と黒色投影時の輝度値の差が
BLACKTHRESHOLD
より大きいならグレイコードが写っていると判断する - 各ビットについて、ポジティブ投影時とネガティブ投影時の輝度値の差が
WHITETHRESHOLD
より大きいなら白黒を判別する - 全てのビットで白黒判定に成功した画素のみ対応関係を取得する
- 白色投影時と黒色投影時の輝度値の差が
- 結果を可視化して画像として表示
- 取得できた対応関係をCSVに保存
2.3. サンプルプログラムの実装の欠点
OpenCVを使ったこの実装にはいくつか欠点があります。
本来、グレイコードは上位何ビットかの白黒判別に成功していれば、下位の何ビットかの判別に失敗しても座標の範囲を絞り込むことができます。
例えば0101**
のように下位2ビットが判別できなかったとしても、010100
〜010111
つまり20〜23の範囲のどれかであることはわかります。
この原理を利用すれば、カメラ画素に対してプロジェクタ画素の解像度が高くしすぎた場合、つまりカメラ画像上でパターン画像の1ブロックが1画素より小さくなるほどパターンを細かくしてしまった場合でも可能な範囲でプロジェクタ画素の座標を計算することができます。 要は「プロジェクタで表示できる限界まで細かさのパターン画像を投影しておいて、カメラ側で可能な限りの値を推定しよう」という作戦が通用するのです。
しかし、OpenCVの実装では全てのビットで白黒判別に成功した画素のみ成功と見なすので、上記のような範囲の絞り込みはしてくれません。 そのため、最も細かいパターンをカメラ側で判別できるよう(モアレが発生しないよう)にパターンの幅を調整する必要があります。
また、OpenCVで画像をフルスクリーン表示する場合、環境によっては画面の端にウィンドウの枠が表示されてしまい、思い通りの画素数で表示できないことがあります。 これはOpenCVの実装が原因なので根深い問題です。 stack overflowのこちらの記事などで議論されているので、どうしてもこの問題を修正したいという場合は確認してみてください。