diff --git a/CMakeLists.txt b/CMakeLists.txt index 608f836ca8b9ddee82ab349a593831000eb62da6..0279507ae8eeba59f96b8cfb776b9261fc1233a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,9 +254,8 @@ SET(HDRS_PROCESSOR include/core/processor/processor_capture_holder.h include/core/processor/processor_diff_drive.h include/core/processor/processor_factory.h - include/core/processor/processor_frame_nearest_neighbor_filter.h include/core/processor/processor_logging.h - include/core/processor/processor_loopclosure_base.h + include/core/processor/processor_loopclosure.h include/core/processor/processor_motion.h include/core/processor/processor_odom_2D.h include/core/processor/processor_odom_3D.h @@ -350,8 +349,7 @@ SET(SRCS_PROCESSOR src/processor/processor_base.cpp src/processor/processor_capture_holder.cpp src/processor/processor_diff_drive.cpp - src/processor/processor_frame_nearest_neighbor_filter.cpp - src/processor/processor_loopclosure_base.cpp + src/processor/processor_loopclosure.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_base.h b/include/core/processor/processor_base.h index 372c25282212f5990c2cb7ebb8c2c4e4fec0ddfc..4ac6c161d079579783d13ae7a232c0f915a1170f 100644 --- a/include/core/processor/processor_base.h +++ b/include/core/processor/processor_base.h @@ -36,19 +36,89 @@ class PackKeyFrame WOLF_PTR_TYPEDEFS(PackKeyFrame); + +/** \brief Buffer for arbitrary type objects + * + * Object and functions to manage a buffer of objects. + */ +template <typename T> +class Buffer +{ +public: + + typedef typename std::map<TimeStamp,T>::iterator Iterator; // buffer iterator + + Buffer(){}; + ~Buffer(void){}; + + /**\brief Select a Pack from the buffer + * + * Select from the buffer the closest pack (w.r.t. time stamp), + * respecting a defined time tolerances + */ + T select(const TimeStamp& _time_stamp, const Scalar& _time_tolerance); + + T selectFirstBefore(const TimeStamp& _time_stamp, const Scalar& _time_tolerance); + + /**\brief Buffer size + * + */ + SizeStd size(void); + + /**\brief Add a pack to the buffer + * + */ + void add(const TimeStamp& _time_stamp, const T& _element); //const Scalar& _time_tolerance); + + /** \brief returns the container with elements of the buffer + * + * elements are ordered from most recent to oldest + */ + std::map<TimeStamp,T> getContainer(); + + /**\brief Remove all packs in the buffer with a time stamp older than the specified + * + */ + void removeUpTo(const TimeStamp& _time_stamp); + + /**\brief Clear the buffer + * + */ + void clear(); + + /**\brief is the buffer empty ? + * + */ + bool empty(); + + /**\brief Check time tolerance + * + * Check if the time distance between two time stamps is smaller than + * the time tolerance. + */ + static bool simpleCheckTimeTolerance(const TimeStamp& _time_stamp1, const TimeStamp& _time_stamp2, const Scalar& _time_tolerance); + + /**\brief Check time tolerance + * + * Check if the time distance between two time stamps is smaller than + * the minimum time tolerance of the two frames. + */ + static bool doubleCheckTimeTolerance(const TimeStamp& _time_stamp1, const Scalar& _time_tolerance1, const TimeStamp& _time_stamp2, const Scalar& _time_tolerance2); + +protected: + + std::map<TimeStamp,T> container_; // Main buffer container +}; + + /** \brief Buffer of Key frame class objects * * Object and functions to manage a buffer of KFPack objects. */ -class BufferPackKeyFrame +class BufferPackKeyFrame : public Buffer<PackKeyFramePtr> { public: - typedef std::map<TimeStamp,PackKeyFramePtr>::iterator Iterator; // buffer iterator - - BufferPackKeyFrame(void); - ~BufferPackKeyFrame(void); - /**\brief Select a Pack from the buffer * * Select from the buffer the closest pack (w.r.t. time stamp), @@ -60,48 +130,32 @@ class BufferPackKeyFrame PackKeyFramePtr selectFirstPackBefore(const TimeStamp& _time_stamp, const Scalar& _time_tolerance); PackKeyFramePtr selectFirstPackBefore(const CaptureBasePtr _capture, const Scalar& _time_tolerance); - /**\brief Buffer size - * - */ - SizeStd size(void); - /**\brief Add a pack to the buffer * */ void add(const FrameBasePtr& _key_frame, const Scalar& _time_tolerance); - /**\brief Remove all packs in the buffer with a time stamp older than the specified - * - */ - void removeUpTo(const TimeStamp& _time_stamp); - - /**\brief Check time tolerance - * - * Check if the time distance between two time stamps is smaller than - * the minimum time tolerance of the two frames. - */ - bool checkTimeTolerance(const TimeStamp& _time_stamp1, const Scalar& _time_tolerance1, const TimeStamp& _time_stamp2, const Scalar& _time_tolerance2); - - /**\brief Clear the buffer - * - */ - void clear(); - - /**\brief Empty the buffer - * - */ - bool empty(); - /**\brief Print buffer information * */ void print(); - private: + /**\brief Alias funct + * + */ + static bool checkTimeTolerance(const TimeStamp& _time_stamp1, const Scalar& _time_tolerance1, const TimeStamp& _time_stamp2, const Scalar& _time_tolerance2) + { return doubleCheckTimeTolerance(_time_stamp1, _time_tolerance1, _time_stamp2, _time_tolerance2); }; - std::map<TimeStamp,PackKeyFramePtr> container_; // Main buffer container }; + +/** \brief Buffer of Capture class objects + * + * Object and functions to manage a buffer of Capture objects. + */ +class BufferCapture : public Buffer<CaptureBasePtr> {}; + + /** \brief base struct for processor parameters * * Derive from this struct to create structs of processor parameters. @@ -150,6 +204,7 @@ class ProcessorBase : public NodeBase, public std::enable_shared_from_this<Proce unsigned int processor_id_; ProcessorParamsBasePtr params_; BufferPackKeyFrame buffer_pack_kf_; + BufferCapture buffer_capture_; private: SensorBaseWPtr sensor_ptr_; @@ -207,6 +262,7 @@ class ProcessorBase : public NodeBase, public std::enable_shared_from_this<Proce FrameBasePtr emplaceFrame(FrameType _type, CaptureBasePtr _capture_ptr, const Eigen::VectorXs& _state); virtual void keyFrameCallback(FrameBasePtr _keyframe_ptr, const Scalar& _time_tol_other); + virtual void captureCallback(CaptureBasePtr _capture_ptr); SensorBasePtr getSensor(); const SensorBasePtr getSensor() const; @@ -288,31 +344,124 @@ inline void ProcessorBase::setTimeTolerance(Scalar _time_tolerance) params_->time_tolerance = _time_tolerance; } -inline BufferPackKeyFrame::BufferPackKeyFrame(void) +///////////////////////////////////////////////////////////////////////////////////////// + +template <typename T> +T Buffer<T>::select(const TimeStamp& _time_stamp, const Scalar& _time_tolerance) { + if (container_.empty()) + return nullptr; + + Buffer<T>::Iterator post = container_.upper_bound(_time_stamp); + + bool prev_exists = (post != container_.begin()); + bool post_exists = (post != container_.end()); + + bool post_ok = post_exists && simpleCheckTimeTolerance(post->first, _time_stamp, _time_tolerance); + + if (prev_exists) + { + Buffer<T>::Iterator prev = std::prev(post); + + bool prev_ok = simpleCheckTimeTolerance(prev->first, _time_stamp, _time_tolerance); + + if (prev_ok && !post_ok) + return prev->second; + else if (!prev_ok && post_ok) + return post->second; + + else if (prev_ok && post_ok) + { + if (std::fabs(post->first - _time_stamp) < std::fabs(prev->first - _time_stamp)) + return post->second; + else + return prev->second; + } + } + else if (post_ok) + return post->second; + + return nullptr; } -inline BufferPackKeyFrame::~BufferPackKeyFrame(void) +template <typename T> +T Buffer<T>::selectFirstBefore(const TimeStamp& _time_stamp, const Scalar& _time_tolerance) { + // There is no element + if (container_.empty()) + return nullptr; + + // Checking on begin() since packs are ordered in time + // Return first pack if is older than time stamp + if (container_.begin()->first < _time_stamp) + return container_.begin()->second; + + // Return first pack if despite being newer, it is within the time tolerance + if (simpleCheckTimeTolerance(container_.begin()->first, _time_stamp, _time_tolerance)) + return container_.begin()->second; + + // otherwise return nullptr (no pack before the provided ts or within the tolerance was found) + return nullptr; } -inline void BufferPackKeyFrame::clear() +template <typename T> +void Buffer<T>::add(const TimeStamp& _time_stamp, const T& _element) +{ + container_.emplace(_time_stamp, _element); +} + +template <typename T> +std::map<TimeStamp,T> Buffer<T>::getContainer() +{ + return container_; +} + +template <typename T> +inline void Buffer<T>::clear() { container_.clear(); } -inline bool BufferPackKeyFrame::empty() +template <typename T> +inline bool Buffer<T>::empty() { return container_.empty(); } -inline SizeStd BufferPackKeyFrame::size(void) +template <typename T> +inline SizeStd Buffer<T>::size(void) { return container_.size(); } +template <typename T> +inline void Buffer<T>::removeUpTo(const TimeStamp& _time_stamp) +{ + Buffer::Iterator post = container_.upper_bound(_time_stamp); + container_.erase(container_.begin(), post); // erasing by range +} + +template <typename T> +inline bool Buffer<T>::doubleCheckTimeTolerance(const TimeStamp& _time_stamp1, const Scalar& _time_tolerance1, + const TimeStamp& _time_stamp2, const Scalar& _time_tolerance2) +{ + Scalar time_diff = std::fabs(_time_stamp1 - _time_stamp2); + Scalar time_tol = std::min(_time_tolerance1, _time_tolerance2); + bool pass = time_diff <= time_tol; + return pass; +} + +template <typename T> +inline bool Buffer<T>::simpleCheckTimeTolerance(const TimeStamp& _time_stamp1, const TimeStamp& _time_stamp2, + const Scalar& _time_tolerance) +{ + Scalar time_diff = std::fabs(_time_stamp1 - _time_stamp2); + bool pass = time_diff <= _time_tolerance; + return pass; +} + } // namespace wolf #endif diff --git a/include/core/processor/processor_frame_nearest_neighbor_filter.h b/include/core/processor/processor_frame_nearest_neighbor_filter.h deleted file mode 100644 index dffee75750a452891432aa255447ad1ffb8491b2..0000000000000000000000000000000000000000 --- a/include/core/processor/processor_frame_nearest_neighbor_filter.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef _WOLF_SRC_PROCESSOR_FRAME_NEAREST_NEIGHBOR_FILTER_H -#define _WOLF_SRC_PROCESSOR_FRAME_NEAREST_NEIGHBOR_FILTER_H - -// Wolf related headers -#include "core/processor/processor_loopclosure_base.h" -#include "core/state_block/state_block.h" -#include "core/utils/params_server.hpp" - -namespace wolf{ - -WOLF_STRUCT_PTR_TYPEDEFS(ProcessorParamsFrameNearestNeighborFilter) -WOLF_PTR_TYPEDEFS(ProcessorFrameNearestNeighborFilter) - -enum class LoopclosureDistanceType : std::size_t -{ - LC_POINT_ELLIPSE = 1, // 2D - LC_ELLIPSE_ELLIPSE, // 2D - LC_POINT_ELLIPSOID, // 3D - LC_ELLIPSOID_ELLIPSOID, // 3D - LC_MAHALANOBIS_DISTANCE // 2D & 3D -}; - -struct ProcessorParamsFrameNearestNeighborFilter : public ProcessorParamsLoopClosure -{ - using DistanceType = LoopclosureDistanceType; - - ProcessorParamsFrameNearestNeighborFilter() : - buffer_size_(10), - sample_step_degree_(10), - distance_type_(LoopclosureDistanceType::LC_POINT_ELLIPSE), - probability_(5.991) { } - - ProcessorParamsFrameNearestNeighborFilter( - const ProcessorParamsFrameNearestNeighborFilter& o) : - buffer_size_(o.buffer_size_), - sample_step_degree_(o.sample_step_degree_), - distance_type_(o.distance_type_), - probability_(o.probability_) - { - // - } - ProcessorParamsFrameNearestNeighborFilter(std::string _unique_name, const paramsServer& _server): - ProcessorParamsLoopClosure(_unique_name, _server) - { - buffer_size_ = _server.getParam<int>(_unique_name + "/buffer_size"); - sample_step_degree_ = _server.getParam<int>(_unique_name + "/sample_step_degree"); - auto distance_type_str = _server.getParam<std::string>(_unique_name + "/distance_type"); - if(distance_type_str.compare("LC_POINT_ELLIPSE")) distance_type_ = LoopclosureDistanceType::LC_POINT_ELLIPSE; - else if(distance_type_str.compare("LC_ELLIPSE_ELLIPSE")) distance_type_ = LoopclosureDistanceType::LC_ELLIPSE_ELLIPSE; - else if(distance_type_str.compare("LC_POINT_ELLIPSOID")) distance_type_ = LoopclosureDistanceType::LC_POINT_ELLIPSOID; - else if(distance_type_str.compare("LC_ELLIPSOID_ELLIPSOID")) distance_type_ = LoopclosureDistanceType::LC_ELLIPSOID_ELLIPSOID; - else if(distance_type_str.compare("LC_MAHALANOBIS_DISTANCE")) distance_type_ = LoopclosureDistanceType::LC_MAHALANOBIS_DISTANCE; - else throw std::runtime_error("Failed to fetch a valid value for the enumerate LoopclosureDistanceType. Value provided: " + distance_type_str); - probability_ = _server.getParam<Scalar>(_unique_name + "/probability"); - } - virtual ~ProcessorParamsFrameNearestNeighborFilter() = default; - - int buffer_size_; - int sample_step_degree_; - DistanceType distance_type_; - Scalar probability_; -}; - -class ProcessorFrameNearestNeighborFilter : public ProcessorLoopClosureBase -{ -private: - - // area of the computed covariance ellipse. - // depends on how many percent of data should be considered. - Scalar area_; - - ProcessorParamsFrameNearestNeighborFilterPtr params_NNF; - -public: - - using Params = ProcessorParamsFrameNearestNeighborFilter; - using ParamsPtr = ProcessorParamsFrameNearestNeighborFilterPtr; - using DistanceType = Params::DistanceType; - - ProcessorFrameNearestNeighborFilter(ParamsPtr _params_NNF); - virtual ~ProcessorFrameNearestNeighborFilter() = default; - virtual void configure(SensorBasePtr _sensor) { }; - - inline DistanceType getDistanceType() const noexcept {return params_NNF->distance_type_;} - -protected: - - virtual bool findCandidates(const CaptureBasePtr& _incoming_ptr); - - // returns Ellipse in 2D case [ pos_x, pos_y, a, b, tilt] - bool computeEllipse2D(const FrameBasePtr& frame_ptr, - Eigen::Vector5s& ellipse); - - // returns Ellipsoid in 3D case - // [ pos_x, pos_y, pos_z, a, b, c, quat_w, quat_z, quat_y, quat_z] - bool computeEllipsoid3D(const FrameBasePtr& frame_ptr, - Eigen::Vector10s& ellipsoid); - - // returns true if the two 2D ellipses intersect - bool ellipse2DIntersect(const Eigen::VectorXs &ellipse1, - const Eigen::VectorXs &ellipse2); - - // returns true if a 2D point lies inside a 2D ellipse - bool point2DIntersect(const Eigen::VectorXs &point, - const Eigen::VectorXs &ellipse); - - // returns true if the two 3D ellipsoids intersect - bool ellipsoid3DIntersect(const Eigen::VectorXs &ellipsoid1, - const Eigen::VectorXs &ellipsoid2); - - // returns true if a 3D point lies inside a 3D ellipsoid - bool point3DIntersect(const Eigen::VectorXs &point, - const Eigen::VectorXs &ellipsoid); - - // returns true if frame lies within Mahalanobis Distance - bool insideMahalanisDistance(const FrameBasePtr& trajectory_frame, - const FrameBasePtr& query_frame); - - // computes the Mahalanobis Distance - Scalar MahalanobisDistance(const FrameBasePtr& trajectory_frame, - const FrameBasePtr& query_frame); - - bool frameInsideBuffer(const FrameBasePtr& frame_ptr); -}; - -} // namespace wolf - -#endif // _WOLF_SRC_PROCESSOR_FRAME_NEAREST_NEIGHBOR_FILTER_H_ diff --git a/include/core/processor/processor_loopclosure.h b/include/core/processor/processor_loopclosure.h new file mode 100644 index 0000000000000000000000000000000000000000..afd9855b5087e61825706b006456039cba06e93d --- /dev/null +++ b/include/core/processor/processor_loopclosure.h @@ -0,0 +1,154 @@ +#ifndef _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H +#define _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H + +// Wolf related headers +#include "core/processor/processor_base.h" + +namespace wolf{ + +WOLF_STRUCT_PTR_TYPEDEFS(ProcessorParamsLoopClosure); + +struct ProcessorParamsLoopClosure : public ProcessorParamsBase +{ + using ProcessorParamsBase::ProcessorParamsBase; + // virtual ~ProcessorParamsLoopClosure() = 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 : + * - voteComputeFeatures() + * - voteSearchLoopClosure() + * - computeFeatures() + * - findLoopCandidate() + * - createFactors() + * + You can override the following classes : + * - selectPairKC() + * - validateLoop() + * - processLoopClosure() + * + * It establishes factors XXX + * + * Should you need extra functionality for your derived types, + * you can overload these two methods, + * + * - preProcess() { } + * - postProcess() { } + * + * which are called at the beginning and at the end of process() respectively. + */ + +class ProcessorLoopClosure : public ProcessorBase +{ +protected: + + ProcessorParamsLoopClosurePtr params_loop_closure_; + +public: + + ProcessorLoopClosure(const std::string& _type, ProcessorParamsLoopClosurePtr _params_loop_closure); + + virtual ~ProcessorLoopClosure() = default; + virtual void configure(SensorBasePtr _sensor) override { }; + + /** \brief Full processing of an incoming Capture. + * + * Usually you do not need to overload this method in derived classes. + * Overload it only if you want to alter this algorithm. + */ + virtual void process(CaptureBasePtr _incoming_ptr) override; + +protected: + + /** \brief Called by process(). Tells if computeFeatures() will be called + */ + virtual bool voteComputeFeatures() = 0; + + /** \brief Called by process(). Tells if findLoopCandidate() and createFactors() will be called + * + * WARNING : A LC can be searched only when voteComputeFeatures() return true + */ + virtual bool voteSearchLoopClosure() = 0; + + /** \brief returns a KeyFrame-Capture pair compatible together (selected from the buffers) + * + * Should clear elements before the ones selected in buffers. + * In the default implementation, we select the KF with the most recent TimeStamp + * and that is compatible with at least a Capture + */ + virtual std::pair<FrameBasePtr,CaptureBasePtr> selectPairKC(void); + + /** \brief add the Capture and all features needed to the corresponding KF + * + * If the loop closure process requires features associated to each capture, + * the computations to create these features must be done here. + * + * Important: All detected features should be emplaced to the capture. + * + * Returns a bool that tells if features were successfully created + */ + virtual bool detectFeatures(CaptureBasePtr cap) = 0; + + /** \brief Find a KF that would be a good candidate to close a loop + * if validateLoop is not overwritten, a loop will be closed with the returned candidate + * if no good candidate is found, return nullptr + */ + virtual CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) = 0; + + /** \brief validate/discard a loop closure + * + * overwrite it if you want an additional test after findLoopCandidate() + */ + virtual bool validateLoop(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) {return true;}; + + /** \brief emplace the factor(s) + * + */ + virtual void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) = 0; + + + /** Pre-process incoming Capture + * + * This is called by process() just after assigning incoming_ptr_ to a valid Capture. + * + * Overload this function to prepare stuff on derived classes. + * + * Typical uses of prePrecess() are: + * - casting base types to derived types + * - initializing counters, flags, or any derived variables + * - initializing algorithms needed for processing the derived data + */ + virtual void preProcess() { } + + /** Post-process + * + * This is called by process() after finishing the processing algorithm. + * + * Overload this function to post-process stuff on derived classes. + * + * Typical uses of postPrecess() are: + * - resetting and/or clearing variables and/or algorithms at the end of processing + * - drawing / printing / logging the results of the processing + */ + virtual void postProcess() { } + + /** \brief Vote for KeyFrame generation + * + * If a KeyFrame criterion is validated, this function returns true, + * meaning that it wants to create a KeyFrame at the \b last Capture. + * + * WARNING! This function only votes! It does not create KeyFrames! + */ + bool voteForKeyFrame() + { + return false; + }; +}; + +} // namespace wolf + +#endif /* _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H */ diff --git a/include/core/processor/processor_loopclosure_base.h b/include/core/processor/processor_loopclosure_base.h deleted file mode 100644 index a5dc8589156b6b525b0a9b551ec3dc55ab7f5af7..0000000000000000000000000000000000000000 --- a/include/core/processor/processor_loopclosure_base.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H -#define _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H - -// Wolf related headers -#include "core/processor/processor_base.h" - -namespace wolf{ - -WOLF_STRUCT_PTR_TYPEDEFS(ProcessorParamsLoopClosure); - -struct ProcessorParamsLoopClosure : public ProcessorParamsBase -{ - using ProcessorParamsBase::ProcessorParamsBase; -// virtual ~ProcessorParamsLoopClosure() = default; - - // add neccesery parameters for loop closure initialisation here and initialize - // them in constructor -}; - -/** \brief General loop closure processor - * - * This is an abstract class. - * - * It establishes factors XXX - * - * Should you need extra functionality for your derived types, - * you can overload these two methods, - * - * - preProcess() { } - * - postProcess() { } - * - * which are called at the beginning and at the end of process() respectively. - */ - -class ProcessorLoopClosureBase : public ProcessorBase -{ -protected: - - ProcessorParamsLoopClosurePtr params_loop_closure_; - - // Frames that are possible loop closure candidates according to - // findLoopClosure() - std::vector<FrameBasePtr> loop_closure_candidates; - - // Frames that are possible loop closure candidates according to - // findLoopClosure(), but are too recent in the timeline, aka still in a - // 'buffer zone'. This vector will capture the frames that were set just before - // the last keyframe. - std::vector<FrameBasePtr> close_candidates; - -public: - - ProcessorLoopClosureBase(const std::string& _type, ProcessorParamsLoopClosurePtr _params_loop_closure); - - virtual ~ProcessorLoopClosureBase() = default; - virtual void configure(SensorBasePtr _sensor) override { }; - - /** \brief Full processing of an incoming Capture. - * - * Usually you do not need to overload this method in derived classes. - * Overload it only if you want to alter this algorithm. - */ - virtual void process(CaptureBasePtr _incoming_ptr) override; - - virtual void keyFrameCallback(FrameBasePtr _keyframe_ptr, - const Scalar& _time_tol_other) override ; - - const std::vector<FrameBasePtr>& getCandidates() const noexcept; - - const std::vector<FrameBasePtr>& getCloseCandidates() const noexcept; - -protected: - - /** Pre-process incoming Capture - * - * This is called by process() just after assigning incoming_ptr_ to a valid Capture. - * - * Overload this function to prepare stuff on derived classes. - * - * Typical uses of prePrecess() are: - * - casting base types to derived types - * - initializing counters, flags, or any derived variables - * - initializing algorithms needed for processing the derived data - */ - virtual void preProcess() { } - - /** Post-process - * - * This is called by process() after finishing the processing algorithm. - * - * Overload this function to post-process stuff on derived classes. - * - * Typical uses of postPrecess() are: - * - resetting and/or clearing variables and/or algorithms at the end of processing - * - drawing / printing / logging the results of the processing - */ - virtual void postProcess() { } - - /** Find a loop closure between incoming data and all keyframe data - * - * This is called by process() . - * - * Overload this function in derived classes to find loop closure. - */ - virtual bool findCandidates(const CaptureBasePtr& _incoming_ptr) = 0; - - /** perform a validation among the found possible loop closures to confirm - * or dismiss them based on available data - * - * This is called by process() . - * - * Overload this function in derived classes to confirm loop closure. - */ - virtual bool confirmLoopClosure() = 0; - - /** \brief Vote for KeyFrame generation - * - * If a KeyFrame criterion is validated, this function returns true, - * meaning that it wants to create a KeyFrame at the \b last Capture. - * - * WARNING! This function only votes! It does not create KeyFrames! - */ - virtual bool voteForKeyFrame() override = 0; -}; - -inline const std::vector<FrameBasePtr>& -ProcessorLoopClosureBase::getCandidates() const noexcept -{ - return loop_closure_candidates; -} - -inline const std::vector<FrameBasePtr>& -ProcessorLoopClosureBase::getCloseCandidates() const noexcept -{ - return close_candidates; -} - -} // namespace wolf - -#endif /* _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H */ diff --git a/src/processor/processor_base.cpp b/src/processor/processor_base.cpp index 5c2dd85aeb423f4879ac12c135cd18e08233c3a1..a70930c6b936ef898db10f22a01d8b8010cdc1c3 100644 --- a/src/processor/processor_base.cpp +++ b/src/processor/processor_base.cpp @@ -54,7 +54,14 @@ void ProcessorBase::keyFrameCallback(FrameBasePtr _keyframe_ptr, const Scalar& _ { WOLF_DEBUG("P", isMotion() ? "M " : "T ", getName(), ": KF", _keyframe_ptr->id(), " callback received with ts = ", _keyframe_ptr->getTimeStamp()); if (_keyframe_ptr != nullptr) - buffer_pack_kf_.add(_keyframe_ptr,_time_tol_other); + buffer_pack_kf_.add(_keyframe_ptr, _time_tol_other); +} + +void ProcessorBase::captureCallback(CaptureBasePtr _capture_ptr) +{ + WOLF_DEBUG("P", isMotion() ? "M " : "T ", getName(), ": Capture", _capture_ptr->id(), " callback received with ts = ", _capture_ptr->getTimeStamp()); + if (_capture_ptr != nullptr) + buffer_capture_.add(_capture_ptr->getTimeStamp(), _capture_ptr); } void ProcessorBase::remove() @@ -93,17 +100,11 @@ void ProcessorBase::remove() } ///////////////////////////////////////////////////////////////////////////////////////// -void BufferPackKeyFrame::removeUpTo(const TimeStamp& _time_stamp) -{ - BufferPackKeyFrame::Iterator post = container_.upper_bound(_time_stamp); - container_.erase(container_.begin(), post); // erasing by range -} - void BufferPackKeyFrame::add(const FrameBasePtr& _key_frame, const Scalar& _time_tolerance) { TimeStamp time_stamp = _key_frame->getTimeStamp(); PackKeyFramePtr kfpack = std::make_shared<PackKeyFrame>(_key_frame, _time_tolerance); - container_.emplace(time_stamp, kfpack); + Buffer::add(time_stamp, kfpack); } PackKeyFramePtr BufferPackKeyFrame::selectPack(const TimeStamp& _time_stamp, const Scalar& _time_tolerance) @@ -122,13 +123,13 @@ PackKeyFramePtr BufferPackKeyFrame::selectPack(const TimeStamp& _time_stamp, con bool prev_exists = (post != container_.begin()); bool post_exists = (post != container_.end()); - bool post_ok = post_exists && checkTimeTolerance(post->first, post->second->time_tolerance, _time_stamp, _time_tolerance); + bool post_ok = post_exists && doubleCheckTimeTolerance(post->first, post->second->time_tolerance, _time_stamp, _time_tolerance); if (prev_exists) { BufferPackKeyFrame::Iterator prev = std::prev(post); - bool prev_ok = checkTimeTolerance(prev->first, prev->second->time_tolerance, _time_stamp, _time_tolerance); + bool prev_ok = doubleCheckTimeTolerance(prev->first, prev->second->time_tolerance, _time_stamp, _time_tolerance); if (prev_ok && !post_ok) return prev->second; @@ -170,7 +171,7 @@ PackKeyFramePtr BufferPackKeyFrame::selectFirstPackBefore(const TimeStamp& _time return container_.begin()->second; // Return first pack if despite being newer, it is within the time tolerance - if (checkTimeTolerance(container_.begin()->first, container_.begin()->second->time_tolerance, _time_stamp, _time_tolerance)) + if (doubleCheckTimeTolerance(container_.begin()->first, container_.begin()->second->time_tolerance, _time_stamp, _time_tolerance)) return container_.begin()->second; // otherwise return nullptr (no pack before the provided ts or within the tolerance was found) @@ -193,12 +194,4 @@ void BufferPackKeyFrame::print(void) std::cout << "]" << std::endl; } -bool BufferPackKeyFrame::checkTimeTolerance(const TimeStamp& _time_stamp1, const Scalar& _time_tolerance1, - const TimeStamp& _time_stamp2, const Scalar& _time_tolerance2) -{ - Scalar time_diff = std::fabs(_time_stamp1 - _time_stamp2); - Scalar time_tol = std::min(_time_tolerance1, _time_tolerance2); - bool pass = time_diff <= time_tol; - return pass; -} } // namespace wolf diff --git a/src/processor/processor_frame_nearest_neighbor_filter.cpp b/src/processor/processor_frame_nearest_neighbor_filter.cpp deleted file mode 100644 index b4f6303b128b37ba74104cddf02dd82e328c08e8..0000000000000000000000000000000000000000 --- a/src/processor/processor_frame_nearest_neighbor_filter.cpp +++ /dev/null @@ -1,506 +0,0 @@ -#include "core/processor/processor_frame_nearest_neighbor_filter.h" - -namespace wolf -{ -ProcessorFrameNearestNeighborFilter::ProcessorFrameNearestNeighborFilter(ParamsPtr _params_NNF): - ProcessorLoopClosureBase("FRAME NEAREST NEIGHBOR FILTER", _params_NNF), - params_NNF(_params_NNF) -{ - // area of ellipse based on the Chi-Square Probabilities - // https://people.richland.edu/james/lecture/m170/tbl-chi.html - // cover both 2D and 3D cases - - if(params_NNF->distance_type_ == DistanceType::LC_POINT_ELLIPSE || - params_NNF->distance_type_ == DistanceType::LC_ELLIPSE_ELLIPSE) - { - switch ((int)(params_NNF->probability_*100)) - { - case 900: area_ = 4.605; // 90% probability - break; - case 950: area_ = 5.991; // 95% probability - break; - case 990: area_ = 9.210; // 99% probability - break; - default : area_ = 5.991; // 95% probability - break; - } - } - if (params_NNF->distance_type_ == DistanceType::LC_POINT_ELLIPSOID || - params_NNF->distance_type_ == DistanceType::LC_ELLIPSOID_ELLIPSOID) - { - switch ((int)(params_NNF->probability_*100)) - { - case 900: area_ = 6.251; // 90% probability - break; - case 950: area_ = 7.815; // 95% probability - break; - case 990: area_ = 11.345; // 99% probability - break; - default : area_ = 7.815; // 95% probability - break; - } - } - - /* - * The area is based on the Chi-Square Probabilities - * Becasue they are very big for high likelihood, it is here manually set - * on ONE. Therefore the ellipses/ ellipsoids are based on the unit ellipse/ - * ellipsoids and the axis are scaled by a / b / c - */ - - area_ = 1; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::findCandidates(const CaptureBasePtr& /*_incoming_ptr*/) -{ - const ProblemPtr problem_ptr = getProblem(); - - const std::string frame_structure = - problem_ptr->getTrajectory()->getFrameStructure(); - - // get the list of all frames - const FrameBasePtrList& frame_list = problem_ptr-> - getTrajectory()-> - getFrameList(); - - bool found_possible_candidate = false; - - for (const FrameBasePtr& key_it : frame_list) - { - // check for LC just if frame is key frame - // Assert that the evaluated KF has a capture of the - // same sensor as this processor - if (key_it->isKey() && key_it->getCaptureOf(getSensor()/*, "LASER 2D"*/) != nullptr) - { - // Check if the two frames currently evaluated are already - // constrained one-another. - const FactorBasePtrList& fac_list = key_it->getConstrainedByList(); - - bool are_constrained = false; - for (const FactorBasePtr& crt : fac_list) - { - // Are the two frames constrained one-another ? - if (crt->getFrameOther() == problem_ptr->getLastKeyFrame()) - { - // By this very processor ? - if (crt->getProcessor() == shared_from_this()) - { - WOLF_DEBUG("Frames are already constrained together."); - are_constrained = true; - break; - } - } - } - if (are_constrained) continue; - - // check if feame is potential candidate for loop closure with - // chosen distance type - switch (params_NNF->distance_type_) - { - case LoopclosureDistanceType::LC_POINT_ELLIPSE: - { - if (frame_structure == "PO 3D" || frame_structure == "POV 3D") // @todo add frame structure "PQVBB 3D" - { - WOLF_ERROR("Distance Type LC_POINT_ELLIPSE does not fit 3D frame structure"); - return false; - } - - Eigen::Vector5s frame_covariance; - if (!computeEllipse2D(key_it, frame_covariance)) continue; - - const Eigen::VectorXs current_position = getProblem()->getCurrentState(); - found_possible_candidate = point2DIntersect(current_position, - frame_covariance); - break; - } - - case LoopclosureDistanceType::LC_ELLIPSE_ELLIPSE: - { - if (frame_structure == "PO 3D" || frame_structure == "POV 3D") - { - WOLF_ERROR("Distance Type LC_ELLIPSE_ELLIPSE does not fit 3D frame structure"); - return false; - } - - Eigen::Vector5s frame_covariance, current_covariance; - if (!computeEllipse2D(key_it, - frame_covariance)) continue; - if (!computeEllipse2D(getProblem()->getLastKeyFrame(), - current_covariance)) continue; - found_possible_candidate = ellipse2DIntersect(frame_covariance, - current_covariance); - break; - } - - case LoopclosureDistanceType::LC_POINT_ELLIPSOID: - { - if (frame_structure == "PO 2D") - { - WOLF_ERROR("Distance Type POINT_ELLIPSOID does not fit 2D frame structure"); - return false; - } - Eigen::Vector10s frame_covariance; - if (!computeEllipsoid3D(key_it, - frame_covariance)) continue; - const Eigen::VectorXs current_position = getProblem()->getCurrentState(); - found_possible_candidate = point3DIntersect(current_position, - frame_covariance); - break; - } - - case LoopclosureDistanceType::LC_ELLIPSOID_ELLIPSOID: - { - if (frame_structure == "PO 2D") - { - WOLF_ERROR("Distance Type ELLIPSOID_ELLIPSOID does not fit 2D frame structure"); - return false; - } - Eigen::Vector10s frame_covariance, current_covariance; - if (!computeEllipsoid3D(key_it, - frame_covariance)) continue; - if (!computeEllipsoid3D(getProblem()->getLastKeyFrame(), - frame_covariance)) continue; - found_possible_candidate = ellipsoid3DIntersect(frame_covariance, - current_covariance); - break; - } - - case LoopclosureDistanceType::LC_MAHALANOBIS_DISTANCE: - { - found_possible_candidate = insideMahalanisDistance(key_it, - problem_ptr->getLastKeyFrame()); - break; - } - - default: - WOLF_WARN("NO IMPLEMENTATION FOR OTHER DISTANCE CALCULATION TYPES YET."); - found_possible_candidate = false; - return false; - } - - // if a candidate was detected, push it to the appropiate list - if (found_possible_candidate) - { - if (!frameInsideBuffer(key_it)) - loop_closure_candidates.push_back(key_it); - else - close_candidates.push_back(key_it); - } - - } // end if key_it is keyframe - } // end loop through every frame in list - - return found_possible_candidate; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::computeEllipse2D(const FrameBasePtr& frame_ptr, - Eigen::Vector5s& ellipse) -{ - // get 3x3 covariance matrix AKA variance - Eigen::MatrixXs covariance; - if (!getProblem()->getFrameCovariance(frame_ptr, covariance)) - { - WOLF_WARN("Frame covariance not found!"); - return false; - } - - if (!isCovariance(covariance)) - { - WOLF_WARN("Covariance is not proper !"); - return false; - } - - // get position of frame AKA mean [x, y] - const Eigen::VectorXs frame_position = frame_ptr->getP()->getState(); - ellipse(0) = frame_position(0); - ellipse(1) = frame_position(1); - - // compute covariance error ellipse around state - Eigen::SelfAdjointEigenSolver<Eigen::Matrix2s> solver(covariance.block(0,0,2,2)); - Scalar eigenvalue1 = solver.eigenvalues()[0]; // smaller value - Scalar eigenvalue2 = solver.eigenvalues()[1]; // bigger value - //Eigen::VectorXs eigenvector1 = solver.eigenvectors().col(0); - Eigen::VectorXs eigenvector2 = solver.eigenvectors().col(1); - - const Scalar scale = std::sqrt(area_); - ellipse(2) = scale * std::sqrt(eigenvalue2) / 2.; // majorAxis - ellipse(3) = scale * std::sqrt(eigenvalue1) / 2.; // minorAxis - ellipse(4) = std::atan2(eigenvector2[1], eigenvector2[0]); // tilt - - if (ellipse(4) < Scalar(0)) ellipse(4) += Scalar(2) * M_PI; - - // [ pos_x, pos_y, a, b, tilt] - return true; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::computeEllipsoid3D(const FrameBasePtr& frame_pointer, - Eigen::Vector10s& ellipsoid) -{ - // Ellipse description [ pos_x, pos_y, pos_z, a, b, c, quat_w, quat_z, quat_y, quat_z] - - // get position of frame AKA mean [x, y, z] - const Eigen::VectorXs frame_position = frame_pointer->getP()->getState(); - ellipsoid(0) = frame_position(0); - ellipsoid(1) = frame_position(1); - ellipsoid(2) = frame_position(2); - - // get 9x9 covariance matrix AKA variance - Eigen::MatrixXs covariance; - if (!getProblem()->getFrameCovariance(frame_pointer, covariance)) - { - WOLF_WARN("Frame covariance not found!"); - return false; - } - - if (!isCovariance(covariance)) - { - WOLF_WARN("Covariance is not proper !"); - return false; - } - - Eigen::SelfAdjointEigenSolver<Eigen::Matrix3s> solver(covariance.block(0,0,3,3)); - const Scalar eigenvalue1 = solver.eigenvalues()[0]; // smaller value - const Scalar eigenvalue2 = solver.eigenvalues()[1]; // mediate value - const Scalar eigenvalue3 = solver.eigenvalues()[2]; // bigger value - - const Scalar scale = std::sqrt(area_); - - ellipsoid(3) = scale * std::sqrt(std::abs(eigenvalue3)) / Scalar(2); // majorAxis - ellipsoid(4) = scale * std::sqrt(std::abs(eigenvalue2)) / Scalar(2); // mediumAxis - ellipsoid(5) = scale * std::sqrt(std::abs(eigenvalue1)) / Scalar(2); // minorAxis - - // ROTATION COMPUTATION get rotation of the three axis from eigenvector - // eigenvector belonging to biggest eigenvalue gives direction / z_axis - // of ellipsoid - - // get eigenvectors that correspond to eigenvalues - //const Eigen::Vector3s eigenvector1 = solver.eigenvectors().col(0); // minorAxis - const Eigen::Vector3s eigenvector2 = solver.eigenvectors().col(1); // mediumAxis ->y - const Eigen::Vector3s eigenvector3 = solver.eigenvectors().col(2); // majorAxis -> x - - // computed axis of coordinate system in ellipsoid center - const Eigen::Vector3s z_axis = eigenvector3.cross(eigenvector2); - const Eigen::Vector3s y_axis = z_axis.cross(eigenvector3); - - // use them to fill rotation matrix - Eigen::Matrix3s rotationMatrix; - rotationMatrix.col(0) = eigenvector3.normalized(); - rotationMatrix.col(1) = y_axis.normalized(); - rotationMatrix.col(2) = z_axis.normalized(); - - // get quaternions for transformation - Eigen::Quaternions rotation(rotationMatrix); - rotation.normalize(); - - ellipsoid(6) = rotation.w(); - ellipsoid(7) = rotation.x(); - ellipsoid(8) = rotation.y(); - ellipsoid(9) = rotation.z(); - - WOLF_DEBUG("made ellipsoid: \n[", ellipsoid.transpose(), "]"); - - // [ pos_x, pos_y, pos_z, a, b, c, alpha, beta, gamma] - return true; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::ellipse2DIntersect(const Eigen::VectorXs& ellipse1, - const Eigen::VectorXs& ellipse2) -{ - for (int i = 0 ; i < 360 ; i += params_NNF->sample_step_degree_) - { - // parameterized form of first ellipse gives point on first ellipse - // https://www.uwgb.edu/dutchs/Geometry/HTMLCanvas/ObliqueEllipses5.HTM - Eigen::VectorXs pointOnEllipse(2); - const Scalar theta = Scalar(i) * M_PI / 180.0; - - pointOnEllipse(0) = ellipse1(0) + - ellipse1(2) * std::cos(ellipse1(4)) * std::cos(theta) - - ellipse1(3) * std::sin(ellipse1(4)) * std::sin(theta); - - pointOnEllipse(1) = ellipse1(1) + - ellipse1(2) * std::sin(ellipse1(4)) * std::cos(theta) + - ellipse1(3) * std::cos(ellipse1(4)) * std::sin(theta); - - //WOLF_DEBUG(" for ", i, " deg / ", theta " rad ---> [" , - // pointOnEllipse.transpose(), "]"); - - // check if point lies inside the other ellipse - if (point2DIntersect(pointOnEllipse, ellipse2)) return true; - } - - return false; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::point2DIntersect(const Eigen::VectorXs& point, - const Eigen::VectorXs& ellipse) -{ - const Scalar area51 = std::pow( ((point(0) - ellipse(0)) * cos(ellipse(4)) - + (point(1) - ellipse(1)) * sin(ellipse(4))), 2 ) - / std::pow( ellipse(2), 2 ) - + std::pow( ((point(0) - ellipse(0)) * sin(ellipse(4)) - - (point(1) - ellipse(1)) * cos(ellipse(4))), 2 ) - / std::pow( ellipse(3), 2 ); - - //WOLF_DEBUG("computed area = ", area51); - - if ( area51 - area_ <= 0) return true; - - return false; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::ellipsoid3DIntersect(const Eigen::VectorXs &ellipsoid1, - const Eigen::VectorXs &ellipsoid2) -{ - // get transformation from elliposiod1 center frame to world frame - Eigen::Matrix4s transformation_matrix; - Eigen::Quaternions rotation(ellipsoid1(6),ellipsoid1(7), - ellipsoid1(8),ellipsoid1(9)); -// Eigen::Vector4s translation; -// translation << ellipsoid1(0), ellipsoid1(1), ellipsoid1(2), 1; - transformation_matrix.block(0,0,3,3) = rotation.toRotationMatrix(); - transformation_matrix.col(3) << ellipsoid1(0), ellipsoid1(1), ellipsoid1(2), 1; - - Eigen::Vector4s point_hom = Eigen::Vector4s::Constant(1); - - Scalar omega = 0; - for (int i = 0 ; i < 360 ; i += params_NNF->sample_step_degree_) - { - const Scalar theta = Scalar(i) * M_PI / 180.0; - for( int j = 0 ; j < 180 ; j += params_NNF->sample_step_degree_) - { - omega = Scalar(j) * M_PI / 180.0; - - // compute point on surface of first ellipsoid - point_hom(0) = ellipsoid1(3) * std::cos(theta) * std::sin(omega); - point_hom(1) = ellipsoid1(4) * std::sin(theta) * std::sin(omega); - point_hom(2) = ellipsoid1(5) * std::cos(omega); - - // transform point into world frame - const Eigen::Vector4s point_on_ellipsoid = transformation_matrix * point_hom; - - // check if 3D point lies inside the second ellipsoid - if (point3DIntersect(point_on_ellipsoid, ellipsoid2)) - return true; - } - } - return false; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::point3DIntersect(const Eigen::VectorXs &point, - const Eigen::VectorXs &ellipsoid) -{ - // get transformation from ellipsoid center frame to world frame - Eigen::Matrix4s transformation_matrix = Eigen::Matrix4s::Identity(); - - Eigen::Quaternions rotation(ellipsoid(6),ellipsoid(7), - ellipsoid(8),ellipsoid(9)); -// Eigen::Vector4s translation; -// translation << ellipsoid(0), ellipsoid(1), ellipsoid(2), 1; - transformation_matrix.block(0,0,3,3) = rotation.toRotationMatrix(); - transformation_matrix.col(3) << ellipsoid(0), ellipsoid(1), ellipsoid(2), 1; - // inverse to get transformation from world frame to ellipsoid center frame - transformation_matrix = transformation_matrix.inverse().eval(); - - // homogenize 3D point - // ??? - Eigen::Vector4s point_hom; - point_hom << point(0), point(1), point(2), 1; - - // transform point from world frame to elliposiod center frame - Eigen::Vector4s transformed_point = transformation_matrix * point_hom; - - // check if point is inside ellipsoid with general equation - Scalar area51 = std::pow(transformed_point(0), 2) / std::pow( ellipsoid(3), 2) + - std::pow(transformed_point(1), 2) / std::pow(ellipsoid(4), 2) + - std::pow(transformed_point(2), 2) / std::pow(ellipsoid(5), 2); - - WOLF_DEBUG("computed area = ", area51); - - if (area51 - area_ <= 0) return true; - - return false; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::insideMahalanisDistance(const FrameBasePtr& trajectory_frame, - const FrameBasePtr& query_frame) -{ - const Scalar distance = MahalanobisDistance(trajectory_frame, query_frame); - - WOLF_DEBUG("Mahalanobis Distance = ", distance); - - if ( distance < 0 ) - { - WOLF_DEBUG("NO COVARIANCE AVAILABLE"); - } - - /* - * TODO: - * check if distance is smaller than a threshold, - * if yes -> return true - * else -> return false - * - */ - return false; -} - -//############################################################################## -Scalar ProcessorFrameNearestNeighborFilter::MahalanobisDistance(const FrameBasePtr& trajectory, - const FrameBasePtr& query) -{ - Scalar distance = -1; - Eigen::VectorXs traj_pose, query_pose; - - // get state and covariance matrix for both frames - if (trajectory->getP()->getState().size() == 2) - { - traj_pose.resize(3); - query_pose.resize(3); - } - else - { - traj_pose.resize(7); - query_pose.resize(7); - } - - traj_pose << trajectory->getP()->getState(), - trajectory->getO()->getState(); - - query_pose << query->getP()->getState(), - query->getO()->getState(); - - Eigen::MatrixXs traj_covariance, query_covariance; - if ( !getProblem()->getFrameCovariance(trajectory, traj_covariance) || - !getProblem()->getFrameCovariance(query, query_covariance) || - !isCovariance(traj_covariance) || - !isCovariance(query_covariance)) - return distance; - - const Eigen::MatrixXs covariance = traj_covariance * query_covariance.transpose(); - - const Eigen::VectorXs pose_difference = traj_pose - query_pose; - distance = pose_difference.transpose() * covariance * pose_difference; - distance = std::sqrt(distance); - - return distance; -} - -//############################################################################## -bool ProcessorFrameNearestNeighborFilter::frameInsideBuffer(const FrameBasePtr& frame_ptr) -{ - FrameBasePtr keyframe = getProblem()->getLastKeyFrame(); - if ( (int)frame_ptr->id() < ( (int)keyframe->id() - params_NNF->buffer_size_ )) - return false; - else - return true; -} - -} // namespace wolf - diff --git a/src/processor/processor_loopclosure.cpp b/src/processor/processor_loopclosure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bbc8c1c41d601759475b810f37f004326a22cada --- /dev/null +++ b/src/processor/processor_loopclosure.cpp @@ -0,0 +1,104 @@ +/** + * \file processor_loop_closure.h + * + * Created on: Mai 31, 2019 + * \author: Pierre Guetschel + */ + +#include <core/processor/processor_loopclosure.h> + + +namespace wolf +{ + +ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type, ProcessorParamsLoopClosurePtr _params_loop_closure): + ProcessorBase(_type, _params_loop_closure), + params_loop_closure_(_params_loop_closure) +{ + // +} + +//############################################################################## +void ProcessorLoopClosure::process(CaptureBasePtr _incoming_ptr) +{ + // the pre-process, if necessary, is implemented in the derived classes + preProcess(); + + if (voteComputeFeatures()) + { + std::pair<FrameBasePtr,CaptureBasePtr> pairKC = selectPairKC(); + + + auto cap_1 = pairKC.second; + auto kf_1 = pairKC.first; + + if (kf_1==nullptr || cap_1==nullptr) return; + bool success_computeFeatures = detectFeatures(cap_1); + + // if succeded + if (success_computeFeatures) + { + // link the capture to the KF (if not already linked) + if (cap_1->getFrame() != kf_1) + { + assert(cap_1->getFrame() == nullptr && "capture already linked to a different frame"); //FIXME + cap_1->link(kf_1); + } + + // search loop closure + if(voteSearchLoopClosure()) + { + auto cap_2 = findLoopCandidate(cap_1); + if (cap_2==nullptr) + return; + if (validateLoop(cap_1, cap_2)==false) + return; + if (cap_1->getFrame() == nullptr || cap_2->getFrame() == nullptr) + { + WOLF_WARN("ProcessorLoopClosureBase : tried to close a loop with captures linked to no KF"); + return; + } + if (cap_1->getFrame() == cap_2->getFrame()) + { + WOLF_WARN("ProcessorLoopClosureBase : findLoopCandidate() returned two captures of the same frame"); + return; + } + emplaceFactors(cap_1, cap_2); + } + } + }; + + // the post-process, if necessary, is implemented in the derived classes + postProcess(); +} + +/** + * In the default implementation, we select the KF with the most recent TimeStamp + * and that is compatible with at least a Capture + */ +std::pair<FrameBasePtr,CaptureBasePtr> ProcessorLoopClosure::selectPairKC() +{ + std::map<TimeStamp,PackKeyFramePtr> kf_container = buffer_pack_kf_.getContainer(); + if (kf_container.empty()){ + return std::make_pair(nullptr, nullptr);}; + + for (auto kf_it=kf_container.begin(); kf_it!=kf_container.end(); ++kf_it) + { + CaptureBasePtr cap_ptr = buffer_capture_.select(kf_it->first, kf_it->second->time_tolerance); + if (cap_ptr != nullptr) + { + // clear the buffers : + buffer_capture_.removeUpTo(cap_ptr->getTimeStamp()); + buffer_pack_kf_.removeUpTo(kf_it->first); + // return the KF-Cap pair : + return std::make_pair(kf_it->second->key_frame, cap_ptr); + }; + } + return std::make_pair(nullptr, nullptr); +} + + +//############################################################################## + + +}// namespace wolf diff --git a/src/processor/processor_loopclosure_base.cpp b/src/processor/processor_loopclosure_base.cpp deleted file mode 100644 index 7ce19414499ead4b38e8432077f47e15116ca27e..0000000000000000000000000000000000000000 --- a/src/processor/processor_loopclosure_base.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * \file processor_loop_closure.h - * - * Created on: Aug 10, 2017 - * \author: Tessa Johanna - */ - -#include "core/processor/processor_loopclosure_base.h" - -namespace wolf -{ - -ProcessorLoopClosureBase::ProcessorLoopClosureBase(const std::string& _type, ProcessorParamsLoopClosurePtr _params_loop_closure): - ProcessorBase(_type, _params_loop_closure), - params_loop_closure_(_params_loop_closure) -{ - // -} - -//############################################################################## -void ProcessorLoopClosureBase::process(CaptureBasePtr _incoming_ptr) -{ - // clear all previous data from vector - loop_closure_candidates.clear(); - close_candidates.clear(); - - // the pre-process, if necessary, is implemented in the derived classes - preProcess(); - - findCandidates(_incoming_ptr); - - confirmLoopClosure(); - - WOLF_DEBUG("ProcessorLoopClosureBase::process found ", - loop_closure_candidates.size(), " candidates found."); - - // the post-process, if necessary, is implemented in the derived classes - postProcess(); -} - -//############################################################################## -void ProcessorLoopClosureBase::keyFrameCallback(FrameBasePtr /*_keyframe_ptr*/, - const Scalar& /*_time_tol_other*/) -{ - // -} - -}// namespace wolf - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2bdc605a30dc5f70f4028c6c5007f8b731b48467..f4d96771cf0b3b1c7d8be66202d129296bb02daf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -178,9 +178,9 @@ target_link_libraries(gtest_map_yaml ${PROJECT_NAME}) wolf_add_gtest(gtest_param_prior gtest_param_prior.cpp) target_link_libraries(gtest_param_prior ${PROJECT_NAME}) -# ProcessorFrameNearestNeighborFilter class test -wolf_add_gtest(gtest_processor_frame_nearest_neighbor_filter_2D gtest_processor_frame_nearest_neighbor_filter_2D.cpp) -target_link_libraries(gtest_processor_frame_nearest_neighbor_filter_2D ${PROJECT_NAME}) +# ProcessorLoopClosureBase class test +wolf_add_gtest(gtest_processor_loopclosure gtest_processor_loopclosure.cpp) +target_link_libraries(gtest_processor_loopclosure ${PROJECT_NAME}) # ProcessorMotion in 2D wolf_add_gtest(gtest_odom_2D gtest_odom_2D.cpp) diff --git a/test/gtest_processor_frame_nearest_neighbor_filter_2D.cpp b/test/gtest_processor_frame_nearest_neighbor_filter_2D.cpp deleted file mode 100644 index 82b3d2ae02e1e1821b9dca28b35792f6855a8f5e..0000000000000000000000000000000000000000 --- a/test/gtest_processor_frame_nearest_neighbor_filter_2D.cpp +++ /dev/null @@ -1,228 +0,0 @@ - -/** - * \file gtest_processor_frame_nearest_neighbor_filter.cpp - * - * Created on: Aug 2, 2017 - * \author: tessajohanna - */ - -#include "core/utils/utils_gtest.h" -#include "core/utils/logging.h" - -#include "core/sensor/sensor_odom_2D.h" -#include "core/processor/processor_frame_nearest_neighbor_filter.h" - -#include <iostream> - -// Dummy class so that it is no pur virtual -struct DummyLoopCloser : public wolf::ProcessorFrameNearestNeighborFilter -{ - DummyLoopCloser(ParamsPtr _params) : - wolf::ProcessorFrameNearestNeighborFilter(_params) { } - - bool confirmLoopClosure() override { return false; } - bool voteForKeyFrame() override { return false; } -}; - -// Declare Wolf problem -wolf::ProblemPtr problem = wolf::Problem::create("PO", 2); - -// Declare Sensor -Eigen::Vector3s odom_extrinsics = Eigen::Vector3s(0,0,0); - -std::shared_ptr<wolf::IntrinsicsOdom2D> odom_intrinsics = - std::make_shared<wolf::IntrinsicsOdom2D>(wolf::IntrinsicsOdom2D()); - -wolf::SensorBasePtr sensor_ptr; - -// Declare Processors -wolf::ProcessorFrameNearestNeighborFilterPtr processor_ptr_point2d; -wolf::ProcessorParamsFrameNearestNeighborFilterPtr processor_params_point2d = - std::make_shared<wolf::ProcessorParamsFrameNearestNeighborFilter>(); - -wolf::ProcessorFrameNearestNeighborFilterPtr processor_ptr_ellipse2d; -wolf::ProcessorParamsFrameNearestNeighborFilterPtr processor_params_ellipse2d = - std::make_shared<wolf::ProcessorParamsFrameNearestNeighborFilter>(); - -// Declare Frame Pointer -wolf::StateBlockPtr stateblock_ptr1, stateblock_ptr2, stateblock_ptr3, stateblock_ptr4; -wolf::FrameBasePtr F1, F2, F3, F4; -wolf::CaptureBasePtr capture_dummy, incomming_dummy; - -//############################################################################## -TEST(ProcessorFrameNearestNeighborFilter, PointInEllipseRotated) -{ - // four different states [x, y] - Eigen::Vector3s state1, state2, state3, state4; - state1 << 3.0, 5.0, 0.0; - state2 << 3.0, 6.0, 0.0; - state3 << 3.0, 7.0, 0.0; - state4 << 100.0, 100.0, 0.0; - - - // create Keyframes - F1 = problem->emplaceFrame(wolf::KEY, state1, 1); - F2 = problem->emplaceFrame(wolf::KEY, state2, 2); - F3 = problem->emplaceFrame(wolf::KEY, state3, 3); - F4 = problem->emplaceFrame(wolf::KEY, state4, 4); - - auto stateblock_pptr1 = F1->getP(); - auto stateblock_optr1 = F1->getO(); - - auto stateblock_pptr2 = F2->getP(); - auto stateblock_optr2 = F2->getO(); - - auto stateblock_pptr3 = F3->getP(); - auto stateblock_optr3 = F3->getO(); - - auto stateblock_pptr4 = F4->getP(); - auto stateblock_optr4 = F4->getO(); - - // add dummy capture - wolf::CaptureBase::emplace<wolf::CaptureBase>(F1, "DUMMY", 1.0, sensor_ptr); - wolf::CaptureBase::emplace<wolf::CaptureBase>(F2, "DUMMY", 1.0, sensor_ptr); - wolf::CaptureBase::emplace<wolf::CaptureBase>(F3, "DUMMY", 1.0, sensor_ptr); - wolf::CaptureBase::emplace<wolf::CaptureBase>(F4, "DUMMY", 1.0, sensor_ptr); - - // capture_dummy = std::make_shared<wolf::CaptureBase>("DUMMY", - // 1.0, - // sensor_ptr); - // F1->addCapture(capture_dummy); - // F2->addCapture(capture_dummy); - // F3->addCapture(capture_dummy); - // F4->addCapture(capture_dummy); - - // Add same covariances for all states - Eigen::Matrix2s position_covariance_matrix; - position_covariance_matrix << 9.0, 0.0, - 0.0, 5.0; - - Eigen::Matrix1s orientation_covariance_matrix; - orientation_covariance_matrix << 0.01; - - Eigen::Vector2s tt_covariance_matrix; - tt_covariance_matrix << 0.0, 0.0; - - problem->addCovarianceBlock( stateblock_pptr1, stateblock_pptr1, - position_covariance_matrix); - problem->addCovarianceBlock( stateblock_optr1, stateblock_optr1, - orientation_covariance_matrix); - problem->addCovarianceBlock( stateblock_pptr1, stateblock_optr1, - tt_covariance_matrix); - - problem->addCovarianceBlock( stateblock_pptr2, stateblock_pptr2, - position_covariance_matrix); - problem->addCovarianceBlock( stateblock_optr2, stateblock_optr2, - orientation_covariance_matrix); - problem->addCovarianceBlock( stateblock_pptr2, stateblock_optr2, - tt_covariance_matrix); - - problem->addCovarianceBlock( stateblock_pptr3, stateblock_pptr3, - position_covariance_matrix); - problem->addCovarianceBlock( stateblock_optr3, stateblock_optr3, - orientation_covariance_matrix); - problem->addCovarianceBlock( stateblock_pptr3, stateblock_optr3, - tt_covariance_matrix); - - problem->addCovarianceBlock( stateblock_pptr4, stateblock_pptr4, - position_covariance_matrix); - problem->addCovarianceBlock( stateblock_optr4, stateblock_optr4, - orientation_covariance_matrix); - problem->addCovarianceBlock( stateblock_pptr4, stateblock_optr4, - tt_covariance_matrix); - // create dummy capture - incomming_dummy = wolf::CaptureBase::emplace<wolf::CaptureBase>(nullptr, "DUMMY", 1.2, sensor_ptr); - - // Make 3rd frame last Key frame - F3->setTimeStamp(wolf::TimeStamp(2.0)); - problem->getTrajectory()->sortFrame(F3); - - // trigger search for loopclosure - processor_ptr_point2d->process(incomming_dummy); - - // const std::vector<wolf::FrameBasePtr> &testloops = - // processor_ptr_point2d->getCandidates(); - - //TODO: Due to changes in the emplace refactor these tests are not working. To be fixed. - // ASSERT_EQ(testloops.size(), (unsigned int) 1); - // ASSERT_EQ(testloops[0]->id(), (unsigned int) 2); -} - -//############################################################################## -TEST(ProcessorFrameNearestNeighborFilter, EllipseEllipseRotatedCrosscovariance) -{ - // set covariance to a -90 degree turned ellipse - Eigen::Matrix2s position_covariance_matrix; -// position_covariance_matrix << 9.0, 1.8, -// 1.8, 5.0; - - position_covariance_matrix << 5.0, 0.0, - 0.0, 9.0; - - problem->addCovarianceBlock( F1->getP(), F1->getP(), - position_covariance_matrix); - problem->addCovarianceBlock( F2->getP(), F2->getP(), - position_covariance_matrix); - problem->addCovarianceBlock( F3->getP(), F3->getP(), - position_covariance_matrix); - problem->addCovarianceBlock( F4->getP(), F4->getP(), - position_covariance_matrix); - - // Make 3rd frame last Key frame - F3->setTimeStamp(wolf::TimeStamp(2.0)); - problem->getTrajectory()->sortFrame(F3); - - // trigger search for loopclosure - processor_ptr_ellipse2d->process(incomming_dummy); - const std::vector<wolf::FrameBasePtr> &testloops = - processor_ptr_ellipse2d->getCandidates(); - - //TODO: Due to changes in the emplace refactor these tests are not working. To be fixed. - // ASSERT_EQ(testloops.size(), (unsigned int) 2); - // ASSERT_EQ(testloops[0]->id(), (unsigned int) 1); - // ASSERT_EQ(testloops[1]->id(), (unsigned int) 2); - - // Make 4th frame last Key frame - F4->setTimeStamp(wolf::TimeStamp(3.0)); - problem->getTrajectory()->sortFrame(F4); - - // trigger search for loopclosure again - processor_ptr_ellipse2d->process(incomming_dummy); - ASSERT_EQ(testloops.size(), (unsigned int) 0); -} - -//############################################################################## -int main(int argc, char **argv) -{ - // SENSOR PARAMETERS - odom_intrinsics->k_disp_to_disp = 0.2; - odom_intrinsics->k_rot_to_rot = 0.2; - - // install sensor - sensor_ptr = problem->installSensor("ODOM 2D", "odom", - odom_extrinsics, odom_intrinsics); - - // install the processors - processor_params_point2d->probability_ = 0.95; - processor_params_point2d->buffer_size_ = 0; // just exclude identical frameIDs - processor_params_point2d->distance_type_ = wolf::LoopclosureDistanceType::LC_POINT_ELLIPSE; - - // processor_ptr_point2d = std::make_shared<DummyLoopCloser>(processor_params_point2d); - processor_ptr_point2d = std::static_pointer_cast<wolf::ProcessorFrameNearestNeighborFilter>(wolf::ProcessorBase::emplace<DummyLoopCloser>(sensor_ptr, processor_params_point2d)); - processor_ptr_point2d->setName("processor2Dpoint"); - - // sensor_ptr->addProcessor(processor_ptr_point2d); - - processor_params_ellipse2d->probability_ = 0.95; - processor_params_ellipse2d->buffer_size_ = 0; // just exclude identical frameIDs - processor_params_ellipse2d->distance_type_ = wolf::LoopclosureDistanceType::LC_ELLIPSE_ELLIPSE; - - // processor_ptr_ellipse2d = std::make_shared<DummyLoopCloser>(processor_params_ellipse2d); - processor_ptr_ellipse2d = std::static_pointer_cast<wolf::ProcessorFrameNearestNeighborFilter>(wolf::ProcessorBase::emplace<DummyLoopCloser>(sensor_ptr, processor_params_ellipse2d)); - processor_ptr_ellipse2d->setName("processor2Dellipse"); - - // sensor_ptr->addProcessor(processor_ptr_ellipse2d); - - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/test/gtest_processor_loopclosure.cpp b/test/gtest_processor_loopclosure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f2c99aac536fb2265ad100f00f41def2aa2abeae --- /dev/null +++ b/test/gtest_processor_loopclosure.cpp @@ -0,0 +1,101 @@ + +#include "core/utils/utils_gtest.h" +#include "core/problem/problem.h" +#include "core/capture/capture_void.h" + +#include "core/processor/processor_loopclosure.h" + + +// STL +#include <iterator> +#include <iostream> + +using namespace wolf; +using namespace Eigen; + + +WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy); + +// dummy class: +class ProcessorLoopClosureDummy : public ProcessorLoopClosure +{ +private: + bool* factor_created; + +public: + ProcessorLoopClosureDummy(ProcessorParamsLoopClosurePtr _params_loop_closure, bool& factor_created): + ProcessorLoopClosure("LOOP CLOSURE DUMMY", _params_loop_closure), + factor_created(&factor_created){}; + std::pair<FrameBasePtr,CaptureBasePtr> public_selectPairKC(){ return selectPairKC();}; + +protected: + bool voteComputeFeatures() { return true;}; + bool voteSearchLoopClosure() { return true;}; + bool detectFeatures(CaptureBasePtr cap) { return true;}; + CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) { + for (FrameBasePtr kf : getProblem()->getTrajectory()->getFrameList()) { + if (kf->isKey()) { + for (CaptureBasePtr cap : kf->getCaptureList()) { + return cap; } + };} return nullptr; + }; + void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) { + std::cout << "factor created\n"; + *factor_created = true; + }; +}; + + + +TEST(ProcessorLoopClosure, installProcessor) +{ + using namespace wolf; + using std::shared_ptr; + using std::make_shared; + using std::static_pointer_cast; + using Eigen::Vector2s; + + bool factor_created = false; + + + Scalar dt = 0.01; + + // Wolf problem + ProblemPtr problem = Problem::create("PO", 2); + + // Install tracker (sensor and processor) + auto sens_lc = SensorBase::emplace<SensorBase>(problem->getHardware(), + "SENSOR BASE", + std::make_shared<StateBlock>(Eigen::VectorXs::Zero(2)), + std::make_shared<StateBlock>(Eigen::VectorXs::Zero(1)), + std::make_shared<StateBlock>(Eigen::VectorXs::Zero(2)), 2); + ProcessorParamsLoopClosurePtr params = std::make_shared<ProcessorParamsLoopClosure>(); + ProcessorLoopClosureDummyPtr proc_lc = std::make_shared<ProcessorLoopClosureDummy>(params, factor_created); + proc_lc->link(sens_lc); + std::cout << "sensor & processor created and added to wolf problem" << std::endl; + + // initialize + TimeStamp t(0.0); + Vector3s x(0,0,0); + Matrix3s P = Matrix3s::Identity() * 0.1; + problem->setPrior(x, P, t, dt/2); // KF1 + + // new KF + t += dt; + FrameBasePtr kf = FrameBase::create_PO_2D (KEY, t, x); //KF2 + proc_lc->keyFrameCallback(kf, dt/2); + + // new capture + CaptureVoidPtr capt_lc(make_shared<CaptureVoid>(t, sens_lc)); + proc_lc->captureCallback(capt_lc); + + proc_lc->process(capt_lc); + + ASSERT_TRUE(factor_created); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}