OpenCvSharp4でカメラ用キャリブレーションファイルを使う

## はじめに
こんばんは
OpenCvSharp4を使ってWEBカメラのキャリブレーションを行ったので備忘録として残します。
キャリブレーション自体の理論や理屈はまだまだ理解できていないのでコードが動いただけですが。。。

## 環境
WEBカメラ : FullHD 1080P(200万画素カメラ)
キャリブレーションファイル作成：python(python-opencv)
カメラレンズ歪み計算：C#(opencvsharp4.windows)

## pythonでカメラキャリブレーションyamlファイルの作成
本当はOpencvsharpでキャリブレーション計算も行いたかったのですが
いかんせん3系のサンプル達の流用が難しく、4系で置き換えるのも私のレベルでは太刀打ちできそうにもなかったので大人しくサンプルのあるpythonで計算を行います

というわけで[こちら](https://qiita.com/ReoNagai/items/5da95dea149c66ddbbdd)を参考に以下のコードを作成して実行します。
核になる部分はほとんどコピペです。。。
いや、ほんとありがたい。

```Python:CalcCalibration.py
import os
import sys
import numpy as np
import cv2
from time import sleep
from datetime import datetime

FILE_NAME = "calib.yml"
# 参照画像の枚数
REFERENCE_IMG = 40
# 正方形の1辺のサイズ[cm]
SQUARE_SIZE = 2.0
# 交差ポイントの数
PATTERN_SIZE = (8, 13)


def main():
    """
    メイン関数
    :return:
    """
    calc_camera()  # カメラの歪みを計算


def calc_camera():
    """
    カメラの歪みを計算する関数
    :return:
    """
    pattern_points = np.zeros((np.prod(PATTERN_SIZE), 3), np.float32)  # チェスボード（X,Y,Z）座標の指定 (Z=0)
    pattern_points[:, :2] = np.indices(PATTERN_SIZE).T.reshape(-1, 2)
    pattern_points *= SQUARE_SIZE
    obj_points = []
    img_points = []

    capture = cv2.VideoCapture(0)
    # 解像度を指定
    # カメラ画像の横幅を1920に設定
    capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
    # カメラ画像の縦幅を1080に設定
    capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)

    while len(obj_points) < REFERENCE_IMG:
        # 画像の取得
        ret, img = capture.read()
        height = img.shape[0]
        width = img.shape[1]

        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        # チェスボードのコーナーを検出
        ret, corner = cv2.findChessboardCorners(gray, PATTERN_SIZE)
        # コーナーがあれば
        if ret:
            print("detected coner!")
            print(str(len(obj_points) + 1) + "/" + str(REFERENCE_IMG))
            term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.1)
            cv2.cornerSubPix(gray, corner, (5, 5), (-1, -1), term)
            img_points.append(corner.reshape(-1, 2))  # appendメソッド：リストの最後に因数のオブジェクトを追加
            obj_points.append(pattern_points)

        cv2.imshow('image', img)
        # 毎回判定するから 200 ms 待つ．遅延するのはココ
        if cv2.waitKey(200) & 0xFF == ord('q'):
            break

    print("calculating camera parameter...")
    # 内部パラメータを計算
    rms, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)

    # 計算結果を表示
    print("RMS = ", rms)
    print("mtx = \n", mtx)
    print("dist = ", dist.ravel())

    # ymlに保存
    f = cv2.FileStorage(FILE_NAME, flags=1)
    f.write('mtx', mtx)
    f.write('dist', dist)
    # f.write('translation', rvecs)
    # f.write('distortion', tvecs)
    f.release()


if __name__ == '__main__':
    main()
```
<br />
参照画像の枚数、正方形の1辺のサイズ、交差ポイントの数、カメラ解像度の設定は
環境に合わせて設定します。
今回は[こちら](https://drive.google.com/file/d/1A56mjyobKN3cLkFkPgokO8qCoDXUdXIC/view?usp=sharing)を作成し、A4サイズで印刷してプラ板に貼って使用しました。
正方形1辺=20mmなので2.0cm、交差ポイントは横方向8、縦方向13です。
↓のようにまんべんなく移動させながら歪み計算を行います。
![img_20200401012008.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/fe2471c8-f19c-d3da-44e6-82e2ab4cb49a.jpeg)
![img_20200401012013.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/6abf5305-4be0-2c42-4ab7-7d3b7aa234b7.jpeg)
![img_20200401012017.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/390faef9-27ed-1da9-1faf-8445a2821ff1.jpeg)

40枚分チェスボードのコーナーが検出できれば歪みを計算します。
↓のように結果が確認できます。同時に"calib.yml"が出力されている事も確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/473ce821-d256-cfb0-9af6-33ddccccc94e.png)

```YAML:calib.yml
%YAML:1.0
---
mtx: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 7.7958988893525259e+02, 0., 9.8266449367809537e+02, 0.,
       7.7847873908657630e+02, 5.7636196300911377e+02, 0., 0., 1. ]
dist: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ -1.8378651673412880e-01, 4.1014929211162864e-02,
       -1.2046811513395908e-03, -4.8516056956278577e-04,
       -4.8595996923656995e-03 ]

```

## OpenCvSharp4でyamlファイルを読み込んで歪み補正を行う
まずはopencvsharpをインストールします。
何故か上側のOpenCvSharp4では実行時にDLLの読み込みエラーが発生したので
OpenCvSharp4.WIndowsをインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/fb52d0f8-5661-30b4-41f5-7de968cb32ae.png)
インストールが完了したら"calib.yml"と検証用の画像を用意し
以下のコードを動かします。

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenCvSharp;

namespace CameraCalibrationSample
{
    class Program
    {
        static void Main(string[] args)
        {
            const string ImagePath = @"./img/";
            const string YamlFilePath = @"calib.yml";

            // (1)キャリブレーション画像(補正前)の読み込み
            string[] imagePaths = System.IO.Directory.EnumerateFiles(ImagePath, "*", System.IO.SearchOption.AllDirectories).ToArray();
            int imageNum = imagePaths.Length;

            Mat[] srcImg = new Mat[imageNum];
            for (int i = 0; i < imageNum; i++)
            {
                srcImg[i] = Cv2.ImRead(imagePaths[i], ImreadModes.Color);
                Mat src = Cv2.ImRead(imagePaths[i], ImreadModes.Color);

                // ymlファイルを読み来み計算パラメータを取得
                using (var fs = new FileStorage(YamlFilePath, FileStorage.Mode.Read))
                {
                    var mtx = fs["mtx"].ReadMat();
                    var dist = fs["dist"].ReadMat();

                    Mat calib = new Mat();
                    Cv2.Undistort(src, calib, mtx, dist);
                    Cv2.ImShow("src", srcImg[i]);
                    Cv2.ImShow("calib", calib);
                    OpenCvSharp.Cv2.WaitKey(0);
                }
            }

            Cv2.DestroyAllWindows();
        }
    }
}
```
歪み計算がうまく機能していれば↓のようになります。
・補正前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/e99a1d99-7f9d-7650-13d7-e886062bef8a.png)

・補正後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/7af99af4-c813-7e16-8a0f-332ebd26cb22.png)

ちゃんと補正されているのが確認できます。やったね！(^O^)

## 終わりに
3系と4系で結構コードに違いがあり、旧バージョンのサンプルを4で書き直そうと思っても敷居が高かったです。。。
本家OpenCVの公式サイトではC++, pythonのサンプルソースがありますがこれをC#に置き換えるのも自分にとってはなかなか難しい。
今回は計算部分をC#で書くのは諦めて補正パラメータを使用するところだけC#で書くことにしました。
それでも3系と4系でFileStorageの書き方がかなり変わっていたので割と時間がかかりました。ちゃんと[Wiki](https://github.com/shimat/opencvsharp/wiki/FileStorage)を見ればすぐにわかるのですが3系のサンプルばかり追っかけていたので気づくのに時間がかかってしまいました。ちゃんと調べるべきでしたね。


## 参考・出典元

上記URLに感謝です。
https://qiita.com/ReoNagai/items/5da95dea149c66ddbbdd
https://github.com/shimat/opencvsharp/wiki/FileStorage
https://stackoverrun.com/ja/q/11850575
http://hima-tubusi.blogspot.com/2016/02/opencvsharp_3.html
https://qiita.com/Kazuhito/items/cc6b8a0bd75cf9689bf9
https://www.learnopencv.com/camera-calibration-using-opencv/