vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:10:33 +08:00
commit f7f077da11
6933 changed files with 2335208 additions and 0 deletions
+9
View File
@@ -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
)
+12
View File
@@ -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}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load Diff
+14
View File
@@ -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
+12
View File
@@ -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);
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"whitelist":
{
"": [
"calibrateCameraExtended"
]
}
}
+5
View File
@@ -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)
}
}
+96
View File
@@ -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()
+164
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
#include "perf_precomp.hpp"
#if defined(HAVE_HPX)
#include <hpx/hpx_main.hpp>
#endif
CV_PERF_TEST_MAIN(calib3d)
+10
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+123
View File
@@ -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
File diff suppressed because it is too large Load Diff
+61
View File
@@ -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
+52
View File
@@ -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. */
+988
View 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> &param, 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(&param[0]+(c-1)*6 , rvec.val, 3*sizeof(double));
memcpy(&param[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(&param[0]+(cnt_valid_frame+NUM_CAMERAS-1)*6 , rvec_0.val, 3*sizeof(double));
memcpy(&param[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 = &param[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);
}
}
+139
View File
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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
+767
View File
@@ -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
+10
View File
@@ -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("")
+231
View File
@@ -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
+709
View File
@@ -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);
}
}}
+14
View File
@@ -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