vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b
@@ -0,0 +1,9 @@
|
||||
set(the_description "Camera Calibration and 3D Reconstruction")
|
||||
|
||||
set(debug_modules "")
|
||||
if(DEBUG_opencv_calib)
|
||||
list(APPEND debug_modules opencv_highgui)
|
||||
endif()
|
||||
ocv_define_module(calib opencv_imgproc opencv_objdetect opencv_flann opencv_geometry opencv_stereo ${debug_modules}
|
||||
WRAP java objc python js
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
@inproceedings{strobl2011iccv,
|
||||
title={More accurate pinhole camera calibration with imperfect planar target},
|
||||
author={Strobl, Klaus H. and Hirzinger, Gerd},
|
||||
booktitle={2011 IEEE International Conference on Computer Vision (ICCV)},
|
||||
pages={1068-1075},
|
||||
month={Nov},
|
||||
year={2011},
|
||||
address={Barcelona, Spain},
|
||||
publisher={IEEE},
|
||||
url={https://elib.dlr.de/71888/1/strobl_2011iccv.pdf},
|
||||
doi={10.1109/ICCVW.2011.6130369}
|
||||
}
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1,14 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
// this is the header for backward compatibility with OpenCV 4.x and earlier versions
|
||||
|
||||
#ifndef OPENCV_CALIB3D_HPP
|
||||
#define OPENCV_CALIB3D_HPP
|
||||
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "opencv2/stereo.hpp"
|
||||
#include "opencv2/calib.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
// this is the header for backward compatibility with OpenCV 4.x and earlier versions
|
||||
|
||||
#ifndef OPENCV_CALIB3D_CALIB3D_HPP
|
||||
#define OPENCV_CALIB3D_CALIB3D_HPP
|
||||
|
||||
#include "opencv2/calib3d.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"class_ignore_list": [
|
||||
"CirclesGridFinderParameters"
|
||||
],
|
||||
"namespaces_dict": {
|
||||
"cv.fisheye": "fisheye"
|
||||
},
|
||||
"func_arg_fix" : {
|
||||
"initCameraMatrix2D" : { "objectPoints" : {"ctype" : "vector_vector_Point3f"},
|
||||
"imagePoints" : {"ctype" : "vector_vector_Point2f"} }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.opencv.test.calib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.opencv.calib.Calib;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfDouble;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.MatOfPoint3f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
public class CalibTest extends OpenCVTestCase {
|
||||
|
||||
public void testConstants()
|
||||
{
|
||||
// calib3d.hpp: some constants have conflict with constants from 'fisheye' namespace
|
||||
assertEquals(1, Calib.CALIB_USE_INTRINSIC_GUESS);
|
||||
assertEquals(2, Calib.CALIB_FIX_ASPECT_RATIO);
|
||||
assertEquals(4, Calib.CALIB_FIX_PRINCIPAL_POINT);
|
||||
assertEquals(8, Calib.CALIB_ZERO_TANGENT_DIST);
|
||||
assertEquals(16, Calib.CALIB_FIX_FOCAL_LENGTH);
|
||||
assertEquals(32, Calib.CALIB_FIX_K1);
|
||||
assertEquals(64, Calib.CALIB_FIX_K2);
|
||||
assertEquals(128, Calib.CALIB_FIX_K3);
|
||||
assertEquals(0x0800, Calib.CALIB_FIX_K4);
|
||||
assertEquals(0x1000, Calib.CALIB_FIX_K5);
|
||||
assertEquals(0x2000, Calib.CALIB_FIX_K6);
|
||||
assertEquals(0x4000, Calib.CALIB_RATIONAL_MODEL);
|
||||
assertEquals(0x8000, Calib.CALIB_THIN_PRISM_MODEL);
|
||||
assertEquals(0x10000, Calib.CALIB_FIX_S1_S2_S3_S4);
|
||||
assertEquals(0x40000, Calib.CALIB_TILTED_MODEL);
|
||||
assertEquals(0x80000, Calib.CALIB_FIX_TAUX_TAUY);
|
||||
assertEquals(0x100000, Calib.CALIB_USE_QR);
|
||||
assertEquals(0x200000, Calib.CALIB_FIX_TANGENT_DIST);
|
||||
assertEquals(0x100, Calib.CALIB_FIX_INTRINSIC);
|
||||
assertEquals(0x200, Calib.CALIB_SAME_FOCAL_LENGTH);
|
||||
assertEquals(0x400, Calib.CALIB_ZERO_DISPARITY);
|
||||
assertEquals((1 << 17), Calib.CALIB_USE_LU);
|
||||
assertEquals((1 << 22), Calib.CALIB_USE_EXTRINSIC_GUESS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"whitelist":
|
||||
{
|
||||
"": [
|
||||
"calibrateCameraExtended"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"namespaces_dict": {
|
||||
"cv.fisheye": "fisheye"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Calib3dTest.swift
|
||||
//
|
||||
// Created by Giles Payne on 2020/05/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import OpenCV
|
||||
|
||||
class CalibTest: OpenCVTestCase {
|
||||
|
||||
func testConstants()
|
||||
{
|
||||
// calib3d.hpp: some constants have conflict with constants from 'fisheye' namespace
|
||||
XCTAssertEqual(1, Calib.CALIB_USE_INTRINSIC_GUESS)
|
||||
XCTAssertEqual(2, Calib.CALIB_FIX_ASPECT_RATIO)
|
||||
XCTAssertEqual(4, Calib.CALIB_FIX_PRINCIPAL_POINT)
|
||||
XCTAssertEqual(8, Calib.CALIB_ZERO_TANGENT_DIST)
|
||||
XCTAssertEqual(16, Calib.CALIB_FIX_FOCAL_LENGTH)
|
||||
XCTAssertEqual(32, Calib.CALIB_FIX_K1)
|
||||
XCTAssertEqual(64, Calib.CALIB_FIX_K2)
|
||||
XCTAssertEqual(128, Calib.CALIB_FIX_K3)
|
||||
XCTAssertEqual(0x0800, Calib.CALIB_FIX_K4)
|
||||
XCTAssertEqual(0x1000, Calib.CALIB_FIX_K5)
|
||||
XCTAssertEqual(0x2000, Calib.CALIB_FIX_K6)
|
||||
XCTAssertEqual(0x4000, Calib.CALIB_RATIONAL_MODEL)
|
||||
XCTAssertEqual(0x8000, Calib.CALIB_THIN_PRISM_MODEL)
|
||||
XCTAssertEqual(0x10000, Calib.CALIB_FIX_S1_S2_S3_S4)
|
||||
XCTAssertEqual(0x40000, Calib.CALIB_TILTED_MODEL)
|
||||
XCTAssertEqual(0x80000, Calib.CALIB_FIX_TAUX_TAUY)
|
||||
XCTAssertEqual(0x100000, Calib.CALIB_USE_QR)
|
||||
XCTAssertEqual(0x200000, Calib.CALIB_FIX_TANGENT_DIST)
|
||||
XCTAssertEqual(0x100, Calib.CALIB_FIX_INTRINSIC)
|
||||
XCTAssertEqual(0x200, Calib.CALIB_SAME_FOCAL_LENGTH)
|
||||
XCTAssertEqual(0x400, Calib.CALIB_ZERO_DISPARITY)
|
||||
XCTAssertEqual((1 << 17), Calib.CALIB_USE_LU)
|
||||
XCTAssertEqual((1 << 22), Calib.CALIB_USE_EXTRINSIC_GUESS)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
camera calibration for distorted images with chess board samples
|
||||
reads distorted images, calculates the calibration and write undistorted images
|
||||
'''
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class calibration_test(NewOpenCVTests):
|
||||
|
||||
def test_calibration(self):
|
||||
img_names = []
|
||||
for i in range(1, 15):
|
||||
if i < 10:
|
||||
img_names.append('samples/data/left0{}.jpg'.format(str(i)))
|
||||
elif i != 10:
|
||||
img_names.append('samples/data/left{}.jpg'.format(str(i)))
|
||||
|
||||
square_size = 1.0
|
||||
pattern_size = (9, 6)
|
||||
pattern_points = np.zeros((np.prod(pattern_size), 3), np.float32)
|
||||
pattern_points[:, :2] = np.indices(pattern_size).T.reshape(-1, 2)
|
||||
pattern_points *= square_size
|
||||
|
||||
obj_points = []
|
||||
img_points = []
|
||||
h, w = 0, 0
|
||||
for fn in img_names:
|
||||
img = self.get_sample(fn, 0)
|
||||
if img is None:
|
||||
continue
|
||||
|
||||
h, w = img.shape[:2]
|
||||
found, corners = cv.findChessboardCorners(img, pattern_size)
|
||||
if found:
|
||||
term = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_COUNT, 30, 0.1)
|
||||
cv.cornerSubPix(img, corners, (5, 5), (-1, -1), term)
|
||||
|
||||
if not found:
|
||||
continue
|
||||
|
||||
img_points.append(corners.reshape(-1, 2))
|
||||
obj_points.append(pattern_points)
|
||||
|
||||
# calculate camera distortion
|
||||
rms, camera_matrix, dist_coefs, _rvecs, _tvecs = cv.calibrateCamera(obj_points, img_points, (w, h), None, None, flags = 0)
|
||||
|
||||
eps = 0.01
|
||||
normCamEps = 10.0
|
||||
normDistEps = 0.05
|
||||
|
||||
cameraMatrixTest = [[ 532.80992189, 0., 342.4952186 ],
|
||||
[ 0., 532.93346422, 233.8879292 ],
|
||||
[ 0., 0., 1. ]]
|
||||
|
||||
distCoeffsTest = [ -2.81325576e-01, 2.91130406e-02,
|
||||
1.21234330e-03, -1.40825372e-04, 1.54865844e-01]
|
||||
|
||||
self.assertLess(abs(rms - 0.196334638034), eps)
|
||||
self.assertLess(cv.norm(camera_matrix - cameraMatrixTest, cv.NORM_L1), normCamEps)
|
||||
self.assertLess(cv.norm(dist_coefs - distCoeffsTest, cv.NORM_L1), normDistEps)
|
||||
|
||||
def test_projectPoints(self):
|
||||
objectPoints = np.array([[181.24588 , 87.80361 , 11.421074],
|
||||
[ 87.17948 , 184.75563 , 37.223446],
|
||||
[ 22.558456, 45.495266, 246.05797 ]], dtype=np.float32)
|
||||
rvec = np.array([[ 0.9357548 , -0.28316498, 0.21019171],
|
||||
[ 0.30293274, 0.9505806 , -0.06803132],
|
||||
[-0.18054008, 0.12733458, 0.9752903 ]], dtype=np.float32)
|
||||
tvec = np.array([ 69.32692 , 17.602057, 135.77672 ], dtype=np.float32)
|
||||
cameraMatrix = np.array([[214.0047 , 26.98735 , 253.37799 ],
|
||||
[189.8172 , 10.038101, 18.862494],
|
||||
[114.07123 , 200.87277 , 194.56332 ]], dtype=np.float32)
|
||||
distCoeffs = distCoeffs = np.zeros((4, 1), dtype=np.float32)
|
||||
|
||||
imagePoints, jacobian = cv.projectPoints(objectPoints, rvec, tvec, cameraMatrix, distCoeffs)
|
||||
self.assertTrue(imagePoints is not None)
|
||||
self.assertTrue(jacobian is not None)
|
||||
|
||||
def test_sampsonDistance_valid2D(self):
|
||||
pt1 = (np.random.rand(3, 10) * 256).astype(np.float64)
|
||||
pt2 = (np.random.rand(3, 10) * 256).astype(np.float64)
|
||||
F = (np.random.rand(3, 3) * 256).astype(np.float64)
|
||||
dist = cv.sampsonDistance(pt1, pt2, F)
|
||||
self.assertTrue(isinstance(dist, (float, np.floating)))
|
||||
self.assertGreaterEqual(dist, 0.0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,164 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
#include "opencv2/core/utils/filesystem.hpp"
|
||||
|
||||
//#define SAVE_IMAGE_POINTS
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
#ifdef SAVE_IMAGE_POINTS
|
||||
|
||||
static std::vector<std::string> loadBulkImages(size_t max_images)
|
||||
{
|
||||
const std::string data_dir = findDataDirectory("perf/calib3d/bulk_n500", false);
|
||||
std::vector<std::string> image_paths;
|
||||
cv::utils::fs::glob(data_dir, "*.png", image_paths, false, false);
|
||||
if (image_paths.empty())
|
||||
cv::utils::fs::glob(data_dir, "*.jpg", image_paths, false, false);
|
||||
if (image_paths.empty())
|
||||
throw SkipTestException("No images found in perf/calib3d/bulk_n500");
|
||||
|
||||
std::sort(image_paths.begin(), image_paths.end());
|
||||
if (image_paths.size() > max_images)
|
||||
image_paths.resize(max_images);
|
||||
return image_paths;
|
||||
}
|
||||
|
||||
static std::vector<std::vector<Point2f>> buildImagePoints(const std::vector<std::string>& image_paths, const cv::Size pattern_size)
|
||||
{
|
||||
std::vector<std::vector<Point2f>> image_points;
|
||||
image_points.reserve(image_paths.size());
|
||||
|
||||
for (const auto& path : image_paths)
|
||||
{
|
||||
Mat gray = imread(path, IMREAD_GRAYSCALE);
|
||||
if (gray.empty())
|
||||
{
|
||||
printf("Can't read image: %s\n", path.c_str());
|
||||
return std::vector<std::vector<Point2f>>();
|
||||
}
|
||||
|
||||
std::vector<Point2f> corners;
|
||||
bool found = findChessboardCorners(
|
||||
gray, pattern_size, corners,
|
||||
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
|
||||
|
||||
if (found)
|
||||
{
|
||||
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),
|
||||
TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1));
|
||||
image_points.push_back(corners);
|
||||
}
|
||||
}
|
||||
|
||||
return image_points;
|
||||
}
|
||||
|
||||
static void saveImagePoints(const std::vector<std::vector<Point2f>>& image_points)
|
||||
{
|
||||
const std::string points_file = "bulk_n500.yaml";
|
||||
cv::FileStorage fs(points_file, cv::FileStorage::WRITE | cv::FileStorage::FORMAT_YAML);
|
||||
if (!fs.isOpened())
|
||||
{
|
||||
printf("Cannot open yaml config \"%s\" for output\n", points_file.c_str());
|
||||
}
|
||||
fs << "count" << (int)image_points.size();
|
||||
for (int i = 0; i < (int)image_points.size(); i++)
|
||||
{
|
||||
fs << cv::format("frame_%d", i) << image_points[i];
|
||||
}
|
||||
|
||||
fs.release();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static std::vector<std::vector<Point2f>> loadImagePoints()
|
||||
{
|
||||
const std::string points_file = findDataFile("perf/calib3d/bulk_n500.yaml");
|
||||
cv::FileStorage fs(points_file, cv::FileStorage::READ);
|
||||
if (!fs.isOpened())
|
||||
{
|
||||
printf("Cannot open yaml config \"%s\" for output\n", points_file.c_str());
|
||||
return std::vector<std::vector<Point2f>>();
|
||||
}
|
||||
int count = fs["count"];
|
||||
std::vector<std::vector<Point2f>> image_points(count);
|
||||
|
||||
for (int i = 0; i < (int)image_points.size(); i++)
|
||||
{
|
||||
fs[cv::format("frame_%d", i)] >> image_points[i];
|
||||
}
|
||||
|
||||
fs.release();
|
||||
|
||||
return image_points;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static std::vector<std::vector<Point3f>> buildObjectPoints(const Size& pattern_size, float square_size, size_t count)
|
||||
{
|
||||
std::vector<Point3f> board;
|
||||
board.reserve(pattern_size.area());
|
||||
|
||||
for (int y = 0; y < pattern_size.height; ++y)
|
||||
for (int x = 0; x < pattern_size.width; ++x)
|
||||
board.push_back(Point3f(x * square_size, y * square_size, 0.f));
|
||||
|
||||
std::vector<std::vector<Point3f> > object_points;
|
||||
object_points.reserve(count);
|
||||
for (size_t i = 0; i < count; i++)
|
||||
object_points.push_back(board);
|
||||
|
||||
return object_points;
|
||||
}
|
||||
|
||||
PERF_TEST(CalibrateCamera, BulkImages_N500)
|
||||
{
|
||||
// NOTE: The images archive is published at https://dl.opencv.org/data/bulk_n500.zip
|
||||
applyTestTag(CV_TEST_TAG_LONG);
|
||||
|
||||
const cv::Size pattern_size(6, 8);
|
||||
const cv::Size image_size(1280, 720);
|
||||
|
||||
#ifdef SAVE_IMAGE_POINTS
|
||||
std::vector<std::string> image_paths = loadBulkImages(500);
|
||||
std::vector<std::vector<Point2f>> image_points = buildImagePoints(image_paths, pattern_size);
|
||||
ASSERT_FALSE(image_points.empty());
|
||||
saveImagePoints(image_points);
|
||||
#else
|
||||
std::vector<std::vector<Point2f>> image_points = loadImagePoints();
|
||||
ASSERT_FALSE(image_points.empty());
|
||||
#endif
|
||||
|
||||
std::vector<std::vector<Point3f> > object_points = buildObjectPoints(pattern_size, 1.0f, image_points.size());
|
||||
|
||||
Mat camera_matrix = Mat::eye(3, 3, CV_64F);
|
||||
Mat dist_coeffs = Mat::zeros(8, 1, CV_64F);
|
||||
std::vector<Mat> rvecs;
|
||||
std::vector<Mat> tvecs;
|
||||
double rms = 0.0;
|
||||
|
||||
declare.in(image_points, object_points);
|
||||
declare.out(camera_matrix, dist_coeffs);
|
||||
declare.iterations(1);
|
||||
|
||||
TEST_CYCLE()
|
||||
{
|
||||
camera_matrix = Mat::eye(3, 3, CV_64F);
|
||||
dist_coeffs = Mat::zeros(8, 1, CV_64F);
|
||||
rvecs.clear();
|
||||
tvecs.clear();
|
||||
rms = calibrateCamera(object_points, image_points, image_size,
|
||||
camera_matrix, dist_coeffs, rvecs, tvecs, 0);
|
||||
}
|
||||
|
||||
EXPECT_NEAR(rms, 1.768263, 1e-4);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
} // namespace opencv_test
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
CV_PERF_TEST_MAIN(calib3d)
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#ifndef __OPENCV_PERF_PRECOMP_HPP__
|
||||
#define __OPENCV_PERF_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/calib.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,123 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef OPENCV_IMGPROC_DETAIL_DISTORTION_MODEL_HPP
|
||||
#define OPENCV_IMGPROC_DETAIL_DISTORTION_MODEL_HPP
|
||||
|
||||
//! @cond IGNORED
|
||||
|
||||
namespace cv {
|
||||
/**
|
||||
Computes the matrix for the projection onto a tilted image sensor
|
||||
\param tauX angular parameter rotation around x-axis
|
||||
\param tauY angular parameter rotation around y-axis
|
||||
\param matTilt if not NULL returns the matrix
|
||||
\f[
|
||||
\vecthreethree{R_{33}(\tau_x, \tau_y)}{0}{-R_{13}((\tau_x, \tau_y)}
|
||||
{0}{R_{33}(\tau_x, \tau_y)}{-R_{23}(\tau_x, \tau_y)}
|
||||
{0}{0}{1} R(\tau_x, \tau_y)
|
||||
\f]
|
||||
where
|
||||
\f[
|
||||
R(\tau_x, \tau_y) =
|
||||
\vecthreethree{\cos(\tau_y)}{0}{-\sin(\tau_y)}{0}{1}{0}{\sin(\tau_y)}{0}{\cos(\tau_y)}
|
||||
\vecthreethree{1}{0}{0}{0}{\cos(\tau_x)}{\sin(\tau_x)}{0}{-\sin(\tau_x)}{\cos(\tau_x)} =
|
||||
\vecthreethree{\cos(\tau_y)}{\sin(\tau_y)\sin(\tau_x)}{-\sin(\tau_y)\cos(\tau_x)}
|
||||
{0}{\cos(\tau_x)}{\sin(\tau_x)}
|
||||
{\sin(\tau_y)}{-\cos(\tau_y)\sin(\tau_x)}{\cos(\tau_y)\cos(\tau_x)}.
|
||||
\f]
|
||||
\param dMatTiltdTauX if not NULL it returns the derivative of matTilt with
|
||||
respect to \f$\tau_x\f$.
|
||||
\param dMatTiltdTauY if not NULL it returns the derivative of matTilt with
|
||||
respect to \f$\tau_y\f$.
|
||||
\param invMatTilt if not NULL it returns the inverse of matTilt
|
||||
**/
|
||||
template <typename FLOAT>
|
||||
void computeTiltProjectionMatrix(FLOAT tauX,
|
||||
FLOAT tauY,
|
||||
Matx<FLOAT, 3, 3>* matTilt = 0,
|
||||
Matx<FLOAT, 3, 3>* dMatTiltdTauX = 0,
|
||||
Matx<FLOAT, 3, 3>* dMatTiltdTauY = 0,
|
||||
Matx<FLOAT, 3, 3>* invMatTilt = 0)
|
||||
{
|
||||
FLOAT cTauX = std::cos(tauX);
|
||||
FLOAT sTauX = std::sin(tauX);
|
||||
FLOAT cTauY = std::cos(tauY);
|
||||
FLOAT sTauY = std::sin(tauY);
|
||||
Matx<FLOAT, 3, 3> matRotX = Matx<FLOAT, 3, 3>(1,0,0,0,cTauX,sTauX,0,-sTauX,cTauX);
|
||||
Matx<FLOAT, 3, 3> matRotY = Matx<FLOAT, 3, 3>(cTauY,0,-sTauY,0,1,0,sTauY,0,cTauY);
|
||||
Matx<FLOAT, 3, 3> matRotXY = matRotY * matRotX;
|
||||
Matx<FLOAT, 3, 3> matProjZ = Matx<FLOAT, 3, 3>(matRotXY(2,2),0,-matRotXY(0,2),0,matRotXY(2,2),-matRotXY(1,2),0,0,1);
|
||||
if (matTilt)
|
||||
{
|
||||
// Matrix for trapezoidal distortion of tilted image sensor
|
||||
*matTilt = matProjZ * matRotXY;
|
||||
}
|
||||
if (dMatTiltdTauX)
|
||||
{
|
||||
// Derivative with respect to tauX
|
||||
Matx<FLOAT, 3, 3> dMatRotXYdTauX = matRotY * Matx<FLOAT, 3, 3>(0,0,0,0,-sTauX,cTauX,0,-cTauX,-sTauX);
|
||||
Matx<FLOAT, 3, 3> dMatProjZdTauX = Matx<FLOAT, 3, 3>(dMatRotXYdTauX(2,2),0,-dMatRotXYdTauX(0,2),
|
||||
0,dMatRotXYdTauX(2,2),-dMatRotXYdTauX(1,2),0,0,0);
|
||||
*dMatTiltdTauX = (matProjZ * dMatRotXYdTauX) + (dMatProjZdTauX * matRotXY);
|
||||
}
|
||||
if (dMatTiltdTauY)
|
||||
{
|
||||
// Derivative with respect to tauY
|
||||
Matx<FLOAT, 3, 3> dMatRotXYdTauY = Matx<FLOAT, 3, 3>(-sTauY,0,-cTauY,0,0,0,cTauY,0,-sTauY) * matRotX;
|
||||
Matx<FLOAT, 3, 3> dMatProjZdTauY = Matx<FLOAT, 3, 3>(dMatRotXYdTauY(2,2),0,-dMatRotXYdTauY(0,2),
|
||||
0,dMatRotXYdTauY(2,2),-dMatRotXYdTauY(1,2),0,0,0);
|
||||
*dMatTiltdTauY = (matProjZ * dMatRotXYdTauY) + (dMatProjZdTauY * matRotXY);
|
||||
}
|
||||
if (invMatTilt)
|
||||
{
|
||||
FLOAT inv = 1./matRotXY(2,2);
|
||||
Matx<FLOAT, 3, 3> invMatProjZ = Matx<FLOAT, 3, 3>(inv,0,inv*matRotXY(0,2),0,inv,inv*matRotXY(1,2),0,0,1);
|
||||
*invMatTilt = matRotXY.t()*invMatProjZ;
|
||||
}
|
||||
}
|
||||
} // namespace detail, cv
|
||||
|
||||
|
||||
//! @endcond
|
||||
|
||||
#endif // OPENCV_IMGPROC_DETAIL_DISTORTION_MODEL_HPP
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef FISHEYE_INTERNAL_H
|
||||
#define FISHEYE_INTERNAL_H
|
||||
#include "precomp.hpp"
|
||||
|
||||
namespace cv { namespace internal {
|
||||
|
||||
struct CV_EXPORTS IntrinsicParams
|
||||
{
|
||||
Vec2d f;
|
||||
Vec2d c;
|
||||
Vec4d k;
|
||||
double alpha;
|
||||
std::vector<uchar> isEstimate;
|
||||
|
||||
IntrinsicParams();
|
||||
IntrinsicParams(Vec2d f, Vec2d c, Vec4d k, double alpha = 0);
|
||||
IntrinsicParams operator+(const Mat& a);
|
||||
IntrinsicParams& operator =(const Mat& a);
|
||||
void Init(const cv::Vec2d& f, const cv::Vec2d& c, const cv::Vec4d& k = Vec4d(0,0,0,0), const double& alpha = 0);
|
||||
};
|
||||
|
||||
void projectPoints(cv::InputArray objectPoints, cv::OutputArray imagePoints,
|
||||
cv::InputArray _rvec,cv::InputArray _tvec,
|
||||
const IntrinsicParams& param, cv::OutputArray jacobian);
|
||||
|
||||
void ComputeExtrinsicRefine(const Mat& imagePoints, const Mat& objectPoints, Mat& rvec,
|
||||
Mat& tvec, Mat& J, const int MaxIter,
|
||||
const IntrinsicParams& param, const double thresh_cond);
|
||||
CV_EXPORTS Mat ComputeHomography(Mat m, Mat M);
|
||||
|
||||
CV_EXPORTS Mat NormalizePixels(const Mat& imagePoints, const IntrinsicParams& param);
|
||||
|
||||
void InitExtrinsics(const Mat& _imagePoints, const Mat& _objectPoints, const IntrinsicParams& param, Mat& omckk, Mat& Tckk);
|
||||
|
||||
void CalibrateExtrinsics(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
|
||||
const IntrinsicParams& param, const int check_cond,
|
||||
const double thresh_cond, InputOutputArray omc, InputOutputArray Tc);
|
||||
|
||||
void ComputeJacobians(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
|
||||
const IntrinsicParams& param, InputArray omc, InputArray Tc,
|
||||
const int& check_cond, const double& thresh_cond, Mat& JJ2_inv, Mat& ex3);
|
||||
|
||||
CV_EXPORTS void EstimateUncertainties(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
|
||||
const IntrinsicParams& params, InputArray omc, InputArray Tc,
|
||||
IntrinsicParams& errors, Vec2d& std_err, double thresh_cond, int check_cond, double& rms);
|
||||
|
||||
void dAB(cv::InputArray A, InputArray B, OutputArray dABdA, OutputArray dABdB);
|
||||
|
||||
void JRodriguesMatlab(const Mat& src, Mat& dst);
|
||||
|
||||
void compose_motion(InputArray _om1, InputArray _T1, InputArray _om2, InputArray _T2,
|
||||
Mat& om3, Mat& T3, Mat& dom3dom1, Mat& dom3dT1, Mat& dom3dom2,
|
||||
Mat& dom3dT2, Mat& dT3dom1, Mat& dT3dT1, Mat& dT3dom2, Mat& dT3dT2);
|
||||
|
||||
double median(const Mat& row);
|
||||
|
||||
Vec3d median3d(InputArray m);
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Copyright (C) 2015, Itseez Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
//
|
||||
// Library initialization file
|
||||
//
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
IPP_INITIALIZER_AUTO
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,988 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
#include "opencv2/core/softfloat.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace multiview {
|
||||
class RobustFunction : public Algorithm {
|
||||
public:
|
||||
virtual float getError(float err) const = 0;
|
||||
};
|
||||
|
||||
#define USE_FAST_EXP 0
|
||||
|
||||
#if USE_FAST_EXP
|
||||
class RobustExpFunction : public RobustFunction {
|
||||
private:
|
||||
const float over_scale, pow_23 = 1 << 23;
|
||||
public:
|
||||
explicit RobustExpFunction (float scale_=30.0f) : over_scale(-1.442695040f /scale_) {}
|
||||
// err > 0
|
||||
float getError(float err) const override {
|
||||
const float under_exp = err * over_scale;
|
||||
if (under_exp < -20) return 0; // prevent overflow further
|
||||
// http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html
|
||||
softfloat vexp = softfloat::fromRaw(static_cast<uint32_t>(pow_23 * (under_exp + 126.94269504f)));
|
||||
return float(vexp);
|
||||
}
|
||||
};
|
||||
#else
|
||||
class RobustExpFunction : public RobustFunction {
|
||||
private:
|
||||
const float minvScale;
|
||||
public:
|
||||
explicit RobustExpFunction (float scale_=30.0f) : minvScale(-1.f / scale_) {}
|
||||
// err > 0
|
||||
float getError(float err) const override
|
||||
{
|
||||
return exp(minvScale * err);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
static double robustWrapper (const Mat& ptsErrors, Mat& weights, const RobustFunction &fnc) {
|
||||
Mat errs;
|
||||
ptsErrors.convertTo(errs, CV_32F);
|
||||
weights.create((int)ptsErrors.total()*ptsErrors.channels(), 1, CV_32FC1);
|
||||
const Point2f * errs_ptr = errs.ptr<Point2f>();
|
||||
float * weights_ptr = weights.ptr<float>();
|
||||
double robust_sum_sqr_errs = 0.0;
|
||||
for (int pt = 0; pt < (int)errs.total(); pt++) {
|
||||
Point2f p = errs_ptr[pt];
|
||||
float sqr_err = p.dot(p);
|
||||
float w = fnc.getError(sqr_err);
|
||||
weights_ptr[pt*2 + 0] = w;
|
||||
weights_ptr[pt*2 + 1] = w;
|
||||
robust_sum_sqr_errs += w * sqr_err;
|
||||
}
|
||||
return robust_sum_sqr_errs;
|
||||
}
|
||||
|
||||
static double computeReprojectionMSE(const Mat &obj_points_, const Mat &img_points_, const Matx33d &K, const Mat &distortion,
|
||||
const Mat &rvec, const Mat &tvec, InputArray rvec2, InputArray tvec2, int model) {
|
||||
Mat r, t;
|
||||
if (!rvec2.empty() && !tvec2.empty()) {
|
||||
composeRT(rvec, tvec, rvec2, tvec2, r, t);
|
||||
} else {
|
||||
r = rvec; t = tvec;
|
||||
}
|
||||
Mat tmpImagePoints, obj_points = obj_points_, img_points = img_points_;
|
||||
if (model == cv::CALIB_MODEL_FISHEYE) {
|
||||
obj_points = obj_points.reshape(3); // must be 3 channels
|
||||
fisheye::projectPoints(obj_points, tmpImagePoints, r, t, K, distortion);
|
||||
} else if (model == cv::CALIB_MODEL_PINHOLE) {
|
||||
projectPoints(obj_points, r, t, K, distortion, tmpImagePoints);
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg, "Unsupported camera model!");
|
||||
}
|
||||
|
||||
if (img_points.channels() != tmpImagePoints.channels())
|
||||
img_points = img_points.reshape(tmpImagePoints.channels());
|
||||
if (img_points.rows != tmpImagePoints.rows)
|
||||
img_points = img_points.t();
|
||||
subtract (tmpImagePoints, img_points, tmpImagePoints);
|
||||
return norm(tmpImagePoints, NORM_L2SQR) / tmpImagePoints.rows;
|
||||
}
|
||||
|
||||
static bool maximumSpanningTree (int NUM_CAMERAS, int NUM_FRAMES, const std::vector<std::vector<bool>> &detection_mask,
|
||||
std::vector<int> &parent, std::vector<std::vector<int>> &overlap,
|
||||
std::vector<std::vector<Vec3d>> &opt_axes,
|
||||
const std::vector<std::vector<bool>> &is_valid_angle2pattern,
|
||||
const std::vector<std::vector<float>> &points_area_ratio,
|
||||
double WEIGHT_ANGLE_PATTERN, double WEIGHT_CAMERAS_ANGLES) {
|
||||
const double THR_CAMERAS_ANGLES = 160*M_PI/180;
|
||||
// build weights matrix
|
||||
overlap = std::vector<std::vector<int>>(NUM_CAMERAS, std::vector<int>(NUM_CAMERAS, 0));
|
||||
std::vector<std::vector<double>> weights(NUM_CAMERAS, std::vector<double>(NUM_CAMERAS, DBL_MIN));
|
||||
for (int c1 = 0; c1 < NUM_CAMERAS; c1++) {
|
||||
for (int c2 = c1+1; c2 < NUM_CAMERAS; c2++) {
|
||||
double weight = 0;
|
||||
int overlaps = 0;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask[c1][f] && detection_mask[c2][f]) {
|
||||
overlaps += 1;
|
||||
weight += points_area_ratio[c1][f] + points_area_ratio[c2][f];
|
||||
weight += WEIGHT_ANGLE_PATTERN * ((int)is_valid_angle2pattern[c1][f] + (int)is_valid_angle2pattern[c2][f]);
|
||||
if (WEIGHT_CAMERAS_ANGLES > 0) {
|
||||
// angle between cameras optical axes
|
||||
weight += WEIGHT_CAMERAS_ANGLES * int(acos(opt_axes[c1][f].dot(opt_axes[c2][f])) < THR_CAMERAS_ANGLES);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (overlaps > 0) {
|
||||
overlap[c1][c2] = overlap[c2][c1] = overlaps;
|
||||
weights[c1][c2] = weights[c2][c1] = overlaps + weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find maximum spanning tree using Prim's algorithm
|
||||
std::vector<bool> visited(NUM_CAMERAS, false);
|
||||
std::vector<double> weight(NUM_CAMERAS, DBL_MIN);
|
||||
parent = std::vector<int>(NUM_CAMERAS, -1);
|
||||
weight[0] = DBL_MAX;
|
||||
for (int cam = 0; cam < NUM_CAMERAS-1; cam++) {
|
||||
int max_weight_idx = -1;
|
||||
auto max_weight = DBL_MIN;
|
||||
for (int cam2 = 0; cam2 < NUM_CAMERAS; cam2++) {
|
||||
if (!visited[cam2] && max_weight < weight[cam2]) {
|
||||
max_weight = weight[cam2];
|
||||
max_weight_idx = cam2;
|
||||
}
|
||||
}
|
||||
if (max_weight_idx == -1)
|
||||
return false;
|
||||
visited[max_weight_idx] = true;
|
||||
for (int cam2 = 0; cam2 < NUM_CAMERAS; cam2++) {
|
||||
if (!visited[cam2] && overlap[max_weight_idx][cam2] > 0) {
|
||||
if (weight[cam2] < weights[max_weight_idx][cam2]) {
|
||||
weight[cam2] = weights[max_weight_idx][cam2];
|
||||
parent[cam2] = max_weight_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void imagePointsArea (const std::vector<Size> &imageSize, const std::vector<std::vector<bool>> &detection_mask_mat,
|
||||
const std::vector<std::vector<Mat>> &imagePoints, std::vector<std::vector<float>> &points_ratio_area) {
|
||||
const int NUM_CAMERAS = (int) imageSize.size(), NUM_FRAMES = (int)detection_mask_mat[0].size();
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
const auto img_area = (float)(imageSize[c].width * imageSize[c].height);
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (!detection_mask_mat[c][f])
|
||||
continue;
|
||||
CV_Assert((imagePoints[c][f].type() == CV_32F && imagePoints[c][f].cols == 2) || imagePoints[c][f].type() == CV_32FC2);
|
||||
std::vector<int> hull;
|
||||
const auto img_pts = imagePoints[c][f];
|
||||
const auto * const image_pts_ptr = (float *) img_pts.data;
|
||||
convexHull(img_pts, hull, true/*has to be clockwise*/, false/*indices*/);
|
||||
float area = 0;
|
||||
int j = hull.back();
|
||||
// http://alienryderflex.com/polygon_area/
|
||||
for (int i : hull) {
|
||||
area += (image_pts_ptr[j*2] + image_pts_ptr[i*2])*(image_pts_ptr[j*2+1] - image_pts_ptr[i*2+1]);
|
||||
j = i;
|
||||
}
|
||||
points_ratio_area[c][f] = area*.5f / img_area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void selectPairsBFS (std::vector<std::pair<int,int>> &pairs, int NUM_CAMERAS, const std::vector<int> &parent) {
|
||||
// find pairs using Breadth First Search graph traversing
|
||||
// it is important to keep this order of pairs, since it is easier
|
||||
// to find relative views wrt to 0-th camera.
|
||||
std::vector<int> nodes = {0};
|
||||
pairs.reserve(NUM_CAMERAS-1);
|
||||
while (!nodes.empty()) {
|
||||
std::vector<int> new_nodes;
|
||||
for (int n : nodes) {
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
if (parent[c] == n) {
|
||||
pairs.emplace_back(std::make_pair(n, c));
|
||||
new_nodes.emplace_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes = new_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
static double getScaleOfObjPoints (int NUM_PATTERN_PTS, const Mat &obj_pts, bool obj_points_in_rows) {
|
||||
double scale_3d_pts = 0.0;
|
||||
// compute scale of 3D points as the maximum pairwise distance
|
||||
for (int i = 0; i < NUM_PATTERN_PTS; i++) {
|
||||
for (int j = i+1; j < NUM_PATTERN_PTS; j++) {
|
||||
double dist;
|
||||
if (obj_points_in_rows) {
|
||||
dist = norm(obj_pts.row(i)-obj_pts.row(j), NORM_L2SQR);
|
||||
} else {
|
||||
dist = norm(obj_pts.col(i)-obj_pts.col(j), NORM_L2SQR);
|
||||
}
|
||||
if (scale_3d_pts < dist) {
|
||||
scale_3d_pts = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scale_3d_pts;
|
||||
}
|
||||
|
||||
static void thresholdPatternCameraAngles (int NUM_PATTERN_PTS, double THR_PATTERN_CAMERA_ANGLES,
|
||||
const std::vector<Mat> &objPoints_norm, const std::vector<std::vector<Vec3d>> &rvecs_all,
|
||||
std::vector<std::vector<Vec3d>> &opt_axes, std::vector<std::vector<bool>> &is_valid_angle2pattern) {
|
||||
const int NUM_FRAMES = (int)objPoints_norm.size(), NUM_CAMERAS = (int)rvecs_all.size();
|
||||
is_valid_angle2pattern = std::vector<std::vector<bool>>(NUM_CAMERAS, std::vector<bool>(NUM_FRAMES, true));
|
||||
int pattern1 = -1, pattern2 = -1, pattern3 = -1;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
double norm_normal = 0;
|
||||
if (pattern1 == -1) {
|
||||
// take non colinear 3 points and save them
|
||||
for (int p1 = 0; p1 < NUM_PATTERN_PTS; p1++) {
|
||||
for (int p2 = p1+1; p2 < NUM_PATTERN_PTS; p2++) {
|
||||
for (int p3 = NUM_PATTERN_PTS-1; p3 > p2; p3--) { // start from the last point
|
||||
Mat pattern_normal = (objPoints_norm[f].row(p2)-objPoints_norm[f].row(p1))
|
||||
.cross(objPoints_norm[f].row(p3)-objPoints_norm[f].row(p1));
|
||||
norm_normal = norm(pattern_normal, NORM_L2SQR);
|
||||
if (norm_normal > 1e-6) {
|
||||
pattern1 = p1;
|
||||
pattern2 = p2;
|
||||
pattern3 = p3;
|
||||
norm_normal = sqrt(norm_normal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pattern1 != -1) break;
|
||||
}
|
||||
if (pattern1 != -1) break;
|
||||
}
|
||||
if (pattern1 == -1) {
|
||||
CV_Error(Error::StsBadArg, "Pattern points are collinear!");
|
||||
}
|
||||
}
|
||||
Vec3d pattern_normal = (objPoints_norm[f].row(pattern2)-objPoints_norm[f].row(pattern1)).
|
||||
cross(objPoints_norm[f].row(pattern3)-objPoints_norm[f].row(pattern1));
|
||||
norm_normal = norm(pattern_normal);
|
||||
pattern_normal /= norm_normal;
|
||||
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
Matx33d R;
|
||||
Rodrigues(rvecs_all[c][f], R);
|
||||
opt_axes[c][f] = Vec3d(Mat(R.row(2)));
|
||||
const double angle = acos(opt_axes[c][f].dot(pattern_normal));
|
||||
is_valid_angle2pattern[c][f] = min(M_PI-angle, angle) < THR_PATTERN_CAMERA_ANGLES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pairwiseRegistration (const std::vector<std::pair<int,int>> &pairs,
|
||||
const cv::Mat &models, const std::vector<Mat> &objPoints_norm,
|
||||
const std::vector<std::vector<Mat>> &imagePoints, const std::vector<std::vector<int>> &overlaps,
|
||||
const std::vector<std::vector<bool>> &detection_mask_mat, const std::vector<Mat> &Ks,
|
||||
const std::vector<Mat> &distortions, std::vector<Matx33d> &Rs_vec, std::vector<Vec3d> &Ts_vec,
|
||||
Mat &intrinsic_flags, int extrinsic_flags, TermCriteria criteria) {
|
||||
CV_UNUSED(intrinsic_flags);
|
||||
const int NUM_FRAMES = (int)objPoints_norm.size();
|
||||
const int NUM_CAMERAS = (int)detection_mask_mat.size();
|
||||
std::vector<Matx33d> Rs_prior;
|
||||
std::vector<Vec3d> Ts_prior;
|
||||
if (extrinsic_flags & cv::CALIB_USE_EXTRINSIC_GUESS) {
|
||||
Rs_prior.resize(NUM_CAMERAS);
|
||||
Ts_prior.resize(NUM_CAMERAS);
|
||||
for (int i = 0; i < NUM_CAMERAS; i++) {
|
||||
Rs_vec[i].copyTo(Rs_prior[i]);
|
||||
Ts_vec[i].copyTo(Ts_prior[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &pair : pairs) {
|
||||
const int c1 = pair.first, c2 = pair.second, overlap = overlaps[c1][c2];
|
||||
// prepare image points of two cameras and grid points
|
||||
std::vector<Mat> image_points1, image_points2, grid_points1, grid_points2;
|
||||
grid_points1.reserve(overlap);
|
||||
grid_points2.reserve(overlap);
|
||||
image_points1.reserve(overlap);
|
||||
image_points2.reserve(overlap);
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[c1][f] && detection_mask_mat[c2][f]) {
|
||||
grid_points1.emplace_back(objPoints_norm[f]);
|
||||
grid_points2.emplace_back(objPoints_norm[f]);
|
||||
image_points1.emplace_back(imagePoints[c1][f]);
|
||||
image_points2.emplace_back(imagePoints[c2][f]);
|
||||
}
|
||||
}
|
||||
Matx33d R;
|
||||
Vec3d T;
|
||||
|
||||
if (extrinsic_flags & cv::CALIB_USE_EXTRINSIC_GUESS) {
|
||||
R = Rs_prior[c2] * Rs_prior[c1].t();
|
||||
T = -R * Ts_prior[c1] + Ts_prior[c2];
|
||||
}
|
||||
|
||||
extrinsic_flags |= CALIB_FIX_INTRINSIC;
|
||||
double err = registerCameras(grid_points1, grid_points2, image_points1, image_points2,
|
||||
Ks[c1], distortions[c1], cv::CameraModel(models.at<uchar>(c1)),
|
||||
Ks[c2], distortions[c2], cv::CameraModel(models.at<uchar>(c2)),
|
||||
R, T, noArray(), noArray(), noArray(), noArray(), noArray(),
|
||||
extrinsic_flags, criteria);
|
||||
CV_LOG_INFO(NULL, "Pair " << c1 << "-" << c2 << " registration RMS " << err);
|
||||
// R_0 = I
|
||||
// R_ij = R_i R_j^T => R_i = R_ij R_j
|
||||
// t_ij = ti - R_ij tj => t_i = t_ij + R_ij t_j
|
||||
if (c1 == 0) {
|
||||
Rs_vec[c2] = R;
|
||||
Ts_vec[c2] = T;
|
||||
} else {
|
||||
Rs_vec[c2] = Matx33d(Mat(R * Rs_vec[c1]));
|
||||
Ts_vec[c2] = Vec3d(Mat(T + R * Ts_vec[c1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pairwiseStereoCalibration (const std::vector<std::pair<int,int>> &pairs,
|
||||
const cv::Mat &models, const std::vector<Mat> &objPoints_norm,
|
||||
const std::vector<std::vector<Mat>> &imagePoints, const std::vector<std::vector<int>> &overlaps,
|
||||
const std::vector<std::vector<bool>> &detection_mask_mat, const std::vector<Mat> &Ks,
|
||||
const std::vector<Mat> &distortions, std::vector<Matx33d> &Rs_vec, std::vector<Vec3d> &Ts_vec,
|
||||
Mat &intrinsic_flags, int extrinsic_flags, TermCriteria criteria) {
|
||||
const int NUM_FRAMES = (int)objPoints_norm.size();
|
||||
const int NUM_CAMERAS = (int)detection_mask_mat.size();
|
||||
std::vector<Matx33d> Rs_prior;
|
||||
std::vector<Vec3d> Ts_prior;
|
||||
if (extrinsic_flags & cv::CALIB_USE_EXTRINSIC_GUESS) {
|
||||
Rs_prior.resize(NUM_CAMERAS);
|
||||
Ts_prior.resize(NUM_CAMERAS);
|
||||
for (int i = 0; i < NUM_CAMERAS; i++) {
|
||||
Rs_vec[i].copyTo(Rs_prior[i]);
|
||||
Ts_vec[i].copyTo(Ts_prior[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &pair : pairs) {
|
||||
const int c1 = pair.first, c2 = pair.second, overlap = overlaps[c1][c2];
|
||||
// prepare image points of two cameras and grid points
|
||||
std::vector<Mat> image_points1, image_points2, grid_points;
|
||||
grid_points.reserve(overlap);
|
||||
image_points1.reserve(overlap);
|
||||
image_points2.reserve(overlap);
|
||||
const bool are_fisheye_cams = models.at<uchar>(c1) == cv::CALIB_MODEL_FISHEYE &&
|
||||
models.at<uchar>(c2) == cv::CALIB_MODEL_FISHEYE;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[c1][f] && detection_mask_mat[c2][f]) {
|
||||
grid_points.emplace_back((are_fisheye_cams && objPoints_norm[f].channels() != 3) ?
|
||||
objPoints_norm[f].reshape(3): objPoints_norm[f]);
|
||||
image_points1.emplace_back((are_fisheye_cams && imagePoints[c1][f].channels() != 2) ?
|
||||
imagePoints[c1][f].reshape(2) : imagePoints[c1][f]);
|
||||
image_points2.emplace_back((are_fisheye_cams && imagePoints[c2][f].channels() != 2) ?
|
||||
imagePoints[c2][f].reshape(2) : imagePoints[c2][f]);
|
||||
}
|
||||
}
|
||||
Matx33d R;
|
||||
Vec3d T;
|
||||
|
||||
if (extrinsic_flags & cv::CALIB_USE_EXTRINSIC_GUESS) {
|
||||
R = Rs_prior[c2] * Rs_prior[c1].t();
|
||||
T = -R * Ts_prior[c1] + Ts_prior[c2];
|
||||
}
|
||||
|
||||
// stereoCalibrate tries to overwrite distortion coefficients
|
||||
Mat dist1 = distortions[c1].clone();
|
||||
Mat dist2 = distortions[c2].clone();
|
||||
|
||||
// image size does not matter since intrinsics are used
|
||||
if (are_fisheye_cams) {
|
||||
extrinsic_flags |= CALIB_FIX_INTRINSIC;
|
||||
double err = fisheye::stereoCalibrate(grid_points, image_points1, image_points2,
|
||||
Ks[c1], dist1,
|
||||
Ks[c2], dist2,
|
||||
Size(), R, T,
|
||||
extrinsic_flags, criteria);
|
||||
CV_LOG_INFO(NULL, "Stereo pair " << c1 << "-" << c2 << " registration RMS " << err);
|
||||
} else {
|
||||
extrinsic_flags |= CALIB_FIX_INTRINSIC;
|
||||
if ((intrinsic_flags.at<int>(c1) & CALIB_RATIONAL_MODEL) || (intrinsic_flags.at<int>(c2) & CALIB_RATIONAL_MODEL))
|
||||
extrinsic_flags |= CALIB_RATIONAL_MODEL;
|
||||
if ((intrinsic_flags.at<int>(c1) & CALIB_THIN_PRISM_MODEL) || (intrinsic_flags.at<int>(c2) & CALIB_THIN_PRISM_MODEL))
|
||||
extrinsic_flags |= CALIB_THIN_PRISM_MODEL;
|
||||
|
||||
double err = stereoCalibrate(grid_points, image_points1, image_points2,
|
||||
Ks[c1], dist1,
|
||||
Ks[c2], dist2,
|
||||
Size(), R, T, noArray(), noArray(), noArray(),
|
||||
extrinsic_flags, criteria);
|
||||
CV_LOG_INFO(NULL, "Stereo pair " << c1 << "-" << c2 << " registration RMS " << err);
|
||||
}
|
||||
|
||||
// R_0 = I
|
||||
// R_ij = R_i R_j^T => R_i = R_ij R_j
|
||||
// t_ij = ti - R_ij tj => t_i = t_ij + R_ij t_j
|
||||
if (c1 == 0) {
|
||||
Rs_vec[c2] = R;
|
||||
Ts_vec[c2] = T;
|
||||
} else {
|
||||
Rs_vec[c2] = Matx33d(Mat(R * Rs_vec[c1]));
|
||||
Ts_vec[c2] = Vec3d(Mat(T + R * Ts_vec[c1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void optimizeLM (std::vector<double> ¶m, const RobustFunction &robust_fnc, const TermCriteria &termCrit,
|
||||
const std::vector<bool> &valid_frames, const std::vector<std::vector<bool>> &detection_mask_mat,
|
||||
const std::vector<Mat> &objPoints_norm, const std::vector<std::vector<Mat>> &imagePoints,
|
||||
const std::vector<Mat> &Ks, const std::vector<Mat> &distortions,
|
||||
const Mat& models, int NUM_PATTERN_PTS) {
|
||||
const int NUM_FRAMES = (int) objPoints_norm.size(), NUM_CAMERAS = (int)detection_mask_mat.size();
|
||||
int iters_lm = 0, cnt_valid_frame = 0;
|
||||
auto lmcallback = [&](InputOutputArray _param, OutputArray JtErr_, OutputArray JtJ_, double& errnorm) {
|
||||
auto * param_p = _param.getMat().ptr<double>();
|
||||
errnorm = 0;
|
||||
cnt_valid_frame = 0;
|
||||
for (int i = 0; i < NUM_FRAMES; i++ ) {
|
||||
if (!valid_frames[i]) continue;
|
||||
for (int k = 0; k < NUM_CAMERAS; k++ ) {
|
||||
// Pose for camera #0 is not optimized, but it's re-projection error is taken into account
|
||||
if (!detection_mask_mat[k][i]) continue;
|
||||
const int cam_idx = (k-1)*6; // camera extrinsics
|
||||
const auto * const pose_k = (k > 0)? (param_p + cam_idx) : nullptr;
|
||||
Vec3d om_0ToK = (k > 0)? Vec3d(pose_k[0], pose_k[1], pose_k[2]) : Vec3d(0., 0., 0.), om[2];
|
||||
Vec3d T_0ToK = (k > 0)? Vec3d(pose_k[3], pose_k[4], pose_k[5]) : Vec3d(0., 0., 0.), T[2];
|
||||
Matx33d dr3dr1, dr3dr2, dt3dr2, dt3dt1, dt3dt2;
|
||||
|
||||
auto * pi = param_p + (cnt_valid_frame+NUM_CAMERAS-1)*6; // get rvecs / tvecs for frame pose
|
||||
om[0] = Vec3d(pi[0], pi[1], pi[2]);
|
||||
T[0] = Vec3d(pi[3], pi[4], pi[5]);
|
||||
|
||||
if( JtJ_.needed() || JtErr_.needed() )
|
||||
composeRT( om[0], T[0], om_0ToK, T_0ToK, om[1], T[1], dr3dr1, noArray(),
|
||||
dr3dr2, noArray(), noArray(), dt3dt1, dt3dr2, dt3dt2 );
|
||||
else
|
||||
composeRT( om[0], T[0], om_0ToK, T_0ToK, om[1], T[1] );
|
||||
|
||||
// get object points
|
||||
Mat objpt_i = objPoints_norm[i].reshape(3, 1);
|
||||
objpt_i.convertTo(objpt_i, CV_64FC3);
|
||||
|
||||
Mat err( NUM_PATTERN_PTS*2, 1, CV_64F ), tmpImagePoints = err.reshape(2, 1);
|
||||
Mat Je( NUM_PATTERN_PTS*2, 6, CV_64F ), J_0ToK( NUM_PATTERN_PTS*2, 6, CV_64F );
|
||||
Mat dpdrot = Je.colRange(0, 3), dpdt = Je.colRange(3, 6); // num_points*2 x 3 each
|
||||
// get image points
|
||||
Mat imgpt_ik = imagePoints[k][i].reshape(2, 1);
|
||||
imgpt_ik.convertTo(imgpt_ik, CV_64FC2);
|
||||
|
||||
if (models.at<uchar>(k)) {
|
||||
if( JtJ_.needed() || JtErr_.needed() ) {
|
||||
Mat jacobian; // of size num_points*2 x 15 (2 + 2 + 1 + 4 + 3 + 3; // f, c, alpha, k, om, T)
|
||||
fisheye::projectPoints(objpt_i, tmpImagePoints, om[1], T[1], Ks[k], distortions[k], 0, jacobian);
|
||||
jacobian.colRange(8,11).copyTo(dpdrot);
|
||||
jacobian.colRange(11,14).copyTo(dpdt);
|
||||
} else
|
||||
fisheye::projectPoints(objpt_i, tmpImagePoints, om[1], T[1], Ks[k], distortions[k]);
|
||||
} else {
|
||||
if( JtJ_.needed() || JtErr_.needed() )
|
||||
projectPoints(objpt_i, om[1], T[1], Ks[k], distortions[k],
|
||||
tmpImagePoints, dpdrot, dpdt, noArray(), noArray(), noArray(), noArray());
|
||||
else
|
||||
projectPoints(objpt_i, om[1], T[1], Ks[k], distortions[k], tmpImagePoints);
|
||||
}
|
||||
subtract( tmpImagePoints, imgpt_ik, tmpImagePoints);
|
||||
Mat weights;
|
||||
const double robust_l2_norm = multiview::robustWrapper(tmpImagePoints, weights, robust_fnc);
|
||||
errnorm += robust_l2_norm;
|
||||
|
||||
if (JtJ_.needed()) {
|
||||
Mat JtErr = JtErr_.getMat(), JtJ = JtJ_.getMat();
|
||||
const int eofs = (cnt_valid_frame+NUM_CAMERAS-1)*6;
|
||||
assert( JtJ_.needed() && JtErr_.needed() );
|
||||
// JtJ : NUM_PARAMS x NUM_PARAMS, JtErr : NUM_PARAMS x 1
|
||||
// d(err_{x|y}R) ~ de3
|
||||
// convert de3/{dr3,dt3} => de3{dr1,dt1} & de3{dr2,dt2}
|
||||
|
||||
Mat wd;
|
||||
Mat::diag(weights).convertTo(wd, CV_64F);
|
||||
if (k > 0) { // if not camera #0
|
||||
for (int p = 0; p < NUM_PATTERN_PTS * 2; p++) {
|
||||
Matx13d de3dr3, de3dt3, de3dr2, de3dt2, de3dr1, de3dt1;
|
||||
for (int j = 0; j < 3; j++)
|
||||
de3dr3(j) = Je.at<double>(p, j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
de3dt3(j) = Je.at<double>(p, 3 + j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
de3dr2(j) = J_0ToK.at<double>(p, j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
de3dt2(j) = J_0ToK.at<double>(p, 3 + j);
|
||||
|
||||
de3dr1 = de3dr3 * dr3dr1;
|
||||
de3dt1 = de3dt3 * dt3dt1;
|
||||
de3dr2 = de3dr3 * dr3dr2 + de3dt3 * dt3dr2;
|
||||
de3dt2 = de3dt3 * dt3dt2;
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
Je.at<double>(p, j) = de3dr1(j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
Je.at<double>(p, 3 + j) = de3dt1(j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
J_0ToK.at<double>(p, j) = de3dr2(j);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
J_0ToK.at<double>(p, 3 + j) = de3dt2(j);
|
||||
}
|
||||
|
||||
// 6 x (ni*2) * (ni*2 x ni*2) * (ni*2) x 6
|
||||
JtJ(Rect((k - 1) * 6, (k - 1) * 6, 6, 6)) += (J_0ToK.t() * wd * J_0ToK);
|
||||
JtJ(Rect(eofs, (k - 1) * 6, 6, 6)) = (J_0ToK.t() * wd * Je);
|
||||
JtErr.rowRange((k - 1) * 6, (k - 1) * 6 + 6) += (J_0ToK.t() * wd * err);
|
||||
}
|
||||
JtJ(Rect(eofs, eofs, 6, 6)) += Je.t() * wd * Je;
|
||||
JtErr.rowRange(eofs, eofs + 6) += Je.t() * wd * err;
|
||||
}
|
||||
}
|
||||
cnt_valid_frame++;
|
||||
}
|
||||
iters_lm += 1;
|
||||
return true;
|
||||
};
|
||||
LevMarq solver(param, lmcallback,
|
||||
LevMarq::Settings()
|
||||
.setMaxIterations(termCrit.maxCount)
|
||||
.setStepNormTolerance(termCrit.epsilon)
|
||||
.setSmallEnergyTolerance(termCrit.epsilon * termCrit.epsilon),
|
||||
noArray()/*mask, all variables to optimize*/);
|
||||
cv::LevMarq::Report status = solver.optimize();
|
||||
CV_LOG_INFO(NULL, "LevMarq finished with status " << status.found << " energy " << status.energy << " after " << status.iters << " iterations");
|
||||
}
|
||||
|
||||
static void checkConnected (const std::vector<std::vector<bool>> &detection_mask_mat) {
|
||||
const int NUM_CAMERAS = (int)detection_mask_mat.size(), NUM_FRAMES = (int)detection_mask_mat[0].size();
|
||||
std::vector<bool> visited(NUM_CAMERAS, false);
|
||||
std::function<void(int)> dfs_search;
|
||||
dfs_search = [&] (int cam) {
|
||||
visited[cam] = true;
|
||||
for (int cam2 = 0; cam2 < NUM_CAMERAS; cam2++) {
|
||||
if (!visited[cam2]) {
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[cam][f] && detection_mask_mat[cam2][f]) {
|
||||
dfs_search(cam2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
dfs_search(0);
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
if (! visited[c]) {
|
||||
std::string isolated_cameras = "", visited_str = "";
|
||||
for (int i = 0; i < NUM_CAMERAS; i++) {
|
||||
if (!visited_str[i]) {
|
||||
if (isolated_cameras != "")
|
||||
isolated_cameras += ", ";
|
||||
isolated_cameras += std::to_string(i);
|
||||
} else {
|
||||
if (visited_str != "")
|
||||
visited_str += ", ";
|
||||
visited_str += std::to_string(i);
|
||||
}
|
||||
}
|
||||
CV_Error(Error::StsBadArg, "Isolated cameras (or components) "+isolated_cameras+" from the connected component "+visited_str+"!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double calibrateMultiview(
|
||||
InputArrayOfArrays objPoints, const std::vector<std::vector<Mat>> &imagePoints,
|
||||
const std::vector<cv::Size>& imageSize, InputArray detectionMask, InputArray models,
|
||||
InputOutputArrayOfArrays Ks, InputOutputArrayOfArrays distortions,
|
||||
InputOutputArrayOfArrays Rs, InputOutputArrayOfArrays Ts,
|
||||
OutputArray initializationPairs, OutputArrayOfArrays rvecs0,
|
||||
OutputArrayOfArrays tvecs0, OutputArray perFrameErrors,
|
||||
InputArray flagsForIntrinsics, int flags, TermCriteria criteria) {
|
||||
CV_CheckFalse(objPoints.empty(), "Objects points must not be empty!");
|
||||
CV_CheckFalse(imagePoints.empty(), "Image points must not be empty!");
|
||||
CV_CheckFalse(imageSize.empty(), "Image size per camera must not be empty!");
|
||||
CV_CheckFalse(detectionMask.empty(), "detectionMask matrix must not be empty!");
|
||||
CV_CheckFalse(models.empty(), "Fisheye mask must not be empty!");
|
||||
|
||||
Mat detection_mask_ = detectionMask.getMat();
|
||||
Mat models_mat = models.getMat();
|
||||
|
||||
CV_Assert(detection_mask_.type() == CV_8UC1 || detection_mask_.type() == CV_BoolC1);
|
||||
CV_CheckEQ(models_mat.type(), CV_8U, "models must be of type CV_8U");
|
||||
|
||||
if(flags & cv::CALIB_STEREO_REGISTRATION)
|
||||
{
|
||||
bool is_fisheye = false;
|
||||
bool is_pinhole = false;
|
||||
|
||||
for (int i = 0; i < (int)models_mat.total(); i++) {
|
||||
if (models_mat.at<uchar>(i) == cv::CALIB_MODEL_FISHEYE) {
|
||||
is_fisheye = true;
|
||||
} else if (models_mat.at<uchar>(i) == cv::CALIB_MODEL_PINHOLE) {
|
||||
is_pinhole = true;
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg, "Unsupported camera model");
|
||||
}
|
||||
}
|
||||
CV_CheckEQ(is_fisheye && is_pinhole, false, "Mix of pinhole and fisheye cameras is not supported with CALIB_STEREO_REGISTRATION flag");
|
||||
}
|
||||
|
||||
// equal number of cameras
|
||||
CV_Assert(imageSize.size() == imagePoints.size());
|
||||
CV_Assert(detection_mask_.rows == std::max(models.rows(), models.cols()));
|
||||
CV_Assert(detection_mask_.rows == (int)imageSize.size());
|
||||
CV_Assert(detection_mask_.cols == std::max(objPoints.rows(), objPoints.cols())); // equal number of frames
|
||||
CV_Assert(Rs.isMatVector() == Ts.isMatVector());
|
||||
if (flags & cv::CALIB_USE_INTRINSIC_GUESS) {
|
||||
CV_Assert(Ks.isMatVector() && distortions.isMatVector());
|
||||
CV_Assert(Ks.total() == distortions.total() && Ks.total() == imageSize.size());
|
||||
}
|
||||
if (flags & cv::CALIB_USE_EXTRINSIC_GUESS) {
|
||||
CV_Assert(Rs.isMatVector() && Ts.isMatVector());
|
||||
CV_Assert(Rs.total() == Ts.total() && Rs.total() == imageSize.size());
|
||||
}
|
||||
// normalize object points
|
||||
const Mat obj_pts_0 = objPoints.getMat(0);
|
||||
CV_Assert((obj_pts_0.type() == CV_32F && (obj_pts_0.rows == 3 || obj_pts_0.cols == 3)) ||
|
||||
(obj_pts_0.type() == CV_32FC3 && (obj_pts_0.rows == 1 || obj_pts_0.cols == 1)));
|
||||
const bool obj_points_in_rows = obj_pts_0.cols == 3;
|
||||
const int NUM_CAMERAS = (int)detection_mask_.rows, NUM_FRAMES = (int)detection_mask_.cols;
|
||||
CV_Assert((NUM_CAMERAS > 1) && (NUM_FRAMES > 0));
|
||||
const int NUM_PATTERN_PTS = obj_points_in_rows ? obj_pts_0.rows : obj_pts_0.cols;
|
||||
const double scale_3d_pts = multiview::getScaleOfObjPoints(NUM_PATTERN_PTS, obj_pts_0, obj_points_in_rows);
|
||||
|
||||
Mat flagsForIntrinsics_mat = flagsForIntrinsics.getMat();
|
||||
if (flagsForIntrinsics_mat.empty())
|
||||
{
|
||||
flagsForIntrinsics_mat = Mat(Size(1, NUM_CAMERAS), CV_32SC1, cv::Scalar(0));
|
||||
}
|
||||
|
||||
CV_Assert(flagsForIntrinsics_mat.total() == size_t(NUM_CAMERAS));
|
||||
CV_CheckEQ(flagsForIntrinsics_mat.type(), CV_32S, "flagsForIntrinsics should be of type 32SC1");
|
||||
CV_CheckEQ(flagsForIntrinsics_mat.channels(), 1, "flagsForIntrinsics should be of type 32SC1");
|
||||
|
||||
std::vector<Mat> objPoints_norm;
|
||||
objPoints_norm.reserve(NUM_FRAMES);
|
||||
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||
if (obj_points_in_rows)
|
||||
objPoints_norm.emplace_back(objPoints.getMat(i)*(1/scale_3d_pts));
|
||||
else
|
||||
objPoints_norm.emplace_back(objPoints.getMat(i).t()*(1/scale_3d_pts));
|
||||
objPoints_norm[i] = objPoints_norm[i].reshape(1);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
std::vector<int> num_visible_frames_per_camera(NUM_CAMERAS);
|
||||
std::vector<bool> valid_frames(NUM_FRAMES, false);
|
||||
// process input and count all visible frames and points
|
||||
|
||||
std::vector<std::vector<bool>> detection_mask_mat(NUM_CAMERAS, std::vector<bool>(NUM_FRAMES));
|
||||
const auto * const detection_mask_ptr = detection_mask_.data;
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
int num_visible_frames = 0;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
detection_mask_mat[c][f] = detection_mask_ptr[c*NUM_FRAMES + f] != 0;
|
||||
if (detection_mask_mat[c][f]) {
|
||||
num_visible_frames++;
|
||||
valid_frames[f] = true; // if frame is visible by at least one camera then count is as a valid one
|
||||
}
|
||||
}
|
||||
if (num_visible_frames == 0) {
|
||||
CV_Error(Error::StsBadArg, "camera "+std::to_string(c)+" has no visible frames!");
|
||||
}
|
||||
num_visible_frames_per_camera[c] = num_visible_frames;
|
||||
}
|
||||
|
||||
multiview::checkConnected(detection_mask_mat);
|
||||
|
||||
std::vector<std::vector<float>> points_ratio_area(NUM_CAMERAS, std::vector<float>(NUM_FRAMES));
|
||||
multiview::imagePointsArea(imageSize, detection_mask_mat, imagePoints, points_ratio_area);
|
||||
|
||||
// constant threshold for angle between two camera axes in radians (=160*M_PI/180).
|
||||
// if angle exceeds this threshold then a weight of a camera pair is lowered.
|
||||
const double THR_PATTERN_CAMERA_ANGLES = 160*M_PI/180;
|
||||
std::vector<std::vector<Vec3d>> rvecs_all(NUM_CAMERAS, std::vector<Vec3d>(NUM_FRAMES)),
|
||||
tvecs_all(NUM_CAMERAS, std::vector<Vec3d>(NUM_FRAMES)),
|
||||
opt_axes(NUM_CAMERAS, std::vector<Vec3d>(NUM_FRAMES));
|
||||
|
||||
std::vector<int> camera_rt_best(NUM_FRAMES, -1);
|
||||
std::vector<double> camera_rt_errors(NUM_FRAMES, std::numeric_limits<double>::max());
|
||||
const double WARNING_RMSE = 15.;
|
||||
if ((flags & cv::CALIB_USE_INTRINSIC_GUESS) == 0) {
|
||||
Ks.create(NUM_CAMERAS, 1, CV_64F);
|
||||
distortions.create(NUM_CAMERAS, 1, CV_64F);
|
||||
|
||||
// calibrate each camera independently to find intrinsic parameters - K and distortion coefficients
|
||||
parallel_for_(Range(0, NUM_CAMERAS), [&](const Range& range) {
|
||||
for (int camera = range.start; camera < range.end; camera++) {
|
||||
Mat K, dist;
|
||||
Mat rvecs, tvecs;
|
||||
std::vector<Mat> obj_points_, img_points_;
|
||||
std::vector<double> errors_per_view;
|
||||
obj_points_.reserve(num_visible_frames_per_camera[camera]);
|
||||
img_points_.reserve(num_visible_frames_per_camera[camera]);
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[camera][f]) {
|
||||
obj_points_.emplace_back((models_mat.at<uchar>(camera) == cv::CALIB_MODEL_FISHEYE && objPoints_norm[f].channels() != 3) ?
|
||||
objPoints_norm[f].reshape(3): objPoints_norm[f]);
|
||||
img_points_.emplace_back((models_mat.at<uchar>(camera) == cv::CALIB_MODEL_FISHEYE && imagePoints[camera][f].channels() != 2) ?
|
||||
imagePoints[camera][f].reshape(2) : imagePoints[camera][f]);
|
||||
}
|
||||
}
|
||||
double repr_err;
|
||||
if (models_mat.at<uchar>(camera) == cv::CALIB_MODEL_FISHEYE) {
|
||||
repr_err = fisheye::calibrate(obj_points_, img_points_, imageSize[camera],
|
||||
K, dist, rvecs, tvecs, flagsForIntrinsics_mat.at<int>(camera));
|
||||
CV_LOG_INFO(NULL, "Camera " << camera << " intrinsics calibration RMS " << repr_err);
|
||||
// calibrate does not compute error per view, so compute it manually
|
||||
errors_per_view = std::vector<double>(obj_points_.size());
|
||||
for (int f = 0; f < (int) obj_points_.size(); f++) {
|
||||
double err2 = multiview::computeReprojectionMSE(obj_points_[f],
|
||||
img_points_[f], K, dist, rvecs.row(f), tvecs.row(f), noArray(), noArray(), cv::CALIB_MODEL_FISHEYE);
|
||||
errors_per_view[f] = sqrt(err2);
|
||||
}
|
||||
} else {
|
||||
repr_err = calibrateCamera(obj_points_, img_points_, imageSize[camera], K, dist,
|
||||
rvecs, tvecs, noArray(), noArray(), errors_per_view, flagsForIntrinsics_mat.at<int>(camera));
|
||||
CV_LOG_INFO(NULL, "Camera " << camera << " intrinsics calibration RMS " << repr_err);
|
||||
}
|
||||
CV_LOG_IF_WARNING(NULL, repr_err > WARNING_RMSE, "Warning! Mean RMSE of intrinsics calibration is higher than "+std::to_string(WARNING_RMSE)+" pixels!");
|
||||
int cnt_visible_frame = 0;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[camera][f]) {
|
||||
rvecs_all[camera][f] = Vec3d(Mat(3, 1, CV_64F, rvecs.row(cnt_visible_frame).data));
|
||||
tvecs_all[camera][f] = Vec3d(Mat(3, 1, CV_64F, tvecs.row(cnt_visible_frame).data));
|
||||
double err = errors_per_view[cnt_visible_frame];
|
||||
double err2 = err * err;
|
||||
if (camera_rt_errors[f] > err2) {
|
||||
camera_rt_errors[f] = err2;
|
||||
camera_rt_best[f] = camera;
|
||||
}
|
||||
cnt_visible_frame++;
|
||||
}
|
||||
}
|
||||
|
||||
Ks.create(K.rows, K.cols, CV_64F, camera);
|
||||
distortions.create(dist.rows, dist.cols, CV_64F, camera, true);
|
||||
K.copyTo(Ks.getMat(camera));
|
||||
dist.copyTo(distortions.getMat(camera));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// use PnP to compute rvecs and tvecs
|
||||
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||
for (int k = 0; k < NUM_CAMERAS; k++) {
|
||||
if (!detection_mask_mat[k][i]) continue;
|
||||
Vec3d rvec, tvec;
|
||||
solvePnP(objPoints_norm[i], imagePoints[k][i], Ks.getMat(k), distortions.getMat(k), rvec, tvec, false, SOLVEPNP_ITERATIVE);
|
||||
rvecs_all[k][i] = rvec;
|
||||
tvecs_all[k][i] = tvec;
|
||||
const double err2 = multiview::computeReprojectionMSE(objPoints_norm[i], imagePoints[k][i], Ks.getMat(k), distortions.getMat(k), Mat(rvec), Mat(tvec), noArray(), noArray(), models_mat.at<uchar>(k));
|
||||
if (camera_rt_errors[i] > err2) {
|
||||
camera_rt_errors[i] = err2;
|
||||
camera_rt_best[i] = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<cv::Mat> Ks_vec, distortions_vec;
|
||||
Ks.getMatVector(Ks_vec);
|
||||
distortions.getMatVector(distortions_vec);
|
||||
|
||||
std::vector<std::vector<bool>> is_valid_angle2pattern;
|
||||
multiview::thresholdPatternCameraAngles(NUM_PATTERN_PTS, THR_PATTERN_CAMERA_ANGLES, objPoints_norm, rvecs_all, opt_axes, is_valid_angle2pattern);
|
||||
|
||||
std::vector<Matx33d> Rs_vec(NUM_CAMERAS);
|
||||
std::vector<Vec3d> Ts_vec(NUM_CAMERAS);
|
||||
Rs_vec[0] = Matx33d ::eye();
|
||||
Ts_vec[0] = Vec3d::zeros();
|
||||
|
||||
std::vector<int> parent;
|
||||
std::vector<std::vector<int>> overlaps;
|
||||
if (! multiview::maximumSpanningTree(NUM_CAMERAS, NUM_FRAMES, detection_mask_mat, parent, overlaps, opt_axes,
|
||||
is_valid_angle2pattern, points_ratio_area, .5, 1.0)) {
|
||||
// failed to find suitable pairs with constraints!
|
||||
CV_Error(Error::StsInternal, "Failed to build tree for stereo calibration.");
|
||||
}
|
||||
|
||||
std::vector<std::pair<int,int>> pairs;
|
||||
multiview::selectPairsBFS (pairs, NUM_CAMERAS, parent);
|
||||
|
||||
if ((int)pairs.size() != NUM_CAMERAS-1) {
|
||||
CV_Error(Error::StsInternal, "Failed to build tree for stereo calibration. Incorrect number of pairs.");
|
||||
}
|
||||
if (initializationPairs.needed()) {
|
||||
Mat pairs_mat = Mat_<int>(NUM_CAMERAS-1, 2);
|
||||
auto * pairs_ptr = (int *) pairs_mat.data;
|
||||
for (const auto &p : pairs) {
|
||||
(*pairs_ptr++) = p.first;
|
||||
(*pairs_ptr++) = p.second;
|
||||
}
|
||||
pairs_mat.copyTo(initializationPairs);
|
||||
}
|
||||
|
||||
if(flags & cv::CALIB_STEREO_REGISTRATION) {
|
||||
multiview::pairwiseStereoCalibration(pairs, models_mat, objPoints_norm, imagePoints,
|
||||
overlaps, detection_mask_mat, Ks_vec, distortions_vec, Rs_vec, Ts_vec, flagsForIntrinsics_mat, 0, criteria);
|
||||
} else {
|
||||
multiview::pairwiseRegistration(pairs, models_mat, objPoints_norm, imagePoints,
|
||||
overlaps, detection_mask_mat, Ks_vec, distortions_vec, Rs_vec, Ts_vec, flagsForIntrinsics_mat, 0, criteria);
|
||||
}
|
||||
|
||||
const int NUM_VALID_FRAMES = countNonZero(valid_frames);
|
||||
const int nparams = (NUM_VALID_FRAMES + NUM_CAMERAS - 1) * 6; // rvecs + tvecs (6)
|
||||
std::vector<double> param(nparams, 0.);
|
||||
|
||||
// use found relative extrinsics to initialize parameters
|
||||
for (int c = 1; c < NUM_CAMERAS; c++) {
|
||||
Vec3d rvec;
|
||||
Rodrigues(Rs_vec[c], rvec);
|
||||
memcpy(¶m[0]+(c-1)*6 , rvec.val, 3*sizeof(double));
|
||||
memcpy(¶m[0]+(c-1)*6+3, Ts_vec[c].val, 3*sizeof(double));
|
||||
}
|
||||
|
||||
// use found rvecs / tvecs or estimate them to initialize rest of parameters
|
||||
int cnt_valid_frame = 0;
|
||||
for (int i = 0; i < NUM_FRAMES; i++ ) {
|
||||
if (!valid_frames[i]) continue;
|
||||
Vec3d rvec_0, tvec_0;
|
||||
if (camera_rt_best[i] != 0) {
|
||||
// convert rvecs / tvecs from k-th camera to the first one
|
||||
|
||||
// formulas for relative rotation / translation
|
||||
// R = R_k R0^T => R_k = R R_0
|
||||
// t = t_k - R t_0 => t_k = t + R t_0
|
||||
|
||||
// initial camera R_0 = I, t_0 = 0 is fixed to R(rvec_0) and tvec_0
|
||||
// R_0 = R(rvec_0)
|
||||
// t_0 = tvec_0
|
||||
|
||||
// R'_k = R(rvec_k) = R_k R_0 => R_0 = R_k^T R(rvec_k)
|
||||
// t'_k = tvec_k = t_k + R_k t_0 => t_0 = R_k^T (tvec_k - t_k)
|
||||
const int rt_best_idx = camera_rt_best[i];
|
||||
Matx33d R_k;
|
||||
Rodrigues(rvecs_all[rt_best_idx][i], R_k);
|
||||
tvec_0 = Rs_vec[rt_best_idx].t() * (tvecs_all[rt_best_idx][i] - Ts_vec[rt_best_idx]);
|
||||
Rodrigues(Rs_vec[rt_best_idx].t() * R_k, rvec_0);
|
||||
} else {
|
||||
rvec_0 = rvecs_all[0][i];
|
||||
tvec_0 = tvecs_all[0][i];
|
||||
}
|
||||
|
||||
// save rvecs0 / tvecs0 parameters
|
||||
memcpy(¶m[0]+(cnt_valid_frame+NUM_CAMERAS-1)*6 , rvec_0.val, 3*sizeof(double));
|
||||
memcpy(¶m[0]+(cnt_valid_frame+NUM_CAMERAS-1)*6+3, tvec_0.val, 3*sizeof(double));
|
||||
cnt_valid_frame++;
|
||||
}
|
||||
|
||||
const float RBS_FNC_SCALE = 30;
|
||||
multiview::RobustExpFunction robust_fnc(RBS_FNC_SCALE);
|
||||
multiview::optimizeLM(param, robust_fnc, criteria, valid_frames, detection_mask_mat, objPoints_norm,
|
||||
imagePoints, Ks_vec, distortions_vec, models_mat, NUM_PATTERN_PTS);
|
||||
const auto * const params = ¶m[0];
|
||||
|
||||
// extract extrinsics (R_i, t_i) for i = 1 ... NUM_CAMERAS:
|
||||
const bool rt_mat_vec = Rs.isMatVector();
|
||||
Mat rs, ts;
|
||||
if (rt_mat_vec) {
|
||||
Rs.create(NUM_CAMERAS, 1, CV_64F);
|
||||
Ts.create(NUM_CAMERAS, 1, CV_64F);
|
||||
} else {
|
||||
rs = Mat_<double>(NUM_CAMERAS, 3);
|
||||
ts = Mat_<double>(NUM_CAMERAS, 3);
|
||||
}
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
Mat r_store, t_store;
|
||||
if (rt_mat_vec) {
|
||||
Rs.create(3, 1, CV_64F, c, true);
|
||||
Ts.create(3, 1, CV_64F, c, true);
|
||||
r_store = Rs.getMat(c);
|
||||
t_store = Ts.getMat(c);
|
||||
} else {
|
||||
r_store = rs.row(c);
|
||||
t_store = ts.row(c);
|
||||
}
|
||||
if (c == 0) {
|
||||
memcpy(r_store.ptr(), Vec3d(0,0,0).val, 3*sizeof(double));
|
||||
memcpy(t_store.ptr(), Vec3d(0,0,0).val, 3*sizeof(double));
|
||||
} else {
|
||||
memcpy(r_store.ptr(), params + (c-1)*6, 3*sizeof(double));
|
||||
memcpy(t_store.ptr(), params + (c-1)*6+3, 3*sizeof(double)); // and de-normalize translation
|
||||
t_store *= scale_3d_pts;
|
||||
}
|
||||
Mat R;
|
||||
Rodrigues(r_store, R);
|
||||
}
|
||||
if (! rt_mat_vec) {
|
||||
rs.copyTo(Rs);
|
||||
ts.copyTo(Ts);
|
||||
}
|
||||
Mat rvecs0_, tvecs0_;
|
||||
bool is_mat_vec = rvecs0.needed() && rvecs0.isMatVector();
|
||||
if (is_mat_vec) {
|
||||
rvecs0.create(NUM_FRAMES, 1, CV_64F);
|
||||
} else {
|
||||
rvecs0_ = Mat_<double>(NUM_FRAMES, 3);
|
||||
}
|
||||
cnt_valid_frame = 0;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (!valid_frames[f]) continue;
|
||||
if (is_mat_vec)
|
||||
rvecs0.create(3, 1, CV_64F, f, true);
|
||||
Mat store = is_mat_vec ? rvecs0.getMat(f) : rvecs0_.row(f);
|
||||
memcpy(store.ptr(), params + (cnt_valid_frame + NUM_CAMERAS - 1)*6, 3*sizeof(double));
|
||||
cnt_valid_frame += 1;
|
||||
}
|
||||
if (!is_mat_vec && rvecs0.needed())
|
||||
rvecs0_.copyTo(rvecs0);
|
||||
|
||||
is_mat_vec = tvecs0.needed() && tvecs0.isMatVector();
|
||||
if (is_mat_vec) {
|
||||
tvecs0.create(NUM_FRAMES, 1, CV_64F);
|
||||
} else {
|
||||
tvecs0_ = Mat_<double>(NUM_FRAMES, 3);
|
||||
}
|
||||
cnt_valid_frame = 0;
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (!valid_frames[f]) continue;
|
||||
if (is_mat_vec)
|
||||
tvecs0.create(3, 1, CV_64F, f, true);
|
||||
Mat store = is_mat_vec ? tvecs0.getMat(f) : tvecs0_.row(f);
|
||||
memcpy(store.ptr(), params + (cnt_valid_frame + NUM_CAMERAS - 1)*6+3, 3*sizeof(double));
|
||||
store *= scale_3d_pts;
|
||||
cnt_valid_frame += 1;
|
||||
}
|
||||
if (!is_mat_vec && tvecs0.needed())
|
||||
tvecs0_.copyTo(tvecs0);
|
||||
double sum_errors = 0, cnt_errors = 0;
|
||||
|
||||
const bool rvecs_mat_vec = rvecs0.needed() && rvecs0.isMatVector(), tvecs_mat_vec = tvecs0.needed() && tvecs0.isMatVector();
|
||||
const bool r_mat_vec = Rs.isMatVector(), t_mat_vec = Ts.isMatVector();
|
||||
Mat errs = Mat_<double>(NUM_CAMERAS, NUM_FRAMES);
|
||||
auto * errs_ptr = (double *) errs.data;
|
||||
for (int c = 0; c < NUM_CAMERAS; c++) {
|
||||
const Mat rvec = r_mat_vec ? Rs.getMat(c) : Rs.getMat().row(c).t();
|
||||
const Mat tvec = t_mat_vec ? Ts.getMat(c) : Ts.getMat().row(c).t();
|
||||
for (int f = 0; f < NUM_FRAMES; f++) {
|
||||
if (detection_mask_mat[c][f]) {
|
||||
const Mat rvec0 = rvecs_mat_vec ? rvecs0.getMat(f) : rvecs0_.row(f).t();
|
||||
const Mat tvec0 = tvecs_mat_vec ? tvecs0.getMat(f) : tvecs0_.row(f).t();
|
||||
const double err2 = multiview::computeReprojectionMSE(objPoints.getMat(f), imagePoints[c][f], Ks_vec[c],
|
||||
distortions_vec[c], rvec0, tvec0, rvec, tvec, models_mat.at<uchar>(c));
|
||||
(*errs_ptr++) = sqrt(err2);
|
||||
sum_errors += err2;
|
||||
cnt_errors += 1;
|
||||
} else (*errs_ptr++) = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (perFrameErrors.needed()) {
|
||||
errs.copyTo(perFrameErrors);
|
||||
}
|
||||
|
||||
return sqrt(sum_errors / cnt_errors);
|
||||
}
|
||||
|
||||
double calibrateMultiview(
|
||||
InputArrayOfArrays objPoints, const std::vector<std::vector<Mat>> &imagePoints,
|
||||
const std::vector<cv::Size>& imageSize, InputArray detectionMask, InputArray models,
|
||||
InputOutputArrayOfArrays Ks, InputOutputArrayOfArrays distortions,
|
||||
InputOutputArrayOfArrays Rs, InputOutputArrayOfArrays Ts,
|
||||
InputArray flagsForIntrinsics, int flags, TermCriteria criteria) {
|
||||
|
||||
return calibrateMultiview(objPoints, imagePoints, imageSize, detectionMask, models, Ks, distortions,
|
||||
Rs, Ts, noArray(), noArray(), noArray(), noArray(), flagsForIntrinsics, flags, criteria);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
#ifndef __OPENCV_PRECOMP_H__
|
||||
#define __OPENCV_PRECOMP_H__
|
||||
|
||||
#include "opencv2/core/utility.hpp"
|
||||
|
||||
#include "opencv2/core/private.hpp"
|
||||
|
||||
#include "opencv2/calib.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/features.hpp"
|
||||
|
||||
#include "opencv2/core/ocl.hpp"
|
||||
|
||||
#define GET_OPTIMIZED(func) (func)
|
||||
|
||||
|
||||
namespace cv {
|
||||
|
||||
/**
|
||||
* Compute the number of iterations given the confidence, outlier ratio, number
|
||||
* of model points and the maximum iteration number.
|
||||
*
|
||||
* @param p confidence value
|
||||
* @param ep outlier ratio
|
||||
* @param modelPoints number of model points required for estimation
|
||||
* @param maxIters maximum number of iterations
|
||||
* @return The number of iterations according to the formula
|
||||
* \f[
|
||||
* \frac{\ln(1-p)}{\ln\left(1-(1-ep)^\mathrm{modelPoints}\right)}
|
||||
* \f]
|
||||
*
|
||||
* If the computed number of iterations is larger than maxIters, then maxIters is returned.
|
||||
*/
|
||||
int RANSACUpdateNumIters( double p, double ep, int modelPoints, int maxIters );
|
||||
|
||||
class CV_EXPORTS PointSetRegistrator : public Algorithm
|
||||
{
|
||||
public:
|
||||
class CV_EXPORTS Callback
|
||||
{
|
||||
public:
|
||||
virtual ~Callback() {}
|
||||
virtual int runKernel(InputArray m1, InputArray m2, OutputArray model) const = 0;
|
||||
virtual void computeError(InputArray m1, InputArray m2, InputArray model, OutputArray err) const = 0;
|
||||
virtual bool checkSubset(InputArray, InputArray, int) const { return true; }
|
||||
};
|
||||
|
||||
virtual void setCallback(const Ptr<PointSetRegistrator::Callback>& cb) = 0;
|
||||
virtual bool run(InputArray m1, InputArray m2, OutputArray model, OutputArray mask) const = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS Ptr<PointSetRegistrator> createRANSACPointSetRegistrator(const Ptr<PointSetRegistrator::Callback>& cb,
|
||||
int modelPoints, double threshold,
|
||||
double confidence=0.99, int maxIters=1000 );
|
||||
|
||||
CV_EXPORTS Ptr<PointSetRegistrator> createLMeDSPointSetRegistrator(const Ptr<PointSetRegistrator::Callback>& cb,
|
||||
int modelPoints, double confidence=0.99, int maxIters=1000 );
|
||||
|
||||
template<typename T> inline int compressElems( T* ptr, const uchar* mask, int mstep, int count )
|
||||
{
|
||||
int i, j;
|
||||
for( i = j = 0; i < count; i++ )
|
||||
if( mask[i*mstep] )
|
||||
{
|
||||
if( i > j )
|
||||
ptr[j] = ptr[i];
|
||||
j++;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
static inline bool haveCollinearPoints( const Mat& m, int count )
|
||||
{
|
||||
int j, k, i = count-1;
|
||||
const Point2f* ptr = m.ptr<Point2f>();
|
||||
|
||||
// check that the i-th selected point does not belong
|
||||
// to a line connecting some previously selected points
|
||||
// also checks that points are not too close to each other
|
||||
for( j = 0; j < i; j++ )
|
||||
{
|
||||
double dx1 = ptr[j].x - ptr[i].x;
|
||||
double dy1 = ptr[j].y - ptr[i].y;
|
||||
for( k = 0; k < j; k++ )
|
||||
{
|
||||
double dx2 = ptr[k].x - ptr[i].x;
|
||||
double dy2 = ptr[k].y - ptr[i].y;
|
||||
if( fabs(dx2*dy1 - dy2*dx1) <= FLT_EPSILON*(fabs(dx1) + fabs(dy1) + fabs(dx2) + fabs(dy2)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,864 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static void generatePose(RNG& rng, double min_theta, double max_theta,
|
||||
double min_tx, double max_tx,
|
||||
double min_ty, double max_ty,
|
||||
double min_tz, double max_tz,
|
||||
Mat& R, Mat& tvec,
|
||||
bool random_sign)
|
||||
{
|
||||
Mat axis(3, 1, CV_64FC1);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
axis.at<double>(i,0) = rng.uniform(-1.0, 1.0);
|
||||
}
|
||||
double theta = rng.uniform(min_theta, max_theta);
|
||||
if (random_sign)
|
||||
{
|
||||
theta *= std::copysign(1.0, rng.uniform(-1.0, 1.0));
|
||||
}
|
||||
|
||||
Mat rvec(3, 1, CV_64FC1);
|
||||
rvec.at<double>(0,0) = theta*axis.at<double>(0,0);
|
||||
rvec.at<double>(1,0) = theta*axis.at<double>(1,0);
|
||||
rvec.at<double>(2,0) = theta*axis.at<double>(2,0);
|
||||
|
||||
tvec.create(3, 1, CV_64FC1);
|
||||
tvec.at<double>(0,0) = rng.uniform(min_tx, max_tx);
|
||||
tvec.at<double>(1,0) = rng.uniform(min_ty, max_ty);
|
||||
tvec.at<double>(2,0) = rng.uniform(min_tz, max_tz);
|
||||
|
||||
if (random_sign)
|
||||
{
|
||||
tvec.at<double>(0,0) *= std::copysign(1.0, rng.uniform(-1.0, 1.0));
|
||||
tvec.at<double>(1,0) *= std::copysign(1.0, rng.uniform(-1.0, 1.0));
|
||||
tvec.at<double>(2,0) *= std::copysign(1.0, rng.uniform(-1.0, 1.0));
|
||||
}
|
||||
|
||||
cv::Rodrigues(rvec, R);
|
||||
}
|
||||
|
||||
static Mat homogeneousInverse(const Mat& T)
|
||||
{
|
||||
CV_Assert( T.rows == 4 && T.cols == 4 );
|
||||
|
||||
Mat R = T(Rect(0, 0, 3, 3));
|
||||
Mat t = T(Rect(3, 0, 1, 3));
|
||||
Mat Rt = R.t();
|
||||
Mat tinv = -Rt * t;
|
||||
Mat Tinv = Mat::eye(4, 4, T.type());
|
||||
Rt.copyTo(Tinv(Rect(0, 0, 3, 3)));
|
||||
tinv.copyTo(Tinv(Rect(3, 0, 1, 3)));
|
||||
|
||||
return Tinv;
|
||||
}
|
||||
|
||||
static void simulateDataEyeInHand(RNG& rng, int nPoses,
|
||||
std::vector<Mat> &R_gripper2base, std::vector<Mat> &t_gripper2base,
|
||||
std::vector<Mat> &R_target2cam, std::vector<Mat> &t_target2cam,
|
||||
bool noise, Mat& R_cam2gripper, Mat& t_cam2gripper)
|
||||
{
|
||||
//to avoid generating values close to zero,
|
||||
//we use positive range values and randomize the sign
|
||||
const bool random_sign = true;
|
||||
generatePose(rng, 10.0*CV_PI/180.0, 50.0*CV_PI/180.0,
|
||||
0.05, 0.5, 0.05, 0.5, 0.05, 0.5,
|
||||
R_cam2gripper, t_cam2gripper, random_sign);
|
||||
|
||||
Mat R_target2base, t_target2base;
|
||||
generatePose(rng, 5.0*CV_PI/180.0, 85.0*CV_PI/180.0,
|
||||
0.5, 3.5, 0.5, 3.5, 0.5, 3.5,
|
||||
R_target2base, t_target2base, random_sign);
|
||||
|
||||
for (int i = 0; i < nPoses; i++)
|
||||
{
|
||||
Mat R_gripper2base_, t_gripper2base_;
|
||||
generatePose(rng, 5.0*CV_PI/180.0, 45.0*CV_PI/180.0,
|
||||
0.5, 1.5, 0.5, 1.5, 0.5, 1.5,
|
||||
R_gripper2base_, t_gripper2base_, random_sign);
|
||||
|
||||
R_gripper2base.push_back(R_gripper2base_);
|
||||
t_gripper2base.push_back(t_gripper2base_);
|
||||
|
||||
Mat T_cam2gripper = Mat::eye(4, 4, CV_64FC1);
|
||||
R_cam2gripper.copyTo(T_cam2gripper(Rect(0, 0, 3, 3)));
|
||||
t_cam2gripper.copyTo(T_cam2gripper(Rect(3, 0, 1, 3)));
|
||||
|
||||
Mat T_gripper2base = Mat::eye(4, 4, CV_64FC1);
|
||||
R_gripper2base_.copyTo(T_gripper2base(Rect(0, 0, 3, 3)));
|
||||
t_gripper2base_.copyTo(T_gripper2base(Rect(3, 0, 1, 3)));
|
||||
|
||||
Mat T_base2cam = homogeneousInverse(T_cam2gripper) * homogeneousInverse(T_gripper2base);
|
||||
Mat T_target2base = Mat::eye(4, 4, CV_64FC1);
|
||||
R_target2base.copyTo(T_target2base(Rect(0, 0, 3, 3)));
|
||||
t_target2base.copyTo(T_target2base(Rect(3, 0, 1, 3)));
|
||||
Mat T_target2cam = T_base2cam * T_target2base;
|
||||
|
||||
if (noise)
|
||||
{
|
||||
//Add some noise for the transformation between the target and the camera
|
||||
Mat R_target2cam_noise = T_target2cam(Rect(0, 0, 3, 3));
|
||||
Mat rvec_target2cam_noise;
|
||||
cv::Rodrigues(R_target2cam_noise, rvec_target2cam_noise);
|
||||
rvec_target2cam_noise.at<double>(0,0) += rng.gaussian(0.002);
|
||||
rvec_target2cam_noise.at<double>(1,0) += rng.gaussian(0.002);
|
||||
rvec_target2cam_noise.at<double>(2,0) += rng.gaussian(0.002);
|
||||
|
||||
cv::Rodrigues(rvec_target2cam_noise, R_target2cam_noise);
|
||||
|
||||
Mat t_target2cam_noise = T_target2cam(Rect(3, 0, 1, 3));
|
||||
t_target2cam_noise.at<double>(0,0) += rng.gaussian(0.005);
|
||||
t_target2cam_noise.at<double>(1,0) += rng.gaussian(0.005);
|
||||
t_target2cam_noise.at<double>(2,0) += rng.gaussian(0.005);
|
||||
|
||||
//Add some noise for the transformation between the gripper and the robot base
|
||||
Mat R_gripper2base_noise = T_gripper2base(Rect(0, 0, 3, 3));
|
||||
Mat rvec_gripper2base_noise;
|
||||
cv::Rodrigues(R_gripper2base_noise, rvec_gripper2base_noise);
|
||||
rvec_gripper2base_noise.at<double>(0,0) += rng.gaussian(0.001);
|
||||
rvec_gripper2base_noise.at<double>(1,0) += rng.gaussian(0.001);
|
||||
rvec_gripper2base_noise.at<double>(2,0) += rng.gaussian(0.001);
|
||||
|
||||
cv::Rodrigues(rvec_gripper2base_noise, R_gripper2base_noise);
|
||||
|
||||
Mat t_gripper2base_noise = T_gripper2base(Rect(3, 0, 1, 3));
|
||||
t_gripper2base_noise.at<double>(0,0) += rng.gaussian(0.001);
|
||||
t_gripper2base_noise.at<double>(1,0) += rng.gaussian(0.001);
|
||||
t_gripper2base_noise.at<double>(2,0) += rng.gaussian(0.001);
|
||||
}
|
||||
|
||||
//Test rvec representation
|
||||
Mat rvec_target2cam;
|
||||
cv::Rodrigues(T_target2cam(Rect(0, 0, 3, 3)), rvec_target2cam);
|
||||
R_target2cam.push_back(rvec_target2cam);
|
||||
t_target2cam.push_back(T_target2cam(Rect(3, 0, 1, 3)));
|
||||
}
|
||||
}
|
||||
|
||||
static void simulateDataEyeToHand(RNG& rng, int nPoses,
|
||||
std::vector<Mat> &R_base2gripper, std::vector<Mat> &t_base2gripper,
|
||||
std::vector<Mat> &R_target2cam, std::vector<Mat> &t_target2cam,
|
||||
bool noise, Mat& R_cam2base, Mat& t_cam2base)
|
||||
{
|
||||
//to avoid generating values close to zero,
|
||||
//we use positive range values and randomize the sign
|
||||
const bool random_sign = true;
|
||||
generatePose(rng, 10.0*CV_PI/180.0, 50.0*CV_PI/180.0,
|
||||
0.5, 3.5, 0.5, 3.5, 0.5, 3.5,
|
||||
R_cam2base, t_cam2base, random_sign);
|
||||
|
||||
Mat R_target2gripper, t_target2gripper;
|
||||
generatePose(rng, 5.0*CV_PI/180.0, 85.0*CV_PI/180.0,
|
||||
0.05, 0.5, 0.05, 0.5, 0.05, 0.5,
|
||||
R_target2gripper, t_target2gripper, random_sign);
|
||||
|
||||
Mat T_target2gripper = Mat::eye(4, 4, CV_64FC1);
|
||||
R_target2gripper.copyTo(T_target2gripper(Rect(0, 0, 3, 3)));
|
||||
t_target2gripper.copyTo(T_target2gripper(Rect(3, 0, 1, 3)));
|
||||
|
||||
for (int i = 0; i < nPoses; i++)
|
||||
{
|
||||
Mat R_gripper2base_, t_gripper2base_;
|
||||
generatePose(rng, 5.0*CV_PI/180.0, 45.0*CV_PI/180.0,
|
||||
0.5, 1.5, 0.5, 1.5, 0.5, 1.5,
|
||||
R_gripper2base_, t_gripper2base_, random_sign);
|
||||
|
||||
Mat R_base2gripper_ = R_gripper2base_.t();
|
||||
Mat t_base2gripper_ = -R_base2gripper_ * t_gripper2base_;
|
||||
|
||||
Mat T_gripper2base = Mat::eye(4, 4, CV_64FC1);
|
||||
R_gripper2base_.copyTo(T_gripper2base(Rect(0, 0, 3, 3)));
|
||||
t_gripper2base_.copyTo(T_gripper2base(Rect(3, 0, 1, 3)));
|
||||
|
||||
Mat T_cam2base = Mat::eye(4, 4, CV_64FC1);
|
||||
R_cam2base.copyTo(T_cam2base(Rect(0, 0, 3, 3)));
|
||||
t_cam2base.copyTo(T_cam2base(Rect(3, 0, 1, 3)));
|
||||
|
||||
Mat T_target2cam = homogeneousInverse(T_cam2base) * T_gripper2base * T_target2gripper;
|
||||
|
||||
if (noise)
|
||||
{
|
||||
//Add some noise for the transformation between the target and the camera
|
||||
Mat R_target2cam_noise = T_target2cam(Rect(0, 0, 3, 3));
|
||||
Mat rvec_target2cam_noise;
|
||||
cv::Rodrigues(R_target2cam_noise, rvec_target2cam_noise);
|
||||
rvec_target2cam_noise.at<double>(0,0) += rng.gaussian(0.002);
|
||||
rvec_target2cam_noise.at<double>(1,0) += rng.gaussian(0.002);
|
||||
rvec_target2cam_noise.at<double>(2,0) += rng.gaussian(0.002);
|
||||
|
||||
cv::Rodrigues(rvec_target2cam_noise, R_target2cam_noise);
|
||||
|
||||
Mat t_target2cam_noise = T_target2cam(Rect(3, 0, 1, 3));
|
||||
t_target2cam_noise.at<double>(0,0) += rng.gaussian(0.005);
|
||||
t_target2cam_noise.at<double>(1,0) += rng.gaussian(0.005);
|
||||
t_target2cam_noise.at<double>(2,0) += rng.gaussian(0.005);
|
||||
|
||||
//Add some noise for the transformation between the robot base and the gripper
|
||||
Mat rvec_base2gripper_noise;
|
||||
cv::Rodrigues(R_base2gripper_, rvec_base2gripper_noise);
|
||||
rvec_base2gripper_noise.at<double>(0,0) += rng.gaussian(0.001);
|
||||
rvec_base2gripper_noise.at<double>(1,0) += rng.gaussian(0.001);
|
||||
rvec_base2gripper_noise.at<double>(2,0) += rng.gaussian(0.001);
|
||||
|
||||
cv::Rodrigues(rvec_base2gripper_noise, R_base2gripper_);
|
||||
|
||||
t_base2gripper_.at<double>(0,0) += rng.gaussian(0.001);
|
||||
t_base2gripper_.at<double>(1,0) += rng.gaussian(0.001);
|
||||
t_base2gripper_.at<double>(2,0) += rng.gaussian(0.001);
|
||||
}
|
||||
|
||||
R_base2gripper.push_back(R_base2gripper_);
|
||||
t_base2gripper.push_back(t_base2gripper_);
|
||||
|
||||
//Test rvec representation
|
||||
Mat rvec_target2cam;
|
||||
cv::Rodrigues(T_target2cam(Rect(0, 0, 3, 3)), rvec_target2cam);
|
||||
R_target2cam.push_back(rvec_target2cam);
|
||||
t_target2cam.push_back(T_target2cam(Rect(3, 0, 1, 3)));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getMethodName(HandEyeCalibrationMethod method)
|
||||
{
|
||||
std::string method_name = "";
|
||||
switch (method)
|
||||
{
|
||||
case CALIB_HAND_EYE_TSAI:
|
||||
method_name = "Tsai";
|
||||
break;
|
||||
|
||||
case CALIB_HAND_EYE_PARK:
|
||||
method_name = "Park";
|
||||
break;
|
||||
|
||||
case CALIB_HAND_EYE_HORAUD:
|
||||
method_name = "Horaud";
|
||||
break;
|
||||
|
||||
case CALIB_HAND_EYE_ANDREFF:
|
||||
method_name = "Andreff";
|
||||
break;
|
||||
|
||||
case CALIB_HAND_EYE_DANIILIDIS:
|
||||
method_name = "Daniilidis";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return method_name;
|
||||
}
|
||||
|
||||
static std::string getMethodName(RobotWorldHandEyeCalibrationMethod method)
|
||||
{
|
||||
std::string method_name = "";
|
||||
switch (method)
|
||||
{
|
||||
case CALIB_ROBOT_WORLD_HAND_EYE_SHAH:
|
||||
method_name = "Shah";
|
||||
break;
|
||||
|
||||
case CALIB_ROBOT_WORLD_HAND_EYE_LI:
|
||||
method_name = "Li";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return method_name;
|
||||
}
|
||||
|
||||
static void printStats(const std::string& methodName, const std::vector<double>& rvec_diff, const std::vector<double>& tvec_diff)
|
||||
{
|
||||
double max_rvec_diff = *std::max_element(rvec_diff.begin(), rvec_diff.end());
|
||||
double mean_rvec_diff = std::accumulate(rvec_diff.begin(),
|
||||
rvec_diff.end(), 0.0) / rvec_diff.size();
|
||||
double sq_sum_rvec_diff = std::inner_product(rvec_diff.begin(), rvec_diff.end(),
|
||||
rvec_diff.begin(), 0.0);
|
||||
double std_rvec_diff = std::sqrt(sq_sum_rvec_diff / rvec_diff.size() - mean_rvec_diff * mean_rvec_diff);
|
||||
|
||||
double max_tvec_diff = *std::max_element(tvec_diff.begin(), tvec_diff.end());
|
||||
double mean_tvec_diff = std::accumulate(tvec_diff.begin(),
|
||||
tvec_diff.end(), 0.0) / tvec_diff.size();
|
||||
double sq_sum_tvec_diff = std::inner_product(tvec_diff.begin(), tvec_diff.end(),
|
||||
tvec_diff.begin(), 0.0);
|
||||
double std_tvec_diff = std::sqrt(sq_sum_tvec_diff / tvec_diff.size() - mean_tvec_diff * mean_tvec_diff);
|
||||
|
||||
std::cout << "Method " << methodName << ":\n"
|
||||
<< "Max rvec error: " << max_rvec_diff << ", Mean rvec error: " << mean_rvec_diff
|
||||
<< ", Std rvec error: " << std_rvec_diff << "\n"
|
||||
<< "Max tvec error: " << max_tvec_diff << ", Mean tvec error: " << mean_tvec_diff
|
||||
<< ", Std tvec error: " << std_tvec_diff << std::endl;
|
||||
}
|
||||
|
||||
static void loadDataset(std::vector<Mat>& R_target2cam, std::vector<Mat>& t_target2cam,
|
||||
std::vector<Mat>& R_base2gripper, std::vector<Mat>& t_base2gripper)
|
||||
{
|
||||
const std::string camera_poses_filename = findDataFile("cv/robot_world_hand_eye_calibration/cali.txt");
|
||||
const std::string end_effector_poses = findDataFile("cv/robot_world_hand_eye_calibration/robot_cali.txt");
|
||||
|
||||
// Parse camera poses, the pose of the chessboard in the camera frame
|
||||
{
|
||||
std::ifstream file(camera_poses_filename);
|
||||
ASSERT_TRUE(file.is_open());
|
||||
|
||||
int ndata = 0;
|
||||
file >> ndata;
|
||||
R_target2cam.reserve(ndata);
|
||||
t_target2cam.reserve(ndata);
|
||||
|
||||
std::string image_name;
|
||||
Matx33d cameraMatrix;
|
||||
Matx33d R;
|
||||
Matx31d t;
|
||||
Matx16d distCoeffs;
|
||||
Matx13d distCoeffs2;
|
||||
while (file >> image_name >>
|
||||
cameraMatrix(0,0) >> cameraMatrix(0,1) >> cameraMatrix(0,2) >>
|
||||
cameraMatrix(1,0) >> cameraMatrix(1,1) >> cameraMatrix(1,2) >>
|
||||
cameraMatrix(2,0) >> cameraMatrix(2,1) >> cameraMatrix(2,2) >>
|
||||
R(0,0) >> R(0,1) >> R(0,2) >>
|
||||
R(1,0) >> R(1,1) >> R(1,2) >>
|
||||
R(2,0) >> R(2,1) >> R(2,2) >>
|
||||
t(0) >> t(1) >> t(2) >>
|
||||
distCoeffs(0) >> distCoeffs(1) >> distCoeffs(2) >> distCoeffs(3) >> distCoeffs(4) >>
|
||||
distCoeffs2(0) >> distCoeffs2(1) >> distCoeffs2(2)) {
|
||||
R_target2cam.push_back(Mat(R));
|
||||
t_target2cam.push_back(Mat(t));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse robot poses, the pose of the robot base in the robot hand frame
|
||||
{
|
||||
std::ifstream file(end_effector_poses);
|
||||
ASSERT_TRUE(file.is_open());
|
||||
|
||||
int ndata = 0;
|
||||
file >> ndata;
|
||||
R_base2gripper.reserve(ndata);
|
||||
t_base2gripper.reserve(ndata);
|
||||
|
||||
Matx33d R;
|
||||
Matx31d t;
|
||||
Matx14d last_row;
|
||||
while (file >>
|
||||
R(0,0) >> R(0,1) >> R(0,2) >> t(0) >>
|
||||
R(1,0) >> R(1,1) >> R(1,2) >> t(1) >>
|
||||
R(2,0) >> R(2,1) >> R(2,2) >> t(2) >>
|
||||
last_row(0) >> last_row(1) >> last_row(2) >> last_row(3)) {
|
||||
R_base2gripper.push_back(Mat(R));
|
||||
t_base2gripper.push_back(Mat(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loadResults(Matx33d& wRb, Matx31d& wtb, Matx33d& cRg, Matx31d& ctg)
|
||||
{
|
||||
const std::string transformations_filename = findDataFile("cv/robot_world_hand_eye_calibration/rwhe_AA_RPI/transformations.txt");
|
||||
std::ifstream file(transformations_filename);
|
||||
ASSERT_TRUE(file.is_open());
|
||||
|
||||
std::string str;
|
||||
//Parse X
|
||||
file >> str;
|
||||
Matx44d wTb;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
file >> wTb(i,j);
|
||||
}
|
||||
}
|
||||
|
||||
//Parse Z
|
||||
file >> str;
|
||||
int cam_num = 0;
|
||||
//Parse camera number
|
||||
file >> cam_num;
|
||||
Matx44d cTg;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
file >> cTg(i,j);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
wRb(i,j) = wTb(i,j);
|
||||
cRg(i,j) = cTg(i,j);
|
||||
}
|
||||
wtb(i) = wTb(i,3);
|
||||
ctg(i) = cTg(i,3);
|
||||
}
|
||||
}
|
||||
|
||||
class CV_CalibrateHandEyeTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_CalibrateHandEyeTest(bool eyeToHand) : eyeToHandConfig(eyeToHand) {
|
||||
eps_rvec[CALIB_HAND_EYE_TSAI] = 1.0e-8;
|
||||
eps_rvec[CALIB_HAND_EYE_PARK] = 1.0e-8;
|
||||
eps_rvec[CALIB_HAND_EYE_HORAUD] = 1.0e-8;
|
||||
eps_rvec[CALIB_HAND_EYE_ANDREFF] = 1.0e-8;
|
||||
eps_rvec[CALIB_HAND_EYE_DANIILIDIS] = 1.0e-8;
|
||||
|
||||
eps_tvec[CALIB_HAND_EYE_TSAI] = 1.0e-8;
|
||||
eps_tvec[CALIB_HAND_EYE_PARK] = 1.0e-8;
|
||||
eps_tvec[CALIB_HAND_EYE_HORAUD] = 1.0e-8;
|
||||
eps_tvec[CALIB_HAND_EYE_ANDREFF] = 1.0e-8;
|
||||
eps_tvec[CALIB_HAND_EYE_DANIILIDIS] = 1.0e-8;
|
||||
|
||||
eps_rvec_noise[CALIB_HAND_EYE_TSAI] = 2.0e-2;
|
||||
eps_rvec_noise[CALIB_HAND_EYE_PARK] = 2.0e-2;
|
||||
eps_rvec_noise[CALIB_HAND_EYE_HORAUD] = 2.0e-2;
|
||||
eps_rvec_noise[CALIB_HAND_EYE_ANDREFF] = 1.0e-2;
|
||||
eps_rvec_noise[CALIB_HAND_EYE_DANIILIDIS] = 1.0e-2;
|
||||
|
||||
eps_tvec_noise[CALIB_HAND_EYE_TSAI] = 7.0e-2;
|
||||
eps_tvec_noise[CALIB_HAND_EYE_PARK] = 7.0e-2;
|
||||
eps_tvec_noise[CALIB_HAND_EYE_HORAUD] = 7.0e-2;
|
||||
if (eyeToHandConfig)
|
||||
{
|
||||
eps_tvec_noise[CALIB_HAND_EYE_ANDREFF] = 7.0e-2;
|
||||
}
|
||||
else
|
||||
{
|
||||
eps_tvec_noise[CALIB_HAND_EYE_ANDREFF] = 5.0e-2;
|
||||
}
|
||||
eps_tvec_noise[CALIB_HAND_EYE_DANIILIDIS] = 5.0e-2;
|
||||
}
|
||||
protected:
|
||||
virtual void run(int);
|
||||
|
||||
bool eyeToHandConfig;
|
||||
double eps_rvec[5];
|
||||
double eps_tvec[5];
|
||||
double eps_rvec_noise[5];
|
||||
double eps_tvec_noise[5];
|
||||
};
|
||||
|
||||
void CV_CalibrateHandEyeTest::run(int)
|
||||
{
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
|
||||
RNG& rng = cv::theRNG();
|
||||
|
||||
std::vector<std::vector<double> > vec_rvec_diff(5);
|
||||
std::vector<std::vector<double> > vec_tvec_diff(5);
|
||||
std::vector<std::vector<double> > vec_rvec_diff_noise(5);
|
||||
std::vector<std::vector<double> > vec_tvec_diff_noise(5);
|
||||
|
||||
std::vector<HandEyeCalibrationMethod> methods;
|
||||
methods.push_back(CALIB_HAND_EYE_TSAI);
|
||||
methods.push_back(CALIB_HAND_EYE_PARK);
|
||||
methods.push_back(CALIB_HAND_EYE_HORAUD);
|
||||
methods.push_back(CALIB_HAND_EYE_ANDREFF);
|
||||
methods.push_back(CALIB_HAND_EYE_DANIILIDIS);
|
||||
|
||||
const int nTests = 100;
|
||||
for (int i = 0; i < nTests; i++)
|
||||
{
|
||||
const int nPoses = 10;
|
||||
if (eyeToHandConfig)
|
||||
{
|
||||
{
|
||||
//No noise
|
||||
std::vector<Mat> R_base2gripper, t_base2gripper;
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
Mat R_cam2base_true, t_cam2base_true;
|
||||
|
||||
const bool noise = false;
|
||||
simulateDataEyeToHand(rng, nPoses, R_base2gripper, t_base2gripper, R_target2cam, t_target2cam, noise,
|
||||
R_cam2base_true, t_cam2base_true);
|
||||
|
||||
for (size_t idx = 0; idx < methods.size(); idx++)
|
||||
{
|
||||
Mat rvec_cam2base_true;
|
||||
cv::Rodrigues(R_cam2base_true, rvec_cam2base_true);
|
||||
|
||||
Mat R_cam2base_est, t_cam2base_est;
|
||||
calibrateHandEye(R_base2gripper, t_base2gripper, R_target2cam, t_target2cam, R_cam2base_est, t_cam2base_est, methods[idx]);
|
||||
|
||||
Mat rvec_cam2base_est;
|
||||
cv::Rodrigues(R_cam2base_est, rvec_cam2base_est);
|
||||
|
||||
double rvecDiff = cvtest::norm(rvec_cam2base_true, rvec_cam2base_est, NORM_L2);
|
||||
double tvecDiff = cvtest::norm(t_cam2base_true, t_cam2base_est, NORM_L2);
|
||||
|
||||
vec_rvec_diff[idx].push_back(rvecDiff);
|
||||
vec_tvec_diff[idx].push_back(tvecDiff);
|
||||
|
||||
const double epsilon_rvec = eps_rvec[idx];
|
||||
const double epsilon_tvec = eps_tvec[idx];
|
||||
|
||||
//Maybe a better accuracy test would be to compare the mean and std errors with some thresholds?
|
||||
if (rvecDiff > epsilon_rvec || tvecDiff > epsilon_tvec)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "Invalid accuracy (no noise) for method: %s, rvecDiff: %f, epsilon_rvec: %f, tvecDiff: %f, epsilon_tvec: %f\n",
|
||||
getMethodName(methods[idx]).c_str(), rvecDiff, epsilon_rvec, tvecDiff, epsilon_tvec);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//Gaussian noise on transformations between calibration target frame and camera frame and between robot base and gripper frames
|
||||
std::vector<Mat> R_base2gripper, t_base2gripper;
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
Mat R_cam2base_true, t_cam2base_true;
|
||||
|
||||
const bool noise = true;
|
||||
simulateDataEyeToHand(rng, nPoses, R_base2gripper, t_base2gripper, R_target2cam, t_target2cam, noise,
|
||||
R_cam2base_true, t_cam2base_true);
|
||||
|
||||
for (size_t idx = 0; idx < methods.size(); idx++)
|
||||
{
|
||||
Mat rvec_cam2base_true;
|
||||
cv::Rodrigues(R_cam2base_true, rvec_cam2base_true);
|
||||
|
||||
Mat R_cam2base_est, t_cam2base_est;
|
||||
calibrateHandEye(R_base2gripper, t_base2gripper, R_target2cam, t_target2cam, R_cam2base_est, t_cam2base_est, methods[idx]);
|
||||
|
||||
Mat rvec_cam2base_est;
|
||||
cv::Rodrigues(R_cam2base_est, rvec_cam2base_est);
|
||||
|
||||
double rvecDiff = cvtest::norm(rvec_cam2base_true, rvec_cam2base_est, NORM_L2);
|
||||
double tvecDiff = cvtest::norm(t_cam2base_true, t_cam2base_est, NORM_L2);
|
||||
|
||||
vec_rvec_diff_noise[idx].push_back(rvecDiff);
|
||||
vec_tvec_diff_noise[idx].push_back(tvecDiff);
|
||||
|
||||
const double epsilon_rvec = eps_rvec_noise[idx];
|
||||
const double epsilon_tvec = eps_tvec_noise[idx];
|
||||
|
||||
//Maybe a better accuracy test would be to compare the mean and std errors with some thresholds?
|
||||
if (rvecDiff > epsilon_rvec || tvecDiff > epsilon_tvec)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "Invalid accuracy (noise) for method: %s, rvecDiff: %f, epsilon_rvec: %f, tvecDiff: %f, epsilon_tvec: %f\n",
|
||||
getMethodName(methods[idx]).c_str(), rvecDiff, epsilon_rvec, tvecDiff, epsilon_tvec);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
//No noise
|
||||
std::vector<Mat> R_gripper2base, t_gripper2base;
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
Mat R_cam2gripper_true, t_cam2gripper_true;
|
||||
|
||||
const bool noise = false;
|
||||
simulateDataEyeInHand(rng, nPoses, R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, noise,
|
||||
R_cam2gripper_true, t_cam2gripper_true);
|
||||
|
||||
for (size_t idx = 0; idx < methods.size(); idx++)
|
||||
{
|
||||
Mat rvec_cam2gripper_true;
|
||||
cv::Rodrigues(R_cam2gripper_true, rvec_cam2gripper_true);
|
||||
|
||||
Mat R_cam2gripper_est, t_cam2gripper_est;
|
||||
calibrateHandEye(R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, R_cam2gripper_est, t_cam2gripper_est, methods[idx]);
|
||||
|
||||
Mat rvec_cam2gripper_est;
|
||||
cv::Rodrigues(R_cam2gripper_est, rvec_cam2gripper_est);
|
||||
|
||||
double rvecDiff = cvtest::norm(rvec_cam2gripper_true, rvec_cam2gripper_est, NORM_L2);
|
||||
double tvecDiff = cvtest::norm(t_cam2gripper_true, t_cam2gripper_est, NORM_L2);
|
||||
|
||||
vec_rvec_diff[idx].push_back(rvecDiff);
|
||||
vec_tvec_diff[idx].push_back(tvecDiff);
|
||||
|
||||
const double epsilon_rvec = eps_rvec[idx];
|
||||
const double epsilon_tvec = eps_tvec[idx];
|
||||
|
||||
//Maybe a better accuracy test would be to compare the mean and std errors with some thresholds?
|
||||
if (rvecDiff > epsilon_rvec || tvecDiff > epsilon_tvec)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "Invalid accuracy (no noise) for method: %s, rvecDiff: %f, epsilon_rvec: %f, tvecDiff: %f, epsilon_tvec: %f\n",
|
||||
getMethodName(methods[idx]).c_str(), rvecDiff, epsilon_rvec, tvecDiff, epsilon_tvec);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//Gaussian noise on transformations between calibration target frame and camera frame and between gripper and robot base frames
|
||||
std::vector<Mat> R_gripper2base, t_gripper2base;
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
Mat R_cam2gripper_true, t_cam2gripper_true;
|
||||
|
||||
const bool noise = true;
|
||||
simulateDataEyeInHand(rng, nPoses, R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, noise,
|
||||
R_cam2gripper_true, t_cam2gripper_true);
|
||||
|
||||
for (size_t idx = 0; idx < methods.size(); idx++)
|
||||
{
|
||||
Mat rvec_cam2gripper_true;
|
||||
cv::Rodrigues(R_cam2gripper_true, rvec_cam2gripper_true);
|
||||
|
||||
Mat R_cam2gripper_est, t_cam2gripper_est;
|
||||
calibrateHandEye(R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, R_cam2gripper_est, t_cam2gripper_est, methods[idx]);
|
||||
|
||||
Mat rvec_cam2gripper_est;
|
||||
cv::Rodrigues(R_cam2gripper_est, rvec_cam2gripper_est);
|
||||
|
||||
double rvecDiff = cvtest::norm(rvec_cam2gripper_true, rvec_cam2gripper_est, NORM_L2);
|
||||
double tvecDiff = cvtest::norm(t_cam2gripper_true, t_cam2gripper_est, NORM_L2);
|
||||
|
||||
vec_rvec_diff_noise[idx].push_back(rvecDiff);
|
||||
vec_tvec_diff_noise[idx].push_back(tvecDiff);
|
||||
|
||||
const double epsilon_rvec = eps_rvec_noise[idx];
|
||||
const double epsilon_tvec = eps_tvec_noise[idx];
|
||||
|
||||
//Maybe a better accuracy test would be to compare the mean and std errors with some thresholds?
|
||||
if (rvecDiff > epsilon_rvec || tvecDiff > epsilon_tvec)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "Invalid accuracy (noise) for method: %s, rvecDiff: %f, epsilon_rvec: %f, tvecDiff: %f, epsilon_tvec: %f\n",
|
||||
getMethodName(methods[idx]).c_str(), rvecDiff, epsilon_rvec, tvecDiff, epsilon_tvec);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < methods.size(); idx++)
|
||||
{
|
||||
std::cout << std::endl;
|
||||
printStats(getMethodName(methods[idx]), vec_rvec_diff[idx], vec_tvec_diff[idx]);
|
||||
printStats("(noise) " + getMethodName(methods[idx]), vec_rvec_diff_noise[idx], vec_tvec_diff_noise[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TEST(Calib3d_CalibrateHandEye, regression_eye_in_hand)
|
||||
{
|
||||
//Eye-in-Hand configuration (camera mounted on the robot end-effector observing a static calibration pattern)
|
||||
const bool eyeToHand = false;
|
||||
CV_CalibrateHandEyeTest test(eyeToHand);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Calib3d_CalibrateHandEye, regression_eye_to_hand)
|
||||
{
|
||||
//Eye-to-Hand configuration (static camera observing a calibration pattern mounted on the robot end-effector)
|
||||
const bool eyeToHand = true;
|
||||
CV_CalibrateHandEyeTest test(eyeToHand);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Calib3d_CalibrateHandEye, regression_17986)
|
||||
{
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
// Dataset contains transformation from base to gripper frame since it contains data for AX = ZB calibration problem
|
||||
std::vector<Mat> R_base2gripper, t_base2gripper;
|
||||
loadDataset(R_target2cam, t_target2cam, R_base2gripper, t_base2gripper);
|
||||
|
||||
std::vector<HandEyeCalibrationMethod> methods = {CALIB_HAND_EYE_TSAI,
|
||||
CALIB_HAND_EYE_PARK,
|
||||
CALIB_HAND_EYE_HORAUD,
|
||||
CALIB_HAND_EYE_ANDREFF,
|
||||
CALIB_HAND_EYE_DANIILIDIS};
|
||||
|
||||
for (auto method : methods) {
|
||||
SCOPED_TRACE(cv::format("method=%s", getMethodName(method).c_str()));
|
||||
|
||||
Matx33d R_cam2base_est;
|
||||
Matx31d t_cam2base_est;
|
||||
calibrateHandEye(R_base2gripper, t_base2gripper, R_target2cam, t_target2cam, R_cam2base_est, t_cam2base_est, method);
|
||||
|
||||
EXPECT_TRUE(checkRange(R_cam2base_est));
|
||||
EXPECT_TRUE(checkRange(t_cam2base_est));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Calib3d_CalibrateRobotWorldHandEye, regression)
|
||||
{
|
||||
std::vector<Mat> R_world2cam, t_worldt2cam;
|
||||
std::vector<Mat> R_base2gripper, t_base2gripper;
|
||||
loadDataset(R_world2cam, t_worldt2cam, R_base2gripper, t_base2gripper);
|
||||
|
||||
std::vector<Mat> rvec_R_world2cam;
|
||||
rvec_R_world2cam.reserve(R_world2cam.size());
|
||||
for (size_t i = 0; i < R_world2cam.size(); i++)
|
||||
{
|
||||
Mat rvec;
|
||||
cv::Rodrigues(R_world2cam[i], rvec);
|
||||
rvec_R_world2cam.push_back(rvec);
|
||||
}
|
||||
|
||||
std::vector<RobotWorldHandEyeCalibrationMethod> methods = {CALIB_ROBOT_WORLD_HAND_EYE_SHAH,
|
||||
CALIB_ROBOT_WORLD_HAND_EYE_LI};
|
||||
|
||||
Matx33d wRb, cRg;
|
||||
Matx31d wtb, ctg;
|
||||
loadResults(wRb, wtb, cRg, ctg);
|
||||
|
||||
for (auto method : methods) {
|
||||
SCOPED_TRACE(cv::format("method=%s", getMethodName(method).c_str()));
|
||||
|
||||
Matx33d wRb_est, cRg_est;
|
||||
Matx31d wtb_est, ctg_est;
|
||||
calibrateRobotWorldHandEye(rvec_R_world2cam, t_worldt2cam, R_base2gripper, t_base2gripper,
|
||||
wRb_est, wtb_est, cRg_est, ctg_est, method);
|
||||
|
||||
EXPECT_TRUE(checkRange(wRb_est));
|
||||
EXPECT_TRUE(checkRange(wtb_est));
|
||||
EXPECT_TRUE(checkRange(cRg_est));
|
||||
EXPECT_TRUE(checkRange(ctg_est));
|
||||
|
||||
//Arbitrary thresholds
|
||||
const double rotation_threshold = 1.0; //1deg
|
||||
const double translation_threshold = 50.0; //5cm
|
||||
|
||||
//X
|
||||
//rotation error
|
||||
Matx33d wRw_est = wRb * wRb_est.t();
|
||||
Matx31d rvec_wRw_est;
|
||||
cv::Rodrigues(wRw_est, rvec_wRw_est);
|
||||
double X_rotation_error = cv::norm(rvec_wRw_est)*180/CV_PI;
|
||||
//translation error
|
||||
double X_t_error = cv::norm(wtb_est - wtb);
|
||||
SCOPED_TRACE(cv::format("X rotation error=%f", X_rotation_error));
|
||||
SCOPED_TRACE(cv::format("X translation error=%f", X_t_error));
|
||||
EXPECT_TRUE(X_rotation_error < rotation_threshold);
|
||||
EXPECT_TRUE(X_t_error < translation_threshold);
|
||||
|
||||
//Z
|
||||
//rotation error
|
||||
Matx33d cRc_est = cRg * cRg_est.t();
|
||||
Matx31d rvec_cMc_est;
|
||||
cv::Rodrigues(cRc_est, rvec_cMc_est);
|
||||
double Z_rotation_error = cv::norm(rvec_cMc_est)*180/CV_PI;
|
||||
//translation error
|
||||
double Z_t_error = cv::norm(ctg_est - ctg);
|
||||
SCOPED_TRACE(cv::format("Z rotation error=%f", Z_rotation_error));
|
||||
SCOPED_TRACE(cv::format("Z translation error=%f", Z_t_error));
|
||||
EXPECT_TRUE(Z_rotation_error < rotation_threshold);
|
||||
EXPECT_TRUE(Z_t_error < translation_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Calib3d_CalibrateHandEye, regression_24871)
|
||||
{
|
||||
std::vector<Mat> R_target2cam, t_target2cam;
|
||||
std::vector<Mat> R_gripper2base, t_gripper2base;
|
||||
Mat T_true_cam2gripper;
|
||||
|
||||
T_true_cam2gripper = (cv::Mat_<double>(4, 4) << 0, 0, -1, 0.1,
|
||||
1, 0, 0, 0.2,
|
||||
0, -1, 0, 0.3,
|
||||
0, 0, 0, 1);
|
||||
|
||||
R_target2cam.push_back((cv::Mat_<double>(3, 3) <<
|
||||
0.04964505493834381, 0.5136826827431226, 0.8565427426404346,
|
||||
-0.3923117691818854, 0.7987004864191318, -0.4562554205214679,
|
||||
-0.9184916136152514, -0.3133809733274676, 0.2411752915926112));
|
||||
t_target2cam.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-1.588728904724121,
|
||||
0.07843752950429916,
|
||||
-1.002813339233398));
|
||||
|
||||
R_gripper2base.push_back((cv::Mat_<double>(3, 3) <<
|
||||
-0.4143743581399177, -0.6105088815982459, -0.6749613298595637,
|
||||
-0.1598851232573451, -0.6812625208693498, 0.71436554019614,
|
||||
-0.895952364066927, 0.4039310376145889, 0.1846864320259794));
|
||||
t_gripper2base.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-1.249274406461827,
|
||||
-1.916570771580279,
|
||||
2.005069553422765));
|
||||
|
||||
R_target2cam.push_back((cv::Mat_<double>(3, 3) <<
|
||||
-0.3048000068139332, 0.6971848192711539, 0.6488684640388026,
|
||||
-0.9377589344241749, -0.3387497187353627, -0.07652979135179161,
|
||||
0.1664486009369332, -0.6318084803439735, 0.7570422097951847));
|
||||
t_target2cam.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-1.906493663787842,
|
||||
-0.07281044125556946,
|
||||
0.6088893413543701));
|
||||
|
||||
R_gripper2base.push_back((cv::Mat_<double>(3, 3) <<
|
||||
0.7262439860936567, -0.201662933718935, -0.6571923111439066,
|
||||
-0.4640017362244384, -0.8491808316335328, -0.2521791108852766,
|
||||
-0.5072199339965884, 0.4880819361030014, -0.7102844234575628));
|
||||
t_gripper2base.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-0.7375172846804027,
|
||||
-2.579760910816792,
|
||||
1.336561572270101));
|
||||
|
||||
R_target2cam.push_back((cv::Mat_<double>(3, 3) <<
|
||||
-0.590234879685801, -0.7051138289845309, -0.3929850823848928,
|
||||
0.6017371069678565, -0.7088332765096816, 0.3680595606834615,
|
||||
-0.5380847896941907, -0.01923211603859842, 0.8426712792141644));
|
||||
t_target2cam.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-0.9809040427207947,
|
||||
-0.2707894444465637,
|
||||
-0.2577074766159058));
|
||||
|
||||
R_gripper2base.push_back((cv::Mat_<double>(3, 3) <<
|
||||
0.2541996332132083, 0.6186461729765909, 0.7434106934499181,
|
||||
0.2194912986375709, 0.711701808961156, -0.6673111005698995,
|
||||
-0.9419161938817396, 0.3328024155303503, 0.04512688689130734));
|
||||
t_gripper2base.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-1.040123533893404,
|
||||
-0.1303773962721222,
|
||||
1.068029475621886));
|
||||
|
||||
R_target2cam.push_back((cv::Mat_<double>(3, 3) <<
|
||||
0.7643667483125168, -0.08523002870239212, 0.63912386614923,
|
||||
-0.2583463792779588, 0.8676987164647345, 0.424683512464778,
|
||||
-0.5907627462764713, -0.489729292214425, 0.6412211770980741));
|
||||
t_target2cam.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-1.58987033367157,
|
||||
-1.924914002418518,
|
||||
-0.3109001517295837));
|
||||
|
||||
R_gripper2base.push_back((cv::Mat_<double>(3, 3) <<
|
||||
0.116348305340805, -0.9917998080681939, 0.0528792261688552,
|
||||
-0.2760629007224059, 0.01884966191381591, 0.9609547154213178,
|
||||
-0.9540714578526358, -0.1264034452126562, -0.2716060057313114));
|
||||
t_gripper2base.push_back((cv::Mat_<double>(3, 1) <<
|
||||
-2.551899142554571,
|
||||
-2.986937398237611,
|
||||
1.317613923218308));
|
||||
|
||||
Mat R_true_cam2gripper;
|
||||
Mat t_true_cam2gripper;
|
||||
R_true_cam2gripper = T_true_cam2gripper(Rect(0, 0, 3, 3));
|
||||
t_true_cam2gripper = T_true_cam2gripper(Rect(3, 0, 1, 3));
|
||||
|
||||
std::vector<HandEyeCalibrationMethod> methods = {CALIB_HAND_EYE_TSAI,
|
||||
CALIB_HAND_EYE_PARK,
|
||||
CALIB_HAND_EYE_HORAUD,
|
||||
CALIB_HAND_EYE_ANDREFF,
|
||||
CALIB_HAND_EYE_DANIILIDIS};
|
||||
|
||||
for (auto method : methods) {
|
||||
SCOPED_TRACE(cv::format("method=%s", getMethodName(method).c_str()));
|
||||
|
||||
Matx33d R_cam2gripper_est;
|
||||
Matx31d t_cam2gripper_est;
|
||||
calibrateHandEye(R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, R_cam2gripper_est, t_cam2gripper_est, method);
|
||||
|
||||
EXPECT_TRUE(cv::norm(R_cam2gripper_est - R_true_cam2gripper) < 1e-9);
|
||||
EXPECT_TRUE(cv::norm(t_cam2gripper_est - t_true_cam2gripper) < 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,427 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
#include "opencv2/objdetect.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
//template<class T> ostream& operator<<(ostream& out, const Mat_<T>& mat)
|
||||
//{
|
||||
// for(Mat_<T>::const_iterator pos = mat.begin(), end = mat.end(); pos != end; ++pos)
|
||||
// out << *pos << " ";
|
||||
// return out;
|
||||
//}
|
||||
//ostream& operator<<(ostream& out, const Mat& mat) { return out << Mat_<double>(mat); }
|
||||
|
||||
Mat calcRvec(const vector<Point3f>& points, const Size& cornerSize)
|
||||
{
|
||||
Point3f p00 = points[0];
|
||||
Point3f p10 = points[1];
|
||||
Point3f p01 = points[cornerSize.width];
|
||||
|
||||
Vec3d ex(p10.x - p00.x, p10.y - p00.y, p10.z - p00.z);
|
||||
Vec3d ey(p01.x - p00.x, p01.y - p00.y, p01.z - p00.z);
|
||||
Vec3d ez = ex.cross(ey);
|
||||
|
||||
Mat rot(3, 3, CV_64F);
|
||||
*rot.ptr<Vec3d>(0) = ex;
|
||||
*rot.ptr<Vec3d>(1) = ey;
|
||||
*rot.ptr<Vec3d>(2) = ez * (1.0/cv::norm(ez)); // TODO cvtest
|
||||
|
||||
Mat res;
|
||||
Rodrigues(rot.t(), res);
|
||||
return res.reshape(1, 1);
|
||||
}
|
||||
|
||||
class CV_CalibrateCameraArtificialTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_CalibrateCameraArtificialTest() :
|
||||
r(0)
|
||||
{
|
||||
}
|
||||
~CV_CalibrateCameraArtificialTest() {}
|
||||
protected:
|
||||
int r;
|
||||
|
||||
const static int JUST_FIND_CORNERS = 0;
|
||||
const static int USE_CORNERS_SUBPIX = 1;
|
||||
const static int USE_4QUAD_CORNERS = 2;
|
||||
const static int ARTIFICIAL_CORNERS = 4;
|
||||
|
||||
|
||||
bool checkErr(double a, double a0, double eps, double delta)
|
||||
{
|
||||
return fabs(a - a0) > eps * (fabs(a0) + delta);
|
||||
}
|
||||
|
||||
void compareCameraMatrs(const Mat_<double>& camMat, const Mat& camMat_est)
|
||||
{
|
||||
if ( camMat_est.at<double>(0, 1) != 0 || camMat_est.at<double>(1, 0) != 0 ||
|
||||
camMat_est.at<double>(2, 0) != 0 || camMat_est.at<double>(2, 1) != 0 ||
|
||||
camMat_est.at<double>(2, 2) != 1)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Bad shape of camera matrix returned \n");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
}
|
||||
|
||||
double fx_e = camMat_est.at<double>(0, 0), fy_e = camMat_est.at<double>(1, 1);
|
||||
double cx_e = camMat_est.at<double>(0, 2), cy_e = camMat_est.at<double>(1, 2);
|
||||
|
||||
double fx = camMat(0, 0), fy = camMat(1, 1), cx = camMat(0, 2), cy = camMat(1, 2);
|
||||
|
||||
const double eps = 1e-2;
|
||||
const double dlt = 1e-5;
|
||||
|
||||
bool fail = checkErr(fx_e, fx, eps, dlt) || checkErr(fy_e, fy, eps, dlt) ||
|
||||
checkErr(cx_e, cx, eps, dlt) || checkErr(cy_e, cy, eps, dlt);
|
||||
|
||||
if (fail)
|
||||
{
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
ts->printf( cvtest::TS::LOG, "%d) Expected [Fx Fy Cx Cy] = [%.3f %.3f %.3f %.3f]\n", r, fx, fy, cx, cy);
|
||||
ts->printf( cvtest::TS::LOG, "%d) Estimated [Fx Fy Cx Cy] = [%.3f %.3f %.3f %.3f]\n", r, fx_e, fy_e, cx_e, cy_e);
|
||||
}
|
||||
|
||||
void compareDistCoeffs(const Mat_<double>& distCoeffs, const Mat& distCoeffs_est)
|
||||
{
|
||||
const double *dt_e = distCoeffs_est.ptr<double>();
|
||||
|
||||
double k1_e = dt_e[0], k2_e = dt_e[1], k3_e = dt_e[4];
|
||||
double p1_e = dt_e[2], p2_e = dt_e[3];
|
||||
|
||||
double k1 = distCoeffs(0, 0), k2 = distCoeffs(0, 1), k3 = distCoeffs(0, 4);
|
||||
double p1 = distCoeffs(0, 2), p2 = distCoeffs(0, 3);
|
||||
|
||||
const double eps = 5e-2;
|
||||
const double dlt = 1e-3;
|
||||
|
||||
const double eps_k3 = 5;
|
||||
const double dlt_k3 = 1e-3;
|
||||
|
||||
bool fail = checkErr(k1_e, k1, eps, dlt) || checkErr(k2_e, k2, eps, dlt) || checkErr(k3_e, k3, eps_k3, dlt_k3) ||
|
||||
checkErr(p1_e, p1, eps, dlt) || checkErr(p2_e, p2, eps, dlt);
|
||||
|
||||
if (fail)
|
||||
{
|
||||
// commented according to vp123's recommendation. TODO - improve accuracy
|
||||
//ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ss
|
||||
}
|
||||
ts->printf( cvtest::TS::LOG, "%d) DistCoeff exp=(%.2f, %.2f, %.4f, %.4f %.2f)\n", r, k1, k2, p1, p2, k3);
|
||||
ts->printf( cvtest::TS::LOG, "%d) DistCoeff est=(%.2f, %.2f, %.4f, %.4f %.2f)\n", r, k1_e, k2_e, p1_e, p2_e, k3_e);
|
||||
ts->printf( cvtest::TS::LOG, "%d) AbsError = [%.5f %.5f %.5f %.5f %.5f]\n", r, fabs(k1-k1_e), fabs(k2-k2_e), fabs(p1-p1_e), fabs(p2-p2_e), fabs(k3-k3_e));
|
||||
}
|
||||
|
||||
void compareShiftVecs(const vector<Mat>& tvecs, const vector<Mat>& tvecs_est)
|
||||
{
|
||||
const double eps = 1e-2;
|
||||
const double dlt = 1e-4;
|
||||
|
||||
int err_count = 0;
|
||||
const int errMsgNum = 4;
|
||||
for(size_t i = 0; i < tvecs.size(); ++i)
|
||||
{
|
||||
const Point3d& tvec = *tvecs[i].ptr<Point3d>();
|
||||
const Point3d& tvec_est = *tvecs_est[i].ptr<Point3d>();
|
||||
|
||||
double n1 = cv::norm(tvec_est - tvec); // TODO cvtest
|
||||
double n2 = cv::norm(tvec); // TODO cvtest
|
||||
if (n1 > eps* (n2 + dlt))
|
||||
{
|
||||
if (err_count++ < errMsgNum)
|
||||
{
|
||||
if (err_count == errMsgNum)
|
||||
ts->printf( cvtest::TS::LOG, "%d) ...\n", r);
|
||||
else
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "%d) Bad accuracy in returned tvecs. Index = %d\n", r, i);
|
||||
ts->printf( cvtest::TS::LOG, "%d) norm(tvec_est - tvec) = %f, norm(tvec_exp) = %f \n", r, n1, n2);
|
||||
}
|
||||
}
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compareRotationVecs(const vector<Mat>& rvecs, const vector<Mat>& rvecs_est)
|
||||
{
|
||||
const double eps = 2e-2;
|
||||
const double dlt = 1e-4;
|
||||
|
||||
Mat rmat, rmat_est;
|
||||
int err_count = 0;
|
||||
const int errMsgNum = 4;
|
||||
for(size_t i = 0; i < rvecs.size(); ++i)
|
||||
{
|
||||
Rodrigues(rvecs[i], rmat);
|
||||
Rodrigues(rvecs_est[i], rmat_est);
|
||||
|
||||
if (cvtest::norm(rmat_est, rmat, NORM_L2) > eps* (cvtest::norm(rmat, NORM_L2) + dlt))
|
||||
{
|
||||
if (err_count++ < errMsgNum)
|
||||
{
|
||||
if (err_count == errMsgNum)
|
||||
ts->printf( cvtest::TS::LOG, "%d) ...\n", r);
|
||||
else
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "%d) Bad accuracy in returned rvecs (rotation matrs). Index = %d\n", r, i);
|
||||
ts->printf( cvtest::TS::LOG, "%d) norm(rot_mat_est - rot_mat_exp) = %f, norm(rot_mat_exp) = %f \n", r,
|
||||
cvtest::norm(rmat_est, rmat, NORM_L2), cvtest::norm(rmat, NORM_L2));
|
||||
|
||||
}
|
||||
}
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double reprojectErrorWithoutIntrinsics(const vector<Point3f>& cb3d, const vector<Mat>& _rvecs_exp, const vector<Mat>& _tvecs_exp,
|
||||
const vector<Mat>& rvecs_est, const vector<Mat>& tvecs_est)
|
||||
{
|
||||
const static Mat eye33 = Mat::eye(3, 3, CV_64F);
|
||||
const static Mat zero15 = Mat::zeros(1, 5, CV_64F);
|
||||
Mat _chessboard3D(cb3d);
|
||||
vector<Point2f> uv_exp, uv_est;
|
||||
double res = 0;
|
||||
|
||||
for(size_t i = 0; i < rvecs_exp.size(); ++i)
|
||||
{
|
||||
projectPoints(_chessboard3D, _rvecs_exp[i], _tvecs_exp[i], eye33, zero15, uv_exp);
|
||||
projectPoints(_chessboard3D, rvecs_est[i], tvecs_est[i], eye33, zero15, uv_est);
|
||||
for(size_t j = 0; j < cb3d.size(); ++j)
|
||||
res += cv::norm(uv_exp[i] - uv_est[i]); // TODO cvtest
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Size2f sqSile;
|
||||
|
||||
vector<Point3f> chessboard3D;
|
||||
vector<Mat> boards, rvecs_exp, tvecs_exp, rvecs_spnp, tvecs_spnp;
|
||||
vector< vector<Point3f> > objectPoints;
|
||||
vector< vector<Point2f> > imagePoints_art;
|
||||
vector< vector<Point2f> > imagePoints_findCb;
|
||||
|
||||
|
||||
void prepareForTest(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, size_t brdsNum, const ChessBoardGenerator& cbg)
|
||||
{
|
||||
sqSile = Size2f(1.f, 1.f);
|
||||
Size cornersSize = cbg.cornersSize();
|
||||
|
||||
chessboard3D.clear();
|
||||
for(int j = 0; j < cornersSize.height; ++j)
|
||||
for(int i = 0; i < cornersSize.width; ++i)
|
||||
chessboard3D.push_back(Point3f(sqSile.width * i, sqSile.height * j, 0));
|
||||
|
||||
boards.resize(brdsNum);
|
||||
rvecs_exp.resize(brdsNum);
|
||||
tvecs_exp.resize(brdsNum);
|
||||
objectPoints.clear();
|
||||
objectPoints.resize(brdsNum, chessboard3D);
|
||||
imagePoints_art.clear();
|
||||
imagePoints_findCb.clear();
|
||||
|
||||
vector<Point2f> corners_art, corners_fcb;
|
||||
for(size_t i = 0; i < brdsNum; ++i)
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
boards[i] = cbg(bg, camMat, distCoeffs, sqSile, corners_art);
|
||||
if(findChessboardCorners(boards[i], cornersSize, corners_fcb))
|
||||
break;
|
||||
}
|
||||
|
||||
//cv::namedWindow("CB"); imshow("CB", boards[i]); cv::waitKey();
|
||||
|
||||
imagePoints_art.push_back(corners_art);
|
||||
imagePoints_findCb.push_back(corners_fcb);
|
||||
|
||||
tvecs_exp[i].create(1, 3, CV_64F);
|
||||
*tvecs_exp[i].ptr<Point3d>() = cbg.corners3d[0];
|
||||
rvecs_exp[i] = calcRvec(cbg.corners3d, cbg.cornersSize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void runTest(const Size& imgSize, const Mat_<double>& camMat, const Mat_<double>& distCoeffs, size_t brdsNum, const Size& cornersSize, int flag = 0)
|
||||
{
|
||||
const TermCriteria tc(TermCriteria::EPS|TermCriteria::MAX_ITER, 30, 0.1);
|
||||
|
||||
vector< vector<Point2f> > imagePoints;
|
||||
|
||||
switch(flag)
|
||||
{
|
||||
case JUST_FIND_CORNERS: imagePoints = imagePoints_findCb; break;
|
||||
case ARTIFICIAL_CORNERS: imagePoints = imagePoints_art; break;
|
||||
|
||||
case USE_CORNERS_SUBPIX:
|
||||
for(size_t i = 0; i < brdsNum; ++i)
|
||||
{
|
||||
Mat gray;
|
||||
cvtColor(boards[i], gray, COLOR_BGR2GRAY);
|
||||
vector<Point2f> tmp = imagePoints_findCb[i];
|
||||
cornerSubPix(gray, tmp, Size(5, 5), Size(-1,-1), tc);
|
||||
imagePoints.push_back(tmp);
|
||||
}
|
||||
break;
|
||||
case USE_4QUAD_CORNERS:
|
||||
for(size_t i = 0; i < brdsNum; ++i)
|
||||
{
|
||||
Mat gray;
|
||||
cvtColor(boards[i], gray, COLOR_BGR2GRAY);
|
||||
vector<Point2f> tmp = imagePoints_findCb[i];
|
||||
find4QuadCornerSubpix(gray, tmp, Size(5, 5));
|
||||
imagePoints.push_back(tmp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
Mat camMat_est = Mat::eye(3, 3, CV_64F), distCoeffs_est = Mat::zeros(1, 5, CV_64F);
|
||||
vector<Mat> rvecs_est, tvecs_est;
|
||||
|
||||
int flags = /*CALIB_FIX_K3|*/CALIB_FIX_K4|CALIB_FIX_K5|CALIB_FIX_K6; //CALIB_FIX_K3; //CALIB_FIX_ASPECT_RATIO | | CALIB_ZERO_TANGENT_DIST;
|
||||
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, DBL_EPSILON);
|
||||
double rep_error = calibrateCamera(objectPoints, imagePoints, imgSize, camMat_est, distCoeffs_est, rvecs_est, tvecs_est, flags, criteria);
|
||||
rep_error /= brdsNum * cornersSize.area();
|
||||
|
||||
const double thres = 1;
|
||||
if (rep_error > thres)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "%d) Too big reproject error = %f\n", r, rep_error);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
|
||||
compareCameraMatrs(camMat, camMat_est);
|
||||
compareDistCoeffs(distCoeffs, distCoeffs_est);
|
||||
compareShiftVecs(tvecs_exp, tvecs_est);
|
||||
compareRotationVecs(rvecs_exp, rvecs_est);
|
||||
|
||||
double rep_errorWOI = reprojectErrorWithoutIntrinsics(chessboard3D, rvecs_exp, tvecs_exp, rvecs_est, tvecs_est);
|
||||
rep_errorWOI /= brdsNum * cornersSize.area();
|
||||
|
||||
const double thres2 = 0.01;
|
||||
if (rep_errorWOI > thres2)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "%d) Too big reproject error without intrinsics = %f\n", r, rep_errorWOI);
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
|
||||
ts->printf( cvtest::TS::LOG, "%d) Testing solvePnP...\n", r);
|
||||
rvecs_spnp.resize(brdsNum);
|
||||
tvecs_spnp.resize(brdsNum);
|
||||
for(size_t i = 0; i < brdsNum; ++i)
|
||||
solvePnP(objectPoints[i], imagePoints[i], camMat, distCoeffs, rvecs_spnp[i], tvecs_spnp[i]);
|
||||
|
||||
compareShiftVecs(tvecs_exp, tvecs_spnp);
|
||||
compareRotationVecs(rvecs_exp, rvecs_spnp);
|
||||
}
|
||||
|
||||
void run(int)
|
||||
{
|
||||
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
RNG& rng = theRNG();
|
||||
|
||||
int progress = 0;
|
||||
int repeat_num = 3;
|
||||
for(r = 0; r < repeat_num; ++r)
|
||||
{
|
||||
const int brds_num = 20;
|
||||
|
||||
Mat bg(Size(640, 480), CV_8UC3);
|
||||
randu(bg, Scalar::all(32), Scalar::all(255));
|
||||
GaussianBlur(bg, bg, Size(5, 5), 2);
|
||||
|
||||
double fx = 300 + (20 * (double)rng - 10);
|
||||
double fy = 300 + (20 * (double)rng - 10);
|
||||
|
||||
double cx = bg.cols/2 + (40 * (double)rng - 20);
|
||||
double cy = bg.rows/2 + (40 * (double)rng - 20);
|
||||
|
||||
Mat_<double> camMat(3, 3);
|
||||
camMat << fx, 0., cx, 0, fy, cy, 0., 0., 1.;
|
||||
|
||||
double k1 = 0.5 + (double)rng/5;
|
||||
double k2 = (double)rng/5;
|
||||
double k3 = (double)rng/5;
|
||||
|
||||
double p1 = 0.001 + (double)rng/10;
|
||||
double p2 = 0.001 + (double)rng/10;
|
||||
|
||||
Mat_<double> distCoeffs(1, 5, 0.0);
|
||||
distCoeffs << k1, k2, p1, p2, k3;
|
||||
|
||||
ChessBoardGenerator cbg(Size(9, 8));
|
||||
cbg.min_cos = 0.9;
|
||||
cbg.cov = 0.8;
|
||||
|
||||
progress = update_progress(progress, r, repeat_num, 0);
|
||||
ts->printf( cvtest::TS::LOG, "\n");
|
||||
prepareForTest(bg, camMat, distCoeffs, brds_num, cbg);
|
||||
|
||||
ts->printf( cvtest::TS::LOG, "artificial corners\n");
|
||||
runTest(bg.size(), camMat, distCoeffs, brds_num, cbg.cornersSize(), ARTIFICIAL_CORNERS);
|
||||
progress = update_progress(progress, r, repeat_num, 0);
|
||||
|
||||
ts->printf( cvtest::TS::LOG, "findChessboard corners\n");
|
||||
runTest(bg.size(), camMat, distCoeffs, brds_num, cbg.cornersSize(), JUST_FIND_CORNERS);
|
||||
progress = update_progress(progress, r, repeat_num, 0);
|
||||
|
||||
ts->printf( cvtest::TS::LOG, "cornersSubPix corners\n");
|
||||
runTest(bg.size(), camMat, distCoeffs, brds_num, cbg.cornersSize(), USE_CORNERS_SUBPIX);
|
||||
progress = update_progress(progress, r, repeat_num, 0);
|
||||
|
||||
ts->printf( cvtest::TS::LOG, "4quad corners\n");
|
||||
runTest(bg.size(), camMat, distCoeffs, brds_num, cbg.cornersSize(), USE_4QUAD_CORNERS);
|
||||
progress = update_progress(progress, r, repeat_num, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Calib3d_CalibrateCamera_CPP, DISABLED_accuracy_on_artificial_data) { CV_CalibrateCameraArtificialTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,396 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_CameraCalibrationBadArgTest : public cvtest::BadArgTest
|
||||
{
|
||||
public:
|
||||
CV_CameraCalibrationBadArgTest() {}
|
||||
~CV_CameraCalibrationBadArgTest() {}
|
||||
protected:
|
||||
void run(int);
|
||||
void run_func(void) {}
|
||||
|
||||
struct C_Caller
|
||||
{
|
||||
_InputArray imgPts_arg;
|
||||
_InputArray objPts_arg;
|
||||
_OutputArray rvecs_arg;
|
||||
_OutputArray tvecs_arg;
|
||||
_OutputArray newObjPts_arg;
|
||||
_InputOutputArray cameraMatrix_arg;
|
||||
_InputOutputArray distCoeffs_arg;
|
||||
|
||||
std::vector<std::vector<Point2f> > imgPts;
|
||||
std::vector<std::vector<Point3f> > objPts;
|
||||
|
||||
Size imageSize0, imageSize;
|
||||
int iFixedPoint0, iFixedPoint;
|
||||
Mat cameraMatrix;
|
||||
Mat distCoeffs;
|
||||
std::vector<Mat> rvecs;
|
||||
std::vector<Mat> tvecs;
|
||||
std::vector<Point3f> newObjPts;
|
||||
int flags0, flags;
|
||||
|
||||
void initArgs()
|
||||
{
|
||||
imgPts_arg = imgPts;
|
||||
objPts_arg = objPts;
|
||||
rvecs_arg = rvecs;
|
||||
tvecs_arg = tvecs;
|
||||
newObjPts_arg = newObjPts;
|
||||
cameraMatrix_arg = cameraMatrix;
|
||||
distCoeffs_arg = distCoeffs;
|
||||
imageSize = imageSize0;
|
||||
flags = flags0;
|
||||
iFixedPoint = iFixedPoint0;
|
||||
}
|
||||
|
||||
void operator()() const
|
||||
{
|
||||
calibrateCameraRO(objPts_arg, imgPts_arg, imageSize, iFixedPoint,
|
||||
cameraMatrix_arg, distCoeffs_arg, rvecs_arg, tvecs_arg,
|
||||
newObjPts_arg, flags);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
void CV_CameraCalibrationBadArgTest::run( int /* start_from */ )
|
||||
{
|
||||
const int M = 2;
|
||||
Size imgSize(800, 600);
|
||||
Mat_<float> camMat(3, 3);
|
||||
Mat_<float> distCoeffs0(1, 5);
|
||||
|
||||
camMat << 300.f, 0.f, imgSize.width/2.f, 0, 300.f, imgSize.height/2.f, 0.f, 0.f, 1.f;
|
||||
distCoeffs0 << 1.2f, 0.2f, 0.f, 0.f, 0.f;
|
||||
|
||||
ChessBoardGenerator cbg(Size(8,6));
|
||||
Size corSize = cbg.cornersSize();
|
||||
vector<Point2f> corners;
|
||||
cbg(Mat(imgSize, CV_8U, Scalar(0)), camMat, distCoeffs0, corners);
|
||||
|
||||
C_Caller caller;
|
||||
caller.imageSize0 = imgSize;
|
||||
caller.iFixedPoint0 = -1;
|
||||
caller.flags0 = 0;
|
||||
|
||||
/////////////////////////////
|
||||
Mat cameraMatrix_cpp;
|
||||
Mat distCoeffs_cpp;
|
||||
Mat rvecs_cpp;
|
||||
Mat tvecs_cpp;
|
||||
Mat newObjPts_cpp;
|
||||
|
||||
std::vector<Point3f> objPts_cpp;
|
||||
for(int y = 0; y < corSize.height; ++y)
|
||||
for(int x = 0; x < corSize.width; ++x)
|
||||
objPts_cpp.push_back(Point3f((float)x, (float)y, 0.f));
|
||||
caller.objPts.resize(M);
|
||||
caller.imgPts.resize(M);
|
||||
for(int i = 0; i < M; i++)
|
||||
{
|
||||
caller.objPts[i] = objPts_cpp;
|
||||
caller.imgPts[i] = corners;
|
||||
}
|
||||
caller.cameraMatrix.create(3, 3, CV_32F);
|
||||
caller.distCoeffs.create(5, 1, CV_32F);
|
||||
caller.rvecs.clear();
|
||||
caller.tvecs.clear();
|
||||
caller.newObjPts.clear();
|
||||
|
||||
/* /*//*/ */
|
||||
int errors = 0;
|
||||
|
||||
caller.initArgs();
|
||||
caller.objPts_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "None passed in objPts", caller);
|
||||
|
||||
caller.initArgs();
|
||||
caller.imgPts_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "None passed in imgPts", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.cameraMatrix_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero passed in cameraMatrix", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.distCoeffs_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero passed in distCoeffs", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.imageSize.width = -1;
|
||||
errors += run_test_case( cv::Error::StsOutOfRange, "Bad image width", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.imageSize.height = -1;
|
||||
errors += run_test_case( cv::Error::StsOutOfRange, "Bad image height", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.imgPts[0].clear();
|
||||
errors += run_test_case( cv::Error::StsBadSize, "Bad imgpts[0]", caller );
|
||||
caller.imgPts[0] = caller.imgPts[1];
|
||||
|
||||
caller.initArgs();
|
||||
caller.objPts[1].clear();
|
||||
errors += run_test_case( cv::Error::StsBadSize, "Bad objpts[1]", caller );
|
||||
caller.objPts[1] = caller.objPts[0];
|
||||
|
||||
caller.initArgs();
|
||||
Mat badCM = Mat::zeros(4, 4, CV_64F);
|
||||
caller.cameraMatrix_arg = badCM;
|
||||
caller.flags = CALIB_USE_INTRINSIC_GUESS;
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad camearaMatrix header", caller );
|
||||
|
||||
caller.initArgs();
|
||||
Mat badDC = Mat::zeros(10, 10, CV_64F);
|
||||
caller.distCoeffs_arg = badDC;
|
||||
caller.flags = CALIB_USE_INTRINSIC_GUESS;
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad camearaMatrix header", caller );
|
||||
|
||||
if (errors)
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
else
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
}
|
||||
|
||||
|
||||
class CV_Rodrigues2BadArgTest : public cvtest::BadArgTest
|
||||
{
|
||||
public:
|
||||
CV_Rodrigues2BadArgTest() {}
|
||||
~CV_Rodrigues2BadArgTest() {}
|
||||
protected:
|
||||
void run_func(void) {}
|
||||
|
||||
struct C_Caller
|
||||
{
|
||||
_InputArray src_arg;
|
||||
_OutputArray dst_arg, j_arg;
|
||||
|
||||
Mat src;
|
||||
Mat dst;
|
||||
Mat jacobian;
|
||||
|
||||
void initArgs()
|
||||
{
|
||||
src_arg = src;
|
||||
dst_arg = dst;
|
||||
j_arg = jacobian;
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
cv::Rodrigues(src_arg, dst_arg, j_arg);
|
||||
}
|
||||
};
|
||||
|
||||
void run(int /* start_from */ )
|
||||
{
|
||||
Mat src_cpp(3, 1, CV_32F);
|
||||
Mat dst_cpp(3, 3, CV_32F);
|
||||
|
||||
C_Caller caller;
|
||||
|
||||
/*/*//*/*/
|
||||
int errors = 0;
|
||||
|
||||
caller.initArgs();
|
||||
caller.src_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Src is empty matrix", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.src = Mat::zeros(3, 1, CV_8U);
|
||||
errors += run_test_case( cv::Error::StsUnsupportedFormat, "Bad src formart", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.src = Mat::zeros(1, 1, CV_32F);
|
||||
errors += run_test_case( cv::Error::StsBadSize, "Bad src size", caller );
|
||||
|
||||
if (errors)
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
else
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
class CV_ProjectPoints2BadArgTest : public cvtest::BadArgTest
|
||||
{
|
||||
public:
|
||||
CV_ProjectPoints2BadArgTest() : camMat(3, 3), distCoeffs(1, 5)
|
||||
{
|
||||
Size imsSize(800, 600);
|
||||
camMat << 300.f, 0.f, imsSize.width/2.f, 0, 300.f, imsSize.height/2.f, 0.f, 0.f, 1.f;
|
||||
distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;
|
||||
}
|
||||
~CV_ProjectPoints2BadArgTest() {}
|
||||
protected:
|
||||
void run_func(void) {}
|
||||
|
||||
Mat_<float> camMat;
|
||||
Mat_<float> distCoeffs;
|
||||
|
||||
struct C_Caller
|
||||
{
|
||||
_InputArray objectPoints_arg, rvec_arg, tvec_arg, A_arg, DC_arg;
|
||||
_OutputArray imagePoints_arg;
|
||||
Mat objectPoints;
|
||||
Mat r_vec;
|
||||
Mat t_vec;
|
||||
Mat A;
|
||||
Mat distCoeffs;
|
||||
Mat imagePoints;
|
||||
Mat J;
|
||||
double aspectRatio0, aspectRatio;
|
||||
|
||||
void initArgs()
|
||||
{
|
||||
objectPoints_arg = objectPoints;
|
||||
imagePoints_arg = imagePoints;
|
||||
rvec_arg = r_vec;
|
||||
tvec_arg = t_vec;
|
||||
A_arg = A;
|
||||
DC_arg = distCoeffs;
|
||||
aspectRatio = aspectRatio0;
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
projectPoints(objectPoints_arg, rvec_arg, tvec_arg, A_arg, DC_arg,
|
||||
imagePoints_arg, J, aspectRatio );
|
||||
}
|
||||
};
|
||||
|
||||
void run(int /* start_from */ )
|
||||
{
|
||||
C_Caller caller;
|
||||
|
||||
const int n = 10;
|
||||
|
||||
Mat objectPoints_cpp(1, n, CV_32FC3);
|
||||
randu(objectPoints_cpp, Scalar::all(1), Scalar::all(10));
|
||||
caller.objectPoints = objectPoints_cpp;
|
||||
caller.t_vec = Mat::zeros(1, 3, CV_32F);
|
||||
Rodrigues(Mat::eye(3, 3, CV_32F), caller.r_vec);
|
||||
caller.A = Mat::eye(3, 3, CV_32F);
|
||||
caller.distCoeffs = Mat::zeros(1, 5, CV_32F);
|
||||
caller.aspectRatio0 = 1.0;
|
||||
|
||||
/********************/
|
||||
int errors = 0;
|
||||
|
||||
caller.initArgs();
|
||||
caller.objectPoints_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero objectPoints", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.rvec_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero r_vec", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.tvec_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero t_vec", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.A_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero camMat", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.imagePoints_arg = noArray();
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Zero imagePoints", caller );
|
||||
|
||||
Mat save_rvec = caller.r_vec;
|
||||
caller.initArgs();
|
||||
caller.r_vec.create(2, 2, CV_32F);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad rvec format", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.r_vec.create(1, 3, CV_8U);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad rvec format", caller );
|
||||
caller.r_vec = save_rvec;
|
||||
|
||||
/****************************/
|
||||
Mat save_tvec = caller.t_vec;
|
||||
caller.initArgs();
|
||||
caller.t_vec.create(3, 3, CV_32F);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad tvec format", caller );
|
||||
|
||||
caller.initArgs();
|
||||
caller.t_vec.create(1, 3, CV_8U);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad tvec format", caller );
|
||||
caller.t_vec = save_tvec;
|
||||
|
||||
/****************************/
|
||||
Mat save_A = caller.A;
|
||||
caller.initArgs();
|
||||
caller.A.create(2, 2, CV_32F);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad A format", caller );
|
||||
caller.A = save_A;
|
||||
|
||||
/****************************/
|
||||
Mat save_DC = caller.distCoeffs;
|
||||
caller.initArgs();
|
||||
caller.distCoeffs.create(3, 3, CV_32F);
|
||||
errors += run_test_case( cv::Error::StsBadArg, "Bad distCoeffs format", caller );
|
||||
caller.distCoeffs = save_DC;
|
||||
|
||||
if (errors)
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
else
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST(Calib3d_CalibrateCamera_CPP, badarg) { CV_CameraCalibrationBadArgTest test; test.safe_run(); }
|
||||
TEST(Calib3d_Rodrigues_CPP, badarg) { CV_Rodrigues2BadArgTest test; test.safe_run(); }
|
||||
TEST(Calib3d_ProjectPoints_CPP, badarg) { CV_ProjectPoints2BadArgTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,693 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/ts/cuda_test.hpp" // EXPECT_MAT_NEAR
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#define NUM_DIST_COEFF_TILT 14
|
||||
|
||||
/**
|
||||
Some conventions:
|
||||
- the first camera determines the world coordinate system
|
||||
- y points down, hence top means minimal y value (negative) and
|
||||
bottom means maximal y value (positive)
|
||||
- the field of view plane is tilted around x such that it
|
||||
intersects the xy-plane in a line with a large (positive)
|
||||
y-value
|
||||
- image sensor and object are both modelled in the halfspace
|
||||
z > 0
|
||||
|
||||
|
||||
**/
|
||||
class cameraCalibrationTiltTest : public ::testing::Test {
|
||||
|
||||
protected:
|
||||
cameraCalibrationTiltTest()
|
||||
: m_toRadian(acos(-1.0)/180.0)
|
||||
, m_toDegree(180.0/acos(-1.0))
|
||||
{}
|
||||
virtual void SetUp();
|
||||
|
||||
protected:
|
||||
static const cv::Size m_imageSize;
|
||||
static const double m_pixelSize;
|
||||
static const double m_circleConfusionPixel;
|
||||
static const double m_lensFocalLength;
|
||||
static const double m_lensFNumber;
|
||||
static const double m_objectDistance;
|
||||
static const double m_planeTiltDegree;
|
||||
static const double m_pointTargetDist;
|
||||
static const int m_pointTargetNum;
|
||||
|
||||
/** image distance corresponding to working distance */
|
||||
double m_imageDistance;
|
||||
/** image tilt angle corresponding to the tilt of the object plane */
|
||||
double m_imageTiltDegree;
|
||||
/** center of the field of view, near and far plane */
|
||||
std::vector<cv::Vec3d> m_fovCenter;
|
||||
/** normal of the field of view, near and far plane */
|
||||
std::vector<cv::Vec3d> m_fovNormal;
|
||||
/** points on a plane calibration target */
|
||||
std::vector<cv::Point3d> m_pointTarget;
|
||||
/** rotations for the calibration target */
|
||||
std::vector<cv::Vec3d> m_pointTargetRvec;
|
||||
/** translations for the calibration target */
|
||||
std::vector<cv::Vec3d> m_pointTargetTvec;
|
||||
/** camera matrix */
|
||||
cv::Matx33d m_cameraMatrix;
|
||||
/** distortion coefficients */
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> m_distortionCoeff;
|
||||
|
||||
/** random generator */
|
||||
cv::RNG m_rng;
|
||||
/** degree to radian conversion factor */
|
||||
const double m_toRadian;
|
||||
/** radian to degree conversion factor */
|
||||
const double m_toDegree;
|
||||
|
||||
/**
|
||||
computes for a given distance of an image or object point
|
||||
the distance of the corresponding object or image point
|
||||
*/
|
||||
double opticalMap(double dist) {
|
||||
return m_lensFocalLength*dist/(dist - m_lensFocalLength);
|
||||
}
|
||||
|
||||
/** magnification of the optical map */
|
||||
double magnification(double dist) {
|
||||
return m_lensFocalLength/(dist - m_lensFocalLength);
|
||||
}
|
||||
|
||||
/**
|
||||
Changes given distortion coefficients randomly by adding
|
||||
a uniformly distributed random variable in [-max max]
|
||||
\param coeff input
|
||||
\param max limits for the random variables
|
||||
*/
|
||||
void randomDistortionCoeff(
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT>& coeff,
|
||||
const cv::Vec<double, NUM_DIST_COEFF_TILT>& max)
|
||||
{
|
||||
for (int i = 0; i < coeff.rows; ++i)
|
||||
coeff(i) += m_rng.uniform(-max(i), max(i));
|
||||
}
|
||||
|
||||
/** numerical jacobian */
|
||||
void numericalDerivative(
|
||||
cv::Mat& jac,
|
||||
double eps,
|
||||
const std::vector<cv::Point3d>& obj,
|
||||
const cv::Vec3d& rvec,
|
||||
const cv::Vec3d& tvec,
|
||||
const cv::Matx33d& camera,
|
||||
const cv::Vec<double, NUM_DIST_COEFF_TILT>& distor);
|
||||
|
||||
/** remove points with projection outside the sensor array */
|
||||
void removeInvalidPoints(
|
||||
std::vector<cv::Point2d>& imagePoints,
|
||||
std::vector<cv::Point3d>& objectPoints);
|
||||
|
||||
/** add uniform distribute noise in [-halfWidthNoise, halfWidthNoise]
|
||||
to the image points and remove out of range points */
|
||||
void addNoiseRemoveInvalidPoints(
|
||||
std::vector<cv::Point2f>& imagePoints,
|
||||
std::vector<cv::Point3f>& objectPoints,
|
||||
std::vector<cv::Point2f>& noisyImagePoints,
|
||||
double halfWidthNoise);
|
||||
};
|
||||
|
||||
/** Number of Pixel of the sensor */
|
||||
const cv::Size cameraCalibrationTiltTest::m_imageSize(1600, 1200);
|
||||
/** Size of a pixel in mm */
|
||||
const double cameraCalibrationTiltTest::m_pixelSize(.005);
|
||||
/** Diameter of the circle of confusion */
|
||||
const double cameraCalibrationTiltTest::m_circleConfusionPixel(3);
|
||||
/** Focal length of the lens */
|
||||
const double cameraCalibrationTiltTest::m_lensFocalLength(16.4);
|
||||
/** F-Number */
|
||||
const double cameraCalibrationTiltTest::m_lensFNumber(8);
|
||||
/** Working distance */
|
||||
const double cameraCalibrationTiltTest::m_objectDistance(200);
|
||||
/** Angle between optical axis and object plane normal */
|
||||
const double cameraCalibrationTiltTest::m_planeTiltDegree(55);
|
||||
/** the calibration target are points on a square grid with this side length */
|
||||
const double cameraCalibrationTiltTest::m_pointTargetDist(5);
|
||||
/** the calibration target has (2*n + 1) x (2*n + 1) points */
|
||||
const int cameraCalibrationTiltTest::m_pointTargetNum(15);
|
||||
|
||||
|
||||
void cameraCalibrationTiltTest::SetUp()
|
||||
{
|
||||
m_imageDistance = opticalMap(m_objectDistance);
|
||||
m_imageTiltDegree = m_toDegree * atan2(
|
||||
m_imageDistance * tan(m_toRadian * m_planeTiltDegree),
|
||||
m_objectDistance);
|
||||
// half sensor height
|
||||
double tmp = .5 * (m_imageSize.height - 1) * m_pixelSize
|
||||
* cos(m_toRadian * m_imageTiltDegree);
|
||||
// y-Value of tilted sensor
|
||||
double yImage[2] = {tmp, -tmp};
|
||||
// change in z because of the tilt
|
||||
tmp *= sin(m_toRadian * m_imageTiltDegree);
|
||||
// z-values of the sensor lower and upper corner
|
||||
double zImage[2] = {
|
||||
m_imageDistance + tmp,
|
||||
m_imageDistance - tmp};
|
||||
// circle of confusion
|
||||
double circleConfusion = m_circleConfusionPixel*m_pixelSize;
|
||||
// aperture of the lense
|
||||
double aperture = m_lensFocalLength/m_lensFNumber;
|
||||
// near and far factor on the image side
|
||||
double nearFarFactorImage[2] = {
|
||||
aperture/(aperture - circleConfusion),
|
||||
aperture/(aperture + circleConfusion)};
|
||||
// on the object side - points that determine the field of
|
||||
// view
|
||||
std::vector<cv::Vec3d> fovBottomTop(6);
|
||||
std::vector<cv::Vec3d>::iterator itFov = fovBottomTop.begin();
|
||||
for (size_t iBottomTop = 0; iBottomTop < 2; ++iBottomTop)
|
||||
{
|
||||
// mapping sensor to field of view
|
||||
*itFov = cv::Vec3d(0,yImage[iBottomTop],zImage[iBottomTop]);
|
||||
*itFov *= magnification((*itFov)(2));
|
||||
++itFov;
|
||||
for (size_t iNearFar = 0; iNearFar < 2; ++iNearFar, ++itFov)
|
||||
{
|
||||
// scaling to the near and far distance on the
|
||||
// image side
|
||||
*itFov = cv::Vec3d(0,yImage[iBottomTop],zImage[iBottomTop]) *
|
||||
nearFarFactorImage[iNearFar];
|
||||
// scaling to the object side
|
||||
*itFov *= magnification((*itFov)(2));
|
||||
}
|
||||
}
|
||||
m_fovCenter.resize(3);
|
||||
m_fovNormal.resize(3);
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
m_fovCenter[i] = .5*(fovBottomTop[i] + fovBottomTop[i+3]);
|
||||
m_fovNormal[i] = fovBottomTop[i+3] - fovBottomTop[i];
|
||||
m_fovNormal[i] = cv::normalize(m_fovNormal[i]);
|
||||
m_fovNormal[i] = cv::Vec3d(
|
||||
m_fovNormal[i](0),
|
||||
-m_fovNormal[i](2),
|
||||
m_fovNormal[i](1));
|
||||
// one target position in each plane
|
||||
m_pointTargetTvec.push_back(m_fovCenter[i]);
|
||||
cv::Vec3d rvec = cv::Vec3d(0,0,1).cross(m_fovNormal[i]);
|
||||
rvec = cv::normalize(rvec);
|
||||
rvec *= acos(m_fovNormal[i](2));
|
||||
m_pointTargetRvec.push_back(rvec);
|
||||
}
|
||||
// calibration target
|
||||
size_t num = 2*m_pointTargetNum + 1;
|
||||
m_pointTarget.resize(num*num);
|
||||
std::vector<cv::Point3d>::iterator itTarget = m_pointTarget.begin();
|
||||
for (int iY = -m_pointTargetNum; iY <= m_pointTargetNum; ++iY)
|
||||
{
|
||||
for (int iX = -m_pointTargetNum; iX <= m_pointTargetNum; ++iX, ++itTarget)
|
||||
{
|
||||
*itTarget = cv::Point3d(iX, iY, 0) * m_pointTargetDist;
|
||||
}
|
||||
}
|
||||
// oblique target positions
|
||||
// approximate distance to the near and far plane
|
||||
double dist = std::max(
|
||||
std::abs(m_fovNormal[0].dot(m_fovCenter[0] - m_fovCenter[1])),
|
||||
std::abs(m_fovNormal[0].dot(m_fovCenter[0] - m_fovCenter[2])));
|
||||
// maximal angle such that target border "reaches" near and far plane
|
||||
double maxAngle = atan2(dist, m_pointTargetNum*m_pointTargetDist);
|
||||
std::vector<double> angle;
|
||||
angle.push_back(-maxAngle);
|
||||
angle.push_back(maxAngle);
|
||||
cv::Matx33d baseMatrix;
|
||||
cv::Rodrigues(m_pointTargetRvec.front(), baseMatrix);
|
||||
for (std::vector<double>::const_iterator itAngle = angle.begin(); itAngle != angle.end(); ++itAngle)
|
||||
{
|
||||
cv::Matx33d rmat;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
cv::Vec3d rvec(0,0,0);
|
||||
rvec(i) = *itAngle;
|
||||
cv::Rodrigues(rvec, rmat);
|
||||
rmat = baseMatrix*rmat;
|
||||
cv::Rodrigues(rmat, rvec);
|
||||
m_pointTargetTvec.push_back(m_fovCenter.front());
|
||||
m_pointTargetRvec.push_back(rvec);
|
||||
}
|
||||
}
|
||||
// camera matrix
|
||||
double cx = .5 * (m_imageSize.width - 1);
|
||||
double cy = .5 * (m_imageSize.height - 1);
|
||||
double f = m_imageDistance/m_pixelSize;
|
||||
m_cameraMatrix = cv::Matx33d(
|
||||
f,0,cx,
|
||||
0,f,cy,
|
||||
0,0,1);
|
||||
// distortion coefficients
|
||||
m_distortionCoeff = cv::Vec<double, NUM_DIST_COEFF_TILT>::all(0);
|
||||
// tauX
|
||||
m_distortionCoeff(12) = -m_toRadian*m_imageTiltDegree;
|
||||
|
||||
}
|
||||
|
||||
void cameraCalibrationTiltTest::numericalDerivative(
|
||||
cv::Mat& jac,
|
||||
double eps,
|
||||
const std::vector<cv::Point3d>& obj,
|
||||
const cv::Vec3d& rvec,
|
||||
const cv::Vec3d& tvec,
|
||||
const cv::Matx33d& camera,
|
||||
const cv::Vec<double, NUM_DIST_COEFF_TILT>& distor)
|
||||
{
|
||||
cv::Vec3d r(rvec);
|
||||
cv::Vec3d t(tvec);
|
||||
cv::Matx33d cm(camera);
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> dc(distor);
|
||||
double* param[10+NUM_DIST_COEFF_TILT] = {
|
||||
&r(0), &r(1), &r(2),
|
||||
&t(0), &t(1), &t(2),
|
||||
&cm(0,0), &cm(1,1), &cm(0,2), &cm(1,2),
|
||||
&dc(0), &dc(1), &dc(2), &dc(3), &dc(4), &dc(5), &dc(6),
|
||||
&dc(7), &dc(8), &dc(9), &dc(10), &dc(11), &dc(12), &dc(13)};
|
||||
std::vector<cv::Point2d> pix0, pix1;
|
||||
double invEps = .5/eps;
|
||||
|
||||
for (int col = 0; col < 10+NUM_DIST_COEFF_TILT; ++col)
|
||||
{
|
||||
double save = *(param[col]);
|
||||
*(param[col]) = save + eps;
|
||||
cv::projectPoints(obj, r, t, cm, dc, pix0);
|
||||
*(param[col]) = save - eps;
|
||||
cv::projectPoints(obj, r, t, cm, dc, pix1);
|
||||
*(param[col]) = save;
|
||||
|
||||
std::vector<cv::Point2d>::const_iterator it0 = pix0.begin();
|
||||
std::vector<cv::Point2d>::const_iterator it1 = pix1.begin();
|
||||
int row = 0;
|
||||
for (;it0 != pix0.end(); ++it0, ++it1)
|
||||
{
|
||||
cv::Point2d d = invEps*(*it0 - *it1);
|
||||
jac.at<double>(row, col) = d.x;
|
||||
++row;
|
||||
jac.at<double>(row, col) = d.y;
|
||||
++row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cameraCalibrationTiltTest::removeInvalidPoints(
|
||||
std::vector<cv::Point2d>& imagePoints,
|
||||
std::vector<cv::Point3d>& objectPoints)
|
||||
{
|
||||
// remove object and imgage points out of range
|
||||
std::vector<cv::Point2d>::iterator itImg = imagePoints.begin();
|
||||
std::vector<cv::Point3d>::iterator itObj = objectPoints.begin();
|
||||
while (itImg != imagePoints.end())
|
||||
{
|
||||
bool ok =
|
||||
itImg->x >= 0 &&
|
||||
itImg->x <= m_imageSize.width - 1.0 &&
|
||||
itImg->y >= 0 &&
|
||||
itImg->y <= m_imageSize.height - 1.0;
|
||||
if (ok)
|
||||
{
|
||||
++itImg;
|
||||
++itObj;
|
||||
}
|
||||
else
|
||||
{
|
||||
itImg = imagePoints.erase(itImg);
|
||||
itObj = objectPoints.erase(itObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cameraCalibrationTiltTest::addNoiseRemoveInvalidPoints(
|
||||
std::vector<cv::Point2f>& imagePoints,
|
||||
std::vector<cv::Point3f>& objectPoints,
|
||||
std::vector<cv::Point2f>& noisyImagePoints,
|
||||
double halfWidthNoise)
|
||||
{
|
||||
std::vector<cv::Point2f>::iterator itImg = imagePoints.begin();
|
||||
std::vector<cv::Point3f>::iterator itObj = objectPoints.begin();
|
||||
noisyImagePoints.clear();
|
||||
noisyImagePoints.reserve(imagePoints.size());
|
||||
while (itImg != imagePoints.end())
|
||||
{
|
||||
cv::Point2f pix = *itImg + cv::Point2f(
|
||||
(float)m_rng.uniform(-halfWidthNoise, halfWidthNoise),
|
||||
(float)m_rng.uniform(-halfWidthNoise, halfWidthNoise));
|
||||
bool ok =
|
||||
pix.x >= 0 &&
|
||||
pix.x <= m_imageSize.width - 1.0 &&
|
||||
pix.y >= 0 &&
|
||||
pix.y <= m_imageSize.height - 1.0;
|
||||
if (ok)
|
||||
{
|
||||
noisyImagePoints.push_back(pix);
|
||||
++itImg;
|
||||
++itObj;
|
||||
}
|
||||
else
|
||||
{
|
||||
itImg = imagePoints.erase(itImg);
|
||||
itObj = objectPoints.erase(itObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_F(cameraCalibrationTiltTest, projectPoints)
|
||||
{
|
||||
std::vector<cv::Point2d> imagePoints;
|
||||
std::vector<cv::Point3d> objectPoints = m_pointTarget;
|
||||
cv::Vec3d rvec = m_pointTargetRvec.front();
|
||||
cv::Vec3d tvec = m_pointTargetTvec.front();
|
||||
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> coeffNoiseHalfWidth(
|
||||
.1, .1, // k1 k2
|
||||
.01, .01, // p1 p2
|
||||
.001, .001, .001, .001, // k3 k4 k5 k6
|
||||
.001, .001, .001, .001, // s1 s2 s3 s4
|
||||
.01, .01); // tauX tauY
|
||||
for (size_t numTest = 0; numTest < 10; ++numTest)
|
||||
{
|
||||
// create random distortion coefficients
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> distortionCoeff = m_distortionCoeff;
|
||||
randomDistortionCoeff(distortionCoeff, coeffNoiseHalfWidth);
|
||||
|
||||
// projection
|
||||
cv::projectPoints(
|
||||
objectPoints,
|
||||
rvec,
|
||||
tvec,
|
||||
m_cameraMatrix,
|
||||
distortionCoeff,
|
||||
imagePoints);
|
||||
|
||||
// remove object and imgage points out of range
|
||||
removeInvalidPoints(imagePoints, objectPoints);
|
||||
|
||||
int numPoints = (int)imagePoints.size();
|
||||
int numParams = 10 + distortionCoeff.rows;
|
||||
cv::Mat jacobian(2*numPoints, numParams, CV_64FC1);
|
||||
|
||||
// projection and jacobian
|
||||
cv::projectPoints(
|
||||
objectPoints,
|
||||
rvec,
|
||||
tvec,
|
||||
m_cameraMatrix,
|
||||
distortionCoeff,
|
||||
imagePoints,
|
||||
jacobian);
|
||||
|
||||
// numerical derivatives
|
||||
cv::Mat numericJacobian(2*numPoints, numParams, CV_64FC1);
|
||||
double eps = 1e-7;
|
||||
numericalDerivative(
|
||||
numericJacobian,
|
||||
eps,
|
||||
objectPoints,
|
||||
rvec,
|
||||
tvec,
|
||||
m_cameraMatrix,
|
||||
distortionCoeff);
|
||||
|
||||
#if 0
|
||||
for (size_t row = 0; row < 2; ++row)
|
||||
{
|
||||
std::cout << "------ Row = " << row << " ------\n";
|
||||
for (size_t i = 0; i < 10+NUM_DIST_COEFF_TILT; ++i)
|
||||
{
|
||||
std::cout << i
|
||||
<< " jac = " << jacobian.at<double>(row,i)
|
||||
<< " num = " << numericJacobian.at<double>(row,i)
|
||||
<< " rel. diff = " << abs(numericJacobian.at<double>(row,i) - jacobian.at<double>(row,i))/abs(numericJacobian.at<double>(row,i))
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// relative difference for large values (rvec and tvec)
|
||||
cv::Mat check = abs(jacobian(cv::Range::all(), cv::Range(0,6)) - numericJacobian(cv::Range::all(), cv::Range(0,6)))/
|
||||
(1 + abs(jacobian(cv::Range::all(), cv::Range(0,6))));
|
||||
double minVal, maxVal;
|
||||
cv::minMaxIdx(check, &minVal, &maxVal);
|
||||
EXPECT_LE(maxVal, .01);
|
||||
// absolute difference for distortion and camera matrix
|
||||
EXPECT_MAT_NEAR(jacobian(cv::Range::all(), cv::Range(6,numParams)), numericJacobian(cv::Range::all(), cv::Range(6,numParams)), 1e-5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(cameraCalibrationTiltTest, undistortPoints)
|
||||
{
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> coeffNoiseHalfWidth(
|
||||
.2, .1, // k1 k2
|
||||
.01, .01, // p1 p2
|
||||
.01, .01, .01, .01, // k3 k4 k5 k6
|
||||
.001, .001, .001, .001, // s1 s2 s3 s4
|
||||
.001, .001); // tauX tauY
|
||||
double step = 99;
|
||||
double toleranceBackProjection = 1e-5;
|
||||
|
||||
for (size_t numTest = 0; numTest < 10; ++numTest)
|
||||
{
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> distortionCoeff = m_distortionCoeff;
|
||||
randomDistortionCoeff(distortionCoeff, coeffNoiseHalfWidth);
|
||||
|
||||
// distorted points
|
||||
std::vector<cv::Point2d> distorted;
|
||||
for (double x = 0; x <= m_imageSize.width-1; x += step)
|
||||
for (double y = 0; y <= m_imageSize.height-1; y += step)
|
||||
distorted.push_back(cv::Point2d(x,y));
|
||||
std::vector<cv::Point2d> normalizedUndistorted;
|
||||
|
||||
// undistort
|
||||
cv::undistortPoints(distorted,
|
||||
normalizedUndistorted,
|
||||
m_cameraMatrix,
|
||||
distortionCoeff);
|
||||
|
||||
// copy normalized points to 3D
|
||||
std::vector<cv::Point3d> objectPoints;
|
||||
for (std::vector<cv::Point2d>::const_iterator itPnt = normalizedUndistorted.begin();
|
||||
itPnt != normalizedUndistorted.end(); ++itPnt)
|
||||
objectPoints.push_back(cv::Point3d(itPnt->x, itPnt->y, 1));
|
||||
|
||||
// project
|
||||
std::vector<cv::Point2d> imagePoints(objectPoints.size());
|
||||
cv::projectPoints(objectPoints,
|
||||
cv::Vec3d(0,0,0),
|
||||
cv::Vec3d(0,0,0),
|
||||
m_cameraMatrix,
|
||||
distortionCoeff,
|
||||
imagePoints);
|
||||
|
||||
EXPECT_MAT_NEAR(distorted, imagePoints, toleranceBackProjection);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename INPUT, typename ESTIMATE>
|
||||
void show(const std::string& name, const INPUT in, const ESTIMATE est)
|
||||
{
|
||||
std::cout << name << " = " << est << " (init = " << in
|
||||
<< ", diff = " << est-in << ")\n";
|
||||
}
|
||||
|
||||
template <typename INPUT>
|
||||
void showVec(const std::string& name, const INPUT& in, const cv::Mat& est)
|
||||
{
|
||||
|
||||
for (size_t i = 0; i < in.channels; ++i)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << name << "[" << i << "]";
|
||||
show(ss.str(), in(i), est.at<double>(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
For given camera matrix and distortion coefficients
|
||||
- project point target in different positions onto the sensor
|
||||
- add pixel noise
|
||||
- estimate camera model with noisy measurements
|
||||
- compare result with initial model parameter
|
||||
|
||||
Parameter are differently affected by the noise
|
||||
*/
|
||||
TEST_F(cameraCalibrationTiltTest, calibrateCamera)
|
||||
{
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> coeffNoiseHalfWidth(
|
||||
.2, .1, // k1 k2
|
||||
.01, .01, // p1 p2
|
||||
0, 0, 0, 0, // k3 k4 k5 k6
|
||||
.001, .001, .001, .001, // s1 s2 s3 s4
|
||||
.001, .001); // tauX tauY
|
||||
double pixelNoiseHalfWidth = .5;
|
||||
std::vector<cv::Point3f> pointTarget;
|
||||
pointTarget.reserve(m_pointTarget.size());
|
||||
for (std::vector<cv::Point3d>::const_iterator it = m_pointTarget.begin(); it != m_pointTarget.end(); ++it)
|
||||
pointTarget.push_back(cv::Point3f(
|
||||
(float)(it->x),
|
||||
(float)(it->y),
|
||||
(float)(it->z)));
|
||||
|
||||
for (size_t numTest = 0; numTest < 5; ++numTest)
|
||||
{
|
||||
// create random distortion coefficients
|
||||
cv::Vec<double, NUM_DIST_COEFF_TILT> distortionCoeff = m_distortionCoeff;
|
||||
randomDistortionCoeff(distortionCoeff, coeffNoiseHalfWidth);
|
||||
|
||||
// container for calibration data
|
||||
std::vector<std::vector<cv::Point3f> > viewsObjectPoints;
|
||||
std::vector<std::vector<cv::Point2f> > viewsImagePoints;
|
||||
std::vector<std::vector<cv::Point2f> > viewsNoisyImagePoints;
|
||||
|
||||
// simulate calibration data with projectPoints
|
||||
std::vector<cv::Vec3d>::const_iterator itRvec = m_pointTargetRvec.begin();
|
||||
std::vector<cv::Vec3d>::const_iterator itTvec = m_pointTargetTvec.begin();
|
||||
// loop over different views
|
||||
for (;itRvec != m_pointTargetRvec.end(); ++ itRvec, ++itTvec)
|
||||
{
|
||||
std::vector<cv::Point3f> objectPoints(pointTarget);
|
||||
std::vector<cv::Point2f> imagePoints;
|
||||
std::vector<cv::Point2f> noisyImagePoints;
|
||||
// project calibration target to sensor
|
||||
cv::projectPoints(
|
||||
objectPoints,
|
||||
*itRvec,
|
||||
*itTvec,
|
||||
m_cameraMatrix,
|
||||
distortionCoeff,
|
||||
imagePoints);
|
||||
// remove invisible points
|
||||
addNoiseRemoveInvalidPoints(
|
||||
imagePoints,
|
||||
objectPoints,
|
||||
noisyImagePoints,
|
||||
pixelNoiseHalfWidth);
|
||||
// add data for view
|
||||
viewsNoisyImagePoints.push_back(noisyImagePoints);
|
||||
viewsImagePoints.push_back(imagePoints);
|
||||
viewsObjectPoints.push_back(objectPoints);
|
||||
}
|
||||
|
||||
// Output
|
||||
std::vector<cv::Mat> outRvecs, outTvecs;
|
||||
cv::Mat outCameraMatrix(3, 3, CV_64F, cv::Scalar::all(1)), outDistCoeff;
|
||||
|
||||
// Stopping criteria
|
||||
cv::TermCriteria stop(
|
||||
cv::TermCriteria::COUNT+cv::TermCriteria::EPS,
|
||||
50000,
|
||||
1e-14);
|
||||
// model choice
|
||||
int flag =
|
||||
cv::CALIB_FIX_ASPECT_RATIO |
|
||||
// cv::CALIB_RATIONAL_MODEL |
|
||||
cv::CALIB_FIX_K3 |
|
||||
// cv::CALIB_FIX_K6 |
|
||||
cv::CALIB_THIN_PRISM_MODEL |
|
||||
cv::CALIB_TILTED_MODEL;
|
||||
// estimate
|
||||
double backProjErr = cv::calibrateCamera(
|
||||
viewsObjectPoints,
|
||||
viewsNoisyImagePoints,
|
||||
m_imageSize,
|
||||
outCameraMatrix,
|
||||
outDistCoeff,
|
||||
outRvecs,
|
||||
outTvecs,
|
||||
flag,
|
||||
stop);
|
||||
|
||||
EXPECT_LE(backProjErr, pixelNoiseHalfWidth);
|
||||
|
||||
#if 0
|
||||
std::cout << "------ estimate ------\n";
|
||||
std::cout << "back projection error = " << backProjErr << "\n";
|
||||
std::cout << "points per view = {" << viewsObjectPoints.front().size();
|
||||
for (size_t i = 1; i < viewsObjectPoints.size(); ++i)
|
||||
std::cout << ", " << viewsObjectPoints[i].size();
|
||||
std::cout << "}\n";
|
||||
show("fx", m_cameraMatrix(0,0), outCameraMatrix.at<double>(0,0));
|
||||
show("fy", m_cameraMatrix(1,1), outCameraMatrix.at<double>(1,1));
|
||||
show("cx", m_cameraMatrix(0,2), outCameraMatrix.at<double>(0,2));
|
||||
show("cy", m_cameraMatrix(1,2), outCameraMatrix.at<double>(1,2));
|
||||
showVec("distor", distortionCoeff, outDistCoeff);
|
||||
#endif
|
||||
if (pixelNoiseHalfWidth > 0)
|
||||
{
|
||||
double tolRvec = pixelNoiseHalfWidth;
|
||||
double tolTvec = m_objectDistance * tolRvec;
|
||||
// back projection error
|
||||
for (size_t i = 0; i < viewsNoisyImagePoints.size(); ++i)
|
||||
{
|
||||
double dRvec = cv::norm(m_pointTargetRvec[i],
|
||||
cv::Vec3d(outRvecs[i].at<double>(0), outRvecs[i].at<double>(1), outRvecs[i].at<double>(2))
|
||||
);
|
||||
EXPECT_LE(dRvec, tolRvec);
|
||||
double dTvec = cv::norm(m_pointTargetTvec[i],
|
||||
cv::Vec3d(outTvecs[i].at<double>(0), outTvecs[i].at<double>(1), outTvecs[i].at<double>(2))
|
||||
);
|
||||
EXPECT_LE(dTvec, tolTvec);
|
||||
|
||||
std::vector<cv::Point2f> backProjection;
|
||||
cv::projectPoints(
|
||||
viewsObjectPoints[i],
|
||||
outRvecs[i],
|
||||
outTvecs[i],
|
||||
outCameraMatrix,
|
||||
outDistCoeff,
|
||||
backProjection);
|
||||
EXPECT_MAT_NEAR(backProjection, viewsNoisyImagePoints[i], 1.5*pixelNoiseHalfWidth);
|
||||
EXPECT_MAT_NEAR(backProjection, viewsImagePoints[i], 1.5*pixelNoiseHalfWidth);
|
||||
}
|
||||
}
|
||||
pixelNoiseHalfWidth *= .25;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,331 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
ChessBoardGenerator::ChessBoardGenerator(const Size& _patternSize) : sensorWidth(32), sensorHeight(24),
|
||||
squareEdgePointsNum(200), min_cos(std::sqrt(3.f)*0.5f), cov(0.5),
|
||||
patternSize(_patternSize), rendererResolutionMultiplier(4), tvec(Mat::zeros(1, 3, CV_32F))
|
||||
{
|
||||
rvec.create(3, 1, CV_32F);
|
||||
Rodrigues(Mat::eye(3, 3, CV_32F), rvec);
|
||||
}
|
||||
|
||||
void ChessBoardGenerator::generateEdge(const Point3f& p1, const Point3f& p2, vector<Point3f>& out) const
|
||||
{
|
||||
Point3f step = (p2 - p1) * (1.f/squareEdgePointsNum);
|
||||
for(size_t n = 0; n < squareEdgePointsNum; ++n)
|
||||
out.push_back( p1 + step * (float)n);
|
||||
}
|
||||
|
||||
Size ChessBoardGenerator::cornersSize() const
|
||||
{
|
||||
return Size(patternSize.width-1, patternSize.height-1);
|
||||
}
|
||||
|
||||
struct Mult
|
||||
{
|
||||
float m;
|
||||
Mult(int mult) : m((float)mult) {}
|
||||
Point2f operator()(const Point2f& p)const { return p * m; }
|
||||
};
|
||||
|
||||
void ChessBoardGenerator::generateBasis(Point3f& pb1, Point3f& pb2) const
|
||||
{
|
||||
RNG& rng = theRNG();
|
||||
|
||||
Vec3f n;
|
||||
for(;;)
|
||||
{
|
||||
n[0] = rng.uniform(-1.f, 1.f);
|
||||
n[1] = rng.uniform(-1.f, 1.f);
|
||||
n[2] = rng.uniform(0.0f, 1.f);
|
||||
float len = (float)norm(n);
|
||||
if (len < 1e-3)
|
||||
continue;
|
||||
n[0]/=len;
|
||||
n[1]/=len;
|
||||
n[2]/=len;
|
||||
|
||||
if (n[2] > min_cos)
|
||||
break;
|
||||
}
|
||||
|
||||
Vec3f n_temp = n; n_temp[0] += 100;
|
||||
Vec3f b1 = n.cross(n_temp);
|
||||
Vec3f b2 = n.cross(b1);
|
||||
float len_b1 = (float)norm(b1);
|
||||
float len_b2 = (float)norm(b2);
|
||||
|
||||
pb1 = Point3f(b1[0]/len_b1, b1[1]/len_b1, b1[2]/len_b1);
|
||||
pb2 = Point3f(b2[0]/len_b1, b2[1]/len_b2, b2[2]/len_b2);
|
||||
}
|
||||
|
||||
|
||||
Mat ChessBoardGenerator::generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
|
||||
float sqWidth, float sqHeight, const vector<Point3f>& whole,
|
||||
vector<Point2f>& corners) const
|
||||
{
|
||||
vector< vector<Point> > squares_black;
|
||||
for(int i = 0; i < patternSize.width; ++i)
|
||||
for(int j = 0; j < patternSize.height; ++j)
|
||||
if ( (i % 2 == 0 && j % 2 == 0) || (i % 2 != 0 && j % 2 != 0) )
|
||||
{
|
||||
vector<Point3f> pts_square3d;
|
||||
vector<Point2f> pts_square2d;
|
||||
|
||||
Point3f p1 = zero + (i + 0) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
|
||||
Point3f p2 = zero + (i + 1) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
|
||||
Point3f p3 = zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
|
||||
Point3f p4 = zero + (i + 0) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
|
||||
generateEdge(p1, p2, pts_square3d);
|
||||
generateEdge(p2, p3, pts_square3d);
|
||||
generateEdge(p3, p4, pts_square3d);
|
||||
generateEdge(p4, p1, pts_square3d);
|
||||
|
||||
projectPoints(pts_square3d, rvec, tvec, camMat, distCoeffs, pts_square2d);
|
||||
squares_black.resize(squares_black.size() + 1);
|
||||
vector<Point2f> temp;
|
||||
approxPolyDP(pts_square2d, temp, 1.0, true);
|
||||
transform(temp.begin(), temp.end(), back_inserter(squares_black.back()), Mult(rendererResolutionMultiplier));
|
||||
}
|
||||
|
||||
/* calculate corners */
|
||||
corners3d.clear();
|
||||
for(int j = 0; j < patternSize.height - 1; ++j)
|
||||
for(int i = 0; i < patternSize.width - 1; ++i)
|
||||
corners3d.push_back(zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2);
|
||||
corners.clear();
|
||||
projectPoints(corners3d, rvec, tvec, camMat, distCoeffs, corners);
|
||||
|
||||
vector<Point3f> whole3d;
|
||||
vector<Point2f> whole2d;
|
||||
generateEdge(whole[0], whole[1], whole3d);
|
||||
generateEdge(whole[1], whole[2], whole3d);
|
||||
generateEdge(whole[2], whole[3], whole3d);
|
||||
generateEdge(whole[3], whole[0], whole3d);
|
||||
projectPoints(whole3d, rvec, tvec, camMat, distCoeffs, whole2d);
|
||||
vector<Point2f> temp_whole2d;
|
||||
approxPolyDP(whole2d, temp_whole2d, 1.0, true);
|
||||
|
||||
vector< vector<Point > > whole_contour(1);
|
||||
transform(temp_whole2d.begin(), temp_whole2d.end(),
|
||||
back_inserter(whole_contour.front()), Mult(rendererResolutionMultiplier));
|
||||
|
||||
Mat result;
|
||||
if (rendererResolutionMultiplier == 1)
|
||||
{
|
||||
result = bg.clone();
|
||||
drawContours(result, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
|
||||
drawContours(result, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat tmp;
|
||||
resize(bg, tmp, bg.size() * rendererResolutionMultiplier, 0, 0, INTER_LINEAR_EXACT);
|
||||
drawContours(tmp, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
|
||||
drawContours(tmp, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
|
||||
resize(tmp, result, bg.size(), 0, 0, INTER_AREA);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
double fovx, fovy, focalLen;
|
||||
Point2d principalPoint;
|
||||
double aspect;
|
||||
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
|
||||
fovx, fovy, focalLen, principalPoint, aspect);
|
||||
|
||||
RNG& rng = theRNG();
|
||||
|
||||
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
|
||||
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
|
||||
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
|
||||
|
||||
Point3f p;
|
||||
p.z = std::cos(ah) * d1;
|
||||
p.x = std::sin(ah) * d1;
|
||||
p.y = p.z * std::tan(av);
|
||||
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = static_cast<float>(norm(p) * std::sin( std::min(fovx, fovy) * 0.5 * CV_PI / 180));
|
||||
float cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
for(;;)
|
||||
{
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
|
||||
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
|
||||
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
|
||||
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
|
||||
|
||||
if (inrect1 && inrect2 && inrect3 && inrect4)
|
||||
break;
|
||||
|
||||
cbHalfWidth*=0.8f;
|
||||
cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
|
||||
|
||||
cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
}
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
float sqWidth = 2 * cbHalfWidth/patternSize.width;
|
||||
float sqHeight = 2 * cbHalfHeight/patternSize.height;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2, sqWidth, sqHeight, pts3d, corners);
|
||||
}
|
||||
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Size2f& squareSize, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
double fovx, fovy, focalLen;
|
||||
Point2d principalPoint;
|
||||
double aspect;
|
||||
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
|
||||
fovx, fovy, focalLen, principalPoint, aspect);
|
||||
|
||||
RNG& rng = theRNG();
|
||||
|
||||
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
|
||||
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
|
||||
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
|
||||
|
||||
Point3f p;
|
||||
p.z = std::cos(ah) * d1;
|
||||
p.x = std::sin(ah) * d1;
|
||||
p.y = p.z * std::tan(av);
|
||||
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
|
||||
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
for(;;)
|
||||
{
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
|
||||
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
|
||||
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
|
||||
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
|
||||
|
||||
if ( inrect1 && inrect2 && inrect3 && inrect4)
|
||||
break;
|
||||
|
||||
p.z *= 1.1f;
|
||||
}
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
|
||||
squareSize.width, squareSize.height, pts3d, corners);
|
||||
}
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Size2f& squareSize, const Point3f& pos, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
Point3f p = pos;
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
|
||||
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
|
||||
squareSize.width, squareSize.height, pts3d, corners);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#ifndef CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
|
||||
#define CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
using std::vector;
|
||||
|
||||
class ChessBoardGenerator
|
||||
{
|
||||
public:
|
||||
double sensorWidth;
|
||||
double sensorHeight;
|
||||
size_t squareEdgePointsNum;
|
||||
double min_cos;
|
||||
mutable double cov;
|
||||
Size patternSize;
|
||||
int rendererResolutionMultiplier;
|
||||
|
||||
ChessBoardGenerator(const Size& patternSize = Size(8, 6));
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, std::vector<Point2f>& corners) const;
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, std::vector<Point2f>& corners) const;
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, const Point3f& pos, std::vector<Point2f>& corners) const;
|
||||
Size cornersSize() const;
|
||||
|
||||
mutable std::vector<Point3f> corners3d;
|
||||
private:
|
||||
void generateEdge(const Point3f& p1, const Point3f& p2, std::vector<Point3f>& out) const;
|
||||
Mat generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
|
||||
float sqWidth, float sqHeight, const std::vector<Point3f>& whole, std::vector<Point2f>& corners) const;
|
||||
void generateBasis(Point3f& pb1, Point3f& pb2) const;
|
||||
|
||||
Mat rvec, tvec;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,767 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include <opencv2/ts/cuda_test.hpp> // EXPECT_MAT_NEAR
|
||||
#include "../src/fisheye.hpp"
|
||||
#include "opencv2/videoio.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class fisheyeTest : public ::testing::Test {
|
||||
|
||||
protected:
|
||||
const static cv::Size imageSize;
|
||||
const static cv::Matx33d K;
|
||||
const static cv::Vec4d D;
|
||||
const static cv::Matx33d R;
|
||||
const static cv::Vec3d T;
|
||||
std::string datasets_repository_path;
|
||||
|
||||
virtual void SetUp() {
|
||||
datasets_repository_path = combine(cvtest::TS::ptr()->get_data_path(), "cv/cameracalibration/fisheye");
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string combine(const std::string& _item1, const std::string& _item2);
|
||||
};
|
||||
|
||||
const cv::Size fisheyeTest::imageSize(1280, 800);
|
||||
|
||||
const cv::Matx33d fisheyeTest::K(558.478087865323, 0, 620.458515360843,
|
||||
0, 560.506767351568, 381.939424848348,
|
||||
0, 0, 1);
|
||||
|
||||
const cv::Vec4d fisheyeTest::D(-0.0014613319981768, -0.00329861110580401, 0.00605760088590183, -0.00374209380722371);
|
||||
|
||||
|
||||
const cv::Matx33d fisheyeTest::R ( 9.9756700084424932e-01, 6.9698277640183867e-02, 1.4929569991321144e-03,
|
||||
-6.9711825162322980e-02, 9.9748249845531767e-01, 1.2997180766418455e-02,
|
||||
-5.8331736398316541e-04,-1.3069635393884985e-02, 9.9991441852366736e-01);
|
||||
|
||||
const cv::Vec3d fisheyeTest::T(-9.9217369356044638e-02, 3.1741831972356663e-03, 1.8551007952921010e-04);
|
||||
|
||||
std::string fisheyeTest::combine(const std::string& _item1, const std::string& _item2)
|
||||
{
|
||||
std::string item1 = _item1, item2 = _item2;
|
||||
std::replace(item1.begin(), item1.end(), '\\', '/');
|
||||
std::replace(item2.begin(), item2.end(), '\\', '/');
|
||||
|
||||
if (item1.empty())
|
||||
return item2;
|
||||
|
||||
if (item2.empty())
|
||||
return item1;
|
||||
|
||||
char last = item1[item1.size()-1];
|
||||
return item1 + (last != '/' ? "/" : "") + item2;
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, Calibration)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const cv::Matx33d goldK(558.4780870585967, 0, 620.4585053962692,
|
||||
0, 560.5067667343917, 381.9394122875291,
|
||||
0, 0, 1);
|
||||
const cv::Vec4d goldD(-0.00146136, -0.00329847, 0.00605742, -0.00374201);
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > imagePoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> imagePoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
cv::Matx33d theK;
|
||||
cv::Vec4d theD;
|
||||
|
||||
cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, theK, theD,
|
||||
cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6));
|
||||
|
||||
EXPECT_MAT_NEAR(theK, goldK, 1e-8);
|
||||
EXPECT_MAT_NEAR(theD, goldD, 1e-8);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, CalibrationWithFixedFocalLength)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > imagePoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> imagePoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
flag |= cv::CALIB_FIX_FOCAL_LENGTH;
|
||||
flag |= cv::CALIB_USE_INTRINSIC_GUESS;
|
||||
|
||||
cv::Matx33d theK = this->K;
|
||||
const cv::Matx33d newK(
|
||||
558.478088, 0.000000, 620.458461,
|
||||
0.000000, 560.506767, 381.939362,
|
||||
0.000000, 0.000000, 1.000000);
|
||||
|
||||
cv::Vec4d theD;
|
||||
const cv::Vec4d newD(-0.001461, -0.003298, 0.006057, -0.003742);
|
||||
|
||||
cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, theK, theD,
|
||||
cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6));
|
||||
|
||||
// ensure that CALIB_FIX_FOCAL_LENGTH works and focal length has not changed
|
||||
EXPECT_EQ(theK(0,0), K(0,0));
|
||||
EXPECT_EQ(theK(1,1), K(1,1));
|
||||
|
||||
EXPECT_MAT_NEAR(theK, newK, 1e-6);
|
||||
EXPECT_MAT_NEAR(theD, newD, 1e-6);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, Homography)
|
||||
{
|
||||
const int n_images = 1;
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > imagePoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> imagePoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
cv::internal::IntrinsicParams param;
|
||||
param.Init(cv::Vec2d(cv::max(imageSize.width, imageSize.height) / CV_PI, cv::max(imageSize.width, imageSize.height) / CV_PI),
|
||||
cv::Vec2d(imageSize.width / 2.0 - 0.5, imageSize.height / 2.0 - 0.5));
|
||||
|
||||
cv::Mat _imagePoints (imagePoints[0]);
|
||||
cv::Mat _objectPoints(objectPoints[0]);
|
||||
|
||||
cv::Mat imagePointsNormalized = NormalizePixels(_imagePoints, param).reshape(1).t();
|
||||
_objectPoints = _objectPoints.reshape(1, (int)_objectPoints.total()).t();
|
||||
cv::Mat objectPointsMean, covObjectPoints;
|
||||
|
||||
int Np = imagePointsNormalized.cols;
|
||||
cv::calcCovarMatrix(_objectPoints, covObjectPoints, objectPointsMean, cv::COVAR_NORMAL | cv::COVAR_COLS);
|
||||
cv::SVD svd(covObjectPoints);
|
||||
cv::Mat theR(svd.vt);
|
||||
|
||||
if (cv::norm(theR(cv::Rect(2, 0, 1, 2))) < 1e-6)
|
||||
theR = cv::Mat::eye(3,3, CV_64FC1);
|
||||
if (cv::determinant(theR) < 0)
|
||||
theR = -theR;
|
||||
|
||||
cv::Mat theT = -theR * objectPointsMean;
|
||||
cv::Mat X_new = theR * _objectPoints + theT * cv::Mat::ones(1, Np, CV_64FC1);
|
||||
cv::Mat H = cv::internal::ComputeHomography(imagePointsNormalized, X_new.rowRange(0, 2));
|
||||
|
||||
cv::Mat M = cv::Mat::ones(3, X_new.cols, CV_64FC1);
|
||||
X_new.rowRange(0, 2).copyTo(M.rowRange(0, 2));
|
||||
cv::Mat mrep = H * M;
|
||||
|
||||
cv::divide(mrep, cv::Mat::ones(3,1, CV_64FC1) * mrep.row(2).clone(), mrep);
|
||||
|
||||
cv::Mat merr = (mrep.rowRange(0, 2) - imagePointsNormalized).t();
|
||||
|
||||
cv::Vec2d std_err;
|
||||
cv::meanStdDev(merr.reshape(2), cv::noArray(), std_err);
|
||||
std_err *= sqrt((double)merr.reshape(2).total() / (merr.reshape(2).total() - 1));
|
||||
|
||||
cv::Vec2d correct_std_err(0.00516740156010384, 0.00644205331553901);
|
||||
EXPECT_MAT_NEAR(std_err, correct_std_err, 1e-12);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, EstimateUncertainties)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > imagePoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> imagePoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
cv::Matx33d theK;
|
||||
cv::Vec4d theD;
|
||||
std::vector<cv::Vec3d> rvec;
|
||||
std::vector<cv::Vec3d> tvec;
|
||||
|
||||
cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, theK, theD,
|
||||
rvec, tvec, flag, cv::TermCriteria(3, 20, 1e-6));
|
||||
|
||||
cv::internal::IntrinsicParams param, errors;
|
||||
cv::Vec2d err_std;
|
||||
double thresh_cond = 1e6;
|
||||
int check_cond = 1;
|
||||
param.Init(cv::Vec2d(theK(0,0), theK(1,1)), cv::Vec2d(theK(0,2), theK(1, 2)), theD);
|
||||
param.isEstimate = std::vector<uchar>(9, 1);
|
||||
param.isEstimate[4] = 0;
|
||||
|
||||
errors.isEstimate = param.isEstimate;
|
||||
|
||||
double rms;
|
||||
|
||||
cv::internal::EstimateUncertainties(objectPoints, imagePoints, param, rvec, tvec,
|
||||
errors, err_std, thresh_cond, check_cond, rms);
|
||||
|
||||
EXPECT_MAT_NEAR(errors.f, cv::Vec2d(1.34250246865020720, 1.36037536429654530), 1e-6);
|
||||
EXPECT_MAT_NEAR(errors.c, cv::Vec2d(0.92070526160049848, 0.84383585812851514), 1e-6);
|
||||
EXPECT_MAT_NEAR(errors.k, cv::Vec4d(0.0053379581373996041, 0.017389792901700545, 0.022036256089491224, 0.0094714594258908952), 1e-7);
|
||||
EXPECT_MAT_NEAR(err_std, cv::Vec2d(0.187475975266883, 0.185678953263995), 1e-7);
|
||||
CV_Assert(fabs(rms - 0.263782587133546) < 1e-10);
|
||||
CV_Assert(errors.alpha == 0);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, stereoCalibrate)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > leftPoints(n_images);
|
||||
std::vector<std::vector<cv::Point2d> > rightPoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> leftPoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_right.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_right[cv::format("image_%d", i )] >> rightPoints[i];
|
||||
fs_right.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
cv::Matx33d K1, K2, theR;
|
||||
cv::Vec3d theT;
|
||||
cv::Vec4d D1, D2;
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints,
|
||||
K1, D1, K2, D2, imageSize, theR, theT, flag,
|
||||
cv::TermCriteria(3, 12, 0));
|
||||
|
||||
cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523,
|
||||
-0.06956823121068059, 0.9975601387249519, 0.005833595226966235,
|
||||
-0.006071257768382089, -0.006271040135405457, 0.9999619062167968);
|
||||
cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699);
|
||||
cv::Matx33d K1_correct (561.195925927249, 0, 621.282400272412,
|
||||
0, 562.849402029712, 380.555455380889,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Matx33d K2_correct (560.395452535348, 0, 678.971652040359,
|
||||
0, 561.90171021422, 380.401340535339,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Vec4d D1_correct (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771);
|
||||
cv::Vec4d D2_correct (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222);
|
||||
|
||||
EXPECT_MAT_NEAR(theR, R_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(theT, T_correct, 1e-10);
|
||||
|
||||
EXPECT_MAT_NEAR(K1, K1_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(K2, K2_correct, 1e-10);
|
||||
|
||||
EXPECT_MAT_NEAR(D1, D1_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(D2, D2_correct, 1e-10);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, stereoCalibrateFixIntrinsic)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > leftPoints(n_images);
|
||||
std::vector<std::vector<cv::Point2d> > rightPoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> leftPoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_right.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_right[cv::format("image_%d", i )] >> rightPoints[i];
|
||||
fs_right.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
cv::Matx33d theR;
|
||||
cv::Vec3d theT;
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
flag |= cv::CALIB_FIX_INTRINSIC;
|
||||
|
||||
cv::Matx33d K1 (561.195925927249, 0, 621.282400272412,
|
||||
0, 562.849402029712, 380.555455380889,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Matx33d K2 (560.395452535348, 0, 678.971652040359,
|
||||
0, 561.90171021422, 380.401340535339,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Vec4d D1 (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771);
|
||||
cv::Vec4d D2 (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222);
|
||||
|
||||
cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints,
|
||||
K1, D1, K2, D2, imageSize, theR, theT, flag,
|
||||
cv::TermCriteria(3, 12, 0));
|
||||
|
||||
cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523,
|
||||
-0.06956823121068059, 0.9975601387249519, 0.005833595226966235,
|
||||
-0.006071257768382089, -0.006271040135405457, 0.9999619062167968);
|
||||
cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699);
|
||||
|
||||
|
||||
EXPECT_MAT_NEAR(theR, R_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(theT, T_correct, 1e-10);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, CalibrationWithDifferentPointsNumber)
|
||||
{
|
||||
const int n_images = 2;
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > imagePoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
std::vector<cv::Point2d> imgPoints1(10);
|
||||
std::vector<cv::Point2d> imgPoints2(15);
|
||||
|
||||
std::vector<cv::Point3d> objectPoints1(imgPoints1.size());
|
||||
std::vector<cv::Point3d> objectPoints2(imgPoints2.size());
|
||||
|
||||
for (size_t i = 0; i < imgPoints1.size(); i++)
|
||||
{
|
||||
imgPoints1[i] = cv::Point2d((double)i, (double)i);
|
||||
objectPoints1[i] = cv::Point3d((double)i, (double)i, 10.0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < imgPoints2.size(); i++)
|
||||
{
|
||||
imgPoints2[i] = cv::Point2d(i + 0.5, i + 0.5);
|
||||
objectPoints2[i] = cv::Point3d(i + 0.5, i + 0.5, 10.0);
|
||||
}
|
||||
|
||||
imagePoints[0] = imgPoints1;
|
||||
imagePoints[1] = imgPoints2;
|
||||
objectPoints[0] = objectPoints1;
|
||||
objectPoints[1] = objectPoints2;
|
||||
|
||||
cv::Matx33d theK = cv::Matx33d::eye();
|
||||
cv::Vec4d theD;
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_USE_INTRINSIC_GUESS;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
cv::fisheye::calibrate(objectPoints, imagePoints, cv::Size(100, 100), theK, theD,
|
||||
cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(fisheyeTest, stereoCalibrateWithPerViewTransformations)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
|
||||
std::vector<std::vector<cv::Point2d> > leftPoints(n_images);
|
||||
std::vector<std::vector<cv::Point2d> > rightPoints(n_images);
|
||||
std::vector<std::vector<cv::Point3d> > objectPoints(n_images);
|
||||
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> leftPoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_right.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_right[cv::format("image_%d", i )] >> rightPoints[i];
|
||||
fs_right.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
cv::Matx33d K1, K2, theR;
|
||||
cv::Vec3d theT;
|
||||
cv::Vec4d D1, D2;
|
||||
|
||||
std::vector<cv::Mat> rvecs, tvecs;
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
double rmsErrorStereoCalib = cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints,
|
||||
K1, D1, K2, D2, imageSize, theR, theT, rvecs, tvecs, flag,
|
||||
cv::TermCriteria(3, 12, 0));
|
||||
|
||||
std::vector<cv::Point2d> reprojectedImgPts[2] = { std::vector<cv::Point2d>(n_images),
|
||||
std::vector<cv::Point2d>(n_images) };
|
||||
size_t totalPoints = 0;
|
||||
double totalMSError[2] = { 0, 0 };
|
||||
for( size_t i = 0; i < n_images; i++ )
|
||||
{
|
||||
cv::Matx33d viewRotMat1, viewRotMat2;
|
||||
cv::Vec3d viewT1, viewT2;
|
||||
cv::Mat rVec;
|
||||
cv::Rodrigues( rvecs[i], rVec );
|
||||
rVec.convertTo(viewRotMat1, CV_64F);
|
||||
tvecs[i].convertTo(viewT1, CV_64F);
|
||||
|
||||
viewRotMat2 = theR * viewRotMat1;
|
||||
cv::Vec3d T2t = theR * viewT1;
|
||||
viewT2 = T2t + theT;
|
||||
|
||||
cv::Vec3d viewRotVec1, viewRotVec2;
|
||||
cv::Rodrigues(viewRotMat1, viewRotVec1);
|
||||
cv::Rodrigues(viewRotMat2, viewRotVec2);
|
||||
|
||||
double alpha1 = K1(0, 1) / K1(0, 0);
|
||||
double alpha2 = K2(0, 1) / K2(0, 0);
|
||||
cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[0], viewRotVec1, viewT1, K1, D1, alpha1);
|
||||
cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[1], viewRotVec2, viewT2, K2, D2, alpha2);
|
||||
|
||||
double viewMSError[2] = {
|
||||
cv::norm(leftPoints[i], reprojectedImgPts[0], cv::NORM_L2SQR),
|
||||
cv::norm(rightPoints[i], reprojectedImgPts[1], cv::NORM_L2SQR)
|
||||
};
|
||||
|
||||
size_t n = objectPoints[i].size();
|
||||
totalMSError[0] += viewMSError[0];
|
||||
totalMSError[1] += viewMSError[1];
|
||||
totalPoints += n;
|
||||
}
|
||||
double rmsErrorFromReprojectedImgPts = std::sqrt((totalMSError[0] + totalMSError[1]) / (2 * totalPoints));
|
||||
|
||||
cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523,
|
||||
-0.06956823121068059, 0.9975601387249519, 0.005833595226966235,
|
||||
-0.006071257768382089, -0.006271040135405457, 0.9999619062167968);
|
||||
cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699);
|
||||
cv::Matx33d K1_correct (561.195925927249, 0, 621.282400272412,
|
||||
0, 562.849402029712, 380.555455380889,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Matx33d K2_correct (560.395452535348, 0, 678.971652040359,
|
||||
0, 561.90171021422, 380.401340535339,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Vec4d D1_correct (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771);
|
||||
cv::Vec4d D2_correct (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222);
|
||||
|
||||
EXPECT_MAT_NEAR(theR, R_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(theT, T_correct, 1e-10);
|
||||
|
||||
EXPECT_MAT_NEAR(K1, K1_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(K2, K2_correct, 1e-10);
|
||||
|
||||
EXPECT_MAT_NEAR(D1, D1_correct, 1e-10);
|
||||
EXPECT_MAT_NEAR(D2, D2_correct, 1e-10);
|
||||
|
||||
EXPECT_NEAR(rmsErrorStereoCalib, rmsErrorFromReprojectedImgPts, 1e-4);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, multiview_calibration)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
|
||||
std::vector<std::vector<cv::Point2f> > leftPoints(n_images);
|
||||
std::vector<std::vector<cv::Point2f> > rightPoints(n_images);
|
||||
std::vector<std::vector<cv::Point3f> > objectPoints(n_images);
|
||||
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> leftPoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_right.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_right[cv::format("image_%d", i )] >> rightPoints[i];
|
||||
fs_right.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
std::vector<std::vector<cv::Mat>> image_points_all(2, std::vector<cv::Mat>(leftPoints.size()));
|
||||
for (int i = 0; i < (int)leftPoints.size(); i++) {
|
||||
cv::Mat left_pts(leftPoints[i], false) , right_pts(rightPoints[i], false);
|
||||
left_pts.copyTo(image_points_all[0][i]);
|
||||
right_pts.copyTo(image_points_all[1][i]);
|
||||
}
|
||||
std::vector<cv::Size> image_sizes(2, imageSize);
|
||||
cv::Mat visibility_mat = cv::Mat_<uchar>::ones(2, (int)leftPoints.size());
|
||||
std::vector<cv::Mat> Rs, Ts, Ks, distortions;
|
||||
std::vector<uchar> models(2, cv::CALIB_MODEL_FISHEYE);
|
||||
std::vector<int> all_flags(2, cv::CALIB_RECOMPUTE_EXTRINSIC | cv::CALIB_CHECK_COND | cv::CALIB_FIX_SKEW);
|
||||
|
||||
calibrateMultiview(objectPoints, image_points_all, image_sizes, visibility_mat,
|
||||
models, Ks, distortions, Rs, Ts, all_flags);
|
||||
cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523,
|
||||
-0.06956823121068059, 0.9975601387249519, 0.005833595226966235,
|
||||
-0.006071257768382089, -0.006271040135405457, 0.9999619062167968);
|
||||
cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699);
|
||||
cv::Matx33d K1_correct (561.195925927249, 0, 621.282400272412,
|
||||
0, 562.849402029712, 380.555455380889,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Matx33d K2_correct (560.395452535348, 0, 678.971652040359,
|
||||
0, 561.90171021422, 380.401340535339,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Vec4d D1_correct (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771);
|
||||
cv::Vec4d D2_correct (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222);
|
||||
|
||||
cv::Mat theR;
|
||||
cv::Rodrigues(Rs[1], theR);
|
||||
|
||||
EXPECT_MAT_NEAR(theR, R_correct, 1e-2);
|
||||
EXPECT_MAT_NEAR(Ts[1], T_correct, 5e-3);
|
||||
|
||||
EXPECT_MAT_NEAR(Ks[0], K1_correct, 4);
|
||||
EXPECT_MAT_NEAR(Ks[1], K2_correct, 5);
|
||||
|
||||
EXPECT_MAT_NEAR(distortions[0], D1_correct, 1e-2);
|
||||
EXPECT_MAT_NEAR(distortions[1], D2_correct, 5e-2);
|
||||
}
|
||||
|
||||
TEST_F(fisheyeTest, cameraRegistrationWithPerViewTransformations)
|
||||
{
|
||||
const int n_images = 34;
|
||||
|
||||
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
|
||||
|
||||
std::vector<std::vector<cv::Point2f> > leftPoints(n_images);
|
||||
std::vector<std::vector<cv::Point2f> > rightPoints(n_images);
|
||||
std::vector<std::vector<cv::Point3f> > objectPoints(n_images);
|
||||
|
||||
cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_left.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_left[cv::format("image_%d", i )] >> leftPoints[i];
|
||||
fs_left.release();
|
||||
|
||||
cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_right.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_right[cv::format("image_%d", i )] >> rightPoints[i];
|
||||
fs_right.release();
|
||||
|
||||
cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ);
|
||||
CV_Assert(fs_object.isOpened());
|
||||
for(int i = 0; i < n_images; ++i)
|
||||
fs_object[cv::format("image_%d", i )] >> objectPoints[i];
|
||||
fs_object.release();
|
||||
|
||||
cv::Matx33d K1, K2, theR;
|
||||
cv::Vec3d theT;
|
||||
cv::Vec4d D1, D2;
|
||||
|
||||
int flag = 0;
|
||||
flag |= cv::CALIB_RECOMPUTE_EXTRINSIC;
|
||||
flag |= cv::CALIB_CHECK_COND;
|
||||
flag |= cv::CALIB_FIX_SKEW;
|
||||
|
||||
cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints,
|
||||
K1, D1, K2, D2, imageSize, theR, theT,flag, cv::TermCriteria(3, 12, 0));
|
||||
|
||||
cv::Mat E, F, perViewErrors;
|
||||
std::vector<cv::Mat> rvecs, tvecs;
|
||||
flag = 0;
|
||||
double rmsErrorRegisterCamera = cv::registerCameras(objectPoints, objectPoints, leftPoints, rightPoints,
|
||||
K1, D1, CALIB_MODEL_FISHEYE,
|
||||
K2, D2, CALIB_MODEL_FISHEYE,
|
||||
theR, theT, E, F, rvecs, tvecs, perViewErrors, flag,
|
||||
cv::TermCriteria(3, 12, 0));
|
||||
std::vector<cv::Point2f> reprojectedImgPts[2] = { std::vector<cv::Point2f>(n_images),
|
||||
std::vector<cv::Point2f>(n_images) };
|
||||
size_t totalPoints = 0;
|
||||
double totalMSError[2] = { 0, 0 };
|
||||
for( size_t i = 0; i < n_images; i++ )
|
||||
{
|
||||
cv::Matx33d viewRotMat1, viewRotMat2;
|
||||
cv::Vec3d viewT1, viewT2;
|
||||
cv::Mat rVec;
|
||||
cv::Rodrigues( rvecs[i], rVec );
|
||||
rVec.convertTo(viewRotMat1, CV_64F);
|
||||
tvecs[i].convertTo(viewT1, CV_64F);
|
||||
|
||||
viewRotMat2 = theR * viewRotMat1;
|
||||
cv::Vec3d T2t = theR * viewT1;
|
||||
viewT2 = T2t + theT;
|
||||
|
||||
cv::Vec3d viewRotVec1, viewRotVec2;
|
||||
cv::Rodrigues(viewRotMat1, viewRotVec1);
|
||||
cv::Rodrigues(viewRotMat2, viewRotVec2);
|
||||
|
||||
double alpha1 = K1(0, 1) / K1(0, 0);
|
||||
double alpha2 = K2(0, 1) / K2(0, 0);
|
||||
cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[0], viewRotVec1, viewT1, K1, D1, alpha1);
|
||||
cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[1], viewRotVec2, viewT2, K2, D2, alpha2);
|
||||
|
||||
double viewMSError[2] = {
|
||||
cv::norm(leftPoints[i], reprojectedImgPts[0], cv::NORM_L2SQR),
|
||||
cv::norm(rightPoints[i], reprojectedImgPts[1], cv::NORM_L2SQR)
|
||||
};
|
||||
|
||||
size_t n = objectPoints[i].size();
|
||||
totalMSError[0] += viewMSError[0];
|
||||
totalMSError[1] += viewMSError[1];
|
||||
totalPoints += n;
|
||||
}
|
||||
|
||||
double rmsErrorFromReprojectedImgPts = std::sqrt((totalMSError[0] + totalMSError[1]) / (2 * totalPoints));
|
||||
|
||||
cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523,
|
||||
-0.06956823121068059, 0.9975601387249519, 0.005833595226966235,
|
||||
-0.006071257768382089, -0.006271040135405457, 0.9999619062167968);
|
||||
cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699);
|
||||
cv::Matx33d K1_correct (561.195925927249, 0, 621.282400272412,
|
||||
0, 562.849402029712, 380.555455380889,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Matx33d K2_correct (560.395452535348, 0, 678.971652040359,
|
||||
0, 561.90171021422, 380.401340535339,
|
||||
0, 0, 1);
|
||||
|
||||
cv::Vec4d D1_correct (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771);
|
||||
cv::Vec4d D2_correct (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222);
|
||||
|
||||
EXPECT_MAT_NEAR(theR, R_correct, 1e-6);
|
||||
EXPECT_MAT_NEAR(theT, T_correct, 1e-6);
|
||||
|
||||
EXPECT_MAT_NEAR(K1, K1_correct, 1e-4);
|
||||
EXPECT_MAT_NEAR(K2, K2_correct, 1e-4);
|
||||
|
||||
EXPECT_MAT_NEAR(D1, D1_correct, 1e-5);
|
||||
EXPECT_MAT_NEAR(D2, D2_correct, 1e-5);
|
||||
|
||||
EXPECT_NEAR(rmsErrorRegisterCamera, rmsErrorFromReprojectedImgPts, 1e-4);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
CV_TEST_MAIN("")
|
||||
@@ -0,0 +1,231 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#if 0
|
||||
#include "_modelest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
class BareModelEstimator : public CvModelEstimator2
|
||||
{
|
||||
public:
|
||||
BareModelEstimator(int modelPoints, CvSize modelSize, int maxBasicSolutions);
|
||||
|
||||
virtual int runKernel( const CvMat*, const CvMat*, CvMat* );
|
||||
virtual void computeReprojError( const CvMat*, const CvMat*,
|
||||
const CvMat*, CvMat* );
|
||||
|
||||
bool checkSubsetPublic( const CvMat* ms1, int count, bool checkPartialSubset );
|
||||
};
|
||||
|
||||
BareModelEstimator::BareModelEstimator(int _modelPoints, CvSize _modelSize, int _maxBasicSolutions)
|
||||
:CvModelEstimator2(_modelPoints, _modelSize, _maxBasicSolutions)
|
||||
{
|
||||
}
|
||||
|
||||
int BareModelEstimator::runKernel( const CvMat*, const CvMat*, CvMat* )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BareModelEstimator::computeReprojError( const CvMat*, const CvMat*,
|
||||
const CvMat*, CvMat* )
|
||||
{
|
||||
}
|
||||
|
||||
bool BareModelEstimator::checkSubsetPublic( const CvMat* ms1, int count, bool checkPartialSubset )
|
||||
{
|
||||
checkPartialSubsets = checkPartialSubset;
|
||||
return checkSubset(ms1, count);
|
||||
}
|
||||
|
||||
class CV_ModelEstimator2_Test : public cvtest::ArrayTest
|
||||
{
|
||||
public:
|
||||
CV_ModelEstimator2_Test();
|
||||
|
||||
protected:
|
||||
void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
|
||||
void fill_array( int test_case_idx, int i, int j, Mat& arr );
|
||||
double get_success_error_level( int test_case_idx, int i, int j );
|
||||
void run_func();
|
||||
void prepare_to_validation( int test_case_idx );
|
||||
|
||||
bool checkPartialSubsets;
|
||||
int usedPointsCount;
|
||||
|
||||
bool checkSubsetResult;
|
||||
int generalPositionsCount;
|
||||
int maxPointsCount;
|
||||
};
|
||||
|
||||
CV_ModelEstimator2_Test::CV_ModelEstimator2_Test()
|
||||
{
|
||||
generalPositionsCount = get_test_case_count() / 2;
|
||||
maxPointsCount = 100;
|
||||
|
||||
test_array[INPUT].push_back(NULL);
|
||||
test_array[OUTPUT].push_back(NULL);
|
||||
test_array[REF_OUTPUT].push_back(NULL);
|
||||
}
|
||||
|
||||
void CV_ModelEstimator2_Test::get_test_array_types_and_sizes( int /*test_case_idx*/,
|
||||
vector<vector<Size> > &sizes, vector<vector<int> > &types )
|
||||
{
|
||||
RNG &rng = ts->get_rng();
|
||||
checkPartialSubsets = (cvtest::randInt(rng) % 2 == 0);
|
||||
|
||||
int pointsCount = cvtest::randInt(rng) % maxPointsCount;
|
||||
usedPointsCount = pointsCount == 0 ? 0 : cvtest::randInt(rng) % pointsCount;
|
||||
|
||||
sizes[INPUT][0] = cvSize(1, pointsCount);
|
||||
types[INPUT][0] = CV_64FC2;
|
||||
|
||||
sizes[OUTPUT][0] = sizes[REF_OUTPUT][0] = cvSize(1, 1);
|
||||
types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_8UC1;
|
||||
}
|
||||
|
||||
void CV_ModelEstimator2_Test::fill_array( int test_case_idx, int i, int j, Mat& arr )
|
||||
{
|
||||
if( i != INPUT )
|
||||
{
|
||||
cvtest::ArrayTest::fill_array( test_case_idx, i, j, arr );
|
||||
return;
|
||||
}
|
||||
|
||||
if (test_case_idx < generalPositionsCount)
|
||||
{
|
||||
//generate points in a general position (i.e. no three points can lie on the same line.)
|
||||
|
||||
bool isGeneralPosition;
|
||||
do
|
||||
{
|
||||
ArrayTest::fill_array(test_case_idx, i, j, arr);
|
||||
|
||||
//a simple check that the position is general:
|
||||
// for each line check that all other points don't belong to it
|
||||
isGeneralPosition = true;
|
||||
for (int startPointIndex = 0; startPointIndex < usedPointsCount && isGeneralPosition; startPointIndex++)
|
||||
{
|
||||
for (int endPointIndex = startPointIndex + 1; endPointIndex < usedPointsCount && isGeneralPosition; endPointIndex++)
|
||||
{
|
||||
|
||||
for (int testPointIndex = 0; testPointIndex < usedPointsCount && isGeneralPosition; testPointIndex++)
|
||||
{
|
||||
if (testPointIndex == startPointIndex || testPointIndex == endPointIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CV_Assert(arr.type() == CV_64FC2);
|
||||
Point2d tangentVector_1 = arr.at<Point2d>(endPointIndex) - arr.at<Point2d>(startPointIndex);
|
||||
Point2d tangentVector_2 = arr.at<Point2d>(testPointIndex) - arr.at<Point2d>(startPointIndex);
|
||||
|
||||
const float eps = 1e-4f;
|
||||
//TODO: perhaps it is better to normalize the cross product by norms of the tangent vectors
|
||||
if (fabs(tangentVector_1.cross(tangentVector_2)) < eps)
|
||||
{
|
||||
isGeneralPosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while(!isGeneralPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
//create points in a degenerate position (there are at least 3 points belonging to the same line)
|
||||
|
||||
ArrayTest::fill_array(test_case_idx, i, j, arr);
|
||||
if (usedPointsCount <= 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RNG &rng = ts->get_rng();
|
||||
int startPointIndex, endPointIndex, modifiedPointIndex;
|
||||
do
|
||||
{
|
||||
startPointIndex = cvtest::randInt(rng) % usedPointsCount;
|
||||
endPointIndex = cvtest::randInt(rng) % usedPointsCount;
|
||||
modifiedPointIndex = checkPartialSubsets ? usedPointsCount - 1 : cvtest::randInt(rng) % usedPointsCount;
|
||||
}
|
||||
while (startPointIndex == endPointIndex || startPointIndex == modifiedPointIndex || endPointIndex == modifiedPointIndex);
|
||||
|
||||
double startWeight = cvtest::randReal(rng);
|
||||
CV_Assert(arr.type() == CV_64FC2);
|
||||
arr.at<Point2d>(modifiedPointIndex) = startWeight * arr.at<Point2d>(startPointIndex) + (1.0 - startWeight) * arr.at<Point2d>(endPointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double CV_ModelEstimator2_Test::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CV_ModelEstimator2_Test::prepare_to_validation( int test_case_idx )
|
||||
{
|
||||
test_mat[OUTPUT][0].at<uchar>(0) = checkSubsetResult;
|
||||
test_mat[REF_OUTPUT][0].at<uchar>(0) = test_case_idx < generalPositionsCount || usedPointsCount <= 2;
|
||||
}
|
||||
|
||||
void CV_ModelEstimator2_Test::run_func()
|
||||
{
|
||||
//make the input continuous
|
||||
Mat input = test_mat[INPUT][0].clone();
|
||||
CvMat _input = input;
|
||||
|
||||
RNG &rng = ts->get_rng();
|
||||
int modelPoints = cvtest::randInt(rng);
|
||||
CvSize modelSize = cvSize(2, modelPoints);
|
||||
int maxBasicSolutions = cvtest::randInt(rng);
|
||||
BareModelEstimator modelEstimator(modelPoints, modelSize, maxBasicSolutions);
|
||||
checkSubsetResult = modelEstimator.checkSubsetPublic(&_input, usedPointsCount, checkPartialSubsets);
|
||||
}
|
||||
|
||||
TEST(Calib3d_ModelEstimator2, accuracy) { CV_ModelEstimator2_Test test; test.safe_run(); }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,709 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include <opencv2/ts/cuda_test.hpp> // EXPECT_MAT_NEAR
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
TEST(multiview_calibration, accuracy) {
|
||||
// convert euler angles to rotation matrix
|
||||
const auto euler2rot = [] (double x, double y, double z) {
|
||||
cv::Matx33d R_x(1, 0, 0, 0, cos(x), -sin(x), 0, sin(x), cos(x));
|
||||
cv::Matx33d R_y(cos(y), 0, sin(y), 0, 1, 0, -sin(y), 0, cos(y));
|
||||
cv::Matx33d R_z(cos(z), -sin(z), 0, sin(z), cos(z), 0, 0, 0, 1);
|
||||
return cv::Mat(R_z * R_y * R_x);
|
||||
};
|
||||
const cv::Size board_size (5,4);
|
||||
cv::RNG rng(0);
|
||||
const double board_len = 0.08, noise_std = 0.04;
|
||||
const int num_cameras = 4, num_pts = board_size.area();
|
||||
std::vector<cv::Vec3f> board_pattern (num_pts);
|
||||
// fill pattern points
|
||||
for (int j = 0; j < board_size.height; j++) {
|
||||
for (int i = 0; i < board_size.width; i++) {
|
||||
board_pattern[j*board_size.width+i] = cv::Vec3f ((float)i, (float)j, 0)*board_len;
|
||||
}
|
||||
}
|
||||
std::vector<uchar> models(num_cameras, cv::CALIB_MODEL_PINHOLE);
|
||||
std::vector<cv::Size> image_sizes(num_cameras);
|
||||
std::vector<cv::Mat> Ks_gt, distortions_gt, Rs_gt, Ts_gt;
|
||||
for (int c = 0; c < num_cameras; c++) {
|
||||
// generate intrinsics and extrinsics
|
||||
image_sizes[c] = cv::Size(rng.uniform(1300, 1500), rng.uniform(900, 1300));
|
||||
const double focal = rng.uniform(900.0, 1300.0);
|
||||
cv::Matx33d K(focal, 0, (double)image_sizes[c].width/2.,
|
||||
0, focal, (double)image_sizes[c].height/2.,
|
||||
0, 0, 1);
|
||||
cv::Matx<double, 1, 5> dist (rng.uniform(1e-1, 3e-1), rng.uniform(1e-2, 5e-2), rng.uniform(1e-2, 5e-2), rng.uniform(1e-2, 5e-2), rng.uniform(1e-2, 5e-2));
|
||||
Ks_gt.emplace_back(cv::Mat(K));
|
||||
distortions_gt.emplace_back(cv::Mat(dist));
|
||||
if (c == 0) {
|
||||
// I | 0
|
||||
Rs_gt.emplace_back(cv::Mat(cv::Matx33d::eye()));
|
||||
Ts_gt.emplace_back(cv::Mat(cv::Vec3d::zeros()));
|
||||
} else {
|
||||
const double ty_min = -.3, ty_max = .3, tx_min = -.3, tx_max = .3, tz_min = -.1, tz_max = .1;
|
||||
const double yaw_min = -20, yaw_max = 20, pitch_min = -20, pitch_max = 20, roll_min = -20, roll_max = 20;
|
||||
Rs_gt.emplace_back(euler2rot(rng.uniform(yaw_min, yaw_max)*M_PI/180,
|
||||
rng.uniform(pitch_min, pitch_max)*M_PI/180,
|
||||
rng.uniform(roll_min, roll_max)*M_PI/180));
|
||||
Ts_gt.emplace_back(cv::Mat(cv::Vec3d(rng.uniform(tx_min, tx_max),
|
||||
rng.uniform(ty_min, ty_max),
|
||||
rng.uniform(tz_min, tz_max))));
|
||||
}
|
||||
}
|
||||
|
||||
const int MAX_SAMPLES = 2000, MAX_FRAMES = 50;
|
||||
cv::Mat pattern (board_pattern, true/*copy*/);
|
||||
pattern = pattern.reshape(1, num_pts).t();
|
||||
pattern.row(2) = 2.0; // set approximate depth of object points
|
||||
const double ty_min = -2, ty_max = 2, tx_min = -2, tx_max = 2, tz_min = -1, tz_max = 1;
|
||||
const double yaw_min = -45, yaw_max = 45, pitch_min = -45, pitch_max = 45, roll_min = -45, roll_max = 45;
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints;
|
||||
std::vector<std::vector<cv::Mat>> image_points_all(num_cameras);
|
||||
cv::Mat ones = cv::Mat_<float>::ones(1, num_pts);
|
||||
std::vector<std::vector<uchar>> visibility;
|
||||
cv::Mat centroid = cv::Mat(cv::Matx31f(
|
||||
(float)cv::mean(pattern.row(0)).val[0],
|
||||
(float)cv::mean(pattern.row(1)).val[0],
|
||||
(float)cv::mean(pattern.row(2)).val[0]));
|
||||
for (int f = 0; f < MAX_SAMPLES; f++) {
|
||||
cv::Mat R = euler2rot(rng.uniform(yaw_min, yaw_max)*M_PI/180,
|
||||
rng.uniform(pitch_min, pitch_max)*M_PI/180,
|
||||
rng.uniform(roll_min, roll_max)*M_PI/180);
|
||||
cv::Mat t = cv::Mat(cv::Matx31f(
|
||||
(float)rng.uniform(tx_min, tx_max),
|
||||
(float)rng.uniform(ty_min, ty_max),
|
||||
(float)rng.uniform(tz_min, tz_max)));
|
||||
|
||||
R.convertTo(R, CV_32F);
|
||||
cv::Mat pattern_new = (R * (pattern - centroid * ones) + centroid * ones + t * ones).t();
|
||||
|
||||
std::vector<cv::Mat> img_pts_cams(num_cameras);
|
||||
std::vector<uchar> visible(num_cameras, (uchar)0);
|
||||
int num_visible_patterns = 0;
|
||||
for (int c = 0; c < num_cameras; c++) {
|
||||
cv::Mat img_pts;
|
||||
if (models[c] == cv::CALIB_MODEL_FISHEYE) {
|
||||
cv::fisheye::projectPoints(pattern_new, img_pts, Rs_gt[c], Ts_gt[c], Ks_gt[c], distortions_gt[c]);
|
||||
} else {
|
||||
cv::projectPoints(pattern_new, Rs_gt[c], Ts_gt[c], Ks_gt[c], distortions_gt[c], img_pts);
|
||||
}
|
||||
|
||||
// add normal / Gaussian noise to image points
|
||||
cv::Mat noise (img_pts.rows, img_pts.cols, img_pts.type());
|
||||
rng.fill(noise, cv::RNG::NORMAL, 0, noise_std);
|
||||
img_pts += noise;
|
||||
|
||||
bool are_all_pts_in_image = true;
|
||||
const auto * const pts = (float *) img_pts.data;
|
||||
for (int i = 0; i < num_pts; i++) {
|
||||
if (pts[i*2 ] < 0 || pts[i*2 ] > (float)image_sizes[c].width ||
|
||||
pts[i*2+1] < 0 || pts[i*2+1] > (float)image_sizes[c].height) {
|
||||
are_all_pts_in_image = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (are_all_pts_in_image) {
|
||||
visible[c] = 1;
|
||||
num_visible_patterns += 1;
|
||||
img_pts.copyTo(img_pts_cams[c]);
|
||||
}
|
||||
}
|
||||
|
||||
if (num_visible_patterns >= 2) {
|
||||
objPoints.emplace_back(board_pattern);
|
||||
visibility.emplace_back(visible);
|
||||
for (int c = 0; c < num_cameras; c++) {
|
||||
image_points_all[c].emplace_back(img_pts_cams[c].clone());
|
||||
}
|
||||
if (objPoints.size() >= MAX_FRAMES)
|
||||
break;
|
||||
}
|
||||
}
|
||||
cv::Mat visibility_mat = cv::Mat_<uchar>(num_cameras, (int)objPoints.size());
|
||||
for (int c = 0; c < num_cameras; c++) {
|
||||
for (int f = 0; f < (int)objPoints.size(); f++) {
|
||||
visibility_mat.at<uchar>(c, f) = visibility[f][c];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<cv::Mat> Ks, distortions, Rs, Ts;
|
||||
calibrateMultiview(objPoints, image_points_all, image_sizes, visibility_mat,
|
||||
models, Ks, distortions, Rs, Ts);
|
||||
|
||||
const double K_err_tol = 1e1, dist_tol = 5e-2, R_tol = 1e-2, T_tol = 1e-2;
|
||||
for (int c = 0; c < num_cameras; c++) {
|
||||
cv::Mat R;
|
||||
cv::Rodrigues(Rs[c], R);
|
||||
EXPECT_MAT_NEAR(Ks_gt[c], Ks[c], K_err_tol);
|
||||
CV_LOG_INFO(NULL, "true distortions: " << distortions_gt[c]);
|
||||
CV_LOG_INFO(NULL, "found distortions: " << distortions[c]);
|
||||
EXPECT_MAT_NEAR(distortions_gt[c], distortions[c], dist_tol);
|
||||
EXPECT_MAT_NEAR(Rs_gt[c], R, R_tol);
|
||||
EXPECT_MAT_NEAR(Ts_gt[c], Ts[c], T_tol);
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiViewTest : public ::testing::Test
|
||||
{
|
||||
std::vector<cv::Vec3f> genAsymmetricObjectPoints(cv::Size board_size = cv::Size(8, 11), float square_size = 0.04)
|
||||
{
|
||||
std::vector<cv::Vec3f> objectPoints;
|
||||
objectPoints.reserve(board_size.height*board_size.width);
|
||||
for( int i = 0; i < board_size.height; i++ )
|
||||
{
|
||||
for( int j = 0; j < board_size.width; j++ )
|
||||
{
|
||||
objectPoints.push_back(cv::Point3f((2*j + i % 2)*square_size, i*square_size, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return objectPoints;
|
||||
}
|
||||
|
||||
void loadImagePoints(const std::string& base_dir, const std::vector<std::string> cameras, int frameCount,
|
||||
std::vector<std::vector<cv::Mat>>& image_points_all, cv::Mat& visibility)
|
||||
{
|
||||
image_points_all.clear();
|
||||
visibility.create(static_cast<int>(cameras.size()), frameCount, CV_BoolC1);
|
||||
for (int c = 0; c < static_cast<int>(cameras.size()); c++)
|
||||
{
|
||||
std::vector<cv::Mat> camera_image_points;
|
||||
std::string fname = base_dir + cameras[c] + ".json";
|
||||
FileStorage fs(fname, cv::FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()) << "Cannot open points file " << fname;
|
||||
for (int i = 0; i < frameCount; i++)
|
||||
{
|
||||
std::string nodeName = cv::format("frame_%d", i);
|
||||
FileNode node = fs[nodeName];
|
||||
if (!node.empty())
|
||||
{
|
||||
camera_image_points.push_back(node.mat().reshape(2, 1));
|
||||
visibility.at<uchar>(c, i) = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_image_points.push_back(cv::Mat());
|
||||
visibility.at<uchar>(c, i) = 0;
|
||||
}
|
||||
}
|
||||
fs.release();
|
||||
image_points_all.push_back(camera_image_points);
|
||||
}
|
||||
}
|
||||
|
||||
double calibrateMono(const std::vector<cv::Vec3f>& board_pattern,
|
||||
const std::vector<cv::Mat>& image_points,
|
||||
const cv::Size& image_size,
|
||||
cv::CameraModel model,
|
||||
int flags,
|
||||
Mat& K,
|
||||
Mat& dist)
|
||||
{
|
||||
std::vector<cv::Mat> filtered_image_points;
|
||||
for(size_t i = 0; i < image_points.size(); i++)
|
||||
{
|
||||
if(!image_points[i].empty())
|
||||
filtered_image_points.push_back(image_points[i]);
|
||||
}
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints(filtered_image_points.size(), board_pattern);
|
||||
|
||||
std::vector<cv::Mat> rvec, tvec;
|
||||
cv::Mat K1, dist1;
|
||||
if(model == cv::CALIB_MODEL_PINHOLE)
|
||||
{
|
||||
return cv::calibrateCamera(objPoints, filtered_image_points, image_size, K, dist, rvec, tvec, flags);
|
||||
}
|
||||
else if(model == cv::CALIB_MODEL_FISHEYE)
|
||||
{
|
||||
return cv::fisheye::calibrate(objPoints, filtered_image_points, image_size, K, dist, rvec, tvec, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Unsupported camera model!");
|
||||
}
|
||||
|
||||
return FLT_MAX;
|
||||
}
|
||||
|
||||
void validateCameraPose(const Mat& R, Mat T, const Mat& R_gt, const Mat& T_gt,
|
||||
double angle_tol = 1.*M_PI/180., double pos_tol = 0.01)
|
||||
{
|
||||
double cos_r = (cv::trace(R_gt.t() * R)[0] - 1) / 2.;
|
||||
double angle = std::acos(std::max(std::min(cos_r, 1.), -1.));
|
||||
cv::Mat dist_mat;
|
||||
subtract(R_gt.t() * T_gt, R.t() * T, dist_mat);
|
||||
double dist = cv::norm(dist_mat);
|
||||
CV_LOG_INFO(NULL, "rotation error: " << angle);
|
||||
CV_LOG_INFO(NULL, "position error: " << dist);
|
||||
EXPECT_NEAR(angle, 0., angle_tol);
|
||||
EXPECT_NEAR(dist, 0., pos_tol);
|
||||
}
|
||||
|
||||
void validateAllPoses(const std::vector<cv::Mat>& Rs_gt,
|
||||
const std::vector<cv::Mat>& Ts_gt,
|
||||
const std::vector<cv::Mat>& Rs,
|
||||
const std::vector<cv::Mat>& Ts,
|
||||
double angle_tol = 1.*M_PI/180.,
|
||||
double pos_tol = 0.01)
|
||||
{
|
||||
ASSERT_EQ(Rs_gt.size(), Ts_gt.size());
|
||||
ASSERT_EQ(Rs.size(), Ts.size());
|
||||
ASSERT_EQ(Rs_gt.size(), Rs.size());
|
||||
|
||||
const size_t num_cameras = Rs_gt.size();
|
||||
for (size_t c = 1; c < num_cameras; c++)
|
||||
{
|
||||
validateCameraPose(Rs[c], Ts[c], Rs_gt[c], Ts_gt[c], angle_tol, pos_tol);
|
||||
double distance0 = cv::norm(Rs[c].t()*Ts[c]);
|
||||
CV_LOG_INFO(NULL, "distance to camera #0: " << distance0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MultiViewTest, OneLine)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-one-line/";
|
||||
const std::vector<std::string> cam_names = {"cam_0", "cam_1", "cam_3"};
|
||||
const std::vector<cv::Size> image_sizes = {{1920, 1080}, {1920, 1080}, {1920, 1080} };
|
||||
std::vector<uchar> models(3, cv::CALIB_MODEL_PINHOLE);
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
0.9996914489704484, -0.01160060078752197, -0.02196435559568884,
|
||||
0.012283315339906, 0.9994374509454836, 0.03120739995344806,
|
||||
0.02158997497973892, -0.03146756598408248, 0.9992715673286274
|
||||
};
|
||||
double rs_2_gt_data[9] = {
|
||||
0.9988848194142131, -0.0255827884561986, -0.03968171466355882,
|
||||
0.0261796234191418, 0.999550713317242, 0.0145944792515729,
|
||||
0.03929051872229011, -0.0156170561181697, 0.9991057815350362
|
||||
};
|
||||
|
||||
double ts_1_gt_data[3] = {0.5078811293323259, 0.002753469433719865, 0.02413521839310227};
|
||||
double ts_2_gt_data[3] = {1.007213763725429, 0.01645068247976361, 0.05394643957910365};
|
||||
|
||||
std::vector<cv::Mat> Rs_gt = {
|
||||
cv::Mat::eye(3, 3, CV_64FC1),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_1_gt_data),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_2_gt_data)
|
||||
};
|
||||
|
||||
std::vector<cv::Mat> Ts_gt = {
|
||||
cv::Mat::zeros(3, 1, CV_64FC1),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_1_gt_data),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_2_gt_data)
|
||||
};
|
||||
|
||||
const int num_frames = 96;
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
ASSERT_EQ(cam_names.size(), image_points_all.size());
|
||||
ASSERT_TRUE(!image_points_all.empty());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
EXPECT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints(num_frames, board_pattern);
|
||||
|
||||
std::vector<int> flagsForIntrinsics(3, CALIB_RATIONAL_MODEL);
|
||||
|
||||
std::vector<cv::Mat> Ks, distortions, Rs, Rs_rvec, Ts;
|
||||
double rms = calibrateMultiview(objPoints, image_points_all, image_sizes, visibility, models,
|
||||
Ks, distortions, Rs_rvec, Ts, flagsForIntrinsics);
|
||||
CV_LOG_INFO(NULL, "RMS: " << rms);
|
||||
|
||||
EXPECT_LE(rms, .3);
|
||||
|
||||
Rs.resize(Rs_rvec.size());
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
cv::Rodrigues(Rs_rvec[c], Rs[c]);
|
||||
CV_LOG_INFO(NULL, "R" << c << ":" << Rs[c]);
|
||||
CV_LOG_INFO(NULL, "T" << c << ":" << Ts[c]);
|
||||
}
|
||||
|
||||
validateAllPoses(Rs_gt, Ts_gt, Rs, Ts);
|
||||
}
|
||||
|
||||
TEST_F(MultiViewTest, OneLineInitialGuess)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-one-line/";
|
||||
const std::vector<std::string> cam_names = {"cam_0", "cam_1", "cam_3"};
|
||||
const std::vector<cv::Size> image_sizes = {{1920, 1080}, {1920, 1080}, {1920, 1080} };
|
||||
std::vector<uchar> models(3, cv::CALIB_MODEL_PINHOLE);
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
0.9996914489704484, -0.01160060078752197, -0.02196435559568884,
|
||||
0.012283315339906, 0.9994374509454836, 0.03120739995344806,
|
||||
0.02158997497973892, -0.03146756598408248, 0.9992715673286274
|
||||
};
|
||||
double rs_2_gt_data[9] = {
|
||||
0.9988848194142131, -0.0255827884561986, -0.03968171466355882,
|
||||
0.0261796234191418, 0.999550713317242, 0.0145944792515729,
|
||||
0.03929051872229011, -0.0156170561181697, 0.9991057815350362
|
||||
};
|
||||
|
||||
double ts_1_gt_data[3] = {0.5078811293323259, 0.002753469433719865, 0.02413521839310227};
|
||||
double ts_2_gt_data[3] = {1.007213763725429, 0.01645068247976361, 0.05394643957910365};
|
||||
|
||||
std::vector<cv::Mat> Rs_gt = {
|
||||
cv::Mat::eye(3, 3, CV_64FC1),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_1_gt_data),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_2_gt_data)
|
||||
};
|
||||
|
||||
std::vector<cv::Mat> Ts_gt = {
|
||||
cv::Mat::zeros(3, 1, CV_64FC1),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_1_gt_data),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_2_gt_data)
|
||||
};
|
||||
|
||||
const int num_frames = 96;
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
EXPECT_EQ(cam_names.size(), image_points_all.size());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
EXPECT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints(num_frames, board_pattern);
|
||||
|
||||
std::vector<int> flagsForIntrinsics(3, CALIB_RATIONAL_MODEL);
|
||||
|
||||
std::vector<cv::Mat> Ks, distortions;
|
||||
std::vector<cv::Mat> Rs(3);
|
||||
std::vector<cv::Mat> Ts(3);
|
||||
std::vector<cv::Mat> Rs_rvec(3);
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
Mat K, dist;
|
||||
double mono_rms = calibrateMono(board_pattern, image_points_all[c], image_sizes[c],
|
||||
cv::CALIB_MODEL_PINHOLE, cv::CALIB_RATIONAL_MODEL,
|
||||
K, dist);
|
||||
|
||||
CV_LOG_INFO(NULL, "K:" << K);
|
||||
CV_LOG_INFO(NULL, "dist:" << dist);
|
||||
Ks.push_back(K);
|
||||
distortions.push_back(dist);
|
||||
CV_LOG_INFO(NULL, "Calibrate mono RMS #" << c << ": " << mono_rms);
|
||||
EXPECT_LE(mono_rms, .3);
|
||||
}
|
||||
|
||||
const auto euler2rot = [] (double x, double y, double z) {
|
||||
cv::Matx33d R_x(1, 0, 0, 0, cos(x), -sin(x), 0, sin(x), cos(x));
|
||||
cv::Matx33d R_y(cos(y), 0, sin(y), 0, 1, 0, -sin(y), 0, cos(y));
|
||||
cv::Matx33d R_z(cos(z), -sin(z), 0, sin(z), cos(z), 0, 0, 0, 1);
|
||||
return cv::Mat(R_z * R_y * R_x);
|
||||
};
|
||||
|
||||
// Introduce small noise by rotating ground truth camera pose a bit
|
||||
Rs[0] = Rs_gt[0].clone();
|
||||
Ts[0] = Ts_gt[0].clone();
|
||||
double sign = 1.;
|
||||
for (int c = 1; c < 3; c++)
|
||||
{
|
||||
Mat noise = euler2rot(0., sign*M_PI/180., 0.);
|
||||
sign *= -1.;
|
||||
Rs[c] = noise*Rs_gt[c];
|
||||
Ts[c] = Ts_gt[c].clone();
|
||||
cv::Rodrigues(Rs[c], Rs_rvec[c]);
|
||||
}
|
||||
|
||||
int flags = cv::CALIB_USE_EXTRINSIC_GUESS | cv::CALIB_USE_INTRINSIC_GUESS | cv::CALIB_STEREO_REGISTRATION;
|
||||
double rms = calibrateMultiview(objPoints, image_points_all, image_sizes, visibility, models,
|
||||
Ks, distortions, Rs_rvec, Ts, flagsForIntrinsics, flags);
|
||||
CV_LOG_INFO(NULL, "RMS: " << rms);
|
||||
|
||||
EXPECT_LE(rms, .3);
|
||||
|
||||
Rs.resize(Rs_rvec.size());
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
cv::Rodrigues(Rs_rvec[c], Rs[c]);
|
||||
CV_LOG_INFO(NULL, "R" << c << ":" << Rs[c]);
|
||||
CV_LOG_INFO(NULL, "T" << c << ":" << Ts[c]);
|
||||
}
|
||||
|
||||
validateAllPoses(Rs_gt, Ts_gt, Rs, Ts);
|
||||
}
|
||||
|
||||
TEST_F(MultiViewTest, CamsToFloor)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-to-floor/";
|
||||
const std::vector<std::string> cam_names = {"cam_0", "cam_1", "cam_2"};
|
||||
std::vector<cv::Size> image_sizes = {{1920, 1080}, {1920, 1080}, {1280, 720}};
|
||||
std::vector<uchar> models(3, cv::CALIB_MODEL_PINHOLE);
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
-0.05217184989559624, 0.6470741242690249, -0.7606399777686852,
|
||||
-0.526982982144755, 0.6291523784496631, 0.5713634755748329,
|
||||
0.8482729717539585, 0.4306534133065782, 0.3081730082260634
|
||||
};
|
||||
double rs_2_gt_data[9] = {
|
||||
0.001580678474783847, -0.62542080411436, 0.7802860496231537,
|
||||
0.4843796328138114, 0.683118871472744, 0.5465573883435866,
|
||||
-0.8748564869569847, 0.3770907387072139, 0.304020890746888
|
||||
};
|
||||
|
||||
double ts_1_gt_data[3] = {1.064278166833888, -0.7727142268275895, 1.140555926119704};
|
||||
double ts_2_gt_data[3] = {-0.9391478506021244, -1.048084838193036, 1.3973875466639};
|
||||
|
||||
std::vector<cv::Mat> Rs_gt = {
|
||||
cv::Mat::eye(3, 3, CV_64FC1),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_1_gt_data),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_2_gt_data)
|
||||
};
|
||||
|
||||
std::vector<cv::Mat> Ts_gt = {
|
||||
cv::Mat::zeros(3, 1, CV_64FC1),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_1_gt_data),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_2_gt_data)
|
||||
};
|
||||
|
||||
const int num_frames = 125;
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
ASSERT_EQ(cam_names.size(), image_points_all.size());
|
||||
ASSERT_TRUE(!image_points_all.empty());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
EXPECT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints(num_frames, board_pattern);
|
||||
|
||||
std::vector<int> flagsForIntrinsics(3, cv::CALIB_RATIONAL_MODEL);
|
||||
|
||||
std::vector<cv::Mat> Ks, distortions, Rs, Rs_rvec, Ts;
|
||||
double rms = calibrateMultiview(objPoints, image_points_all, image_sizes, visibility, models,
|
||||
Ks, distortions, Rs_rvec, Ts, flagsForIntrinsics);
|
||||
CV_LOG_INFO(NULL, "RMS: " << rms);
|
||||
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
Rs.resize(Rs_rvec.size());
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
cv::Rodrigues(Rs_rvec[c], Rs[c]);
|
||||
CV_LOG_INFO(NULL, "R" << c << ":" << Rs[c]);
|
||||
CV_LOG_INFO(NULL, "T" << c << ":" << Ts[c]);
|
||||
}
|
||||
|
||||
validateAllPoses(Rs_gt, Ts_gt, Rs, Ts);
|
||||
}
|
||||
|
||||
TEST_F(MultiViewTest, Hetero)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-hetero/";
|
||||
const std::vector<std::string> cam_names = {"cam_7", "cam_4", "cam_8"};
|
||||
std::vector<cv::Size> image_sizes = {{1920, 1080}, {1920, 1080}, {2048, 2048}};
|
||||
std::vector<uchar> models = { cv::CALIB_MODEL_PINHOLE, cv::CALIB_MODEL_PINHOLE, cv::CALIB_MODEL_FISHEYE};
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
0.9927140815671712, 0.1070962138895326, 0.05521913824730116,
|
||||
-0.05355858010980671, -0.01832224712027507, 0.9983966014350634,
|
||||
0.1079362346706077, -0.994079823872807, -0.0124528315711911
|
||||
};
|
||||
double rs_2_gt_data[9] = {
|
||||
0.9974414183162762, 0.06892036265048015, 0.0189894876008139,
|
||||
-0.06886936047115397, 0.9976201373221448, -0.003327581349727079,
|
||||
-0.0191736333413733, 0.002011273594291581, 0.9998141460106571
|
||||
};
|
||||
|
||||
double ts_1_gt_data[3] = {0.5106665738153067, -0.3450096979616873, 0.7854530821015541};
|
||||
double ts_2_gt_data[3] = {1.01304902944076, 0.01197702701032772, -0.01801263208619407};
|
||||
|
||||
std::vector<cv::Mat> Rs_gt = {
|
||||
cv::Mat::eye(3, 3, CV_64FC1),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_1_gt_data),
|
||||
cv::Mat(3, 3, CV_64FC1, rs_2_gt_data)
|
||||
};
|
||||
|
||||
std::vector<cv::Mat> Ts_gt = {
|
||||
cv::Mat::zeros(3, 1, CV_64FC1),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_1_gt_data),
|
||||
cv::Mat(3, 1, CV_64FC1, ts_2_gt_data)
|
||||
};
|
||||
|
||||
const int num_frames = 127;
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
ASSERT_EQ(cam_names.size(), image_points_all.size());
|
||||
ASSERT_TRUE(!image_points_all.empty());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
EXPECT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
std::vector<std::vector<cv::Vec3f>> objPoints(num_frames, board_pattern);
|
||||
|
||||
std::vector<int> flagsForIntrinsics= {
|
||||
cv::CALIB_RATIONAL_MODEL, cv::CALIB_RATIONAL_MODEL,
|
||||
cv::CALIB_RECOMPUTE_EXTRINSIC | cv::CALIB_FIX_SKEW};
|
||||
|
||||
std::vector<cv::Mat> Ks, distortions, Rs, Rs_rvec, Ts;
|
||||
double rms = calibrateMultiview(objPoints, image_points_all, image_sizes, visibility, models,
|
||||
Ks, distortions, Rs_rvec, Ts, flagsForIntrinsics);
|
||||
CV_LOG_INFO(NULL, "RMS: " << rms);
|
||||
|
||||
EXPECT_LE(rms, 2.5);
|
||||
|
||||
Rs.resize(Rs_rvec.size());
|
||||
for(int c = 0; c < 3; c++)
|
||||
{
|
||||
cv::Rodrigues(Rs_rvec[c], Rs[c]);
|
||||
CV_LOG_INFO(NULL, "R" << c << ":" << Rs[c]);
|
||||
CV_LOG_INFO(NULL, "T" << c << ":" << Ts[c]);
|
||||
}
|
||||
|
||||
validateAllPoses(Rs_gt, Ts_gt, Rs, Ts);
|
||||
}
|
||||
|
||||
struct RegisterCamerasTest: public MultiViewTest
|
||||
{
|
||||
void filterPoints(const std::vector<std::vector<cv::Mat>>& image_points_all,
|
||||
std::vector<cv::Mat>& visible_image_points1,
|
||||
std::vector<cv::Mat>& visible_image_points2)
|
||||
{
|
||||
for (size_t i = 0; i < std::min(image_points_all[0].size(), image_points_all[1].size()); i++)
|
||||
{
|
||||
if(!image_points_all[0][i].empty() && !image_points_all[1][i].empty())
|
||||
{
|
||||
visible_image_points1.push_back(image_points_all[0][i]);
|
||||
visible_image_points2.push_back(image_points_all[1][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RegisterCamerasTest, hetero1)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-hetero/";
|
||||
const std::vector<std::string> cam_names = {"cam_7", "cam_4"};
|
||||
std::vector<cv::Size> image_sizes = {{1920, 1080}, {2048, 2048}};
|
||||
std::vector<cv::CameraModel> models = {cv::CALIB_MODEL_PINHOLE, cv::CALIB_MODEL_FISHEYE};
|
||||
std::vector<int> flagsForIntrinsics = {cv::CALIB_RATIONAL_MODEL, cv::CALIB_RECOMPUTE_EXTRINSIC | cv::CALIB_FIX_SKEW};
|
||||
const int num_frames = 127;
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
0.9923998627583629, 0.1102270543935739, 0.05470382872247866,
|
||||
-0.05295473891691575, -0.01873572048960163, 0.9984211377990636,
|
||||
0.1110779367085268, -0.9937298270945939, -0.01275628155556733
|
||||
};
|
||||
cv::Mat R_gt(3, 3, CV_64FC1, rs_1_gt_data);
|
||||
|
||||
double ts_1_gt_data[3] = {0.5132123397314717, -0.345554256449513, 0.7851208074917889};
|
||||
cv::Mat T_gt(3, 1, CV_64FC1, ts_1_gt_data);
|
||||
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
ASSERT_EQ(cam_names.size(), image_points_all.size());
|
||||
ASSERT_TRUE(!image_points_all.empty());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
ASSERT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
cv::Mat K1, dist1;
|
||||
double rms = calibrateMono(board_pattern, image_points_all[0], image_sizes[0], models[0], flagsForIntrinsics[0], K1, dist1);
|
||||
CV_LOG_INFO(NULL, "Mono #1 RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
cv::Mat K2, dist2;
|
||||
rms = calibrateMono(board_pattern, image_points_all[1], image_sizes[1], models[1], flagsForIntrinsics[1], K2, dist2);
|
||||
CV_LOG_INFO(NULL, "Mono #2 RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
std::vector<cv::Mat> visible_image_points1, visible_image_points2;
|
||||
filterPoints(image_points_all, visible_image_points1, visible_image_points2);
|
||||
std::vector<std::vector<cv::Vec3f>> object_points(visible_image_points1.size(), board_pattern);
|
||||
|
||||
cv::Mat R, T, E, F;
|
||||
cv::Mat rvec_reg, tvec_reg, per_view_err;
|
||||
rms = registerCameras(object_points, object_points, visible_image_points1, visible_image_points2,
|
||||
K1, dist1, cv::CALIB_MODEL_PINHOLE, K2, dist2, cv::CALIB_MODEL_FISHEYE,
|
||||
R, T, E, F, rvec_reg, tvec_reg, per_view_err);
|
||||
CV_LOG_INFO(NULL, "Register RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
CV_LOG_INFO(NULL, "R:" << R);
|
||||
CV_LOG_INFO(NULL, "T:" << T);
|
||||
|
||||
validateCameraPose(R, T, R_gt, T_gt);
|
||||
}
|
||||
|
||||
TEST_F(RegisterCamerasTest, hetero2)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path() + "cv/cameracalibration/multiview/3cams-hetero/";
|
||||
const std::vector<std::string> cam_names = {"cam_4", "cam_8"};
|
||||
std::vector<cv::Size> image_sizes = {{2048, 2048}, {1920, 1080}};
|
||||
std::vector<cv::CameraModel> models = {cv::CALIB_MODEL_FISHEYE, cv::CALIB_MODEL_PINHOLE};
|
||||
std::vector<int> flagsForIntrinsics = { cv::CALIB_RECOMPUTE_EXTRINSIC | cv::CALIB_FIX_SKEW, cv::CALIB_RATIONAL_MODEL};
|
||||
const int num_frames = 127;
|
||||
std::vector<cv::Vec3f> board_pattern = genAsymmetricObjectPoints();
|
||||
|
||||
double rs_1_gt_data[9] = {
|
||||
0.9987381520324473, -0.03742623778583679, 0.0334870183804049,
|
||||
0.03272769253311544, -0.02072052049800844, -0.9992494974588425,
|
||||
0.03809201775004091, 0.999084549352801, -0.01946949994840527
|
||||
};
|
||||
cv::Mat R_gt(3, 3, CV_64FC1, rs_1_gt_data);
|
||||
|
||||
double ts_1_gt_data[3] = {0.4660746974363485, 0.7703195273112146, 0.3243138654899712};
|
||||
cv::Mat T_gt(3, 1, CV_64FC1, ts_1_gt_data);
|
||||
|
||||
std::vector<std::vector<cv::Mat>> image_points_all;
|
||||
cv::Mat visibility;
|
||||
loadImagePoints(root, cam_names, num_frames, image_points_all, visibility);
|
||||
ASSERT_EQ(cam_names.size(), image_points_all.size());
|
||||
ASSERT_TRUE(!image_points_all.empty());
|
||||
for(size_t i = 0; i < cam_names.size(); i++)
|
||||
{
|
||||
ASSERT_TRUE(!image_points_all[i].empty());
|
||||
}
|
||||
|
||||
cv::Mat K1, dist1;
|
||||
double rms = calibrateMono(board_pattern, image_points_all[0], image_sizes[0], models[0], flagsForIntrinsics[0], K1, dist1);
|
||||
CV_LOG_INFO(NULL, "Mono #1 RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
cv::Mat K2, dist2;
|
||||
rms = calibrateMono(board_pattern, image_points_all[1], image_sizes[1], models[1], flagsForIntrinsics[1], K2, dist2);
|
||||
CV_LOG_INFO(NULL, "Mono #2 RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
std::vector<cv::Mat> visible_image_points1, visible_image_points2;
|
||||
filterPoints(image_points_all, visible_image_points1, visible_image_points2);
|
||||
std::vector<std::vector<cv::Vec3f>> object_points(visible_image_points1.size(), board_pattern);
|
||||
|
||||
cv::Mat R, T, E, F;
|
||||
cv::Mat rvec_reg, tvec_reg, per_view_err;
|
||||
rms = registerCameras(object_points, object_points, visible_image_points1, visible_image_points2,
|
||||
K1, dist1, cv::CALIB_MODEL_FISHEYE, K2, dist2, cv::CALIB_MODEL_PINHOLE,
|
||||
R, T, E, F, rvec_reg, tvec_reg, per_view_err);
|
||||
CV_LOG_INFO(NULL, "Register RMS: " << rms);
|
||||
EXPECT_LE(rms, 1.);
|
||||
|
||||
CV_LOG_INFO(NULL, "R:" << R);
|
||||
CV_LOG_INFO(NULL, "T:" << T);
|
||||
|
||||
validateCameraPose(R, T, R_gt, T_gt);
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,14 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "opencv2/calib.hpp"
|
||||
|
||||
#endif
|
||||