facebook（現Meta）のキャリブレーションパターン検出器の精度はOpenCV実装よりも高い

この記事は[OpenCV Advent Calendar 2022](https://qiita.com/advent-calendar/2022/opencv)の12日目の記事です．

# 1. はじめに
キャリブレーションパターンことチェッカーボードの検出には，`cv::findChessboardCorners`が欠かせません．
多くのカメラがこの関数でキャリブレーションされてきたでことしょう．

この記事は，このパターンコーナー検出についての精度が足りないよ！（つまり使わなくてもいいよ）っていう内容の記事です．

# 2. パターン検出（OpenCV）

OpenCVのチェッカーパターン検出は，下の画像のような市松模様のコーナーをサブピクセル精度で見つける関数です．
下記画像を検出した結果を描画する関数に番号を添えて描画した結果です．
画像だけだとわかりませんが，コーナーは小数点のサブピクセルまで見つけています．
このサブピクセル精度は，`cv::cornerSubPix`という関数で，Harrisコーナーディテクタを利用して実現しています．
この関数は，`cv::findChessboardCorners`内部でも呼ばれていますが，多くの場合でもう一度適切なパラメータで`cv::cornerSubPix`を呼んだほうが高精度化します．
この関数はチェッカーの交点のグラデーションから高精度化するため，画像が適切にぼけていない場合は，`cv::blur`関数で画像を等方にぼかしてあげると精度が向上します．
![pattern.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/c32013b5-4855-8270-1011-85b4a59bf805.png)

# 3. パターン検出（Facebook）
旧Facebookから以下の論文で三角パターンの検出方法が出ています．
コードはここからダウンロードできます．[https://github.com/deltille/detector](https://github.com/deltille/detector)（ライセンスは LGPL-2.1）
```
H. Ha, M. Perdoch, H. Alismail, I. So Kweon and Y. Sheikh, 
"Deltille Grids for Geometric Camera Calibration,", 
in Proc. International Conference on Computer Vision (ICCV), 
2017.
```

三角形は，四角形よりもコーナーの密度が高くてたくさん点が取れるのと同時に精度も高いよという内容です．
参考までに下記にパターンを表示します．
ただこの記事はこの三角パターンを使いません．
この論文では，コーナーの最適化を提案しており，三角形だけでなく四角形のコーナー検出を最適化することもできます．
今回はそれを使います．

この公開されているコードは多くにOpenCVの関数が使われていて，OpenCVから使えるようにはなっているものの，OpenCVの更新が進み新しいOpenCVを使うとコンパイルが通りません．
また，おそらくまだ完成していなかった（or公開するコードが未整理or自分が発見できていない）のか，コーナーの検出位置の順番がバラバラで左上から順番にコーナーがきれいに並んでくれないためキャリブレーションにそのまま使うのがめんどくさいです．
（ある程度並び替える関数はあるのですが，並び方が4通り出てくるため整理が大変．．．）

その関数を，ひとまず最近のOpenCVでコンパイルして，左から順に検出できるように改訂しました．
コードはここ[https://github.com/norishigefukushima/detector](https://github.com/norishigefukushima/detector)

下記関数から使えるようにしています．
OpenCVの関数にだいたいそろえていますが，最後にisDrawという描画するかどうかのオプションがついています．
OpenCVのライブラリ関数は，描画するのにパターン検出結果だけが必要ですが，このfacebookの関数の描画機能はそれ以外にも必要だったのでdraw機能を内部に組み込んでいます．
（いじれば分離できますが，まぁどっちでもいいですよね．）

```cpp
bool findSquareCorners(Mat& input, cv::Size board_size, vector<Point2f>& dest, const bool isDraw)
bool findDeltilleCorners(Mat& input, cv::Size board_size, vector<Point2f>& dest, const bool isDraw)
```
![deltille.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/a69e39dc-ca8b-7743-c25d-f02102932a9d.png)


# 4. 精度比較実験
というわけで，パターンの検出精度の比較をやっていきます．
ほんとうは実写で精度比較しようかと思ってたのですが時間が無かったのでCGによる比較だけです．
CG生成は頑張ってレンダラを書いてサブピクセル精度できれいに出るように作りました．
（次世代のOpenCV5シリーズでは，BlenderによるCG使って作ったパターンのキャリブレーション精度検証サンプルがつくみたいですが）

今回は，CGなので真値との差が分かるため真値とのｘ方向，ｙ方向の誤差を2次元ヒストグラムで表示します．
だいたい200,000個のコーナーを検出した結果です．
（ヒストグラム表示には微妙に点がたりない気もしますが論文じゃないのでこれくらいで．）

比較対象は下記三つです．
* `cv::findChessboardCorners`
* `cv::findChessboardCorners`+`cv::blur`+`cv::cornerSubPix`
* facebookの検出器`findSquareCorners`

※ OpenCV Refineは5x5の`cv::blur`でぼかした後`cv::cornerSubPix`を直径9で最適化した結果です．

# 5. 実験結果
デフォルトはなんか角にパターンがロッキングしてしまうような影響があります．
ぼかしてリファインすると四角形に集まります．
一方，facebookのは中心にエラーが集まってくれています．
また2次元ヒストグラムは，見やすいようにスケールを変えています．
誤差の平均二乗誤差（RMSE）はそれぞれ
* 0.0319 [pixel]
* 0.0072 [pixel]
* 0.0026 [pixel]

となっており，facebookのが圧倒的に小さくなっています（左上の数値のスケールに注目）．
パターンの検出速度もすべて概ね7ms@Core i7 7700Kと大差がないです．
なお，これは歪みの無いCGの結果なので，実画像だともっと精度は悪いです．
実画像はレンズ歪，レンズぼけ，モーションブラー，画像のノイズなどなど検出精度を下げる要因がたくさんあります．
（OpenCVのデフォルトですら0.03画素以内で検出できており，キャリブレーションするのには十分です．）


![opencv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/27946520-976f-3717-a513-3cf766d47028.png)

![opencvrefine.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/1a7b27ac-057a-7cd8-1e28-afbfb1712ad6.png)

![facebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/173bc047-2979-3831-71c2-74435403b860.png)


# 6. まとめ
Facebookのチェッカーボードの検出についての紹介をしました．
改訂したコードはここです．
[https://github.com/norishigefukushima/detector](https://github.com/norishigefukushima/detector)
なお，元のコードは，cmakeでコードジェネレートできるようになっていますが，その辺いじっていなくて，新たに追加した/buildディレクトリ内の作成済みのslnファイルからしかコンパイルできません．（cmakeの書き方詳しくないので．．．）
また，その他機能もいろいろついてますが，ここで説明したものしかいじってません．
スタティックリンクのライブラリとしてコンパイルして出力するようにしています．
まともに使える（エクスポートする用）の関数は
`deltille.hpp`
のヘッダにあるものだけです．

次の記事は，@suo-takefumi さんの「OpenCVとMatplotlibでAR(拡張現実)する」です．