diff --git a/CMakeLists.txt b/CMakeLists.txt index b9f3ad7358ad2dad416d6cf0c8923f23d5a90ece..0bcdb9f10bfcfb6d4dc59a279649335f3a25b165 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,7 +247,8 @@ SET(HDRS_PROCESSOR include/core/processor/processor_diff_drive.h include/core/processor/factory_processor.h include/core/processor/processor_logging.h - include/core/processor/processor_loopclosure.h + #include/core/processor/processor_loopclosure.h + include/core/processor/processor_loop_closure.h include/core/processor/processor_motion.h include/core/processor/processor_odom_2d.h include/core/processor/processor_odom_3d.h @@ -343,7 +344,8 @@ SET(SRCS_PROCESSOR src/processor/motion_buffer.cpp src/processor/processor_base.cpp src/processor/processor_diff_drive.cpp - src/processor/processor_loopclosure.cpp + #src/processor/processor_loopclosure.cpp + src/processor/processor_loop_closure.cpp src/processor/processor_motion.cpp src/processor/processor_odom_2d.cpp src/processor/processor_odom_3d.cpp diff --git a/include/core/processor/processor_loop_closure.h b/include/core/processor/processor_loop_closure.h new file mode 100644 index 0000000000000000000000000000000000000000..39813708a7954a94a883b5fbbbdbcedf873f3fc9 --- /dev/null +++ b/include/core/processor/processor_loop_closure.h @@ -0,0 +1,88 @@ +#ifndef _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H +#define _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H + +// Wolf related headers +#include "core/processor/processor_base.h" + +namespace wolf{ + +WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorLoopClosure); + +struct ParamsProcessorLoopClosure : public ParamsProcessorBase +{ + using ParamsProcessorBase::ParamsProcessorBase; + // virtual ~ParamsProcessorLoopClosure() = default; + + // add neccesery parameters for loop closure initialisation here and initialize + // them in constructor +}; + +/** \brief General loop closure processor + * + * This is an abstract class. + * + You must define the following classes : + * - voteFindLoopClosures(CaptureBasePtr) + * - emplaceFeatures(CaptureBasePtr) + * - findLoopClosures(CaptureBasePtr) + * - validateLoop(CaptureBasePtr, CaptureBasePtr) + * - emplaceFactors(CaptureBasePtr, CaptureBasePtr) + * + You can override the following classes : + * - process() + */ + +class ProcessorLoopClosure : public ProcessorBase +{ +protected: + + ParamsProcessorLoopClosurePtr params_loop_closure_; + +public: + + ProcessorLoopClosure(const std::string& _type, int _dim, ParamsProcessorLoopClosurePtr _params_loop_closure); + + ~ProcessorLoopClosure() override = default; + void configure(SensorBasePtr _sensor) override { }; + +protected: + + /** \brief Process a frame containing a capture. + * If voteFindLoopClosures() returns true, findLoopClosures() is called. + * emplaceFactors() is called for pairs of current capture and each capture returned by findLoopClosures() + */ + virtual void process(FrameBasePtr, CaptureBasePtr); + + /** \brief Returns if findLoopClosures() has to be called for the given capture + */ + virtual bool voteFindLoopClosures(CaptureBasePtr cap) = 0; + + /** \brief detects and emplaces all features of the given capture + */ + virtual void emplaceFeatures(CaptureBasePtr cap) = 0; + + /** \brief Find captures that correspond to loop closures with the given capture + */ + virtual CaptureBasePtrList findLoopClosures(CaptureBasePtr _capture) = 0; + + /** \brief validates a loop closure + */ + virtual bool validateLoopClosure(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) = 0; + + /** \brief emplaces the factor(s) corresponding to a Loop Closure between two captures + */ + virtual void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) = 0; + + void processCapture(CaptureBasePtr) override; + void processKeyFrame(FrameBasePtr, const double&) override; + + bool triggerInCapture(CaptureBasePtr _cap) const override { return true;}; + bool triggerInKeyFrame(FrameBasePtr _frm, const double& _time_tol) const override { return true;}; + + bool storeKeyFrame(FrameBasePtr _frm) override { return false;}; + bool storeCapture(CaptureBasePtr _cap) override { return false;}; + + bool voteForKeyFrame() const override { return false;}; +}; + +} // namespace wolf + +#endif /* _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H */ diff --git a/src/processor/processor_loop_closure.cpp b/src/processor/processor_loop_closure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bf70934d9e820fd910ee7de744b6896b261ae1f4 --- /dev/null +++ b/src/processor/processor_loop_closure.cpp @@ -0,0 +1,113 @@ +#include "core/processor/processor_loop_closure.h" + +namespace wolf +{ + +ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type, + int _dim, + ParamsProcessorLoopClosurePtr _params_loop_closure): + ProcessorBase(_type, _dim, _params_loop_closure), + params_loop_closure_(_params_loop_closure) +{ + // +} + +void ProcessorLoopClosure::processCapture(CaptureBasePtr _capture) +{ + /* This function has 3 scenarios: + * 1. Capture already linked to a frame -> process + * 2. Capture has a timestamp compatible with any stored frame -> link + process + * 3. Otherwise -> store capture (Note that more than one processor can be emplacing frames, so an older frame can arrive later than this one) + */ + + // CASE 1: + if (_capture->getFrame()) + { + process(_capture->getFrame(), _capture); + + // remove the frame and older frames + buffer_pack_kf_.removeUpTo(_capture->getFrame()->getTimeStamp()); + } + + // Search for any stored frame within time tolerance of capture + auto frame_pack = buffer_pack_kf_.select(_capture->getTimeStamp(), params_->time_tolerance); + + // CASE 2: + if (frame_pack) + { + _capture->link(frame_pack->key_frame); + + process(frame_pack->key_frame, _capture); + + // remove the frame and older frames + buffer_pack_kf_.removeUpTo(frame_pack->key_frame->getTimeStamp()); + } + // CASE 3: + else + buffer_capture_.add(_capture->getTimeStamp(), _capture); +} + +void ProcessorLoopClosure::processKeyFrame(FrameBasePtr _frame, const double& _time_tolerance) +{ + /* This function has 4 scenarios: + * 1. Frame already have a capture of the sensor -> process + * 2. Frame has a timestamp within time tolerances of any stored capture -> link + process + * 3. Frame is more recent than any stored capture -> store frame to be processed later in processCapture + * 4. Otherwise: The frame is not compatible with any stored capture -> discard frame + */ + + // CASE 1: + auto cap = _frame->getCaptureOf(getSensor()); + if (cap) + { + process(_frame, cap); + + // remove the capture (if stored) + buffer_capture_.getContainer().erase(cap->getTimeStamp()); + } + + // Search for any stored capture within time tolerance of frame + auto capture = buffer_capture_.select(_frame->getTimeStamp(), params_->time_tolerance); + + // CASE 2: + if (capture) + { + process(_frame, capture); + + // remove the capture (if stored) + buffer_capture_.getContainer().erase(capture->getTimeStamp()); + + // remove old captures (10s of old captures are kept in case frames arrives unordered) + buffer_capture_.removeUpTo(_frame->getTimeStamp() - 10); + } + // CASE 3: + else if (buffer_capture_.selectLastAfter(_frame->getTimeStamp(), params_->time_tolerance) == nullptr) + { + // store frame + buffer_pack_kf_.add(_frame, _time_tolerance); + } + // CASE 4: + // nothing (discard frame) +} + +void ProcessorLoopClosure::process(FrameBasePtr _frame, CaptureBasePtr _capture) +{ + assert(_capture->getFrame() == _frame && "ProcessorLoopClosure::process _capture not linked to _frame"); + + // Detect and emplace features + emplaceFeatures(_capture); + + // Vote for loop closure search + if (voteFindLoopClosures(_capture)) + { + // Find loop closures + auto cap_lc_list = findLoopClosures(_capture); + + // Emplace factors for each LC if validated + for (auto cap_lc : cap_lc_list) + if (validateLoopClosure(cap_lc, _capture)) + emplaceFactors(cap_lc, _capture); + } +} + +}// namespace wolf diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 885c1f53a456deb4b74392d011d560cb7216bafe..b48adb281744d8bd30314bd19546e85a0eea9cf6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -225,9 +225,13 @@ target_link_libraries(gtest_param_prior ${PLUGIN_NAME}) wolf_add_gtest(gtest_processor_diff_drive gtest_processor_diff_drive.cpp) target_link_libraries(gtest_processor_diff_drive ${PLUGIN_NAME}) -# ProcessorLoopClosureBase class test -wolf_add_gtest(gtest_processor_loopclosure gtest_processor_loopclosure.cpp) -target_link_libraries(gtest_processor_loopclosure ${PLUGIN_NAME}) +# ProcessorLoopClosure class test +#wolf_add_gtest(gtest_processor_loopclosure gtest_processor_loopclosure.cpp) +#target_link_libraries(gtest_processor_loopclosure ${PLUGIN_NAME}) + +# ProcessorLoopClosure class test +wolf_add_gtest(gtest_processor_loop_closure gtest_processor_loop_closure.cpp) +target_link_libraries(gtest_processor_loop_closure ${PLUGIN_NAME}) # ProcessorFrameNearestNeighborFilter class test # wolf_add_gtest(gtest_processor_frame_nearest_neighbor_filter_2d gtest_processor_frame_nearest_neighbor_filter_2d.cpp) diff --git a/test/dummy/processor_loop_closure_dummy.h b/test/dummy/processor_loop_closure_dummy.h new file mode 100644 index 0000000000000000000000000000000000000000..8fff4db18d933975bb25cd0c5df327c4e2f101b9 --- /dev/null +++ b/test/dummy/processor_loop_closure_dummy.h @@ -0,0 +1,85 @@ +#ifndef TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ +#define TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ + +#include "core/processor/processor_loop_closure.h" + +using namespace wolf; +using namespace Eigen; + +WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy); + +// dummy class: +class ProcessorLoopClosureDummy : public ProcessorLoopClosure +{ + public: + ProcessorLoopClosureDummy(ParamsProcessorLoopClosurePtr _params) : + ProcessorLoopClosure("ProcessorLoopClosureDummy", 2, _params) + { + } + + protected: + bool voteFindLoopClosures(CaptureBasePtr cap) override { return true;}; + bool validateLoopClosure(CaptureBasePtr, CaptureBasePtr) override { return true;}; + + void emplaceFeatures(CaptureBasePtr cap) override + { + // feature = frame pose + FeatureBase::emplace<FeatureBase>(cap, + "FeatureLoopClosureDummy", + cap->getFrame()->getState().vector("PO"), + MatrixXd::Identity(3,3)); + } + + CaptureBasePtrList findLoopClosures(CaptureBasePtr _capture) override + { + CaptureBasePtrList cap_lc_list; + + auto old_frame = _capture->getFrame()->getPreviousFrame(); + while (old_frame) + { + // match if features (frames psoe) are close enough + for (auto cap : old_frame->getCaptureList()) + for (auto feat : cap->getFeatureList()) + if (feat->getType() == "FeatureLoopClosureDummy" and + (feat->getMeasurement() - _capture->getFeatureList().front()->getMeasurement()).norm() < 1e-3) + { + cap_lc_list.push_back(cap); + } + + old_frame = old_frame->getPreviousFrame(); + } + + return cap_lc_list; + } + + void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) override + { + FeatureBasePtr feat_2; + for (auto feat : _capture_2->getFeatureList()) + if (feat->getType() == "FeatureLoopClosureDummy") + { + feat_2 = feat; + break; + } + + FactorBase::emplace<FactorRelativePose2d>(feat_2, feat_2, + _capture_1->getFrame(), + shared_from_this(), + false, + TOP_LOOP); + } + + public: + unsigned int getNStoredFrames() + { + return buffer_pack_kf_.getContainer().size(); + } + + unsigned int getNStoredCaptures() + { + return buffer_capture_.getContainer().size(); + } +}; + + +#endif /* TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ */ diff --git a/test/gtest_processor_loop_closure.cpp b/test/gtest_processor_loop_closure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2cd9fe39c0142018dcf88fe558e492b38518671c --- /dev/null +++ b/test/gtest_processor_loop_closure.cpp @@ -0,0 +1,76 @@ + +#include "core/utils/utils_gtest.h" +#include "core/problem/problem.h" +#include "core/capture/capture_base.h" +#include "core/factor/factor_relative_pose_2d.h" + +#include "dummy/processor_loop_closure_dummy.h" + +// STL +#include <iterator> +#include <iostream> + +using namespace wolf; +using namespace Eigen; + +// Wolf problem +ProblemPtr problem = Problem::create("PO", 2); + +ProcessorLoopClosureDummyPtr proc_lc; + +void setup() +{ + // Emplace sensor + auto sens_lc = SensorBase::emplace<SensorBase>(problem->getHardware(), + "SensorBase", + std::make_shared<StateBlock>(Vector2d::Zero()), + std::make_shared<StateBlock>(Vector1d::Zero()), + nullptr, + 2); + + // Emplace processor + ParamsProcessorLoopClosurePtr params = std::make_shared<ParamsProcessorLoopClosure>(); + proc_lc = ProcessorBase::emplace<ProcessorLoopClosureDummy>(sens_lc, params); +} + +TEST(ProcessorLoopClosure, installProcessor) +{ + setup(); + + EXPECT_EQ(proc_lc->getNStoredFrames(), 0); + EXPECT_EQ(proc_lc->getNStoredCaptures(), 0); +} + +TEST(ProcessorLoopClosure, frame_stored) +{ + setup(); + + // new frame + auto fr1 = problem->emplaceFrame(0, Vector3d::Zero()); + + // keyframecallback + problem->keyFrameCallback(fr1, nullptr, 1); + + EXPECT_EQ(proc_lc->getNStoredFrames(), 1); + EXPECT_EQ(proc_lc->getNStoredCaptures(), 0); +} + +TEST(ProcessorLoopClosure, capture_stored) +{ + setup(); + + // new capture + auto cap1 = std::make_shared<CaptureBase>("CaptureBase", 0, nullptr); + + // keyframecallback + proc_lc->captureCallback(cap1); + + EXPECT_EQ(proc_lc->getNStoredFrames(), 0); + EXPECT_EQ(proc_lc->getNStoredCaptures(), 1); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}