|
|
|
/*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
|