diff --git a/include/vision/processor/processor_visual_odometry.h b/include/vision/processor/processor_visual_odometry.h index 8538777e55096d3f633e726d825c3a0dd98b860d..388e3aa4eb3167b5064a456185c1bd1e030f6a2b 100644 --- a/include/vision/processor/processor_visual_odometry.h +++ b/include/vision/processor/processor_visual_odometry.h @@ -118,6 +118,7 @@ struct ParamsProcessorVisualOdometry unsigned int min_track_length; double min_pixel_dist; bool use_homogeneous; + double distance_init; }; DebugParams debug; @@ -229,6 +230,7 @@ struct ParamsProcessorVisualOdometry lmk_creation.min_track_length = _params["landmark_creation"]["min_track_length"].as<unsigned int>(); lmk_creation.min_pixel_dist = _params["landmark_creation"]["min_pixel_dist"].as<unsigned int>(); lmk_creation.use_homogeneous = _params["landmark_creation"]["use_homogeneous"].as<bool>(); + lmk_creation.distance_init = _params["landmark_creation"]["distance_init"].as<double>(); } }; @@ -238,7 +240,7 @@ class ProcessorVisualOdometry : public ProcessorTracker, public MotionProvider { public: ProcessorVisualOdometry(const YAML::Node& _params); - virtual ~ProcessorVisualOdometry() override{}; + virtual ~ProcessorVisualOdometry() override {}; TimeStamp getTimeStamp() const override; VectorComposite getState(StateKeys _structure = "") const override; @@ -396,11 +398,12 @@ class ProcessorVisualOdometry : public ProcessorTracker, public MotionProvider /** * \brief Emplace a landmark corresponding to a track and initialize it with triangulation. * \param _feature_ptr a pointer to the feature used to create the new landmark + * \param _distance distance to the camera in meters * \return a pointer to the created landmark * - * Implementation: Use rays of features detections in last frame and create a landmark at 1 meter (arbitrary) + * Implementation: Use rays of features detections in last frame and create a landmark at '_distance' meters */ - LandmarkBasePtr emplaceLandmarkNaive(FeaturePointImagePtr _feature_ptr); + LandmarkBasePtr emplaceLandmarkNaive(FeaturePointImagePtr _feature_ptr, double _distance); /** * \brief Emplace a landmark corresponding to a track and initialize it with triangulation. diff --git a/schema/processor/ProcessorVisualOdometry.schema b/schema/processor/ProcessorVisualOdometry.schema index b10cb351959a4fb0480fae8f464877b7fd44b594..9c886abf7c71e2c4313f5b0a2dd63ff008d3fdbb 100644 --- a/schema/processor/ProcessorVisualOdometry.schema +++ b/schema/processor/ProcessorVisualOdometry.schema @@ -148,4 +148,10 @@ landmark_creation: use_homogeneous: _type: bool _mandatory: true - _doc: The created landmarks will use homogeneous coordinates (recommended), if false, it will create euclidean 3D points. \ No newline at end of file + _doc: The created landmarks will use homogeneous coordinates (recommended), if false, it will create euclidean 3D points. + + distance_init: + _type: double + _mandatory: false + _default: 10.0 + _doc: "The distance used to initialize the landmarks in meters projecting the feature direction, only done when triangulation method fails." \ No newline at end of file diff --git a/src/processor/processor_visual_odometry.cpp b/src/processor/processor_visual_odometry.cpp index 82582470cb80c9042d5859a296059a6d55943990..c63f98259cdffa53a9e0ab8108f9e3db350c1c4a 100644 --- a/src/processor/processor_visual_odometry.cpp +++ b/src/processor/processor_visual_odometry.cpp @@ -386,7 +386,7 @@ void ProcessorVisualOdometry::establishFactors() // std::cout << "NEW valid track \\o/" << std::endl; auto lmk_ptr = emplaceLandmarkTriangulation(feat_pi, track_kf); - if (not lmk_ptr) lmk_ptr = emplaceLandmarkNaive(feat_pi); + if (not lmk_ptr) lmk_ptr = emplaceLandmarkNaive(feat_pi, params_visual_odometry_.lmk_creation.distance_init); // Associate all features of the track to the landmark for (auto feat_track : track_matrix_.track(feat->trackId())) @@ -563,9 +563,6 @@ bool ProcessorVisualOdometry::filterWithEssential(const KeyPointsMap _mwkps_prev cv::Mat& _E, VectorComposite& _pose_prev_curr) const { - // TODO: Check new RANSAC methods introduced in opencv 4.5.0 - // https://opencv.org/evaluating-opencvs-new-ransacs/ - // We need at least five tracks // TODO: this case is NOT handled by the rest of the algorithm currently if (_tracks_prev_curr.size() < 5) return false; @@ -812,28 +809,26 @@ LandmarkBasePtr ProcessorVisualOdometry::emplaceLandmarkTriangulation(FeaturePoi // at least 2 KF needed to triangulate if (_track_kf.size() < 2) return nullptr; - Vector4d pt_c; - if (not tryTriangulationFromFeatures(_feat, _track_kf, pt_c)) return nullptr; + Vector4d pt_w; + if (not tryTriangulationFromFeatures(_feat, _track_kf, pt_w)) return nullptr; if (params_visual_odometry_.lmk_creation.use_homogeneous) - return LandmarkBase::emplace<LandmarkHp>(getProblem()->getMap(), pt_c, _feat->getKeyPoint().getDescriptor()); + return LandmarkBase::emplace<LandmarkHp>(getProblem()->getMap(), pt_w, _feat->getKeyPoint().getDescriptor()); else return LandmarkBase::emplace<LandmarkVisualPoint3d>( - getProblem()->getMap(), pt_c.head<3>() / pt_c(3), _feat->getKeyPoint().getDescriptor()); + getProblem()->getMap(), pt_w.head<3>() / pt_w(3), _feat->getKeyPoint().getDescriptor()); } bool ProcessorVisualOdometry::tryTriangulationFromFeatures(FeaturePointImagePtr _feat, Track _track_kf, - Vector4d& pt_c) const + Vector4d& pt_w) const { // heuristic: use oldest feature/KF to triangulate with respect to current time FeaturePointImagePtr feat_pi1 = std::static_pointer_cast<FeaturePointImage>(_track_kf.begin()->second); cv::Vec2d pix1, pix2; - // WOLF_INFO("TOTO") cv::eigen2cv(feat_pi1->getMeasurement(), pix1); cv::eigen2cv(_feat->getMeasurement(), pix2); - // WOLF_INFO(pix1, pix2); // create 3x4 projection matrices [K|0] * Tcw Matrix4d Tcw1 = computeTcw(feat_pi1->getCapture()->getTimeStamp()).matrix(); @@ -847,26 +842,27 @@ bool ProcessorVisualOdometry::tryTriangulationFromFeatures(FeaturePointImagePtr // perform triangulation of one landmark cv::Vec4d ptcv_w; // output: homogeneous landmark point expressed in world frame cv::triangulatePoints(P1_cv, P2_cv, pix1, pix2, ptcv_w); - // WOLF_INFO("YAY: ", ptcv_w) - ///////////////////////////////////////////////////////// - // check that triangulation was done with enough parallax - ///////////////////////////////////////////////////////// - bool triangulation_is_a_success = true; // Not implemented yet - if (!triangulation_is_a_success) + // convert to Eigen + cv::cv2eigen(ptcv_w, pt_w); + + // Normalization necessary since homogeneous point stateblock is supposed to be unitary + pt_w.normalize(); + if (pt_w(3) < 0) + pt_w *= -1; + + // Cheriality check + // transform to camera frame + auto pt_c = Tcw1.inverse().matrix() * pt_w; + // check 3rd coordinate is positive + if (pt_c(2) / pt_c(3) < 0) { + WOLF_DEBUG("pt_w:", pt_w.transpose()); + WOLF_DEBUG("pt_c:", pt_c.transpose()); + WOLF_DEBUG("Triangulation failed: negative depth."); return false; } - // normalize to make equivalent to a unit quaternion - cv::cv2eigen(ptcv_w, pt_c); - - // HACK: to avoid "nans" in residual, set Z = 1 - // pt_c(2) = 1 * pt_c(3); - - // Normalization necessary since homogeneous point stateblock is supposed to be unitary - pt_c.normalize(); - return true; } @@ -888,15 +884,14 @@ Eigen::Isometry3d ProcessorVisualOdometry::computeTcw(TimeStamp _ts) const return (T_w_r * T_r_c).inverse(); } -LandmarkBasePtr ProcessorVisualOdometry::emplaceLandmarkNaive(FeaturePointImagePtr _feat) +LandmarkBasePtr ProcessorVisualOdometry::emplaceLandmarkNaive(FeaturePointImagePtr _feat, double _distance) { - // Initialize the landmark in its ray (based on pixel meas) and using a arbitrary distance - double distance = 1; + // Initialize the landmark in its ray (based on pixel meas) and using an arbitrary distance Eigen::Vector3d point3d_c = pinhole::backprojectPoint(getSensor()->getIntrinsic()->getState(), (std::static_pointer_cast<SensorCamera>(getSensor()))->getCorrectionVector(), _feat->getMeasurement(), - distance); + _distance); // lmk from camera to world coordinate frame. Transform<double, 3, Isometry> T_w_r = Translation<double, 3>(_feat->getFrame()->getP()->getState()) * diff --git a/test/gtest_processor_visual_odometry.cpp b/test/gtest_processor_visual_odometry.cpp index c682c3435beeeeb0ac2436b4afb328e263d8603b..d82e50bfaf66fd41e69312f861d99f529cec278a 100644 --- a/test/gtest_processor_visual_odometry.cpp +++ b/test/gtest_processor_visual_odometry.cpp @@ -114,9 +114,9 @@ class ProcessorVisualOdometryPublic : public ProcessorVisualOdometry return ProcessorVisualOdometry::tryTriangulationFromFeatures(_feat, _track_kf, pt_c); } - LandmarkBasePtr emplaceLandmarkNaive(FeaturePointImagePtr _feature_ptr) + LandmarkBasePtr emplaceLandmarkNaive(FeaturePointImagePtr _feature_ptr, double _distance) { - return ProcessorVisualOdometry::emplaceLandmarkNaive(_feature_ptr); + return ProcessorVisualOdometry::emplaceLandmarkNaive(_feature_ptr, _distance); } LandmarkBasePtr emplaceLandmarkTriangulation(FeaturePointImagePtr _feature_ptr, Track _track_kf) @@ -177,6 +177,7 @@ TEST(ProcessorVisualOdometry, kltTrack) _params["landmark_creation"]["min_track_length"] = 4; _params["landmark_creation"]["min_pixel_dist"] = 1; _params["landmark_creation"]["use_homogeneous"] = true; + _params["landmark_creation"]["distance_init"] = 10.0; ProcessorVisualOdometryPublicPtr processor = std::static_pointer_cast<ProcessorVisualOdometryPublic>( ProcessorVisualOdometry::create(_params, {vision_dir, wolf_schema_dir})); @@ -405,7 +406,7 @@ class ProcessorVOMultiView_class : public testing::Test Eigen::Quaterniond q_21, q_12; Eigen::Vector4d k; cv::Mat Kcv; - std::vector<Eigen::Vector4d> X_c1; + std::vector<Eigen::Vector4d> X_c1, X_w; void SetUp() override { @@ -423,7 +424,7 @@ class ProcessorVOMultiView_class : public testing::Test cv::eigen2cv(camera->getIntrinsicMatrix(), Kcv); k << Kcv.at<double>(0, 2), Kcv.at<double>(1, 2), Kcv.at<double>(0, 0), Kcv.at<double>(1, 1); - // Set 3D points on the previous camera frame -> all in front of the camera is better + // Set 3D points on the first camera frame -> all in front of the camera X_c1.clear(); X_c1.push_back((Eigen::Vector4d() << 1.0, 1.0, 2.0, 1.0).finished()); X_c1.push_back((Eigen::Vector4d() << -1.0, -1.0, 2.0, 1.0).finished()); @@ -434,20 +435,24 @@ class ProcessorVOMultiView_class : public testing::Test X_c1.push_back((Eigen::Vector4d() << 0.1, -1.0, 3.0, 1.0).finished()); X_c1.push_back((Eigen::Vector4d() << 0.75, 0.75, 2.0, 1.0).finished()); - // Project pixels in the previous view + // Keep 3D points in the world frame + for(auto i = 0; i < X_c1.size(); i++) + X_w.push_back(X_c1.at(i)); + + // Project pixels in the first view auto cam_dist_vec = camera->getDistortionVector(); for (auto i = 0; i < 8; i++) mwkps_c1.emplace(i, WKeyPoint(pinhole::projectPoint(k, cam_dist_vec, X_c1.at(i).head<3>()))); // Set the transformation between views 1 and 2 - t_12 = Vector3d(0.2, 0.1, 0.0); - // t_12 = Vector3d(2, 0, 0.0); + t_12 = Vector3d(0.2, 0.1, 0.0); // t_12 = Vector3d(2, 0, 0.0); Eigen::Vector3d euler(0.2, 0.1, 0.3); q_12 = e2q(euler); SE3::inverse(t_12, q_12, t_21, q_21); + // Project pixels in the second view for (auto i = 0; i < 8; i++) mwkps_c2.emplace(i, WKeyPoint(pinhole::projectPoint(k, cam_dist_vec, q_21 * X_c1.at(i).head<3>() + t_21))); @@ -544,7 +549,7 @@ TEST_F(ProcessorVOMultiView_class, tryTriangulationFromFeatures) track.emplace(1.0, f2); Vector4d pt_c; - processor->tryTriangulationFromFeatures(std::static_pointer_cast<FeaturePointImage>(f2), track, pt_c); + EXPECT_TRUE(processor->tryTriangulationFromFeatures(std::static_pointer_cast<FeaturePointImage>(f2), track, pt_c)); EXPECT_QUATERNION_VECTOR_APPROX( X_c1.at(i).normalized(), pt_c, 1e-3); // Homogeneous (if normalized) is equivalent to quaternion @@ -568,6 +573,9 @@ TEST_F(ProcessorVOMultiView_class, emplaceLandmarkTriangulation) // create features for (auto i = 0; i < 8; i++) { + WOLF_DEBUG("i = ", i); + WOLF_DEBUG("X_c1 = ", X_c1.at(i).head<3>().transpose()); + auto f1 = FeatureBase::emplace<FeaturePointImage>(c1, mwkps_c1.at(i), Matrix2d::Identity()); auto f2 = FeatureBase::emplace<FeaturePointImage>(c2, mwkps_c2.at(i), Matrix2d::Identity()); @@ -577,6 +585,7 @@ TEST_F(ProcessorVOMultiView_class, emplaceLandmarkTriangulation) auto lmk = processor->emplaceLandmarkTriangulation(std::static_pointer_cast<FeaturePointImage>(f2), track); + ASSERT_TRUE(lmk); EXPECT_QUATERNION_VECTOR_APPROX(X_c1.at(i).normalized(), lmk->getState().at('H'), 1e-3); // Homogeneous (if normalized) is equivalent to quaternion @@ -596,7 +605,7 @@ TEST_F(ProcessorVOMultiView_class, emplaceLandmarkNaive) { auto f1 = FeatureBase::emplace<FeaturePointImage>(c1, mwkps_c1.at(i), Matrix2d::Identity()); - auto lmk = processor->emplaceLandmarkNaive(f1); + auto lmk = processor->emplaceLandmarkNaive(f1, 10.0); EXPECT_MATRIX_APPROX(X_c1.at(i).head<3>() / X_c1.at(i)(2), lmk->getState().at('H').head<3>() / lmk->getState().at('H')(2),