// ---------------------------------------------------------------------------- // - Open3D: www.open3d.org - // ---------------------------------------------------------------------------- // Copyright (c) 2018-2023 www.open3d.org // SPDX-License-Identifier: MIT // ---------------------------------------------------------------------------- /// \file RGBDOdometry.h /// All the 4x4 transformation in this file, from params to returns, are /// Float64. Only convert to Float32 in kernel calls. #pragma once #include "open3d/core/Tensor.h" #include "open3d/t/geometry/Image.h" #include "open3d/t/geometry/RGBDImage.h" namespace open3d { namespace t { namespace pipelines { namespace odometry { enum class Method { PointToPlane, // Implemented and commented in // ComputeOdometryResultPointToPlane Intensity, // Implemented and commented in ComputeOdometryResultIntensity Hybrid, // Implemented and commented in ComputeOdometryResultHybrid }; class OdometryConvergenceCriteria { public: /// \brief Constructor for the convergence criteria, where we stop /// iterations once the criteria are met. /// /// \param max_iteration Maximum iteration before iteration stops. /// \param relative_rmse Relative rmse threshold where we stop iterations /// when \f$ |rmse_{i+1} - rmse_i|/rmse_i < relative rmse\f$. /// \param relative_fitness Relative fitness threshold where we stop /// iterations when \f$ |fitness_{i+1} - fitness_i|/fitness_i < relative /// fitness\f$ OdometryConvergenceCriteria(int max_iteration, double relative_rmse = 1e-6, double relative_fitness = 1e-6) : max_iteration_(max_iteration), relative_rmse_(relative_rmse), relative_fitness_(relative_fitness) {} public: /// Maximum iteration before iteration stops. int max_iteration_; /// If relative change (difference) of inliner RMSE score is lower than /// `relative_rmse`, the iteration stops. double relative_rmse_; /// If relative change (difference) of fitness score is lower than /// `relative_fitness`, the iteration stops. double relative_fitness_; }; class OdometryResult { public: /// \brief Constructor for the odometry result. /// /// \param transformation The estimated transformation matrix of dtype /// Float64 on CPU device. /// \param inlier_rmse RMSE of the inliers. /// \param fitness Ratio between #inliers and #pixels. OdometryResult(const core::Tensor& transformation = core::Tensor::Eye( 4, core::Float64, core::Device("CPU:0")), double inlier_rmse = 0.0, double fitness = 0.0) : transformation_(transformation), inlier_rmse_(inlier_rmse), fitness_(fitness) {} ~OdometryResult() {} public: /// The estimated transformation matrix of dtype Float64 on CPU device. core::Tensor transformation_; /// RMSE of all inlier. Lower is better. double inlier_rmse_; /// The overlapping area (# of inlier correspondences / # of points /// in target). Higher is better. double fitness_; }; class OdometryLossParams { public: /// \brief Constructor for the odometry loss function. /// /// \param depth_outlier_trunc Threshold to filter outlier associations /// where two depths differ significantly. /// \param depth_huber_delta Huber norm parameter applied to depth loss (for /// PointToPlane and Hybrid). /// \param intensity_huber_delta Huber norm parameter applied to intensity /// loss (for Intensity and Hybrid). OdometryLossParams(float depth_outlier_trunc = 0.07, float depth_huber_delta = 0.05, float intensity_huber_delta = 0.1) : depth_outlier_trunc_(depth_outlier_trunc), depth_huber_delta_(depth_huber_delta), intensity_huber_delta_(intensity_huber_delta) { if (depth_outlier_trunc_ < 0) { utility::LogWarning( "Depth outlier truncation < 0, outliers will be counted!"); } if (depth_huber_delta_ >= depth_outlier_trunc_) { utility::LogWarning( "Huber delta is greater than truncation, huber norm will " "degenerate to L2 norm!"); } } public: /// Depth difference threshold used to filter projective associations. float depth_outlier_trunc_; float depth_huber_delta_; float intensity_huber_delta_; }; /// \brief Create an RGBD image pyramid given the original source and target /// RGBD images, and perform hierarchical odometry using specified \p /// method. /// Can be used for offline odometry where we do not expect to push performance /// to the extreme and not reuse vertex/normal map computed before. /// Input RGBD images hold a depth image (UInt16 or Float32) with a scale /// factor and a color image (UInt8 x 3). /// \param source Source RGBD image. /// \param target Target RGBD image. /// \param intrinsics (3, 3) intrinsic matrix for projection of core::Float64 /// on CPU. /// \param init_source_to_target (4, 4) initial transformation matrix from /// source to target of core::Float64 on CPU. /// \param depth_scale Converts depth pixel values to meters by dividing the /// scale factor. /// \param depth_max Max depth to truncate depth image with noisy measurements. /// \param criteria_list Criteria used to define and terminate iterations. In /// multiscale odometry the order is from coarse to fine. Inputting a vector of /// iterations by default triggers the implicit conversion. /// \param method Method used to apply RGBD odometry. /// \param params Parameters used in loss function, including outlier rejection /// threshold and Huber norm parameters. /// \return odometry result, with (4, 4) optimized transformation matrix from /// source to target, inlier ratio, and fitness. OdometryResult RGBDOdometryMultiScale( const t::geometry::RGBDImage& source, const t::geometry::RGBDImage& target, const core::Tensor& intrinsics, const core::Tensor& init_source_to_target = core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), const float depth_scale = 1000.0f, const float depth_max = 3.0f, const std::vector& criteria_list = {10, 5, 3}, const Method method = Method::Hybrid, const OdometryLossParams& params = OdometryLossParams()); /// \brief Estimates the 4x4 rigid transformation T from source to target, with /// inlier rmse and fitness. /// Performs one iteration of RGBD odometry using loss function /// \f$[(V_p - V_q)^T N_p]^2\f$, where /// \f$ V_p \f$ denotes the vertex at pixel p in the source, /// \f$ V_q \f$ denotes the vertex at pixel q in the target, /// \f$ N_p \f$ denotes the normal at pixel p in the source. /// q is obtained by transforming p with \p init_source_to_target then /// projecting with \p intrinsics. /// KinectFusion, ISMAR 2011 /// /// \param source_vertex_map (rows, cols, channels=3) Float32 source vertex /// image obtained by CreateVertexMap before calling this function. /// \param target_vertex_map (rows, cols, channels=3) Float32 target vertex /// image obtained by CreateVertexMap before calling this function. /// \param target_normal_map (rows, cols, channels=3) Float32 target normal /// image obtained by CreateNormalMap before calling this function. /// \param intrinsics (3, 3) intrinsic matrix for projection. /// \param init_source_to_target (4, 4) initial transformation matrix from /// source to target. /// \param depth_outlier_trunc Depth difference threshold used to filter /// projective associations. /// \param depth_huber_delta Huber norm parameter used in depth loss. /// \return odometry result, with (4, 4) optimized transformation matrix from /// source to target, inlier ratio, and fitness. OdometryResult ComputeOdometryResultPointToPlane( const core::Tensor& source_vertex_map, const core::Tensor& target_vertex_map, const core::Tensor& target_normal_map, const core::Tensor& intrinsics, const core::Tensor& init_source_to_target, const float depth_outlier_trunc, const float depth_huber_delta); /// \brief Estimates the 4x4 rigid transformation T from source to target, with /// inlier rmse and fitness. /// Performs one iteration of RGBD odometry using loss function /// \f$(I_p - I_q)^2\f$, where /// \f$ I_p \f$ denotes the intensity at pixel p in the source, /// \f$ I_q \f$ denotes the intensity at pixel q in the target. /// q is obtained by transforming p with \p init_source_to_target then /// projecting with \p intrinsics. /// Real-time visual odometry from dense RGB-D images, ICCV Workshops, 2011 /// /// \param source_depth (rows, cols, channels=1) Float32 source depth image /// obtained by PreprocessDepth before calling this function. /// \param target_depth (rows, cols, channels=1) Float32 target depth image /// obtained by PreprocessDepth before calling this function. /// \param source_intensity (rows, cols, channels=1) Float32 source intensity /// image obtained by RGBToGray before calling this function. /// \param target_intensity (rows, cols, channels=1) Float32 target intensity /// image obtained by RGBToGray before calling this function. /// \param target_intensity_dx (rows, cols, channels=1) Float32 target intensity /// gradient image along x-axis obtained by FilterSobel before calling this /// function. /// \param target_intensity_dy (rows, cols, channels=1) Float32 target intensity /// gradient image along y-axis obtained by FilterSobel before calling this /// function. /// \param source_vertex_map (rows, cols, channels=3) Float32 source vertex /// image obtained by CreateVertexMap before calling this function. /// \param intrinsics (3, 3) intrinsic matrix for projection. /// \param init_source_to_target (4, 4) initial transformation matrix from /// source to target. /// \param depth_outlier_trunc Depth difference threshold used to filter /// projective associations. /// \param intensity_huber_delta Huber norm parameter used in intensity loss. /// \return odometry result, with(4, 4) optimized transformation matrix /// from source to target, inlier ratio, and fitness. OdometryResult ComputeOdometryResultIntensity( const core::Tensor& source_depth, const core::Tensor& target_depth, const core::Tensor& source_intensity, const core::Tensor& target_intensity, const core::Tensor& target_intensity_dx, const core::Tensor& target_intensity_dy, const core::Tensor& source_vertex_map, const core::Tensor& intrinsics, const core::Tensor& init_source_to_target, const float depth_outlier_trunc, const float intensity_huber_delta); /// \brief Estimates the 4x4 rigid transformation T from source to target, with /// inlier rmse and fitness. /// Performs one iteration of RGBD odometry using loss function /// \f$(I_p - I_q)^2 + \lambda(D_p - (D_q)')^2\f$, where /// \f$ I_p \f$ denotes the intensity at pixel p in the source, /// \f$ I_q \f$ denotes the intensity at pixel q in the target. /// \f$ D_p \f$ denotes the depth pixel p in the source, /// \f$ D_q \f$ denotes the depth pixel q in the target. /// q is obtained by transforming p with \p init_source_to_target then /// projecting with \p intrinsics. /// Reference: J. Park, Q.Y. Zhou, and V. Koltun, /// Colored Point Cloud Registration Revisited, ICCV, 2017. /// /// \param source_depth (rows, cols, channels=1) Float32 source depth image /// obtained by PreprocessDepth before calling this function. /// \param target_depth (rows, cols, channels=1) Float32 target depth image /// obtained by PreprocessDepth before calling this function. /// \param source_intensity (rows, cols, channels=1) Float32 source intensity /// image obtained by RGBToGray before calling this function. /// \param target_intensity (rows, cols, channels=1) Float32 target intensity /// image obtained by RGBToGray before calling this function. /// \param target_depth_dx (rows, cols, channels=1) Float32 target depth /// gradient image along x-axis obtained by FilterSobel before calling this /// function. /// \param target_depth_dy (rows, cols, channels=1) Float32 target depth /// gradient image along y-axis obtained by FilterSobel before calling this /// function. /// \param target_intensity_dx (rows, cols, channels=1) Float32 target intensity /// gradient image along x-axis obtained by FilterSobel before calling this /// function. /// \param target_intensity_dy (rows, cols, channels=1) Float32 target intensity /// gradient image along y-axis obtained by FilterSobel before calling this /// function. /// \param source_vertex_map (rows, cols, channels=3) Float32 source vertex /// image obtained by CreateVertexMap before calling this function. /// \param intrinsics (3, 3) intrinsic matrix for projection. /// \param init_source_to_target (4, 4) initial transformation matrix from /// source to target. /// \param depth_outlier_trunc Depth difference threshold used to filter /// projective associations. /// \param depth_huber_delta Huber norm parameter used in depth loss. /// \param intensity_huber_delta Huber norm parameter used in intensity loss. /// \return odometry result, with(4, 4) optimized transformation matrix /// from source to target, inlier ratio, and fitness. OdometryResult ComputeOdometryResultHybrid( const core::Tensor& source_depth, const core::Tensor& target_depth, const core::Tensor& source_intensity, const core::Tensor& target_intensity, const core::Tensor& target_depth_dx, const core::Tensor& target_depth_dy, const core::Tensor& target_intensity_dx, const core::Tensor& target_intensity_dy, const core::Tensor& source_vertex_map, const core::Tensor& intrinsics, const core::Tensor& init_source_to_target, const float depth_outlier_trunc, const float depth_huber_delta, const float intensity_huber_delta); /// Estimates 6x6 information matrix from a pair of depth images. /// The process is akin to information matrix creation for point clouds. core::Tensor ComputeOdometryInformationMatrix( const geometry::Image& source_depth, const geometry::Image& target_depth, const core::Tensor& intrinsic, const core::Tensor& source_to_target, const float dist_thr, const float depth_scale = 1000.0f, const float depth_max = 3.0f); } // namespace odometry } // namespace pipelines } // namespace t } // namespace open3d