···11-// Copyright 2021, Collabora, Ltd.
22-// SPDX-License-Identifier: BSL-1.0
33-/*!
44- * @file
55- * @brief SLAM tracker class header for usage in Monado.
66- * @author Mateo de Mayo <mateo.demayo@collabora.com>
77- * @ingroup aux_tracking
88- *
99- * This file contains the declaration of the @ref slam_tracker class. This
1010- * header is intended to appear in both Monado and an external SLAM system. The
1111- * implementation of `slam_tracker` is provided by the external system.
1212- * Additional data types are declared for the communication between Monado and
1313- * the system.
1414- *
1515- */
1616-1717-#pragma once
1818-1919-#include <opencv2/core/mat.hpp>
2020-2121-#include <cstdint>
2222-#include <iostream>
2323-#include <memory>
2424-#include <string>
2525-#include <vector>
2626-#include <chrono>
2727-2828-namespace xrt::auxiliary::tracking::slam {
2929-3030-// For implementation: same as IMPLEMENTATION_VERSION_*
3131-// For user: expected IMPLEMENTATION_VERSION_*. Should be checked in runtime.
3232-constexpr int HEADER_VERSION_MAJOR = 7; //!< API Breakages
3333-constexpr int HEADER_VERSION_MINOR = 0; //!< Backwards compatible API changes
3434-constexpr int HEADER_VERSION_PATCH = 0; //!< Backw. comp. .h-implemented changes
3535-3636-// Which header version the external system is implementing.
3737-extern const int IMPLEMENTATION_VERSION_MAJOR;
3838-extern const int IMPLEMENTATION_VERSION_MINOR;
3939-extern const int IMPLEMENTATION_VERSION_PATCH;
4040-4141-enum class pose_ext_type : int;
4242-4343-/*!
4444- * @brief Standard pose type to communicate Monado with the external SLAM system
4545- */
4646-struct pose {
4747- std::int64_t timestamp; //!< In same clock as input samples
4848- float px, py, pz; //!< Position vector
4949- float rx, ry, rz, rw = 1; //!< Orientation quaternion
5050- std::shared_ptr<struct pose_extension> next = nullptr;
5151-5252- pose() = default;
5353- pose(std::int64_t timestamp, //
5454- float px, float py, float pz, //
5555- float rx, float ry, float rz, float rw)
5656- : timestamp(timestamp), //
5757- px(px), py(py), pz(pz), //
5858- rx(rx), ry(ry), rz(rz), rw(rw) {}
5959-6060- std::shared_ptr<pose_extension>
6161- find_pose_extension(pose_ext_type required_type) const;
6262-};
6363-6464-struct rect {
6565- float x, y, w, h;
6666-};
6767-6868-/*!
6969- * @brief IMU Sample type to pass around between programs
7070- */
7171-struct imu_sample {
7272- std::int64_t timestamp; //!< In nanoseconds
7373- double ax, ay, az; //!< Accel in meters per second squared (m / s^2)
7474- double wx, wy, wz; //!< Gyro in radians per second (rad / s)
7575- imu_sample() = default;
7676- imu_sample(std::int64_t timestamp, double ax, double ay, double az, double wx,
7777- double wy, double wz)
7878- : timestamp(timestamp), ax(ax), ay(ay), az(az), wx(wx), wy(wy), wz(wz) {}
7979-};
8080-8181-/*!
8282- * @brief Image sample type to pass around between programs. It is expected that
8383- * any SLAM system takes OpenCV matrices as input.
8484- */
8585-struct img_sample {
8686- std::int64_t timestamp;
8787- cv::Mat img;
8888- int cam_index;
8989- std::vector<rect> masks{}; //!< Masks to ignore
9090- img_sample() = default;
9191- img_sample(std::int64_t timestamp, const cv::Mat &img, int cam_index)
9292- : timestamp(timestamp), img(img), cam_index(cam_index) {}
9393-};
9494-9595-/*!
9696- * @brief Parameters for creating the system pipeline.
9797- */
9898-struct slam_config {
9999- //! Path to a implementation-specific config file. If null, use defaults.
100100- std::shared_ptr<std::string> config_file;
101101-102102- //! Number of cameras to use. Required.
103103- int cam_count = -1;
104104-105105- //! If supported, whether to open the system's UI.
106106- bool show_ui = false;
107107-};
108108-109109-/*!
110110- * @brief slam_tracker serves as an interface between Monado and external SLAM
111111- * systems.
112112- *
113113- * This class uses the pointer-to-implementation pattern, and its implementation
114114- * should be provided by an external SLAM system.
115115- */
116116-struct slam_tracker {
117117- slam_tracker(const slam_config &config);
118118- ~slam_tracker();
119119-120120- slam_tracker(const slam_tracker &) = delete;
121121- slam_tracker &operator=(const slam_tracker &) = delete;
122122-123123- void initialize();
124124- void start();
125125- bool is_running();
126126- void stop();
127127- void finalize();
128128-129129- /*!
130130- * @brief Push an IMU sample into the tracker.
131131- *
132132- * There must be a single producer thread pushing samples.
133133- * Samples must have monotonically increasing timestamps.
134134- * The implementation must be non-blocking.
135135- * Thus, a separate consumer thread should process the samples.
136136- */
137137- void push_imu_sample(const imu_sample &sample);
138138-139139- /*!
140140- * @brief Push an image sample into the tracker.
141141- *
142142- * Same conditions as @ref push_imu_sample apply.
143143- * When using N>1 cameras, the N frames must be pushed following cam_id order.
144144- * The bundle of N frames must have the same timestamps.
145145- */
146146- void push_frame(const img_sample &sample);
147147-148148- /*!
149149- * @brief Get the latest tracked pose from the SLAM system.
150150- *
151151- * There must be a single thread consuming this method.
152152- *
153153- * @param[out] out_pose Dequeued pose.
154154- * @return true If a new pose was dequeued into @p out_pose.
155155- * @return false If there was no pose to dequeue.
156156- */
157157- bool try_dequeue_pose(pose &out_pose);
158158-159159- //! Asks the SLAM system whether it supports a specific feature.
160160- bool supports_feature(int feature_id);
161161-162162- /*!
163163- * @brief Use a special feature of the SLAM tracker.
164164- *
165165- * This method uses heap allocated objects for passing parameters and
166166- * obtaining the results. Use `std::static_pointer_cast` to shared pointers to
167167- * the expected types.
168168- *
169169- * @param feature_id Id of the special feature.
170170- * @param params Pointer to the parameter object for this feature.
171171- * @param result Pointer to the result produced by the feature call.
172172- * @return false if the feature was not supported, true otherwise.
173173- */
174174- bool use_feature(int feature_id, const std::shared_ptr<void> ¶ms,
175175- std::shared_ptr<void> &result);
176176-177177-private:
178178- struct implementation;
179179- std::unique_ptr<implementation> impl;
180180-};
181181-182182-/*
183183- * Special features
184184- *
185185- * A special feature is comprised of an ID, a PARAMS type and a RESULT type. It
186186- * can be defined using DEFINE_FEATURE. Once defined, the definition should not
187187- * suffer future changes.
188188- *
189189- * One of the main concerns in the features interface is the ability to add new
190190- * features without being required to update the SLAM systems that are not
191191- * interested in implementing the feature.
192192- *
193193- */
194194-195195-#define DEFINE_FEATURE(NAME, SHORT_NAME, ID, PARAMS_TYPE, RESULT_TYPE) \
196196- using FPARAMS_##SHORT_NAME = PARAMS_TYPE; \
197197- using FRESULT_##SHORT_NAME = RESULT_TYPE; \
198198- constexpr int FID_##SHORT_NAME = ID; \
199199- constexpr int F_##NAME = ID;
200200-201201-/*!
202202- * Container of parameters for a pinhole camera calibration (fx, fy, cx, cy)
203203- * with an optional distortion.
204204- *
205205- *`distortion_model` and its corresponding `distortion` parameters are not
206206- * standardized in this struct to facilitate implementation prototyping.
207207- */
208208-struct cam_calibration {
209209- int cam_index; //!< For multi-camera setups. For stereo 0 ~ left, 1 ~ right.
210210- int width, height; //<! Resolution
211211- double frequency; //<! Frames per second
212212- double fx, fy; //<! Focal point
213213- double cx, cy; //<! Principal point
214214- std::string distortion_model; //!< Models like: none, rt4, rt5, rt8, kb4
215215- std::vector<double> distortion{}; //!< Parameters for the distortion_model
216216- cv::Matx<double, 4, 4> t_imu_cam; //!< Transformation from IMU to camera
217217-};
218218-219219-struct inertial_calibration {
220220- // Calibration intrinsics to apply to each raw measurement.
221221-222222- //! This transform will be applied to raw measurements.
223223- cv::Matx<double, 3, 3> transform;
224224-225225- //! Offset to add to raw measurements to; called bias in other contexts.
226226- cv::Matx<double, 3, 1> offset;
227227-228228- // Parameters for the random processes that model this IMU. See section "2.1
229229- // Gyro Noise Model" of N. Trawny and S. I. Roumeliotis, "Indirect Kalman
230230- // Filter for 3D Attitude Estimation". Analogous for accelerometers.
231231- // http://mars.cs.umn.edu/tr/reports/Trawny05b.pdf#page=15
232232-233233- //! IMU internal bias ~ wiener process with steps N(0, σ²); this field is σ;
234234- //! [σ] = U / sqrt(sec³) with U = rad if gyroscope, U = m/s if accelerometer.
235235- cv::Matx<double, 3, 1> bias_std;
236236-237237- //! IMU measurement noise ~ N(0, σ²); this field is σ.
238238- //! [σ] = U / sqrt(sec) with U = rad if gyroscope, U = m/s if accelerometer.
239239- cv::Matx<double, 3, 1> noise_std;
240240-241241- inertial_calibration() : transform(cv::Matx<double, 3, 3>::eye()) {}
242242-};
243243-244244-struct imu_calibration {
245245- int imu_index; //!< For multi-imu setups. Usually just 0.
246246- double frequency; //!< Samples per second
247247- inertial_calibration accel;
248248- inertial_calibration gyro;
249249-};
250250-251251-/*!
252252- * Feature ADD_CAMERA_CALIBRATION
253253- *
254254- * Use it after constructor but before `start()` to write or overwrite camera
255255- * calibration data that might come from the system-specific config file.
256256- */
257257-DEFINE_FEATURE(ADD_CAMERA_CALIBRATION, ACC, 1, cam_calibration, void)
258258-259259-/*!
260260- * Feature ADD_IMU_CALIBRATION
261261- *
262262- * Use it after constructor but before `start()` to write or overwrite IMU
263263- * calibration data that might come from the system-specific config file.
264264- */
265265-DEFINE_FEATURE(ADD_IMU_CALIBRATION, AIC, 2, imu_calibration, void)
266266-267267-/*!
268268- * Feature ENABLE_POSE_EXT_TIMING
269269- *
270270- * Enable/disable adding internal timestamps to the estimated poses.
271271- * Returns a vector with names for the timestamps in `pose_ext_timing`.
272272- */
273273-DEFINE_FEATURE(ENABLE_POSE_EXT_TIMING, EPET, 3, bool, std::vector<std::string>)
274274-275275-/*!
276276- * Feature ENABLE_POSE_EXT_FEATURES
277277- *
278278- * Enable/disable adding feature information to the estimated poses.
279279- */
280280-DEFINE_FEATURE(ENABLE_POSE_EXT_FEATURES, EPEF, 4, bool, void)
281281-282282-/*!
283283- * Feature RESET_TRACKER_STATE
284284- *
285285- * Reset tracker state.
286286- */
287287-DEFINE_FEATURE(RESET_TRACKER_STATE, RS, 5, void, void)
288288-289289-/*
290290- * Pose extensions
291291- *
292292- * A pose extension is a struct that gets linked in the `pose.next` field. You
293293- * first ask if the implementation supports enabling such extension with a
294294- * `supports_feature()` call with the appropriate `ENABLE_POSE_EXT_*`. Then, it
295295- * can be enabled with the corresponding `use_feature()` call.
296296- *
297297- */
298298-299299-enum class pose_ext_type : int {
300300- UNDEFINED = 0,
301301- TIMING = 1,
302302- FEATURES = 2,
303303-};
304304-305305-struct pose_extension {
306306- pose_ext_type type = pose_ext_type::UNDEFINED;
307307- std::shared_ptr<pose_extension> next = nullptr;
308308-309309- pose_extension(pose_ext_type type) : type(type) {}
310310-};
311311-312312-inline std::shared_ptr<pose_extension>
313313-pose::find_pose_extension(pose_ext_type required_type) const {
314314- std::shared_ptr<pose_extension> pe = next;
315315- while (pe != nullptr && pe->type != required_type) {
316316- pe = pe->next;
317317- }
318318- return pe;
319319-}
320320-321321-// Timing pose extension
322322-struct pose_ext_timing_data {
323323- //! Internal pipeline stage timestamps of interest when generating the pose.
324324- //! In steady clock ns. Must have the same number of elements in the same run.
325325- std::vector<std::int64_t> timing{};
326326-327327- //! Names of each timing stage. Should point to static memory.
328328- const std::vector<std::string> *timing_titles = nullptr;
329329-};
330330-331331-struct pose_ext_timing : pose_extension, pose_ext_timing_data {
332332- pose_ext_timing() : pose_extension{pose_ext_type::TIMING} {}
333333- pose_ext_timing(const pose_ext_timing_data &petd)
334334- : pose_extension{pose_ext_type::TIMING}, pose_ext_timing_data{petd} {}
335335-};
336336-337337-// Features pose extension
338338-struct pose_ext_features_data {
339339- struct feature {
340340- std::int64_t id;
341341- float u;
342342- float v;
343343- float depth;
344344- };
345345-346346- std::vector<std::vector<feature>> features_per_cam{};
347347-};
348348-349349-struct pose_ext_features : pose_extension, pose_ext_features_data {
350350- pose_ext_features() : pose_extension{pose_ext_type::FEATURES} {}
351351- pose_ext_features(const pose_ext_features_data &pefd)
352352- : pose_extension{pose_ext_type::FEATURES}, pose_ext_features_data{pefd} {}
353353-};
354354-355355-/*!
356356- * Utility object to keep track of different stats for a particular timestamp.
357357- * Stats usually correspond with a particular pose extension.
358358- */
359359-struct timestats : pose_ext_timing_data, pose_ext_features_data {
360360- using ptr = std::shared_ptr<timestats>;
361361-362362- std::int64_t ts = -1;
363363- bool timing_enabled = false;
364364- bool features_enabled = false;
365365-366366- void addTime(const char *name, int64_t ts = INT64_MIN) {
367367- if (!timing_enabled) {
368368- return;
369369- }
370370- if (timing_titles) {
371371- std::string expected = timing_titles->at(timing.size());
372372- if (expected != name) {
373373- std::cout << "Invalid timing stage\n";
374374- std::cout << "expected: " << expected;
375375- std::cout << ", got: " << name << std::endl;
376376- exit(EXIT_FAILURE);
377377- }
378378- }
379379- if (ts == INT64_MIN) {
380380- ts = std::chrono::steady_clock::now().time_since_epoch().count();
381381- }
382382- timing.push_back(ts);
383383- }
384384-385385- void addFeature(size_t cam, const feature &f) {
386386- if (!features_enabled) {
387387- return;
388388- }
389389- if (cam >= features_per_cam.size()) {
390390- features_per_cam.resize(cam + 1);
391391- }
392392- features_per_cam.at(cam).push_back(f);
393393- }
394394-};
395395-396396-} // namespace xrt::auxiliary::tracking::slam