From e85bacff7b676a7789bea6501e694cac299b21be Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Viel Date: Thu, 26 Dec 2013 19:44:23 +0100 Subject: [PATCH 01/44] Avoid obtaining several identical dimensions between two LSH sub-vectors by choosing orthogonal sub-vectors. --- modules/flann/include/opencv2/flann/lsh_table.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/flann/include/opencv2/flann/lsh_table.h b/modules/flann/include/opencv2/flann/lsh_table.h index b0f3223851..6b8d614f23 100644 --- a/modules/flann/include/opencv2/flann/lsh_table.h +++ b/modules/flann/include/opencv2/flann/lsh_table.h @@ -348,13 +348,21 @@ inline LshTable::LshTable(unsigned int feature_size, unsigned int mask_ = std::vector((size_t)ceil((float)(feature_size * sizeof(char)) / (float)sizeof(size_t)), 0); // A bit brutal but fast to code - std::vector indices(feature_size * CHAR_BIT); - for (size_t i = 0; i < feature_size * CHAR_BIT; ++i) indices[i] = i; - std::random_shuffle(indices.begin(), indices.end()); + static std::vector indices(feature_size * CHAR_BIT); + + //Ensure the Nth bit will be selected only once among the different LshTables + //to avoid having two different tables with signatures sharing many dimensions/many bits + if( (indices.size() == feature_size * CHAR_BIT) || (indices.size() < key_size_) ) + { + indices.resize( feature_size * CHAR_BIT ); + for (size_t i = 0; i < feature_size * CHAR_BIT; ++i) indices[i] = i; + std::random_shuffle(indices.begin(), indices.end()); + } // Generate a random set of order of subsignature_size_ bits for (unsigned int i = 0; i < key_size_; ++i) { - size_t index = indices[i]; + size_t index = indices[0]; + indices.erase( indices.begin() ); // Set that bit in the mask size_t divisor = CHAR_BIT * sizeof(size_t); From 8e93c19de311a76704028b0195abfe04cfad900d Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Viel Date: Thu, 26 Dec 2013 23:04:54 +0100 Subject: [PATCH 02/44] Fix a heap issue with static on Windows --- .../flann/include/opencv2/flann/lsh_table.h | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/flann/include/opencv2/flann/lsh_table.h b/modules/flann/include/opencv2/flann/lsh_table.h index 6b8d614f23..18fb139c91 100644 --- a/modules/flann/include/opencv2/flann/lsh_table.h +++ b/modules/flann/include/opencv2/flann/lsh_table.h @@ -348,21 +348,27 @@ inline LshTable::LshTable(unsigned int feature_size, unsigned int mask_ = std::vector((size_t)ceil((float)(feature_size * sizeof(char)) / (float)sizeof(size_t)), 0); // A bit brutal but fast to code - static std::vector indices(feature_size * CHAR_BIT); + static std::vector* indices = NULL; //Ensure the Nth bit will be selected only once among the different LshTables //to avoid having two different tables with signatures sharing many dimensions/many bits - if( (indices.size() == feature_size * CHAR_BIT) || (indices.size() < key_size_) ) + if( indices == NULL ) { - indices.resize( feature_size * CHAR_BIT ); - for (size_t i = 0; i < feature_size * CHAR_BIT; ++i) indices[i] = i; - std::random_shuffle(indices.begin(), indices.end()); + indices = new std::vector( feature_size * CHAR_BIT ); + } + else if( indices->size() < key_size_ ) + { + indices->resize( feature_size * CHAR_BIT ); + for (size_t i = 0; i < feature_size * CHAR_BIT; ++i) { + (*indices)[i] = i; + } + std::random_shuffle(indices->begin(), indices->end()); } // Generate a random set of order of subsignature_size_ bits for (unsigned int i = 0; i < key_size_; ++i) { - size_t index = indices[0]; - indices.erase( indices.begin() ); + size_t index = (*indices)[0]; + indices->erase( indices->begin() ); // Set that bit in the mask size_t divisor = CHAR_BIT * sizeof(size_t); From 9c7e0bfd33cb65a57b06d1bf9a81e663fda01d22 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Fri, 25 Apr 2014 14:49:36 +0400 Subject: [PATCH 03/44] 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 f213a114f4..2675ad402a 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 0000000000..0dcc5e70fd --- /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 0000000000..3989e1bc2f --- /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 04/44] Added test for jacobians --- modules/calib3d/test/test_fisheye.cpp | 91 ++++++++++++++++++++------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 3989e1bc2f..b2f47987cb 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_Assert(!"/////////"); + 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::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 05/44] Added FisheyeTest --- modules/calib3d/test/test_fisheye.cpp | 329 ++++++++++++++------------ 1 file changed, 173 insertions(+), 156 deletions(-) diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index b2f47987cb..e7d4b311c7 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__ > > - -/// 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 -{ - 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; +class FisheyeTest : public ::testing::Test { + +protected: + const static cv::Size imageSize; + const static cv::Matx33d K; + const static cv::Vec4d D; + const static cv::Matx33d R; + const static cv::Vec3d T; + std::string datasets_repository_path; + + virtual void SetUp() { + datasets_repository_path = combine(cvtest::TS::ptr()->get_data_path(), "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) -{ - cv::FileStorage fs(file, cv::FileStorage::READ); - CV_Assert(fs.isOpened()); +protected: + std::string combine(const std::string& _item1, const std::string& _item2); - 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); -} + std::string combine_format(const std::string& item1, const std::string& item2, ...); -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); + void readPoins(std::vector >& objectPoints, + std::vector >& imagePoints, + const std::string& path, const int n_images, const int n_points); - 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)); + 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); - return merged; -} + cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r); +}; -} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// TESTS:: -TEST(FisheyeTest, projectPoints) +TEST_F(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 06/44] 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 37159b016b..3d19b72895 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 2675ad402a..816ed76f70 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 07/44] 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 | 69 ++++++------------- 5 files changed, 74 insertions(+), 124 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 3d19b72895..710672cf52 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 816ed76f70..8caebd363c 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 0dcc5e70fd..a9172c9b26 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 0000000000..e000e635a0 --- /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 e7d4b311c7..41bbfea59d 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(); + fs1[cv::format("image_%d", i)] >> objectPoints[i]; + } + fs1.release(); - imagePoints[image_idx] = image; - objectPoints[image_idx] = object; + 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 08/44] 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 8caebd363c..b495fbe2d0 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 a9172c9b26..19b30428e9 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 e000e635a0..fa4bfdb388 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 09/44] Added tests for stereoCalibrate --- .../include/opencv2/calib3d/calib3d.hpp | 6 +- modules/calib3d/src/fisheye.cpp | 206 ++++++--------- modules/calib3d/test/test_fisheye.cpp | 235 ++++++++++++++++++ 3 files changed, 317 insertions(+), 130 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index b495fbe2d0..b6d85a0959 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 19b30428e9..50dd045281 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)); - - 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; + std::vector rvecs1(n_images), tvecs1(n_images), rvecs2(n_images), tvecs2(n_images); + + 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.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.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)); - - 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], + 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]; + } + + rms /= (e.total() / 2); + rms = sqrt(rms); + + _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 (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()); + 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()); - - return 0; + return rms; } namespace cv{ namespace { diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 41bbfea59d..0caaf7f45f 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 10/44] 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 710672cf52..e020b21e34 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 11/44] 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 b6d85a0959..7b7f92efc8 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 36afd4ef55fd291d0948da41324d124aa2d3eb4a Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Tue, 6 May 2014 16:30:03 +0400 Subject: [PATCH 12/44] added additionalInfo in faceRecognition --- .../include/opencv2/contrib/contrib.hpp | 8 + modules/contrib/src/facerec.cpp | 186 +++++++++++++++++- samples/cpp/facerec_demo.cpp | 16 +- 3 files changed, 204 insertions(+), 6 deletions(-) diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index 7d881c359a..18c6eeb4b6 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -948,6 +948,14 @@ namespace cv // Deserializes this object from a given cv::FileStorage. virtual void load(const FileStorage& fs) = 0; + // Sets additions information as pairs label - info. + virtual void setLabelsInfo(const std::map& additionalInfo) = 0; + + // Gets string information by label + virtual string getLabelInfo(const int label) = 0; + + // Gets labels by string + virtual vector getLabelsByString(const string str) = 0; }; CV_EXPORTS_W Ptr createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index a3d695ad16..20bd411075 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -18,6 +18,43 @@ #include "precomp.hpp" #include +struct LabelInfo +{ + LabelInfo():label(-1), value("") {} + LabelInfo(int _label, std::string _value): label(_label), value(_value) {} + std::string value; + int label; + void write(cv::FileStorage& fs) const + { + fs << "{" << "label" << label << "value" << value << "}"; + } + void read(const cv::FileNode& node) + { + label = (int)node["label"]; + value = (std::string)node["value"]; + } + friend std::ostream& operator<<(std::ostream& out, const LabelInfo& info); +}; + +static void write(cv::FileStorage& fs, const std::string&, const LabelInfo& x) +{ + x.write(fs); +} + +static void read(const cv::FileNode& node, LabelInfo& x, const LabelInfo& default_value = LabelInfo()) +{ + if(node.empty()) + x = default_value; + else + x.read(node); +} + +std::ostream& operator<<(std::ostream& out, const LabelInfo& info) +{ + out << "{ label = " << info.label << ", " << "value = " << info.value << "}"; + return out; +} + namespace cv { @@ -98,7 +135,6 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { return elems; } - // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Cognitive Neuroscience 3 (1991), 71–86. class Eigenfaces : public FaceRecognizer @@ -111,6 +147,7 @@ private: Mat _eigenvectors; Mat _eigenvalues; Mat _mean; + std::map _labelsInfo; public: using FaceRecognizer::save; @@ -147,6 +184,15 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; + // Sets additions information as pairs label - info. + void setLabelsInfo(const std::map& labelsInfo); + + // Gets additional information by label + string getLabelInfo(const int label); + + // Gets labels by string + std::vector getLabelsByString(const string str); + AlgorithmInfo* info() const; }; @@ -164,6 +210,7 @@ private: Mat _mean; vector _projections; Mat _labels; + std::map _labelsInfo; public: using FaceRecognizer::save; @@ -202,6 +249,15 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; + // Sets additions information as pairs label - info. + void setLabelsInfo(const std::map& labelsInfo); + + // Gets additional information by label + string getLabelInfo(const int label); + + // Gets labels by string + std::vector getLabelsByString(const string str); + AlgorithmInfo* info() const; }; @@ -222,6 +278,7 @@ private: vector _histograms; Mat _labels; + std::map _labelsInfo; // Computes a LBPH model with images in src and // corresponding labels in labels, possibly preserving @@ -287,6 +344,15 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; + // Sets additions information as pairs label - info. + void setLabelsInfo(const std::map& labelsInfo); + + // Gets additional information by label + string getLabelInfo(const int label); + + // Gets labels by string + std::vector getLabelsByString(const string str); + // Getter functions. int neighbors() const { return _neighbors; } int radius() const { return _radius; } @@ -423,6 +489,17 @@ void Eigenfaces::load(const FileStorage& fs) { // read sequences readFileNodeList(fs["projections"], _projections); fs["labels"] >> _labels; + const FileNode& fn = fs["info"]; + if (fn.type() == FileNode::SEQ) + { + _labelsInfo.clear(); + for (FileNodeIterator it = fn.begin(); it != fn.end();) + { + LabelInfo item; + it >> item; + _labelsInfo.insert(std::make_pair(item.label, item.value)); + } + } } void Eigenfaces::save(FileStorage& fs) const { @@ -434,6 +511,34 @@ void Eigenfaces::save(FileStorage& fs) const { // write sequences writeFileNodeList(fs, "projections", _projections); fs << "labels" << _labels; + fs << "info" << "["; + for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + fs << LabelInfo(it->first, it->second); + fs << "]"; +} + +void Eigenfaces::setLabelsInfo(const std::map& labelsInfo) +{ + _labelsInfo = labelsInfo; +} + +string Eigenfaces::getLabelInfo(const int label) +{ + if(_labelsInfo.count(label) > 0) + return _labelsInfo[label]; + return ""; +} + +vector Eigenfaces::getLabelsByString(const string str) +{ + vector labels; + for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + { + size_t found = (it->second).find(str); + if(found != string::npos) + labels.push_back(it->first); + } + return labels; } //------------------------------------------------------------------------------ @@ -544,6 +649,17 @@ void Fisherfaces::load(const FileStorage& fs) { // read sequences readFileNodeList(fs["projections"], _projections); fs["labels"] >> _labels; + const FileNode& fn = fs["info"]; + if (fn.type() == FileNode::SEQ) + { + _labelsInfo.clear(); + for (FileNodeIterator it = fn.begin(); it != fn.end();) + { + LabelInfo item; + it >> item; + _labelsInfo.insert(std::make_pair(item.label, item.value)); + } + } } // See FaceRecognizer::save. @@ -556,6 +672,34 @@ void Fisherfaces::save(FileStorage& fs) const { // write sequences writeFileNodeList(fs, "projections", _projections); fs << "labels" << _labels; + fs << "info" << "["; + for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + fs << LabelInfo(it->first, it->second); + fs << "]"; +} + +void Fisherfaces::setLabelsInfo(const std::map& labelsInfo) +{ + _labelsInfo = labelsInfo; +} + +string Fisherfaces::getLabelInfo(const int label) +{ + if(_labelsInfo.count(label) > 0) + return _labelsInfo[label]; + return ""; +} + +vector Fisherfaces::getLabelsByString(const string str) +{ + vector labels; + for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + { + size_t found = (it->second).find(str); + if(found != string::npos) + labels.push_back(it->first); + } + return labels; } //------------------------------------------------------------------------------ @@ -743,6 +887,17 @@ void LBPH::load(const FileStorage& fs) { //read matrices readFileNodeList(fs["histograms"], _histograms); fs["labels"] >> _labels; + const FileNode& fn = fs["info"]; + if (fn.type() == FileNode::SEQ) + { + _labelsInfo.clear(); + for (FileNodeIterator it = fn.begin(); it != fn.end();) + { + LabelInfo item; + it >> item; + _labelsInfo.insert(std::make_pair(item.label, item.value)); + } + } } // See FaceRecognizer::save. @@ -754,6 +909,34 @@ void LBPH::save(FileStorage& fs) const { // write matrices writeFileNodeList(fs, "histograms", _histograms); fs << "labels" << _labels; + fs << "info" << "["; + for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + fs << LabelInfo(it->first, it->second); + fs << "]"; +} + +void LBPH::setLabelsInfo(const std::map& labelsInfo) +{ + _labelsInfo = labelsInfo; +} + +string LBPH::getLabelInfo(const int label) +{ + if(_labelsInfo.count(label) > 0) + return _labelsInfo[label]; + return ""; +} + +vector LBPH::getLabelsByString(const string str) +{ + vector labels; + for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + { + size_t found = (it->second).find(str); + if(found != string::npos) + labels.push_back(it->first); + } + return labels; } void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels) { @@ -849,7 +1032,6 @@ int LBPH::predict(InputArray _src) const { return label; } - Ptr createEigenFaceRecognizer(int num_components, double threshold) { return new Eigenfaces(num_components, threshold); diff --git a/samples/cpp/facerec_demo.cpp b/samples/cpp/facerec_demo.cpp index 6402082e82..dfc15aa81e 100644 --- a/samples/cpp/facerec_demo.cpp +++ b/samples/cpp/facerec_demo.cpp @@ -39,20 +39,23 @@ static Mat toGrayscale(InputArray _src) { return dst; } -static void read_csv(const string& filename, vector& images, vector& labels, char separator = ';') { +static void read_csv(const string& filename, vector& images, vector& labels, std::map& labelsInfo, char separator = ';') { std::ifstream file(filename.c_str(), ifstream::in); if (!file) { string error_message = "No valid input file was given, please check the given filename."; CV_Error(CV_StsBadArg, error_message); } - string line, path, classlabel; + string line, path, classlabel, info; while (getline(file, line)) { stringstream liness(line); getline(liness, path, separator); - getline(liness, classlabel); + getline(liness, classlabel, separator); + getline(liness, info, separator); if(!path.empty() && !classlabel.empty()) { images.push_back(imread(path, 0)); labels.push_back(atoi(classlabel.c_str())); + if(!info.empty()) + labelsInfo.insert(std::make_pair(labels.back(), info)); } } } @@ -69,15 +72,17 @@ int main(int argc, const char *argv[]) { // These vectors hold the images and corresponding labels. vector images; vector labels; + std::map labelsInfo; // Read in the data. This can fail if no valid // input filename is given. try { - read_csv(fn_csv, images, labels); + read_csv(fn_csv, images, labels, labelsInfo); } catch (cv::Exception& e) { cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; // nothing more we can do exit(1); } + // Quit if there are not enough images for this demo. if(images.size() <= 1) { string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; @@ -111,6 +116,7 @@ int main(int argc, const char *argv[]) { // cv::createEigenFaceRecognizer(10, 123.0); // Ptr model = createEigenFaceRecognizer(); + model->setLabelsInfo(labelsInfo); model->train(images, labels); // The following line predicts the label of a given // test image: @@ -124,6 +130,8 @@ int main(int argc, const char *argv[]) { // string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); cout << result_message << endl; + if( (predictedLabel == testLabel) && (model->getLabelInfo(predictedLabel) != "") ) + cout << format("%d-th label's info: %s", predictedLabel, model->getLabelInfo(predictedLabel).c_str()) << endl; // Sometimes you'll need to get/set internal model data, // which isn't exposed by the public cv::FaceRecognizer. // Since each cv::FaceRecognizer is derived from a From 349ff631a5d30e979608581adaf75abf7450643b Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Wed, 7 May 2014 20:53:07 +0400 Subject: [PATCH 13/44] 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 e020b21e34..20a731f958 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); + + 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(); - readPoints(objectPoints, imagePoints, combine(datasets_repository_path, "calib-3_stereo_from_JY/left"), n_images); + 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 aa76ef9a98d64e45f441eb914541f78d66912b9f Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Wed, 7 May 2014 17:19:22 +0400 Subject: [PATCH 14/44] fixes --- .../include/opencv2/contrib/contrib.hpp | 4 +- modules/contrib/src/facerec.cpp | 46 +++++++++---------- samples/cpp/facerec_demo.cpp | 2 +- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index 18c6eeb4b6..b3b4f330db 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -952,10 +952,10 @@ namespace cv virtual void setLabelsInfo(const std::map& additionalInfo) = 0; // Gets string information by label - virtual string getLabelInfo(const int label) = 0; + virtual string getLabelInfo(int label) const = 0; // Gets labels by string - virtual vector getLabelsByString(const string str) = 0; + virtual vector getLabelsByString(const string& str) = 0; }; CV_EXPORTS_W Ptr createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index 20bd411075..9c310e0316 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -21,9 +21,9 @@ struct LabelInfo { LabelInfo():label(-1), value("") {} - LabelInfo(int _label, std::string _value): label(_label), value(_value) {} - std::string value; + LabelInfo(int _label, const std::string &_value): label(_label), value(_value) {} int label; + std::string value; void write(cv::FileStorage& fs) const { fs << "{" << "label" << label << "value" << value << "}"; @@ -33,7 +33,11 @@ struct LabelInfo label = (int)node["label"]; value = (std::string)node["value"]; } - friend std::ostream& operator<<(std::ostream& out, const LabelInfo& info); + std::ostream& operator<<(std::ostream& out) + { + out << "{ label = " << label << ", " << "value = " << value << "}"; + return out; + } }; static void write(cv::FileStorage& fs, const std::string&, const LabelInfo& x) @@ -49,12 +53,6 @@ static void read(const cv::FileNode& node, LabelInfo& x, const LabelInfo& defaul x.read(node); } -std::ostream& operator<<(std::ostream& out, const LabelInfo& info) -{ - out << "{ label = " << info.label << ", " << "value = " << info.value << "}"; - return out; -} - namespace cv { @@ -188,10 +186,10 @@ public: void setLabelsInfo(const std::map& labelsInfo); // Gets additional information by label - string getLabelInfo(const int label); + string getLabelInfo(int label) const; // Gets labels by string - std::vector getLabelsByString(const string str); + std::vector getLabelsByString(const string& str); AlgorithmInfo* info() const; }; @@ -253,10 +251,10 @@ public: void setLabelsInfo(const std::map& labelsInfo); // Gets additional information by label - string getLabelInfo(const int label); + string getLabelInfo(int label) const; // Gets labels by string - std::vector getLabelsByString(const string str); + std::vector getLabelsByString(const string& str); AlgorithmInfo* info() const; }; @@ -348,10 +346,10 @@ public: void setLabelsInfo(const std::map& labelsInfo); // Gets additional information by label - string getLabelInfo(const int label); + string getLabelInfo(int label) const; // Gets labels by string - std::vector getLabelsByString(const string str); + std::vector getLabelsByString(const string& str); // Getter functions. int neighbors() const { return _neighbors; } @@ -522,14 +520,14 @@ void Eigenfaces::setLabelsInfo(const std::map& labelsInfo) _labelsInfo = labelsInfo; } -string Eigenfaces::getLabelInfo(const int label) +string Eigenfaces::getLabelInfo(int label) const { if(_labelsInfo.count(label) > 0) - return _labelsInfo[label]; + return _labelsInfo.at(label); return ""; } -vector Eigenfaces::getLabelsByString(const string str) +vector Eigenfaces::getLabelsByString(const string& str) { vector labels; for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) @@ -683,14 +681,14 @@ void Fisherfaces::setLabelsInfo(const std::map& labelsInfo) _labelsInfo = labelsInfo; } -string Fisherfaces::getLabelInfo(const int label) +string Fisherfaces::getLabelInfo(int label) const { if(_labelsInfo.count(label) > 0) - return _labelsInfo[label]; + return _labelsInfo.at(label); return ""; } -vector Fisherfaces::getLabelsByString(const string str) +vector Fisherfaces::getLabelsByString(const string& str) { vector labels; for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) @@ -920,14 +918,14 @@ void LBPH::setLabelsInfo(const std::map& labelsInfo) _labelsInfo = labelsInfo; } -string LBPH::getLabelInfo(const int label) +string LBPH::getLabelInfo(int label) const { if(_labelsInfo.count(label) > 0) - return _labelsInfo[label]; + return _labelsInfo.at(label); return ""; } -vector LBPH::getLabelsByString(const string str) +vector LBPH::getLabelsByString(const string& str) { vector labels; for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) diff --git a/samples/cpp/facerec_demo.cpp b/samples/cpp/facerec_demo.cpp index dfc15aa81e..ef480c6df7 100644 --- a/samples/cpp/facerec_demo.cpp +++ b/samples/cpp/facerec_demo.cpp @@ -130,7 +130,7 @@ int main(int argc, const char *argv[]) { // string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); cout << result_message << endl; - if( (predictedLabel == testLabel) && (model->getLabelInfo(predictedLabel) != "") ) + if( (predictedLabel == testLabel) && !model->getLabelInfo(predictedLabel).empty() ) cout << format("%d-th label's info: %s", predictedLabel, model->getLabelInfo(predictedLabel).c_str()) << endl; // Sometimes you'll need to get/set internal model data, // which isn't exposed by the public cv::FaceRecognizer. From d67c9aabff3d16e6e7b1c966b5983ad513dcb5e7 Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Thu, 8 May 2014 16:43:03 +0400 Subject: [PATCH 15/44] docs --- modules/contrib/doc/facerec/facerec_api.rst | 38 ++++++++++++++++++ .../include/opencv2/contrib/contrib.hpp | 4 +- modules/contrib/src/facerec.cpp | 39 +++++++++---------- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/modules/contrib/doc/facerec/facerec_api.rst b/modules/contrib/doc/facerec/facerec_api.rst index 2aa3dcfa00..8de4359c52 100644 --- a/modules/contrib/doc/facerec/facerec_api.rst +++ b/modules/contrib/doc/facerec/facerec_api.rst @@ -46,6 +46,15 @@ a unified access to all face recongition algorithms in OpenCV. :: // Deserializes this object from a given cv::FileStorage. virtual void load(const FileStorage& fs) = 0; + + // Sets additional information as pairs label - info. + virtual void setLabelsInfo(const std::map& labelsInfo) = 0; + + // Gets string information by label + virtual string getLabelInfo(int label) const = 0; + + // Gets labels by string + virtual vector getLabelsByString(const string& str) = 0; }; @@ -70,6 +79,8 @@ Moreover every :ocv:class:`FaceRecognizer` supports the: * **Loading/Saving** the model state from/to a given XML or YAML. +* **Setting/Getting labels info**, that is storaged as a string. + .. note:: When using the FaceRecognizer interface in combination with Python, please stick to Python 2. Some underlying scripts like create_csv will not work in other versions, like Python 3. Setting the Thresholds @@ -293,6 +304,30 @@ to enable loading the model state. ``FaceRecognizer::load(FileStorage& fs)`` in turn gets called by ``FaceRecognizer::load(const string& filename)``, to ease saving a model. +FaceRecognizer::setLabelsInfo +----------------------------- + +Sets string information about labels into the model. +.. ocv:function:: void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) = 0 + +Information about the label loads as a pair label-its info. + +FaceRecognizer::getLabelInfo +---------------------------- + +Gets string information by label. +.. ocv:function:: string FaceRecognizer::getLabelInfo(int label) const = 0 + +If there is no such label in the model or there is no information about the label it will return an empty string. + +FaceRecognizer::getLabelsByString +--------------------------------- +Gets vector of labels by string. + +.. ocv:function:: vector FaceRecognizer::getLabelsByString(const string& str) = 0 + +If the string contained in a string information for a label, this label will be pushed into the vector. + createEigenFaceRecognizer ------------------------- @@ -319,6 +354,7 @@ Model internal data: * ``mean`` The sample mean calculated from the training data. * ``projections`` The projections of the training data. * ``labels`` The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1. +* ``labelsInfo`` The string information about the labels. createFisherFaceRecognizer -------------------------- @@ -346,6 +382,7 @@ Model internal data: * ``mean`` The sample mean calculated from the training data. * ``projections`` The projections of the training data. * ``labels`` The labels corresponding to the projections. +* ``labelsInfo`` The string information about the labels. createLBPHFaceRecognizer @@ -375,3 +412,4 @@ Model internal data: * ``threshold`` see :ocv:func:`createLBPHFaceRecognizer`. * ``histograms`` Local Binary Patterns Histograms calculated from the given training data (empty if none was given). * ``labels`` Labels corresponding to the calculated Local Binary Patterns Histograms. +* ``labelsInfo`` The string information about the labels. diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index b3b4f330db..e6e11d816a 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -948,8 +948,8 @@ namespace cv // Deserializes this object from a given cv::FileStorage. virtual void load(const FileStorage& fs) = 0; - // Sets additions information as pairs label - info. - virtual void setLabelsInfo(const std::map& additionalInfo) = 0; + // Sets additional information as pairs label - info. + virtual void setLabelsInfo(const std::map& labelsInfo) = 0; // Gets string information by label virtual string getLabelInfo(int label) const = 0; diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index 9c310e0316..ef32b5273a 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -182,10 +182,10 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additions information as pairs label - info. + // Sets additional information as pairs label - info. void setLabelsInfo(const std::map& labelsInfo); - // Gets additional information by label + // Gets string information by label string getLabelInfo(int label) const; // Gets labels by string @@ -247,10 +247,10 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additions information as pairs label - info. + // Sets additional information as pairs label - info. void setLabelsInfo(const std::map& labelsInfo); - // Gets additional information by label + // Gets string information by label string getLabelInfo(int label) const; // Gets labels by string @@ -342,10 +342,10 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additions information as pairs label - info. + // Sets additional information as pairs label - info. void setLabelsInfo(const std::map& labelsInfo); - // Gets additional information by label + // Gets string information by label string getLabelInfo(int label) const; // Gets labels by string @@ -487,7 +487,7 @@ void Eigenfaces::load(const FileStorage& fs) { // read sequences readFileNodeList(fs["projections"], _projections); fs["labels"] >> _labels; - const FileNode& fn = fs["info"]; + const FileNode& fn = fs["labelsInfo"]; if (fn.type() == FileNode::SEQ) { _labelsInfo.clear(); @@ -509,7 +509,7 @@ void Eigenfaces::save(FileStorage& fs) const { // write sequences writeFileNodeList(fs, "projections", _projections); fs << "labels" << _labels; - fs << "info" << "["; + fs << "labelsInfo" << "["; for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) fs << LabelInfo(it->first, it->second); fs << "]"; @@ -522,9 +522,8 @@ void Eigenfaces::setLabelsInfo(const std::map& labelsInfo) string Eigenfaces::getLabelInfo(int label) const { - if(_labelsInfo.count(label) > 0) - return _labelsInfo.at(label); - return ""; + std::map::const_iterator iter(_labelsInfo.find(label)); + return iter != _labelsInfo.end() ? iter->second : ""; } vector Eigenfaces::getLabelsByString(const string& str) @@ -647,7 +646,7 @@ void Fisherfaces::load(const FileStorage& fs) { // read sequences readFileNodeList(fs["projections"], _projections); fs["labels"] >> _labels; - const FileNode& fn = fs["info"]; + const FileNode& fn = fs["labelsInfo"]; if (fn.type() == FileNode::SEQ) { _labelsInfo.clear(); @@ -670,7 +669,7 @@ void Fisherfaces::save(FileStorage& fs) const { // write sequences writeFileNodeList(fs, "projections", _projections); fs << "labels" << _labels; - fs << "info" << "["; + fs << "labelsInfo" << "["; for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) fs << LabelInfo(it->first, it->second); fs << "]"; @@ -683,9 +682,8 @@ void Fisherfaces::setLabelsInfo(const std::map& labelsInfo) string Fisherfaces::getLabelInfo(int label) const { - if(_labelsInfo.count(label) > 0) - return _labelsInfo.at(label); - return ""; + std::map::const_iterator iter(_labelsInfo.find(label)); + return iter != _labelsInfo.end() ? iter->second : ""; } vector Fisherfaces::getLabelsByString(const string& str) @@ -885,7 +883,7 @@ void LBPH::load(const FileStorage& fs) { //read matrices readFileNodeList(fs["histograms"], _histograms); fs["labels"] >> _labels; - const FileNode& fn = fs["info"]; + const FileNode& fn = fs["labelsInfo"]; if (fn.type() == FileNode::SEQ) { _labelsInfo.clear(); @@ -907,7 +905,7 @@ void LBPH::save(FileStorage& fs) const { // write matrices writeFileNodeList(fs, "histograms", _histograms); fs << "labels" << _labels; - fs << "info" << "["; + fs << "labelsInfo" << "["; for (std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) fs << LabelInfo(it->first, it->second); fs << "]"; @@ -920,9 +918,8 @@ void LBPH::setLabelsInfo(const std::map& labelsInfo) string LBPH::getLabelInfo(int label) const { - if(_labelsInfo.count(label) > 0) - return _labelsInfo.at(label); - return ""; + std::map::const_iterator iter(_labelsInfo.find(label)); + return iter != _labelsInfo.end() ? iter->second : ""; } vector LBPH::getLabelsByString(const string& str) From e4a9c0f184024330ab33eb7997874921d9fe0195 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 12 May 2014 15:37:47 +0400 Subject: [PATCH 16/44] 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 20a731f958..2f1bc69819 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 7b7f92efc8..67cf56890f 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 17/44] 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 2f1bc69819..5f64125eba 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 67cf56890f..0b048fe8a5 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 50dd045281..da7a8394f0 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 fa4bfdb388..82c9f34598 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 dfdba98fcf..7457a94127 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 a46f119fdf1f0dc05608e2e6d8a120325650c13c Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Mon, 19 May 2014 09:54:15 +0400 Subject: [PATCH 18/44] docs fixes --- modules/contrib/doc/facerec/facerec_api.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/contrib/doc/facerec/facerec_api.rst b/modules/contrib/doc/facerec/facerec_api.rst index 8de4359c52..4c91bb7cb9 100644 --- a/modules/contrib/doc/facerec/facerec_api.rst +++ b/modules/contrib/doc/facerec/facerec_api.rst @@ -310,7 +310,7 @@ FaceRecognizer::setLabelsInfo Sets string information about labels into the model. .. ocv:function:: void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) = 0 -Information about the label loads as a pair label-its info. +Information about the label loads as a pair "label id - string info". FaceRecognizer::getLabelInfo ---------------------------- @@ -318,7 +318,7 @@ FaceRecognizer::getLabelInfo Gets string information by label. .. ocv:function:: string FaceRecognizer::getLabelInfo(int label) const = 0 -If there is no such label in the model or there is no information about the label it will return an empty string. +If an unknown label id is provided or there is no label information assosiated with the specified label id the method returns an empty string. FaceRecognizer::getLabelsByString --------------------------------- @@ -326,7 +326,7 @@ Gets vector of labels by string. .. ocv:function:: vector FaceRecognizer::getLabelsByString(const string& str) = 0 -If the string contained in a string information for a label, this label will be pushed into the vector. +The function searches for the labels containing the specified substring in the associated string info. createEigenFaceRecognizer ------------------------- From 651b13f72a786ee26fe6c12ee6a305fc0f6b7d71 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 19 May 2014 17:55:32 +0400 Subject: [PATCH 19/44] 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 0b048fe8a5..5e9cde8ec0 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 da7a8394f0..34d11d1fa8 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 7457a94127..ed53ec415e 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 20/44] 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 5f64125eba..ae6aa27f83 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 21/44] 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 34d11d1fa8..66cf589564 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 ed53ec415e..2749a1a6e3 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" From 511ed4388ec233b471c9a4300f508640d2e7fdd9 Mon Sep 17 00:00:00 2001 From: Alexander Karsakov Date: Mon, 19 May 2014 18:15:04 +0400 Subject: [PATCH 22/44] Disabled BORDER_CONSTANT for pyramid functions. --- modules/imgproc/doc/filtering.rst | 28 ++++++++++++++++++---------- modules/imgproc/src/pyramids.cpp | 6 ++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/modules/imgproc/doc/filtering.rst b/modules/imgproc/doc/filtering.rst index efab258d4a..130b8e01a3 100755 --- a/modules/imgproc/doc/filtering.rst +++ b/modules/imgproc/doc/filtering.rst @@ -558,6 +558,8 @@ Constructs the Gaussian pyramid for an image. :param maxlevel: 0-based index of the last (the smallest) pyramid layer. It must be non-negative. + :param borderType: Pixel extrapolation method (BORDER_CONSTANT don't supported). See :ocv:func:`borderInterpolate` for details. + The function constructs a vector of images and builds the Gaussian pyramid by recursively applying :ocv:func:`pyrDown` to the previously built pyramid layers, starting from ``dst[0]==src`` . @@ -1343,12 +1345,16 @@ Blurs an image and downsamples it. :param dst: output image; it has the specified size and the same type as ``src``. - :param dstsize: size of the output image; by default, it is computed as ``Size((src.cols+1)/2, (src.rows+1)/2)``, but in any case, the following conditions should be satisfied: + :param dstsize: size of the output image. - .. math:: + :param borderType: Pixel extrapolation method (BORDER_CONSTANT don't supported). See :ocv:func:`borderInterpolate` for details. + +By default, size of the output image is computed as ``Size((src.cols+1)/2, (src.rows+1)/2)``, but in any case, the following conditions should be satisfied: - \begin{array}{l} - | \texttt{dstsize.width} *2-src.cols| \leq 2 \\ | \texttt{dstsize.height} *2-src.rows| \leq 2 \end{array} +.. math:: + + \begin{array}{l} + | \texttt{dstsize.width} *2-src.cols| \leq 2 \\ | \texttt{dstsize.height} *2-src.rows| \leq 2 \end{array} The function performs the downsampling step of the Gaussian pyramid construction. First, it convolves the source image with the kernel: @@ -1358,8 +1364,6 @@ The function performs the downsampling step of the Gaussian pyramid construction Then, it downsamples the image by rejecting even rows and columns. - - pyrUp ----- Upsamples an image and then blurs it. @@ -1376,12 +1380,16 @@ Upsamples an image and then blurs it. :param dst: output image. It has the specified size and the same type as ``src`` . - :param dstsize: size of the output image; by default, it is computed as ``Size(src.cols*2, (src.rows*2)``, but in any case, the following conditions should be satisfied: + :param dstsize: size of the output image. - .. math:: + :param borderType: Pixel extrapolation method (only BORDER_DEFAULT supported). See :ocv:func:`borderInterpolate` for details. + +By default, size of the output image is computed as ``Size(src.cols*2, (src.rows*2)``, but in any case, the following conditions should be satisfied: + +.. math:: - \begin{array}{l} - | \texttt{dstsize.width} -src.cols*2| \leq ( \texttt{dstsize.width} \mod 2) \\ | \texttt{dstsize.height} -src.rows*2| \leq ( \texttt{dstsize.height} \mod 2) \end{array} + \begin{array}{l} + | \texttt{dstsize.width} -src.cols*2| \leq ( \texttt{dstsize.width} \mod 2) \\ | \texttt{dstsize.height} -src.rows*2| \leq ( \texttt{dstsize.height} \mod 2) \end{array} The function performs the upsampling step of the Gaussian pyramid construction, though it can actually be used to construct the Laplacian pyramid. First, it upsamples the source image by injecting even zero rows and columns and then convolves the result with the same kernel as in :ocv:func:`pyrDown` multiplied by 4. diff --git a/modules/imgproc/src/pyramids.cpp b/modules/imgproc/src/pyramids.cpp index 01e510e7de..4000167497 100644 --- a/modules/imgproc/src/pyramids.cpp +++ b/modules/imgproc/src/pyramids.cpp @@ -403,6 +403,8 @@ typedef void (*PyrFunc)(const Mat&, Mat&, int); void cv::pyrDown( InputArray _src, OutputArray _dst, const Size& _dsz, int borderType ) { + CV_Assert(borderType != BORDER_CONSTANT); + Mat src = _src.getMat(); Size dsz = _dsz == Size() ? Size((src.cols + 1)/2, (src.rows + 1)/2) : _dsz; _dst.create( dsz, src.type() ); @@ -433,6 +435,8 @@ void cv::pyrDown( InputArray _src, OutputArray _dst, const Size& _dsz, int borde void cv::pyrUp( InputArray _src, OutputArray _dst, const Size& _dsz, int borderType ) { + CV_Assert(borderType == BORDER_DEFAULT); + Mat src = _src.getMat(); Size dsz = _dsz == Size() ? Size(src.cols*2, src.rows*2) : _dsz; _dst.create( dsz, src.type() ); @@ -463,6 +467,8 @@ void cv::pyrUp( InputArray _src, OutputArray _dst, const Size& _dsz, int borderT void cv::buildPyramid( InputArray _src, OutputArrayOfArrays _dst, int maxlevel, int borderType ) { + CV_Assert(borderType != BORDER_CONSTANT); + Mat src = _src.getMat(); _dst.create( maxlevel + 1, 1, 0 ); _dst.getMatRef(0) = src; From e63d7de87c3565555e4a73220a465ed39488160d Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Viel Date: Tue, 20 May 2014 22:52:11 +0200 Subject: [PATCH 23/44] Allows to choose orthogonal sub-vectors for LSH without using a static table among LshTable instances --- .../flann/include/opencv2/flann/lsh_index.h | 14 ++++++++- .../flann/include/opencv2/flann/lsh_table.h | 30 +++++-------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/modules/flann/include/opencv2/flann/lsh_index.h b/modules/flann/include/opencv2/flann/lsh_index.h index 4d4670ea50..2530a0143a 100644 --- a/modules/flann/include/opencv2/flann/lsh_index.h +++ b/modules/flann/include/opencv2/flann/lsh_index.h @@ -109,10 +109,22 @@ public: */ void buildIndex() { + std::vector indices(feature_size_ * CHAR_BIT); + tables_.resize(table_number_); for (unsigned int i = 0; i < table_number_; ++i) { + + //re-initialize the random indices table that the LshTable will use to pick its sub-dimensions + if( (indices.size() == feature_size_ * CHAR_BIT) || (indices.size() < key_size_) ) + { + indices.resize( feature_size_ * CHAR_BIT ); + for (size_t i = 0; i < feature_size_ * CHAR_BIT; ++i) + indices[i] = i; + std::random_shuffle(indices.begin(), indices.end()); + } + lsh::LshTable& table = tables_[i]; - table = lsh::LshTable(feature_size_, key_size_); + table = lsh::LshTable(feature_size_, key_size_, indices); // Add the features to the table table.add(dataset_); diff --git a/modules/flann/include/opencv2/flann/lsh_table.h b/modules/flann/include/opencv2/flann/lsh_table.h index 18fb139c91..f6e68dc767 100644 --- a/modules/flann/include/opencv2/flann/lsh_table.h +++ b/modules/flann/include/opencv2/flann/lsh_table.h @@ -153,7 +153,7 @@ public: * @param feature_size is the size of the feature (considered as a ElementType[]) * @param key_size is the number of bits that are turned on in the feature */ - LshTable(unsigned int /*feature_size*/, unsigned int /*key_size*/) + LshTable(unsigned int /*feature_size*/, unsigned int /*key_size*/, std::vector & /*indices*/) { std::cerr << "LSH is not implemented for that type" << std::endl; assert(0); @@ -341,34 +341,20 @@ private: // Specialization for unsigned char template<> -inline LshTable::LshTable(unsigned int feature_size, unsigned int subsignature_size) +inline LshTable::LshTable( unsigned int feature_size, + unsigned int subsignature_size, + std::vector & indices ) { initialize(subsignature_size); // Allocate the mask mask_ = std::vector((size_t)ceil((float)(feature_size * sizeof(char)) / (float)sizeof(size_t)), 0); - // A bit brutal but fast to code - static std::vector* indices = NULL; - - //Ensure the Nth bit will be selected only once among the different LshTables - //to avoid having two different tables with signatures sharing many dimensions/many bits - if( indices == NULL ) - { - indices = new std::vector( feature_size * CHAR_BIT ); - } - else if( indices->size() < key_size_ ) - { - indices->resize( feature_size * CHAR_BIT ); - for (size_t i = 0; i < feature_size * CHAR_BIT; ++i) { - (*indices)[i] = i; - } - std::random_shuffle(indices->begin(), indices->end()); - } - // Generate a random set of order of subsignature_size_ bits for (unsigned int i = 0; i < key_size_; ++i) { - size_t index = (*indices)[0]; - indices->erase( indices->begin() ); + //Ensure the Nth bit will be selected only once among the different LshTables + //to avoid having two different tables with signatures sharing many dimensions/many bits + size_t index = indices[0]; + indices.erase( indices.begin() ); // Set that bit in the mask size_t divisor = CHAR_BIT * sizeof(size_t); From 8d4a76925c47703ebc13d593787dacc4b69568cc Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Tue, 20 May 2014 18:03:20 +0400 Subject: [PATCH 24/44] fixed binary compatibility --- modules/contrib/doc/facerec/facerec_api.rst | 15 +- .../include/opencv2/contrib/contrib.hpp | 6 +- modules/contrib/src/facerec.cpp | 169 +++++++----------- samples/cpp/facerec_demo.cpp | 1 + 4 files changed, 76 insertions(+), 115 deletions(-) diff --git a/modules/contrib/doc/facerec/facerec_api.rst b/modules/contrib/doc/facerec/facerec_api.rst index 4c91bb7cb9..edb6edb1d5 100644 --- a/modules/contrib/doc/facerec/facerec_api.rst +++ b/modules/contrib/doc/facerec/facerec_api.rst @@ -48,13 +48,13 @@ a unified access to all face recongition algorithms in OpenCV. :: virtual void load(const FileStorage& fs) = 0; // Sets additional information as pairs label - info. - virtual void setLabelsInfo(const std::map& labelsInfo) = 0; + void setLabelsInfo(const std::map& labelsInfo); // Gets string information by label - virtual string getLabelInfo(int label) const = 0; + string getLabelInfo(const int &label); // Gets labels by string - virtual vector getLabelsByString(const string& str) = 0; + vector getLabelsByString(const string& str); }; @@ -308,7 +308,7 @@ FaceRecognizer::setLabelsInfo ----------------------------- Sets string information about labels into the model. -.. ocv:function:: void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) = 0 +.. ocv:function:: void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) Information about the label loads as a pair "label id - string info". @@ -316,7 +316,7 @@ FaceRecognizer::getLabelInfo ---------------------------- Gets string information by label. -.. ocv:function:: string FaceRecognizer::getLabelInfo(int label) const = 0 +.. ocv:function:: string FaceRecognizer::getLabelInfo(const int &label) If an unknown label id is provided or there is no label information assosiated with the specified label id the method returns an empty string. @@ -324,7 +324,7 @@ FaceRecognizer::getLabelsByString --------------------------------- Gets vector of labels by string. -.. ocv:function:: vector FaceRecognizer::getLabelsByString(const string& str) = 0 +.. ocv:function:: vector FaceRecognizer::getLabelsByString(const string& str) The function searches for the labels containing the specified substring in the associated string info. @@ -354,7 +354,6 @@ Model internal data: * ``mean`` The sample mean calculated from the training data. * ``projections`` The projections of the training data. * ``labels`` The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1. -* ``labelsInfo`` The string information about the labels. createFisherFaceRecognizer -------------------------- @@ -382,7 +381,6 @@ Model internal data: * ``mean`` The sample mean calculated from the training data. * ``projections`` The projections of the training data. * ``labels`` The labels corresponding to the projections. -* ``labelsInfo`` The string information about the labels. createLBPHFaceRecognizer @@ -412,4 +410,3 @@ Model internal data: * ``threshold`` see :ocv:func:`createLBPHFaceRecognizer`. * ``histograms`` Local Binary Patterns Histograms calculated from the given training data (empty if none was given). * ``labels`` Labels corresponding to the calculated Local Binary Patterns Histograms. -* ``labelsInfo`` The string information about the labels. diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index e6e11d816a..5684ee2422 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -949,13 +949,13 @@ namespace cv virtual void load(const FileStorage& fs) = 0; // Sets additional information as pairs label - info. - virtual void setLabelsInfo(const std::map& labelsInfo) = 0; + void setLabelsInfo(const std::map& labelsInfo); // Gets string information by label - virtual string getLabelInfo(int label) const = 0; + string getLabelInfo(const int &label); // Gets labels by string - virtual vector getLabelsByString(const string& str) = 0; + vector getLabelsByString(const string& str); }; CV_EXPORTS_W Ptr createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index ef32b5273a..170f636687 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -133,9 +133,51 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { return elems; } +// This class was introduced to avoid an addition of new virtual functions in FaceRecognizer class. +// It is safe for a binary compatibility. +class FaceRecognizerBase : public FaceRecognizer +{ +protected: + // Stored pairs "label id - string info" + std::map _labelsInfo; + +public: + // Sets additional information as pairs label - info. + virtual void setLabelsInfo(const std::map& labelsInfo); + + // Gets string information by label + virtual string getLabelInfo(int label) const; + + // Gets labels by string + virtual vector getLabelsByString(const string& str); +}; + +void FaceRecognizerBase::setLabelsInfo(const std::map& labelsInfo) +{ + _labelsInfo = labelsInfo; +} + +string FaceRecognizerBase::getLabelInfo(int label) const +{ + std::map::const_iterator iter(_labelsInfo.find(label)); + return iter != _labelsInfo.end() ? iter->second : ""; +} + +vector FaceRecognizerBase::getLabelsByString(const string& str) +{ + vector labels; + for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + { + size_t found = (it->second).find(str); + if(found != string::npos) + labels.push_back(it->first); + } + return labels; +} + // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Cognitive Neuroscience 3 (1991), 71–86. -class Eigenfaces : public FaceRecognizer +class Eigenfaces : public FaceRecognizerBase { private: int _num_components; @@ -145,7 +187,6 @@ private: Mat _eigenvectors; Mat _eigenvalues; Mat _mean; - std::map _labelsInfo; public: using FaceRecognizer::save; @@ -182,15 +223,6 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additional information as pairs label - info. - void setLabelsInfo(const std::map& labelsInfo); - - // Gets string information by label - string getLabelInfo(int label) const; - - // Gets labels by string - std::vector getLabelsByString(const string& str); - AlgorithmInfo* info() const; }; @@ -198,7 +230,7 @@ public: // faces: Recognition using class specific linear projection.". IEEE // Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997), // 711–720. -class Fisherfaces: public FaceRecognizer +class Fisherfaces: public FaceRecognizerBase { private: int _num_components; @@ -208,7 +240,6 @@ private: Mat _mean; vector _projections; Mat _labels; - std::map _labelsInfo; public: using FaceRecognizer::save; @@ -247,15 +278,6 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additional information as pairs label - info. - void setLabelsInfo(const std::map& labelsInfo); - - // Gets string information by label - string getLabelInfo(int label) const; - - // Gets labels by string - std::vector getLabelsByString(const string& str); - AlgorithmInfo* info() const; }; @@ -265,7 +287,7 @@ public: // patterns: Application to face recognition." IEEE Transactions on Pattern // Analysis and Machine Intelligence, 28(12):2037-2041. // -class LBPH : public FaceRecognizer +class LBPH : public FaceRecognizerBase { private: int _grid_x; @@ -276,14 +298,12 @@ private: vector _histograms; Mat _labels; - std::map _labelsInfo; // Computes a LBPH model with images in src and // corresponding labels in labels, possibly preserving // old model data. void train(InputArrayOfArrays src, InputArray labels, bool preserveData); - public: using FaceRecognizer::save; using FaceRecognizer::load; @@ -342,15 +362,6 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - // Sets additional information as pairs label - info. - void setLabelsInfo(const std::map& labelsInfo); - - // Gets string information by label - string getLabelInfo(int label) const; - - // Gets labels by string - std::vector getLabelsByString(const string& str); - // Getter functions. int neighbors() const { return _neighbors; } int radius() const { return _radius; } @@ -391,6 +402,27 @@ void FaceRecognizer::load(const string& filename) { fs.release(); } +void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) +{ + FaceRecognizerBase* base = dynamic_cast(this); + CV_Assert(base != 0); + base->setLabelsInfo(labelsInfo); +} + +string FaceRecognizer::getLabelInfo(const int &label) +{ + FaceRecognizerBase* base = dynamic_cast(this); + CV_Assert(base != 0); + return base->getLabelInfo(label); +} + +vector FaceRecognizer::getLabelsByString(const string& str) +{ + FaceRecognizerBase* base = dynamic_cast(this); + CV_Assert(base != 0); + return base->getLabelsByString(str); +} + //------------------------------------------------------------------------------ // Eigenfaces //------------------------------------------------------------------------------ @@ -515,29 +547,6 @@ void Eigenfaces::save(FileStorage& fs) const { fs << "]"; } -void Eigenfaces::setLabelsInfo(const std::map& labelsInfo) -{ - _labelsInfo = labelsInfo; -} - -string Eigenfaces::getLabelInfo(int label) const -{ - std::map::const_iterator iter(_labelsInfo.find(label)); - return iter != _labelsInfo.end() ? iter->second : ""; -} - -vector Eigenfaces::getLabelsByString(const string& str) -{ - vector labels; - for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) - { - size_t found = (it->second).find(str); - if(found != string::npos) - labels.push_back(it->first); - } - return labels; -} - //------------------------------------------------------------------------------ // Fisherfaces //------------------------------------------------------------------------------ @@ -675,29 +684,6 @@ void Fisherfaces::save(FileStorage& fs) const { fs << "]"; } -void Fisherfaces::setLabelsInfo(const std::map& labelsInfo) -{ - _labelsInfo = labelsInfo; -} - -string Fisherfaces::getLabelInfo(int label) const -{ - std::map::const_iterator iter(_labelsInfo.find(label)); - return iter != _labelsInfo.end() ? iter->second : ""; -} - -vector Fisherfaces::getLabelsByString(const string& str) -{ - vector labels; - for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) - { - size_t found = (it->second).find(str); - if(found != string::npos) - labels.push_back(it->first); - } - return labels; -} - //------------------------------------------------------------------------------ // LBPH //------------------------------------------------------------------------------ @@ -911,29 +897,6 @@ void LBPH::save(FileStorage& fs) const { fs << "]"; } -void LBPH::setLabelsInfo(const std::map& labelsInfo) -{ - _labelsInfo = labelsInfo; -} - -string LBPH::getLabelInfo(int label) const -{ - std::map::const_iterator iter(_labelsInfo.find(label)); - return iter != _labelsInfo.end() ? iter->second : ""; -} - -vector LBPH::getLabelsByString(const string& str) -{ - vector labels; - for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) - { - size_t found = (it->second).find(str); - if(found != string::npos) - labels.push_back(it->first); - } - return labels; -} - void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels) { this->train(_in_src, _in_labels, false); } diff --git a/samples/cpp/facerec_demo.cpp b/samples/cpp/facerec_demo.cpp index ef480c6df7..b92308e898 100644 --- a/samples/cpp/facerec_demo.cpp +++ b/samples/cpp/facerec_demo.cpp @@ -118,6 +118,7 @@ int main(int argc, const char *argv[]) { Ptr model = createEigenFaceRecognizer(); model->setLabelsInfo(labelsInfo); model->train(images, labels); + // The following line predicts the label of a given // test image: int predictedLabel = model->predict(testSample); From 2f8b5731dabb3162ca62a0e1901bb13f0ed83e4c Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Viel Date: Wed, 21 May 2014 12:27:38 +0200 Subject: [PATCH 25/44] Fix local variable shadowing --- modules/flann/include/opencv2/flann/lsh_index.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/flann/include/opencv2/flann/lsh_index.h b/modules/flann/include/opencv2/flann/lsh_index.h index 2530a0143a..23988d436a 100644 --- a/modules/flann/include/opencv2/flann/lsh_index.h +++ b/modules/flann/include/opencv2/flann/lsh_index.h @@ -118,8 +118,8 @@ public: if( (indices.size() == feature_size_ * CHAR_BIT) || (indices.size() < key_size_) ) { indices.resize( feature_size_ * CHAR_BIT ); - for (size_t i = 0; i < feature_size_ * CHAR_BIT; ++i) - indices[i] = i; + for (size_t j = 0; j < feature_size_ * CHAR_BIT; ++j) + indices[j] = j; std::random_shuffle(indices.begin(), indices.end()); } From ec99f96c6253240b10335ebcdc4018950df1bc20 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Viel Date: Wed, 21 May 2014 13:16:12 +0200 Subject: [PATCH 26/44] Add the ensureSimpleDistance() method to ensure the user the returned distance is not ^2 (the default for L2 for instance) --- modules/flann/include/opencv2/flann/dist.h | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/modules/flann/include/opencv2/flann/dist.h b/modules/flann/include/opencv2/flann/dist.h index 2afceb8893..88f9abc305 100644 --- a/modules/flann/include/opencv2/flann/dist.h +++ b/modules/flann/include/opencv2/flann/dist.h @@ -872,6 +872,66 @@ typename Distance::ResultType ensureSquareDistance( typename Distance::ResultTyp return dummy( dist ); } + +/* + * ...and a template to ensure the user that he will process the normal distance, + * and not squared distance, without loosing processing time calling sqrt(ensureSquareDistance) + * that will result in doing actually sqrt(dist*dist) for L1 distance for instance. + */ +template +struct simpleDistance +{ + typedef typename Distance::ResultType ResultType; + ResultType operator()( ResultType dist ) { return dist; } +}; + + +template +struct simpleDistance, ElementType> +{ + typedef typename L2_Simple::ResultType ResultType; + ResultType operator()( ResultType dist ) { return sqrt(dist); } +}; + +template +struct simpleDistance, ElementType> +{ + typedef typename L2::ResultType ResultType; + ResultType operator()( ResultType dist ) { return sqrt(dist); } +}; + + +template +struct simpleDistance, ElementType> +{ + typedef typename MinkowskiDistance::ResultType ResultType; + ResultType operator()( ResultType dist ) { return sqrt(dist); } +}; + +template +struct simpleDistance, ElementType> +{ + typedef typename HellingerDistance::ResultType ResultType; + ResultType operator()( ResultType dist ) { return sqrt(dist); } +}; + +template +struct simpleDistance, ElementType> +{ + typedef typename ChiSquareDistance::ResultType ResultType; + ResultType operator()( ResultType dist ) { return sqrt(dist); } +}; + + +template +typename Distance::ResultType ensureSimpleDistance( typename Distance::ResultType dist ) +{ + typedef typename Distance::ElementType ElementType; + + simpleDistance dummy; + return dummy( dist ); +} + } #endif //OPENCV_FLANN_DIST_H_ From 59c8edfd987a93f77cf862f14ab25b2d384132d7 Mon Sep 17 00:00:00 2001 From: Konstantin Matskevich Date: Wed, 28 May 2014 15:28:54 +0400 Subject: [PATCH 27/44] facerec2 --- modules/contrib/doc/facerec/facerec_api.rst | 2 +- .../include/opencv2/contrib/contrib.hpp | 20 ++++++++++ modules/contrib/src/facerec.cpp | 37 +++++-------------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/modules/contrib/doc/facerec/facerec_api.rst b/modules/contrib/doc/facerec/facerec_api.rst index edb6edb1d5..3da37bdd17 100644 --- a/modules/contrib/doc/facerec/facerec_api.rst +++ b/modules/contrib/doc/facerec/facerec_api.rst @@ -79,7 +79,7 @@ Moreover every :ocv:class:`FaceRecognizer` supports the: * **Loading/Saving** the model state from/to a given XML or YAML. -* **Setting/Getting labels info**, that is storaged as a string. +* **Setting/Getting labels info**, that is storaged as a string. String labels info is useful for keeping names of the recognized people. .. note:: When using the FaceRecognizer interface in combination with Python, please stick to Python 2. Some underlying scripts like create_csv will not work in other versions, like Python 3. diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index 5684ee2422..f74d9ca49f 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -958,6 +958,26 @@ namespace cv vector getLabelsByString(const string& str); }; + // The FaceRecognizerBase class is introduced to keep the FaceRecognizer binary backward compatibility in 2.4 + // In master setLabelInfo/getLabelInfo/getLabelsByString should be virtual and _labelsInfo should be moved to FaceRecognizer + // that allows to avoid FaceRecognizer2 in master + class FaceRecognizer2 : public FaceRecognizer + { + protected: + // Stored pairs "label id - string info" + std::map _labelsInfo; + + public: + // Sets additional information as pairs label - info. + virtual void setLabelsInfo(const std::map& labelsInfo); + + // Gets string information by label + virtual string getLabelInfo(int label) const; + + // Gets labels by string + virtual vector getLabelsByString(const string& str); + }; + CV_EXPORTS_W Ptr createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); CV_EXPORTS_W Ptr createFisherFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); CV_EXPORTS_W Ptr createLBPHFaceRecognizer(int radius=1, int neighbors=8, diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index 170f636687..f24e3ff4c4 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -133,37 +133,18 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { return elems; } -// This class was introduced to avoid an addition of new virtual functions in FaceRecognizer class. -// It is safe for a binary compatibility. -class FaceRecognizerBase : public FaceRecognizer -{ -protected: - // Stored pairs "label id - string info" - std::map _labelsInfo; - -public: - // Sets additional information as pairs label - info. - virtual void setLabelsInfo(const std::map& labelsInfo); - - // Gets string information by label - virtual string getLabelInfo(int label) const; - - // Gets labels by string - virtual vector getLabelsByString(const string& str); -}; - -void FaceRecognizerBase::setLabelsInfo(const std::map& labelsInfo) +void FaceRecognizer2::setLabelsInfo(const std::map& labelsInfo) { _labelsInfo = labelsInfo; } -string FaceRecognizerBase::getLabelInfo(int label) const +string FaceRecognizer2::getLabelInfo(int label) const { std::map::const_iterator iter(_labelsInfo.find(label)); return iter != _labelsInfo.end() ? iter->second : ""; } -vector FaceRecognizerBase::getLabelsByString(const string& str) +vector FaceRecognizer2::getLabelsByString(const string& str) { vector labels; for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) @@ -177,7 +158,7 @@ vector FaceRecognizerBase::getLabelsByString(const string& str) // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Cognitive Neuroscience 3 (1991), 71–86. -class Eigenfaces : public FaceRecognizerBase +class Eigenfaces : public FaceRecognizer2 { private: int _num_components; @@ -230,7 +211,7 @@ public: // faces: Recognition using class specific linear projection.". IEEE // Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997), // 711–720. -class Fisherfaces: public FaceRecognizerBase +class Fisherfaces: public FaceRecognizer2 { private: int _num_components; @@ -287,7 +268,7 @@ public: // patterns: Application to face recognition." IEEE Transactions on Pattern // Analysis and Machine Intelligence, 28(12):2037-2041. // -class LBPH : public FaceRecognizerBase +class LBPH : public FaceRecognizer2 { private: int _grid_x; @@ -404,21 +385,21 @@ void FaceRecognizer::load(const string& filename) { void FaceRecognizer::setLabelsInfo(const std::map& labelsInfo) { - FaceRecognizerBase* base = dynamic_cast(this); + FaceRecognizer2* base = dynamic_cast(this); CV_Assert(base != 0); base->setLabelsInfo(labelsInfo); } string FaceRecognizer::getLabelInfo(const int &label) { - FaceRecognizerBase* base = dynamic_cast(this); + FaceRecognizer2* base = dynamic_cast(this); CV_Assert(base != 0); return base->getLabelInfo(label); } vector FaceRecognizer::getLabelsByString(const string& str) { - FaceRecognizerBase* base = dynamic_cast(this); + FaceRecognizer2* base = dynamic_cast(this); CV_Assert(base != 0); return base->getLabelsByString(str); } From fc610979bb6597d9c8f986e32ed20cbde909f7b6 Mon Sep 17 00:00:00 2001 From: berak Date: Sat, 7 Jun 2014 16:34:53 +0200 Subject: [PATCH 28/44] export BOW to script wrappers --- .../include/opencv2/features2d/features2d.hpp | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/features2d/include/opencv2/features2d/features2d.hpp b/modules/features2d/include/opencv2/features2d/features2d.hpp index 7536128c71..7c04a22ea4 100644 --- a/modules/features2d/include/opencv2/features2d/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d/features2d.hpp @@ -1528,17 +1528,17 @@ CV_EXPORTS void evaluateGenericDescriptorMatcher( const Mat& img1, const Mat& im /* * Abstract base class for training of a 'bag of visual words' vocabulary from a set of descriptors */ -class CV_EXPORTS BOWTrainer +class CV_EXPORTS_W BOWTrainer { public: BOWTrainer(); virtual ~BOWTrainer(); - void add( const Mat& descriptors ); - const vector& getDescriptors() const; - int descripotorsCount() const; + CV_WRAP void add( const Mat& descriptors ); + CV_WRAP const vector& getDescriptors() const; + CV_WRAP int descripotorsCount() const; - virtual void clear(); + CV_WRAP virtual void clear(); /* * Train visual words vocabulary, that is cluster training descriptors and @@ -1547,8 +1547,8 @@ public: * * descriptors Training descriptors computed on images keypoints. */ - virtual Mat cluster() const = 0; - virtual Mat cluster( const Mat& descriptors ) const = 0; + CV_WRAP virtual Mat cluster() const = 0; + CV_WRAP virtual Mat cluster( const Mat& descriptors ) const = 0; protected: vector descriptors; @@ -1558,16 +1558,16 @@ protected: /* * This is BOWTrainer using cv::kmeans to get vocabulary. */ -class CV_EXPORTS BOWKMeansTrainer : public BOWTrainer +class CV_EXPORTS_W BOWKMeansTrainer : public BOWTrainer { public: - BOWKMeansTrainer( int clusterCount, const TermCriteria& termcrit=TermCriteria(), + CV_WRAP BOWKMeansTrainer( int clusterCount, const TermCriteria& termcrit=TermCriteria(), int attempts=3, int flags=KMEANS_PP_CENTERS ); virtual ~BOWKMeansTrainer(); // Returns trained vocabulary (i.e. cluster centers). - virtual Mat cluster() const; - virtual Mat cluster( const Mat& descriptors ) const; + CV_WRAP virtual Mat cluster() const; + CV_WRAP virtual Mat cluster( const Mat& descriptors ) const; protected: @@ -1580,21 +1580,24 @@ protected: /* * Class to compute image descriptor using bag of visual words. */ -class CV_EXPORTS BOWImgDescriptorExtractor +class CV_EXPORTS_W BOWImgDescriptorExtractor { public: - BOWImgDescriptorExtractor( const Ptr& dextractor, + CV_WRAP BOWImgDescriptorExtractor( const Ptr& dextractor, const Ptr& dmatcher ); virtual ~BOWImgDescriptorExtractor(); - void setVocabulary( const Mat& vocabulary ); - const Mat& getVocabulary() const; + CV_WRAP void setVocabulary( const Mat& vocabulary ); + CV_WRAP const Mat& getVocabulary() const; void compute( const Mat& image, vector& keypoints, Mat& imgDescriptor, vector >* pointIdxsOfClusters=0, Mat* descriptors=0 ); // compute() is not constant because DescriptorMatcher::match is not constant - int descriptorSize() const; - int descriptorType() const; + CV_WRAP_AS(compute) void compute2( const Mat& image, vector& keypoints, Mat& imgDescriptor ) + { compute(image,keypoints,imgDescriptor); } + + CV_WRAP int descriptorSize() const; + CV_WRAP int descriptorType() const; protected: Mat vocabulary; From 02b32d86d342083dbc4327300a94f0bf7cb5b48f Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 9 Jun 2014 13:26:45 +0400 Subject: [PATCH 29/44] moving FaceRecognizer2 from public header to .cpp --- .../include/opencv2/contrib/contrib.hpp | 20 --- modules/contrib/src/facerec.cpp | 117 ++++++++++-------- 2 files changed, 67 insertions(+), 70 deletions(-) diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index f74d9ca49f..5684ee2422 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -958,26 +958,6 @@ namespace cv vector getLabelsByString(const string& str); }; - // The FaceRecognizerBase class is introduced to keep the FaceRecognizer binary backward compatibility in 2.4 - // In master setLabelInfo/getLabelInfo/getLabelsByString should be virtual and _labelsInfo should be moved to FaceRecognizer - // that allows to avoid FaceRecognizer2 in master - class FaceRecognizer2 : public FaceRecognizer - { - protected: - // Stored pairs "label id - string info" - std::map _labelsInfo; - - public: - // Sets additional information as pairs label - info. - virtual void setLabelsInfo(const std::map& labelsInfo); - - // Gets string information by label - virtual string getLabelInfo(int label) const; - - // Gets labels by string - virtual vector getLabelsByString(const string& str); - }; - CV_EXPORTS_W Ptr createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); CV_EXPORTS_W Ptr createFisherFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); CV_EXPORTS_W Ptr createLBPHFaceRecognizer(int radius=1, int neighbors=8, diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index f24e3ff4c4..3f8aafbdae 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -18,41 +18,6 @@ #include "precomp.hpp" #include -struct LabelInfo -{ - LabelInfo():label(-1), value("") {} - LabelInfo(int _label, const std::string &_value): label(_label), value(_value) {} - int label; - std::string value; - void write(cv::FileStorage& fs) const - { - fs << "{" << "label" << label << "value" << value << "}"; - } - void read(const cv::FileNode& node) - { - label = (int)node["label"]; - value = (std::string)node["value"]; - } - std::ostream& operator<<(std::ostream& out) - { - out << "{ label = " << label << ", " << "value = " << value << "}"; - return out; - } -}; - -static void write(cv::FileStorage& fs, const std::string&, const LabelInfo& x) -{ - x.write(fs); -} - -static void read(const cv::FileNode& node, LabelInfo& x, const LabelInfo& default_value = LabelInfo()) -{ - if(node.empty()) - x = default_value; - else - x.read(node); -} - namespace cv { @@ -133,29 +98,81 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { return elems; } -void FaceRecognizer2::setLabelsInfo(const std::map& labelsInfo) +// The FaceRecognizer2 class is introduced to keep the FaceRecognizer binary backward compatibility in 2.4 +// In master setLabelInfo/getLabelInfo/getLabelsByString should be virtual and _labelsInfo should be moved +// to FaceRecognizer, that allows to avoid FaceRecognizer2 in master +class FaceRecognizer2 : public FaceRecognizer { - _labelsInfo = labelsInfo; -} +protected: + // Stored pairs "label id - string info" + std::map _labelsInfo; -string FaceRecognizer2::getLabelInfo(int label) const -{ - std::map::const_iterator iter(_labelsInfo.find(label)); - return iter != _labelsInfo.end() ? iter->second : ""; -} +public: + // Sets additional information as pairs label - info. + virtual void setLabelsInfo(const std::map& labelsInfo) + { + _labelsInfo = labelsInfo; + } -vector FaceRecognizer2::getLabelsByString(const string& str) + // Gets string information by label + virtual string getLabelInfo(int label) const + { + std::map::const_iterator iter(_labelsInfo.find(label)); + return iter != _labelsInfo.end() ? iter->second : ""; + } + + // Gets labels by string + virtual vector getLabelsByString(const string& str) + { + vector labels; + for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + { + size_t found = (it->second).find(str); + if(found != string::npos) + labels.push_back(it->first); + } + return labels; + } + +}; + +// Utility structure to load/save face label info (a pair of int and string) via FileStorage +struct LabelInfo { - vector labels; - for(std::map::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++) + LabelInfo():label(-1), value("") {} + LabelInfo(int _label, const std::string &_value): label(_label), value(_value) {} + int label; + std::string value; + void write(cv::FileStorage& fs) const { - size_t found = (it->second).find(str); - if(found != string::npos) - labels.push_back(it->first); + fs << "{" << "label" << label << "value" << value << "}"; } - return labels; + void read(const cv::FileNode& node) + { + label = (int)node["label"]; + value = (std::string)node["value"]; + } + std::ostream& operator<<(std::ostream& out) + { + out << "{ label = " << label << ", " << "value = " << value << "}"; + return out; + } +}; + +static void write(cv::FileStorage& fs, const std::string&, const LabelInfo& x) +{ + x.write(fs); +} + +static void read(const cv::FileNode& node, LabelInfo& x, const LabelInfo& default_value = LabelInfo()) +{ + if(node.empty()) + x = default_value; + else + x.read(node); } + // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Cognitive Neuroscience 3 (1991), 71–86. class Eigenfaces : public FaceRecognizer2 From 4d0848b3e879789b523a98fe16ef134556641ca0 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 10 Jun 2014 18:12:38 +0400 Subject: [PATCH 30/44] fix -Wmaybe-uninitialized warning (initialize pointers to NULL) --- modules/python/src2/gen.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/python/src2/gen.py b/modules/python/src2/gen.py index 40879e569f..d43a8023f2 100755 --- a/modules/python/src2/gen.py +++ b/modules/python/src2/gen.py @@ -153,6 +153,8 @@ def gen(name, args, ty, flags): ctype = remap.get(a.ty, a.ty) if a.init: init = " = %s" % a.init + elif ctype[-1] == '*': + init = ' = NULL' else: init = '' yield " %s %s%s;" % (ctype, a.nm, init) From 3500c940d47c0fa6c9d4c281824d20ecb8385a5c Mon Sep 17 00:00:00 2001 From: berak Date: Wed, 11 Jun 2014 11:50:22 +0200 Subject: [PATCH 31/44] add Bag of Words to python wrapper --- modules/features2d/include/opencv2/features2d/features2d.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/features2d/include/opencv2/features2d/features2d.hpp b/modules/features2d/include/opencv2/features2d/features2d.hpp index 7c04a22ea4..02e65315e2 100644 --- a/modules/features2d/include/opencv2/features2d/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d/features2d.hpp @@ -1593,7 +1593,7 @@ public: vector >* pointIdxsOfClusters=0, Mat* descriptors=0 ); // compute() is not constant because DescriptorMatcher::match is not constant - CV_WRAP_AS(compute) void compute2( const Mat& image, vector& keypoints, Mat& imgDescriptor ) + CV_WRAP_AS(compute) void compute2( const Mat& image, vector& keypoints, CV_OUT Mat& imgDescriptor ) { compute(image,keypoints,imgDescriptor); } CV_WRAP int descriptorSize() const; From b6e25a9fc7b6fe511be1cd6f47ca2b45bbb51658 Mon Sep 17 00:00:00 2001 From: Neo Alienson Date: Mon, 16 Jun 2014 18:48:10 +0800 Subject: [PATCH 32/44] Fix typos --- .../introduction/android_binary_package/android_dev_intro.rst | 2 +- modules/highgui/src/cap_libv4l.cpp | 4 ++-- .../src/org/opencv/samples/puzzle15/Puzzle15Activity.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/tutorials/introduction/android_binary_package/android_dev_intro.rst b/doc/tutorials/introduction/android_binary_package/android_dev_intro.rst index 9545bee286..8c470925ff 100644 --- a/doc/tutorials/introduction/android_binary_package/android_dev_intro.rst +++ b/doc/tutorials/introduction/android_binary_package/android_dev_intro.rst @@ -5,7 +5,7 @@ Introduction into Android Development ************************************* -This guide was designed to help you in learning Android development basics and seting up your +This guide was designed to help you in learning Android development basics and setting up your working environment quickly. It was written with Windows 7 in mind, though it would work with Linux (Ubuntu), Mac OS X and any other OS supported by Android SDK. diff --git a/modules/highgui/src/cap_libv4l.cpp b/modules/highgui/src/cap_libv4l.cpp index e7aa5b5dfe..a05c4b2123 100644 --- a/modules/highgui/src/cap_libv4l.cpp +++ b/modules/highgui/src/cap_libv4l.cpp @@ -158,12 +158,12 @@ the symptoms were damaged image and 'Corrupt JPEG data: premature end of data se 11th patch: Apr 13, 2010, Filipe Almeida filipe.almeida@ist.utl.pt - Tries to setup all properties first through v4l2_ioctl call. -- Allows seting up all Video4Linux properties through cvSetCaptureProperty instead of only CV_CAP_PROP_BRIGHTNESS, CV_CAP_PROP_CONTRAST, CV_CAP_PROP_SATURATION, CV_CAP_PROP_HUE, CV_CAP_PROP_GAIN and CV_CAP_PROP_EXPOSURE. +- Allows setting up all Video4Linux properties through cvSetCaptureProperty instead of only CV_CAP_PROP_BRIGHTNESS, CV_CAP_PROP_CONTRAST, CV_CAP_PROP_SATURATION, CV_CAP_PROP_HUE, CV_CAP_PROP_GAIN and CV_CAP_PROP_EXPOSURE. 12th patch: Apr 16, 2010, Filipe Almeida filipe.almeida@ist.utl.pt - CvCaptureCAM_V4L structure cleanup (no longer needs _{min,max,} variables) - Introduction of v4l2_ctrl_range - minimum and maximum allowed values for v4l controls -- Allows seting up all Video4Linux properties through cvSetCaptureProperty using input values between 0.0 and 1.0 +- Allows setting up all Video4Linux properties through cvSetCaptureProperty using input values between 0.0 and 1.0 - Gets v4l properties first through v4l2_ioctl call (ignores capture->is_v4l2_device) - cvGetCaptureProperty adjusted to support the changes - Returns device properties to initial values after device closes diff --git a/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/Puzzle15Activity.java b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/Puzzle15Activity.java index ebd34fc7e2..e82f0f9236 100644 --- a/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/Puzzle15Activity.java +++ b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/Puzzle15Activity.java @@ -56,7 +56,7 @@ public class Puzzle15Activity extends Activity implements CvCameraViewListener, super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - Log.d(TAG, "Creating and seting view"); + Log.d(TAG, "Creating and setting view"); mOpenCvCameraView = (CameraBridgeViewBase) new JavaCameraView(this, -1); setContentView(mOpenCvCameraView); mOpenCvCameraView.setCvCameraViewListener(this); From 660d7cd3aeb06c4eba5d498ab3b2ec069ae5326c Mon Sep 17 00:00:00 2001 From: Daniel Angelov Date: Mon, 16 Jun 2014 13:05:17 +0100 Subject: [PATCH 33/44] Updated findHomography docs branch 2.4 Updated the documents to give warning to the users of `findHomography` that the function may return an empty matrix in some cases. The user must take care of checking that. --- .../calib3d/doc/camera_calibration_and_3d_reconstruction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst index 8afc5ce39b..d616dfdea2 100644 --- a/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst +++ b/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.rst @@ -746,7 +746,7 @@ is minimized. If the parameter ``method`` is set to the default value 0, the fun uses all the point pairs to compute an initial homography estimate with a simple least-squares scheme. However, if not all of the point pairs ( -:math:`srcPoints_i`,:math:`dstPoints_i` ) fit the rigid perspective transformation (that is, there +:math:`srcPoints_i`, :math:`dstPoints_i` ) fit the rigid perspective transformation (that is, there are some outliers), this initial estimate will be poor. In this case, you can use one of the two robust methods. Both methods, ``RANSAC`` and ``LMeDS`` , try many different random subsets of the corresponding point pairs (of four pairs each), estimate @@ -769,7 +769,7 @@ if there are no outliers and the noise is rather small, use the default method ( The function is used to find initial intrinsic and extrinsic matrices. Homography matrix is determined up to a scale. Thus, it is normalized so that -:math:`h_{33}=1` . +:math:`h_{33}=1`. Note that whenever an H matrix cannot be estimated, an empty one will be returned. .. seealso:: From 05e0b3b7e6bf24f1f245ad9a54f1ce71c8784ff7 Mon Sep 17 00:00:00 2001 From: Marc Rollins Date: Thu, 19 Jun 2014 14:14:10 -0700 Subject: [PATCH 34/44] Fixing build error when using post-increment operator. --- modules/core/include/opencv2/core/mat.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 45c25900c3..8a3167a032 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -2564,7 +2564,7 @@ SparseMatConstIterator_<_Tp>::operator ++() template inline SparseMatConstIterator_<_Tp> SparseMatConstIterator_<_Tp>::operator ++(int) { - SparseMatConstIterator it = *this; + SparseMatConstIterator_<_Tp> it = *this; SparseMatConstIterator::operator ++(); return it; } @@ -2608,7 +2608,7 @@ SparseMatIterator_<_Tp>::operator ++() template inline SparseMatIterator_<_Tp> SparseMatIterator_<_Tp>::operator ++(int) { - SparseMatIterator it = *this; + SparseMatIterator_<_Tp> it = *this; SparseMatConstIterator::operator ++(); return it; } From 84bb77e9140e6d566a689566548fc01c6b5d8e55 Mon Sep 17 00:00:00 2001 From: Ilya Krylov Date: Mon, 23 Jun 2014 17:06:40 +0400 Subject: [PATCH 35/44] Fixed android and windows x64 build issues --- modules/calib3d/src/fisheye.cpp | 74 +++++++++++++-------------- modules/calib3d/test/test_fisheye.cpp | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 66cf589564..77a0e84288 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -309,47 +309,47 @@ void cv::fisheye::distortPoints(InputArray undistorted, OutputArray distorted, I ////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 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); 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_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) + if (K.depth() == CV_32F) { - Matx33f camMat = _K.getMat(); + 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(); + 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(); + 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) + if (!R.empty() && R.total() * R.channels() == 3) { cv::Vec3d rvec; - _R.getMat().convertTo(rvec, CV_64F); + 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); + else if (!R.empty() && R.size() == Size(3, 3)) + R.getMat().convertTo(RR, CV_64F); - if(!_P.empty()) + if(!P.empty()) { - cv::Matx33d P; - _P.getMat().colRange(0, 3).convertTo(P, CV_64F); - RR = P * RR; + cv::Matx33d PP; + P.getMat().colRange(0, 3).convertTo(PP, CV_64F); + RR = PP * RR; } // start undistorting @@ -398,52 +398,52 @@ void cv::fisheye::undistortPoints( InputArray distorted, OutputArray undistorted ////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 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 ); 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_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) + if (K.depth() == CV_32F) { - Matx33f camMat = _K.getMat(); + 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(); + 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(); + 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::Matx33d RR = cv::Matx33d::eye(); + if (!R.empty() && R.total() * R.channels() == 3) { cv::Vec3d rvec; - _R.getMat().convertTo(rvec, CV_64F); - R = Affine3d(rvec).rotation(); + R.getMat().convertTo(rvec, CV_64F); + RR = Affine3d(rvec).rotation(); } - else if (!_R.empty() && _R.size() == Size(3, 3)) - _R.getMat().convertTo(R, CV_64F); + else if (!R.empty() && R.size() == Size(3, 3)) + R.getMat().convertTo(RR, CV_64F); - cv::Matx33d P = cv::Matx33d::eye(); - if (!_P.empty()) - _P.getMat().colRange(0, 3).convertTo(P, CV_64F); + cv::Matx33d PP = cv::Matx33d::eye(); + if (!P.empty()) + P.getMat().colRange(0, 3).convertTo(PP, CV_64F); - cv::Matx33d iR = (P * R).inv(cv::DECOMP_SVD); + cv::Matx33d iR = (PP * RR).inv(cv::DECOMP_SVD); for( int i = 0; i < size.height; ++i) { diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 2749a1a6e3..774364df79 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -149,7 +149,7 @@ TEST_F(fisheyeTest, jacobians) cv::Mat k(4, 1, CV_64F); double alpha; - cv::RNG& r = cv::theRNG(); + cv::RNG r; r.fill(X, cv::RNG::NORMAL, 2, 1); X = cv::abs(X) * 10; From 98421e5970819c652a940fd28937c205ca27dba7 Mon Sep 17 00:00:00 2001 From: Nisarg Thakkar Date: Mon, 23 Jun 2014 20:15:23 +0530 Subject: [PATCH 36/44] Fix for Bug#3757: All dimension values are 0 after release is called --- modules/core/include/opencv2/core/mat.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 45c25900c3..90e7de099a 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -366,7 +366,8 @@ inline void Mat::release() if( refcount && CV_XADD(refcount, -1) == 1 ) deallocate(); data = datastart = dataend = datalimit = 0; - size.p[0] = 0; + for(int i = 0; i < dims; i++) + size.p[i] = 0; refcount = 0; } From f45da9866a94dbf52f5fb955bea67fce913ca515 Mon Sep 17 00:00:00 2001 From: Jasper Date: Tue, 24 Jun 2014 11:52:56 +0100 Subject: [PATCH 37/44] Fix for VTK6.2 issue. --- modules/viz/src/vtk/vtkCocoaInteractorFix.mm | 235 +++++++++++++------ 1 file changed, 167 insertions(+), 68 deletions(-) diff --git a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm index dad41b073e..481baf96ba 100644 --- a/modules/viz/src/vtk/vtkCocoaInteractorFix.mm +++ b/modules/viz/src/vtk/vtkCocoaInteractorFix.mm @@ -1,48 +1,54 @@ /*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) 2013, OpenCV Foundation, 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. -// -// Authors: -// * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com -// -// This workaround code was taken from PCL library(www.pointclouds.org) -// -//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) 2013, OpenCV Foundation, 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. + // + // Authors: + // * Anatoly Baksheev, Itseez Inc. myname.mysurname <> mycompany.com + // + // This workaround code was taken from PCL library(www.pointclouds.org) + // + // Modified by Jasper Shemilt to work with VTK 6.2 + // The fix was needed because GetCocoaServer has been moved from + // vtkCocoaRenderWindowInteractor to vtkCocoaRenderWindow in VTK 6.2. + // This alteration to VTK happened almost a year ago according to the gitHub + // commit a3e9fc9. + // + //M*/ #import #include @@ -118,14 +124,14 @@ [application stop:application]; NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined - location:NSMakePoint(0.0,0.0) - modifierFlags:0 - timestamp:0 - windowNumber:-1 - context:nil - subtype:0 - data1:0 - data2:0]; + location:NSMakePoint(0.0,0.0) + modifierFlags:0 + timestamp:0 + windowNumber:-1 + context:nil + subtype:0 + data1:0 + data2:0]; [application postEvent:event atStart:YES]; } @@ -154,30 +160,121 @@ //---------------------------------------------------------------------------- +#if VTK_MAJOR_VERSION >= 6 && VTK_MINOR_VERSION >=2 + namespace cv { namespace viz + { + class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor + { + public: + static vtkCocoaRenderWindowInteractorFix *New (); + vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) + + virtual void Start (); + virtual void TerminateApp (); + + protected: + vtkCocoaRenderWindowInteractorFix () {} + ~vtkCocoaRenderWindowInteractorFix () {} + + private: + vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + }; + + vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) + + vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); + + class vtkCocoaRenderWindowFix : public vtkCocoaRenderWindow + { + public: + static vtkCocoaRenderWindowFix *New (); + vtkTypeMacro ( vtkCocoaRenderWindowFix, vtkCocoaRenderWindow) + + virtual vtkCocoaServerFix * GetCocoaServer (); + virtual void SetCocoaServer (void* ); + + protected: + vtkCocoaRenderWindowFix () {} + ~vtkCocoaRenderWindowFix () {} + + private: + vtkCocoaRenderWindowFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + void operator = (const vtkCocoaRenderWindowFix&); // Not implemented. + }; + + vtkStandardNewMacro (vtkCocoaRenderWindowFix) + + vtkSmartPointer vtkCocoaRenderWindowNew(); + }} + +vtkCocoaServerFix * cv::viz::vtkCocoaRenderWindowFix::GetCocoaServer () +{ + return reinterpret_cast (this->GetCocoaServer ()); +} + +void cv::viz::vtkCocoaRenderWindowFix::SetCocoaServer (void* server) { - class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor + this->SetCocoaServer (server); +} + +void cv::viz::vtkCocoaRenderWindowInteractorFix::Start () +{ + vtkCocoaRenderWindowFix* renWin = vtkCocoaRenderWindowFix::SafeDownCast(this->GetRenderWindow ()); + if (renWin != NULL) { - public: - static vtkCocoaRenderWindowInteractorFix *New (); - vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) + vtkCocoaServerFix *server = reinterpret_cast (renWin->GetCocoaServer ()); + if (!renWin->GetCocoaServer ()) + { + server = [vtkCocoaServerFix cocoaServerWithRenderWindow:renWin]; + renWin->SetCocoaServer (reinterpret_cast (server)); + } + + [server start]; + } +} + +void cv::viz::vtkCocoaRenderWindowInteractorFix::TerminateApp () +{ + vtkCocoaRenderWindowFix *renWin = vtkCocoaRenderWindowFix::SafeDownCast (this->RenderWindow); + if (renWin) + { + vtkCocoaServerFix *server = reinterpret_cast (renWin->GetCocoaServer ()); + [server stop]; + } +} + +vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteractorNew() +{ + return vtkSmartPointer::New(); +} + +#else +namespace cv { namespace viz + { + class vtkCocoaRenderWindowInteractorFix : public vtkCocoaRenderWindowInteractor + { + public: + static vtkCocoaRenderWindowInteractorFix *New (); + vtkTypeMacro (vtkCocoaRenderWindowInteractorFix, vtkCocoaRenderWindowInteractor) - virtual void Start (); - virtual void TerminateApp (); + virtual void Start (); + virtual void TerminateApp (); - protected: - vtkCocoaRenderWindowInteractorFix () {} - ~vtkCocoaRenderWindowInteractorFix () {} + protected: + vtkCocoaRenderWindowInteractorFix () {} + ~vtkCocoaRenderWindowInteractorFix () {} - private: - vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. - }; + private: + vtkCocoaRenderWindowInteractorFix (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + void operator = (const vtkCocoaRenderWindowInteractorFix&); // Not implemented. + }; - vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) + vtkStandardNewMacro (vtkCocoaRenderWindowInteractorFix) - vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); -}} + vtkSmartPointer vtkCocoaRenderWindowInteractorNew(); + }} void cv::viz::vtkCocoaRenderWindowInteractorFix::Start () { @@ -209,3 +306,5 @@ vtkSmartPointer cv::viz::vtkCocoaRenderWindowInteract { return vtkSmartPointer::New(); } + +#endif From 3a8af7d691e28ad4052a68fb2c490d3532fa4735 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 30 Jun 2014 16:03:07 +0400 Subject: [PATCH 38/44] fix python tests --- modules/python/test/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/python/test/test.py b/modules/python/test/test.py index 10f32260b4..212fdbcd99 100755 --- a/modules/python/test/test.py +++ b/modules/python/test/test.py @@ -73,7 +73,7 @@ class OpenCVTests(unittest.TestCase): def get_sample(self, filename, iscolor = cv.CV_LOAD_IMAGE_COLOR): if not filename in self.image_cache: - filedata = urllib.urlopen("https://raw.github.com/Itseez/opencv/master/" + filename).read() + filedata = urllib.urlopen("https://raw.github.com/Itseez/opencv/2.4/" + filename).read() imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1) cv.SetData(imagefiledata, filedata, len(filedata)) self.image_cache[filename] = cv.DecodeImageM(imagefiledata, iscolor) From 5c8cd76893b6035748ae406c6409d5a52cf8bcf9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 30 Jun 2014 16:03:20 +0400 Subject: [PATCH 39/44] fix bug with invalid signature size (should not be less than signatureLength()) --- modules/highgui/src/loadsave.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/highgui/src/loadsave.cpp b/modules/highgui/src/loadsave.cpp index 6658b1335e..81c708acdd 100644 --- a/modules/highgui/src/loadsave.cpp +++ b/modules/highgui/src/loadsave.cpp @@ -137,9 +137,9 @@ static ImageDecoder findDecoder( const Mat& buf ) maxlen = std::max(maxlen, len); } + string signature(maxlen, ' '); size_t bufSize = buf.rows*buf.cols*buf.elemSize(); maxlen = std::min(maxlen, bufSize); - string signature(maxlen, ' '); memcpy( &signature[0], buf.data, maxlen ); for( i = 0; i < codecs.decoders.size(); i++ ) From ebb0255e195e28033ca734783b38fc26e4f93218 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 30 Jun 2014 16:12:04 +0400 Subject: [PATCH 40/44] Remove a couple of useless casts in core headers This helps users who compile their code with -Wuseless-cast. --- modules/core/include/opencv2/core/core_c.h | 2 +- modules/core/include/opencv2/core/types_c.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/core_c.h b/modules/core/include/opencv2/core/core_c.h index 38abfc409b..3613cf5337 100644 --- a/modules/core/include/opencv2/core/core_c.h +++ b/modules/core/include/opencv2/core/core_c.h @@ -1107,7 +1107,7 @@ CV_INLINE CvSetElem* cvSetNew( CvSet* set_header ) set_header->active_count++; } else - cvSetAdd( set_header, NULL, (CvSetElem**)&elem ); + cvSetAdd( set_header, NULL, &elem ); return elem; } diff --git a/modules/core/include/opencv2/core/types_c.h b/modules/core/include/opencv2/core/types_c.h index 99ac0d2575..989b799fdf 100644 --- a/modules/core/include/opencv2/core/types_c.h +++ b/modules/core/include/opencv2/core/types_c.h @@ -790,7 +790,7 @@ CV_INLINE void cvmSet( CvMat* mat, int row, int col, double value ) else { assert( type == CV_64FC1 ); - ((double*)(void*)(mat->data.ptr + (size_t)mat->step*row))[col] = (double)value; + ((double*)(void*)(mat->data.ptr + (size_t)mat->step*row))[col] = value; } } From 95550c25828891a5de6ff7c2ac295f20cdc7e915 Mon Sep 17 00:00:00 2001 From: Mike Maraya Date: Mon, 30 Jun 2014 22:17:52 -0400 Subject: [PATCH 41/44] test.py: Check if camera_calibration.tar.gz file exists before downloading it, opencv bug #3782 --- modules/python/test/test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/python/test/test.py b/modules/python/test/test.py index 10f32260b4..c9e7c6d695 100755 --- a/modules/python/test/test.py +++ b/modules/python/test/test.py @@ -1646,7 +1646,8 @@ class AreaTests(OpenCVTests): cv.SetData(imagefiledata, filedata, len(filedata)) return cv.DecodeImageM(imagefiledata) - urllib.urlretrieve("http://docs.opencv.org/data/camera_calibration.tar.gz", "camera_calibration.tar.gz") + if (not os.path.isfile("camera_calibration.tar.gz")): + urllib.urlretrieve("http://docs.opencv.org/data/camera_calibration.tar.gz", "camera_calibration.tar.gz") tf = tarfile.open("camera_calibration.tar.gz") num_x_ints = 8 From 070be56e148c6caf85ffaab415040fc194cfcbff Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Tue, 1 Jul 2014 14:31:25 +0400 Subject: [PATCH 42/44] fixed warnings --- modules/highgui/src/cap_qtkit.mm | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/highgui/src/cap_qtkit.mm b/modules/highgui/src/cap_qtkit.mm index 6b915977fe..580e8a0406 100644 --- a/modules/highgui/src/cap_qtkit.mm +++ b/modules/highgui/src/cap_qtkit.mm @@ -450,13 +450,12 @@ double CvCaptureCAM::getProperty(int property_id){ QTFormatDescription* format = [[connections objectAtIndex:0] formatDescription]; NSSize s1 = [[format attributeForKey:QTFormatDescriptionVideoCleanApertureDisplaySizeAttribute] sizeValue]; - int width=s1.width, height=s1.height; switch (property_id) { case CV_CAP_PROP_FRAME_WIDTH: - retval = width; + retval = s1.width; break; case CV_CAP_PROP_FRAME_HEIGHT: - retval = height; + retval = s1.height; break; default: retval = 0; @@ -1013,22 +1012,22 @@ bool CvVideoWriter_QT::writeFrame(const IplImage* image) { cvCvtColor(image, argbimage, CV_BGR2BGRA); - unsigned char* imagedata = (unsigned char*)argbimage->imageData; + unsigned char* imagedata_ = (unsigned char*)argbimage->imageData; //BGRA --> ARGB for (int j = 0; j < argbimage->height; j++) { int rowstart = argbimage->widthStep * j; for (int i = rowstart; i < rowstart+argbimage->widthStep; i+=4) { - unsigned char temp = imagedata[i]; - imagedata[i] = 255; - imagedata[i+3] = temp; - temp = imagedata[i+2]; - imagedata[i+2] = imagedata[i+1]; - imagedata[i+1] = temp; + unsigned char temp = imagedata_[i]; + imagedata_[i] = 255; + imagedata_[i+3] = temp; + temp = imagedata_[i+2]; + imagedata_[i+2] = imagedata_[i+1]; + imagedata_[i+1] = temp; } } - NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&imagedata + NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&imagedata_ pixelsWide:movieSize.width pixelsHigh:movieSize.height bitsPerSample:8 From 43e4946cca7b1cc086dbe2a1c1f3f3c40c25d2cf Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Tue, 1 Jul 2014 14:53:39 +0400 Subject: [PATCH 43/44] fix for fisheye --- modules/calib3d/test/test_fisheye.cpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/modules/calib3d/test/test_fisheye.cpp b/modules/calib3d/test/test_fisheye.cpp index 774364df79..1ea47ab4e1 100644 --- a/modules/calib3d/test/test_fisheye.cpp +++ b/modules/calib3d/test/test_fisheye.cpp @@ -60,8 +60,6 @@ protected: protected: std::string combine(const std::string& _item1, const std::string& _item2); - std::string combine_format(const std::string& item1, const std::string& item2, ...); - cv::Mat mergeRectification(const cv::Mat& l, const cv::Mat& r); }; @@ -427,10 +425,10 @@ TEST_F(fisheyeTest, rectify) cv::Mat rectification = mergeRectification(lundist, rundist); - cv::Mat correct = cv::imread(combine_format(datasets_repository_path, "rectification_AB_%03d.png", i)); + cv::Mat correct = cv::imread(combine(datasets_repository_path, cv::format("rectification_AB_%03d.png", i))); if (correct.empty()) - cv::imwrite(combine_format(datasets_repository_path, "rectification_AB_%03d.png", i), rectification); + cv::imwrite(combine(datasets_repository_path, cv::format("rectification_AB_%03d.png", i)), rectification); else EXPECT_MAT_NEAR(correct, rectification, 1e-10); } @@ -599,17 +597,6 @@ 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 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); -} - cv::Mat fisheyeTest::mergeRectification(const cv::Mat& l, const cv::Mat& r) { CV_Assert(l.type() == r.type() && l.size() == r.size()); From b1ac35e14a8ddfcc184db83c52a9bd928e0310ef Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 1 Jul 2014 20:02:02 +0400 Subject: [PATCH 44/44] ocl: fix mac and superres test --- .../ocl/include/opencv2/ocl/cl_runtime/cl_runtime.hpp | 11 +++++++++++ modules/superres/test/test_superres.cpp | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/modules/ocl/include/opencv2/ocl/cl_runtime/cl_runtime.hpp b/modules/ocl/include/opencv2/ocl/cl_runtime/cl_runtime.hpp index 86e7ebcc39..5472e0616f 100644 --- a/modules/ocl/include/opencv2/ocl/cl_runtime/cl_runtime.hpp +++ b/modules/ocl/include/opencv2/ocl/cl_runtime/cl_runtime.hpp @@ -6,6 +6,17 @@ #if defined(HAVE_OPENCL_STATIC) #if defined __APPLE__ +// APPLE ignores CL_USE_DEPRECATED_OPENCL_1_1_APIS so use this hack: +#include +#ifdef CL_EXT_PREFIX__VERSION_1_1_DEPRECATED +#undef CL_EXT_PREFIX__VERSION_1_1_DEPRECATED +#define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED +#endif +#ifdef CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED +#undef CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED +#define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED +#endif + #include #else #include diff --git a/modules/superres/test/test_superres.cpp b/modules/superres/test/test_superres.cpp index 5cb078f77c..14d8eaf35a 100644 --- a/modules/superres/test/test_superres.cpp +++ b/modules/superres/test/test_superres.cpp @@ -278,6 +278,16 @@ TEST_F(SuperResolution, BTVL1_GPU) #if defined(HAVE_OPENCV_OCL) && defined(HAVE_OPENCL) TEST_F(SuperResolution, BTVL1_OCL) { + try + { + const cv::ocl::DeviceInfo& dev = cv::ocl::Context::getContext()->getDeviceInfo(); + std::cout << "Device name:" << dev.deviceName << std::endl; + } + catch (...) + { + std::cout << "Device name: N/A" << std::endl; + return; // skip test + } RunTest(cv::superres::createSuperResolution_BTVL1_OCL()); } #endif