Hamming Color Codeを使って、プロジェクターの（幾何学的）キャリブレーション

# 背景

コンピュータービジョンの分野だと、カメラの焦点距離や位置・姿勢情報を推定することをカメラキャリブレーションと呼んでいると思います（正確には幾何学的なキャリブレーション？）。
これらの情報が推定できれば、ARでCGを重畳させたり、2台以上のキャリブレーション済みのカメラを使って、撮影シーンの3次元情報を推定したり色々できるようです。

また、世の中にはOpenCVというライブラリがあって、このライブラリを使えは、ゼロからカメラキャリブレーションのプログラムを実装する必要もなさそうです。

一方で、プロジェクターの幾何学的キャリブレーションとなると、カメラキャリブレーションと比べてネット上での情報が少ないと思いました。

というわけで、プロジェクターの幾何学的キャリブレーションをやってみたので、そのメモを書きます。


# プロジェクターの幾何学的キャリブレーションとは？
プロジェクターの焦点距離とか光軸の位置（画像上の位置）を求めることだと思います。
カメラは、シーンから入射してくる光を画像として受け取ります。一方でプロジェクターは入力画像をシーンに出射します。光の進行方向は逆だけど、光の経路はどちらも同じカンジ。ということはプロジェクターもカメラと同じようにキャリブレーションできる、という話だと思います。


# 前提知識（この記事で説明を省略する内容）
- カメラキャリブレーション
- ステレオカメラのキャリブレーション
- 使用ライブラリ／ソフトのインストール方法


# 実行環境

- windows 8.1（64ビット）
- python 2.7
- Microsoft Visual Studio Express 2013 for Windows Desktop
- OpenCV 3.1.0
- プロジェクター
- カメラ


# 作業＆処理の流れ
1. カメラキャリブレーション用のパターンを印刷して板かなにかに貼る
2. ハミングカラーコード画像を作る
3. プロジェクターを使って、ハミングカラーコードを板に投影してカメラで撮影する
4. 撮影画像をデコードする
5. 板のパターンを検出して、検出点の座標（カメラ画像上の座標）をデコード情報に基づいてプロジェクター上の座標に変換して、その対応点セットからステレオキャリブレーションする。


上記の作業＆処理の流れは、要は、どうにかしてOpenCVに既に実装されているカメラキャリブレーション関数を利用して楽したい、というカンジ。
キャリブレーション用板の画素座標（プロジェクター画像上の画素座標）さえ分かれば、後はOpenCVにお任せできるので、どうにかしてプロジェクター画像上の座標を求めたい。そのためにハミングカラーコードを使います。

## 1. カメラキャリブレーション用のパターンを印刷して板かなにかに貼る

OpenCVに実装されているキャリブレーションでは、処理の途中で、市松模様のコーナー点を画像上で検出して、そのコーナー点情報に基づいて焦点距離などを推定します。
まずは、そのためのパターンを印刷して板に貼ります。
今回使ったのは↓のようなものです。OpenCVをインストールすると、白黒の市松模様の画像（pdfかも）が一緒に入ってくると思います。今回は、黒色を少し明るくして灰色にしました（プロジェクター光の反射光を観測したいから）。

![P7300001.JPG](https://qiita-image-store.s3.amazonaws.com/0/122887/649e8a45-f98c-b8ba-fa5c-93eac0fbd59c.jpeg)



## 2.ハミングカラーコード画像を作る
ハミングカラーコードは文献[1]に書かれている数列のようなものです。
例えば文献[1]中のFigure.1にあるような数列では、6767という数列はかならず1回しか現れません。
つまり、数列を色に変換して、その縞模様をプロジェクターからシーンに投影した場合、その色の組み合わせから、カメラ座標とプロジェクターの座標の対応が分かるというわけです。

文献[1]のFigure. 1のカラーコードを生成するプログラムは以下です。
実行すれば、カラーコード画像が生成されます。
https://github.com/kibekibe/structured_light/blob/master/hamming_color_code/generate_hamming_code.py


## 3. プロジェクターを使って、ハミングカラーコードを板に投影してカメラで撮影する
1.で作成した板に対して、真っ白の画像、2で作った縞模様（縦と横で計2枚）をそれぞれ投影します。

![hamming_scene_cap.jpg](https://qiita-image-store.s3.amazonaws.com/0/122887/3cacd9b2-0cac-eb78-bdb0-62b3c186972d.jpeg)


この3枚を1セットとして、板の向きや位置を変えながら複数セット撮影します。このとき、カメラとプロジェクターは動かさず、固定したままにします。

真っ白パターンを撮るのは、後で、市松模様のコーナー点を検出する処理があるからです。




## 4. 撮影画像をデコードする

文献[1]では高尚なアルゴリズムで高度なパターンマッチングを実現していますが、
レベルが高そうだったので、それは実装せずに、単純に一致するパターン列を探すだけのプログラムを書いてみました。
縦模様の画像から、画像の横軸方向の対応を探します。同様に横模様の画像から、画像の縦軸方向の対応を探します。これらの対応点探索処理は、スキャンラインごとに独立で行いました。
デコードするプログラムは以下です。
https://github.com/kibekibe/structured_light/blob/master/hamming_color_code/hamming_decode.py

実行時に引数で-in_dirオプションを付けて画像フォルダを指定します。
例えば、以下のように実行するとcap_chessboardというフォルダにある画像に対してデコード処理を行います。
`python hamming_decode.py -in_dir cap_chessboard`

実行すると、プロジェクターの対応座標値が格納された画像が生成されます。画像フォーマットはexr画像とpng画像の2種類です。pngはデコード結果の確認用に出力しています。
実行結果例は以下です。
左が入力画像で、右がデコード結果の画像です。プロジェクターの対応座標値が輝度値を表しています。

![cap_decode_x.jpg](https://qiita-image-store.s3.amazonaws.com/0/122887/49b5cfb6-456e-03b4-16d5-797aaacdab1e.jpeg) 
![cap_decode_y.jpg](https://qiita-image-store.s3.amazonaws.com/0/122887/8c1a6fdf-ed8f-5426-8616-bf9ceca349d3.jpeg)

ちなみに上記プログラムでは、デコード結果に対してローパスフィルターを掛けています。


## 5. 板のパターンを検出して、検出座標をデコード情報に基づいてプロジェクター上の座標に変換して、その対応点セットからステレオキャリブレーションする。

プロジェクター画像上でコーナー点を見つけるために、まずカメラ画像上でコーナー点を探します（コーナー点を検出してくれる関数がOpenCVに実装されていて、それを呼ぶだけです）。そして、その検出したコーナー点の画像座標をデコード情報に基づいてプロジェクター画像座標上のコーナー点位置に変換します。
あとは、検出したコーナー点の対応点セットを入力としてステレオキャリブレーションします。ステレオキャリブレーション自体はOpenCVで実装されているので、然るべき関数を呼び出すだけです。

そのプログラムは以下です。
https://github.com/kibekibe/structured_light/blob/master/projcalib_from_decoded_imgs/main_projcalib.cpp


# プログラム、サンプルデータセット

- プログラム
https://github.com/kibekibe/structured_light/tree/master/hamming_color_code

- サンプルデータ
https://github.com/kibekibe/structured_light/tree/master/sample_data/cap_chessboard




# 参考文献
[1] Hamming Color Code for Dense and Robust One-shot 3D Scanning. Shuntaro Yamazaki, Akira Nukada, Masaaki Mochimaru. Proceedings of the British Machine Vision Conference (BMVC2011), pages 96.1-96.9. BMVA Press, September 2011.