From 9c7e0bfd33cb65a57b06d1bf9a81e663fda01d22 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Fri, 25 Apr 2014 14:49:36 +0400 Subject: [PATCH 01/15] Added fisheye camera model --- .../include/opencv2/calib3d/calib3d.hpp | 127 ++ modules/calib3d/src/fisheye.cpp | 1217 +++++++++++++++++ modules/calib3d/test/test_fisheye.cpp | 423 ++++++ 3 files changed, 1767 insertions(+) create mode 100644 modules/calib3d/src/fisheye.cpp create mode 100644 modules/calib3d/test/test_fisheye.cpp diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index f213a114f..2675ad402 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -45,6 +45,7 @@ #include "opencv2/core/core.hpp" #include "opencv2/features2d/features2d.hpp" +#include "opencv2/core/affine.hpp" #ifdef __cplusplus extern "C" { @@ -744,6 +745,132 @@ CV_EXPORTS_W int estimateAffine3D(InputArray src, InputArray dst, OutputArray out, OutputArray inliers, double ransacThreshold=3, double confidence=0.99); + +class Fisheye +{ +public: + + //Definitions: + // Let P be a point in 3D of coordinates X in the world reference frame (stored in the matrix X) + // The coordinate vector of P in the camera reference frame is: Xc = R*X + T + // where R is the rotation matrix corresponding to the rotation vector om: R = rodrigues(om); + // call x, y and z the 3 coordinates of Xc: x = Xc(1); y = Xc(2); z = Xc(3); + // The pinehole projection coordinates of P is [a;b] where a=x/z and b=y/z. + // call r^2 = a^2 + b^2, + // call theta = atan(r), + // + // Fisheye distortion -> theta_d = theta * (1 + k(1)*theta^2 + k(2)*theta^4 + k(3)*theta^6 + k(4)*theta^8) + // + // The distorted point coordinates are: xd = [xx;yy] where: + // + // xx = (theta_d / r) * x + // yy = (theta_d / r) * y + // + // Finally, convertion into pixel coordinates: The final pixel coordinates vector xp=[xxp;yyp] where: + // + // xxp = f(1)*(xx + alpha*yy) + c(1) + // yyp = f(2)*yy + c(2) + + enum{ + CALIB_USE_INTRINSIC_GUESS = 1, + CALIB_RECOMPUTE_EXTRINSIC = 2, + CALIB_CHECK_COND = 4, + CALIB_FIX_SKEW = 8, + CALIB_FIX_K1 = 16, + CALIB_FIX_K2 = 32, + CALIB_FIX_K3 = 64, + CALIB_FIX_K4 = 128 + }; + + //! projects 3D points using fisheye model + static void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, + InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); + + //! projects points using fisheye model + static void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, + InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); + + //! distorts 2D points using fisheye model + static void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); + + //! undistorts 2D points using fisheye model + static void undistortPoints(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()); + + //! computing undistortion and rectification maps for image transform by cv::remap() + //! If D is empty zero distortion is used, if R or P is empty identity matrixes are used + static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, + const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); + + //! undistorts image, optinally chanes resolution and camera matrix. If Knew zero identity matrix is used + static void undistortImage(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); + + //! estimates new camera matrix for undistortion or rectification + static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); + + //! stereo rectification for fisheye camera model + static void stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, + InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, + int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ); + + //! performs camera calibaration + static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, + TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); + + //! stereo rectification estimation + static void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, + OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), + double balance = 0.0, double fov_scale = 1.0); +}; + + + +namespace internal { + +struct IntrinsicParams +{ + Vec2d f; + Vec2d c; + Vec4d k; + double alpha; + std::vector 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); +Mat ComputeHomography(Mat m, Mat M); + +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); + +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); + +} } #endif diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp new file mode 100644 index 000000000..0dcc5e70f --- /dev/null +++ b/modules/calib3d/src/fisheye.cpp @@ -0,0 +1,1217 @@ +#include "opencv2/opencv.hpp" +#include "opencv2/core/affine.hpp" +#include "opencv2/core/affine.hpp" + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::projectPoints + +namespace cv { namespace +{ + struct JacobianRow + { + Vec2d df, dc; + Vec4d dk; + Vec3d dom, dT; + double dalpha; + }; +}} + +void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, + InputArray K, InputArray D, double alpha, OutputArray jacobian) +{ + projectPoints(objectPoints, imagePoints, affine.rvec(), affine.translation(), K, D, alpha, jacobian); +} + +void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray _rvec, + InputArray _tvec, InputArray _K, InputArray _D, double alpha, OutputArray jacobian) +{ + // will support only 3-channel data now for points + CV_Assert(objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3); + imagePoints.create(objectPoints.size(), CV_MAKETYPE(objectPoints.depth(), 2)); + size_t n = objectPoints.total(); + + CV_Assert(_rvec.total() * _rvec.channels() == 3 && (_rvec.depth() == CV_32F || _rvec.depth() == CV_64F)); + CV_Assert(_tvec.total() * _tvec.channels() == 3 && (_tvec.depth() == CV_32F || _tvec.depth() == CV_64F)); + CV_Assert(_tvec.getMat().isContinuous() && _rvec.getMat().isContinuous()); + + Vec3d om = _rvec.depth() == CV_32F ? (Vec3d)*_rvec.getMat().ptr() : *_rvec.getMat().ptr(); + Vec3d T = _tvec.depth() == CV_32F ? (Vec3d)*_tvec.getMat().ptr() : *_tvec.getMat().ptr(); + + CV_Assert(_K.size() == Size(3,3) && (_K.type() == CV_32F || _K.type() == CV_64F) && _D.type() == _K.type() && _D.total() == 4); + + cv::Vec2d f, c; + if (_K.depth() == CV_32F) + { + Matx33f K = _K.getMat(); + f = Vec2f(K(0, 0), K(1, 1)); + c = Vec2f(K(0, 2), K(1, 2)); + } + else + { + Matx33d K = _K.getMat(); + f = Vec2d(K(0, 0), K(1, 1)); + c = Vec2d(K(0, 2), K(1, 2)); + } + + Vec4d k = _D.depth() == CV_32F ? (Vec4d)*_D.getMat().ptr(): *_D.getMat().ptr(); + + JacobianRow *Jn = 0; + if (jacobian.needed()) + { + int nvars = 2 + 2 + 1 + 4 + 3 + 3; // f, c, alpha, k, om, T, + jacobian.create(2*(int)n, nvars, CV_64F); + Jn = jacobian.getMat().ptr(0); + } + + Matx33d R; + Matx dRdom; + Rodrigues(om, R, dRdom); + Affine3d aff(om, T); + + const Vec3f* Xf = objectPoints.getMat().ptr(); + const Vec3d* Xd = objectPoints.getMat().ptr(); + Vec2f *xpf = imagePoints.getMat().ptr(); + Vec2d *xpd = imagePoints.getMat().ptr(); + + for(size_t i = 0; i < n; ++i) + { + Vec3d Xi = objectPoints.depth() == CV_32F ? (Vec3d)Xf[i] : Xd[i]; + Vec3d Y = aff*Xi; + + Vec2d x(Y[0]/Y[2], Y[1]/Y[2]); + + double r2 = x.dot(x); + double r = std::sqrt(r2); + + // Angle of the incoming ray: + double theta = atan(r); + + double theta2 = theta*theta, theta3 = theta2*theta, theta4 = theta2*theta2, theta5 = theta4*theta, + theta6 = theta3*theta3, theta7 = theta6*theta, theta8 = theta4*theta4, theta9 = theta8*theta; + + double theta_d = theta + k[0]*theta3 + k[1]*theta5 + k[2]*theta7 + k[3]*theta9; + + double inv_r = r > 1e-8 ? 1.0/r : 1; + double cdist = r > 1e-8 ? theta_d * inv_r : 1; + + Vec2d xd1 = x * cdist; + Vec2d xd3(xd1[0] + alpha*xd1[1], xd1[1]); + Vec2d final_point(xd3[0] * f[0] + c[0], xd3[1] * f[1] + c[1]); + + if (objectPoints.depth() == CV_32F) + xpf[i] = final_point; + else + xpd[i] = final_point; + + if (jacobian.needed()) + { + //Vec3d Xi = pdepth == CV_32F ? (Vec3d)Xf[i] : Xd[i]; + //Vec3d Y = aff*Xi; + double dYdR[] = { Xi[0], Xi[1], Xi[2], 0, 0, 0, 0, 0, 0, + 0, 0, 0, Xi[0], Xi[1], Xi[2], 0, 0, 0, + 0, 0, 0, 0, 0, 0, Xi[0], Xi[1], Xi[2] }; + + Matx33d dYdom_data = Matx(dYdR) * dRdom.t(); + const Vec3d *dYdom = (Vec3d*)dYdom_data.val; + + Matx33d dYdT_data = Matx33d::eye(); + const Vec3d *dYdT = (Vec3d*)dYdT_data.val; + + //Vec2d x(Y[0]/Y[2], Y[1]/Y[2]); + Vec3d dxdom[2]; + dxdom[0] = (1.0/Y[2]) * dYdom[0] - x[0]/Y[2] * dYdom[2]; + dxdom[1] = (1.0/Y[2]) * dYdom[1] - x[1]/Y[2] * dYdom[2]; + + Vec3d dxdT[2]; + dxdT[0] = (1.0/Y[2]) * dYdT[0] - x[0]/Y[2] * dYdT[2]; + dxdT[1] = (1.0/Y[2]) * dYdT[1] - x[1]/Y[2] * dYdT[2]; + + //double r2 = x.dot(x); + Vec3d dr2dom = 2 * x[0] * dxdom[0] + 2 * x[1] * dxdom[1]; + Vec3d dr2dT = 2 * x[0] * dxdT[0] + 2 * x[1] * dxdT[1]; + + //double r = std::sqrt(r2); + double drdr2 = r > 1e-8 ? 1.0/(2*r) : 1; + Vec3d drdom = drdr2 * dr2dom; + Vec3d drdT = drdr2 * dr2dT; + + // Angle of the incoming ray: + //double theta = atan(r); + double dthetadr = 1.0/(1+r2); + Vec3d dthetadom = dthetadr * drdom; + Vec3d dthetadT = dthetadr * drdT; + + //double theta_d = theta + k[0]*theta3 + k[1]*theta5 + k[2]*theta7 + k[3]*theta9; + double dtheta_ddtheta = 1 + 3*k[0]*theta2 + 5*k[1]*theta4 + 7*k[2]*theta6 + 9*k[3]*theta8; + Vec3d dtheta_ddom = dtheta_ddtheta * dthetadom; + Vec3d dtheta_ddT = dtheta_ddtheta * dthetadT; + Vec4d dtheta_ddk = Vec4d(theta3, theta5, theta7, theta9); + + //double inv_r = r > 1e-8 ? 1.0/r : 1; + //double cdist = r > 1e-8 ? theta_d / r : 1; + Vec3d dcdistdom = inv_r * (dtheta_ddom - cdist*drdom); + Vec3d dcdistdT = inv_r * (dtheta_ddT - cdist*drdT); + Vec4d dcdistdk = inv_r * dtheta_ddk; + + //Vec2d xd1 = x * cdist; + Vec4d dxd1dk[2]; + Vec3d dxd1dom[2], dxd1dT[2]; + dxd1dom[0] = x[0] * dcdistdom + cdist * dxdom[0]; + dxd1dom[1] = x[1] * dcdistdom + cdist * dxdom[1]; + dxd1dT[0] = x[0] * dcdistdT + cdist * dxdT[0]; + dxd1dT[1] = x[1] * dcdistdT + cdist * dxdT[1]; + dxd1dk[0] = x[0] * dcdistdk; + dxd1dk[1] = x[1] * dcdistdk; + + //Vec2d xd3(xd1[0] + alpha*xd1[1], xd1[1]); + Vec4d dxd3dk[2]; + Vec3d dxd3dom[2], dxd3dT[2]; + dxd3dom[0] = dxd1dom[0] + alpha * dxd1dom[1]; + dxd3dom[1] = dxd1dom[1]; + dxd3dT[0] = dxd1dT[0] + alpha * dxd1dT[1]; + dxd3dT[1] = dxd1dT[1]; + dxd3dk[0] = dxd1dk[0] + alpha * dxd1dk[1]; + dxd3dk[1] = dxd1dk[1]; + + Vec2d dxd3dalpha(xd1[1], 0); + + //final jacobian + Jn[0].dom = f[0] * dxd3dom[0]; + Jn[1].dom = f[1] * dxd3dom[1]; + + Jn[0].dT = f[0] * dxd3dT[0]; + Jn[1].dT = f[1] * dxd3dT[1]; + + Jn[0].dk = f[0] * dxd3dk[0]; + Jn[1].dk = f[1] * dxd3dk[1]; + + Jn[0].dalpha = f[0] * dxd3dalpha[0]; + Jn[1].dalpha = 0; //f[1] * dxd3dalpha[1]; + + Jn[0].df = Vec2d(xd3[0], 0); + Jn[1].df = Vec2d(0, xd3[1]); + + Jn[0].dc = Vec2d(1, 0); + Jn[1].dc = Vec2d(0, 1); + + //step to jacobian rows for next point + Jn += 2; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::distortPoints + +void cv::Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha) +{ + // will support only 2-channel data now for points + CV_Assert(undistorted.type() == CV_32FC2 || undistorted.type() == CV_64FC2); + distorted.create(undistorted.size(), undistorted.type()); + size_t n = undistorted.total(); + + CV_Assert(K.size() == Size(3,3) && (K.type() == CV_32F || K.type() == CV_64F) && D.total() == 4); + + cv::Vec2d f, c; + if (K.depth() == CV_32F) + { + Matx33f camMat = K.getMat(); + f = Vec2f(camMat(0, 0), camMat(1, 1)); + c = Vec2f(camMat(0, 2), camMat(1, 2)); + } + else + { + Matx33d camMat = K.getMat(); + f = Vec2d(camMat(0, 0), camMat(1, 1)); + c = Vec2d(camMat(0 ,2), camMat(1, 2)); + } + + Vec4d k = D.depth() == CV_32F ? (Vec4d)*D.getMat().ptr(): *D.getMat().ptr(); + + const Vec2f* Xf = undistorted.getMat().ptr(); + const Vec2d* Xd = undistorted.getMat().ptr(); + Vec2f *xpf = distorted.getMat().ptr(); + Vec2d *xpd = distorted.getMat().ptr(); + + for(size_t i = 0; i < n; ++i) + { + Vec2d x = undistorted.depth() == CV_32F ? (Vec2d)Xf[i] : Xd[i]; + + double r2 = x.dot(x); + double r = std::sqrt(r2); + + // Angle of the incoming ray: + double theta = atan(r); + + double theta2 = theta*theta, theta3 = theta2*theta, theta4 = theta2*theta2, theta5 = theta4*theta, + theta6 = theta3*theta3, theta7 = theta6*theta, theta8 = theta4*theta4, theta9 = theta8*theta; + + double theta_d = theta + k[0]*theta3 + k[1]*theta5 + k[2]*theta7 + k[3]*theta9; + + double inv_r = r > 1e-8 ? 1.0/r : 1; + double cdist = r > 1e-8 ? theta_d * inv_r : 1; + + Vec2d xd1 = x * cdist; + Vec2d xd3(xd1[0] + alpha*xd1[1], xd1[1]); + Vec2d final_point(xd3[0] * f[0] + c[0], xd3[1] * f[1] + c[1]); + + if (undistorted.depth() == CV_32F) + xpf[i] = final_point; + else + xpd[i] = final_point; + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::undistortPoints + +void cv::Fisheye::undistortPoints( InputArray distorted, OutputArray undistorted, InputArray _K, InputArray _D, InputArray _R, InputArray _P) +{ + // will support only 2-channel data now for points + CV_Assert(distorted.type() == CV_32FC2 || distorted.type() == CV_64FC2); + undistorted.create(distorted.size(), distorted.type()); + + CV_Assert(_P.empty() || _P.size() == Size(3, 3) || _P.size() == Size(4, 3)); + CV_Assert(_R.empty() || _R.size() == Size(3, 3) || _R.total() * _R.channels() == 3); + CV_Assert(_D.total() == 4 && _K.size() == Size(3, 3) && (_K.depth() == CV_32F || _K.depth() == CV_64F)); + + cv::Vec2d f, c; + if (_K.depth() == CV_32F) + { + Matx33f camMat = _K.getMat(); + f = Vec2f(camMat(0, 0), camMat(1, 1)); + c = Vec2f(camMat(0, 2), camMat(1, 2)); + } + else + { + Matx33d camMat = _K.getMat(); + f = Vec2d(camMat(0, 0), camMat(1, 1)); + c = Vec2d(camMat(0, 2), camMat(1, 2)); + } + + Vec4d k = _D.depth() == CV_32F ? (Vec4d)*_D.getMat().ptr(): *_D.getMat().ptr(); + + cv::Matx33d RR = cv::Matx33d::eye(); + if (!_R.empty() && _R.total() * _R.channels() == 3) + { + cv::Vec3d rvec; + _R.getMat().convertTo(rvec, CV_64F); + RR = cv::Affine3d(rvec).rotation(); + } + else if (!_R.empty() && _R.size() == Size(3, 3)) + _R.getMat().convertTo(RR, CV_64F); + + if(!_P.empty()) + { + cv::Matx33d P; + _P.getMat().colRange(0, 3).convertTo(P, CV_64F); + RR = P * RR; + } + + // start undistorting + const cv::Vec2f* srcf = distorted.getMat().ptr(); + const cv::Vec2d* srcd = distorted.getMat().ptr(); + cv::Vec2f* dstf = undistorted.getMat().ptr(); + cv::Vec2d* dstd = undistorted.getMat().ptr(); + + size_t n = distorted.total(); + int sdepth = distorted.depth(); + + for(size_t i = 0; i < n; i++ ) + { + Vec2d pi = sdepth == CV_32F ? (Vec2d)srcf[i] : srcd[i]; // image point + Vec2d pw((pi[0] - c[0])/f[0], (pi[1] - c[1])/f[1]); // world point + + double scale = 1.0; + + double theta_d = sqrt(pw[0]*pw[0] + pw[1]*pw[1]); + if (theta_d > 1e-8) + { + // compensate distortion iteratively + double theta = theta_d; + for(int j = 0; j < 10; j++ ) + { + double theta2 = theta*theta, theta4 = theta2*theta2, theta6 = theta4*theta2, theta8 = theta6*theta2; + theta = theta_d / (1 + k[0] * theta2 + k[1] * theta4 + k[2] * theta6 + k[3] * theta8); + } + + scale = std::tan(theta) / theta_d; + } + + Vec2d pu = pw * scale; //undistorted point + + // reproject + Vec3d pr = RR * Vec3d(pu[0], pu[1], 1.0); // rotated point optionally multiplied by new camera matrix + Vec2d fi(pr[0]/pr[2], pr[1]/pr[2]); // final + + if( sdepth == CV_32F ) + dstf[i] = fi; + else + dstd[i] = fi; + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::undistortPoints + +void cv::Fisheye::initUndistortRectifyMap( InputArray _K, InputArray _D, InputArray _R, InputArray _P, + const cv::Size& size, int m1type, OutputArray map1, OutputArray map2 ) +{ + CV_Assert( m1type == CV_16SC2 || m1type == CV_32F || m1type <=0 ); + map1.create( size, m1type <= 0 ? CV_16SC2 : m1type ); + map2.create( size, map1.type() == CV_16SC2 ? CV_16UC1 : CV_32F ); + + CV_Assert((_K.depth() == CV_32F || _K.depth() == CV_64F) && (_D.depth() == CV_32F || _D.depth() == CV_64F)); + CV_Assert((_P.depth() == CV_32F || _P.depth() == CV_64F) && (_R.depth() == CV_32F || _R.depth() == CV_64F)); + CV_Assert(_K.size() == Size(3, 3) && (_D.empty() || _D.total() == 4)); + CV_Assert(_R.empty() || _R.size() == Size(3, 3) || _R.total() * _R.channels() == 3); + CV_Assert(_P.empty() || _P.size() == Size(3, 3) || _P.size() == Size(4, 3)); + + cv::Vec2d f, c; + if (_K.depth() == CV_32F) + { + Matx33f camMat = _K.getMat(); + f = Vec2f(camMat(0, 0), camMat(1, 1)); + c = Vec2f(camMat(0, 2), camMat(1, 2)); + } + else + { + Matx33d camMat = _K.getMat(); + f = Vec2d(camMat(0, 0), camMat(1, 1)); + c = Vec2d(camMat(0, 2), camMat(1, 2)); + } + + Vec4d k = Vec4d::all(0); + if (!_D.empty()) + k = _D.depth() == CV_32F ? (Vec4d)*_D.getMat().ptr(): *_D.getMat().ptr(); + + cv::Matx33d R = cv::Matx33d::eye(); + if (!_R.empty() && _R.total() * _R.channels() == 3) + { + cv::Vec3d rvec; + _R.getMat().convertTo(rvec, CV_64F); + R = Affine3d(rvec).rotation(); + } + else if (!_R.empty() && _R.size() == Size(3, 3)) + _R.getMat().convertTo(R, CV_64F); + + cv::Matx33d P = cv::Matx33d::eye(); + if (!_P.empty()) + _P.getMat().colRange(0, 3).convertTo(P, CV_64F); + + cv::Matx33d iR = (P * R).inv(cv::DECOMP_SVD); + + for( int i = 0; i < size.height; ++i) + { + float* m1f = map1.getMat().ptr(i); + float* m2f = map2.getMat().ptr(i); + short* m1 = (short*)m1f; + ushort* m2 = (ushort*)m2f; + + double _x = i*iR(0, 1) + iR(0, 2), + _y = i*iR(1, 1) + iR(1, 2), + _w = i*iR(2, 1) + iR(2, 2); + + for( int j = 0; j < size.width; ++j) + { + double x = _x/_w, y = _y/_w; + + double r = sqrt(x*x + y*y); + double theta = atan(r); + + double theta2 = theta*theta, theta4 = theta2*theta2, theta6 = theta4*theta2, theta8 = theta4*theta4; + double theta_d = theta * (1 + k[0]*theta2 + k[1]*theta4 + k[2]*theta6 + k[3]*theta8); + + double scale = (r == 0) ? 1.0 : theta_d / r; + double u = f[0]*x*scale + c[0]; + double v = f[1]*y*scale + c[1]; + + if( m1type == CV_16SC2 ) + { + int iu = cv::saturate_cast(u*cv::INTER_TAB_SIZE); + int iv = cv::saturate_cast(v*cv::INTER_TAB_SIZE); + m1[j*2+0] = (short)(iu >> cv::INTER_BITS); + m1[j*2+1] = (short)(iv >> cv::INTER_BITS); + m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1))); + } + else if( m1type == CV_32FC1 ) + { + m1f[j] = (float)u; + m2f[j] = (float)v; + } + + _x += iR(0, 0); + _y += iR(1, 0); + _w += iR(2, 0); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::undistortImage + +void cv::Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray Knew, const Size& new_size) +{ + Size size = new_size.area() != 0 ? new_size : distorted.size(); + + cv::Mat map1, map2; + initUndistortRectifyMap(K, D, cv::Matx33d::eye(), Knew, size, CV_16SC2, map1, map2 ); + cv::remap(distorted, undistorted, map1, map2, INTER_LINEAR, BORDER_CONSTANT); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::estimateNewCameraMatrixForUndistortRectify + +void cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + OutputArray P, double balance, const Size& new_size, double fov_scale) +{ + CV_Assert( K.size() == Size(3, 3) && (K.depth() == CV_32F || K.depth() == CV_64F)); + CV_Assert((D.empty() || D.total() == 4) && (D.depth() == CV_32F || D.depth() == CV_64F || D.empty())); + + int w = image_size.width, h = image_size.height; + balance = std::min(std::max(balance, 0.0), 1.0); + + cv::Mat points(1, 4, CV_64FC2); + Vec2d* pptr = points.ptr(); + pptr[0] = Vec2d(w/2, 0); + pptr[1] = Vec2d(w, h/2); + pptr[2] = Vec2d(w/2, h); + pptr[3] = Vec2d(0, h/2); + +#if 0 + const int N = 10; + cv::Mat points(1, N * 4, CV_64FC2); + Vec2d* pptr = points.ptr(); + for(int i = 0, k = 0; i < 10; ++i) + { + pptr[k++] = Vec2d(w/2, 0) - Vec2d(w/8, 0) + Vec2d(w/4/N*i, 0); + pptr[k++] = Vec2d(w/2, h-1) - Vec2d(w/8, h-1) + Vec2d(w/4/N*i, h-1); + + pptr[k++] = Vec2d(0, h/2) - Vec2d(0, h/8) + Vec2d(0, h/4/N*i); + pptr[k++] = Vec2d(w-1, h/2) - Vec2d(w-1, h/8) + Vec2d(w-1, h/4/N*i); + } +#endif + + undistortPoints(points, points, K, D, R); + cv::Scalar center_mass = mean(points); + cv::Vec2d cn(center_mass.val); + + double aspect_ratio = (K.depth() == CV_32F) ? K.getMat().at(0,0)/K.getMat().at (1,1) + : K.getMat().at(0,0)/K.getMat().at(1,1); + + // convert to identity ratio + cn[0] *= aspect_ratio; + for(size_t i = 0; i < points.total(); ++i) + pptr[i][1] *= aspect_ratio; + + double minx = DBL_MAX, miny = DBL_MAX, maxx = -DBL_MAX, maxy = -DBL_MAX; + for(size_t i = 0; i < points.total(); ++i) + { + miny = std::min(miny, pptr[i][1]); + maxy = std::max(maxy, pptr[i][1]); + minx = std::min(minx, pptr[i][0]); + maxx = std::max(maxx, pptr[i][0]); + } + +#if 0 + double minx = -DBL_MAX, miny = -DBL_MAX, maxx = DBL_MAX, maxy = DBL_MAX; + for(size_t i = 0; i < points.total(); ++i) + { + if (i % 4 == 0) miny = std::max(miny, pptr[i][1]); + if (i % 4 == 1) maxy = std::min(maxy, pptr[i][1]); + if (i % 4 == 2) minx = std::max(minx, pptr[i][0]); + if (i % 4 == 3) maxx = std::min(maxx, pptr[i][0]); + } +#endif + + double f1 = w * 0.5/(cn[0] - minx); + double f2 = w * 0.5/(maxx - cn[0]); + double f3 = h * 0.5 * aspect_ratio/(cn[1] - miny); + double f4 = h * 0.5 * aspect_ratio/(maxy - cn[1]); + + double fmin = std::min(f1, std::min(f2, std::min(f3, f4))); + double fmax = std::max(f1, std::max(f2, std::max(f3, f4))); + + double f = balance * fmin + (1.0 - balance) * fmax; + f *= fov_scale > 0 ? 1.0/fov_scale : 1.0; + + cv::Vec2d new_f(f, f), new_c = -cn * f + Vec2d(w, h * aspect_ratio) * 0.5; + + // restore aspect ratio + new_f[1] /= aspect_ratio; + new_c[1] /= aspect_ratio; + + if (new_size.area() > 0) + { + double rx = new_size.width /(double)image_size.width; + double ry = new_size.height/(double)image_size.height; + + new_f[0] *= rx; new_f[1] *= ry; + new_c[0] *= rx; new_c[1] *= ry; + } + + Mat(Matx33d(new_f[0], 0, new_c[0], + 0, new_f[1], new_c[1], + 0, 0, 1)).convertTo(P, P.empty() ? K.type() : P.type()); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::stereoRectify + +void cv::Fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size& imageSize, + InputArray _R, InputArray _tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, + OutputArray Q, int flags, const Size& newImageSize, double balance, double fov_scale) +{ + CV_Assert((_R.size() == Size(3, 3) || _R.total() * _R.channels() == 3) && (_R.depth() == CV_32F || _R.depth() == CV_64F)); + CV_Assert(_tvec.total() * _tvec.channels() == 3 && (_tvec.depth() == CV_32F || _tvec.depth() == CV_64F)); + + + cv::Mat aaa = _tvec.getMat().reshape(3, 1); + + Vec3d rvec; // Rodrigues vector + if (_R.size() == Size(3, 3)) + { + cv::Matx33d rmat; + _R.getMat().convertTo(rmat, CV_64F); + rvec = Affine3d(rmat).rvec(); + } + else if (_R.total() * _R.channels() == 3) + _R.getMat().convertTo(rvec, CV_64F); + + Vec3d tvec; + _tvec.getMat().convertTo(tvec, CV_64F); + + // rectification algorithm + rvec *= -0.5; // get average rotation + + Matx33d r_r; + Rodrigues(rvec, r_r); // rotate cameras to same orientation by averaging + + Vec3d t = r_r * tvec; + Vec3d uu(t[0] > 0 ? 1 : -1, 0, 0); + + // calculate global Z rotation + Vec3d ww = t.cross(uu); + double nw = norm(ww); + if (nw > 0.0) + ww *= acos(fabs(t[0])/cv::norm(t))/nw; + + Matx33d wr; + Rodrigues(ww, wr); + + // apply to both views + Matx33d ri1 = wr * r_r.t(); + Mat(ri1, false).convertTo(R1, R1.empty() ? CV_64F : R1.type()); + Matx33d ri2 = wr * r_r; + Mat(ri2, false).convertTo(R2, R2.empty() ? CV_64F : R2.type()); + Vec3d tnew = ri2 * tvec; + + // calculate projection/camera matrices. these contain the relevant rectified image internal params (fx, fy=fx, cx, cy) + Matx33d newK1, newK2; + estimateNewCameraMatrixForUndistortRectify(K1, D1, imageSize, R1, newK1, balance, newImageSize, fov_scale); + estimateNewCameraMatrixForUndistortRectify(K2, D2, imageSize, R2, newK2, balance, newImageSize, fov_scale); + + double fc_new = std::min(newK1(1,1), newK2(1,1)); + Point2d cc_new[2] = { Vec2d(newK1(0, 2), newK1(1, 2)), Vec2d(newK2(0, 2), newK2(1, 2)) }; + + // Vertical focal length must be the same for both images to keep the epipolar constraint use fy for fx also. + // For simplicity, set the principal points for both cameras to be the average + // of the two principal points (either one of or both x- and y- coordinates) + if( flags & cv::CALIB_ZERO_DISPARITY ) + cc_new[0] = cc_new[1] = (cc_new[0] + cc_new[1]) * 0.5; + else + cc_new[0].y = cc_new[1].y = (cc_new[0].y + cc_new[1].y)*0.5; + + Mat(Matx34d(fc_new, 0, cc_new[0].x, 0, + 0, fc_new, cc_new[0].y, 0, + 0, 0, 1, 0), false).convertTo(P1, P1.empty() ? CV_64F : P1.type()); + + Mat(Matx34d(fc_new, 0, cc_new[1].x, tnew[0]*fc_new, // baseline * focal length;, + 0, fc_new, cc_new[1].y, 0, + 0, 0, 1, 0), false).convertTo(P2, P2.empty() ? CV_64F : P2.type()); + + if (Q.needed()) + Mat(Matx44d(1, 0, 0, -cc_new[0].x, + 0, 1, 0, -cc_new[0].y, + 0, 0, 0, fc_new, + 0, 0, -1./tnew[0], (cc_new[0].x - cc_new[1].x)/tnew[0]), false).convertTo(Q, Q.empty() ? CV_64F : Q.depth()); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::calibrate + +double cv::Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, + int flags , cv::TermCriteria criteria) +{ + CV_Assert(!objectPoints.empty() && !imagePoints.empty() && objectPoints.total() == imagePoints.total()); + CV_Assert(objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3); + CV_Assert(imagePoints.type() == CV_32FC2 || imagePoints.type() == CV_64FC2); + CV_Assert((!K.empty() && K.size() == Size(3,3)) || K.empty()); + CV_Assert((!D.empty() && D.total() == 4) || D.empty()); + CV_Assert((!rvecs.empty() && rvecs.channels() == 3) || rvecs.empty()); + CV_Assert((!tvecs.empty() && tvecs.channels() == 3) || tvecs.empty()); + + CV_Assert(((flags & CALIB_USE_INTRINSIC_GUESS) && !K.empty() && !D.empty()) || !(flags & CALIB_USE_INTRINSIC_GUESS)); + + using namespace cv::internal; + //-------------------------------Initialization + IntrinsicParams finalParam; + IntrinsicParams currentParam; + IntrinsicParams errors; + + finalParam.isEstimate[0] = 1; + finalParam.isEstimate[1] = 1; + finalParam.isEstimate[2] = 1; + finalParam.isEstimate[3] = 1; + finalParam.isEstimate[4] = flags & CALIB_FIX_SKEW ? 0 : 1; + finalParam.isEstimate[5] = flags & CALIB_FIX_K1 ? 0 : 1; + finalParam.isEstimate[6] = flags & CALIB_FIX_K2 ? 0 : 1; + finalParam.isEstimate[7] = flags & CALIB_FIX_K3 ? 0 : 1; + finalParam.isEstimate[8] = flags & CALIB_FIX_K4 ? 0 : 1; + + const int recompute_extrinsic = flags & CALIB_RECOMPUTE_EXTRINSIC ? 1: 0; + const int check_cond = flags & CALIB_CHECK_COND ? 1 : 0; + + const double alpha_smooth = 0.4; + const double thresh_cond = 1e6; + double change = 1; + Vec2d err_std; + + Matx33d _K; + Vec4d _D; + if (flags & CALIB_USE_INTRINSIC_GUESS) + { + K.getMat().convertTo(_K, CV_64FC1); + D.getMat().convertTo(_D, CV_64FC1); + finalParam.Init(Vec2d(_K(0,0), _K(1, 1)), + Vec2d(_K(0,2), _K(1, 2)), + Vec4d(flags & CALIB_FIX_K1 ? 0 : _D[0], + flags & CALIB_FIX_K2 ? 0 : _D[1], + flags & CALIB_FIX_K3 ? 0 : _D[2], + flags & CALIB_FIX_K4 ? 0 : _D[3]), + _K(0, 1) / _K(0, 0)); + } + else + { + finalParam.Init(Vec2d(max(image_size.width, image_size.height) / CV_PI, max(image_size.width, image_size.height) / CV_PI), + Vec2d(image_size.width / 2.0 - 0.5, image_size.height / 2.0 - 0.5)); + } + + errors.isEstimate = finalParam.isEstimate; + + std::vector omc(objectPoints.total()), Tc(objectPoints.total()); + + CalibrateExtrinsics(objectPoints, imagePoints, finalParam, check_cond, thresh_cond, omc, Tc); + + + //-------------------------------Optimization + for(int iter = 0; ; ++iter) + { + if ((criteria.type == 1 && iter >= criteria.maxCount) || + (criteria.type == 2 && change <= criteria.epsilon) || + (criteria.type == 3 && (change <= criteria.epsilon || iter >= criteria.maxCount))) + break; + + double alpha_smooth2 = 1 - std::pow(1 - alpha_smooth, iter + 1.0); + + Mat JJ2_inv, ex3; + ComputeJacobians(objectPoints, imagePoints, finalParam, omc, Tc, check_cond,thresh_cond, JJ2_inv, ex3); + + Mat G = alpha_smooth2 * JJ2_inv * ex3; + + currentParam = finalParam + G; + + change = norm(Vec4d(currentParam.f[0], currentParam.f[1], currentParam.c[0], currentParam.c[1]) - + Vec4d(finalParam.f[0], finalParam.f[1], finalParam.c[0], finalParam.c[1])) + / norm(Vec4d(currentParam.f[0], currentParam.f[1], currentParam.c[0], currentParam.c[1])); + + finalParam = currentParam; + + if (recompute_extrinsic) + { + CalibrateExtrinsics(objectPoints, imagePoints, finalParam, check_cond, + thresh_cond, omc, Tc); + } + } + + //-------------------------------Validation + double rms; + EstimateUncertainties(objectPoints, imagePoints, finalParam, omc, Tc, errors, err_std, thresh_cond, + check_cond, rms); + + //------------------------------- + _K = Matx33d(finalParam.f[0], finalParam.f[0] * finalParam.alpha, finalParam.c[0], + 0, finalParam.f[1], finalParam.c[1], + 0, 0, 1); + + if (K.needed()) cv::Mat(_K).convertTo(K, K.empty() ? CV_64FC1 : K.type()); + if (D.needed()) cv::Mat(finalParam.k).convertTo(D, D.empty() ? CV_64FC1 : D.type()); + if (rvecs.needed()) cv::Mat(omc).convertTo(rvecs, rvecs.empty() ? CV_64FC3 : rvecs.type()); + if (tvecs.needed()) cv::Mat(Tc).convertTo(tvecs, tvecs.empty() ? CV_64FC3 : tvecs.type()); + + return rms; +} + +namespace cv{ namespace { +void subMatrix(const Mat& src, Mat& dst, const vector& cols, const vector& rows) +{ + CV_Assert(src.type() == CV_64FC1); + + int nonzeros_cols = cv::countNonZero(cols); + Mat tmp(src.rows, nonzeros_cols, CV_64FC1); + + for (size_t i = 0, j = 0; i < cols.size(); i++) + { + if (cols[i]) + { + src.col(i).copyTo(tmp.col(j++)); + } + } + + int nonzeros_rows = cv::countNonZero(rows); + Mat tmp1(nonzeros_rows, nonzeros_cols, CV_64FC1); + for (size_t i = 0, j = 0; i < rows.size(); i++) + { + if (rows[i]) + { + tmp.row(i).copyTo(tmp1.row(j++)); + } + } + + dst = tmp1.clone(); +} + +}} + +cv::internal::IntrinsicParams::IntrinsicParams(): + f(Vec2d::all(0)), c(Vec2d::all(0)), k(Vec4d::all(0)), alpha(0), isEstimate(9,0) +{ +} + +cv::internal::IntrinsicParams::IntrinsicParams(Vec2d _f, Vec2d _c, Vec4d _k, double _alpha): + f(_f), c(_c), k(_k), alpha(_alpha), isEstimate(9,0) +{ +} + +cv::internal::IntrinsicParams cv::internal::IntrinsicParams::operator+(const Mat& a) +{ + CV_Assert(a.type() == CV_64FC1); + IntrinsicParams tmp; + const double* ptr = a.ptr(); + + int j = 0; + tmp.f[0] = this->f[0] + (isEstimate[0] ? ptr[j++] : 0); + tmp.f[1] = this->f[1] + (isEstimate[1] ? ptr[j++] : 0); + tmp.c[0] = this->c[0] + (isEstimate[2] ? ptr[j++] : 0); + tmp.alpha = this->alpha + (isEstimate[4] ? ptr[j++] : 0); + tmp.c[1] = this->c[1] + (isEstimate[3] ? ptr[j++] : 0); + tmp.k[0] = this->k[0] + (isEstimate[5] ? ptr[j++] : 0); + tmp.k[1] = this->k[1] + (isEstimate[6] ? ptr[j++] : 0); + tmp.k[2] = this->k[2] + (isEstimate[7] ? ptr[j++] : 0); + tmp.k[3] = this->k[3] + (isEstimate[8] ? ptr[j++] : 0); + + tmp.isEstimate = isEstimate; + return tmp; +} + +cv::internal::IntrinsicParams& cv::internal::IntrinsicParams::operator =(const Mat& a) +{ + CV_Assert(a.type() == CV_64FC1); + const double* ptr = a.ptr(); + + int j = 0; + + this->f[0] = isEstimate[0] ? ptr[j++] : 0; + this->f[1] = isEstimate[1] ? ptr[j++] : 0; + this->c[0] = isEstimate[2] ? ptr[j++] : 0; + this->c[1] = isEstimate[3] ? ptr[j++] : 0; + this->alpha = isEstimate[4] ? ptr[j++] : 0; + this->k[0] = isEstimate[5] ? ptr[j++] : 0; + this->k[1] = isEstimate[6] ? ptr[j++] : 0; + this->k[2] = isEstimate[7] ? ptr[j++] : 0; + this->k[3] = isEstimate[8] ? ptr[j++] : 0; + + return *this; +} + +void cv::internal::IntrinsicParams::Init(const cv::Vec2d& _f, const cv::Vec2d& _c, const cv::Vec4d& _k, const double& _alpha) +{ + this->c = _c; + this->f = _f; + this->k = _k; + this->alpha = _alpha; +} + +void cv::internal::projectPoints(cv::InputArray objectPoints, cv::OutputArray imagePoints, + cv::InputArray _rvec,cv::InputArray _tvec, + const IntrinsicParams& param, cv::OutputArray jacobian) +{ + CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3); + Matx33d K(param.f[0], param.f[0] * param.alpha, param.c[0], + 0, param.f[1], param.c[1], + 0, 0, 1); + Fisheye::projectPoints(objectPoints, imagePoints, _rvec, _tvec, K, param.k, param.alpha, jacobian); +} + +void cv::internal::ComputeExtrinsicRefine(const Mat& imagePoints, const Mat& objectPoints, Mat& rvec, + Mat& tvec, Mat& J, const int MaxIter, + const IntrinsicParams& param, const double thresh_cond) +{ + CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3); + CV_Assert(!imagePoints.empty() && imagePoints.type() == CV_64FC2); + Vec6d extrinsics(rvec.at(0), rvec.at(1), rvec.at(2), + tvec.at(0), tvec.at(1), tvec.at(2)); + double change = 1; + int iter = 0; + + while (change > 1e-10 && iter < MaxIter) + { + std::vector x; + Mat jacobians; + projectPoints(objectPoints, x, rvec, tvec, param, jacobians); + + Mat ex = imagePoints - Mat(x).t(); + ex = ex.reshape(1, 2); + + J = jacobians.colRange(8, 14).clone(); + + SVD svd(J, SVD::NO_UV); + double condJJ = svd.w.at(0)/svd.w.at(5); + + if (condJJ > thresh_cond) + change = 0; + else + { + Vec6d param_innov; + solve(J, ex.reshape(1, (int)ex.total()), param_innov, DECOMP_SVD + DECOMP_NORMAL); + + Vec6d param_up = extrinsics + param_innov; + change = norm(param_innov)/norm(param_up); + extrinsics = param_up; + iter = iter + 1; + + rvec = Mat(Vec3d(extrinsics.val)); + tvec = Mat(Vec3d(extrinsics.val+3)); + } + } +} + +cv::Mat cv::internal::ComputeHomography(Mat m, Mat M) +{ + int Np = m.cols; + + if (m.rows < 3) + { + vconcat(m, Mat::ones(1, Np, CV_64FC1), m); + } + if (M.rows < 3) + { + vconcat(M, Mat::ones(1, Np, CV_64FC1), M); + } + + divide(m, Mat::ones(3, 1, CV_64FC1) * m.row(2), m); + divide(M, Mat::ones(3, 1, CV_64FC1) * M.row(2), M); + + Mat ax = m.row(0).clone(); + Mat ay = m.row(1).clone(); + + double mxx = mean(ax)[0]; + double myy = mean(ay)[0]; + + ax = ax - mxx; + ay = ay - myy; + + double scxx = mean(abs(ax))[0]; + double scyy = mean(abs(ay))[0]; + + Mat Hnorm (Matx33d( 1/scxx, 0.0, -mxx/scxx, + 0.0, 1/scyy, -myy/scyy, + 0.0, 0.0, 1.0 )); + + Mat inv_Hnorm (Matx33d( scxx, 0, mxx, + 0, scyy, myy, + 0, 0, 1 )); + Mat mn = Hnorm * m; + + Mat L = Mat::zeros(2*Np, 9, CV_64FC1); + + for (int i = 0; i < Np; ++i) + { + for (int j = 0; j < 3; j++) + { + L.at(2 * i, j) = M.at(j, i); + L.at(2 * i + 1, j + 3) = M.at(j, i); + L.at(2 * i, j + 6) = -mn.at(0,i) * M.at(j, i); + L.at(2 * i + 1, j + 6) = -mn.at(1,i) * M.at(j, i); + } + } + + if (Np > 4) L = L.t() * L; + SVD svd(L); + Mat hh = svd.vt.row(8) / svd.vt.row(8).at(8); + Mat Hrem = hh.reshape(1, 3); + Mat H = inv_Hnorm * Hrem; + + if (Np > 4) + { + Mat hhv = H.reshape(1, 9)(Rect(0, 0, 1, 8)).clone(); + for (int iter = 0; iter < 10; iter++) + { + Mat mrep = H * M; + Mat J = Mat::zeros(2 * Np, 8, CV_64FC1); + Mat MMM; + divide(M, Mat::ones(3, 1, CV_64FC1) * mrep(Rect(0, 2, mrep.cols, 1)), MMM); + divide(mrep, Mat::ones(3, 1, CV_64FC1) * mrep(Rect(0, 2, mrep.cols, 1)), mrep); + Mat m_err = m(Rect(0,0, m.cols, 2)) - mrep(Rect(0,0, mrep.cols, 2)); + m_err = Mat(m_err.t()).reshape(1, m_err.cols * m_err.rows); + Mat MMM2, MMM3; + multiply(Mat::ones(3, 1, CV_64FC1) * mrep(Rect(0, 0, mrep.cols, 1)), MMM, MMM2); + multiply(Mat::ones(3, 1, CV_64FC1) * mrep(Rect(0, 1, mrep.cols, 1)), MMM, MMM3); + + for (int i = 0; i < Np; ++i) + { + for (int j = 0; j < 3; ++j) + { + J.at(2 * i, j) = -MMM.at(j, i); + J.at(2 * i + 1, j + 3) = -MMM.at(j, i); + } + + for (int j = 0; j < 2; ++j) + { + J.at(2 * i, j + 6) = MMM2.at(j, i); + J.at(2 * i + 1, j + 6) = MMM3.at(j, i); + } + } + divide(M, Mat::ones(3, 1, CV_64FC1) * mrep(Rect(0,2,mrep.cols,1)), MMM); + Mat hh_innov = (J.t() * J).inv() * (J.t()) * m_err; + Mat hhv_up = hhv - hh_innov; + Mat tmp; + vconcat(hhv_up, Mat::ones(1,1,CV_64FC1), tmp); + Mat H_up = tmp.reshape(1,3); + hhv = hhv_up; + H = H_up; + } + } + return H; +} + +cv::Mat cv::internal::NormalizePixels(const Mat& imagePoints, const IntrinsicParams& param) +{ + CV_Assert(!imagePoints.empty() && imagePoints.type() == CV_64FC2); + + Mat distorted((int)imagePoints.total(), 1, CV_64FC2), undistorted; + const Vec2d* ptr = imagePoints.ptr(0); + Vec2d* ptr_d = distorted.ptr(0); + for (size_t i = 0; i < imagePoints.total(); ++i) + { + ptr_d[i] = (ptr[i] - param.c).mul(Vec2d(1.0 / param.f[0], 1.0 / param.f[1])); + ptr_d[i][0] = ptr_d[i][0] - param.alpha * ptr_d[i][1]; + } + cv::Fisheye::undistortPoints(distorted, undistorted, Matx33d::eye(), param.k); + return undistorted; +} + +void cv::internal::InitExtrinsics(const Mat& _imagePoints, const Mat& _objectPoints, const IntrinsicParams& param, Mat& omckk, Mat& Tckk) +{ + + CV_Assert(!_objectPoints.empty() && _objectPoints.type() == CV_64FC3); + CV_Assert(!_imagePoints.empty() && _imagePoints.type() == CV_64FC2); + + Mat imagePointsNormalized = NormalizePixels(_imagePoints.t(), param).reshape(1).t(); + Mat objectPoints = Mat(_objectPoints.t()).reshape(1).t(); + Mat objectPointsMean, covObjectPoints; + Mat Rckk; + int Np = imagePointsNormalized.cols; + calcCovarMatrix(objectPoints, covObjectPoints, objectPointsMean, CV_COVAR_NORMAL | CV_COVAR_COLS); + SVD svd(covObjectPoints); + Mat R(svd.vt); + if (norm(R(Rect(2, 0, 1, 2))) < 1e-6) + R = Mat::eye(3,3, CV_64FC1); + if (determinant(R) < 0) + R = -R; + Mat T = -R * objectPointsMean; + Mat X_new = R * objectPoints + T * Mat::ones(1, Np, CV_64FC1); + Mat H = ComputeHomography(imagePointsNormalized, X_new(Rect(0,0,X_new.cols,2))); + double sc = .5 * (norm(H.col(0)) + norm(H.col(1))); + H = H / sc; + Mat u1 = H.col(0).clone(); + u1 = u1 / norm(u1); + Mat u2 = H.col(1).clone() - u1.dot(H.col(1).clone()) * u1; + u2 = u2 / norm(u2); + Mat u3 = u1.cross(u2); + Mat RRR; + hconcat(u1, u2, RRR); + hconcat(RRR, u3, RRR); + Rodrigues(RRR, omckk); + Rodrigues(omckk, Rckk); + Tckk = H.col(2).clone(); + Tckk = Tckk + Rckk * T; + Rckk = Rckk * R; + Rodrigues(Rckk, omckk); +} + +void cv::internal::CalibrateExtrinsics(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, + const IntrinsicParams& param, const int check_cond, + const double thresh_cond, InputOutputArray omc, InputOutputArray Tc) +{ + CV_Assert(!objectPoints.empty() && (objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3)); + CV_Assert(!imagePoints.empty() && (imagePoints.type() == CV_32FC2 || imagePoints.type() == CV_64FC2)); + CV_Assert(omc.type() == CV_64FC3 || Tc.type() == CV_64FC3); + + if (omc.empty()) omc.create(1, (int)objectPoints.total(), CV_64FC3); + if (Tc.empty()) Tc.create(1, (int)objectPoints.total(), CV_64FC3); + + const int maxIter = 20; + + for(int image_idx = 0; image_idx < (int)imagePoints.total(); ++image_idx) + { + Mat omckk, Tckk, JJ_kk; + Mat image, object; + + objectPoints.getMat(image_idx).convertTo(object, CV_64FC3); + imagePoints.getMat (image_idx).convertTo(image, CV_64FC2); + + InitExtrinsics(image, object, param, omckk, Tckk); + + ComputeExtrinsicRefine(image, object, omckk, Tckk, JJ_kk, maxIter, param, thresh_cond); + if (check_cond) + { + SVD svd(JJ_kk, SVD::NO_UV); + if (svd.w.at(0) / svd.w.at((int)svd.w.total() - 1) > thresh_cond) + { + CV_Assert(!"cond > thresh_cond"); + } + } + omckk.reshape(3,1).copyTo(omc.getMat().col(image_idx)); + Tckk.reshape(3,1).copyTo(Tc.getMat().col(image_idx)); + } +} + + +void cv::internal::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_Assert(!objectPoints.empty() && (objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3)); + CV_Assert(!imagePoints.empty() && (imagePoints.type() == CV_32FC2 || imagePoints.type() == CV_64FC2)); + + CV_Assert(!omc.empty() && omc.type() == CV_64FC3); + CV_Assert(!Tc.empty() && Tc.type() == CV_64FC3); + + int n = (int)objectPoints.total(); + + Mat JJ3 = Mat::zeros(9 + 6 * n, 9 + 6 * n, CV_64FC1); + ex3 = Mat::zeros(9 + 6 * n, 1, CV_64FC1 ); + + for (int image_idx = 0; image_idx < n; ++image_idx) + { + Mat image, object; + objectPoints.getMat(image_idx).convertTo(object, CV_64FC3); + imagePoints.getMat (image_idx).convertTo(image, CV_64FC2); + + Mat om(omc.getMat().col(image_idx)), T(Tc.getMat().col(image_idx)); + + std::vector x; + Mat jacobians; + projectPoints(object, x, om, T, param, jacobians); + Mat exkk = image.t() - Mat(x); + + Mat A(jacobians.rows, 9, CV_64FC1); + jacobians.colRange(0, 4).copyTo(A.colRange(0, 4)); + jacobians.col(14).copyTo(A.col(4)); + jacobians.colRange(4, 8).copyTo(A.colRange(5, 9)); + + A = A.t(); + + Mat B = jacobians.colRange(8, 14).clone(); + B = B.t(); + + JJ3(Rect(0, 0, 9, 9)) = JJ3(Rect(0, 0, 9, 9)) + A * A.t(); + JJ3(Rect(9 + 6 * image_idx, 9 + 6 * image_idx, 6, 6)) = B * B.t(); + + Mat AB = A * B.t(); + AB.copyTo(JJ3(Rect(9 + 6 * image_idx, 0, 6, 9))); + + JJ3(Rect(0, 9 + 6 * image_idx, 9, 6)) = AB.t(); + ex3(Rect(0,0,1,9)) = ex3(Rect(0,0,1,9)) + A * exkk.reshape(1, 2 * exkk.rows); + + ex3(Rect(0, 9 + 6 * image_idx, 1, 6)) = B * exkk.reshape(1, 2 * exkk.rows); + + if (check_cond) + { + Mat JJ_kk = B.t(); + SVD svd(JJ_kk, SVD::NO_UV); + double cond = svd.w.at(0) / svd.w.at(svd.w.rows - 1); + if (cond > thresh_cond) + { + CV_Assert(!"cond > thresh_cond"); + } + } + } + + vector idxs(param.isEstimate); + idxs.insert(idxs.end(), 6 * n, 1); + + subMatrix(JJ3, JJ3, idxs, idxs); + subMatrix(ex3, ex3, std::vector(1, 1), idxs); + JJ2_inv = JJ3.inv(); +} + +void cv::internal::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) +{ + CV_Assert(!objectPoints.empty() && (objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3)); + CV_Assert(!imagePoints.empty() && (imagePoints.type() == CV_32FC2 || imagePoints.type() == CV_64FC2)); + + CV_Assert(!omc.empty() && omc.type() == CV_64FC3); + CV_Assert(!Tc.empty() && Tc.type() == CV_64FC3); + + Mat ex((int)(objectPoints.getMat(0).total() * objectPoints.total()), 1, CV_64FC2); + + for (size_t image_idx = 0; image_idx < objectPoints.total(); ++image_idx) + { + Mat image, object; + objectPoints.getMat(image_idx).convertTo(object, CV_64FC3); + imagePoints.getMat (image_idx).convertTo(image, CV_64FC2); + + Mat om(omc.getMat().col(image_idx)), T(Tc.getMat().col(image_idx)); + + std::vector x; + projectPoints(object, x, om, T, params, noArray()); + Mat ex_ = image.t() - Mat(x); + ex_.copyTo(ex.rowRange(ex_.rows * image_idx, ex_.rows * (image_idx + 1))); + } + + meanStdDev(ex, noArray(), std_err); + std_err *= sqrt(ex.total()/(ex.total() - 1.0)); + + Mat sigma_x; + meanStdDev(ex.reshape(1, 1), noArray(), sigma_x); + sigma_x *= sqrt(2 * ex.total()/(2 * ex.total() - 1.0)); + + Mat _JJ2_inv, ex3; + ComputeJacobians(objectPoints, imagePoints, params, omc, Tc, check_cond, thresh_cond, _JJ2_inv, ex3); + + Mat_& JJ2_inv = (Mat_&)_JJ2_inv; + + sqrt(JJ2_inv, JJ2_inv); + + double s = sigma_x.at(0); + Mat r = 3 * s * JJ2_inv.diag(); + errors = r; + + rms = 0; + const Vec2d* ptr_ex = ex.ptr(); + for (size_t i = 0; i < ex.total(); i++) + { + rms += ptr_ex[i][0] * ptr_ex[i][0] + ptr_ex[i][1] * ptr_ex[i][1]; + } + + rms /= ex.total(); + rms = sqrt(rms); +} diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp new file mode 100644 index 000000000..3989e1bc2 --- /dev/null +++ b/modules/calib3d/test/test_fisheye.cpp @@ -0,0 +1,423 @@ +#include "test_precomp.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DEF_PARAM_TEST(name, ...) typedef ::perf::TestBaseWithParam< std::tr1::tuple< __VA_ARGS__ > > name +#define PARAM_TEST_CASE(name, ...) struct name : testing::TestWithParam< std::tr1::tuple< __VA_ARGS__ > > + + +namespace FishEye +{ + const static cv::Size imageSize(1280, 800); + + const static cv::Matx33d K(558.478087865323, 0, 620.458515360843, + 0, 560.506767351568, 381.939424848348, + 0, 0, 1); + + const static cv::Vec4d D(-0.0014613319981768, -0.00329861110580401, 0.00605760088590183, -0.00374209380722371); + + const static cv::Matx33d 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 static cv::Vec3d T(-9.9217369356044638e-02, 3.1741831972356663e-03, 1.8551007952921010e-04); +} + +namespace{ +std::string 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; +} + +std::string combine_format(const std::string& item1, const std::string& item2, ...) +{ + std::string fmt = combine(item1, item2); + char buffer[1 << 16]; + va_list args; + va_start( args, item2 ); + vsprintf( buffer, fmt.c_str(), args ); + va_end( args ); + return std::string(buffer); +} + +void readPoins(std::vector >& objectPoints, + std::vector >& imagePoints, + const std::string& path, const int n_images, const int n_points) +{ + objectPoints.resize(n_images); + imagePoints.resize(n_images); + + std::vector image(n_points); + std::vector object(n_points); + + std::ifstream ipStream; + std::ifstream opStream; + + for (int image_idx = 0; image_idx < n_images; image_idx++) + { + std::stringstream ss; + ss << image_idx; + std::string idxStr = ss.str(); + + ipStream.open(combine(path, std::string(std::string("x_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); + opStream.open(combine(path, std::string(std::string("X_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); + CV_Assert(ipStream.is_open() && opStream.is_open()); + + for (int point_idx = 0; point_idx < n_points; point_idx++) + { + double x, y, z; + char delim; + ipStream >> x >> delim >> y; + image[point_idx] = cv::Point2d(x, y); + opStream >> x >> delim >> y >> delim >> z; + object[point_idx] = cv::Point3d(x, y, z); + } + ipStream.close(); + opStream.close(); + + imagePoints[image_idx] = image; + objectPoints[image_idx] = object; + } +} + +void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, + cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q) +{ + cv::FileStorage fs(file, cv::FileStorage::READ); + CV_Assert(fs.isOpened()); + + cv::Mat R, T, R1, R2, P1, P2, Q; + fs["R"] >> R; fs["T"] >> T; fs["R1"] >> R1; fs["R2"] >> R2; fs["P1"] >> P1; fs["P2"] >> P2; fs["Q"] >> Q; + if (_R.needed()) R.copyTo(_R); if(_T.needed()) T.copyTo(_T); if (_R1.needed()) R1.copyTo(_R1); if (_R2.needed()) R2.copyTo(_R2); + if(_P1.needed()) P1.copyTo(_P1); if(_P2.needed()) P2.copyTo(_P2); if(_Q.needed()) Q.copyTo(_Q); +} + +cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r, double scale) +{ + CV_Assert(l.type() == r.type() && l.size() == r.size()); + cv::Mat merged(l.rows, l.cols * 2, l.type()); + cv::Mat lpart = merged.colRange(0, l.cols); + cv::Mat rpart = merged.colRange(l.cols, merged.cols); + l.copyTo(lpart); + r.copyTo(rpart); + + for(int i = 0; i < l.rows; i+=20) + cv::line(merged, cv::Point(0, i), cv::Point(merged.cols, i), CV_RGB(0, 255, 0)); + + return merged; +} + +} + + + +/// Change this parameter via CMake: cmake -DDATASETS_REPOSITORY_FOLDER= +//const static std::string datasets_repository_path = "DATASETS_REPOSITORY_FOLDER"; +const static std::string datasets_repository_path = "/home/krylov/data"; + +TEST(FisheyeTest, projectPoints) +{ + double cols = FishEye::imageSize.width, + rows = FishEye::imageSize.height; + + const int N = 20; + cv::Mat distorted0(1, N*N, CV_64FC2), undist1, undist2, distorted1, distorted2; + undist2.create(distorted0.size(), CV_MAKETYPE(distorted0.depth(), 3)); + cv::Vec2d* pts = distorted0.ptr(); + + cv::Vec2d c(FishEye::K(0, 2), FishEye::K(1, 2)); + for(int y = 0, k = 0; y < N; ++y) + for(int x = 0; x < N; ++x) + { + cv::Vec2d point(x*cols/(N-1.f), y*rows/(N-1.f)); + pts[k++] = (point - c) * 0.85 + c; + } + + cv::Fisheye::undistortPoints(distorted0, undist1, FishEye::K, FishEye::D); + + cv::Vec2d* u1 = undist1.ptr(); + cv::Vec3d* u2 = undist2.ptr(); + for(int i = 0; i < (int)distorted0.total(); ++i) + u2[i] = cv::Vec3d(u1[i][0], u1[i][1], 1.0); + + cv::Fisheye::distortPoints(undist1, distorted1, FishEye::K, FishEye::D); + cv::Fisheye::projectPoints(undist2, distorted2, cv::Vec3d::all(0), cv::Vec3d::all(0), FishEye::K, FishEye::D); + + EXPECT_MAT_NEAR(distorted0, distorted1, 1e-5); + EXPECT_MAT_NEAR(distorted0, distorted2, 1e-5); +} + +TEST(FisheyeTest, undistortImage) +{ + cv::Matx33d K = FishEye::K; + cv::Mat D = cv::Mat(FishEye::D); + std::string file = combine(datasets_repository_path, "image000001.png"); + + cv::Matx33d newK = K; + cv::Mat distorted = cv::imread(file), undistorted; + + { + newK(0, 0) = 100; + newK(1, 1) = 100; + cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/new_f_100.png")); + if (correct.empty()) + CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/new_f_100.png"), undistorted)); + else + EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + } + { + double balance = 1.0; + cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); + cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/balance_1.0.png")); + if (correct.empty()) + CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_1.0.png"), undistorted)); + else + EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + } + + { + double balance = 0.0; + cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); + cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/balance_0.0.png")); + if (correct.empty()) + CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_0.0.png"), undistorted)); + else + EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + } + + cv::waitKey(); +} + +TEST(FisheyeTest, jacobians) +{ + int n = 10; + cv::Mat X(1, n, CV_32FC4); + cv::Mat om(3, 1, CV_64F), T(3, 1, CV_64F); + cv::Mat f(2, 1, CV_64F), c(2, 1, CV_64F); + cv::Mat k(4, 1, CV_64F); + double alpha; + + cv::RNG& r = cv::theRNG(); + + r.fill(X, cv::RNG::NORMAL, 0, 1); + X = cv::abs(X) * 10; + + r.fill(om, cv::RNG::NORMAL, 0, 1); + om = cv::abs(om); + + r.fill(T, cv::RNG::NORMAL, 0, 1); + T = cv::abs(T); T.at(2) = 4; T *= 10; + + r.fill(f, cv::RNG::NORMAL, 0, 1); + f = cv::abs(f) * 1000; + + r.fill(c, cv::RNG::NORMAL, 0, 1); + c = cv::abs(c) * 1000; + + r.fill(k, cv::RNG::NORMAL, 0, 1); + k*= 0.5; + + alpha = 0.01*r.gaussian(1); + + + CV_Assert(!"/////////"); +} + +TEST(FisheyeTest, Calibration) +{ + const int n_images = 34; + const int n_points = 48; + + cv::Size imageSize = cv::Size(1280, 800); + std::vector > imagePoints; + std::vector > objectPoints; + + readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + + int flag = 0; + flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::Fisheye::CALIB_CHECK_COND; + flag |= cv::Fisheye::CALIB_FIX_SKEW; + + cv::Matx33d K; + cv::Vec4d D; + + cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, + cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6)); + + EXPECT_MAT_NEAR(K, FishEye::K, 1e-11); + EXPECT_MAT_NEAR(D, FishEye::D, 1e-12); +} + +TEST(FisheyeTest, Homography) +{ + const int n_images = 1; + const int n_points = 48; + + cv::Size imageSize = cv::Size(1280, 800); + std::vector > imagePoints; + std::vector > objectPoints; + + readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + 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).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 R(svd.vt); + + if (cv::norm(R(cv::Rect(2, 0, 1, 2))) < 1e-6) + R = cv::Mat::eye(3,3, CV_64FC1); + if (cv::determinant(R) < 0) + R = -R; + + cv::Mat T = -R * objectPointsMean; + cv::Mat X_new = R * _objectPoints + T * 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-16); +} + +TEST(TestFisheye, EtimateUncertainties) +{ + const int n_images = 34; + const int n_points = 48; + + cv::Size imageSize = cv::Size(1280, 800); + std::vector > imagePoints; + std::vector > objectPoints; + + readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + + int flag = 0; + flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::Fisheye::CALIB_CHECK_COND; + flag |= cv::Fisheye::CALIB_FIX_SKEW; + + cv::Matx33d K; + cv::Vec4d D; + std::vector rvec; + std::vector tvec; + + cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, + cv::noArray(), cv::noArray(), 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(K(0,0), K(1,1)), cv::Vec2d(K(0,2), K(1, 2)), D); + param.isEstimate = std::vector(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.29837104202046, 1.31565641071524), 1e-14); + EXPECT_MAT_NEAR(errors.c, cv::Vec2d(0.890439368129246, 0.816096854937896), 1e-15); + EXPECT_MAT_NEAR(errors.k, cv::Vec4d(0.00516248605191506, 0.0168181467500934, 0.0213118690274604, 0.00916010877545648), 1e-15); + EXPECT_MAT_NEAR(err_std, cv::Vec2d(0.187475975266883, 0.185678953263995), 1e-15); + CV_Assert(abs(rms - 0.263782587133546) < 1e-15); + CV_Assert(errors.alpha == 0); + } + +TEST(FisheyeTest, rectify) +{ + const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY"); + + cv::Size calibration_size = FishEye::imageSize, requested_size = calibration_size; + cv::Matx33d K1 = FishEye::K, K2 = K1; + cv::Mat D1 = cv::Mat(FishEye::D), D2 = D1; + + cv::Vec3d T = FishEye::T; + cv::Matx33d R = FishEye::R; + + double balance = 0.0, fov_scale = 1.1; + cv::Mat R1, R2, P1, P2, Q; + cv::Fisheye::stereoRectify(K1, D1, K2, D2, calibration_size, R, T, R1, R2, P1, P2, Q, + cv::CALIB_ZERO_DISPARITY, requested_size, balance, fov_scale); + + cv::Mat lmapx, lmapy, rmapx, rmapy; + //rewrite for fisheye + cv::Fisheye::initUndistortRectifyMap(K1, D1, R1, P1, requested_size, CV_32F, lmapx, lmapy); + cv::Fisheye::initUndistortRectifyMap(K2, D2, R2, P2, requested_size, CV_32F, rmapx, rmapy); + + cv::Mat l, r, lundist, rundist; + cv::VideoCapture lcap(combine(folder, "left/stereo_pair_%03d.jpg")), + rcap(combine(folder, "right/stereo_pair_%03d.jpg")); + + for(int i = 0;; ++i) + { + lcap >> l; rcap >> r; + if (l.empty() || r.empty()) + break; + + int ndisp = 128; + cv::rectangle(l, cv::Rect(255, 0, 829, l.rows-1), CV_RGB(255, 0, 0)); + cv::rectangle(r, cv::Rect(255, 0, 829, l.rows-1), CV_RGB(255, 0, 0)); + cv::rectangle(r, cv::Rect(255-ndisp, 0, 829+ndisp ,l.rows-1), CV_RGB(255, 0, 0)); + cv::remap(l, lundist, lmapx, lmapy, cv::INTER_LINEAR); + cv::remap(r, rundist, rmapx, rmapy, cv::INTER_LINEAR); + + cv::Mat rectification = mergeRectification(lundist, rundist, 0.75); + + cv::Mat correct = cv::imread(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i)); + if (correct.empty()) + cv::imwrite(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i), rectification); + else + EXPECT_MAT_NEAR(correct, rectification, 1e-15); + } +} + + + + + From 35e1b322cbc9640fc60ab03b6fc5783782c6ca1b Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Fri, 25 Apr 2014 18:36:48 +0400 Subject: [PATCH 02/15] Added test for jacobians --- modules/calib3d/test/test_fisheye.cpp | 89 +++++++++++++++++++++------ 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 3989e1bc2..b2f47987c 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -1,18 +1,13 @@ #include "test_precomp.hpp" - -#include -#include -#include -#include -#include - +#include #include -#include -#include #define DEF_PARAM_TEST(name, ...) typedef ::perf::TestBaseWithParam< std::tr1::tuple< __VA_ARGS__ > > name #define PARAM_TEST_CASE(name, ...) struct name : testing::TestWithParam< std::tr1::tuple< __VA_ARGS__ > > +/// Change this parameter via CMake: cmake -DDATASETS_REPOSITORY_FOLDER= +//const static std::string datasets_repository_path = "DATASETS_REPOSITORY_FOLDER"; +const static std::string datasets_repository_path = "/home/krylov/data"; namespace FishEye { @@ -111,7 +106,7 @@ void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray if(_P1.needed()) P1.copyTo(_P1); if(_P2.needed()) P2.copyTo(_P2); if(_Q.needed()) Q.copyTo(_Q); } -cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r, double scale) +cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r) { CV_Assert(l.type() == r.type() && l.size() == r.size()); cv::Mat merged(l.rows, l.cols * 2, l.type()); @@ -128,12 +123,6 @@ cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r, double scale) } - - -/// Change this parameter via CMake: cmake -DDATASETS_REPOSITORY_FOLDER= -//const static std::string datasets_repository_path = "DATASETS_REPOSITORY_FOLDER"; -const static std::string datasets_repository_path = "/home/krylov/data"; - TEST(FisheyeTest, projectPoints) { double cols = FishEye::imageSize.width, @@ -213,7 +202,7 @@ TEST(FisheyeTest, undistortImage) TEST(FisheyeTest, jacobians) { int n = 10; - cv::Mat X(1, n, CV_32FC4); + cv::Mat X(1, n, CV_64FC3); cv::Mat om(3, 1, CV_64F), T(3, 1, CV_64F); cv::Mat f(2, 1, CV_64F), c(2, 1, CV_64F); cv::Mat k(4, 1, CV_64F); @@ -221,7 +210,7 @@ TEST(FisheyeTest, jacobians) cv::RNG& r = cv::theRNG(); - r.fill(X, cv::RNG::NORMAL, 0, 1); + r.fill(X, cv::RNG::NORMAL, 2, 1); X = cv::abs(X) * 10; r.fill(om, cv::RNG::NORMAL, 0, 1); @@ -241,8 +230,68 @@ TEST(FisheyeTest, jacobians) alpha = 0.01*r.gaussian(1); + cv::Mat x1, x2, xpred; + cv::Matx33d K(f.at(0), alpha * f.at(0), c.at(0), + 0, f.at(1), c.at(1), + 0, 0, 1); - CV_Assert(!"/////////"); + cv::Mat jacobians; + cv::Fisheye::projectPoints(X, x1, om, T, K, k, alpha, jacobians); + + //test on T: + cv::Mat dT(3, 1, CV_64FC1); + r.fill(dT, cv::RNG::NORMAL, 0, 1); + dT *= 1e-9*cv::norm(T); + cv::Mat T2 = T + dT; + cv::Fisheye::projectPoints(X, x2, om, T2, K, k, alpha, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.colRange(11,14) * dT).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); + + //test on om: + cv::Mat dom(3, 1, CV_64FC1); + r.fill(dom, cv::RNG::NORMAL, 0, 1); + dom *= 1e-9*cv::norm(om); + cv::Mat om2 = om + dom; + cv::Fisheye::projectPoints(X, x2, om2, T, K, k, alpha, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.colRange(8,11) * dom).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); + + //test on f: + cv::Mat df(2, 1, CV_64FC1); + r.fill(df, cv::RNG::NORMAL, 0, 1); + df *= 1e-9*cv::norm(f); + cv::Matx33d K2 = K + cv::Matx33d(df.at(0), df.at(0) * alpha, 0, 0, df.at(1), 0, 0, 0, 0); + cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.colRange(0,2) * df).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); + + //test on c: + cv::Mat dc(2, 1, CV_64FC1); + r.fill(dc, cv::RNG::NORMAL, 0, 1); + dc *= 1e-9*cv::norm(c); + K2 = K + cv::Matx33d(0, 0, dc.at(0), 0, 0, dc.at(1), 0, 0, 0); + cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.colRange(2,4) * dc).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); + + //test on k: + cv::Mat dk(4, 1, CV_64FC1); + r.fill(dk, cv::RNG::NORMAL, 0, 1); + dk *= 1e-9*cv::norm(k); + cv::Mat k2 = k + dk; + cv::Fisheye::projectPoints(X, x2, om, T, K, k2, alpha, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.colRange(4,8) * dk).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); + + //test on alpha: + cv::Mat dalpha(1, 1, CV_64FC1); + r.fill(dalpha, cv::RNG::NORMAL, 0, 1); + dalpha *= 1e-9*cv::norm(f); + double alpha2 = alpha + dalpha.at(0); + K2 = K + cv::Matx33d(0, f.at(0) * dalpha.at(0), 0, 0, 0, 0, 0, 0, 0); + cv::Fisheye::projectPoints(X, x2, om, T, K, k, alpha2, cv::noArray()); + xpred = x1 + cv::Mat(jacobians.col(14) * dalpha).reshape(2, 1); + CV_Assert (cv::norm(x2 - xpred) < 1e-12); } TEST(FisheyeTest, Calibration) @@ -407,7 +456,7 @@ TEST(FisheyeTest, rectify) cv::remap(l, lundist, lmapx, lmapy, cv::INTER_LINEAR); cv::remap(r, rundist, rmapx, rmapy, cv::INTER_LINEAR); - cv::Mat rectification = mergeRectification(lundist, rundist, 0.75); + cv::Mat rectification = mergeRectification(lundist, rundist); cv::Mat correct = cv::imread(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i)); if (correct.empty()) From 05ee15f10881265f1e930cc65a83951844f7828a Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 28 Apr 2014 12:01:27 +0400 Subject: [PATCH 03/15] Added FisheyeTest --- modules/calib3d/test/test_fisheye.cpp | 337 ++++++++++++++------------ 1 file changed, 177 insertions(+), 160 deletions(-) diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index b2f47987c..e7d4b311c 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -2,138 +2,49 @@ #include #include -#define DEF_PARAM_TEST(name, ...) typedef ::perf::TestBaseWithParam< std::tr1::tuple< __VA_ARGS__ > > name -#define PARAM_TEST_CASE(name, ...) struct name : testing::TestWithParam< std::tr1::tuple< __VA_ARGS__ > > +class FisheyeTest : public ::testing::Test { -/// Change this parameter via CMake: cmake -DDATASETS_REPOSITORY_FOLDER= -//const static std::string datasets_repository_path = "DATASETS_REPOSITORY_FOLDER"; -const static std::string datasets_repository_path = "/home/krylov/data"; +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; -namespace FishEye -{ - const static cv::Size imageSize(1280, 800); - - const static cv::Matx33d K(558.478087865323, 0, 620.458515360843, - 0, 560.506767351568, 381.939424848348, - 0, 0, 1); - - const static cv::Vec4d D(-0.0014613319981768, -0.00329861110580401, 0.00605760088590183, -0.00374209380722371); - - const static cv::Matx33d 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 static cv::Vec3d T(-9.9217369356044638e-02, 3.1741831972356663e-03, 1.8551007952921010e-04); -} - -namespace{ -std::string 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; -} - -std::string combine_format(const std::string& item1, const std::string& item2, ...) -{ - std::string fmt = combine(item1, item2); - char buffer[1 << 16]; - va_list args; - va_start( args, item2 ); - vsprintf( buffer, fmt.c_str(), args ); - va_end( args ); - return std::string(buffer); -} - -void readPoins(std::vector >& objectPoints, - std::vector >& imagePoints, - const std::string& path, const int n_images, const int n_points) -{ - objectPoints.resize(n_images); - imagePoints.resize(n_images); - - std::vector image(n_points); - std::vector object(n_points); - - std::ifstream ipStream; - std::ifstream opStream; - - for (int image_idx = 0; image_idx < n_images; image_idx++) - { - std::stringstream ss; - ss << image_idx; - std::string idxStr = ss.str(); - - ipStream.open(combine(path, std::string(std::string("x_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); - opStream.open(combine(path, std::string(std::string("X_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); - CV_Assert(ipStream.is_open() && opStream.is_open()); - - for (int point_idx = 0; point_idx < n_points; point_idx++) - { - double x, y, z; - char delim; - ipStream >> x >> delim >> y; - image[point_idx] = cv::Point2d(x, y); - opStream >> x >> delim >> y >> delim >> z; - object[point_idx] = cv::Point3d(x, y, z); - } - ipStream.close(); - opStream.close(); - - imagePoints[image_idx] = image; - objectPoints[image_idx] = object; + virtual void SetUp() { + datasets_repository_path = combine(cvtest::TS::ptr()->get_data_path(), "cameracalibration/fisheye"); } -} -void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, - cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q) +protected: + std::string combine(const std::string& _item1, const std::string& _item2); + + std::string combine_format(const std::string& item1, const std::string& item2, ...); + + void readPoins(std::vector >& objectPoints, + std::vector >& imagePoints, + const std::string& path, const int n_images, const int n_points); + + void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, + cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q); + + cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// TESTS:: + +TEST_F(FisheyeTest, projectPoints) { - cv::FileStorage fs(file, cv::FileStorage::READ); - CV_Assert(fs.isOpened()); - - cv::Mat R, T, R1, R2, P1, P2, Q; - fs["R"] >> R; fs["T"] >> T; fs["R1"] >> R1; fs["R2"] >> R2; fs["P1"] >> P1; fs["P2"] >> P2; fs["Q"] >> Q; - if (_R.needed()) R.copyTo(_R); if(_T.needed()) T.copyTo(_T); if (_R1.needed()) R1.copyTo(_R1); if (_R2.needed()) R2.copyTo(_R2); - if(_P1.needed()) P1.copyTo(_P1); if(_P2.needed()) P2.copyTo(_P2); if(_Q.needed()) Q.copyTo(_Q); -} - -cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r) -{ - CV_Assert(l.type() == r.type() && l.size() == r.size()); - cv::Mat merged(l.rows, l.cols * 2, l.type()); - cv::Mat lpart = merged.colRange(0, l.cols); - cv::Mat rpart = merged.colRange(l.cols, merged.cols); - l.copyTo(lpart); - r.copyTo(rpart); - - for(int i = 0; i < l.rows; i+=20) - cv::line(merged, cv::Point(0, i), cv::Point(merged.cols, i), CV_RGB(0, 255, 0)); - - return merged; -} - -} - -TEST(FisheyeTest, projectPoints) -{ - double cols = FishEye::imageSize.width, - rows = FishEye::imageSize.height; + double cols = this->imageSize.width, + rows = this->imageSize.height; const int N = 20; cv::Mat distorted0(1, N*N, CV_64FC2), undist1, undist2, distorted1, distorted2; undist2.create(distorted0.size(), CV_MAKETYPE(distorted0.depth(), 3)); cv::Vec2d* pts = distorted0.ptr(); - cv::Vec2d c(FishEye::K(0, 2), FishEye::K(1, 2)); + cv::Vec2d c(this->K(0, 2), this->K(1, 2)); for(int y = 0, k = 0; y < N; ++y) for(int x = 0; x < N; ++x) { @@ -141,29 +52,27 @@ TEST(FisheyeTest, projectPoints) pts[k++] = (point - c) * 0.85 + c; } - cv::Fisheye::undistortPoints(distorted0, undist1, FishEye::K, FishEye::D); + cv::Fisheye::undistortPoints(distorted0, undist1, this->K, this->D); cv::Vec2d* u1 = undist1.ptr(); cv::Vec3d* u2 = undist2.ptr(); for(int i = 0; i < (int)distorted0.total(); ++i) u2[i] = cv::Vec3d(u1[i][0], u1[i][1], 1.0); - cv::Fisheye::distortPoints(undist1, distorted1, FishEye::K, FishEye::D); - cv::Fisheye::projectPoints(undist2, distorted2, cv::Vec3d::all(0), cv::Vec3d::all(0), FishEye::K, FishEye::D); + cv::Fisheye::distortPoints(undist1, distorted1, this->K, this->D); + cv::Fisheye::projectPoints(undist2, distorted2, cv::Vec3d::all(0), cv::Vec3d::all(0), this->K, this->D); - EXPECT_MAT_NEAR(distorted0, distorted1, 1e-5); - EXPECT_MAT_NEAR(distorted0, distorted2, 1e-5); + EXPECT_MAT_NEAR(distorted0, distorted1, 1e-10); + EXPECT_MAT_NEAR(distorted0, distorted2, 1e-10); } -TEST(FisheyeTest, undistortImage) +TEST_F(FisheyeTest, undistortImage) { - cv::Matx33d K = FishEye::K; - cv::Mat D = cv::Mat(FishEye::D); + cv::Matx33d K = this->K; + cv::Mat D = cv::Mat(this->D); std::string file = combine(datasets_repository_path, "image000001.png"); - cv::Matx33d newK = K; cv::Mat distorted = cv::imread(file), undistorted; - { newK(0, 0) = 100; newK(1, 1) = 100; @@ -172,7 +81,7 @@ TEST(FisheyeTest, undistortImage) if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/new_f_100.png"), undistorted)); else - EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } { double balance = 1.0; @@ -182,7 +91,7 @@ TEST(FisheyeTest, undistortImage) if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_1.0.png"), undistorted)); else - EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } { @@ -193,13 +102,13 @@ TEST(FisheyeTest, undistortImage) if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_0.0.png"), undistorted)); else - EXPECT_MAT_NEAR(correct, undistorted, 1e-15); + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } cv::waitKey(); } -TEST(FisheyeTest, jacobians) +TEST_F(FisheyeTest, jacobians) { int n = 10; cv::Mat X(1, n, CV_64FC3); @@ -245,7 +154,7 @@ TEST(FisheyeTest, jacobians) cv::Mat T2 = T + dT; cv::Fisheye::projectPoints(X, x2, om, T2, K, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(11,14) * dT).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); //test on om: cv::Mat dom(3, 1, CV_64FC1); @@ -254,7 +163,7 @@ TEST(FisheyeTest, jacobians) cv::Mat om2 = om + dom; cv::Fisheye::projectPoints(X, x2, om2, T, K, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(8,11) * dom).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); //test on f: cv::Mat df(2, 1, CV_64FC1); @@ -263,7 +172,7 @@ TEST(FisheyeTest, jacobians) cv::Matx33d K2 = K + cv::Matx33d(df.at(0), df.at(0) * alpha, 0, 0, df.at(1), 0, 0, 0, 0); cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(0,2) * df).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); //test on c: cv::Mat dc(2, 1, CV_64FC1); @@ -272,7 +181,7 @@ TEST(FisheyeTest, jacobians) K2 = K + cv::Matx33d(0, 0, dc.at(0), 0, 0, dc.at(1), 0, 0, 0); cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(2,4) * dc).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); //test on k: cv::Mat dk(4, 1, CV_64FC1); @@ -281,7 +190,7 @@ TEST(FisheyeTest, jacobians) cv::Mat k2 = k + dk; cv::Fisheye::projectPoints(X, x2, om, T, K, k2, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(4,8) * dk).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); //test on alpha: cv::Mat dalpha(1, 1, CV_64FC1); @@ -291,10 +200,10 @@ TEST(FisheyeTest, jacobians) K2 = K + cv::Matx33d(0, f.at(0) * dalpha.at(0), 0, 0, 0, 0, 0, 0, 0); cv::Fisheye::projectPoints(X, x2, om, T, K, k, alpha2, cv::noArray()); xpred = x1 + cv::Mat(jacobians.col(14) * dalpha).reshape(2, 1); - CV_Assert (cv::norm(x2 - xpred) < 1e-12); + CV_Assert (cv::norm(x2 - xpred) < 1e-10); } -TEST(FisheyeTest, Calibration) +TEST_F(FisheyeTest, Calibration) { const int n_images = 34; const int n_points = 48; @@ -316,11 +225,11 @@ TEST(FisheyeTest, Calibration) cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6)); - EXPECT_MAT_NEAR(K, FishEye::K, 1e-11); - EXPECT_MAT_NEAR(D, FishEye::D, 1e-12); + EXPECT_MAT_NEAR(K, this->K, 1e-10); + EXPECT_MAT_NEAR(D, this->D, 1e-10); } -TEST(FisheyeTest, Homography) +TEST_F(FisheyeTest, Homography) { const int n_images = 1; const int n_points = 48; @@ -368,10 +277,10 @@ TEST(FisheyeTest, Homography) 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-16); + EXPECT_MAT_NEAR(std_err, correct_std_err, 1e-12); } -TEST(TestFisheye, EtimateUncertainties) +TEST_F(FisheyeTest, EtimateUncertainties) { const int n_images = 34; const int n_points = 48; @@ -393,7 +302,7 @@ TEST(TestFisheye, EtimateUncertainties) std::vector tvec; cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, - cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6)); + rvec, tvec, flag, cv::TermCriteria(3, 20, 1e-6)); cv::internal::IntrinsicParams param, errors; cv::Vec2d err_std; @@ -410,24 +319,24 @@ TEST(TestFisheye, EtimateUncertainties) cv::internal::EstimateUncertainties(objectPoints, imagePoints, param, rvec, tvec, errors, err_std, thresh_cond, check_cond, rms); - EXPECT_MAT_NEAR(errors.f, cv::Vec2d(1.29837104202046, 1.31565641071524), 1e-14); - EXPECT_MAT_NEAR(errors.c, cv::Vec2d(0.890439368129246, 0.816096854937896), 1e-15); - EXPECT_MAT_NEAR(errors.k, cv::Vec4d(0.00516248605191506, 0.0168181467500934, 0.0213118690274604, 0.00916010877545648), 1e-15); - EXPECT_MAT_NEAR(err_std, cv::Vec2d(0.187475975266883, 0.185678953263995), 1e-15); - CV_Assert(abs(rms - 0.263782587133546) < 1e-15); + EXPECT_MAT_NEAR(errors.f, cv::Vec2d(1.29837104202046, 1.31565641071524), 1e-10); + EXPECT_MAT_NEAR(errors.c, cv::Vec2d(0.890439368129246, 0.816096854937896), 1e-10); + EXPECT_MAT_NEAR(errors.k, cv::Vec4d(0.00516248605191506, 0.0168181467500934, 0.0213118690274604, 0.00916010877545648), 1e-10); + EXPECT_MAT_NEAR(err_std, cv::Vec2d(0.187475975266883, 0.185678953263995), 1e-10); + CV_Assert(abs(rms - 0.263782587133546) < 1e-10); CV_Assert(errors.alpha == 0); } -TEST(FisheyeTest, rectify) +TEST_F(FisheyeTest, rectify) { const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY"); - cv::Size calibration_size = FishEye::imageSize, requested_size = calibration_size; - cv::Matx33d K1 = FishEye::K, K2 = K1; - cv::Mat D1 = cv::Mat(FishEye::D), D2 = D1; + cv::Size calibration_size = this->imageSize, requested_size = calibration_size; + cv::Matx33d K1 = this->K, K2 = K1; + cv::Mat D1 = cv::Mat(this->D), D2 = D1; - cv::Vec3d T = FishEye::T; - cv::Matx33d R = FishEye::R; + cv::Vec3d T = this->T; + cv::Matx33d R = this->R; double balance = 0.0, fov_scale = 1.1; cv::Mat R1, R2, P1, P2, Q; @@ -462,11 +371,119 @@ TEST(FisheyeTest, rectify) if (correct.empty()) cv::imwrite(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i), rectification); else - EXPECT_MAT_NEAR(correct, rectification, 1e-15); + EXPECT_MAT_NEAR(correct, rectification, 1e-10); } } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// FisheyeTest:: + +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; +} + +std::string FisheyeTest::combine_format(const std::string& item1, const std::string& item2, ...) +{ + std::string fmt = combine(item1, item2); + char buffer[1 << 16]; + va_list args; + va_start( args, item2 ); + vsprintf( buffer, fmt.c_str(), args ); + va_end( args ); + return std::string(buffer); +} + +void FisheyeTest::readPoins(std::vector >& objectPoints, + std::vector >& imagePoints, + const std::string& path, const int n_images, const int n_points) +{ + objectPoints.resize(n_images); + imagePoints.resize(n_images); + + std::vector image(n_points); + std::vector object(n_points); + + std::ifstream ipStream; + std::ifstream opStream; + + for (int image_idx = 0; image_idx < n_images; image_idx++) + { + std::stringstream ss; + ss << image_idx; + std::string idxStr = ss.str(); + + ipStream.open(combine(path, std::string(std::string("x_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); + opStream.open(combine(path, std::string(std::string("X_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); + CV_Assert(ipStream.is_open() && opStream.is_open()); + + for (int point_idx = 0; point_idx < n_points; point_idx++) + { + double x, y, z; + char delim; + ipStream >> x >> delim >> y; + image[point_idx] = cv::Point2d(x, y); + opStream >> x >> delim >> y >> delim >> z; + object[point_idx] = cv::Point3d(x, y, z); + } + ipStream.close(); + opStream.close(); + + imagePoints[image_idx] = image; + objectPoints[image_idx] = object; + } +} + +void FisheyeTest::readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, + cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q) +{ + cv::FileStorage fs(file, cv::FileStorage::READ); + CV_Assert(fs.isOpened()); + + cv::Mat R, T, R1, R2, P1, P2, Q; + fs["R"] >> R; fs["T"] >> T; fs["R1"] >> R1; fs["R2"] >> R2; fs["P1"] >> P1; fs["P2"] >> P2; fs["Q"] >> Q; + if (_R.needed()) R.copyTo(_R); if(_T.needed()) T.copyTo(_T); if (_R1.needed()) R1.copyTo(_R1); if (_R2.needed()) R2.copyTo(_R2); + if(_P1.needed()) P1.copyTo(_P1); if(_P2.needed()) P2.copyTo(_P2); if(_Q.needed()) Q.copyTo(_Q); +} + +cv::Mat FisheyeTest::mergeRectification(const cv::Mat& l, const cv::Mat& r) +{ + CV_Assert(l.type() == r.type() && l.size() == r.size()); + cv::Mat merged(l.rows, l.cols * 2, l.type()); + cv::Mat lpart = merged.colRange(0, l.cols); + cv::Mat rpart = merged.colRange(l.cols, merged.cols); + l.copyTo(lpart); + r.copyTo(rpart); + + for(int i = 0; i < l.rows; i+=20) + cv::line(merged, cv::Point(0, i), cv::Point(merged.cols, i), CV_RGB(0, 255, 0)); + + return merged; +} From f0f741b796a8c1eaa9217e51d354a31e40b9107a Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 28 Apr 2014 15:27:30 +0400 Subject: [PATCH 04/15] Added documentation --- ...mera_calibration_and_3d_reconstruction.rst | 309 ++++++++++++++++++ .../include/opencv2/calib3d/calib3d.hpp | 2 +- 2 files changed, 310 insertions(+), 1 deletion(-) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 37159b016..3d19b7289 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1487,6 +1487,315 @@ The function reconstructs 3-dimensional points (in homogeneous coordinates) by u :ocv:func:`reprojectImageTo3D` +Fisheye +---------- + +.. ocv:class:: Fisheye + +The methods in this class use a so-called fisheye camera model. :: + + class Fisheye + { + public: + + //! projects 3D points using fisheye model + static void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, + InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); + + //! projects points using fisheye model + static void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, + InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); + + //! distorts 2D points using fisheye model + static void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); + + //! undistorts 2D points using fisheye model + static void undistortPoints(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()); + + //! computing undistortion and rectification maps for image transform by cv::remap() + //! If D is empty zero distortion is used, if R or P is empty identity matrixes are used + static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, + const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); + + //! undistorts image, optionally changes resolution and camera matrix. If Knew zero identity matrix is used + static void undistortImage(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); + + //! estimates new camera matrix for undistortion or rectification + static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); + + //! stereo rectification for fisheye camera model + static void stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, + InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, + int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ); + + //! performs camera calibaration + static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, + TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); + + //! stereo rectification estimation + static void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, + OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), + double balance = 0.0, double fov_scale = 1.0); + + ... + }; + + +Definitions: +Let P be a point in 3D of coordinates X in the world reference frame (stored in the matrix X) +The coordinate vector of P in the camera reference frame is: + +.. class:: center +.. math:: + + Xc = R X + T + +where R is the rotation matrix corresponding to the rotation vector om: R = rodrigues(om); +call x, y and z the 3 coordinates of Xc: + +.. class:: center +.. math:: + x = Xc_1 \\ + y = Xc_2 \\ + z = Xc_3 + +The pinehole projection coordinates of P is [a; b] where + +.. class:: center +.. math:: + + a = x / z \ and \ b = y / z \\ + r^2 = a^2 + b^2 \\ + \theta = atan(r) + +Fisheye distortion: + +.. class:: center +.. math:: + + \theta_d = \theta (1 + k_1 \theta^2 + k_2 \theta^4 + k_3 \theta^6 + k_4 \theta^8) + +The distorted point coordinates are [x'; y'] where + +.. class:: center +.. math:: + + x' = (\theta_d / r) x \\ + y' = (\theta_d / r) y + +Finally, convertion into pixel coordinates: The final pixel coordinates vector [u; v] where: + +.. class:: center +.. math:: + + u = f_x (x' + \alpha y') + c_x \\ + v = f_y yy + c_y + +Fisheye::projectPoints +--------------------------- +Projects points using fisheye model + +.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) + +.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, + InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) + + :param objectPoints: Array of object points, 1xN/Nx1 3-channel (or ``vector`` ), where N is the number of points in the view. + + :param rvec: Rotation vector. See :ocv:func:`Rodrigues` for details. + + :param tvec: Translation vector. + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}` . + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param alpha: The skew coefficient. + + :param imagePoints: Output array of image points, 2xN/Nx2 1-channel or 1xN/Nx1 2-channel, or ``vector`` . + + :param jacobian: Optional output 2Nx15 jacobian matrix of derivatives of image points with respect to components of the focal lengths, coordinates of the principal point, distortion coefficients, rotation vector, translation vector, and the skew. In the old interface different components of the jacobian are returned via different output parameters. + +The function computes projections of 3D +points to the image plane given intrinsic and extrinsic camera +parameters. Optionally, the function computes Jacobians - matrices +of partial derivatives of image points coordinates (as functions of all the +input parameters) with respect to the particular parameters, intrinsic and/or +extrinsic. + +Fisheye::distortPoints +------------------------- +Distorts 2D points using fisheye model. + +.. ocv:function:: void Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0) + + :param undistorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param alpha: The skew coefficient. + + :param distorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . + +Fisheye::undistortPoints +----------------------------- +Undistorts 2D points using fisheye model + +.. ocv:function:: void Fisheye::undistortPoints(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()) + + :param distorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + + :param P: New camera matrix (3x3) or new projection matrix (3x4) + + :param undistorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . + + +Fisheye::initUndistortRectifyMap +------------------------------------- +Computes undistortion and rectification maps for image transform by cv::remap(). If D is empty zero distortion is used, if R or P is empty identity matrixes are used. + +.. ocv:function:: void Fisheye::initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, + const cv::Size& size, int m1type, OutputArray map1, OutputArray map2) + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + + :param P: New camera matrix (3x3) or new projection matrix (3x4) + + :param size: Undistorted image size. + + :param m1type: Type of the first output map that can be CV_32FC1 or CV_16SC2 . See convertMaps() for details. + + :param map1: The first output map. + + :param map2: The second output map. + + +Fisheye::estimateNewCameraMatrixForUndistortRectify +---------------------------------------------------------- +Estimates new camera matrix for undistortion or rectification. + +.. ocv:function:: void Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + + :param P: New camera matrix (3x3) or new projection matrix (3x4) + + :param new_size: New size + + :param balance: Balance. + + :param fov_scale: Field of View scale. + +Fisheye::stereoRectify +------------------------------ +Stereo rectification for fisheye camera model + +.. ocv:function:: void Fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, + InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, + int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ) + +.. ocv:function:: void Fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, + OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), + double balance = 0.0, double fov_scale = 1.0) + + :param K1: First camera matrix. + + :param K2: Second camera matrix. + + :param D1: First camera distortion parameters. + + :param D2: Second camera distortion parameters. + + :param imageSize: Size of the image used for stereo calibration. + + :param rotation: Rotation matrix between the coordinate systems of the first and the second cameras. + + :param tvec: Translation vector between coordinate systems of the cameras. + + :param R1: Output 3x3 rectification transform (rotation matrix) for the first camera. + + :param R2: Output 3x3 rectification transform (rotation matrix) for the second camera. + + :param P1: Output 3x4 projection matrix in the new (rectified) coordinate systems for the first camera. + + :param P2: Output 3x4 projection matrix in the new (rectified) coordinate systems for the second camera. + + :param Q: Output :math:`4 \times 4` disparity-to-depth mapping matrix (see :ocv:func:`reprojectImageTo3D` ). + + :param flags: Operation flags that may be zero or ``CV_CALIB_ZERO_DISPARITY`` . If the flag is set, the function makes the principal points of each camera have the same pixel coordinates in the rectified views. And if the flag is not set, the function may still shift the images in the horizontal or vertical direction (depending on the orientation of epipolar lines) to maximize the useful image area. + + :param alpha: Free scaling parameter. If it is -1 or absent, the function performs the default scaling. Otherwise, the parameter should be between 0 and 1. ``alpha=0`` means that the rectified images are zoomed and shifted so that only valid pixels are visible (no black areas after rectification). ``alpha=1`` means that the rectified image is decimated and shifted so that all the pixels from the original images from the cameras are retained in the rectified images (no source image pixels are lost). Obviously, any intermediate value yields an intermediate result between those two extreme cases. + + :param newImageSize: New image resolution after rectification. The same size should be passed to :ocv:func:`initUndistortRectifyMap` (see the ``stereo_calib.cpp`` sample in OpenCV samples directory). When (0,0) is passed (default), it is set to the original ``imageSize`` . Setting it to larger value can help you preserve details in the original image, especially when there is a big radial distortion. + + :param roi1: Optional output rectangles inside the rectified images where all the pixels are valid. If ``alpha=0`` , the ROIs cover the whole images. Otherwise, they are likely to be smaller (see the picture below). + + :param roi2: Optional output rectangles inside the rectified images where all the pixels are valid. If ``alpha=0`` , the ROIs cover the whole images. Otherwise, they are likely to be smaller (see the picture below). + + :param balance: Balance. + + :param fov_scale: Field of View scale. + + + +Fisheye::calibrate +---------------------------- +Performs camera calibaration + +.. ocv:function:: double Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, + TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) + + :param objectPoints: vector of vectors of calibration pattern points in the calibration pattern coordinate space. The outer vector contains as many elements as the number of the pattern views. If the same calibration pattern is shown in each view and it is fully visible, all the vectors will be the same. Although, it is possible to use partially occluded patterns, or even different patterns in different views. Then, the vectors will be different. The points are 3D, but since they are in a pattern coordinate system, then, if the rig is planar, it may make sense to put the model to a XY coordinate plane so that Z-coordinate of each input object point is 0. + + :param imagePoints: vector of vectors of the projections of calibration pattern points. ``imagePoints.size()`` and ``objectPoints.size()`` and ``imagePoints[i].size()`` must be equal to ``objectPoints[i].size()`` for each ``i``. + + :param image_size: Size of the image used only to initialize the intrinsic camera matrix. + + :param K: Output 3x3 floating-point camera matrix :math:`A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}` . If ``Fisheye::CALIB_USE_INTRINSIC_GUESS``/ is specified, some or all of ``fx, fy, cx, cy`` must be initialized before calling the function. + + :param D: Output vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param rvecs: Output vector of rotation vectors (see :ocv:func:`Rodrigues` ) estimated for each pattern view. That is, each k-th rotation vector together with the corresponding k-th translation vector (see the next output parameter description) brings the calibration pattern from the model coordinate space (in which object points are specified) to the world coordinate space, that is, a real position of the calibration pattern in the k-th pattern view (k=0.. *M* -1). + + :param tvecs: Output vector of translation vectors estimated for each pattern view. + + :param flags: Different flags that may be zero or a combination of the following values: + + * **Fisheye::CALIB_USE_INTRINSIC_GUESS** ``cameraMatrix`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center ( ``imageSize`` is used), and focal distances are computed in a least-squares fashion. + + * **Fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. + + * **Fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. + + * **Fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. + + * **Fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. + + :param criteria: Termination criteria for the iterative optimization algorithm. + .. [BT98] Birchfield, S. and Tomasi, C. A pixel dissimilarity measure that is insensitive to image sampling. IEEE Transactions on Pattern Analysis and Machine Intelligence. 1998. diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 2675ad402..816ed76f7 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -802,7 +802,7 @@ public: static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); - //! undistorts image, optinally chanes resolution and camera matrix. If Knew zero identity matrix is used + //! undistorts image, optionally changes resolution and camera matrix. If Knew zero identity matrix is used static void undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); From e6aa8ce93264401b2c0ad68ced33bf024849435d Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Tue, 29 Apr 2014 10:24:39 +0400 Subject: [PATCH 05/15] Corrected notes --- ...mera_calibration_and_3d_reconstruction.rst | 6 -- .../include/opencv2/calib3d/calib3d.hpp | 68 ------------------ modules/calib3d/src/fisheye.cpp | 7 +- modules/calib3d/src/fisheye.hpp | 48 +++++++++++++ modules/calib3d/test/test_fisheye.cpp | 71 ++++++------------- 5 files changed, 75 insertions(+), 125 deletions(-) create mode 100644 modules/calib3d/src/fisheye.hpp diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 3d19b7289..710672cf5 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1702,12 +1702,6 @@ Estimates new camera matrix for undistortion or rectification. :param P: New camera matrix (3x3) or new projection matrix (3x4) - :param new_size: New size - - :param balance: Balance. - - :param fov_scale: Field of View scale. - Fisheye::stereoRectify ------------------------------ Stereo rectification for fisheye camera model diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 816ed76f7..8caebd363 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -745,32 +745,10 @@ CV_EXPORTS_W int estimateAffine3D(InputArray src, InputArray dst, OutputArray out, OutputArray inliers, double ransacThreshold=3, double confidence=0.99); - class Fisheye { public: - //Definitions: - // Let P be a point in 3D of coordinates X in the world reference frame (stored in the matrix X) - // The coordinate vector of P in the camera reference frame is: Xc = R*X + T - // where R is the rotation matrix corresponding to the rotation vector om: R = rodrigues(om); - // call x, y and z the 3 coordinates of Xc: x = Xc(1); y = Xc(2); z = Xc(3); - // The pinehole projection coordinates of P is [a;b] where a=x/z and b=y/z. - // call r^2 = a^2 + b^2, - // call theta = atan(r), - // - // Fisheye distortion -> theta_d = theta * (1 + k(1)*theta^2 + k(2)*theta^4 + k(3)*theta^6 + k(4)*theta^8) - // - // The distorted point coordinates are: xd = [xx;yy] where: - // - // xx = (theta_d / r) * x - // yy = (theta_d / r) * y - // - // Finally, convertion into pixel coordinates: The final pixel coordinates vector xp=[xxp;yyp] where: - // - // xxp = f(1)*(xx + alpha*yy) + c(1) - // yyp = f(2)*yy + c(2) - enum{ CALIB_USE_INTRINSIC_GUESS = 1, CALIB_RECOMPUTE_EXTRINSIC = 2, @@ -826,53 +804,7 @@ public: double balance = 0.0, double fov_scale = 1.0); }; - - -namespace internal { - -struct IntrinsicParams -{ - Vec2d f; - Vec2d c; - Vec4d k; - double alpha; - std::vector 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); -Mat ComputeHomography(Mat m, Mat M); - -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); - -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); - -} } #endif - #endif diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 0dcc5e70f..a9172c9b2 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -1,9 +1,7 @@ #include "opencv2/opencv.hpp" #include "opencv2/core/affine.hpp" #include "opencv2/core/affine.hpp" - -////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::projectPoints +#include "fisheye.hpp" namespace cv { namespace { @@ -16,6 +14,9 @@ namespace cv { namespace }; }} +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::projectPoints + void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha, OutputArray jacobian) { diff --git a/modules/calib3d/src/fisheye.hpp b/modules/calib3d/src/fisheye.hpp new file mode 100644 index 000000000..e000e635a --- /dev/null +++ b/modules/calib3d/src/fisheye.hpp @@ -0,0 +1,48 @@ +#ifndef FISHEYE_INTERNAL_H +#define FISHEYE_INTERNAL_H + +namespace cv { namespace internal { + +struct IntrinsicParams +{ + Vec2d f; + Vec2d c; + Vec4d k; + double alpha; + std::vector 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); +Mat ComputeHomography(Mat m, Mat M); + +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); + +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); + +}} + +#endif diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index e7d4b311c..41bbfea59 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -1,6 +1,7 @@ #include "test_precomp.hpp" -#include +#include #include +#include "../src/fisheye.hpp" class FisheyeTest : public ::testing::Test { @@ -21,9 +22,9 @@ protected: std::string combine_format(const std::string& item1, const std::string& item2, ...); - void readPoins(std::vector >& objectPoints, + void readPoints(std::vector >& objectPoints, std::vector >& imagePoints, - const std::string& path, const int n_images, const int n_points); + const std::string& path, const int n_images); void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q); @@ -104,8 +105,6 @@ TEST_F(FisheyeTest, undistortImage) else EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } - - cv::waitKey(); } TEST_F(FisheyeTest, jacobians) @@ -206,13 +205,11 @@ TEST_F(FisheyeTest, jacobians) TEST_F(FisheyeTest, Calibration) { const int n_images = 34; - const int n_points = 48; - cv::Size imageSize = cv::Size(1280, 800); std::vector > imagePoints; std::vector > objectPoints; - readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images); int flag = 0; flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; @@ -232,13 +229,11 @@ TEST_F(FisheyeTest, Calibration) TEST_F(FisheyeTest, Homography) { const int n_images = 1; - const int n_points = 48; - cv::Size imageSize = cv::Size(1280, 800); std::vector > imagePoints; std::vector > objectPoints; - readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images); 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)); @@ -283,13 +278,11 @@ TEST_F(FisheyeTest, Homography) TEST_F(FisheyeTest, EtimateUncertainties) { const int n_images = 34; - const int n_points = 48; - cv::Size imageSize = cv::Size(1280, 800); std::vector > imagePoints; std::vector > objectPoints; - readPoins(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images, n_points); + readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images); int flag = 0; flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; @@ -325,7 +318,7 @@ TEST_F(FisheyeTest, EtimateUncertainties) EXPECT_MAT_NEAR(err_std, cv::Vec2d(0.187475975266883, 0.185678953263995), 1e-10); CV_Assert(abs(rms - 0.263782587133546) < 1e-10); CV_Assert(errors.alpha == 0); - } +} TEST_F(FisheyeTest, rectify) { @@ -375,7 +368,6 @@ TEST_F(FisheyeTest, rectify) } } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// FisheyeTest:: @@ -393,7 +385,6 @@ const cv::Matx33d FisheyeTest::R ( 9.9756700084424932e-01, 6.9698277640183867e-0 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; @@ -421,44 +412,28 @@ std::string FisheyeTest::combine_format(const std::string& item1, const std::str return std::string(buffer); } -void FisheyeTest::readPoins(std::vector >& objectPoints, +void FisheyeTest::readPoints(std::vector >& objectPoints, std::vector >& imagePoints, - const std::string& path, const int n_images, const int n_points) + const std::string& path, const int n_images) { objectPoints.resize(n_images); imagePoints.resize(n_images); - std::vector image(n_points); - std::vector object(n_points); - - std::ifstream ipStream; - std::ifstream opStream; - - for (int image_idx = 0; image_idx < n_images; image_idx++) + cv::FileStorage fs1(combine(path, "objectPoints.xml"), cv::FileStorage::READ); + CV_Assert(fs1.isOpened()); + for (size_t i = 0; i < objectPoints.size(); ++i) { - std::stringstream ss; - ss << image_idx; - std::string idxStr = ss.str(); - - ipStream.open(combine(path, std::string(std::string("x_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); - opStream.open(combine(path, std::string(std::string("X_") + idxStr + std::string(".csv"))).c_str(), std::ifstream::in); - CV_Assert(ipStream.is_open() && opStream.is_open()); - - for (int point_idx = 0; point_idx < n_points; point_idx++) - { - double x, y, z; - char delim; - ipStream >> x >> delim >> y; - image[point_idx] = cv::Point2d(x, y); - opStream >> x >> delim >> y >> delim >> z; - object[point_idx] = cv::Point3d(x, y, z); - } - ipStream.close(); - opStream.close(); - - imagePoints[image_idx] = image; - objectPoints[image_idx] = object; + fs1[cv::format("image_%d", i)] >> objectPoints[i]; } + fs1.release(); + + cv::FileStorage fs2(combine(path, "imagePoints.xml"), cv::FileStorage::READ); + CV_Assert(fs2.isOpened()); + for (size_t i = 0; i < imagePoints.size(); ++i) + { + fs2[cv::format("image_%d", i)] >> imagePoints[i]; + } + fs2.release(); } void FisheyeTest::readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, From c2341fd4464265374ff86829f3e1c8aad0254b50 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 5 May 2014 14:21:24 +0400 Subject: [PATCH 06/15] Added stereoCalibrate for Fisheye camera model --- .../include/opencv2/calib3d/calib3d.hpp | 10 +- modules/calib3d/src/fisheye.cpp | 413 ++++++++++++++++++ modules/calib3d/src/fisheye.hpp | 12 + 3 files changed, 434 insertions(+), 1 deletion(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 8caebd363..b495fbe2d 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -757,7 +757,8 @@ public: CALIB_FIX_K1 = 16, CALIB_FIX_K2 = 32, CALIB_FIX_K3 = 64, - CALIB_FIX_K4 = 128 + CALIB_FIX_K4 = 128, + CALIB_FIX_INTRINSIC = 256 }; //! projects 3D points using fisheye model @@ -802,6 +803,13 @@ public: static void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0); + + //! performs stereo calibaration + static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, + InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, + InputOutputArrayOfArrays rvecs1, InputOutputArrayOfArrays tvecs1, + InputOutputArrayOfArrays rvecs2, InputOutputArrayOfArrays tvecs2, + TermCriteria criteria = TermCriteria(3, 100, 1e-10)); }; } diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index a9172c9b2..19b30428e 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -2,6 +2,7 @@ #include "opencv2/core/affine.hpp" #include "opencv2/core/affine.hpp" #include "fisheye.hpp" +#include "iomanip" namespace cv { namespace { @@ -12,6 +13,8 @@ namespace cv { namespace Vec3d dom, dT; double dalpha; }; + + void subMatrix(const Mat& src, Mat& dst, const vector& cols, const vector& rows); }} ////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -757,6 +760,297 @@ double cv::Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArray return rms; } +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// cv::Fisheye::stereoCalibrate + +double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, + InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, + InputOutputArray rvecs1, InputOutputArrayOfArrays tvecs1, + InputOutputArrayOfArrays rvecs2, InputOutputArrayOfArrays tvecs2, + TermCriteria criteria) +{ + CV_Assert(!objectPoints.empty() && !imagePoints1.empty() && !imagePoints2.empty()); + CV_Assert(objectPoints.total() == imagePoints1.total() || imagePoints1.total() == imagePoints2.total()); + CV_Assert(objectPoints.type() == CV_32FC3 || objectPoints.type() == CV_64FC3); + CV_Assert(imagePoints1.type() == CV_32FC2 || imagePoints1.type() == CV_64FC2); + CV_Assert(imagePoints2.type() == CV_32FC2 || imagePoints2.type() == CV_64FC2); + + CV_Assert((!K1.empty() && K1.size() == Size(3,3)) || K1.empty()); + CV_Assert((!D1.empty() && D1.total() == 4) || D1.empty()); + CV_Assert((!K2.empty() && K1.size() == Size(3,3)) || K2.empty()); + CV_Assert((!D2.empty() && D1.total() == 4) || D2.empty()); + + CV_Assert((!rvecs1.empty() && rvecs1.channels() == 3) || rvecs1.empty()); + CV_Assert((!tvecs1.empty() && tvecs1.channels() == 3) || tvecs1.empty()); + CV_Assert((!rvecs2.empty() && rvecs2.channels() == 3) || rvecs2.empty()); + CV_Assert((!tvecs2.empty() && tvecs2.channels() == 3) || tvecs2.empty()); + + //-------------------------------Initialization + + const int threshold = 50; + + size_t n_points = objectPoints.getMat(0).total(); + size_t n_images = objectPoints.total(); + + double change = 1; + + cv::internal::IntrinsicParams intrinsicLeft; + cv::internal::IntrinsicParams intrinsicRight; + + cv::internal::IntrinsicParams intrinsicLeft_errors; + cv::internal::IntrinsicParams intrinsicRight_errors; + + Matx33d _K; + Vec4d _D; + + K1.getMat().convertTo(_K, CV_64FC1); + D1.getMat().convertTo(_D, CV_64FC1); + intrinsicLeft.Init(Vec2d(_K(0,0), _K(1, 1)), Vec2d(_K(0,2), _K(1, 2)), + Vec4d(_D[0], _D[1], _D[2], _D[3]), _K(0, 1) / _K(0, 0)); + + K2.getMat().convertTo(_K, CV_64FC1); + D2.getMat().convertTo(_D, CV_64FC1); + intrinsicRight.Init(Vec2d(_K(0,0), _K(1, 1)), Vec2d(_K(0,2), _K(1, 2)), + Vec4d(_D[0], _D[1], _D[2], _D[3]), _K(0, 1) / _K(0, 0)); + + intrinsicLeft.isEstimate[0] = 1; + intrinsicLeft.isEstimate[1] = 1; + intrinsicLeft.isEstimate[2] = 1; + intrinsicLeft.isEstimate[3] = 1; + intrinsicLeft.isEstimate[4] = 0; + intrinsicLeft.isEstimate[5] = 1; + intrinsicLeft.isEstimate[6] = 1; + intrinsicLeft.isEstimate[7] = 1; + intrinsicLeft.isEstimate[8] = 1; + + intrinsicRight.isEstimate[0] = 1; + intrinsicRight.isEstimate[1] = 1; + intrinsicRight.isEstimate[2] = 1; + intrinsicRight.isEstimate[3] = 1; + intrinsicRight.isEstimate[4] = 0; + intrinsicRight.isEstimate[5] = 1; + intrinsicRight.isEstimate[6] = 1; + intrinsicRight.isEstimate[7] = 1; + intrinsicRight.isEstimate[8] = 1; + + intrinsicLeft_errors.isEstimate = intrinsicLeft.isEstimate; + intrinsicRight_errors.isEstimate = intrinsicRight.isEstimate; + + std::vector selectedParams; + std::vector tmp(6 * (n_images + 1), 1); + selectedParams.insert(selectedParams.end(), intrinsicLeft.isEstimate.begin(), intrinsicLeft.isEstimate.end()); + selectedParams.insert(selectedParams.end(), intrinsicRight.isEstimate.begin(), intrinsicRight.isEstimate.end()); + selectedParams.insert(selectedParams.end(), tmp.begin(), tmp.end()); + + //Init values for rotation and translation between two views + cv::Mat om_list(1, n_images, CV_64FC3), T_list(1, n_images, CV_64FC3); + cv::Mat om_ref, R_ref, T_ref, R1, R2; + for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + { + cv::Rodrigues(rvecs1.getMat(image_idx), R1); + cv::Rodrigues(rvecs2.getMat(image_idx), R2); + R_ref = R2 * R1.t(); + T_ref = tvecs2.getMat(image_idx).reshape(1, 3) - R_ref * tvecs1.getMat(image_idx).reshape(1, 3); + cv::Rodrigues(R_ref, om_ref); + om_ref.reshape(3, 1).copyTo(om_list.col(image_idx)); + T_ref.reshape(3, 1).copyTo(T_list.col(image_idx)); + } + cv::Vec3d omcur = internal::median3d(om_list); + cv::Vec3d Tcur = internal::median3d(T_list); + + cv::Mat J = cv::Mat::zeros(4 * n_points * n_images, 18 + 6 * (n_images + 1), CV_64FC1), + e = cv::Mat::zeros(4 * n_points * n_images, 1, CV_64FC1), Jkk, ekk; + cv::Mat J2_inv; + for(int iter = 0; ; ++iter) + { + if ((criteria.type == 1 && iter >= criteria.maxCount) || + (criteria.type == 2 && change <= criteria.epsilon) || + (criteria.type == 3 && (change <= criteria.epsilon || iter >= criteria.maxCount))) + break; + + J.create(4 * n_points * n_images, 18 + 6 * (n_images + 1), CV_64FC1); + e.create(4 * n_points * n_images, 1, CV_64FC1); + Jkk.create(4 * n_points, 18 + 6 * (n_images + 1), CV_64FC1); + ekk.create(4 * n_points, 1, CV_64FC1); + + cv::Mat omr, Tr, domrdomckk, domrdTckk, domrdom, domrdT, dTrdomckk, dTrdTckk, dTrdom, dTrdT; + + for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + { + Jkk = cv::Mat::zeros(4 * n_points, 18 + 6 * (n_images + 1), CV_64FC1); + + cv::Mat object = objectPoints.getMat(image_idx).clone(); + cv::Mat imageLeft = imagePoints1.getMat(image_idx).clone(); + cv::Mat imageRight = imagePoints2.getMat(image_idx).clone(); + cv::Mat jacobians, projected; + + //left camera jacobian + cv::Mat rvec = rvecs1.getMat(image_idx).clone(); + cv::Mat tvec = tvecs1.getMat(image_idx).clone(); + cv::internal::projectPoints(object, projected, rvec, tvec, intrinsicLeft, jacobians); + cv::Mat(cv::Mat((imageLeft - projected).t()).reshape(1, 1).t()).copyTo(ekk.rowRange(0, 2 * n_points)); + jacobians.colRange(8, 11).copyTo(Jkk.colRange(24 + image_idx * 6, 27 + image_idx * 6).rowRange(0, 2 * n_points)); + jacobians.colRange(11, 14).copyTo(Jkk.colRange(27 + image_idx * 6, 30 + image_idx * 6).rowRange(0, 2 * n_points)); + jacobians.colRange(0, 2).copyTo(Jkk.colRange(0, 2).rowRange(0, 2 * n_points)); + jacobians.colRange(2, 4).copyTo(Jkk.colRange(2, 4).rowRange(0, 2 * n_points)); + jacobians.colRange(4, 8).copyTo(Jkk.colRange(5, 9).rowRange(0, 2 * n_points)); + jacobians.col(14).copyTo(Jkk.col(4).rowRange(0, 2 * n_points)); + + //right camera jacobian + internal::compose_motion(rvec, tvec, omcur, Tcur, omr, Tr, domrdomckk, domrdTckk, domrdom, domrdT, dTrdomckk, dTrdTckk, dTrdom, dTrdT); + rvec = rvecs2.getMat(image_idx).clone(); + tvec = tvecs2.getMat(image_idx).clone(); + + cv::internal::projectPoints(object, projected, omr, Tr, intrinsicRight, jacobians); + cv::Mat(cv::Mat((imageRight - projected).t()).reshape(1, 1).t()).copyTo(ekk.rowRange(2 * n_points, 4 * n_points)); + cv::Mat dxrdom = jacobians.colRange(8, 11) * domrdom + jacobians.colRange(11, 14) * dTrdom; + cv::Mat dxrdT = jacobians.colRange(8, 11) * domrdT + jacobians.colRange(11, 14)* dTrdT; + cv::Mat dxrdomckk = jacobians.colRange(8, 11) * domrdomckk + jacobians.colRange(11, 14) * dTrdomckk; + cv::Mat dxrdTckk = jacobians.colRange(8, 11) * domrdTckk + jacobians.colRange(11, 14) * dTrdTckk; + + dxrdom.copyTo(Jkk.colRange(18, 21).rowRange(2 * n_points, 4 * n_points)); + dxrdT.copyTo(Jkk.colRange(21, 24).rowRange(2 * n_points, 4 * n_points)); + dxrdomckk.copyTo(Jkk.colRange(24 + image_idx * 6, 27 + image_idx * 6).rowRange(2 * n_points, 4 * n_points)); + dxrdTckk.copyTo(Jkk.colRange(27 + image_idx * 6, 30 + image_idx * 6).rowRange(2 * n_points, 4 * n_points)); + jacobians.colRange(0, 2).copyTo(Jkk.colRange(9 + 0, 9 + 2).rowRange(2 * n_points, 4 * n_points)); + jacobians.colRange(2, 4).copyTo(Jkk.colRange(9 + 2, 9 + 4).rowRange(2 * n_points, 4 * n_points)); + jacobians.colRange(4, 8).copyTo(Jkk.colRange(9 + 5, 9 + 9).rowRange(2 * n_points, 4 * n_points)); + jacobians.col(14).copyTo(Jkk.col(9 + 4).rowRange(2 * n_points, 4 * n_points)); + + //check goodness of sterepair + double abs_max = 0; + for (size_t i = 0; i < 4 * n_points; i++) + { + if (fabs(ekk.at(i)) > abs_max) + { + abs_max = fabs(ekk.at(i)); + } + } + if (abs_max < threshold) + { + Jkk.copyTo(J.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); + ekk.copyTo(e.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); + } + else + { + CV_Assert(!"Bad stereo pair"); + } + } + + cv::Vec6d oldTom(Tcur[0], Tcur[1], Tcur[2], omcur[0], omcur[1], omcur[2]); + + //update all parameters + cv::subMatrix(J, J, selectedParams, std::vector(J.rows, 1)); + cv::Mat J2 = J.t() * J; + J2_inv = J2.inv(); + int a = cv::countNonZero(intrinsicLeft.isEstimate); + int b = cv::countNonZero(intrinsicRight.isEstimate); + cv::Mat deltas = J2_inv * J.t() * e; + intrinsicLeft = intrinsicLeft + deltas.rowRange(0, a); + intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); + omcur = omcur + cv::Vec3d(deltas.rowRange(a + b, a + b + 3)); + Tcur = Tcur + cv::Vec3d(deltas.rowRange(a + b + 3, a + b + 6)); + for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + { + rvecs1.getMat(image_idx) = rvecs1.getMat(image_idx) + deltas.rowRange(a + b + 6 + image_idx * 6, a + b + 9 + image_idx * 6).reshape(3); + tvecs1.getMat(image_idx) = tvecs1.getMat(image_idx) + deltas.rowRange(a + b + 9 + image_idx * 6, a + b + 12 + image_idx * 6).reshape(3); + } + + cv::Vec6d newTom(Tcur[0], Tcur[1], Tcur[2], omcur[0], omcur[1], omcur[2]); + change = cv::norm(newTom - oldTom) / cv::norm(newTom); + } + + //estimate uncertainties + cv::Mat sigma_x; + cv::meanStdDev(e, cv::noArray(), sigma_x); + sigma_x *= sqrt((double)e.total() / (e.total() - 1)); + cv::sqrt(J2_inv, J2_inv); + cv::Mat errors = 3 * J2_inv.diag() * sigma_x; + int a1 = cv::countNonZero(intrinsicLeft_errors.isEstimate); + int b1 = cv::countNonZero(intrinsicRight_errors.isEstimate); + intrinsicLeft_errors = errors.rowRange(0, a1); + intrinsicRight_errors = errors.rowRange(a1, a1 + b1); + cv::Vec3d om_error = cv::Vec3d(errors.rowRange(a1 + b1, a1 + b1 + 3)); + cv::Vec3d T_error = cv::Vec3d(errors.rowRange(a1 + b1 + 3, a1 + b1 + 6)); + + std::cout << std::setprecision(15) << "left f = " << intrinsicLeft.f << std::endl; + std::cout << std::setprecision(15) << "left c = " << intrinsicLeft.c << std::endl; + std::cout << std::setprecision(15) << "left alpha = " << intrinsicLeft.alpha << std::endl; + std::cout << std::setprecision(15) << "left k = " << intrinsicLeft.k << std::endl; + + std::cout << std::setprecision(15) << "right f = " << intrinsicRight.f << std::endl; + std::cout << std::setprecision(15) << "right c = " << intrinsicRight.c << std::endl; + std::cout << std::setprecision(15) << "right alpha = " << intrinsicRight.alpha << std::endl; + std::cout << std::setprecision(15) << "right k = " << intrinsicRight.k << std::endl; + + std::cout << omcur << std::endl; + std::cout << Tcur << std::endl; + std::cout << "====================================================================================" << std::endl; + std::cout.flush(); + + std::cout << std::setprecision(15) << "left f = " << intrinsicLeft_errors.f << std::endl; + std::cout << std::setprecision(15) << "left c = " << intrinsicLeft_errors.c << std::endl; + std::cout << std::setprecision(15) << "left alpha = " << intrinsicLeft_errors.alpha << std::endl; + std::cout << std::setprecision(15) << "left k = " << intrinsicLeft_errors.k << std::endl; + + std::cout << std::setprecision(15) << "right f = " << intrinsicRight_errors.f << std::endl; + std::cout << std::setprecision(15) << "right c = " << intrinsicRight_errors.c << std::endl; + std::cout << std::setprecision(15) << "right alpha = " << intrinsicRight_errors.alpha << std::endl; + std::cout << std::setprecision(15) << "right k = " << intrinsicRight_errors.k << std::endl; + + std::cout << om_error << std::endl; + std::cout << T_error << std::endl; + std::cout << "====================================================================================" << std::endl; + std::cout.flush(); + + CV_Assert(cv::norm(intrinsicLeft.f - cv::Vec2d(561.195925927249, 562.849402029712)) < 1e-12); + CV_Assert(cv::norm(intrinsicLeft.c - cv::Vec2d(621.282400272412, 380.555455380889)) < 1e-12); + CV_Assert(intrinsicLeft.alpha == 0); + CV_Assert(cv::norm(intrinsicLeft.k - cv::Vec4d(-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771)) < 1e-12); + CV_Assert(cv::norm(intrinsicRight.f - cv::Vec2d(560.395452535348, 561.90171021422)) < 1e-12); + CV_Assert(cv::norm(intrinsicRight.c - cv::Vec2d(678.971652040359, 380.401340535339)) < 1e-12); + CV_Assert(intrinsicRight.alpha == 0); + CV_Assert(cv::norm(intrinsicRight.k - cv::Vec4d(-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222)) < 1e-12); + CV_Assert(cv::norm(omcur - cv::Vec3d(-0.00605728469659871, 0.006287139337868821, -0.06960627514977027)) < 1e-12); + CV_Assert(cv::norm(Tcur - cv::Vec3d(-0.09940272472412097, 0.002708121392654134, 0.001293302924726987)) < 1e-12); + + CV_Assert(cv::norm(intrinsicLeft_errors.f - cv::Vec2d(0.71024293066476, 0.717596249442966)) < 1e-12); + CV_Assert(cv::norm(intrinsicLeft_errors.c - cv::Vec2d(0.782164491020839, 0.538718002947604)) < 1e-12); + CV_Assert(intrinsicLeft_errors.alpha == 0); + CV_Assert(cv::norm(intrinsicLeft_errors.k - cv::Vec4d(0.00525819017878291, 0.0179451746982225, 0.0236417266063274, 0.0104757238170252)) < 1e-12); + CV_Assert(cv::norm(intrinsicRight_errors.f - cv::Vec2d(0.748707568264116, 0.745355483082726)) < 1e-12); + CV_Assert(cv::norm(intrinsicRight_errors.c - cv::Vec2d(0.788236834082615, 0.538854504490304)) < 1e-12); + CV_Assert(intrinsicRight_errors.alpha == 0); + CV_Assert(cv::norm(intrinsicRight_errors.k - cv::Vec4d(0.00534743998208779, 0.0175804116710864, 0.0226549382734192, 0.00979255348533809)) < 1e-12); + CV_Assert(cv::norm(om_error - cv::Vec3d(0.0005903298904975326, 0.001048251127879415, 0.0001775640833531587)) < 1e-12); + CV_Assert(cv::norm(T_error - cv::Vec3d(6.691282702437657e-05, 5.566841336891827e-05, 0.0001954901454589594)) < 1e-12); + + + Matx33d _K1 = Matx33d(intrinsicLeft.f[0], intrinsicLeft.f[0] * intrinsicLeft.alpha, intrinsicLeft.c[0], + 0, intrinsicLeft.f[1], intrinsicLeft.c[1], + 0, 0, 1); + + Matx33d _K2 = Matx33d(intrinsicRight.f[0], intrinsicRight.f[0] * intrinsicRight.alpha, intrinsicRight.c[0], + 0, intrinsicRight.f[1], intrinsicRight.c[1], + 0, 0, 1); + + Mat _R; + Rodrigues(omcur, _R); + +// if (K1.needed()) cv::Mat(_K1).convertTo(K2, K1.empty() ? CV_64FC1 : K1.type()); +// if (K2.needed()) cv::Mat(_K2).convertTo(K2, K2.empty() ? CV_64FC1 : K2.type()); +// if (D1.needed()) cv::Mat(intrinsicLeft.k).convertTo(D1, D1.empty() ? CV_64FC1 : D1.type()); +// if (D2.needed()) cv::Mat(intrinsicRight.k).convertTo(D2, D2.empty() ? CV_64FC1 : D2.type()); +// if (R.needed()) _R.convertTo(R, R.empty() ? CV_64FC1 : R.type()); +// if (T.needed()) Tcur.convertTo(R, R.empty() ? CV_64FC1 : R.type()); + +// if (rvecs1.needed()) cv::Mat(omc).convertTo(rvecs, rvecs.empty() ? CV_64FC3 : rvecs.type()); +// if (tvecs.needed()) cv::Mat(Tc).convertTo(tvecs, tvecs.empty() ? CV_64FC3 : tvecs.type()); + + + return 0; +} + namespace cv{ namespace { void subMatrix(const Mat& src, Mat& dst, const vector& cols, const vector& rows) { @@ -1216,3 +1510,122 @@ void cv::internal::EstimateUncertainties(InputArrayOfArrays objectPoints, InputA rms /= ex.total(); rms = sqrt(rms); } + +void cv::internal::dAB(InputArray A, InputArray B, OutputArray dABdA, OutputArray dABdB) +{ + CV_Assert(A.getMat().cols == B.getMat().rows); + CV_Assert(A.type() == CV_64FC1 && B.type() == CV_64FC1); + + size_t p = A.getMat().rows; + size_t n = A.getMat().cols; + size_t q = B.getMat().cols; + + dABdA.create(p * q, p * n, CV_64FC1); + dABdB.create(p * q, q * n, CV_64FC1); + + dABdA.getMat() = Mat::zeros(p * q, p * n, CV_64FC1); + dABdB.getMat() = Mat::zeros(p * q, q * n, CV_64FC1); + + for (size_t i = 0; i < q; ++i) + { + for (size_t j = 0; j < p; ++j) + { + size_t ij = j + i * p; + for (size_t k = 0; k < n; ++k) + { + size_t kj = j + k * p; + dABdA.getMat().at(ij, kj) = B.getMat().at(k, i); + } + } + } + + for (size_t i = 0; i < q; ++i) + { + A.getMat().copyTo(dABdB.getMat().rowRange(i * p, i * p + p).colRange(i * n, i * n + n)); + } +} + +void cv::internal::JRodriguesMatlab(const Mat& src, Mat& dst) +{ + Mat tmp(src.cols, src.rows, src.type()); + if (src.rows == 9) + { + Mat(src.row(0).t()).copyTo(tmp.col(0)); + Mat(src.row(1).t()).copyTo(tmp.col(3)); + Mat(src.row(2).t()).copyTo(tmp.col(6)); + Mat(src.row(3).t()).copyTo(tmp.col(1)); + Mat(src.row(4).t()).copyTo(tmp.col(4)); + Mat(src.row(5).t()).copyTo(tmp.col(7)); + Mat(src.row(6).t()).copyTo(tmp.col(2)); + Mat(src.row(7).t()).copyTo(tmp.col(5)); + Mat(src.row(8).t()).copyTo(tmp.col(8)); + } + else + { + Mat(src.col(0).t()).copyTo(tmp.row(0)); + Mat(src.col(1).t()).copyTo(tmp.row(3)); + Mat(src.col(2).t()).copyTo(tmp.row(6)); + Mat(src.col(3).t()).copyTo(tmp.row(1)); + Mat(src.col(4).t()).copyTo(tmp.row(4)); + Mat(src.col(5).t()).copyTo(tmp.row(7)); + Mat(src.col(6).t()).copyTo(tmp.row(2)); + Mat(src.col(7).t()).copyTo(tmp.row(5)); + Mat(src.col(8).t()).copyTo(tmp.row(8)); + } + dst = tmp.clone(); +} + +void cv::internal::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) +{ + Mat om1 = _om1.getMat(); + Mat om2 = _om2.getMat(); + Mat T1 = _T1.getMat().reshape(1, 3); + Mat T2 = _T2.getMat().reshape(1, 3); + + //% Rotations: + Mat R1, R2, R3, dR1dom1(9, 3, CV_64FC1), dR2dom2; + Rodrigues(om1, R1, dR1dom1); + Rodrigues(om2, R2, dR2dom2); + JRodriguesMatlab(dR1dom1, dR1dom1); + JRodriguesMatlab(dR2dom2, dR2dom2); + R3 = R2 * R1; + Mat dR3dR2, dR3dR1; + dAB(R2, R1, dR3dR2, dR3dR1); + Mat dom3dR3; + Rodrigues(R3, om3, dom3dR3); + JRodriguesMatlab(dom3dR3, dom3dR3); + dom3dom1 = dom3dR3 * dR3dR1 * dR1dom1; + dom3dom2 = dom3dR3 * dR3dR2 * dR2dom2; + dom3dT1 = Mat::zeros(3, 3, CV_64FC1); + dom3dT2 = Mat::zeros(3, 3, CV_64FC1); + + //% Translations: + Mat T3t = R2 * T1; + Mat dT3tdR2, dT3tdT1; + dAB(R2, T1, dT3tdR2, dT3tdT1); + Mat dT3tdom2 = dT3tdR2 * dR2dom2; + T3 = T3t + T2; + dT3dT1 = dT3tdT1; + dT3dT2 = Mat::eye(3, 3, CV_64FC1); + dT3dom2 = dT3tdom2; + dT3dom1 = Mat::zeros(3, 3, CV_64FC1); +} + +double cv::internal::median(const Mat& row) +{ + CV_Assert(row.type() == CV_64FC1); + CV_Assert(!row.empty() && row.rows == 1); + Mat tmp = row.clone(); + sort(tmp, tmp, 0); + if (tmp.total() % 2) return tmp.at(tmp.total() / 2); + else return 0.5 *(tmp.at(tmp.total() / 2) + tmp.at(tmp.total() / 2 - 1)); +} + +cv::Vec3d cv::internal::median3d(InputArray m) +{ + CV_Assert(m.depth() == CV_64F && m.getMat().rows == 1); + Mat M = Mat(m.getMat().t()).reshape(1).t(); + return Vec3d(median(M.row(0)), median(M.row(1)), median(M.row(2))); +} diff --git a/modules/calib3d/src/fisheye.hpp b/modules/calib3d/src/fisheye.hpp index e000e635a..fa4bfdb38 100644 --- a/modules/calib3d/src/fisheye.hpp +++ b/modules/calib3d/src/fisheye.hpp @@ -43,6 +43,18 @@ void EstimateUncertainties(InputArrayOfArrays objectPoints, InputArrayOfArrays i 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 From 50b291995a71c7176dad21a39b7151cf81644700 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 5 May 2014 17:23:03 +0400 Subject: [PATCH 07/15] Added tests for stereoCalibrate --- .../include/opencv2/calib3d/calib3d.hpp | 6 +- modules/calib3d/src/fisheye.cpp | 196 ++++++--------- modules/calib3d/test/test_fisheye.cpp | 235 ++++++++++++++++++ 3 files changed, 312 insertions(+), 125 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index b495fbe2d..b6d85a095 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -806,10 +806,10 @@ public: //! performs stereo calibaration static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, - InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, - InputOutputArrayOfArrays rvecs1, InputOutputArrayOfArrays tvecs1, - InputOutputArrayOfArrays rvecs2, InputOutputArrayOfArrays tvecs2, + InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, + OutputArray R, OutputArray T, int flags, TermCriteria criteria = TermCriteria(3, 100, 1e-10)); + }; } diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 19b30428e..50dd04528 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -764,10 +764,8 @@ double cv::Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArray /// cv::Fisheye::stereoCalibrate double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, - InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, - InputOutputArray rvecs1, InputOutputArrayOfArrays tvecs1, - InputOutputArrayOfArrays rvecs2, InputOutputArrayOfArrays tvecs2, - TermCriteria criteria) + InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, + OutputArray R, OutputArray T, int flags, TermCriteria criteria) { CV_Assert(!objectPoints.empty() && !imagePoints1.empty() && !imagePoints2.empty()); CV_Assert(objectPoints.total() == imagePoints1.total() || imagePoints1.total() == imagePoints2.total()); @@ -780,14 +778,13 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO CV_Assert((!K2.empty() && K1.size() == Size(3,3)) || K2.empty()); CV_Assert((!D2.empty() && D1.total() == 4) || D2.empty()); - CV_Assert((!rvecs1.empty() && rvecs1.channels() == 3) || rvecs1.empty()); - CV_Assert((!tvecs1.empty() && tvecs1.channels() == 3) || tvecs1.empty()); - CV_Assert((!rvecs2.empty() && rvecs2.channels() == 3) || rvecs2.empty()); - CV_Assert((!tvecs2.empty() && tvecs2.channels() == 3) || tvecs2.empty()); + CV_Assert(((flags & CALIB_FIX_INTRINSIC) && !K1.empty() && !K2.empty() && !D1.empty() && !D2.empty()) || !(flags & CALIB_FIX_INTRINSIC)); //-------------------------------Initialization const int threshold = 50; + const double thresh_cond = 1e6; + const int check_cond = 1; size_t n_points = objectPoints.getMat(0).total(); size_t n_images = objectPoints.total(); @@ -800,38 +797,52 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO cv::internal::IntrinsicParams intrinsicLeft_errors; cv::internal::IntrinsicParams intrinsicRight_errors; - Matx33d _K; - Vec4d _D; + Matx33d _K1, _K2; + Vec4d _D1, _D2; + if (!K1.empty()) K1.getMat().convertTo(_K1, CV_64FC1); + if (!D1.empty()) D1.getMat().convertTo(_D1, CV_64FC1); + if (!K2.empty()) K2.getMat().convertTo(_K2, CV_64FC1); + if (!D2.empty()) D2.getMat().convertTo(_D2, CV_64FC1); - K1.getMat().convertTo(_K, CV_64FC1); - D1.getMat().convertTo(_D, CV_64FC1); - intrinsicLeft.Init(Vec2d(_K(0,0), _K(1, 1)), Vec2d(_K(0,2), _K(1, 2)), - Vec4d(_D[0], _D[1], _D[2], _D[3]), _K(0, 1) / _K(0, 0)); + std::vector rvecs1(n_images), tvecs1(n_images), rvecs2(n_images), tvecs2(n_images); - K2.getMat().convertTo(_K, CV_64FC1); - D2.getMat().convertTo(_D, CV_64FC1); - intrinsicRight.Init(Vec2d(_K(0,0), _K(1, 1)), Vec2d(_K(0,2), _K(1, 2)), - Vec4d(_D[0], _D[1], _D[2], _D[3]), _K(0, 1) / _K(0, 0)); + if (!(flags & CALIB_FIX_INTRINSIC)) + { + calibrate(objectPoints, imagePoints1, imageSize, _K1, _D1, rvecs1, tvecs1, flags, TermCriteria(3, 20, 1e-6)); + calibrate(objectPoints, imagePoints2, imageSize, _K2, _D2, rvecs2, tvecs2, flags, TermCriteria(3, 20, 1e-6)); + } - intrinsicLeft.isEstimate[0] = 1; - intrinsicLeft.isEstimate[1] = 1; - intrinsicLeft.isEstimate[2] = 1; - intrinsicLeft.isEstimate[3] = 1; - intrinsicLeft.isEstimate[4] = 0; - intrinsicLeft.isEstimate[5] = 1; - intrinsicLeft.isEstimate[6] = 1; - intrinsicLeft.isEstimate[7] = 1; - intrinsicLeft.isEstimate[8] = 1; + intrinsicLeft.Init(Vec2d(_K1(0,0), _K1(1, 1)), Vec2d(_K1(0,2), _K1(1, 2)), + Vec4d(_D1[0], _D1[1], _D1[2], _D1[3]), _K1(0, 1) / _K1(0, 0)); - intrinsicRight.isEstimate[0] = 1; - intrinsicRight.isEstimate[1] = 1; - intrinsicRight.isEstimate[2] = 1; - intrinsicRight.isEstimate[3] = 1; - intrinsicRight.isEstimate[4] = 0; - intrinsicRight.isEstimate[5] = 1; - intrinsicRight.isEstimate[6] = 1; - intrinsicRight.isEstimate[7] = 1; - intrinsicRight.isEstimate[8] = 1; + intrinsicRight.Init(Vec2d(_K2(0,0), _K2(1, 1)), Vec2d(_K2(0,2), _K2(1, 2)), + Vec4d(_D2[0], _D2[1], _D2[2], _D2[3]), _K2(0, 1) / _K2(0, 0)); + + if ((flags & CALIB_FIX_INTRINSIC)) + { + internal::CalibrateExtrinsics(objectPoints, imagePoints1, intrinsicLeft, check_cond, thresh_cond, rvecs1, tvecs1); + internal::CalibrateExtrinsics(objectPoints, imagePoints2, intrinsicRight, check_cond, thresh_cond, rvecs2, tvecs2); + } + + intrinsicLeft.isEstimate[0] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicLeft.isEstimate[1] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicLeft.isEstimate[2] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicLeft.isEstimate[3] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicLeft.isEstimate[4] = flags & (CALIB_FIX_SKEW | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicLeft.isEstimate[5] = flags & (CALIB_FIX_K1 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicLeft.isEstimate[6] = flags & (CALIB_FIX_K2 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicLeft.isEstimate[7] = flags & (CALIB_FIX_K3 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicLeft.isEstimate[8] = flags & (CALIB_FIX_K4 | CALIB_FIX_INTRINSIC) ? 0 : 1; + + intrinsicRight.isEstimate[0] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicRight.isEstimate[1] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicRight.isEstimate[2] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicRight.isEstimate[3] = flags & CALIB_FIX_INTRINSIC ? 0 : 1; + intrinsicRight.isEstimate[4] = flags & (CALIB_FIX_SKEW | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicRight.isEstimate[5] = flags & (CALIB_FIX_K1 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicRight.isEstimate[6] = flags & (CALIB_FIX_K2 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicRight.isEstimate[7] = flags & (CALIB_FIX_K3 | CALIB_FIX_INTRINSIC) ? 0 : 1; + intrinsicRight.isEstimate[8] = flags & (CALIB_FIX_K4 | CALIB_FIX_INTRINSIC) ? 0 : 1; intrinsicLeft_errors.isEstimate = intrinsicLeft.isEstimate; intrinsicRight_errors.isEstimate = intrinsicRight.isEstimate; @@ -847,10 +858,10 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO cv::Mat om_ref, R_ref, T_ref, R1, R2; for (size_t image_idx = 0; image_idx < n_images; ++image_idx) { - cv::Rodrigues(rvecs1.getMat(image_idx), R1); - cv::Rodrigues(rvecs2.getMat(image_idx), R2); + cv::Rodrigues(rvecs1[image_idx], R1); + cv::Rodrigues(rvecs2[image_idx], R2); R_ref = R2 * R1.t(); - T_ref = tvecs2.getMat(image_idx).reshape(1, 3) - R_ref * tvecs1.getMat(image_idx).reshape(1, 3); + T_ref = cv::Mat(tvecs2[image_idx]) - R_ref * cv::Mat(tvecs1[image_idx]); cv::Rodrigues(R_ref, om_ref); om_ref.reshape(3, 1).copyTo(om_list.col(image_idx)); T_ref.reshape(3, 1).copyTo(T_list.col(image_idx)); @@ -861,6 +872,7 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO cv::Mat J = cv::Mat::zeros(4 * n_points * n_images, 18 + 6 * (n_images + 1), CV_64FC1), e = cv::Mat::zeros(4 * n_points * n_images, 1, CV_64FC1), Jkk, ekk; cv::Mat J2_inv; + for(int iter = 0; ; ++iter) { if ((criteria.type == 1 && iter >= criteria.maxCount) || @@ -885,8 +897,8 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO cv::Mat jacobians, projected; //left camera jacobian - cv::Mat rvec = rvecs1.getMat(image_idx).clone(); - cv::Mat tvec = tvecs1.getMat(image_idx).clone(); + cv::Mat rvec = cv::Mat(rvecs1[image_idx]); + cv::Mat tvec = cv::Mat(tvecs1[image_idx]); cv::internal::projectPoints(object, projected, rvec, tvec, intrinsicLeft, jacobians); cv::Mat(cv::Mat((imageLeft - projected).t()).reshape(1, 1).t()).copyTo(ekk.rowRange(0, 2 * n_points)); jacobians.colRange(8, 11).copyTo(Jkk.colRange(24 + image_idx * 6, 27 + image_idx * 6).rowRange(0, 2 * n_points)); @@ -898,8 +910,8 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO //right camera jacobian internal::compose_motion(rvec, tvec, omcur, Tcur, omr, Tr, domrdomckk, domrdTckk, domrdom, domrdT, dTrdomckk, dTrdTckk, dTrdom, dTrdT); - rvec = rvecs2.getMat(image_idx).clone(); - tvec = tvecs2.getMat(image_idx).clone(); + rvec = cv::Mat(rvecs2[image_idx]); + tvec = cv::Mat(tvecs2[image_idx]); cv::internal::projectPoints(object, projected, omr, Tr, intrinsicRight, jacobians); cv::Mat(cv::Mat((imageRight - projected).t()).reshape(1, 1).t()).copyTo(ekk.rowRange(2 * n_points, 4 * n_points)); @@ -952,103 +964,43 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO Tcur = Tcur + cv::Vec3d(deltas.rowRange(a + b + 3, a + b + 6)); for (size_t image_idx = 0; image_idx < n_images; ++image_idx) { - rvecs1.getMat(image_idx) = rvecs1.getMat(image_idx) + deltas.rowRange(a + b + 6 + image_idx * 6, a + b + 9 + image_idx * 6).reshape(3); - tvecs1.getMat(image_idx) = tvecs1.getMat(image_idx) + deltas.rowRange(a + b + 9 + image_idx * 6, a + b + 12 + image_idx * 6).reshape(3); + rvecs1[image_idx] = cv::Mat(cv::Mat(rvecs1[image_idx]) + deltas.rowRange(a + b + 6 + image_idx * 6, a + b + 9 + image_idx * 6)); + tvecs1[image_idx] = cv::Mat(cv::Mat(tvecs1[image_idx]) + deltas.rowRange(a + b + 9 + image_idx * 6, a + b + 12 + image_idx * 6)); } cv::Vec6d newTom(Tcur[0], Tcur[1], Tcur[2], omcur[0], omcur[1], omcur[2]); change = cv::norm(newTom - oldTom) / cv::norm(newTom); } - //estimate uncertainties - cv::Mat sigma_x; - cv::meanStdDev(e, cv::noArray(), sigma_x); - sigma_x *= sqrt((double)e.total() / (e.total() - 1)); - cv::sqrt(J2_inv, J2_inv); - cv::Mat errors = 3 * J2_inv.diag() * sigma_x; - int a1 = cv::countNonZero(intrinsicLeft_errors.isEstimate); - int b1 = cv::countNonZero(intrinsicRight_errors.isEstimate); - intrinsicLeft_errors = errors.rowRange(0, a1); - intrinsicRight_errors = errors.rowRange(a1, a1 + b1); - cv::Vec3d om_error = cv::Vec3d(errors.rowRange(a1 + b1, a1 + b1 + 3)); - cv::Vec3d T_error = cv::Vec3d(errors.rowRange(a1 + b1 + 3, a1 + b1 + 6)); + double rms = 0; + const Vec2d* ptr_e = e.ptr(); + for (size_t i = 0; i < e.total() / 2; i++) + { + rms += ptr_e[i][0] * ptr_e[i][0] + ptr_e[i][1] * ptr_e[i][1]; + } - std::cout << std::setprecision(15) << "left f = " << intrinsicLeft.f << std::endl; - std::cout << std::setprecision(15) << "left c = " << intrinsicLeft.c << std::endl; - std::cout << std::setprecision(15) << "left alpha = " << intrinsicLeft.alpha << std::endl; - std::cout << std::setprecision(15) << "left k = " << intrinsicLeft.k << std::endl; + rms /= (e.total() / 2); + rms = sqrt(rms); - std::cout << std::setprecision(15) << "right f = " << intrinsicRight.f << std::endl; - std::cout << std::setprecision(15) << "right c = " << intrinsicRight.c << std::endl; - std::cout << std::setprecision(15) << "right alpha = " << intrinsicRight.alpha << std::endl; - std::cout << std::setprecision(15) << "right k = " << intrinsicRight.k << std::endl; - - std::cout << omcur << std::endl; - std::cout << Tcur << std::endl; - std::cout << "====================================================================================" << std::endl; - std::cout.flush(); - - std::cout << std::setprecision(15) << "left f = " << intrinsicLeft_errors.f << std::endl; - std::cout << std::setprecision(15) << "left c = " << intrinsicLeft_errors.c << std::endl; - std::cout << std::setprecision(15) << "left alpha = " << intrinsicLeft_errors.alpha << std::endl; - std::cout << std::setprecision(15) << "left k = " << intrinsicLeft_errors.k << std::endl; - - std::cout << std::setprecision(15) << "right f = " << intrinsicRight_errors.f << std::endl; - std::cout << std::setprecision(15) << "right c = " << intrinsicRight_errors.c << std::endl; - std::cout << std::setprecision(15) << "right alpha = " << intrinsicRight_errors.alpha << std::endl; - std::cout << std::setprecision(15) << "right k = " << intrinsicRight_errors.k << std::endl; - - std::cout << om_error << std::endl; - std::cout << T_error << std::endl; - std::cout << "====================================================================================" << std::endl; - std::cout.flush(); - - CV_Assert(cv::norm(intrinsicLeft.f - cv::Vec2d(561.195925927249, 562.849402029712)) < 1e-12); - CV_Assert(cv::norm(intrinsicLeft.c - cv::Vec2d(621.282400272412, 380.555455380889)) < 1e-12); - CV_Assert(intrinsicLeft.alpha == 0); - CV_Assert(cv::norm(intrinsicLeft.k - cv::Vec4d(-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771)) < 1e-12); - CV_Assert(cv::norm(intrinsicRight.f - cv::Vec2d(560.395452535348, 561.90171021422)) < 1e-12); - CV_Assert(cv::norm(intrinsicRight.c - cv::Vec2d(678.971652040359, 380.401340535339)) < 1e-12); - CV_Assert(intrinsicRight.alpha == 0); - CV_Assert(cv::norm(intrinsicRight.k - cv::Vec4d(-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222)) < 1e-12); - CV_Assert(cv::norm(omcur - cv::Vec3d(-0.00605728469659871, 0.006287139337868821, -0.06960627514977027)) < 1e-12); - CV_Assert(cv::norm(Tcur - cv::Vec3d(-0.09940272472412097, 0.002708121392654134, 0.001293302924726987)) < 1e-12); - - CV_Assert(cv::norm(intrinsicLeft_errors.f - cv::Vec2d(0.71024293066476, 0.717596249442966)) < 1e-12); - CV_Assert(cv::norm(intrinsicLeft_errors.c - cv::Vec2d(0.782164491020839, 0.538718002947604)) < 1e-12); - CV_Assert(intrinsicLeft_errors.alpha == 0); - CV_Assert(cv::norm(intrinsicLeft_errors.k - cv::Vec4d(0.00525819017878291, 0.0179451746982225, 0.0236417266063274, 0.0104757238170252)) < 1e-12); - CV_Assert(cv::norm(intrinsicRight_errors.f - cv::Vec2d(0.748707568264116, 0.745355483082726)) < 1e-12); - CV_Assert(cv::norm(intrinsicRight_errors.c - cv::Vec2d(0.788236834082615, 0.538854504490304)) < 1e-12); - CV_Assert(intrinsicRight_errors.alpha == 0); - CV_Assert(cv::norm(intrinsicRight_errors.k - cv::Vec4d(0.00534743998208779, 0.0175804116710864, 0.0226549382734192, 0.00979255348533809)) < 1e-12); - CV_Assert(cv::norm(om_error - cv::Vec3d(0.0005903298904975326, 0.001048251127879415, 0.0001775640833531587)) < 1e-12); - CV_Assert(cv::norm(T_error - cv::Vec3d(6.691282702437657e-05, 5.566841336891827e-05, 0.0001954901454589594)) < 1e-12); - - - Matx33d _K1 = Matx33d(intrinsicLeft.f[0], intrinsicLeft.f[0] * intrinsicLeft.alpha, intrinsicLeft.c[0], + _K1 = Matx33d(intrinsicLeft.f[0], intrinsicLeft.f[0] * intrinsicLeft.alpha, intrinsicLeft.c[0], 0, intrinsicLeft.f[1], intrinsicLeft.c[1], 0, 0, 1); - Matx33d _K2 = Matx33d(intrinsicRight.f[0], intrinsicRight.f[0] * intrinsicRight.alpha, intrinsicRight.c[0], + _K2 = Matx33d(intrinsicRight.f[0], intrinsicRight.f[0] * intrinsicRight.alpha, intrinsicRight.c[0], 0, intrinsicRight.f[1], intrinsicRight.c[1], 0, 0, 1); Mat _R; Rodrigues(omcur, _R); -// if (K1.needed()) cv::Mat(_K1).convertTo(K2, K1.empty() ? CV_64FC1 : K1.type()); -// if (K2.needed()) cv::Mat(_K2).convertTo(K2, K2.empty() ? CV_64FC1 : K2.type()); -// if (D1.needed()) cv::Mat(intrinsicLeft.k).convertTo(D1, D1.empty() ? CV_64FC1 : D1.type()); -// if (D2.needed()) cv::Mat(intrinsicRight.k).convertTo(D2, D2.empty() ? CV_64FC1 : D2.type()); -// if (R.needed()) _R.convertTo(R, R.empty() ? CV_64FC1 : R.type()); -// if (T.needed()) Tcur.convertTo(R, R.empty() ? CV_64FC1 : R.type()); + if (K1.needed()) cv::Mat(_K1).convertTo(K1, K1.empty() ? CV_64FC1 : K1.type()); + if (K2.needed()) cv::Mat(_K2).convertTo(K2, K2.empty() ? CV_64FC1 : K2.type()); + if (D1.needed()) cv::Mat(intrinsicLeft.k).convertTo(D1, D1.empty() ? CV_64FC1 : D1.type()); + if (D2.needed()) cv::Mat(intrinsicRight.k).convertTo(D2, D2.empty() ? CV_64FC1 : D2.type()); + if (R.needed()) _R.convertTo(R, R.empty() ? CV_64FC1 : R.type()); + if (T.needed()) cv::Mat(Tcur).convertTo(T, T.empty() ? CV_64FC1 : T.type()); -// if (rvecs1.needed()) cv::Mat(omc).convertTo(rvecs, rvecs.empty() ? CV_64FC3 : rvecs.type()); -// if (tvecs.needed()) cv::Mat(Tc).convertTo(tvecs, tvecs.empty() ? CV_64FC3 : tvecs.type()); - - - return 0; + return rms; } namespace cv{ namespace { diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 41bbfea59..0caaf7f45 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -368,6 +368,241 @@ TEST_F(FisheyeTest, rectify) } } +TEST_F(FisheyeTest, stereoCalibrate) +{ + const int n_images = 34; + + const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY"); + + std::vector > leftPoints(n_images); + std::vector > rightPoints(n_images); + std::vector > 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::ofstream fs; + + for (size_t i = 0; i < leftPoints.size(); i++) + { + std::string ss = combine(folder, "left"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < leftPoints[i].size(); j++) + { + double x = leftPoints[i][j].x; + double y = leftPoints[i][j].y; + fs << std::setprecision(15) << x << "; " << y; + fs << std::endl; + } + fs.close(); + } + + for (size_t i = 0; i < rightPoints.size(); i++) + { + std::string ss = combine(folder, "right"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < rightPoints[i].size(); j++) + { + double x = rightPoints[i][j].x; + double y = rightPoints[i][j].y; + fs << std::setprecision(15) << x << "; " << y; + fs << std::endl; + } + fs.close(); + } + + for (size_t i = 0; i < objectPoints.size(); i++) + { + std::string ss = combine(folder, "object"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < objectPoints[i].size(); j++) + { + double x = objectPoints[i][j].x; + double y = objectPoints[i][j].y; + double z = objectPoints[i][j].z; + fs << std::setprecision(15) << x << "; " << y; + fs << std::setprecision(15) << "; " << z; + fs << std::endl; + } + fs.close(); + } + + cv::Matx33d K1, K2, R; + cv::Vec3d T; + cv::Vec4d D1, D2; + + int flag = 0; + flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::Fisheye::CALIB_CHECK_COND; + flag |= cv::Fisheye::CALIB_FIX_SKEW; + // flag |= cv::Fisheye::CALIB_FIX_INTRINSIC; + + cv::Fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints, + K1, D1, K2, D2, imageSize, R, T, 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(R, R_correct, 1e-10); + EXPECT_MAT_NEAR(T, 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 > leftPoints(n_images); + std::vector > rightPoints(n_images); + std::vector > 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::ofstream fs; + + for (size_t i = 0; i < leftPoints.size(); i++) + { + std::string ss = combine(folder, "left"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < leftPoints[i].size(); j++) + { + double x = leftPoints[i][j].x; + double y = leftPoints[i][j].y; + fs << std::setprecision(15) << x << "; " << y; + fs << std::endl; + } + fs.close(); + } + + for (size_t i = 0; i < rightPoints.size(); i++) + { + std::string ss = combine(folder, "right"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < rightPoints[i].size(); j++) + { + double x = rightPoints[i][j].x; + double y = rightPoints[i][j].y; + fs << std::setprecision(15) << x << "; " << y; + fs << std::endl; + } + fs.close(); + } + + for (size_t i = 0; i < objectPoints.size(); i++) + { + std::string ss = combine(folder, "object"); + ss = combine_format(ss, "%d", i); + fs.open(ss.c_str()); + CV_Assert(fs.is_open()); + for (size_t j = 0; j < objectPoints[i].size(); j++) + { + double x = objectPoints[i][j].x; + double y = objectPoints[i][j].y; + double z = objectPoints[i][j].z; + fs << std::setprecision(15) << x << "; " << y; + fs << std::setprecision(15) << "; " << z; + fs << std::endl; + } + fs.close(); + } + + cv::Matx33d R; + cv::Vec3d T; + + int flag = 0; + flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::Fisheye::CALIB_CHECK_COND; + flag |= cv::Fisheye::CALIB_FIX_SKEW; + flag |= cv::Fisheye::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, R, T, 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(R, R_correct, 1e-10); + EXPECT_MAT_NEAR(T, T_correct, 1e-10); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// FisheyeTest:: From ef01044b2f87af12b3ca1a708638dfa79e9e08ec Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Tue, 6 May 2014 11:09:22 +0400 Subject: [PATCH 08/15] Added documentation for Fisheye::stereoCalibrate --- ...mera_calibration_and_3d_reconstruction.rst | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 710672cf5..e020b21e3 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1791,6 +1791,48 @@ Performs camera calibaration :param criteria: Termination criteria for the iterative optimization algorithm. +Fisheye::stereoCalibrate +---------------------------- +Performs stereo calibration calibaration + +.. ocv:function:: double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) + + :param objectPoints: Vector of vectors of the calibration pattern points. + + :param imagePoints1: Vector of vectors of the projections of the calibration pattern points, observed by the first camera. + + :param imagePoints2: Vector of vectors of the projections of the calibration pattern points, observed by the second camera. + + :param K1: Input/output first camera matrix: :math:`\vecthreethree{f_x^{(j)}}{0}{c_x^{(j)}}{0}{f_y^{(j)}}{c_y^{(j)}}{0}{0}{1}` , :math:`j = 0,\, 1` . If any of ``Fisheye::CALIB_USE_INTRINSIC_GUESS`` , ``Fisheye::CV_CALIB_FIX_INTRINSIC`` are specified, some or all of the matrix components must be initialized. + + :param D1: Input/output vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)` of 4 elements. + + :param K2: Input/output second camera matrix. The parameter is similar to ``K1`` . + + :param D2: Input/output lens distortion coefficients for the second camera. The parameter is similar to ``D1`` . + + :param imageSize: Size of the image used only to initialize intrinsic camera matrix. + + :param R: Output rotation matrix between the 1st and the 2nd camera coordinate systems. + + :param T: Output translation vector between the coordinate systems of the cameras. + + :param flags: Different flags that may be zero or a combination of the following values: + + * **Fisheye::CV_CALIB_FIX_INTRINSIC** Fix ``K1, K2?`` and ``D1, D2?`` so that only ``R, T`` matrices are estimated. + + * **Fisheye::CALIB_USE_INTRINSIC_GUESS** ``K1, K2`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center (``imageSize`` is used), and focal distances are computed in a least-squares fashion. + + * **Fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. + + * **Fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. + + * **Fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. + + * **Fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. + + :param criteria: Termination criteria for the iterative optimization algorithm. + .. [BT98] Birchfield, S. and Tomasi, C. A pixel dissimilarity measure that is insensitive to image sampling. IEEE Transactions on Pattern Analysis and Machine Intelligence. 1998. .. [BouguetMCT] J.Y.Bouguet. MATLAB calibration tool. http://www.vision.caltech.edu/bouguetj/calib_doc/ From 1f94b7dfc95684d93908ec3c8c0332685c8c2269 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Tue, 6 May 2014 11:17:10 +0400 Subject: [PATCH 09/15] minor --- modules/calib3d/include/opencv2/calib3d/calib3d.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index b6d85a095..7b7f92efc 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -807,8 +807,8 @@ public: //! performs stereo calibaration static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, - OutputArray R, OutputArray T, int flags, - TermCriteria criteria = TermCriteria(3, 100, 1e-10)); + OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, + TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); }; From 349ff631a5d30e979608581adaf75abf7450643b Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Wed, 7 May 2014 20:53:07 +0400 Subject: [PATCH 10/15] Added sample of work of Fisheye::undistortImage and its description to documentation. Removed readPoints and readExtrinsic (useless) --- ...mera_calibration_and_3d_reconstruction.rst | 32 +++ .../calib3d/doc/pics/fisheye_undistorted.jpg | Bin 0 -> 86109 bytes modules/calib3d/test/test_fisheye.cpp | 226 +++++------------- 3 files changed, 88 insertions(+), 170 deletions(-) create mode 100644 modules/calib3d/doc/pics/fisheye_undistorted.jpg diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index e020b21e3..20a731f95 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1686,6 +1686,38 @@ Computes undistortion and rectification maps for image transform by cv::remap(). :param map2: The second output map. +Fisheye::undistortImage +------------- +Transforms an image to compensate for fisheye lens distortion. + +.. ocv:function:: void Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, + InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()) + + :param distorted: image with fisheye lens distortion. + + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. + + :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. + + :param Knew: Camera matrix of the distorted image. By default, it is the same as ``cameraMatrix`` but you may additionally scale and shift the result by using a different matrix. + + :param undistorted: Output image with compensated fisheye lens distortion. + +The function transforms an image to compensate radial and tangential lens distortion. + +The function is simply a combination of +:ocv:func:`Fisheye::initUndistortRectifyMap` (with unity ``R`` ) and +:ocv:func:`remap` (with bilinear interpolation). See the former function for details of the transformation being performed. + +See below the results of undistortImage. + * a\) result of :ocv:func:`undistort` of perspective camera model (all possible coefficients (k_1, k_2, k_3, k_4, k_5, k_6) of distortion were optimized under calibration) + * b\) result of :ocv:func:`Fisheye::undistrortImage` of fisheye camera model (all possible coefficients (k_1, k_2, k_3, k_4) of fisheye distortion were optimized under calibration) + * c\) original image was captured with fisheye lens + +Pictures a) and b) almost the same. But if we consider points of image located far from the center of image, we can notice that on image a) these points are distorted. + +.. image:: pics/fisheye_undistorted.jpg + Fisheye::estimateNewCameraMatrixForUndistortRectify ---------------------------------------------------------- diff --git a/modules/calib3d/doc/pics/fisheye_undistorted.jpg b/modules/calib3d/doc/pics/fisheye_undistorted.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bfdc9afd5cbd297c10bc43a969a84d0bd3a5091d GIT binary patch literal 86109 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 >& objectPoints, - std::vector >& imagePoints, - const std::string& path, const int n_images); - - void readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, - cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q); - cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r); }; @@ -71,16 +63,16 @@ TEST_F(FisheyeTest, undistortImage) { cv::Matx33d K = this->K; cv::Mat D = cv::Mat(this->D); - std::string file = combine(datasets_repository_path, "image000001.png"); + std::string file = combine(datasets_repository_path, "/calib-3_stereo_from_JY/left/stereo_pair_014.jpg"); cv::Matx33d newK = K; cv::Mat distorted = cv::imread(file), undistorted; { newK(0, 0) = 100; newK(1, 1) = 100; cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/new_f_100.png")); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "new_f_100.png")); if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/new_f_100.png"), undistorted)); + CV_Assert(cv::imwrite(combine(datasets_repository_path, "new_f_100.png"), undistorted)); else EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } @@ -88,9 +80,9 @@ TEST_F(FisheyeTest, undistortImage) double balance = 1.0; cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/balance_1.0.png")); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_1.0.png")); if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_1.0.png"), undistorted)); + CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_1.0.png"), undistorted)); else EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } @@ -99,9 +91,9 @@ TEST_F(FisheyeTest, undistortImage) double balance = 0.0; cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "test_undistortImage/balance_0.0.png")); + cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_0.0.png")); if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "test_undistortImage/balance_0.0.png"), undistorted)); + CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_0.0.png"), undistorted)); else EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } @@ -206,10 +198,21 @@ TEST_F(FisheyeTest, Calibration) { const int n_images = 34; - std::vector > imagePoints; - std::vector > objectPoints; + std::vector > imagePoints(n_images); + std::vector > objectPoints(n_images); - readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), 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::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; @@ -230,10 +233,22 @@ TEST_F(FisheyeTest, Homography) { const int n_images = 1; - std::vector > imagePoints; - std::vector > objectPoints; + std::vector > imagePoints(n_images); + std::vector > 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(); - readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images); 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)); @@ -279,10 +294,21 @@ TEST_F(FisheyeTest, EtimateUncertainties) { const int n_images = 34; - std::vector > imagePoints; - std::vector > objectPoints; + std::vector > imagePoints(n_images); + std::vector > objectPoints(n_images); - readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), 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::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; @@ -360,12 +386,13 @@ TEST_F(FisheyeTest, rectify) cv::Mat rectification = mergeRectification(lundist, rundist); - cv::Mat correct = cv::imread(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i)); + cv::Mat correct = cv::imread(combine_format(datasets_repository_path, "rectification_AB_%03d.png", i)); + if (correct.empty()) - cv::imwrite(combine_format(folder, "test_rectify/rectification_AB_%03d.png", i), rectification); - else - EXPECT_MAT_NEAR(correct, rectification, 1e-10); - } + cv::imwrite(combine_format(datasets_repository_path, "rectification_AB_%03d.png", i), rectification); + else + EXPECT_MAT_NEAR(correct, rectification, 1e-10); + } } TEST_F(FisheyeTest, stereoCalibrate) @@ -396,58 +423,6 @@ TEST_F(FisheyeTest, stereoCalibrate) fs_object[cv::format("image_%d", i )] >> objectPoints[i]; fs_object.release(); - std::ofstream fs; - - for (size_t i = 0; i < leftPoints.size(); i++) - { - std::string ss = combine(folder, "left"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < leftPoints[i].size(); j++) - { - double x = leftPoints[i][j].x; - double y = leftPoints[i][j].y; - fs << std::setprecision(15) << x << "; " << y; - fs << std::endl; - } - fs.close(); - } - - for (size_t i = 0; i < rightPoints.size(); i++) - { - std::string ss = combine(folder, "right"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < rightPoints[i].size(); j++) - { - double x = rightPoints[i][j].x; - double y = rightPoints[i][j].y; - fs << std::setprecision(15) << x << "; " << y; - fs << std::endl; - } - fs.close(); - } - - for (size_t i = 0; i < objectPoints.size(); i++) - { - std::string ss = combine(folder, "object"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < objectPoints[i].size(); j++) - { - double x = objectPoints[i][j].x; - double y = objectPoints[i][j].y; - double z = objectPoints[i][j].z; - fs << std::setprecision(15) << x << "; " << y; - fs << std::setprecision(15) << "; " << z; - fs << std::endl; - } - fs.close(); - } - cv::Matx33d K1, K2, R; cv::Vec3d T; cv::Vec4d D1, D2; @@ -516,59 +491,6 @@ TEST_F(FisheyeTest, stereoCalibrateFixIntrinsic) fs_object[cv::format("image_%d", i )] >> objectPoints[i]; fs_object.release(); - - std::ofstream fs; - - for (size_t i = 0; i < leftPoints.size(); i++) - { - std::string ss = combine(folder, "left"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < leftPoints[i].size(); j++) - { - double x = leftPoints[i][j].x; - double y = leftPoints[i][j].y; - fs << std::setprecision(15) << x << "; " << y; - fs << std::endl; - } - fs.close(); - } - - for (size_t i = 0; i < rightPoints.size(); i++) - { - std::string ss = combine(folder, "right"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < rightPoints[i].size(); j++) - { - double x = rightPoints[i][j].x; - double y = rightPoints[i][j].y; - fs << std::setprecision(15) << x << "; " << y; - fs << std::endl; - } - fs.close(); - } - - for (size_t i = 0; i < objectPoints.size(); i++) - { - std::string ss = combine(folder, "object"); - ss = combine_format(ss, "%d", i); - fs.open(ss.c_str()); - CV_Assert(fs.is_open()); - for (size_t j = 0; j < objectPoints[i].size(); j++) - { - double x = objectPoints[i][j].x; - double y = objectPoints[i][j].y; - double z = objectPoints[i][j].z; - fs << std::setprecision(15) << x << "; " << y; - fs << std::setprecision(15) << "; " << z; - fs << std::endl; - } - fs.close(); - } - cv::Matx33d R; cv::Vec3d T; @@ -647,42 +569,6 @@ std::string FisheyeTest::combine_format(const std::string& item1, const std::str return std::string(buffer); } -void FisheyeTest::readPoints(std::vector >& objectPoints, - std::vector >& imagePoints, - const std::string& path, const int n_images) -{ - objectPoints.resize(n_images); - imagePoints.resize(n_images); - - cv::FileStorage fs1(combine(path, "objectPoints.xml"), cv::FileStorage::READ); - CV_Assert(fs1.isOpened()); - for (size_t i = 0; i < objectPoints.size(); ++i) - { - fs1[cv::format("image_%d", i)] >> objectPoints[i]; - } - fs1.release(); - - cv::FileStorage fs2(combine(path, "imagePoints.xml"), cv::FileStorage::READ); - CV_Assert(fs2.isOpened()); - for (size_t i = 0; i < imagePoints.size(); ++i) - { - fs2[cv::format("image_%d", i)] >> imagePoints[i]; - } - fs2.release(); -} - -void FisheyeTest::readExtrinsics(const std::string& file, cv::OutputArray _R, cv::OutputArray _T, cv::OutputArray _R1, cv::OutputArray _R2, - cv::OutputArray _P1, cv::OutputArray _P2, cv::OutputArray _Q) -{ - cv::FileStorage fs(file, cv::FileStorage::READ); - CV_Assert(fs.isOpened()); - - cv::Mat R, T, R1, R2, P1, P2, Q; - fs["R"] >> R; fs["T"] >> T; fs["R1"] >> R1; fs["R2"] >> R2; fs["P1"] >> P1; fs["P2"] >> P2; fs["Q"] >> Q; - if (_R.needed()) R.copyTo(_R); if(_T.needed()) T.copyTo(_T); if (_R1.needed()) R1.copyTo(_R1); if (_R2.needed()) R2.copyTo(_R2); - if(_P1.needed()) P1.copyTo(_P1); if(_P2.needed()) P2.copyTo(_P2); if(_Q.needed()) Q.copyTo(_Q); -} - cv::Mat FisheyeTest::mergeRectification(const cv::Mat& l, const cv::Mat& r) { CV_Assert(l.type() == r.type() && l.size() == r.size()); From e4a9c0f184024330ab33eb7997874921d9fe0195 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 12 May 2014 15:37:47 +0400 Subject: [PATCH 11/15] Fixed review comments --- ...mera_calibration_and_3d_reconstruction.rst | 23 ++++++++----------- .../include/opencv2/calib3d/calib3d.hpp | 5 ---- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 20a731f95..2f1bc6981 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1518,7 +1518,7 @@ The methods in this class use a so-called fisheye camera model. :: static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); - //! undistorts image, optionally changes resolution and camera matrix. If Knew zero identity matrix is used + //! undistorts image, optionally changes resolution and camera matrix. static void undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); @@ -1526,11 +1526,6 @@ The methods in this class use a so-called fisheye camera model. :: static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); - //! stereo rectification for fisheye camera model - static void stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, - InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, - int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ); - //! performs camera calibaration static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, @@ -1699,7 +1694,7 @@ Transforms an image to compensate for fisheye lens distortion. :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. - :param Knew: Camera matrix of the distorted image. By default, it is the same as ``cameraMatrix`` but you may additionally scale and shift the result by using a different matrix. + :param Knew: Camera matrix of the distorted image. By default, it is the identity matrix but you may additionally scale and shift the result by using a different matrix. :param undistorted: Output image with compensated fisheye lens distortion. @@ -1734,14 +1729,14 @@ Estimates new camera matrix for undistortion or rectification. :param P: New camera matrix (3x3) or new projection matrix (3x4) + :param balance: Sets the new focal length in range between the min focal length and the max focal length. Balance is in range of [0, 1]. + + :param fov_scale: Divisor for new focal length. + Fisheye::stereoRectify ------------------------------ Stereo rectification for fisheye camera model -.. ocv:function:: void Fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, - InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, - int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ) - .. ocv:function:: void Fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0) @@ -1780,9 +1775,9 @@ Stereo rectification for fisheye camera model :param roi2: Optional output rectangles inside the rectified images where all the pixels are valid. If ``alpha=0`` , the ROIs cover the whole images. Otherwise, they are likely to be smaller (see the picture below). - :param balance: Balance. + :param balance: Sets the new focal length in range between the min focal length and the max focal length. Balance is in range of [0, 1]. - :param fov_scale: Field of View scale. + :param fov_scale: Divisor for new focal length. @@ -1794,7 +1789,7 @@ Performs camera calibaration InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) - :param objectPoints: vector of vectors of calibration pattern points in the calibration pattern coordinate space. The outer vector contains as many elements as the number of the pattern views. If the same calibration pattern is shown in each view and it is fully visible, all the vectors will be the same. Although, it is possible to use partially occluded patterns, or even different patterns in different views. Then, the vectors will be different. The points are 3D, but since they are in a pattern coordinate system, then, if the rig is planar, it may make sense to put the model to a XY coordinate plane so that Z-coordinate of each input object point is 0. + :param objectPoints: vector of vectors of calibration pattern points in the calibration pattern coordinate space. :param imagePoints: vector of vectors of the projections of calibration pattern points. ``imagePoints.size()`` and ``objectPoints.size()`` and ``imagePoints[i].size()`` must be equal to ``objectPoints[i].size()`` for each ``i``. diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 7b7f92efc..67cf56890 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -789,11 +789,6 @@ public: static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); - //! stereo rectification for fisheye camera model - static void stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const cv::Size& imageSize, - InputArray rotaion, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, - int flags = cv::CALIB_ZERO_DISPARITY, double alpha = -1, const Size& newImageSize = Size(), Rect* roi1 = 0, Rect* roi2 = 0 ); - //! performs camera calibaration static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, From c30fef1f9ded38817e8280a27c8a67db4e2bea0d Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Tue, 13 May 2014 13:34:46 +0400 Subject: [PATCH 12/15] Fixed build issues --- ...mera_calibration_and_3d_reconstruction.rst | 80 ++++++++----------- .../include/opencv2/calib3d/calib3d.hpp | 2 +- modules/calib3d/src/fisheye.cpp | 78 ++++++++---------- modules/calib3d/src/fisheye.hpp | 9 ++- modules/calib3d/test/test_fisheye.cpp | 1 - 5 files changed, 73 insertions(+), 97 deletions(-) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 2f1bc6981..5f64125eb 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1536,13 +1536,17 @@ The methods in this class use a so-called fisheye camera model. :: OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0); - ... + //! performs stereo calibration + static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, + InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, + OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, + TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); }; Definitions: Let P be a point in 3D of coordinates X in the world reference frame (stored in the matrix X) -The coordinate vector of P in the camera reference frame is: +The coordinate vector of P in the camera reference frame is: .. class:: center .. math:: @@ -1556,13 +1560,13 @@ call x, y and z the 3 coordinates of Xc: .. math:: x = Xc_1 \\ y = Xc_2 \\ - z = Xc_3 + z = Xc_3 -The pinehole projection coordinates of P is [a; b] where +The pinehole projection coordinates of P is [a; b] where .. class:: center .. math:: - + a = x / z \ and \ b = y / z \\ r^2 = a^2 + b^2 \\ \theta = atan(r) @@ -1570,22 +1574,22 @@ The pinehole projection coordinates of P is [a; b] where Fisheye distortion: .. class:: center -.. math:: - +.. math:: + \theta_d = \theta (1 + k_1 \theta^2 + k_2 \theta^4 + k_3 \theta^6 + k_4 \theta^8) The distorted point coordinates are [x'; y'] where -.. class:: center -.. math:: - +..class:: center +.. math:: + x' = (\theta_d / r) x \\ y' = (\theta_d / r) y Finally, convertion into pixel coordinates: The final pixel coordinates vector [u; v] where: .. class:: center -.. math:: +.. math:: u = f_x (x' + \alpha y') + c_x \\ v = f_y yy + c_y @@ -1596,38 +1600,32 @@ Projects points using fisheye model .. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) -.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, - InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) +.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) :param objectPoints: Array of object points, 1xN/Nx1 3-channel (or ``vector`` ), where N is the number of points in the view. - :param rvec: Rotation vector. See :ocv:func:`Rodrigues` for details. + :param rvec: Rotation vector. See :ocv:func:`Rodrigues` for details. :param tvec: Translation vector. - :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}` . + :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. :param alpha: The skew coefficient. - :param imagePoints: Output array of image points, 2xN/Nx2 1-channel or 1xN/Nx1 2-channel, or ``vector`` . + :param imagePoints: Output array of image points, 2xN/Nx2 1-channel or 1xN/Nx1 2-channel, or ``vector``. :param jacobian: Optional output 2Nx15 jacobian matrix of derivatives of image points with respect to components of the focal lengths, coordinates of the principal point, distortion coefficients, rotation vector, translation vector, and the skew. In the old interface different components of the jacobian are returned via different output parameters. -The function computes projections of 3D -points to the image plane given intrinsic and extrinsic camera -parameters. Optionally, the function computes Jacobians - matrices -of partial derivatives of image points coordinates (as functions of all the -input parameters) with respect to the particular parameters, intrinsic and/or -extrinsic. +The function computes projections of 3D points to the image plane given intrinsic and extrinsic camera parameters. Optionally, the function computes Jacobians - matrices of partial derivatives of image points coordinates (as functions of all the input parameters) with respect to the particular parameters, intrinsic and/or extrinsic. Fisheye::distortPoints ------------------------- Distorts 2D points using fisheye model. .. ocv:function:: void Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0) - + :param undistorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. @@ -1642,8 +1640,7 @@ Fisheye::undistortPoints ----------------------------- Undistorts 2D points using fisheye model -.. ocv:function:: void Fisheye::undistortPoints(InputArray distorted, OutputArray undistorted, - InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()) +.. ocv:function:: void Fisheye::undistortPoints(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()) :param distorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. @@ -1651,25 +1648,24 @@ Undistorts 2D points using fisheye model :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. - :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel :param P: New camera matrix (3x3) or new projection matrix (3x4) - :param undistorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . + :param undistorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . Fisheye::initUndistortRectifyMap ------------------------------------- Computes undistortion and rectification maps for image transform by cv::remap(). If D is empty zero distortion is used, if R or P is empty identity matrixes are used. -.. ocv:function:: void Fisheye::initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, - const cv::Size& size, int m1type, OutputArray map1, OutputArray map2) +.. ocv:function:: void Fisheye::initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2) :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. - :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel :param P: New camera matrix (3x3) or new projection matrix (3x4) @@ -1682,11 +1678,10 @@ Computes undistortion and rectification maps for image transform by cv::remap(). :param map2: The second output map. Fisheye::undistortImage -------------- +----------------------- Transforms an image to compensate for fisheye lens distortion. -.. ocv:function:: void Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, - InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()) +.. ocv:function:: void Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()) :param distorted: image with fisheye lens distortion. @@ -1706,7 +1701,7 @@ The function is simply a combination of See below the results of undistortImage. * a\) result of :ocv:func:`undistort` of perspective camera model (all possible coefficients (k_1, k_2, k_3, k_4, k_5, k_6) of distortion were optimized under calibration) - * b\) result of :ocv:func:`Fisheye::undistrortImage` of fisheye camera model (all possible coefficients (k_1, k_2, k_3, k_4) of fisheye distortion were optimized under calibration) + * b\) result of :ocv:func:`Fisheye::undistortImage` of fisheye camera model (all possible coefficients (k_1, k_2, k_3, k_4) of fisheye distortion were optimized under calibration) * c\) original image was captured with fisheye lens Pictures a) and b) almost the same. But if we consider points of image located far from the center of image, we can notice that on image a) these points are distorted. @@ -1718,14 +1713,13 @@ Fisheye::estimateNewCameraMatrixForUndistortRectify ---------------------------------------------------------- Estimates new camera matrix for undistortion or rectification. -.. ocv:function:: void Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, - OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); +.. ocv:function:: void Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0) :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. :param D: Input vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. - :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel + :param R: Rectification transformation in the object space: 3x3 1-channel, or vector: 3x1/1x3 1-channel or 1x1 3-channel :param P: New camera matrix (3x3) or new projection matrix (3x4) @@ -1737,9 +1731,7 @@ Fisheye::stereoRectify ------------------------------ Stereo rectification for fisheye camera model -.. ocv:function:: void Fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, - OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), - double balance = 0.0, double fov_scale = 1.0) +.. ocv:function:: void Fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0) :param K1: First camera matrix. @@ -1785,9 +1777,7 @@ Fisheye::calibrate ---------------------------- Performs camera calibaration -.. ocv:function:: double Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, - InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, - TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) +.. ocv:function:: double Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) :param objectPoints: vector of vectors of calibration pattern points in the calibration pattern coordinate space. @@ -1820,9 +1810,9 @@ Performs camera calibaration Fisheye::stereoCalibrate ---------------------------- -Performs stereo calibration calibaration +Performs stereo calibration -.. ocv:function:: double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) +.. ocv:function:: double Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) :param objectPoints: Vector of vectors of the calibration pattern points. diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 67cf56890..0b048fe8a 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -745,7 +745,7 @@ CV_EXPORTS_W int estimateAffine3D(InputArray src, InputArray dst, OutputArray out, OutputArray inliers, double ransacThreshold=3, double confidence=0.99); -class Fisheye +class CV_EXPORTS Fisheye { public: diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 50dd04528..da7a8394f 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -1,8 +1,4 @@ -#include "opencv2/opencv.hpp" -#include "opencv2/core/affine.hpp" -#include "opencv2/core/affine.hpp" #include "fisheye.hpp" -#include "iomanip" namespace cv { namespace { @@ -46,6 +42,7 @@ void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints cv::Vec2d f, c; if (_K.depth() == CV_32F) { + Matx33f K = _K.getMat(); f = Vec2f(K(0, 0), K(1, 1)); c = Vec2f(K(0, 2), K(1, 2)); @@ -786,8 +783,8 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO const double thresh_cond = 1e6; const int check_cond = 1; - size_t n_points = objectPoints.getMat(0).total(); - size_t n_images = objectPoints.total(); + int n_points = (int)objectPoints.getMat(0).total(); + int n_images = (int)objectPoints.total(); double change = 1; @@ -856,7 +853,7 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO //Init values for rotation and translation between two views cv::Mat om_list(1, n_images, CV_64FC3), T_list(1, n_images, CV_64FC3); cv::Mat om_ref, R_ref, T_ref, R1, R2; - for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + for (int image_idx = 0; image_idx < n_images; ++image_idx) { cv::Rodrigues(rvecs1[image_idx], R1); cv::Rodrigues(rvecs2[image_idx], R2); @@ -887,7 +884,7 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO cv::Mat omr, Tr, domrdomckk, domrdTckk, domrdom, domrdT, dTrdomckk, dTrdTckk, dTrdom, dTrdT; - for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + for (int image_idx = 0; image_idx < n_images; ++image_idx) { Jkk = cv::Mat::zeros(4 * n_points, 18 + 6 * (n_images + 1), CV_64FC1); @@ -931,22 +928,18 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO //check goodness of sterepair double abs_max = 0; - for (size_t i = 0; i < 4 * n_points; i++) + for (int i = 0; i < 4 * n_points; i++) { if (fabs(ekk.at(i)) > abs_max) { abs_max = fabs(ekk.at(i)); } } - if (abs_max < threshold) - { - Jkk.copyTo(J.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); - ekk.copyTo(e.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); - } - else - { - CV_Assert(!"Bad stereo pair"); - } + + CV_Assert(abs_max < threshold); // bad stereo pair + + Jkk.copyTo(J.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); + ekk.copyTo(e.rowRange(image_idx * 4 * n_points, (image_idx + 1) * 4 * n_points)); } cv::Vec6d oldTom(Tcur[0], Tcur[1], Tcur[2], omcur[0], omcur[1], omcur[2]); @@ -962,7 +955,7 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO intrinsicRight = intrinsicRight + deltas.rowRange(a, a + b); omcur = omcur + cv::Vec3d(deltas.rowRange(a + b, a + b + 3)); Tcur = Tcur + cv::Vec3d(deltas.rowRange(a + b + 3, a + b + 6)); - for (size_t image_idx = 0; image_idx < n_images; ++image_idx) + for (int image_idx = 0; image_idx < n_images; ++image_idx) { rvecs1[image_idx] = cv::Mat(cv::Mat(rvecs1[image_idx]) + deltas.rowRange(a + b + 6 + image_idx * 6, a + b + 9 + image_idx * 6)); tvecs1[image_idx] = cv::Mat(cv::Mat(tvecs1[image_idx]) + deltas.rowRange(a + b + 9 + image_idx * 6, a + b + 12 + image_idx * 6)); @@ -979,7 +972,7 @@ double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayO rms += ptr_e[i][0] * ptr_e[i][0] + ptr_e[i][1] * ptr_e[i][1]; } - rms /= (e.total() / 2); + rms /= ((double)e.total() / 2.0); rms = sqrt(rms); _K1 = Matx33d(intrinsicLeft.f[0], intrinsicLeft.f[0] * intrinsicLeft.alpha, intrinsicLeft.c[0], @@ -1011,7 +1004,7 @@ void subMatrix(const Mat& src, Mat& dst, const vector& cols, const vector& cols, const vector(0) / svd.w.at((int)svd.w.total() - 1) > thresh_cond) - { - CV_Assert(!"cond > thresh_cond"); - } + CV_Assert(svd.w.at(0) / svd.w.at((int)svd.w.total() - 1) < thresh_cond); } omckk.reshape(3,1).copyTo(omc.getMat().col(image_idx)); Tckk.reshape(3,1).copyTo(Tc.getMat().col(image_idx)); @@ -1392,11 +1382,7 @@ void cv::internal::ComputeJacobians(InputArrayOfArrays objectPoints, InputArrayO { Mat JJ_kk = B.t(); SVD svd(JJ_kk, SVD::NO_UV); - double cond = svd.w.at(0) / svd.w.at(svd.w.rows - 1); - if (cond > thresh_cond) - { - CV_Assert(!"cond > thresh_cond"); - } + CV_Assert(svd.w.at(0) / svd.w.at(svd.w.rows - 1) < thresh_cond); } } @@ -1420,7 +1406,7 @@ void cv::internal::EstimateUncertainties(InputArrayOfArrays objectPoints, InputA Mat ex((int)(objectPoints.getMat(0).total() * objectPoints.total()), 1, CV_64FC2); - for (size_t image_idx = 0; image_idx < objectPoints.total(); ++image_idx) + for (int image_idx = 0; image_idx < (int)objectPoints.total(); ++image_idx) { Mat image, object; objectPoints.getMat(image_idx).convertTo(object, CV_64FC3); @@ -1435,11 +1421,11 @@ void cv::internal::EstimateUncertainties(InputArrayOfArrays objectPoints, InputA } meanStdDev(ex, noArray(), std_err); - std_err *= sqrt(ex.total()/(ex.total() - 1.0)); + std_err *= sqrt((double)ex.total()/((double)ex.total() - 1.0)); Mat sigma_x; meanStdDev(ex.reshape(1, 1), noArray(), sigma_x); - sigma_x *= sqrt(2 * ex.total()/(2 * ex.total() - 1.0)); + sigma_x *= sqrt(2.0 * (double)ex.total()/(2.0 * (double)ex.total() - 1.0)); Mat _JJ2_inv, ex3; ComputeJacobians(objectPoints, imagePoints, params, omc, Tc, check_cond, thresh_cond, _JJ2_inv, ex3); @@ -1459,7 +1445,7 @@ void cv::internal::EstimateUncertainties(InputArrayOfArrays objectPoints, InputA rms += ptr_ex[i][0] * ptr_ex[i][0] + ptr_ex[i][1] * ptr_ex[i][1]; } - rms /= ex.total(); + rms /= (double)ex.total(); rms = sqrt(rms); } @@ -1468,9 +1454,9 @@ void cv::internal::dAB(InputArray A, InputArray B, OutputArray dABdA, OutputArra CV_Assert(A.getMat().cols == B.getMat().rows); CV_Assert(A.type() == CV_64FC1 && B.type() == CV_64FC1); - size_t p = A.getMat().rows; - size_t n = A.getMat().cols; - size_t q = B.getMat().cols; + int p = A.getMat().rows; + int n = A.getMat().cols; + int q = B.getMat().cols; dABdA.create(p * q, p * n, CV_64FC1); dABdB.create(p * q, q * n, CV_64FC1); @@ -1478,20 +1464,20 @@ void cv::internal::dAB(InputArray A, InputArray B, OutputArray dABdA, OutputArra dABdA.getMat() = Mat::zeros(p * q, p * n, CV_64FC1); dABdB.getMat() = Mat::zeros(p * q, q * n, CV_64FC1); - for (size_t i = 0; i < q; ++i) + for (int i = 0; i < q; ++i) { - for (size_t j = 0; j < p; ++j) + for (int j = 0; j < p; ++j) { - size_t ij = j + i * p; - for (size_t k = 0; k < n; ++k) + int ij = j + i * p; + for (int k = 0; k < n; ++k) { - size_t kj = j + k * p; + int kj = j + k * p; dABdA.getMat().at(ij, kj) = B.getMat().at(k, i); } } } - for (size_t i = 0; i < q; ++i) + for (int i = 0; i < q; ++i) { A.getMat().copyTo(dABdB.getMat().rowRange(i * p, i * p + p).colRange(i * n, i * n + n)); } @@ -1571,8 +1557,8 @@ double cv::internal::median(const Mat& row) CV_Assert(!row.empty() && row.rows == 1); Mat tmp = row.clone(); sort(tmp, tmp, 0); - if (tmp.total() % 2) return tmp.at(tmp.total() / 2); - else return 0.5 *(tmp.at(tmp.total() / 2) + tmp.at(tmp.total() / 2 - 1)); + if ((int)tmp.total() % 2) return tmp.at((int)tmp.total() / 2); + else return 0.5 *(tmp.at((int)tmp.total() / 2) + tmp.at((int)tmp.total() / 2 - 1)); } cv::Vec3d cv::internal::median3d(InputArray m) diff --git a/modules/calib3d/src/fisheye.hpp b/modules/calib3d/src/fisheye.hpp index fa4bfdb38..82c9f3459 100644 --- a/modules/calib3d/src/fisheye.hpp +++ b/modules/calib3d/src/fisheye.hpp @@ -1,9 +1,10 @@ #ifndef FISHEYE_INTERNAL_H #define FISHEYE_INTERNAL_H +#include "precomp.hpp" namespace cv { namespace internal { -struct IntrinsicParams +struct CV_EXPORTS IntrinsicParams { Vec2d f; Vec2d c; @@ -25,9 +26,9 @@ void projectPoints(cv::InputArray objectPoints, cv::OutputArray imagePoints, void ComputeExtrinsicRefine(const Mat& imagePoints, const Mat& objectPoints, Mat& rvec, Mat& tvec, Mat& J, const int MaxIter, const IntrinsicParams& param, const double thresh_cond); -Mat ComputeHomography(Mat m, Mat M); +CV_EXPORTS Mat ComputeHomography(Mat m, Mat M); -Mat NormalizePixels(const Mat& imagePoints, const IntrinsicParams& param); +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); @@ -39,7 +40,7 @@ void ComputeJacobians(InputArrayOfArrays objectPoints, InputArrayOfArrays imageP const IntrinsicParams& param, InputArray omc, InputArray Tc, const int& check_cond, const double& thresh_cond, Mat& JJ2_inv, Mat& ex3); -void EstimateUncertainties(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, +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); diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index dfdba98fc..7457a9412 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -1,5 +1,4 @@ #include "test_precomp.hpp" -#include #include #include "../src/fisheye.hpp" From 651b13f72a786ee26fe6c12ee6a305fc0f6b7d71 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 19 May 2014 17:55:32 +0400 Subject: [PATCH 13/15] Refactored class Fisheye to namespace fisheye --- .../include/opencv2/calib3d/calib3d.hpp | 26 ++--- modules/calib3d/src/fisheye.cpp | 46 ++++---- modules/calib3d/test/test_fisheye.cpp | 110 +++++++++--------- 3 files changed, 90 insertions(+), 92 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 0b048fe8a..5e9cde8ec 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -745,10 +745,8 @@ CV_EXPORTS_W int estimateAffine3D(InputArray src, InputArray dst, OutputArray out, OutputArray inliers, double ransacThreshold=3, double confidence=0.99); -class CV_EXPORTS Fisheye +namespace fisheye { -public: - enum{ CALIB_USE_INTRINSIC_GUESS = 1, CALIB_RECOMPUTE_EXTRINSIC = 2, @@ -762,50 +760,50 @@ public: }; //! projects 3D points using fisheye model - static void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, + CV_EXPORTS void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); //! projects points using fisheye model - static void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, + CV_EXPORTS void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); //! distorts 2D points using fisheye model - static void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); + CV_EXPORTS void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); //! undistorts 2D points using fisheye model - static void undistortPoints(InputArray distorted, OutputArray undistorted, + CV_EXPORTS void undistortPoints(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()); //! computing undistortion and rectification maps for image transform by cv::remap() //! If D is empty zero distortion is used, if R or P is empty identity matrixes are used - static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, + CV_EXPORTS void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); //! undistorts image, optionally changes resolution and camera matrix. If Knew zero identity matrix is used - static void undistortImage(InputArray distorted, OutputArray undistorted, + CV_EXPORTS void undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); //! estimates new camera matrix for undistortion or rectification - static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + CV_EXPORTS void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); //! performs camera calibaration - static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + CV_EXPORTS double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); //! stereo rectification estimation - static void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, + CV_EXPORTS void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0); //! performs stereo calibaration - static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, + CV_EXPORTS double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); -}; +} } diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index da7a8394f..34d11d1fa 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -14,15 +14,15 @@ namespace cv { namespace }} ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::projectPoints +/// cv::fisheye::projectPoints -void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, +void cv::fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha, OutputArray jacobian) { projectPoints(objectPoints, imagePoints, affine.rvec(), affine.translation(), K, D, alpha, jacobian); } -void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray _rvec, +void cv::fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray _rvec, InputArray _tvec, InputArray _K, InputArray _D, double alpha, OutputArray jacobian) { // will support only 3-channel data now for points @@ -202,9 +202,9 @@ void cv::Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::distortPoints +/// cv::fisheye::distortPoints -void cv::Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha) +void cv::fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha) { // will support only 2-channel data now for points CV_Assert(undistorted.type() == CV_32FC2 || undistorted.type() == CV_64FC2); @@ -264,9 +264,9 @@ void cv::Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, I } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::undistortPoints +/// cv::fisheye::undistortPoints -void cv::Fisheye::undistortPoints( InputArray distorted, OutputArray undistorted, InputArray _K, InputArray _D, InputArray _R, InputArray _P) +void cv::fisheye::undistortPoints( InputArray distorted, OutputArray undistorted, InputArray _K, InputArray _D, InputArray _R, InputArray _P) { // will support only 2-channel data now for points CV_Assert(distorted.type() == CV_32FC2 || distorted.type() == CV_64FC2); @@ -353,9 +353,9 @@ void cv::Fisheye::undistortPoints( InputArray distorted, OutputArray undistorted } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::undistortPoints +/// cv::fisheye::undistortPoints -void cv::Fisheye::initUndistortRectifyMap( InputArray _K, InputArray _D, InputArray _R, InputArray _P, +void cv::fisheye::initUndistortRectifyMap( InputArray _K, InputArray _D, InputArray _R, InputArray _P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2 ) { CV_Assert( m1type == CV_16SC2 || m1type == CV_32F || m1type <=0 ); @@ -449,23 +449,23 @@ void cv::Fisheye::initUndistortRectifyMap( InputArray _K, InputArray _D, InputAr } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::undistortImage +/// cv::fisheye::undistortImage -void cv::Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, +void cv::fisheye::undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew, const Size& new_size) { Size size = new_size.area() != 0 ? new_size : distorted.size(); cv::Mat map1, map2; - initUndistortRectifyMap(K, D, cv::Matx33d::eye(), Knew, size, CV_16SC2, map1, map2 ); + fisheye::initUndistortRectifyMap(K, D, cv::Matx33d::eye(), Knew, size, CV_16SC2, map1, map2 ); cv::remap(distorted, undistorted, map1, map2, INTER_LINEAR, BORDER_CONSTANT); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::estimateNewCameraMatrixForUndistortRectify +/// cv::fisheye::estimateNewCameraMatrixForUndistortRectify -void cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, +void cv::fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance, const Size& new_size, double fov_scale) { CV_Assert( K.size() == Size(3, 3) && (K.depth() == CV_32F || K.depth() == CV_64F)); @@ -495,7 +495,7 @@ void cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, Input } #endif - undistortPoints(points, points, K, D, R); + fisheye::undistortPoints(points, points, K, D, R); cv::Scalar center_mass = mean(points); cv::Vec2d cn(center_mass.val); @@ -560,9 +560,9 @@ void cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, Input ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::stereoRectify +/// cv::fisheye::stereoRectify -void cv::Fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size& imageSize, +void cv::fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size& imageSize, InputArray _R, InputArray _tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size& newImageSize, double balance, double fov_scale) { @@ -642,9 +642,9 @@ void cv::Fisheye::stereoRectify( InputArray K1, InputArray D1, InputArray K2, In } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::calibrate +/// cv::fisheye::calibrate -double cv::Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, +double cv::fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags , cv::TermCriteria criteria) { @@ -758,9 +758,9 @@ double cv::Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArray } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// cv::Fisheye::stereoCalibrate +/// cv::fisheye::stereoCalibrate -double cv::Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, +double cv::fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags, TermCriteria criteria) { @@ -1094,7 +1094,7 @@ void cv::internal::projectPoints(cv::InputArray objectPoints, cv::OutputArray im Matx33d K(param.f[0], param.f[0] * param.alpha, param.c[0], 0, param.f[1], param.c[1], 0, 0, 1); - Fisheye::projectPoints(objectPoints, imagePoints, _rvec, _tvec, K, param.k, param.alpha, jacobian); + fisheye::projectPoints(objectPoints, imagePoints, _rvec, _tvec, K, param.k, param.alpha, jacobian); } void cv::internal::ComputeExtrinsicRefine(const Mat& imagePoints, const Mat& objectPoints, Mat& rvec, @@ -1251,7 +1251,7 @@ cv::Mat cv::internal::NormalizePixels(const Mat& imagePoints, const IntrinsicPar ptr_d[i] = (ptr[i] - param.c).mul(Vec2d(1.0 / param.f[0], 1.0 / param.f[1])); ptr_d[i][0] = ptr_d[i][0] - param.alpha * ptr_d[i][1]; } - cv::Fisheye::undistortPoints(distorted, undistorted, Matx33d::eye(), param.k); + cv::fisheye::undistortPoints(distorted, undistorted, Matx33d::eye(), param.k); return undistorted; } diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 7457a9412..ed53ec415 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -2,7 +2,7 @@ #include #include "../src/fisheye.hpp" -class FisheyeTest : public ::testing::Test { +class fisheyeTest : public ::testing::Test { protected: const static cv::Size imageSize; @@ -26,7 +26,7 @@ protected: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// TESTS:: -TEST_F(FisheyeTest, projectPoints) +TEST_F(fisheyeTest, projectPoints) { double cols = this->imageSize.width, rows = this->imageSize.height; @@ -44,21 +44,21 @@ TEST_F(FisheyeTest, projectPoints) pts[k++] = (point - c) * 0.85 + c; } - cv::Fisheye::undistortPoints(distorted0, undist1, this->K, this->D); + cv::fisheye::undistortPoints(distorted0, undist1, this->K, this->D); cv::Vec2d* u1 = undist1.ptr(); cv::Vec3d* u2 = undist2.ptr(); for(int i = 0; i < (int)distorted0.total(); ++i) u2[i] = cv::Vec3d(u1[i][0], u1[i][1], 1.0); - cv::Fisheye::distortPoints(undist1, distorted1, this->K, this->D); - cv::Fisheye::projectPoints(undist2, distorted2, cv::Vec3d::all(0), cv::Vec3d::all(0), this->K, this->D); + cv::fisheye::distortPoints(undist1, distorted1, this->K, this->D); + cv::fisheye::projectPoints(undist2, distorted2, cv::Vec3d::all(0), cv::Vec3d::all(0), this->K, this->D); EXPECT_MAT_NEAR(distorted0, distorted1, 1e-10); EXPECT_MAT_NEAR(distorted0, distorted2, 1e-10); } -TEST_F(FisheyeTest, undistortImage) +TEST_F(fisheyeTest, undistortImage) { cv::Matx33d K = this->K; cv::Mat D = cv::Mat(this->D); @@ -68,7 +68,7 @@ TEST_F(FisheyeTest, undistortImage) { newK(0, 0) = 100; newK(1, 1) = 100; - cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::fisheye::undistortImage(distorted, undistorted, K, D, newK); cv::Mat correct = cv::imread(combine(datasets_repository_path, "new_f_100.png")); if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "new_f_100.png"), undistorted)); @@ -77,8 +77,8 @@ TEST_F(FisheyeTest, undistortImage) } { double balance = 1.0; - cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); - cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); + cv::fisheye::undistortImage(distorted, undistorted, K, D, newK); cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_1.0.png")); if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_1.0.png"), undistorted)); @@ -88,8 +88,8 @@ TEST_F(FisheyeTest, undistortImage) { double balance = 0.0; - cv::Fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); - cv::Fisheye::undistortImage(distorted, undistorted, K, D, newK); + cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, distorted.size(), cv::noArray(), newK, balance); + cv::fisheye::undistortImage(distorted, undistorted, K, D, newK); cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_0.0.png")); if (correct.empty()) CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_0.0.png"), undistorted)); @@ -98,7 +98,7 @@ TEST_F(FisheyeTest, undistortImage) } } -TEST_F(FisheyeTest, jacobians) +TEST_F(fisheyeTest, jacobians) { int n = 10; cv::Mat X(1, n, CV_64FC3); @@ -135,14 +135,14 @@ TEST_F(FisheyeTest, jacobians) 0, 0, 1); cv::Mat jacobians; - cv::Fisheye::projectPoints(X, x1, om, T, K, k, alpha, jacobians); + cv::fisheye::projectPoints(X, x1, om, T, K, k, alpha, jacobians); //test on T: cv::Mat dT(3, 1, CV_64FC1); r.fill(dT, cv::RNG::NORMAL, 0, 1); dT *= 1e-9*cv::norm(T); cv::Mat T2 = T + dT; - cv::Fisheye::projectPoints(X, x2, om, T2, K, k, alpha, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om, T2, K, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(11,14) * dT).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); @@ -151,7 +151,7 @@ TEST_F(FisheyeTest, jacobians) r.fill(dom, cv::RNG::NORMAL, 0, 1); dom *= 1e-9*cv::norm(om); cv::Mat om2 = om + dom; - cv::Fisheye::projectPoints(X, x2, om2, T, K, k, alpha, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om2, T, K, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(8,11) * dom).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); @@ -160,7 +160,7 @@ TEST_F(FisheyeTest, jacobians) r.fill(df, cv::RNG::NORMAL, 0, 1); df *= 1e-9*cv::norm(f); cv::Matx33d K2 = K + cv::Matx33d(df.at(0), df.at(0) * alpha, 0, 0, df.at(1), 0, 0, 0, 0); - cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(0,2) * df).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); @@ -169,7 +169,7 @@ TEST_F(FisheyeTest, jacobians) r.fill(dc, cv::RNG::NORMAL, 0, 1); dc *= 1e-9*cv::norm(c); K2 = K + cv::Matx33d(0, 0, dc.at(0), 0, 0, dc.at(1), 0, 0, 0); - cv::Fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om, T, K2, k, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(2,4) * dc).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); @@ -178,7 +178,7 @@ TEST_F(FisheyeTest, jacobians) r.fill(dk, cv::RNG::NORMAL, 0, 1); dk *= 1e-9*cv::norm(k); cv::Mat k2 = k + dk; - cv::Fisheye::projectPoints(X, x2, om, T, K, k2, alpha, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om, T, K, k2, alpha, cv::noArray()); xpred = x1 + cv::Mat(jacobians.colRange(4,8) * dk).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); @@ -188,12 +188,12 @@ TEST_F(FisheyeTest, jacobians) dalpha *= 1e-9*cv::norm(f); double alpha2 = alpha + dalpha.at(0); K2 = K + cv::Matx33d(0, f.at(0) * dalpha.at(0), 0, 0, 0, 0, 0, 0, 0); - cv::Fisheye::projectPoints(X, x2, om, T, K, k, alpha2, cv::noArray()); + cv::fisheye::projectPoints(X, x2, om, T, K, k, alpha2, cv::noArray()); xpred = x1 + cv::Mat(jacobians.col(14) * dalpha).reshape(2, 1); CV_Assert (cv::norm(x2 - xpred) < 1e-10); } -TEST_F(FisheyeTest, Calibration) +TEST_F(fisheyeTest, Calibration) { const int n_images = 34; @@ -214,21 +214,21 @@ TEST_F(FisheyeTest, Calibration) fs_object.release(); int flag = 0; - flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::Fisheye::CALIB_CHECK_COND; - flag |= cv::Fisheye::CALIB_FIX_SKEW; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_CHECK_COND; + flag |= cv::fisheye::CALIB_FIX_SKEW; cv::Matx33d K; cv::Vec4d D; - cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, + cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6)); EXPECT_MAT_NEAR(K, this->K, 1e-10); EXPECT_MAT_NEAR(D, this->D, 1e-10); } -TEST_F(FisheyeTest, Homography) +TEST_F(fisheyeTest, Homography) { const int n_images = 1; @@ -289,7 +289,7 @@ TEST_F(FisheyeTest, Homography) EXPECT_MAT_NEAR(std_err, correct_std_err, 1e-12); } -TEST_F(FisheyeTest, EtimateUncertainties) +TEST_F(fisheyeTest, EtimateUncertainties) { const int n_images = 34; @@ -310,16 +310,16 @@ TEST_F(FisheyeTest, EtimateUncertainties) fs_object.release(); int flag = 0; - flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::Fisheye::CALIB_CHECK_COND; - flag |= cv::Fisheye::CALIB_FIX_SKEW; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_CHECK_COND; + flag |= cv::fisheye::CALIB_FIX_SKEW; cv::Matx33d K; cv::Vec4d D; std::vector rvec; std::vector tvec; - cv::Fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, + cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, K, D, rvec, tvec, flag, cv::TermCriteria(3, 20, 1e-6)); cv::internal::IntrinsicParams param, errors; @@ -345,7 +345,7 @@ TEST_F(FisheyeTest, EtimateUncertainties) CV_Assert(errors.alpha == 0); } -TEST_F(FisheyeTest, rectify) +TEST_F(fisheyeTest, rectify) { const std::string folder =combine(datasets_repository_path, "calib-3_stereo_from_JY"); @@ -358,13 +358,13 @@ TEST_F(FisheyeTest, rectify) double balance = 0.0, fov_scale = 1.1; cv::Mat R1, R2, P1, P2, Q; - cv::Fisheye::stereoRectify(K1, D1, K2, D2, calibration_size, R, T, R1, R2, P1, P2, Q, + cv::fisheye::stereoRectify(K1, D1, K2, D2, calibration_size, R, T, R1, R2, P1, P2, Q, cv::CALIB_ZERO_DISPARITY, requested_size, balance, fov_scale); cv::Mat lmapx, lmapy, rmapx, rmapy; //rewrite for fisheye - cv::Fisheye::initUndistortRectifyMap(K1, D1, R1, P1, requested_size, CV_32F, lmapx, lmapy); - cv::Fisheye::initUndistortRectifyMap(K2, D2, R2, P2, requested_size, CV_32F, rmapx, rmapy); + cv::fisheye::initUndistortRectifyMap(K1, D1, R1, P1, requested_size, CV_32F, lmapx, lmapy); + cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, requested_size, CV_32F, rmapx, rmapy); cv::Mat l, r, lundist, rundist; cv::VideoCapture lcap(combine(folder, "left/stereo_pair_%03d.jpg")), @@ -394,7 +394,7 @@ TEST_F(FisheyeTest, rectify) } } -TEST_F(FisheyeTest, stereoCalibrate) +TEST_F(fisheyeTest, stereoCalibrate) { const int n_images = 34; @@ -427,12 +427,12 @@ TEST_F(FisheyeTest, stereoCalibrate) cv::Vec4d D1, D2; int flag = 0; - flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::Fisheye::CALIB_CHECK_COND; - flag |= cv::Fisheye::CALIB_FIX_SKEW; - // flag |= cv::Fisheye::CALIB_FIX_INTRINSIC; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_CHECK_COND; + flag |= cv::fisheye::CALIB_FIX_SKEW; + // flag |= cv::fisheye::CALIB_FIX_INTRINSIC; - cv::Fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints, + cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints, K1, D1, K2, D2, imageSize, R, T, flag, cv::TermCriteria(3, 12, 0)); @@ -462,7 +462,7 @@ TEST_F(FisheyeTest, stereoCalibrate) } -TEST_F(FisheyeTest, stereoCalibrateFixIntrinsic) +TEST_F(fisheyeTest, stereoCalibrateFixIntrinsic) { const int n_images = 34; @@ -494,10 +494,10 @@ TEST_F(FisheyeTest, stereoCalibrateFixIntrinsic) cv::Vec3d T; int flag = 0; - flag |= cv::Fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::Fisheye::CALIB_CHECK_COND; - flag |= cv::Fisheye::CALIB_FIX_SKEW; - flag |= cv::Fisheye::CALIB_FIX_INTRINSIC; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_CHECK_COND; + flag |= cv::fisheye::CALIB_FIX_SKEW; + flag |= cv::fisheye::CALIB_FIX_INTRINSIC; cv::Matx33d K1 (561.195925927249, 0, 621.282400272412, 0, 562.849402029712, 380.555455380889, @@ -510,7 +510,7 @@ TEST_F(FisheyeTest, stereoCalibrateFixIntrinsic) 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, + cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints, K1, D1, K2, D2, imageSize, R, T, flag, cv::TermCriteria(3, 12, 0)); @@ -525,23 +525,23 @@ TEST_F(FisheyeTest, stereoCalibrateFixIntrinsic) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// FisheyeTest:: +/// fisheyeTest:: -const cv::Size FisheyeTest::imageSize(1280, 800); +const cv::Size fisheyeTest::imageSize(1280, 800); -const cv::Matx33d FisheyeTest::K(558.478087865323, 0, 620.458515360843, +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::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, +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); +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 fisheyeTest::combine(const std::string& _item1, const std::string& _item2) { std::string item1 = _item1, item2 = _item2; std::replace(item1.begin(), item1.end(), '\\', '/'); @@ -557,7 +557,7 @@ std::string FisheyeTest::combine(const std::string& _item1, const std::string& _ return item1 + (last != '/' ? "/" : "") + item2; } -std::string FisheyeTest::combine_format(const std::string& item1, const std::string& item2, ...) +std::string fisheyeTest::combine_format(const std::string& item1, const std::string& item2, ...) { std::string fmt = combine(item1, item2); char buffer[1 << 16]; @@ -568,7 +568,7 @@ std::string FisheyeTest::combine_format(const std::string& item1, const std::str return std::string(buffer); } -cv::Mat FisheyeTest::mergeRectification(const cv::Mat& l, const cv::Mat& r) +cv::Mat fisheyeTest::mergeRectification(const cv::Mat& l, const cv::Mat& r) { CV_Assert(l.type() == r.type() && l.size() == r.size()); cv::Mat merged(l.rows, l.cols * 2, l.type()); From 0d2fab86b4240202ead7cc6e746b8b8203755d50 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 19 May 2014 18:16:00 +0400 Subject: [PATCH 14/15] Changed documentation for namespace fisheye --- ...mera_calibration_and_3d_reconstruction.rst | 98 +++++++++---------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 5f64125eb..ae6aa27f8 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -1487,57 +1487,53 @@ The function reconstructs 3-dimensional points (in homogeneous coordinates) by u :ocv:func:`reprojectImageTo3D` -Fisheye +fisheye ---------- -.. ocv:class:: Fisheye +The methods in this namespace use a so-called fisheye camera model. :: -The methods in this class use a so-called fisheye camera model. :: - - class Fisheye + namespace fisheye { - public: - //! projects 3D points using fisheye model - static void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, + void projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); //! projects points using fisheye model - static void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, + void projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()); //! distorts 2D points using fisheye model - static void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); + void distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0); //! undistorts 2D points using fisheye model - static void undistortPoints(InputArray distorted, OutputArray undistorted, + void undistortPoints(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()); //! computing undistortion and rectification maps for image transform by cv::remap() //! If D is empty zero distortion is used, if R or P is empty identity matrixes are used - static void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, + void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2); //! undistorts image, optionally changes resolution and camera matrix. - static void undistortImage(InputArray distorted, OutputArray undistorted, + void undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()); //! estimates new camera matrix for undistortion or rectification - static void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, + void estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0); //! performs camera calibaration - static double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, + double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); //! stereo rectification estimation - static void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, + void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0); //! performs stereo calibration - static double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, + double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)); @@ -1594,13 +1590,13 @@ Finally, convertion into pixel coordinates: The final pixel coordinates vector [ u = f_x (x' + \alpha y') + c_x \\ v = f_y yy + c_y -Fisheye::projectPoints +fisheye::projectPoints --------------------------- Projects points using fisheye model -.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) +.. ocv:function:: void fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, const Affine3d& affine, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) -.. ocv:function:: void Fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) +.. ocv:function:: void fisheye::projectPoints(InputArray objectPoints, OutputArray imagePoints, InputArray rvec, InputArray tvec, InputArray K, InputArray D, double alpha = 0, OutputArray jacobian = noArray()) :param objectPoints: Array of object points, 1xN/Nx1 3-channel (or ``vector`` ), where N is the number of points in the view. @@ -1620,11 +1616,11 @@ Projects points using fisheye model The function computes projections of 3D points to the image plane given intrinsic and extrinsic camera parameters. Optionally, the function computes Jacobians - matrices of partial derivatives of image points coordinates (as functions of all the input parameters) with respect to the particular parameters, intrinsic and/or extrinsic. -Fisheye::distortPoints +fisheye::distortPoints ------------------------- Distorts 2D points using fisheye model. -.. ocv:function:: void Fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0) +.. ocv:function:: void fisheye::distortPoints(InputArray undistorted, OutputArray distorted, InputArray K, InputArray D, double alpha = 0) :param undistorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. @@ -1636,11 +1632,11 @@ Distorts 2D points using fisheye model. :param distorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . -Fisheye::undistortPoints +fisheye::undistortPoints ----------------------------- Undistorts 2D points using fisheye model -.. ocv:function:: void Fisheye::undistortPoints(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()) +.. ocv:function:: void fisheye::undistortPoints(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray R = noArray(), InputArray P = noArray()) :param distorted: Array of object points, 1xN/Nx1 2-channel (or ``vector`` ), where N is the number of points in the view. @@ -1655,11 +1651,11 @@ Undistorts 2D points using fisheye model :param undistorted: Output array of image points, 1xN/Nx1 2-channel, or ``vector`` . -Fisheye::initUndistortRectifyMap +fisheye::initUndistortRectifyMap ------------------------------------- Computes undistortion and rectification maps for image transform by cv::remap(). If D is empty zero distortion is used, if R or P is empty identity matrixes are used. -.. ocv:function:: void Fisheye::initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2) +.. ocv:function:: void fisheye::initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P, const cv::Size& size, int m1type, OutputArray map1, OutputArray map2) :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. @@ -1677,11 +1673,11 @@ Computes undistortion and rectification maps for image transform by cv::remap(). :param map2: The second output map. -Fisheye::undistortImage +fisheye::undistortImage ----------------------- Transforms an image to compensate for fisheye lens distortion. -.. ocv:function:: void Fisheye::undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()) +.. ocv:function:: void fisheye::undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), const Size& new_size = Size()) :param distorted: image with fisheye lens distortion. @@ -1696,12 +1692,12 @@ Transforms an image to compensate for fisheye lens distortion. The function transforms an image to compensate radial and tangential lens distortion. The function is simply a combination of -:ocv:func:`Fisheye::initUndistortRectifyMap` (with unity ``R`` ) and +:ocv:func:`fisheye::initUndistortRectifyMap` (with unity ``R`` ) and :ocv:func:`remap` (with bilinear interpolation). See the former function for details of the transformation being performed. See below the results of undistortImage. * a\) result of :ocv:func:`undistort` of perspective camera model (all possible coefficients (k_1, k_2, k_3, k_4, k_5, k_6) of distortion were optimized under calibration) - * b\) result of :ocv:func:`Fisheye::undistortImage` of fisheye camera model (all possible coefficients (k_1, k_2, k_3, k_4) of fisheye distortion were optimized under calibration) + * b\) result of :ocv:func:`fisheye::undistortImage` of fisheye camera model (all possible coefficients (k_1, k_2, k_3, k_4) of fisheye distortion were optimized under calibration) * c\) original image was captured with fisheye lens Pictures a) and b) almost the same. But if we consider points of image located far from the center of image, we can notice that on image a) these points are distorted. @@ -1709,11 +1705,11 @@ Pictures a) and b) almost the same. But if we consider points of image located f .. image:: pics/fisheye_undistorted.jpg -Fisheye::estimateNewCameraMatrixForUndistortRectify +fisheye::estimateNewCameraMatrixForUndistortRectify ---------------------------------------------------------- Estimates new camera matrix for undistortion or rectification. -.. ocv:function:: void Fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0) +.. ocv:function:: void fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, InputArray D, const Size &image_size, InputArray R, OutputArray P, double balance = 0.0, const Size& new_size = Size(), double fov_scale = 1.0) :param K: Camera matrix :math:`K = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{_1}`. @@ -1727,11 +1723,11 @@ Estimates new camera matrix for undistortion or rectification. :param fov_scale: Divisor for new focal length. -Fisheye::stereoRectify +fisheye::stereoRectify ------------------------------ Stereo rectification for fisheye camera model -.. ocv:function:: void Fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0) +.. ocv:function:: void fisheye::stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(), double balance = 0.0, double fov_scale = 1.0) :param K1: First camera matrix. @@ -1773,11 +1769,11 @@ Stereo rectification for fisheye camera model -Fisheye::calibrate +fisheye::calibrate ---------------------------- Performs camera calibaration -.. ocv:function:: double Fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) +.. ocv:function:: double fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) :param objectPoints: vector of vectors of calibration pattern points in the calibration pattern coordinate space. @@ -1785,7 +1781,7 @@ Performs camera calibaration :param image_size: Size of the image used only to initialize the intrinsic camera matrix. - :param K: Output 3x3 floating-point camera matrix :math:`A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}` . If ``Fisheye::CALIB_USE_INTRINSIC_GUESS``/ is specified, some or all of ``fx, fy, cx, cy`` must be initialized before calling the function. + :param K: Output 3x3 floating-point camera matrix :math:`A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}` . If ``fisheye::CALIB_USE_INTRINSIC_GUESS``/ is specified, some or all of ``fx, fy, cx, cy`` must be initialized before calling the function. :param D: Output vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)`. @@ -1795,24 +1791,24 @@ Performs camera calibaration :param flags: Different flags that may be zero or a combination of the following values: - * **Fisheye::CALIB_USE_INTRINSIC_GUESS** ``cameraMatrix`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center ( ``imageSize`` is used), and focal distances are computed in a least-squares fashion. + * **fisheye::CALIB_USE_INTRINSIC_GUESS** ``cameraMatrix`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center ( ``imageSize`` is used), and focal distances are computed in a least-squares fashion. - * **Fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. + * **fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. - * **Fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. + * **fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. - * **Fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. + * **fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. - * **Fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. + * **fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. :param criteria: Termination criteria for the iterative optimization algorithm. -Fisheye::stereoCalibrate +fisheye::stereoCalibrate ---------------------------- Performs stereo calibration -.. ocv:function:: double Fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) +.. ocv:function:: double fisheye::stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize, OutputArray R, OutputArray T, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON)) :param objectPoints: Vector of vectors of the calibration pattern points. @@ -1820,7 +1816,7 @@ Performs stereo calibration :param imagePoints2: Vector of vectors of the projections of the calibration pattern points, observed by the second camera. - :param K1: Input/output first camera matrix: :math:`\vecthreethree{f_x^{(j)}}{0}{c_x^{(j)}}{0}{f_y^{(j)}}{c_y^{(j)}}{0}{0}{1}` , :math:`j = 0,\, 1` . If any of ``Fisheye::CALIB_USE_INTRINSIC_GUESS`` , ``Fisheye::CV_CALIB_FIX_INTRINSIC`` are specified, some or all of the matrix components must be initialized. + :param K1: Input/output first camera matrix: :math:`\vecthreethree{f_x^{(j)}}{0}{c_x^{(j)}}{0}{f_y^{(j)}}{c_y^{(j)}}{0}{0}{1}` , :math:`j = 0,\, 1` . If any of ``fisheye::CALIB_USE_INTRINSIC_GUESS`` , ``fisheye::CV_CALIB_FIX_INTRINSIC`` are specified, some or all of the matrix components must be initialized. :param D1: Input/output vector of distortion coefficients :math:`(k_1, k_2, k_3, k_4)` of 4 elements. @@ -1836,17 +1832,17 @@ Performs stereo calibration :param flags: Different flags that may be zero or a combination of the following values: - * **Fisheye::CV_CALIB_FIX_INTRINSIC** Fix ``K1, K2?`` and ``D1, D2?`` so that only ``R, T`` matrices are estimated. + * **fisheye::CV_CALIB_FIX_INTRINSIC** Fix ``K1, K2?`` and ``D1, D2?`` so that only ``R, T`` matrices are estimated. - * **Fisheye::CALIB_USE_INTRINSIC_GUESS** ``K1, K2`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center (``imageSize`` is used), and focal distances are computed in a least-squares fashion. + * **fisheye::CALIB_USE_INTRINSIC_GUESS** ``K1, K2`` contains valid initial values of ``fx, fy, cx, cy`` that are optimized further. Otherwise, ``(cx, cy)`` is initially set to the image center (``imageSize`` is used), and focal distances are computed in a least-squares fashion. - * **Fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. + * **fisheye::CALIB_RECOMPUTE_EXTRINSIC** Extrinsic will be recomputed after each iteration of intrinsic optimization. - * **Fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. + * **fisheye::CALIB_CHECK_COND** The functions will check validity of condition number. - * **Fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. + * **fisheye::CALIB_FIX_SKEW** Skew coefficient (alpha) is set to zero and stay zero. - * **Fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. + * **fisheye::CALIB_FIX_K1..4** Selected distortion coefficients are set to zeros and stay zero. :param criteria: Termination criteria for the iterative optimization algorithm. From 3678020c28fad07517e2c5123764291dff3eaee2 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Tue, 20 May 2014 12:37:37 +0400 Subject: [PATCH 15/15] Added license to source files --- modules/calib3d/src/fisheye.cpp | 43 +++++++++++++++++++++++++++ modules/calib3d/test/test_fisheye.cpp | 42 ++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 34d11d1fa..66cf58956 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -1,3 +1,46 @@ +/*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 "precomp.hpp" #include "fisheye.hpp" namespace cv { namespace diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index ed53ec415..2749a1a6e 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -1,3 +1,45 @@ +/*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 #include "../src/fisheye.hpp"