1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

Merge pull request #25061 from asmorkalov:as/register_cameras

RegisterCameras function for heterogenious cameras pair #25061

Credits to Linfei Pan
Extracted from https://github.com/opencv/opencv/pull/24052

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake

---------

Co-authored-by: lpanaf <linpan@student.ethz.ch>
This commit is contained in:
Alexander Smorkalov
2024-02-26 15:45:18 +03:00
committed by GitHub
parent 093ed08892
commit 4c549b8707
4 changed files with 1206 additions and 0 deletions
+96
View File
@@ -422,6 +422,11 @@ enum { CALIB_CB_SYMMETRIC_GRID = 1,
#define CALIB_NINTRINSIC 18 //!< Maximal size of camera internal parameters (initrinsics) vector
enum CameraModel {
CALIB_MODEL_PINHOLE = 0, //!< Pinhole camera model
CALIB_MODEL_FISHEYE = 1, //!< Fisheye camera model
};
enum { CALIB_USE_INTRINSIC_GUESS = 0x00001, //!< Use user provided intrinsics as initial point for optimization.
CALIB_FIX_ASPECT_RATIO = 0x00002, //!< Use with CALIB_USE_INTRINSIC_GUESS. The ratio fx/fy stays the same as in the input cameraMatrix.
CALIB_FIX_PRINCIPAL_POINT = 0x00004, //!< The principal point (cx, cy) stays the same as in the input camera matrix. Image center is used as principal point, if CALIB_USE_INTRINSIC_GUESS is not set.
@@ -1136,6 +1141,97 @@ CV_EXPORTS_W double stereoCalibrate( InputArrayOfArrays objectPoints,
OutputArray perViewErrors, int flags = CALIB_FIX_INTRINSIC,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6) );
/** @brief Calibrates a camera pair set up. This function finds the extrinsic parameters between the two cameras.
@param objectPoints1 Vector of vectors of the calibration pattern points for camera 1.
A similar structure as objectPoints in @ref calibrateCamera and for each pattern view,
both cameras do not need to see the same object points. objectPoints1.size(), imagePoints1.size()
nees to be equal,as well as objectPoints1[i].size(), imagePoints1[i].size() need to be equal for each i.
@param objectPoints2 Vector of vectors of the calibration pattern points for camera 2.
A similar structure as objectPoints1. objectPoints2.size(), and imagePoints2.size() nees to be equal,
as well as objectPoints2[i].size(), imagePoints2[i].size() need to be equal for each i.
However, objectPoints1[i].size() and objectPoints2[i].size() are not required to be equal.
@param imagePoints1 Vector of vectors of the projections of the calibration pattern points,
observed by the first camera. The same structure as in @ref calibrateCamera.
@param imagePoints2 Vector of vectors of the projections of the calibration pattern points,
observed by the second camera. The same structure as in @ref calibrateCamera.
@param cameraMatrix1 Input/output camera intrinsic matrix for the first camera, the same as in
@ref calibrateCamera. Furthermore, for the stereo case, additional flags may be used, see below.
@param distCoeffs1 Input/output vector of distortion coefficients, the same as in
@ref calibrateCamera.
@param cameraModel1 Flag reflecting the type of model for camera 1 (pinhole / fisheye):
- @ref CALIB_MODEL_PINHOLE pinhole camera model
- @ref CALIB_MODEL_FISHEYE fisheye camera model
@param cameraMatrix2 Input/output second camera intrinsic matrix for the second camera.
See description for cameraMatrix1.
@param distCoeffs2 Input/output lens distortion coefficients for the second camera. See
description for distCoeffs1.
@param cameraModel2 Flag reflecting the type of model for camera 2 (pinhole / fisheye).
See description for cameraModel1.
@param R Output rotation matrix. Together with the translation vector T, this matrix brings
points given in the first camera's coordinate system to points in the second camera's
coordinate system. In more technical terms, the tuple of R and T performs a change of basis
from the first camera's coordinate system to the second camera's coordinate system. Due to its
duality, this tuple is equivalent to the position of the first camera with respect to the
second camera coordinate system.
@param T Output translation vector, see description above.
@param E Output essential matrix.
@param F Output fundamental matrix.
@param rvecs Output vector of rotation vectors ( @ref Rodrigues ) estimated for each pattern view in the
coordinate system of the first camera of the stereo pair (e.g. std::vector<cv::Mat>). More in detail, each
i-th rotation vector together with the corresponding i-th translation vector (see the next output parameter
description) brings the calibration pattern from the object coordinate space (in which object points are
specified) to the camera coordinate space of the first camera of the stereo pair. In more technical terms,
the tuple of the i-th rotation and translation vector performs a change of basis from object coordinate space
to the camera coordinate space of the first camera of the stereo pair.
@param tvecs Output vector of translation vectors estimated for each pattern view, see parameter description
of previous output parameter ( rvecs ).
@param perViewErrors Output vector of the RMS re-projection error estimated for each pattern view.
@param flags Different flags that may be zero or a combination of the following values:
- @ref CALIB_USE_EXTRINSIC_GUESS R and T contain valid initial values that are optimized further.
@param criteria Termination criteria for the iterative optimization algorithm.
The function estimates the transformation between two cameras similar to stereo pair calibration.
The principle follows closely to @ref stereoCalibrate. To understand the problem of estimating the
relative pose between a camera pair, please refer to the description there. The difference for
this function is that, camera intrinsics are not optimized and two cameras are not required
to have overlapping fields of view as long as they are observing the same calibration target
and the absolute positions of each object point are known.
![](pics/register_pair.png)
The above illustration shows an example where such a case may become relevant.
Additionally, it supports a camera pair with the mixed model (pinhole / fisheye).
Similarly to #calibrateCamera, the function minimizes the total re-projection error for all the
points in all the available views from both cameras.
@return the final value of the re-projection error.
@sa calibrateCamera, stereoCalibrate
*/
CV_EXPORTS_AS(registerCamerasExtended) double registerCameras( InputArrayOfArrays objectPoints1,
InputArrayOfArrays objectPoints2,
InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
InputArray cameraMatrix1, InputArray distCoeffs1,
CameraModel cameraModel1,
InputArray cameraMatrix2, InputArray distCoeffs2,
CameraModel cameraModel2,
InputOutputArray R, InputOutputArray T, OutputArray E, OutputArray F,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
OutputArray perViewErrors,
int flags = 0,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-6) );
/// @overload
CV_EXPORTS_W double registerCameras( InputArrayOfArrays objectPoints1,
InputArrayOfArrays objectPoints2,
InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
InputArray cameraMatrix1, InputArray distCoeffs1,
CameraModel cameraModel1,
InputArray cameraMatrix2, InputArray distCoeffs2,
CameraModel cameraModel2,
InputOutputArray R, InputOutputArray T, OutputArray E, OutputArray F,
OutputArray perViewErrors,
int flags = 0,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-6) );
/** @brief Estimates intrinsics and extrinsics (camera pose) for multi-camera system a.k.a multiview calibraton.
@param[in] objPoints Calibration pattern object points. Expected shape: NUM_FRAMES x NUM_POINTS x 3. Supported data type: CV_32F.
+527
View File
@@ -1182,6 +1182,399 @@ static double stereoCalibrateImpl(
return std::sqrt(reprojErr/(pointsTotal*2));
}
static double registerCamerasImpl(
const Mat& _objectPoints1, const Mat& _objectPoints2,
const Mat& _imagePoints1, const Mat& _imagePoints2,
const Mat& _npoints1, const Mat& _npoints2,
const Mat& _cameraMatrix1, const Mat& _distCoeffs1, CameraModel cameraModel1,
const Mat& _cameraMatrix2, const Mat& _distCoeffs2, CameraModel cameraModel2,
Mat matR, Mat matT,
Mat matE, Mat matF,
Mat rvecs, Mat tvecs,
Mat perViewErr, int flags,
TermCriteria termCrit )
{
Matx33d A[2];
std::vector<Mat> tdists(2);
int pointsTotal[2], maxPoints = 0, nparams;
pointsTotal[0] = 0;
pointsTotal[1] = 0;
CV_Assert( _imagePoints1.type() == _imagePoints2.type() &&
_imagePoints1.depth() == _objectPoints1.depth() &&
_imagePoints2.depth() == _objectPoints2.depth() );
CV_Assert( (_npoints1.cols == 1 || _npoints1.rows == 1) &&
_npoints1.type() == CV_32S );
CV_Assert( (_npoints2.cols == 1 || _npoints2.rows == 1) &&
_npoints2.type() == CV_32S );
Mat _npoints[2];
_npoints[0] = _npoints1;
_npoints[1] = _npoints2;
int nimages = (int)_npoints[0].total();
for(int i = 0; i < nimages; i++ )
{
for (int k = 0; k < 2; k++) {
int ni = _npoints[k].at<int>(i);
maxPoints = std::max(maxPoints, ni);
pointsTotal[k] += ni;
}
}
Mat objectPoints[2];
Mat imagePoints[2];
_objectPoints1.convertTo(objectPoints[0], CV_64F);
_objectPoints2.convertTo(objectPoints[1], CV_64F);
objectPoints[0] = objectPoints[0].reshape(3, 1);
objectPoints[1] = objectPoints[1].reshape(3, 1);
if( !rvecs.empty() )
{
int cn = rvecs.channels();
int depth = rvecs.depth();
CV_Assert(depth == CV_32F || depth == CV_64F);
if(((rvecs.rows != nimages || (rvecs.cols*cn != 3 && rvecs.cols*cn != 9)) &&
(rvecs.rows != 1 || rvecs.cols != nimages || cn != 3)) )
CV_Error( CV_StsBadArg, "the output array of rotation vectors must be 3-channel "
"1xn or nx1 array or 1-channel nx3 or nx9 array, where n is the number of views" );
}
if( !tvecs.empty() )
{
int cn = tvecs.channels();
int depth = tvecs.depth();
CV_Assert(depth == CV_32F || depth == CV_64F);
if(((tvecs.rows != nimages || tvecs.cols*cn != 3) &&
(tvecs.rows != 1 || tvecs.cols != nimages || cn != 3)) )
CV_Error( CV_StsBadArg, "the output array of translation vectors must be 3-channel "
"1xn or nx1 array or 1-channel nx3 array, where n is the number of views" );
}
CameraModel cameraModels[2] = {cameraModel1, cameraModel2};
for(int k = 0; k < 2; k++ )
{
const Mat& points = k == 0 ? _imagePoints1 : _imagePoints2;
const Mat& cameraMatrix = k == 0 ? _cameraMatrix1 : _cameraMatrix2;
const Mat& distCoeffs = k == 0 ? _distCoeffs1 : _distCoeffs2;
int depth = points.depth();
int cn = points.channels();
CV_Assert( (depth == CV_32F || depth == CV_64F) &&
((points.rows == pointsTotal[k] && points.cols*cn == 2) ||
(points.rows == 1 && points.cols == pointsTotal[k] && cn == 2)));
points.convertTo(imagePoints[k], CV_64F);
imagePoints[k] = imagePoints[k].reshape(2, 1);
cameraMatrix.convertTo(A[k], CV_64F);
distCoeffs.convertTo(tdists[k], CV_64F);
}
// we optimize for the inter-camera R(3),t(3)
// Param mapping is:
// - from 0 next 6: stereo pair Rt, from 6+i*6 next 6: Rt for each ith camera of nimages,
nparams = 6*(nimages+1);
std::vector<double> param(nparams, 0.);
// storage for initial [om(R){i}|t{i}] (in order to compute the median for each component)
std::vector<double> rtsort(nimages*6);
/*
Compute initial estimate of pose
For each image, compute:
R(om) is the rotation matrix of om
om(R) is the rotation vector of R
R_ref = R(om_right) * R(om_left)'
T_ref_list = [T_ref_list; T_right - R_ref * T_left]
om_ref_list = {om_ref_list; om(R_ref)]
om = median(om_ref_list)
T = median(T_ref_list)
*/
int pos[2];
pos[0] = 0;
pos[1] = 0;
for(int i = 0; i < nimages; i++ )
{
Matx33d R[2];
Vec3d rv, T[2];
for(int k = 0; k < 2; k++ )
{
int ni = _npoints[k].at<int>(i);
Mat objpt_i = objectPoints[k].colRange(pos[k], pos[k] + ni);
Mat imgpt_ik = imagePoints[k].colRange(pos[k], pos[k] + ni);
if (cameraModels[k] == CALIB_MODEL_PINHOLE)
solvePnP(objpt_i, imgpt_ik, A[k], tdists[k], rv, T[k], false, SOLVEPNP_ITERATIVE );
else if (cameraModels[k] == CALIB_MODEL_FISHEYE)
fisheye::solvePnP(objpt_i, imgpt_ik, A[k], tdists[k], rv, T[k], false, SOLVEPNP_ITERATIVE );
else
CV_Error(CV_StsBadArg, cv::format("Camera type %d is not supported", cameraModels[k]));
Rodrigues(rv, R[k]);
if( k == 0 )
{
// save initial om_left and T_left
param[(i+1)*6 + 0] = rv[0];
param[(i+1)*6 + 1] = rv[1];
param[(i+1)*6 + 2] = rv[2];
param[(i+1)*6 + 3] = T[0][0];
param[(i+1)*6 + 4] = T[0][1];
param[(i+1)*6 + 5] = T[0][2];
}
pos[k] += ni;
}
R[0] = R[1]*R[0].t();
T[1] -= R[0]*T[0];
Rodrigues(R[0], rv);
rtsort[i + nimages*0] = rv[0];
rtsort[i + nimages*1] = rv[1];
rtsort[i + nimages*2] = rv[2];
rtsort[i + nimages*3] = T[1][0];
rtsort[i + nimages*4] = T[1][1];
rtsort[i + nimages*5] = T[1][2];
}
if(flags & CALIB_USE_EXTRINSIC_GUESS)
{
Vec3d R, T;
matT.convertTo(T, CV_64F);
if( matR.rows == 3 && matR.cols == 3 )
Rodrigues(matR, R);
else
{
CV_Assert(matR.total() == 3);
matR.convertTo(R, CV_64F);
}
param[0] = R[0];
param[1] = R[1];
param[2] = R[2];
param[3] = T[0];
param[4] = T[1];
param[5] = T[2];
}
else
{
// find the medians and save the first 6 parameters
for(int i = 0; i < 6; i++ )
{
size_t idx = i*nimages;
std::nth_element(rtsort.begin() + idx,
rtsort.begin() + idx + nimages/2,
rtsort.begin() + idx + nimages);
double h = rtsort[idx + nimages/2];
param[i] = (nimages % 2 == 0) ? (h + rtsort[idx + nimages/2 - 1]) * 0.5 : h;
}
}
// Preallocated place for callback calculations
Mat errBuf( maxPoints*2, 1, CV_64F );
Mat JeBuf( maxPoints*2, 6, CV_64F );
Mat J_LRBuf( maxPoints*2, 6, CV_64F );
// auto lmcallback = [&, nimages, cameraModels, _npoints]
auto lmcallback = [&, nimages]
(InputOutputArray _param, OutputArray JtErr_, OutputArray JtJ_, double& errnorm)
{
Mat_<double> param_m = _param.getMat();
Vec3d om_LR(param_m(0), param_m(1), param_m(2));
Vec3d T_LR(param_m(3), param_m(4), param_m(5));
Vec3d om[2], T[2];
Matx33d dr3dr1, dr3dr2, dt3dr2, dt3dt1, dt3dt2;
double reprojErr = 0;
int ptPos[2];
ptPos[0] = 0;
ptPos[1] = 0;
for(int i = 0; i < nimages; i++ )
{
int idx = (i+1)*6;
om[0] = Vec3d(param_m(idx + 0), param_m(idx + 1), param_m(idx + 2));
T[0] = Vec3d(param_m(idx + 3), param_m(idx + 4), param_m(idx + 5));
if( JtJ_.needed() || JtErr_.needed() )
composeRT( om[0], T[0], om_LR, T_LR, om[1], T[1], dr3dr1, noArray(),
dr3dr2, noArray(), noArray(), dt3dt1, dt3dr2, dt3dt2 );
else
composeRT( om[0], T[0], om_LR, T_LR, om[1], T[1] );
for(int k = 0; k < 2; k++ )
{
int ni = _npoints[k].at<int>(i);
Mat objpt_i = objectPoints[k](Range::all(), Range(ptPos[k], ptPos[k] + ni));
Mat err = errBuf (Range(0, ni*2), Range::all());
Mat Je = JeBuf (Range(0, ni*2), Range::all());
Mat J_LR = J_LRBuf(Range(0, ni*2), Range::all());
Mat tmpImagePoints = err.reshape(2, 1);
Mat dpdrot = Je.colRange(0, 3);
Mat dpdt = Je.colRange(3, 6);
Mat imgpt_ik = imagePoints[k](Range::all(), Range(ptPos[k], ptPos[k] + ni));
if (cameraModels[k] == CALIB_MODEL_PINHOLE) {
if( JtJ_.needed() || JtErr_.needed() )
projectPoints(objpt_i, om[k], T[k], A[k], tdists[k],
tmpImagePoints, dpdrot, dpdt, noArray(), noArray(), noArray(), noArray(), 0.);
else
projectPoints(objpt_i, om[k], T[k], A[k], tdists[k], tmpImagePoints);
} else if (cameraModels[k] == CALIB_MODEL_FISHEYE) {
if( JtJ_.needed() || JtErr_.needed() ) {
Mat jacobian; // of size num_points*2 x 15 (2 + 2+ 4 + 3 + 3 + 1 ; // f, c, k, om, T, alpha)
fisheye::projectPoints(objpt_i, tmpImagePoints, om[k], T[k], A[k], tdists[k], 0, jacobian);
jacobian.colRange(8, 11).copyTo(dpdrot);
jacobian.colRange(11,14).copyTo(dpdt);
} else
fisheye::projectPoints(objpt_i, tmpImagePoints, om[k], T[k], A[k], tdists[k]);
}
subtract( tmpImagePoints, imgpt_ik, tmpImagePoints );
if( JtJ_.needed() )
{
Mat JtErr = JtErr_.getMat();
Mat JtJ = JtJ_.getMat();
int eofs = (i+1)*6;
assert( JtJ_.needed() && JtErr_.needed() );
if( k == 1 )
{
// d(err_{x|y}R) ~ de3
// convert de3/{dr3,dt3} => de3{dr1,dt1} & de3{dr2,dt2}
for(int p = 0; p < ni*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_LR.at<double>(p, j);
for(int j = 0; j < 3; j++)
de3dt2(j) = J_LR.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_LR.at<double>(p, j) = de3dr2(j);
for(int j = 0; j < 3; j++)
J_LR.at<double>(p, 3+j) = de3dt2(j);
}
JtJ(Rect(0, 0, 6, 6)) += J_LR.t()*J_LR;
JtJ(Rect(eofs, 0, 6, 6)) = J_LR.t()*Je;
JtErr.rowRange(0, 6) += J_LR.t()*err;
}
JtJ(Rect(eofs, eofs, 6, 6)) += Je.t()*Je;
JtErr.rowRange(eofs, eofs + 6) += Je.t()*err;
}
double viewErr = norm(err, NORM_L2SQR);
if(!perViewErr.empty())
perViewErr.at<double>(i, k) = std::sqrt(viewErr/ni);
reprojErr += viewErr;
ptPos[k] += ni;
}
}
errnorm = reprojErr;
return true;
};
double reprojErr = 0;
LevMarq solver(param, lmcallback,
LevMarq::Settings()
.setMaxIterations((unsigned int)termCrit.maxCount)
.setStepNormTolerance(termCrit.epsilon)
.setSmallEnergyTolerance(termCrit.epsilon * termCrit.epsilon));
// geodesic not supported for normal callbacks
LevMarq::Report r = solver.optimize();
// If solver failed, then the last calculated perViewErr can be wrong & should be recalculated
if (!r.found && !perViewErr.empty())
{
lmcallback(param, noArray(), noArray(), reprojErr);
}
reprojErr = r.energy;
// Extract optimized params from the param vector
Vec3d om_LR(param[0], param[1], param[2]);
Vec3d T_LR(param[3], param[4], param[5]);
Matx33d R_LR;
Rodrigues( om_LR, R_LR );
if( matR.rows == 1 || matR.cols == 1 )
om_LR.convertTo(matR, matR.depth());
else
R_LR.convertTo(matR, matR.depth());
T_LR.convertTo(matT, matT.depth());
if( !matE.empty() || !matF.empty() )
{
Matx33d Tx(0, -T_LR[2], T_LR[1],
T_LR[2], 0, -T_LR[0],
-T_LR[1], T_LR[0], 0);
Matx33d E = Tx*R_LR;
if( !matE.empty() )
E.convertTo(matE, matE.depth());
if( !matF.empty())
{
Matx33d iA0 = A[0].inv(), iA1 = A[1].inv();
Matx33d F = iA1.t() * E * iA0;
F.convertTo(matF, matF.depth(), fabs(F(2,2)) > 0 ? 1./F(2,2) : 1.);
}
}
Mat r1d = rvecs.empty() ? Mat() : rvecs.reshape(1, nimages);
Mat t1d = tvecs.empty() ? Mat() : tvecs.reshape(1, nimages);
for(int i = 0; i < nimages; i++ )
{
int idx = (i + 1) * 6;
if( !rvecs.empty() )
{
Vec3d srcR(param[idx + 0], param[idx + 1], param[idx + 2]);
if( rvecs.rows * rvecs.cols * rvecs.channels() == nimages * 9 )
{
Matx33d rod;
Rodrigues(srcR, rod);
rod.convertTo(r1d.row(i).reshape(1, 3), rvecs.depth());
}
else if (rvecs.rows * rvecs.cols * rvecs.channels() == nimages * 3 )
{
Mat(Mat(srcR).t()).convertTo(r1d.row(i), rvecs.depth());
}
}
if( !tvecs.empty() )
{
Vec3d srcT(param[idx + 3], param[idx + 4], param[idx + 5]);
Mat(Mat(srcT).t()).convertTo(t1d.row(i), tvecs.depth());
}
}
return std::sqrt(reprojErr/(pointsTotal[0] + pointsTotal[1]));
}
static void collectCalibrationData( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints1,
InputArrayOfArrays imagePoints2,
@@ -1705,6 +2098,140 @@ double stereoCalibrate( InputArrayOfArrays _objectPoints,
return err;
}
double registerCameras( InputArrayOfArrays _objectPoints1,
InputArrayOfArrays _objectPoints2,
InputArrayOfArrays _imagePoints1,
InputArrayOfArrays _imagePoints2,
InputArray _cameraMatrix1, InputArray _distCoeffs1, CameraModel cameraModel1,
InputArray _cameraMatrix2, InputArray _distCoeffs2, CameraModel cameraModel2,
InputOutputArray _Rmat, InputOutputArray _Tmat,
OutputArray _Emat, OutputArray _Fmat,
OutputArray _perViewErrors, int flags,
TermCriteria criteria)
{
return registerCameras(_objectPoints1, _objectPoints2, _imagePoints1, _imagePoints2, _cameraMatrix1, _distCoeffs1, cameraModel1,
_cameraMatrix2, _distCoeffs2, cameraModel2, _Rmat, _Tmat, _Emat, _Fmat,
noArray(), noArray(), _perViewErrors, flags, criteria);
}
double registerCameras( InputArrayOfArrays _objectPoints1,
InputArrayOfArrays _objectPoints2,
InputArrayOfArrays _imagePoints1,
InputArrayOfArrays _imagePoints2,
InputArray _cameraMatrix1, InputArray _distCoeffs1, CameraModel cameraModel1,
InputArray _cameraMatrix2, InputArray _distCoeffs2, CameraModel cameraModel2,
InputOutputArray _Rmat, InputOutputArray _Tmat,
OutputArray _Emat, OutputArray _Fmat,
OutputArrayOfArrays _rvecs, OutputArrayOfArrays _tvecs,
OutputArray _perViewErrors, int flags,
TermCriteria criteria)
{
// Only support fisheye camera model and pinhole camera model
CV_Assert(cameraModel1 == CALIB_MODEL_FISHEYE || cameraModel1 == CALIB_MODEL_PINHOLE);
CV_Assert(cameraModel2 == CALIB_MODEL_FISHEYE || cameraModel2 == CALIB_MODEL_PINHOLE);
// Two cameras should contain the same number of frames
CV_Assert(_objectPoints1.total() == _objectPoints2.total());
int rtype = CV_64F;
Mat cameraMatrix1 = _cameraMatrix1.getMat();
Mat cameraMatrix2 = _cameraMatrix2.getMat();
Mat distCoeffs1 = _distCoeffs1.getMat();
Mat distCoeffs2 = _distCoeffs2.getMat();
cameraMatrix1 = prepareCameraMatrix(cameraMatrix1, rtype, flags);
cameraMatrix2 = prepareCameraMatrix(cameraMatrix2, rtype, flags);
int paramNum1 = int(_distCoeffs1.getMat().total()), paramNum2 = int(_distCoeffs2.getMat().total());
distCoeffs1 = prepareDistCoeffs(distCoeffs1, rtype, paramNum1);
distCoeffs2 = prepareDistCoeffs(distCoeffs2, rtype, paramNum2);
if(!(flags & CALIB_USE_EXTRINSIC_GUESS))
{
_Rmat.create(3, 3, rtype);
_Tmat.create(3, 1, rtype);
}
int nimages = int(_objectPoints1.total());
CV_Assert( nimages > 0 );
Mat objPt1, objPt2, imgPt1, imgPt2, npoints1, npoints2, rvecLM, tvecLM;
collectCalibrationData( _objectPoints1, _imagePoints1, noArray(),
objPt1, imgPt1, noArray(), npoints1 );
collectCalibrationData( _objectPoints2, _imagePoints2, noArray(),
objPt2, imgPt2, noArray(), npoints2 );
Mat matR = _Rmat.getMat(), matT = _Tmat.getMat();
bool E_needed = _Emat.needed(), F_needed = _Fmat.needed();
bool rvecs_needed = _rvecs.needed(), tvecs_needed = _tvecs.needed();
bool errors_needed = _perViewErrors.needed();
Mat matE, matF, matErr;
if( E_needed )
{
_Emat.create(3, 3, rtype);
matE = _Emat.getMat();
}
if( F_needed )
{
_Fmat.create(3, 3, rtype);
matF = _Fmat.getMat();
}
bool rvecs_mat_vec = _rvecs.isMatVector();
bool tvecs_mat_vec = _tvecs.isMatVector();
if( rvecs_needed )
{
_rvecs.create(nimages, 1, CV_64FC3);
if( rvecs_mat_vec )
rvecLM.create(nimages, 3, CV_64F);
else
rvecLM = _rvecs.getMat();
}
if( tvecs_needed )
{
_tvecs.create(nimages, 1, CV_64FC3);
if( tvecs_mat_vec )
tvecLM.create(nimages, 3, CV_64F);
else
tvecLM = _tvecs.getMat();
}
if( errors_needed )
{
_perViewErrors.create(nimages, 2, CV_64F);
matErr = _perViewErrors.getMat();
}
double err = registerCamerasImpl(objPt1, objPt2, imgPt1, imgPt2,
npoints1, npoints2,
cameraMatrix1, distCoeffs1, cameraModel1,
cameraMatrix2, distCoeffs2, cameraModel2,
matR, matT, matE, matF, rvecLM, tvecLM,
matErr, flags, criteria);
for(int i = 0; i < nimages; i++ )
{
if( rvecs_needed && rvecs_mat_vec )
{
_rvecs.create(3, 1, CV_64F, i, true);
Mat rv = _rvecs.getMat(i);
Mat(rvecLM.row(i).t()).copyTo(rv);
}
if( tvecs_needed && tvecs_mat_vec )
{
_tvecs.create(3, 1, CV_64F, i, true);
Mat tv = _tvecs.getMat(i);
Mat(tvecLM.row(i).t()).copyTo(tv);
}
}
return err;
}
}
/* End of file. */
@@ -1769,6 +1769,475 @@ void CV_StereoCalibrationTest_CPP::correct( const Mat& F,
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Register Cameras ////////////////////////////////////////////////
class CV_CameraRegistrationTest : public cvtest::BaseTest
{
public:
CV_CameraRegistrationTest();
~CV_CameraRegistrationTest();
void clear();
protected:
// covers of tested functions
virtual double registerCameraPair( const vector<vector<Point3f> >& objectPoints1,
const vector<vector<Point3f> >& objectPoints2,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1, CameraModel cameraModel1,
Mat& cameraMatrix2, Mat& distCoeffs2, CameraModel cameraModel2,
Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags ) = 0;
virtual double calibrateStereoCamera( const vector<vector<Point3f> >& objectPoints,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1,
Mat& cameraMatrix2, Mat& distCoeffs2,
Size imageSize, Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags ) = 0;
int compare(double* val, double* refVal, int len,
double eps, const char* paramName);
void run(int);
};
CV_CameraRegistrationTest::CV_CameraRegistrationTest()
{
}
CV_CameraRegistrationTest::~CV_CameraRegistrationTest()
{
clear();
}
void CV_CameraRegistrationTest::clear()
{
cvtest::BaseTest::clear();
}
int CV_CameraRegistrationTest::compare(double* val, double* ref_val, int len,
double eps, const char* param_name )
{
return cvtest::cmpEps2_64f( ts, val, ref_val, len, eps, param_name );
}
void CV_CameraRegistrationTest::run( int )
{
const int ntests = 1;
const double maxReprojErr = 2;
const double maxDiffBtwRmsErrors = 1e-4;
const double maxDiffBtwEstErrors = 1e-10;
FILE* f = 0;
for(int testcase = 1; testcase <= ntests; testcase++)
{
cv::String filepath;
char buf[1000];
filepath = cv::format("%scv/stereo/case%d/stereo_calib.txt", ts->get_data_path().c_str(), testcase );
f = fopen(filepath.c_str(), "rt");
Size patternSize;
vector<string> imglist;
if( !f || !fgets(buf, sizeof(buf)-3, f) || sscanf(buf, "%d%d", &patternSize.width, &patternSize.height) != 2 )
{
ts->printf( cvtest::TS::LOG, "The file %s can not be opened or has invalid content\n", filepath.c_str() );
ts->set_failed_test_info( f ? cvtest::TS::FAIL_INVALID_TEST_DATA : cvtest::TS::FAIL_MISSING_TEST_DATA );
if (f)
fclose(f);
return;
}
for(;;)
{
if( !fgets( buf, sizeof(buf)-3, f ))
break;
size_t len = strlen(buf);
while( len > 0 && isspace(buf[len-1]))
buf[--len] = '\0';
if( buf[0] == '#')
continue;
filepath = cv::format("%scv/stereo/case%d/%s", ts->get_data_path().c_str(), testcase, buf );
imglist.push_back(string(filepath));
}
fclose(f);
if( imglist.size() == 0 || imglist.size() % 2 != 0 )
{
ts->printf( cvtest::TS::LOG, "The number of images is 0 or an odd number in the case #%d\n", testcase );
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
return;
}
int nframes = (int)(imglist.size()/2);
int npoints = patternSize.width*patternSize.height;
vector<vector<Point3f> > objpt(nframes);
vector<vector<Point2f> > imgpt1(nframes);
vector<vector<Point2f> > imgpt2(nframes);
Size imgsize;
for( int i = 0; i < nframes; i++ )
{
Mat left = imread(imglist[i*2]);
Mat right = imread(imglist[i*2+1]);
if(left.empty() || right.empty())
{
ts->printf( cvtest::TS::LOG, "Can not load images %s and %s, testcase %d\n",
imglist[i*2].c_str(), imglist[i*2+1].c_str(), testcase );
ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );
return;
}
imgsize = left.size();
bool found1 = findChessboardCorners(left, patternSize, imgpt1[i]);
bool found2 = findChessboardCorners(right, patternSize, imgpt2[i]);
if(!found1 || !found2)
{
ts->printf( cvtest::TS::LOG, "The function could not detect boards (%d x %d) on the images %s and %s, testcase %d\n",
patternSize.width, patternSize.height,
imglist[i*2].c_str(), imglist[i*2+1].c_str(), testcase );
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
return;
}
for( int j = 0; j < npoints; j++ )
objpt[i].push_back(Point3f((float)(j%patternSize.width), (float)(j/patternSize.width), 0.f));
}
vector<RotMat> rotMats1(nframes);
vector<Vec3d> transVecs1(nframes);
vector<RotMat> rotMats2(nframes);
vector<Vec3d> transVecs2(nframes);
vector<double> rmsErrorPerView1(nframes);
vector<double> rmsErrorPerView2(nframes);
vector<double> rmsErrorPerViewFromReprojectedImgPts1(nframes);
vector<double> rmsErrorPerViewFromReprojectedImgPts2(nframes);
Mat M1 = Mat::eye(3,3,CV_64F), M2 = Mat::eye(3,3,CV_64F), D1(5,1,CV_64F), D2(5,1,CV_64F);
Mat R_stereo, T_stereo, R_register, T_register;
Mat E_stereo, F_stereo, E_register, F_register;
M1.at<double>(0,2) = M2.at<double>(0,2)=(imgsize.width-1)*0.5;
M1.at<double>(1,2) = M2.at<double>(1,2)=(imgsize.height-1)*0.5;
D1 = Scalar::all(0);
D2 = Scalar::all(0
);
// Initialize the intrinsics
calibrateStereoCamera(objpt, imgpt1, imgpt2, M1, D1, M2, D2, imgsize, R_stereo, T_stereo, E_stereo, F_stereo,
rotMats1, transVecs1, rmsErrorPerView1, rmsErrorPerView2,
TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6),
CALIB_SAME_FOCAL_LENGTH
//+ CV_CALIB_FIX_ASPECT_RATIO
+ CALIB_FIX_PRINCIPAL_POINT
+ CALIB_ZERO_TANGENT_DIST
+ CALIB_FIX_K3
+ CALIB_FIX_K4 + CALIB_FIX_K5 //+ CV_CALIB_FIX_K6
);
// Use the fixed intrinsics to esimtate with two different methods
double rmsErrorFromStereoCalibStereo = calibrateStereoCamera(objpt, imgpt1, imgpt2, M1, D1, M2, D2, imgsize, R_stereo, T_stereo, E_stereo, F_stereo,
rotMats1, transVecs1, rmsErrorPerView1, rmsErrorPerView2,
TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6),
CALIB_FIX_INTRINSIC
);
double rmsErrorFromStereoCalibRegister = registerCameraPair(objpt, objpt, imgpt1, imgpt2, M1, D1, CALIB_MODEL_PINHOLE, M2, D2, CALIB_MODEL_PINHOLE, R_register, T_register, E_register, F_register,
rotMats2, transVecs2, rmsErrorPerView1, rmsErrorPerView2,
TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6),
0
);
/* rmsErrorFromStereoCalibRegister /= nframes*npoints; */
if (rmsErrorFromStereoCalibRegister > maxReprojErr)
{
ts->printf(cvtest::TS::LOG, "The average reprojection error is too big (=%g), testcase %d\n",
rmsErrorFromStereoCalibRegister, testcase);
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
return;
}
double rmsErrorFromReprojectedImgPts = 0.0f;
if (rotMats1.empty() || transVecs1.empty())
{
rmsErrorPerViewFromReprojectedImgPts1 = rmsErrorPerView1;
rmsErrorPerViewFromReprojectedImgPts2 = rmsErrorPerView2;
rmsErrorFromReprojectedImgPts = rmsErrorFromStereoCalibRegister;
}
else
{
size_t totalPoints = 0;
double totalErr[2] = { 0, 0 };
for (size_t i = 0; i < objpt.size(); ++i) {
RotMat r1 = rotMats1[i];
Vec3d t1 = transVecs1[i];
RotMat r2 = Mat(R_register * r1);
Mat T2t = R_register * t1;
Vec3d t2 = Mat(T2t + T_register);
vector<Point2f> reprojectedImgPts[2] = { vector<Point2f>(nframes),
vector<Point2f>(nframes) };
projectPoints(objpt[i], r1, t1, M1, D1, reprojectedImgPts[0]);
projectPoints(objpt[i], r2, t2, M2, D2, reprojectedImgPts[1]);
double viewErr[2];
viewErr[0] = cv::norm(imgpt1[i], reprojectedImgPts[0], cv::NORM_L2SQR);
viewErr[1] = cv::norm(imgpt2[i], reprojectedImgPts[1], cv::NORM_L2SQR);
size_t n = objpt[i].size();
totalErr[0] += viewErr[0];
totalErr[1] += viewErr[1];
totalPoints += n;
rmsErrorPerViewFromReprojectedImgPts1[i] = sqrt(viewErr[0] / n);
rmsErrorPerViewFromReprojectedImgPts2[i] = sqrt(viewErr[1] / n);
}
rmsErrorFromReprojectedImgPts = std::sqrt((totalErr[0] + totalErr[1]) / (2 * totalPoints));
}
if (abs(rmsErrorFromStereoCalibRegister - rmsErrorFromReprojectedImgPts) > maxDiffBtwRmsErrors)
{
ts->printf(cvtest::TS::LOG,
"The difference of the average reprojection error from the calibration function and from the "
"reprojected image points is too big (|%g - %g| = %g), testcase %d\n",
rmsErrorFromStereoCalibRegister, rmsErrorFromReprojectedImgPts,
(rmsErrorFromStereoCalibRegister - rmsErrorFromReprojectedImgPts), testcase);
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
return;
}
/* ----- Compare per view rms re-projection errors ----- */
CV_Assert(rmsErrorPerView1.size() == (size_t)nframes);
CV_Assert(rmsErrorPerViewFromReprojectedImgPts1.size() == (size_t)nframes);
CV_Assert(rmsErrorPerView2.size() == (size_t)nframes);
CV_Assert(rmsErrorPerViewFromReprojectedImgPts2.size() == (size_t)nframes);
int code1 = compare(&rmsErrorPerView1[0], &rmsErrorPerViewFromReprojectedImgPts1[0], nframes,
maxDiffBtwRmsErrors, "per view errors vector");
int code2 = compare(&rmsErrorPerView2[0], &rmsErrorPerViewFromReprojectedImgPts2[0], nframes,
maxDiffBtwRmsErrors, "per view errors vector");
if (code1 < 0)
{
ts->printf(cvtest::TS::LOG,
"Some of the per view rms reprojection errors differ between calibration function and reprojected "
"points, for the first camera, testcase %d\n",
testcase);
ts->set_failed_test_info(code1);
return;
}
if (code2 < 0)
{
ts->printf(cvtest::TS::LOG,
"Some of the per view rms reprojection errors differ between calibration function and reprojected "
"points, for the second camera, testcase %d\n",
testcase);
ts->set_failed_test_info(code2);
return;
}
/* ----- compare the result from stereoCalibrate and registerCameras ----- */
if (abs(rmsErrorFromStereoCalibStereo - rmsErrorFromStereoCalibRegister) > maxDiffBtwRmsErrors)
{
ts->printf(cvtest::TS::LOG,
"The difference of the average reprojection error from the register camera and stero calibration is too large (|%g - %g| = %g), testcase %d\n",
rmsErrorFromStereoCalibStereo, rmsErrorFromStereoCalibRegister,
(rmsErrorFromStereoCalibStereo - rmsErrorFromStereoCalibRegister), testcase);
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
return;
}
int code3 = compare(&R_stereo.at<double>(0), &R_register.at<double>(0), 9, maxDiffBtwEstErrors, "R vector");
int code4 = compare(&T_stereo.at<double>(0), &T_register.at<double>(0), 3, maxDiffBtwEstErrors, "T vector");
int code5 = compare(&E_stereo.at<double>(0), &E_register.at<double>(0), 9, maxDiffBtwEstErrors, "E vector");
int code6 = compare(&F_stereo.at<double>(0), &F_register.at<double>(0), 9, maxDiffBtwEstErrors, "F vector");
if (code3 < 0)
{
ts->printf(cvtest::TS::LOG,
"The estimated R does not match, testcase %d\n",
testcase);
ts->set_failed_test_info(code3);
return;
}
if (code4 < 0)
{
ts->printf(cvtest::TS::LOG,
"The estimated T does not match, testcase %d\n",
testcase);
ts->set_failed_test_info(code4);
return;
}
if (code5 < 0)
{
ts->printf(cvtest::TS::LOG,
"The estimated E does not match, testcase %d\n",
testcase);
ts->set_failed_test_info(code5);
return;
}
if (code6 < 0)
{
ts->printf(cvtest::TS::LOG,
"The estimated F does not match, testcase %d\n",
testcase);
ts->set_failed_test_info(code6);
return;
}
}
}
//-------------------------------- CV_CameraRegistrationTest_CPP ------------------------------
class CV_CameraRegistrationTest_CPP : public CV_CameraRegistrationTest
{
public:
CV_CameraRegistrationTest_CPP() {}
protected:
virtual double registerCameraPair( const vector<vector<Point3f> >& objectPoints1,
const vector<vector<Point3f> >& objectPoints2,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1, CameraModel cameraModel1,
Mat& cameraMatrix2, Mat& distCoeffs2, CameraModel cameraModel2,
Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags );
virtual double calibrateStereoCamera( const vector<vector<Point3f> >& objectPoints,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1,
Mat& cameraMatrix2, Mat& distCoeffs2,
Size imageSize, Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags );
};
double CV_CameraRegistrationTest_CPP::registerCameraPair( const vector<vector<Point3f> >& objectPoints1,
const vector<vector<Point3f> >& objectPoints2,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1, CameraModel cameraModel1,
Mat& cameraMatrix2, Mat& distCoeffs2, CameraModel cameraModel2,
Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags )
{
vector<Mat> rvecs, tvecs;
Mat perViewErrorsMat;
double avgErr = registerCameras(objectPoints1, objectPoints2, imagePoints1, imagePoints2,
cameraMatrix1, distCoeffs1, cameraModel1,
cameraMatrix2, distCoeffs2, cameraModel2,
R, T, E, F,
rvecs, tvecs, perViewErrorsMat,
flags, criteria);
size_t numImgs = imagePoints1.size();
if (perViewErrors1.size() != numImgs)
{
perViewErrors1.resize(numImgs);
}
if (perViewErrors2.size() != numImgs)
{
perViewErrors2.resize(numImgs);
}
for (int i = 0; i < (int)numImgs; i++)
{
perViewErrors1[i] = perViewErrorsMat.at<double>(i, 0);
perViewErrors2[i] = perViewErrorsMat.at<double>(i, 1);
}
if (rotationMatrices.size() != numImgs)
{
rotationMatrices.resize(numImgs);
}
if (translationVectors.size() != numImgs)
{
translationVectors.resize(numImgs);
}
for( size_t i = 0; i < numImgs; i++ )
{
Mat r9;
cv::Rodrigues( rvecs[i], r9 );
r9.convertTo(rotationMatrices[i], CV_64F);
tvecs[i].convertTo(translationVectors[i], CV_64F);
}
return avgErr;
}
double CV_CameraRegistrationTest_CPP::calibrateStereoCamera( const vector<vector<Point3f> >& objectPoints,
const vector<vector<Point2f> >& imagePoints1,
const vector<vector<Point2f> >& imagePoints2,
Mat& cameraMatrix1, Mat& distCoeffs1,
Mat& cameraMatrix2, Mat& distCoeffs2,
Size imageSize, Mat& R, Mat& T,
Mat& E, Mat& F,
std::vector<RotMat>& rotationMatrices, std::vector<Vec3d>& translationVectors,
vector<double>& perViewErrors1, vector<double>& perViewErrors2,
TermCriteria criteria, int flags )
{
vector<Mat> rvecs, tvecs;
Mat perViewErrorsMat;
double avgErr = stereoCalibrate( objectPoints, imagePoints1, imagePoints2,
cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2,
imageSize, R, T, E, F,
rvecs, tvecs, perViewErrorsMat,
flags, criteria );
size_t numImgs = imagePoints1.size();
if (perViewErrors1.size() != numImgs)
{
perViewErrors1.resize(numImgs);
}
if (perViewErrors2.size() != numImgs)
{
perViewErrors2.resize(numImgs);
}
for (int i = 0; i < (int)numImgs; i++)
{
perViewErrors1[i] = perViewErrorsMat.at<double>(i, 0);
perViewErrors2[i] = perViewErrorsMat.at<double>(i, 1);
}
if (rotationMatrices.size() != numImgs)
{
rotationMatrices.resize(numImgs);
}
if (translationVectors.size() != numImgs)
{
translationVectors.resize(numImgs);
}
for( size_t i = 0; i < numImgs; i++ )
{
Mat r9;
cv::Rodrigues( rvecs[i], r9 );
r9.convertTo(rotationMatrices[i], CV_64F);
tvecs[i].convertTo(translationVectors[i], CV_64F);
}
return avgErr;
}
TEST(Calib3d_CameraRegistration_CPP, regression) { CV_CameraRegistrationTest_CPP test; test.safe_run(); }
///////////////////////////////////////////////////////////////////////////////////////////////////
TEST(Calib3d_CalibrateCamera_CPP, regression) { CV_CameraCalibrationTest_CPP test; test.safe_run(); }
TEST(Calib3d_CalibrationMatrixValues_CPP, accuracy) { CV_CalibrationMatrixValuesTest_CPP test; test.safe_run(); }
TEST(Calib3d_ProjectPoints_CPP, regression) { CV_ProjectPointsTest_CPP test; test.safe_run(); }
+114
View File
@@ -655,4 +655,118 @@ TEST_F(fisheyeTest, multiview_calibration)
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