diff --git a/corelib/include/rtabmap/core/Parameters.h b/corelib/include/rtabmap/core/Parameters.h index cf516784..194bcc99 100644 --- a/corelib/include/rtabmap/core/Parameters.h +++ b/corelib/include/rtabmap/core/Parameters.h @@ -320,18 +320,18 @@ class RTABMAP_EXP Parameters RTABMAP_PARAM(Odom, FeatureType, int, 6, "0=SURF 1=SIFT 2=ORB 3=FAST/FREAK 4=FAST/BRIEF 5=GFTT/FREAK 6=GFTT/BRIEF 7=BRISK."); RTABMAP_PARAM(Odom, EstimationType, int, 0, "Motion estimation approach: 0:3D->3D, 1:3D->2D (PnP)"); RTABMAP_PARAM(Odom, MaxFeatures, int, 1000, "0 no limits."); - RTABMAP_PARAM(Odom, InlierDistance, float, 0.02, "Maximum distance for visual word correspondences."); + RTABMAP_PARAM(Odom, InlierDistance, float, 0.1, "Maximum distance for visual word correspondences. Used by 3D->3D estimation approach."); RTABMAP_PARAM(Odom, MinInliers, int, 20, "Minimum visual word correspondences to compute geometry transform."); RTABMAP_PARAM(Odom, Iterations, int, 100, "Maximum iterations to compute the transform from visual words."); - RTABMAP_PARAM(Odom, RefineIterations, int, 5, "Number of iterations used to refine the transformation found by RANSAC. 0 means that the transformation is not refined."); - RTABMAP_PARAM(Odom, MaxDepth, float, 4.0, "Max depth of the words (0 means no limit)."); + RTABMAP_PARAM(Odom, RefineIterations, int, 5, "Number of iterations used to refine the transformation found by RANSAC. 0 means that the transformation is not refined."); + RTABMAP_PARAM(Odom, MaxDepth, float, 0, "Max depth of the words (0 means no limit)."); RTABMAP_PARAM(Odom, ResetCountdown, int, 0, "Automatically reset odometry after X consecutive images on which odometry cannot be computed (value=0 disables auto-reset)."); RTABMAP_PARAM_STR(Odom, RoiRatios, "0.0 0.0 0.0 0.0", "Region of interest ratios [left, right, top, bottom]."); RTABMAP_PARAM(Odom, Force2D, bool, false, "Force 2D transform (3Dof: x,y and yaw)."); RTABMAP_PARAM(Odom, Holonomic, bool, true, "If the robot is holonomic (strafing commands can be issued). If not, y value will be estimated from x and yaw values (y=x*tan(yaw))."); RTABMAP_PARAM(Odom, FillInfoData, bool, true, "Fill info with data (inliers/outliers features)."); RTABMAP_PARAM(Odom, ImageBufferSize, unsigned int, 1, "Data buffer size (0 min inf)."); - RTABMAP_PARAM(Odom, VarianceFromInliersCount, bool, false, "Set variance as the inverse of the number of inliers. Otherwise, the variance is computed as the average 3D position error of the inliers."); + RTABMAP_PARAM(Odom, VarianceFromInliersCount, bool, true, "Set variance as the inverse of the number of inliers. Otherwise, the variance is computed as the average 3D position error of the inliers."); RTABMAP_PARAM(Odom, PnPReprojError, double, 5.0, "PnP reprojection error."); RTABMAP_PARAM(Odom, PnPFlags, int, 1, "PnP flags: 0=Iterative, 1=EPNP, 2=P3P"); RTABMAP_PARAM(Odom, ParticleFiltering, bool, false, "Particle filtering to smooth the odometry trajectory."); @@ -371,7 +371,7 @@ class RTABMAP_EXP Parameters RTABMAP_PARAM(LccBow, EstimationType, int, 0, "Motion estimation approach: 0:3D->3D, 1:3D->2D (PnP), 2:2D->2D (Epipolar Geometry)"); RTABMAP_PARAM(LccBow, MinInliers, int, 10, "Minimum visual word correspondences to compute geometry transform."); - RTABMAP_PARAM(LccBow, InlierDistance, float, 0.05, "Maximum distance for visual word correspondences."); + RTABMAP_PARAM(LccBow, InlierDistance, float, 0.1, "Maximum distance for visual word correspondences. Used by 3D->3D estimation approach."); RTABMAP_PARAM(LccBow, Iterations, int, 100, "Maximum iterations to compute the transform from visual words."); RTABMAP_PARAM(LccBow, RefineIterations, int, 10, "Number of iterations used to refine the transformation found by RANSAC. 0 means that the transformation is not refined."); RTABMAP_PARAM(LccBow, Force2D, bool, false, "Force 2D transform (3Dof: x,y and yaw)."); diff --git a/corelib/src/util3d_motion_estimation.cpp b/corelib/src/util3d_motion_estimation.cpp index 7f3f939a..50638e6b 100644 --- a/corelib/src/util3d_motion_estimation.cpp +++ b/corelib/src/util3d_motion_estimation.cpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap/utilite/UStl.h" #include "rtabmap/utilite/UMath.h" +#include "rtabmap/utilite/ULogger.h" #include "rtabmap/core/util3d_transforms.h" #include "rtabmap/core/util3d_registration.h" #include "rtabmap/core/util3d_correspondences.h" @@ -39,6 +40,14 @@ namespace rtabmap namespace util3d { +#if CV_MAJOR_VERSION >= 3 +void solvePnPRansac(cv::InputArray _opoints, cv::InputArray _ipoints, + cv::InputArray _cameraMatrix, cv::InputArray _distCoeffs, + cv::OutputArray _rvec, cv::OutputArray _tvec, bool useExtrinsicGuess, + int iterationsCount, float reprojectionError, int minInliersCount, + cv::OutputArray _inliers, int flags); +#endif + Transform estimateMotion3DTo2D( const std::map & words3A, const std::map & words2B, @@ -100,7 +109,11 @@ Transform estimateMotion3DTo2D( cv::Mat tvec = (cv::Mat_(1,3) << (double)guessCameraFrame.x(), (double)guessCameraFrame.y(), (double)guessCameraFrame.z()); +#if CV_MAJOR_VERSION >= 3 + solvePnPRansac( +#else cv::solvePnPRansac( +#endif objectPoints, imagePoints, K, @@ -110,11 +123,7 @@ Transform estimateMotion3DTo2D( true, iterations, reprojError, -#if CV_MAJOR_VERSION < 3 0, // min inliers -#else - 0.99, // confidence -#endif inliers, flagsPnP); @@ -234,6 +243,297 @@ Transform estimateMotion3DTo3D( return transform; } +// Don't know why, but the RANSAC implementation in OpenCV 3 gives me far +// more wrong results than the 2.4x implementation. +// Here is a copy of the RANSAC implementation from OpenCV 2.4.x version +#if CV_MAJOR_VERSION >= 3 +namespace pnpransac +{ + const int MIN_POINTS_COUNT = 4; + + static void project3dPoints(const cv::Mat& points, const cv::Mat& rvec, const cv::Mat& tvec, cv::Mat& modif_points) + { + modif_points.create(1, points.cols, CV_32FC3); + cv::Mat R(3, 3, CV_64FC1); + cv::Rodrigues(rvec, R); + cv::Mat transformation(3, 4, CV_64F); + cv::Mat r = transformation.colRange(0, 3); + R.copyTo(r); + cv::Mat t = transformation.colRange(3, 4); + tvec.copyTo(t); + transform(points, modif_points, transformation); + } + + struct CameraParameters + { + void init(cv::Mat _intrinsics, cv::Mat _distCoeffs) + { + _intrinsics.copyTo(intrinsics); + _distCoeffs.copyTo(distortion); + } + + cv::Mat intrinsics; + cv::Mat distortion; + }; + + struct Parameters + { + int iterationsCount; + float reprojectionError; + int minInliersCount; + bool useExtrinsicGuess; + int flags; + CameraParameters camera; + }; + + template + static void pnpTask(const int curIndex, const std::vector& pointsMask, const cv::Mat& objectPoints, const cv::Mat& imagePoints, + const Parameters& params, std::vector& inliers, int& bestIndex, cv::Mat& rvec, cv::Mat& tvec, + const cv::Mat& rvecInit, const cv::Mat& tvecInit, cv::Mutex& resultsMutex) + { + cv::Mat modelObjectPoints(1, MIN_POINTS_COUNT, CV_MAKETYPE(cv::DataDepth::value, 3)); + cv::Mat modelImagePoints(1, MIN_POINTS_COUNT, CV_MAKETYPE(cv::DataDepth::value, 2)); + for (int i = 0, colIndex = 0; i < (int)pointsMask.size(); i++) + { + if (pointsMask[i]) + { + cv::Mat colModelImagePoints = modelImagePoints(cv::Rect(colIndex, 0, 1, 1)); + imagePoints.col(i).copyTo(colModelImagePoints); + cv::Mat colModelObjectPoints = modelObjectPoints(cv::Rect(colIndex, 0, 1, 1)); + objectPoints.col(i).copyTo(colModelObjectPoints); + colIndex = colIndex+1; + } + } + + //filter same 3d points, hang in solvePnP + double eps = 1e-10; + int num_same_points = 0; + for (int i = 0; i < MIN_POINTS_COUNT; i++) + for (int j = i + 1; j < MIN_POINTS_COUNT; j++) + { + if (norm(modelObjectPoints.at >(0, i) - modelObjectPoints.at >(0, j)) < eps) + num_same_points++; + } + if (num_same_points > 0) + return; + + cv::Mat localRvec, localTvec; + rvecInit.copyTo(localRvec); + tvecInit.copyTo(localTvec); + + // OpenCV 3 + cv::solvePnP( + modelObjectPoints, + modelImagePoints, + params.camera.intrinsics, + params.camera.distortion, + localRvec, + localTvec, + params.useExtrinsicGuess, + params.flags); + + + std::vector > projected_points; + projected_points.resize(objectPoints.cols); + projectPoints(objectPoints, localRvec, localTvec, params.camera.intrinsics, params.camera.distortion, projected_points); + + cv::Mat rotatedPoints; + project3dPoints(objectPoints, localRvec, localTvec, rotatedPoints); + + std::vector localInliers; + for (int i = 0; i < objectPoints.cols; i++) + { + //Although p is a 2D point it needs the same type as the object points to enable the norm calculation + cv::Point_ p((OpointType)imagePoints.at >(0, i)[0], + (OpointType)imagePoints.at >(0, i)[1]); + if ((norm(p - projected_points[i]) < params.reprojectionError) + && (rotatedPoints.at >(0, i)[2] > 0)) //hack + { + localInliers.push_back(i); + } + } + + resultsMutex.lock(); + if ( (localInliers.size() > inliers.size()) || (localInliers.size() == inliers.size() && curIndex > bestIndex)) + { + inliers.clear(); + inliers.resize(localInliers.size()); + memcpy(&inliers[0], &localInliers[0], sizeof(int) * localInliers.size()); + localRvec.copyTo(rvec); + localTvec.copyTo(tvec); + bestIndex = curIndex; + } + resultsMutex.unlock(); + } + + static void pnpTask(const int curIndex, const std::vector& pointsMask, const cv::Mat& objectPoints, const cv::Mat& imagePoints, + const Parameters& params, std::vector& inliers, int& bestIndex, cv::Mat& rvec, cv::Mat& tvec, + const cv::Mat& rvecInit, const cv::Mat& tvecInit, cv::Mutex& resultsMutex) + { + CV_Assert(objectPoints.depth() == CV_64F || objectPoints.depth() == CV_32F); + CV_Assert(imagePoints.depth() == CV_64F || imagePoints.depth() == CV_32F); + const bool objectDoublePrecision = objectPoints.depth() == CV_64F; + const bool imageDoublePrecision = imagePoints.depth() == CV_64F; + if(objectDoublePrecision) + { + if(imageDoublePrecision) + pnpTask(curIndex, pointsMask, objectPoints, imagePoints, params, inliers, bestIndex, rvec, tvec, rvecInit, tvecInit, resultsMutex); + else + pnpTask(curIndex, pointsMask, objectPoints, imagePoints, params, inliers, bestIndex, rvec, tvec, rvecInit, tvecInit, resultsMutex); + } + else + { + if(imageDoublePrecision) + pnpTask(curIndex, pointsMask, objectPoints, imagePoints, params, inliers, bestIndex, rvec, tvec, rvecInit, tvecInit, resultsMutex); + else + pnpTask(curIndex, pointsMask, objectPoints, imagePoints, params, inliers, bestIndex, rvec, tvec, rvecInit, tvecInit, resultsMutex); + } + } + + // TBB removed + class PnPSolver + { + public: + void operator()(int begin, int end) const + { + std::vector pointsMask(objectPoints.cols, 0); + for( int i=begin; i!=end; ++i ) + { + memset(&pointsMask[0], 0, objectPoints.cols ); + memset(&pointsMask[0], 1, MIN_POINTS_COUNT ); + generateVar(pointsMask, rng_base_seed + i); + pnpTask(i, pointsMask, objectPoints, imagePoints, parameters, + inliers, bestIndex, rvec, tvec, initRvec, initTvec, syncMutex); + if ((int)inliers.size() >= parameters.minInliersCount) + { + break; + } + } + } + PnPSolver(const cv::Mat& _objectPoints, const cv::Mat& _imagePoints, const Parameters& _parameters, + cv::Mat& _rvec, cv::Mat& _tvec, std::vector& _inliers, int& _bestIndex, uint64 _rng_base_seed): + objectPoints(_objectPoints), imagePoints(_imagePoints), parameters(_parameters), + rvec(_rvec), tvec(_tvec), inliers(_inliers), bestIndex(_bestIndex), rng_base_seed(_rng_base_seed) + { + bestIndex = -1; + rvec.copyTo(initRvec); + tvec.copyTo(initTvec); + } + private: + PnPSolver& operator=(const PnPSolver&); + + const cv::Mat& objectPoints; + const cv::Mat& imagePoints; + const Parameters& parameters; + cv::Mat &rvec, &tvec; + std::vector& inliers; + int& bestIndex; + const uint64 rng_base_seed; + cv::Mat initRvec, initTvec; + + static cv::Mutex syncMutex; + + void generateVar(std::vector& mask, uint64 rng_seed) const + { + cv::RNG generator(rng_seed); + int size = (int)mask.size(); + for (int i = 0; i < size; i++) + { + int i1 = generator.uniform(0, size); + int i2 = generator.uniform(0, size); + char curr = mask[i1]; + mask[i1] = mask[i2]; + mask[i2] = curr; + } + } + }; + + cv::Mutex PnPSolver::syncMutex; + +} + +void solvePnPRansac(cv::InputArray _opoints, cv::InputArray _ipoints, + cv::InputArray _cameraMatrix, cv::InputArray _distCoeffs, + cv::OutputArray _rvec, cv::OutputArray _tvec, bool useExtrinsicGuess, + int iterationsCount, float reprojectionError, int minInliersCount, + cv::OutputArray _inliers, int flags) +{ + const int _rng_seed = 0; + cv::Mat opoints = _opoints.getMat(), ipoints = _ipoints.getMat(); + cv::Mat cameraMatrix = _cameraMatrix.getMat(), distCoeffs = _distCoeffs.getMat(); + + CV_Assert(opoints.isContinuous()); + CV_Assert(opoints.depth() == CV_32F || opoints.depth() == CV_64F); + CV_Assert((opoints.rows == 1 && opoints.channels() == 3) || opoints.cols*opoints.channels() == 3); + CV_Assert(ipoints.isContinuous()); + CV_Assert(ipoints.depth() == CV_32F || ipoints.depth() == CV_64F); + CV_Assert((ipoints.rows == 1 && ipoints.channels() == 2) || ipoints.cols*ipoints.channels() == 2); + + _rvec.create(3, 1, CV_64FC1); + _tvec.create(3, 1, CV_64FC1); + cv::Mat rvec = _rvec.getMat(); + cv::Mat tvec = _tvec.getMat(); + + cv::Mat objectPoints = opoints.reshape(3, 1), imagePoints = ipoints.reshape(2, 1); + + if (minInliersCount <= 0) + minInliersCount = objectPoints.cols; + pnpransac::Parameters params; + params.iterationsCount = iterationsCount; + params.minInliersCount = minInliersCount; + params.reprojectionError = reprojectionError; + params.useExtrinsicGuess = useExtrinsicGuess; + params.camera.init(cameraMatrix, distCoeffs); + params.flags = flags; + + std::vector localInliers; + cv::Mat localRvec, localTvec; + rvec.copyTo(localRvec); + tvec.copyTo(localTvec); + int bestIndex; + + // TBB not used + if (objectPoints.cols >= pnpransac::MIN_POINTS_COUNT) + { + pnpransac::PnPSolver solver(objectPoints, imagePoints, params, + localRvec, localTvec, localInliers, bestIndex, + _rng_seed); + solver(0, iterationsCount); + } + + if (localInliers.size() >= (size_t)pnpransac::MIN_POINTS_COUNT) + { + if (flags != CV_P3P) + { + int i, pointsCount = (int)localInliers.size(); + cv::Mat inlierObjectPoints(1, pointsCount, CV_MAKE_TYPE(opoints.depth(), 3)), inlierImagePoints(1, pointsCount, CV_MAKE_TYPE(ipoints.depth(), 2)); + for (i = 0; i < pointsCount; i++) + { + int index = localInliers[i]; + cv::Mat colInlierImagePoints = inlierImagePoints(cv::Rect(i, 0, 1, 1)); + imagePoints.col(index).copyTo(colInlierImagePoints); + cv::Mat colInlierObjectPoints = inlierObjectPoints(cv::Rect(i, 0, 1, 1)); + objectPoints.col(index).copyTo(colInlierObjectPoints); + } + solvePnP(inlierObjectPoints, inlierImagePoints, params.camera.intrinsics, params.camera.distortion, localRvec, localTvec, false, flags); + } + localRvec.copyTo(rvec); + localTvec.copyTo(tvec); + if (_inliers.needed()) + cv::Mat(localInliers).copyTo(_inliers); + } + else + { + tvec.setTo(cv::Scalar(0)); + cv::Mat R = cv::Mat::eye(3, 3, CV_64F); + Rodrigues(R, rvec); + if( _inliers.needed() ) + _inliers.release(); + } + return; +} +#endif + } } diff --git a/guilib/src/DatabaseViewer.cpp b/guilib/src/DatabaseViewer.cpp index 106324ab..52567428 100644 --- a/guilib/src/DatabaseViewer.cpp +++ b/guilib/src/DatabaseViewer.cpp @@ -421,6 +421,8 @@ void DatabaseViewer::readSettings() // Visual parameters settings.beginGroup("visual"); + ui_->comboBox_estimationType->setCurrentIndex(settings.value("estimationType", ui_->comboBox_estimationType->currentIndex()).toInt()); + ui_->comboBox_pnpFlags->setCurrentIndex(settings.value("pnpFlags", ui_->comboBox_pnpFlags->currentIndex()).toInt()); ui_->groupBox_visual_recomputeFeatures->setChecked(settings.value("reextract", ui_->groupBox_visual_recomputeFeatures->isChecked()).toBool()); ui_->comboBox_featureType->setCurrentIndex(settings.value("featureType", ui_->comboBox_featureType->currentIndex()).toInt()); ui_->comboBox_nnType->setCurrentIndex(settings.value("nnType", ui_->comboBox_nnType->currentIndex()).toInt()); @@ -517,6 +519,8 @@ void DatabaseViewer::writeSettings() // save Visual parameters settings.beginGroup("visual"); + settings.setValue("estimationType", ui_->comboBox_estimationType->currentIndex()); + settings.setValue("pnpFlags", ui_->comboBox_pnpFlags->currentIndex()); settings.setValue("reextract", ui_->groupBox_visual_recomputeFeatures->isChecked()); settings.setValue("featureType", ui_->comboBox_featureType->currentIndex()); settings.setValue("nnType", ui_->comboBox_nnType->currentIndex()); @@ -3173,6 +3177,7 @@ void DatabaseViewer::refineConstraintVisually(int from, int to, bool silent, boo parameters.insert(ParametersPair(Parameters::kLccBowIterations(), uNumber2Str(ui_->spinBox_visual_iteration->value()))); parameters.insert(ParametersPair(Parameters::kLccBowMinInliers(), uNumber2Str(ui_->spinBox_visual_minCorrespondences->value()))); parameters.insert(ParametersPair(Parameters::kLccBowEstimationType(), uNumber2Str(ui_->comboBox_estimationType->currentIndex()))); + parameters.insert(ParametersPair(Parameters::kLccBowPnPFlags(), uNumber2Str(ui_->comboBox_pnpFlags->currentIndex()))); parameters.insert(ParametersPair(Parameters::kMemGenerateIds(), "false")); parameters.insert(ParametersPair(Parameters::kMemRehearsalSimilarity(), "1.0")); parameters.insert(ParametersPair(Parameters::kKpWordsPerImage(), "0")); @@ -3203,6 +3208,7 @@ void DatabaseViewer::refineConstraintVisually(int from, int to, bool silent, boo parameters.insert(ParametersPair(Parameters::kLccBowIterations(), uNumber2Str(ui_->spinBox_visual_iteration->value()))); parameters.insert(ParametersPair(Parameters::kLccBowMinInliers(), uNumber2Str(ui_->spinBox_visual_minCorrespondences->value()))); parameters.insert(ParametersPair(Parameters::kLccBowEstimationType(), uNumber2Str(ui_->comboBox_estimationType->currentIndex()))); + parameters.insert(ParametersPair(Parameters::kLccBowPnPFlags(), uNumber2Str(ui_->comboBox_pnpFlags->currentIndex()))); memory_->parseParameters(parameters); t = memory_->computeVisualTransform(to, from, &rejectedMsg, &inliers, &variance); } @@ -3292,6 +3298,7 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra parameters.insert(ParametersPair(Parameters::kLccBowIterations(), uNumber2Str(ui_->spinBox_visual_iteration->value()))); parameters.insert(ParametersPair(Parameters::kLccBowMinInliers(), uNumber2Str(ui_->spinBox_visual_minCorrespondences->value()))); parameters.insert(ParametersPair(Parameters::kLccBowEstimationType(), uNumber2Str(ui_->comboBox_estimationType->currentIndex()))); + parameters.insert(ParametersPair(Parameters::kLccBowPnPFlags(), uNumber2Str(ui_->comboBox_pnpFlags->currentIndex()))); parameters.insert(ParametersPair(Parameters::kMemGenerateIds(), "false")); parameters.insert(ParametersPair(Parameters::kMemRehearsalSimilarity(), "1.0")); parameters.insert(ParametersPair(Parameters::kKpWordsPerImage(), "0")); @@ -3329,6 +3336,7 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra parameters.insert(ParametersPair(Parameters::kLccBowIterations(), uNumber2Str(ui_->spinBox_visual_iteration->value()))); parameters.insert(ParametersPair(Parameters::kLccBowMinInliers(), uNumber2Str(ui_->spinBox_visual_minCorrespondences->value()))); parameters.insert(ParametersPair(Parameters::kLccBowEstimationType(), uNumber2Str(ui_->comboBox_estimationType->currentIndex()))); + parameters.insert(ParametersPair(Parameters::kLccBowPnPFlags(), uNumber2Str(ui_->comboBox_pnpFlags->currentIndex()))); memory_->parseParameters(parameters); t = memory_->computeVisualTransform(to, from, &rejectedMsg, &inliers, &variance); } diff --git a/guilib/src/ui/DatabaseViewer.ui b/guilib/src/ui/DatabaseViewer.ui index 9a2e65ca..0cb1029a 100644 --- a/guilib/src/ui/DatabaseViewer.ui +++ b/guilib/src/ui/DatabaseViewer.ui @@ -50,8 +50,8 @@ 0 0 - 175 - 173 + 154 + 184 @@ -236,8 +236,8 @@ 0 0 - 174 - 173 + 154 + 184 @@ -418,7 +418,7 @@ 0 0 1285 - 25 + 22 @@ -829,15 +829,15 @@ - 3 + 1 0 0 - 314 - 303 + 312 + 314 @@ -1056,8 +1056,8 @@ 0 0 - 351 - 347 + 366 + 391 @@ -1066,7 +1066,7 @@ - + 1 @@ -1086,57 +1086,14 @@ - + Max correspondence distance - - - - Iteration - - - - - - - m - - - 1 - - - 0.100000000000000 - - - 1.000000000000000 - - - 0.100000000000000 - - - 0.600000000000000 - - - - - - - NNDR - - - - - - - Min correspondences - - - - + 3 @@ -1156,7 +1113,7 @@ - + m @@ -1196,6 +1153,78 @@ + + + + Iteration + + + + + + + m + + + 1 + + + 0.100000000000000 + + + 1.000000000000000 + + + 0.100000000000000 + + + 0.600000000000000 + + + + + + + NNDR + + + + + + + Min correspondences + + + + + + + PnP flags. + + + + + + + 1 + + + + Iterative + + + + + EPNP + + + + + P3P + + + + @@ -1346,8 +1375,8 @@ 0 0 - 333 - 333 + 338 + 330 @@ -1585,8 +1614,8 @@ 0 0 - 320 - 311 + 248 + 343 @@ -1794,7 +1823,7 @@ 0 0 201 - 117 + 126 @@ -1893,8 +1922,8 @@ 0 0 - 285 - 309 + 283 + 322