超広角カメラをOpenCVを使ってキャリブレーション（比較）

# はじめに
[インタニヤ社のラズベリーパイVR220カメラ](https://products.entaniya.co.jp/ja/products/raspberry-pi/)を購入したのでいじって遊びたいと思います。
視野角が縦200°、横220°となっており、カメラよりも若干後ろ側まで撮影できます。
撮影画像は魚眼カメラのように正面からの角度が大きくなるほど歪みます。
今回はOpenCVのカメラキャリブレーション機能を使って歪みを補正してみます。

# OpenCVのカメラキャリブレーション
OpenCVのカメラキャリブレーション機能は、3つの機能が存在します。 

- cv::calibrateCamera
- cv::fisheye::calibrate
- cv::omnidir::calibrate

cv::calibrateCameraは一般的な中心投影のレンズのカメラのキャリブレーションに使用します。cv::fisheye::calibrateは文字通り魚眼レンズのカメラのカメラキャリブレーションに使用します。cv::omnidir::calibrateはomnidirectional(無指向性、全方位）なカメラのキャリブレーションに使用します。omnidirはopencv_contribに含まれています。
 
今回使用するカメラは[公式サイト](https://products.entaniya.co.jp/ja/products/raspberry-pi/)のサンプル写真からわかるように、レンズの中心から離れるほど画像が歪んでいます。直感的に考えると、まず一般的な中心投影ではないので、cv::calibrateCameraはうまく行かない気がします。fisheyeかomnidirのほうが適していそうな気がしますが、どっちのほうが適しているか、どっちでもOKなのか、はたまたどっちもNGなのかはわかりません。公式サイトに「魚眼」という言葉が使われていないのが気になります。

今回はキャリブレーションアルゴリズムの中身まで深追いするつもりはないので、とりあえず3つ手法を全部試して比較してみたいと思います。

# 入力画像
上記のキャリブレーション機能3つとも、入力は同じです。物体の三次元座標と、その物体が画像上に投影されたときの画像上の2次元座標の対を入力することでパラメータを推定します。
今回はOpenCVでよく使われるチェスボードを撮影し、グリッドの交点の三次元座標と画像上の2次元座標を対として各キャリブレーション関数に入力します。今回はチェスボードを色んな角度から撮影した画像を30枚使用しました。

![photo_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22812/78fe591c-5121-6a57-bf4f-787d9eab158f.jpeg)
↑チェスボード撮影例
※実際の処理では3264×2464のサイズの画像を使用しました。
※220度も画角があるとどうしても自分の部屋や身体が映り込んでしまいプライバシー的に嬉しくないのでボカシを入れています。

# ソースコード
[Github](https://github.com/noueezy/CalibrationSample)にアップロードしています。
なお、ソースコードと一緒にキャリブレーション結果をXMLファイル出力したものと、キャリブレーション結果をつかって魚眼画像の歪みを補正した画像もアップロードしています。
画像の撮影はJetson Nanoで、キャリブレーション処理はMacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)で行いました。
（ラズベリーパイVR220カメラという名前ですが、Jetson Nanoでも使用できます。ラズベリーパイ持ってません）
OpenCVのバージョンは4.2.0（with contrib）です。

# キャリブレーション結果
## RMS
各キャリブレーション関数は、戻り値として再投影誤差のRMSを返します。その値を比較してみます。（XMLファイルのRMSタグの値です）

| キャリブレーション | RMS |
|:----------------|:----|
|cv::calibrateCamera|1.6187411366955708e+01|
|cv::fisheye::calibrate|1.0787150122472680e+02|
|cv::omnidir::calibrate|3.2860994656014744e+00|

念の為ですが、すべてのキャリブレーションの入力に同じチェスボード撮影画像30枚を使用しました。
cv::omnidirの再投影誤差RMS値が一番小さくなっています。
cv::fisheyeの値はとても大きく、桁が2つ違います。おそらくまともにキャリブレーションできていないでしょう。cv::calibrateCameraよりも大きな値となっています。
画像が良くなかったのか、レンズがfisheyeの光学モデルに対応していなかったのかはよくわかりません。

## 歪み補正
算出したキャリブレーションパラメータをつかって画像の歪み補正（Undistortion）を行ってみたいと思います。
それぞれのキャリブレーションに対応するUndistort関数は次のとおりです。

- cv::calibrateCamera -> cv::undistort
- cv::fisheye::calibrate -> cv::fisheye::undistortImage
- cv::omnidir::calibrate -> cv::omnidir::undistortImage

撮影画像とパラメータを対応するundistort関数に入力し、画像がどうなるか見てみます。

### 元画像
![photo_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22812/78fe591c-5121-6a57-bf4f-787d9eab158f.jpeg)

### cv::undistort
![dstPers.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22812/010f387c-cf7f-9a27-7ab7-cef34f2c48b0.jpeg)

### cv::fisheye::undistortImage
![dstFisheye.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22812/e3648c83-91cc-4a59-afeb-6a05b693e8dd.jpeg)
なにこれ

### cv::omnidir::undistortImage
![dstOmni.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22812/99635632-1fe1-7e08-d2c6-9bb41fe20c8b.jpeg)

cv::omnidirではいい感じに歪みが補正できているのではないでしょうか。ラズベリーパイVR220カメラのキャリブレーションにはcv::omnidirが一番向いているようです。
cv::fisheyeの出力画像はもはや何が写っているのかわかりません。画像の撮り方を工夫したらもうちょっと良くなるのでしょうか？
cv::undistortの画像は写っているものが視認できますが、歪みが補正できているとは言えないように見えます。

# おわりに
インタニヤ社のラズベリーパイVR220カメラを、OpenCVのキャリブレーション手法3つを使ってキャリブレーションしてみました。
cv::omnidirのキャリブレーションを使うと一番良い結果になりました。
cv::fisheyeのキャリブレーションは予想に反して結果が悪くなりました。
cv::fisheyeでうまくいかなかった理由は気になるところですが、それよりもcv::omnidirでうまくいくことが分かったので、これを使って何かできないか考えてみたいと思います。