LoginSignup
4

More than 3 years have passed since last update.

JavaでOpenCVのカメラキャリブレーション(CameraCalibration)をやった

Last updated at Posted at 2019-01-26

はじめに

カメラキャリブレーションというものが必要になったのでやってみました。
注意: 筆者は独学で行なっているため、不正確な記述が含まれることがあります。

カメラキャリブレーション(CameraCalibration)とは

空間の位置と画像上の位置を変換できる式がありますが、これはカメラのパラメーターが必要です。
カメラパラメーターには種類があり、大きく内部パラメーターと外部パラメーターに分けられます。
また、内部パラメーターに含まれるのかわからないですが、レンズ歪み係数(歪曲収差)というものもあります。

内部パラメーター

内部パラメーターとは、「カメラによって決定されるパラメーター」です。
・焦点距離 f
・画素の物理的な間隔 δu, δv
・正規化座標に対する画像座標における画像中心 cu, cv

外部パラメーター

外部パラメーターとは、「ワールド座標系に対するカメラの位置と姿勢のパラメーター」です。
・回転行列 R3
・平行移動ベクトル t3

カメラキャリブレーションとは

さて、カメラキャリブレーションとは、このカメラのパラメーターを求めることです。
この際に得られる、内部パラメーターと外部パラメーターを合わせて、カメラ行列または透視投影行列と言います。
予め位置がわかっている空間点と、その画像上への投影点(つまり、画像上の座標)を行列の式に当てはめてパラメーターを決定します。

具体的に言えば、キャリブレーションターゲット(チェスボードなど)を動かしながら写真を撮影します。
チェスボードの格子の長さが既知であるため、格子点間の距離がわかり、空間上の位置が定まります。(逆に言えば、ワールド座標系を定めます)
また、画像上でも格子点の座標を取得します。(画像座標の取得)
画像を複数枚撮影し、最小二乗法を用いて、パラメーターを求めます。

引用(参考)元: 「ディジタル画像処理[改訂新版]」, なお都合より表現を改変している。
参考: カメラキャリブレーションと3次元再構成 - OpenCV.jp

OpenCVでカメラキャリブレーションを行う

今回は比較的簡単だと思う、チェストボードによるカメラキャリブレーションを以下の大まかな手順で行いました。

コードについてはOpenCV_CameraCalibration - GitHubを参照してください。

  1. チェストボード(キャリブレーションターゲット)を持って、動かしながら写真を撮影します。
  2. ワールド座標系と空間上の格子点の座標を定める
  3. 画像上のチェストボードの格子点の座標を求める [Calib3d.findChessboardCorners()を利用]
  4. Calib3d.calibrateCamera()を実行する
  5. 返された カメラ行列とレンズ歪み係数を保存する 概要はこのような感じである。

それぞれについて少し詳しく解説します。

ワールド座標系と空間上の格子点の座標を定める

ワールド座標系を、平面なキャリブレーションターゲット(チェストボード)の平面がZ=0となるように任意に定めます。
このときに格子点の間隔を測って、mm単位でPointを定めます。
ここで呼び出ししています。

List<Mat> objectPoints = getObjectPoints(outputFindChessboardCorners.size(), patternSize); // チェスボードのコーナーの三次元座標(z=0)を、撮影画像枚数分入れる。

OpenCV_CameraCalibration_L68 - GitHub

getObjectPoints()の中身は以下のようになっています。

    public List<Mat> getObjectPoints(int size, Size patternSize) {
        List<Mat> objectPoints = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            objectPoints.add(getObjectPoint(patternSize));
        }
        return objectPoints;
    }

    public MatOfPoint3f getObjectPoint(Size patternSize) {
        MatOfPoint3f objectPoint = new MatOfPoint3f();

        List<Point3> objectPoint_ = new ArrayList<>();
        // final Size patternSize = new Size(6, 9); // 探査するコーナーの数
        for (int row = 0; row < patternSize.height; row++) {
            for (int col = 0; col < patternSize.width; col++) {
                objectPoint_.add(getPoint(row, col));
            }
        }

        objectPoint.fromList(objectPoint_);
        return objectPoint;
    }

    public Point3 getPoint(int row, int col) {
        final double REAL_HEIGHT = 20.0, REAL_WIDTH = 20.0;
        return new Point3(col * REAL_WIDTH, row * REAL_HEIGHT, 0.0); // 多分x, y, zはこういう感じ。
    }

OpenCV_CameraCalibration_L96 - GitHub

画像上のチェストボードの格子点の座標を求める

自分で、findChessboardCorners()というメソッドを書きました。呼び出しは以下のようになっています。

        List<Mat> outputFindChessboardCorners = new ArrayList<>();
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(picFolderPath)) {
            for (Path path : ds) {
                System.out.println(path.toString());

                final Optional<Mat> outputMat = findChessboardCorners(path.toString(), imagePoints, patternSize);

                if (outputMat.isPresent()) {
                    outputFindChessboardCorners.add(outputMat.get());
                    System.out.println("successful to find corners.");
                } else {
                    System.err.println("unsuccessful to find corners.");
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }

OpenCV_CameraCalibration_L50 - GitHub

findChessboardCorners()の中身は以下のようになっています。
Calib3d.findChessboardCorners()というメソッドを利用しました。
なお、返却しているMatは、Calib3d.findChessboardCorners()が正常に実行できた画像が入っています。

    public Optional<Mat> findChessboardCorners(String picPathString, List<Mat> imagePoints, Size patternSize) {
        Mat inputMat = Imgcodecs.imread(picPathString);
        Mat mat = inputMat.clone();
        // final Size patternSize = new Size(6, 9); // 探査するコーナーの数
        MatOfPoint2f corners = new MatOfPoint2f(); // in, 検出されたコーナーの二次元座標のベクトルを受け取る。

        Imgproc.cvtColor(inputMat, inputMat, Imgproc.COLOR_BGR2GRAY);

        final boolean canFindChessboard = Calib3d.findChessboardCorners(inputMat, patternSize, corners);

        if (!canFindChessboard) {
            System.err.println("Cannot find Chessboard Corners.");
            return Optional.empty();
        }

        imagePoints.add(corners);

        Calib3d.drawChessboardCorners(mat, patternSize, corners, true);

        Path picPath = Paths.get(picPathString);
        Path folderPath = Paths.get("S:\\CameraCaliblation\\2018-12-31_output");
        Path path = Paths.get(folderPath.toString(), picPath.getFileName().toString());

        if (!Files.exists(folderPath) || !Files.isDirectory(folderPath)) {
            try {
                System.out.println("There was no folder, so it is createing a folder. : " + folderPath.toString());
                Files.createDirectory(folderPath);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        Imgcodecs.imwrite(path.toString(), mat);

        return Optional.of(inputMat);
    }

Calib3d.calibrateCamera()を実行する

先程用意した空間上の格子点の座標と、先程求めた画像上のチェストボードの格子点の座標を引数として、Calib3d.calibrateCamera()を実行します。
また、このメソッドでカメラ行列とレンズ歪み係数の値が返されるので、それを格納する変数を作っておきます。

        // 受け取るもの
        Mat cameraMatrix = new Mat(), distortionCoefficients = new Mat();
        List<Mat> rotationMatrixs = new ArrayList<>(), translationVectors = new ArrayList<>();

        Calib3d.calibrateCamera(objectPoints, imagePoints, imageSize,
                cameraMatrix, distortionCoefficients, rotationMatrixs, translationVectors);

OpenCV_CameraCalibration_L81 - GitHub

返された カメラ行列とレンズ歪み係数を保存する

カメラ行列とレンズ歪み係数を取得できましたが、この処理を毎回行うのは面倒かつ無駄なので、行列の値を保存しましょう。
CやPythonならはOpenCV側でMatの値を入力・出力するものがあるのですが、Java版ではないので、自分で今回は作りました。
Javaの標準ライブラリにあるXMLの入出力を使いました。

        Map<String, Mat> exportMats = new HashMap<>();
        exportMats.put("CameraMatrix", cameraMatrix);
        exportMats.put("DistortionCoefficients", distortionCoefficients);
        final Path exportFilePath = Paths.get("S:\\CameraCaliblation\\CameraCalibration_2018-12-31.xml");
        MatIO.exportMat(exportMats, exportFilePath);

OpenCV_CameraCalibration_L87 - GitHub

MatIOの中身は、OpenCV_CameraCalibration_MatIO.java - GitHub を参照してください。

おわりに

一応、これを使って、ArUcoのARマーカーの姿勢推定 を行ったところ、正常に動作しました。
我が部の情報班の後輩にも理解&実行してもらい、成功しましたので良かったです。

参考リンク

OpenCV - カメラキャリブレーションを行う方法 - Pynote
カメラキャリブレーション (OpenCV 1.0) - OpenCV.jp
OpenCVとVisual C++による画像処理と認識
pythonとOpenCVでカメラキャリブレーション(1個のカメラの内部パラメータと歪み係数を求める)するコード(パクリ)
カメラ校正
OpenCV 2.4.11&C++によるカメラキャリブレーション - 技術的特異点
Camera Calibration and 3D Reconstruction - OpenCV公式- 改めて公式サイト見ると良い説明ありますね。
CC_Controller.java - opencv-java/camera-calibration(GitHub) - Java版のやり方に参考になりました。
cameracalibration - opencv(GitHub)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4