mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-11 04:19:50 +08:00
Add LIO-SAM as an odometry strategy (#1684)
* Added liosam odometry integration * Add kXYZIRT scan format with per-point ring channel for LIO-SAM integration Introduce PointXYZIRT point type and kXYZIRT LaserScan format (x,y,z, intensity,ring,time) so that ring indices survive the scan pipeline. Update OdometryLIOSAM to require kXYZIRT and properly split ring/time into the parallel buffers LIO-SAM expects. Extend deskewing to preserve ring data and disable base-class deskew in OdometryLIOSAM since LIO-SAM handles it internally. * Address PR review: config file, deferred init, and GUI panel for LIO-SAM - Add OdomLIOSAM/ConfigPath parameter to load LIO-SAM settings from a YAML file. When set, individual params are ignored. Extrinsics from sensor local transforms always override config file values. - Defer LioSamCore initialization until both IMU and lidar local transforms are available, computing T_lidar_imu from sensor data. IMU samples are buffered and replayed after init. - Fix deferred init for scan-only messages that arrive after IMU local transform is already cached. - Add LIO-SAM entry to odometry strategy combo box (index 14) with full PreferencesDialog panel including config path browse button and all parameter widgets. * OdometryLIOSAM: propagate deskewed scan to SensorData Capture the deskewed cloud produced by LIO-SAM's image projection stage and replace the raw scan on SensorData with it, so loop closure registration and other downstream stages operate on the motion- compensated points instead of the raw pre-deskew input. * Minor updates for rtabmap_ros --------- Co-authored-by: matlabbe <[email protected]>
This commit is contained in:
@@ -192,6 +192,7 @@ option(WITH_CCCORELIB "Include CCCoreLib support" OFF)
|
|||||||
option(WITH_OPEN3D "Include Open3D support" OFF)
|
option(WITH_OPEN3D "Include Open3D support" OFF)
|
||||||
option(WITH_LOAM "Include LOAM support" OFF)
|
option(WITH_LOAM "Include LOAM support" OFF)
|
||||||
option(WITH_FLOAM "Include FLOAM support" OFF)
|
option(WITH_FLOAM "Include FLOAM support" OFF)
|
||||||
|
option(WITH_LIOSAM "Include LIO-SAM support" OFF)
|
||||||
option(WITH_FLYCAPTURE2 "Include FlyCapture2/Triclops support" ON)
|
option(WITH_FLYCAPTURE2 "Include FlyCapture2/Triclops support" ON)
|
||||||
option(WITH_ZED "Include ZED sdk support" ON)
|
option(WITH_ZED "Include ZED sdk support" ON)
|
||||||
option(WITH_ZEDOC "Include ZED Open Capture support" ON)
|
option(WITH_ZEDOC "Include ZED Open Capture support" ON)
|
||||||
@@ -630,6 +631,12 @@ IF(WITH_FLOAM)
|
|||||||
FIND_PACKAGE(Ceres REQUIRED)
|
FIND_PACKAGE(Ceres REQUIRED)
|
||||||
ENDIF(floam_FOUND)
|
ENDIF(floam_FOUND)
|
||||||
ENDIF(WITH_FLOAM)
|
ENDIF(WITH_FLOAM)
|
||||||
|
IF(WITH_LIOSAM)
|
||||||
|
find_package(lio_sam QUIET)
|
||||||
|
IF(lio_sam_FOUND)
|
||||||
|
MESSAGE(STATUS "Found lio_sam: ${lio_sam_INCLUDE_DIRS}")
|
||||||
|
ENDIF(lio_sam_FOUND)
|
||||||
|
ENDIF(WITH_LIOSAM)
|
||||||
|
|
||||||
SET(ZED_FOUND FALSE)
|
SET(ZED_FOUND FALSE)
|
||||||
IF(WITH_ZED)
|
IF(WITH_ZED)
|
||||||
@@ -1069,6 +1076,9 @@ ENDIF(NOT loam_velodyne_FOUND)
|
|||||||
IF(NOT floam_FOUND)
|
IF(NOT floam_FOUND)
|
||||||
SET(FLOAM "//")
|
SET(FLOAM "//")
|
||||||
ENDIF(NOT floam_FOUND)
|
ENDIF(NOT floam_FOUND)
|
||||||
|
IF(NOT lio_sam_FOUND)
|
||||||
|
SET(LIOSAM "//")
|
||||||
|
ENDIF(NOT lio_sam_FOUND)
|
||||||
IF(NOT Freenect_FOUND)
|
IF(NOT Freenect_FOUND)
|
||||||
SET(FREENECT "//")
|
SET(FREENECT "//")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
@@ -1873,6 +1883,13 @@ MESSAGE(STATUS " With floam = NO (WITH_FLOAM=OFF)")
|
|||||||
ELSE()
|
ELSE()
|
||||||
MESSAGE(STATUS " With floam = NO (floam not found)")
|
MESSAGE(STATUS " With floam = NO (floam not found)")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
IF(lio_sam_FOUND)
|
||||||
|
MESSAGE(STATUS " With lio_sam = YES (License: BSD)")
|
||||||
|
ELSEIF(NOT WITH_LIOSAM)
|
||||||
|
MESSAGE(STATUS " With lio_sam = NO (WITH_LIOSAM=OFF)")
|
||||||
|
ELSE()
|
||||||
|
MESSAGE(STATUS " With lio_sam = NO (lio_sam not found)")
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
IF(libfovis_FOUND)
|
IF(libfovis_FOUND)
|
||||||
MESSAGE(STATUS " With libfovis = YES (License: GPLv2)")
|
MESSAGE(STATUS " With libfovis = YES (License: GPLv2)")
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
@CUDASIFT@#define RTABMAP_CUDASIFT
|
@CUDASIFT@#define RTABMAP_CUDASIFT
|
||||||
@LOAM@#define RTABMAP_LOAM
|
@LOAM@#define RTABMAP_LOAM
|
||||||
@FLOAM@#define RTABMAP_FLOAM
|
@FLOAM@#define RTABMAP_FLOAM
|
||||||
|
@LIOSAM@#define RTABMAP_LIOSAM
|
||||||
@DC1394@#define RTABMAP_DC1394
|
@DC1394@#define RTABMAP_DC1394
|
||||||
@FLYCAPTURE2@#define RTABMAP_FLYCAPTURE2
|
@FLYCAPTURE2@#define RTABMAP_FLYCAPTURE2
|
||||||
@ZED@#define RTABMAP_ZED
|
@ZED@#define RTABMAP_ZED
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ public:
|
|||||||
kXYZNormal=8,
|
kXYZNormal=8,
|
||||||
kXYZINormal=9,
|
kXYZINormal=9,
|
||||||
kXYZRGBNormal=10,
|
kXYZRGBNormal=10,
|
||||||
kXYZIT=11};
|
kXYZIT=11,
|
||||||
|
kXYZIRT=12};
|
||||||
|
|
||||||
static std::string formatName(const Format & format);
|
static std::string formatName(const Format & format);
|
||||||
static int channels(const Format & format);
|
static int channels(const Format & format);
|
||||||
@@ -57,6 +58,7 @@ public:
|
|||||||
static bool isScanHasRGB(const Format & format);
|
static bool isScanHasRGB(const Format & format);
|
||||||
static bool isScanHasIntensity(const Format & format);
|
static bool isScanHasIntensity(const Format & format);
|
||||||
static bool isScanHasTime(const Format & format);
|
static bool isScanHasTime(const Format & format);
|
||||||
|
static bool isScanHasRing(const Format & format);
|
||||||
static LaserScan backwardCompatibility(
|
static LaserScan backwardCompatibility(
|
||||||
const cv::Mat & oldScanFormat,
|
const cv::Mat & oldScanFormat,
|
||||||
int maxPoints = 0,
|
int maxPoints = 0,
|
||||||
@@ -135,6 +137,7 @@ public:
|
|||||||
bool hasRGB() const {return isScanHasRGB(format_);}
|
bool hasRGB() const {return isScanHasRGB(format_);}
|
||||||
bool hasIntensity() const {return isScanHasIntensity(format_);}
|
bool hasIntensity() const {return isScanHasIntensity(format_);}
|
||||||
bool hasTime() const {return isScanHasTime(format_);}
|
bool hasTime() const {return isScanHasTime(format_);}
|
||||||
|
bool hasRing() const {return isScanHasRing(format_);}
|
||||||
bool isCompressed() const {return !data_.empty() && data_.type()==CV_8UC1;}
|
bool isCompressed() const {return !data_.empty() && data_.type()==CV_8UC1;}
|
||||||
bool isOrganized() const {return data_.rows > 1;}
|
bool isOrganized() const {return data_.rows > 1;}
|
||||||
LaserScan clone() const;
|
LaserScan clone() const;
|
||||||
@@ -143,7 +146,8 @@ public:
|
|||||||
int getIntensityOffset() const {return hasIntensity()?(is2d()?2:3):-1;}
|
int getIntensityOffset() const {return hasIntensity()?(is2d()?2:3):-1;}
|
||||||
int getRGBOffset() const {return hasRGB()?(is2d()?2:3):-1;}
|
int getRGBOffset() const {return hasRGB()?(is2d()?2:3):-1;}
|
||||||
int getNormalsOffset() const {return hasNormals()?(2 + (is2d()?0:1) + ((hasRGB() || hasIntensity())?1:0)):-1;}
|
int getNormalsOffset() const {return hasNormals()?(2 + (is2d()?0:1) + ((hasRGB() || hasIntensity())?1:0)):-1;}
|
||||||
int getTimeOffset() const {return hasTime()?4:-1;}
|
int getRingOffset() const {return format_==kXYZIRT?4:-1;}
|
||||||
|
int getTimeOffset() const {return format_==kXYZIT?4:(format_==kXYZIRT?5:-1);}
|
||||||
|
|
||||||
float & field(unsigned int pointIndex, unsigned int channelOffset);
|
float & field(unsigned int pointIndex, unsigned int channelOffset);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ public:
|
|||||||
kTypeOpenVINS = 10,
|
kTypeOpenVINS = 10,
|
||||||
kTypeFLOAM = 11,
|
kTypeFLOAM = 11,
|
||||||
kTypeOpen3D = 12,
|
kTypeOpen3D = 12,
|
||||||
kTypeCuVSLAM = 13
|
kTypeCuVSLAM = 13,
|
||||||
|
kTypeLIOSAM = 14
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ class RTABMAP_CORE_EXPORT Parameters
|
|||||||
RTABMAP_PARAM(GTSAM, IncRelinearizeSkip, int, 1, "Only relinearize any variables every X calls to ISAM2::update(). See GTSAM::ISAM2 doc for more info.");
|
RTABMAP_PARAM(GTSAM, IncRelinearizeSkip, int, 1, "Only relinearize any variables every X calls to ISAM2::update(). See GTSAM::ISAM2 doc for more info.");
|
||||||
|
|
||||||
// Odometry
|
// Odometry
|
||||||
RTABMAP_PARAM(Odom, Strategy, int, 0, "0=Frame-to-Map (F2M) 1=Frame-to-Frame (F2F) 2=Fovis 3=viso2 4=DVO-SLAM 5=ORB_SLAM 6=OKVIS 7=LOAM 8=MSCKF_VIO 9=VINS-Fusion 10=OpenVINS 11=FLOAM 12=Open3D 13=cuVSLAM");
|
RTABMAP_PARAM(Odom, Strategy, int, 0, "0=Frame-to-Map (F2M) 1=Frame-to-Frame (F2F) 2=Fovis 3=viso2 4=DVO-SLAM 5=ORB_SLAM 6=OKVIS 7=LOAM 8=MSCKF_VIO 9=VINS-Fusion 10=OpenVINS 11=FLOAM 12=Open3D 13=cuVSLAM 14=LIO-SAM");
|
||||||
RTABMAP_PARAM(Odom, ResetCountdown, int, 0, "Automatically reset odometry after X consecutive images where odometry cannot be computed (a value of 0 disables auto-reset). When a reset occurs, odometry resumes from the last successfully computed pose with large covariance to trigger a new map. If external odometry is used, it will also be reset based on the motion estimated relative to the last computed pose but no large covariance will be received, so that a new map won't be triggered.");
|
RTABMAP_PARAM(Odom, ResetCountdown, int, 0, "Automatically reset odometry after X consecutive images where odometry cannot be computed (a value of 0 disables auto-reset). When a reset occurs, odometry resumes from the last successfully computed pose with large covariance to trigger a new map. If external odometry is used, it will also be reset based on the motion estimated relative to the last computed pose but no large covariance will be received, so that a new map won't be triggered.");
|
||||||
RTABMAP_PARAM(Odom, Holonomic, bool, true, "If the robot is holonomic (strafing commands can be issued). If not, y value will be estimated from x and yaw values (y=x*tan(yaw)).");
|
RTABMAP_PARAM(Odom, Holonomic, bool, true, "If the robot is holonomic (strafing commands can be issued). If not, y value will be estimated from x and yaw values (y=x*tan(yaw)).");
|
||||||
RTABMAP_PARAM(Odom, FillInfoData, bool, true, "Fill info with data (inliers/outliers features).");
|
RTABMAP_PARAM(Odom, FillInfoData, bool, true, "Fill info with data (inliers/outliers features).");
|
||||||
@@ -687,6 +687,21 @@ class RTABMAP_CORE_EXPORT Parameters
|
|||||||
// Odometry cuVSLAM
|
// Odometry cuVSLAM
|
||||||
RTABMAP_PARAM(OdomCuVSLAM, MulticamMode, int, 0, "cuVSLAM multicam_mode setting: 0=moderate, 1=performance, 2=precision.");
|
RTABMAP_PARAM(OdomCuVSLAM, MulticamMode, int, 0, "cuVSLAM multicam_mode setting: 0=moderate, 1=performance, 2=precision.");
|
||||||
|
|
||||||
|
// Odometry LIO-SAM
|
||||||
|
RTABMAP_PARAM_STR(OdomLIOSAM, ConfigPath, "", "Path to LIO-SAM params.yaml config file. When set, sensor/IMU/feature parameters are loaded from the file and the individual parameters below are ignored.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, Sensor, int, 0, "LiDAR sensor: 0=Velodyne, 1=Ouster, 2=Livox");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, NScan, int, 16, "Number of LiDAR channels (16, 32, 64, 128).");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, HorizonScan, int, 1800, "Horizontal resolution (Velodyne:1800, Ouster:512/1024/2048).");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, ImuAccNoise, float, 0.01, "IMU accelerometer white noise.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, ImuGyrNoise, float, 0.001, "IMU gyroscope white noise.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, ImuAccBiasN, float, 0.0002,"IMU accelerometer bias noise.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, ImuGyrBiasN, float, 0.00003,"IMU gyroscope bias noise.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, ImuGravity, float, 9.80511,"Gravity magnitude.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, EdgeThreshold,float, 1.0, "Edge feature curvature threshold.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, SurfThreshold,float, 0.1, "Surface feature curvature threshold.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, LinVar, float, 0.01, "Linear output variance.");
|
||||||
|
RTABMAP_PARAM(OdomLIOSAM, AngVar, float, 0.01, "Angular output variance.");
|
||||||
|
|
||||||
// Common registration parameters
|
// Common registration parameters
|
||||||
RTABMAP_PARAM(Reg, RepeatOnce, bool, true, "Do a second registration with the output of the first registration as guess. Only done if no guess was provided for the first registration (like on loop closure). It can be useful if the registration approach used can use a guess to get better matches.");
|
RTABMAP_PARAM(Reg, RepeatOnce, bool, true, "Do a second registration with the output of the first registration as guess. Only done if no guess was provided for the first registration (like on loop closure). It can be useful if the registration approach used can use a guess to get better matches.");
|
||||||
RTABMAP_PARAM(Reg, Strategy, int, 0, "0=Vis, 1=Icp, 2=VisIcp");
|
RTABMAP_PARAM(Reg, Strategy, int, 0, "0=Vis, 1=Icp, 2=VisIcp");
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
return LaserScan();
|
return LaserScan();
|
||||||
}
|
}
|
||||||
//determine the output type
|
//determine the output type
|
||||||
int fieldStates[8] = {0}; // x,y,z,normal_x,normal_y,normal_z,rgb,intensity
|
int fieldStates[10] = {0}; // x,y,z,normal_x,normal_y,normal_z,rgb,intensity,time,ring
|
||||||
#if PCL_VERSION_COMPARE(>=, 1, 10, 0)
|
#if PCL_VERSION_COMPARE(>=, 1, 10, 0)
|
||||||
std::uint32_t fieldOffsets[8] = {0};
|
std::uint32_t fieldOffsets[10] = {0};
|
||||||
#else
|
#else
|
||||||
pcl::uint32_t fieldOffsets[8] = {0};
|
pcl::uint32_t fieldOffsets[10] = {0};
|
||||||
#endif
|
#endif
|
||||||
for(unsigned int i=0; i<cloud.fields.size(); ++i)
|
for(unsigned int i=0; i<cloud.fields.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -102,6 +102,42 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
fieldStates[7] = 1;
|
fieldStates[7] = 1;
|
||||||
fieldOffsets[7] = cloud.fields[i].offset;
|
fieldOffsets[7] = cloud.fields[i].offset;
|
||||||
}
|
}
|
||||||
|
else if(cloud.fields[i].name.compare("time") == 0)
|
||||||
|
{
|
||||||
|
if(cloud.fields[i].datatype != pcl::PCLPointField::FLOAT32)
|
||||||
|
{
|
||||||
|
static bool warningShown = false;
|
||||||
|
if(!warningShown)
|
||||||
|
{
|
||||||
|
UWARN("The input scan cloud has an \"time\" field "
|
||||||
|
"but the datatype (%d) is not supported. Time will be ignored. "
|
||||||
|
"This message is only shown once.", cloud.fields[i].datatype);
|
||||||
|
warningShown = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldStates[8] = 1;
|
||||||
|
fieldOffsets[8] = cloud.fields[i].offset;
|
||||||
|
}
|
||||||
|
else if(cloud.fields[i].name.compare("ring") == 0)
|
||||||
|
{
|
||||||
|
if(cloud.fields[i].datatype != pcl::PCLPointField::UINT16)
|
||||||
|
{
|
||||||
|
static bool warningShown = false;
|
||||||
|
if(!warningShown)
|
||||||
|
{
|
||||||
|
UWARN("The input scan cloud has an \"ring\" field "
|
||||||
|
"but the datatype (%d) is not supported. Ring will be ignored. "
|
||||||
|
"This message is only shown once.", cloud.fields[i].datatype);
|
||||||
|
warningShown = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldStates[9] = 1;
|
||||||
|
fieldOffsets[9] = cloud.fields[i].offset;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UDEBUG("Ignoring \"%s\" field", cloud.fields[i].name.c_str());
|
UDEBUG("Ignoring \"%s\" field", cloud.fields[i].name.c_str());
|
||||||
@@ -117,6 +153,8 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
bool hasNormals = fieldStates[3] || fieldStates[4] || fieldStates[5];
|
bool hasNormals = fieldStates[3] || fieldStates[4] || fieldStates[5];
|
||||||
bool hasIntensity = fieldStates[7];
|
bool hasIntensity = fieldStates[7];
|
||||||
bool hasRGB = !hasIntensity&&fieldStates[6];
|
bool hasRGB = !hasIntensity&&fieldStates[6];
|
||||||
|
bool hasTime = hasIntensity&&fieldStates[8];
|
||||||
|
bool hasRing = hasIntensity&&fieldStates[9];
|
||||||
bool is3D = fieldStates[0] && fieldStates[1] && fieldStates[2];
|
bool is3D = fieldStates[0] && fieldStates[1] && fieldStates[2];
|
||||||
|
|
||||||
LaserScan::Format format;
|
LaserScan::Format format;
|
||||||
@@ -140,7 +178,18 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
}
|
}
|
||||||
else if(!hasNormals && hasIntensity)
|
else if(!hasNormals && hasIntensity)
|
||||||
{
|
{
|
||||||
format = LaserScan::kXYZI;
|
if(hasTime && hasRing)
|
||||||
|
{
|
||||||
|
format = LaserScan::kXYZIRT;
|
||||||
|
}
|
||||||
|
else if(hasTime)
|
||||||
|
{
|
||||||
|
format = LaserScan::kXYZIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
format = LaserScan::kXYZI;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(!hasNormals && hasRGB)
|
else if(!hasNormals && hasRGB)
|
||||||
{
|
{
|
||||||
@@ -183,6 +232,7 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
transformRot = transform.rotation();
|
transformRot = transform.rotation();
|
||||||
}
|
}
|
||||||
int oi=0;
|
int oi=0;
|
||||||
|
UASSERT(cloud.height == 1 || cloud.row_step != 0);
|
||||||
for (uint32_t row = 0; row < (uint32_t)cloud.height; ++row)
|
for (uint32_t row = 0; row < (uint32_t)cloud.height; ++row)
|
||||||
{
|
{
|
||||||
const uint8_t* row_data = &cloud.data[row * cloud.row_step];
|
const uint8_t* row_data = &cloud.data[row * cloud.row_step];
|
||||||
@@ -242,26 +292,45 @@ LaserScan laserScanFromPointCloud(const PointCloud2T & cloud, bool filterNaNs, b
|
|||||||
{
|
{
|
||||||
ptr[0] = *(float*)(msg_data + fieldOffsets[0]);
|
ptr[0] = *(float*)(msg_data + fieldOffsets[0]);
|
||||||
ptr[1] = *(float*)(msg_data + fieldOffsets[1]);
|
ptr[1] = *(float*)(msg_data + fieldOffsets[1]);
|
||||||
ptr[2] = *(float*)(msg_data + fieldOffsets[3]);
|
if(format == LaserScan::kXYZIT)
|
||||||
ptr[3] = *(float*)(msg_data + fieldOffsets[4]);
|
{
|
||||||
ptr[4] = *(float*)(msg_data + fieldOffsets[5]);
|
ptr[2] = *(float*)(msg_data + fieldOffsets[2]);
|
||||||
|
ptr[3] = *(float*)(msg_data + fieldOffsets[7]);
|
||||||
|
ptr[4] = *(float*)(msg_data + fieldOffsets[8]);
|
||||||
|
}
|
||||||
|
else // kXYNormal
|
||||||
|
{
|
||||||
|
ptr[2] = *(float*)(msg_data + fieldOffsets[3]);
|
||||||
|
ptr[3] = *(float*)(msg_data + fieldOffsets[4]);
|
||||||
|
ptr[4] = *(float*)(msg_data + fieldOffsets[5]);
|
||||||
|
}
|
||||||
valid = uIsFinite(ptr[0]) && uIsFinite(ptr[1]) && uIsFinite(ptr[2]) && uIsFinite(ptr[3]) && uIsFinite(ptr[4]);
|
valid = uIsFinite(ptr[0]) && uIsFinite(ptr[1]) && uIsFinite(ptr[2]) && uIsFinite(ptr[3]) && uIsFinite(ptr[4]);
|
||||||
}
|
}
|
||||||
else if(laserScan.channels() == 6)
|
else if(laserScan.channels() == 6)
|
||||||
{
|
{
|
||||||
ptr[0] = *(float*)(msg_data + fieldOffsets[0]);
|
ptr[0] = *(float*)(msg_data + fieldOffsets[0]);
|
||||||
ptr[1] = *(float*)(msg_data + fieldOffsets[1]);
|
ptr[1] = *(float*)(msg_data + fieldOffsets[1]);
|
||||||
if(format == LaserScan::kXYINormal)
|
if(format == LaserScan::kXYZIRT)
|
||||||
{
|
|
||||||
ptr[2] = *(float*)(msg_data + fieldOffsets[7]);
|
|
||||||
}
|
|
||||||
else // XYZNormal
|
|
||||||
{
|
{
|
||||||
ptr[2] = *(float*)(msg_data + fieldOffsets[2]);
|
ptr[2] = *(float*)(msg_data + fieldOffsets[2]);
|
||||||
|
ptr[3] = *(float*)(msg_data + fieldOffsets[7]);
|
||||||
|
ptr[4] = float(*(unsigned short*)(msg_data + fieldOffsets[9])); // Convert 16U to float
|
||||||
|
ptr[5] = *(float*)(msg_data + fieldOffsets[8]);
|
||||||
|
}
|
||||||
|
else // with normal
|
||||||
|
{
|
||||||
|
if(format == LaserScan::kXYINormal)
|
||||||
|
{
|
||||||
|
ptr[2] = *(float*)(msg_data + fieldOffsets[7]);
|
||||||
|
}
|
||||||
|
else // XYZNormal
|
||||||
|
{
|
||||||
|
ptr[2] = *(float*)(msg_data + fieldOffsets[2]);
|
||||||
|
}
|
||||||
|
ptr[3] = *(float*)(msg_data + fieldOffsets[3]);
|
||||||
|
ptr[4] = *(float*)(msg_data + fieldOffsets[4]);
|
||||||
|
ptr[5] = *(float*)(msg_data + fieldOffsets[5]);
|
||||||
}
|
}
|
||||||
ptr[3] = *(float*)(msg_data + fieldOffsets[3]);
|
|
||||||
ptr[4] = *(float*)(msg_data + fieldOffsets[4]);
|
|
||||||
ptr[5] = *(float*)(msg_data + fieldOffsets[5]);
|
|
||||||
valid = uIsFinite(ptr[0]) && uIsFinite(ptr[1]) && uIsFinite(ptr[2]) && uIsFinite(ptr[3]) && uIsFinite(ptr[4]) && uIsFinite(ptr[5]);
|
valid = uIsFinite(ptr[0]) && uIsFinite(ptr[1]) && uIsFinite(ptr[2]) && uIsFinite(ptr[3]) && uIsFinite(ptr[4]) && uIsFinite(ptr[5]);
|
||||||
}
|
}
|
||||||
else if(laserScan.channels() == 7)
|
else if(laserScan.channels() == 7)
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Universite de Sherbrooke nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ODOMETRYLIOSAM_H_
|
||||||
|
#define ODOMETRYLIOSAM_H_
|
||||||
|
|
||||||
|
#include <rtabmap/core/Odometry.h>
|
||||||
|
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
#include <Eigen/Core>
|
||||||
|
#include <Eigen/Geometry>
|
||||||
|
#include <vector>
|
||||||
|
namespace lio_sam { class LioSamCore; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rtabmap {
|
||||||
|
|
||||||
|
class RTABMAP_CORE_EXPORT OdometryLIOSAM : public Odometry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OdometryLIOSAM(const rtabmap::ParametersMap & parameters = rtabmap::ParametersMap());
|
||||||
|
virtual ~OdometryLIOSAM();
|
||||||
|
|
||||||
|
virtual void reset(const Transform & initialPose = Transform::getIdentity());
|
||||||
|
virtual Odometry::Type getType() {return Odometry::kTypeLIOSAM;}
|
||||||
|
virtual bool canProcessAsyncIMU() const {return true;}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual Transform computeTransform(SensorData & data, const Transform & guess = Transform(), OdometryInfo * info = 0);
|
||||||
|
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
bool init(const Transform & imuLocalTransform, const Transform & lidarLocalTransform);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
lio_sam::LioSamCore * lioSam_;
|
||||||
|
Transform lastPose_;
|
||||||
|
bool lost_;
|
||||||
|
float linVar_;
|
||||||
|
float angVar_;
|
||||||
|
ParametersMap parameters_;
|
||||||
|
Transform imuLocalTransform_; // base_link -> imu_link (cached for deferred init)
|
||||||
|
|
||||||
|
// Buffered IMU samples received before initialization
|
||||||
|
struct ImuSample {
|
||||||
|
double stamp;
|
||||||
|
Eigen::Vector3d acc;
|
||||||
|
Eigen::Vector3d gyro;
|
||||||
|
Eigen::Quaterniond orientation;
|
||||||
|
};
|
||||||
|
std::vector<ImuSample> imuBuffer_;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ODOMETRYLIOSAM_H_ */
|
||||||
@@ -39,12 +39,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
#include <rtabmap/core/Parameters.h>
|
#include <rtabmap/core/Parameters.h>
|
||||||
#include <opencv2/core/core.hpp>
|
#include <opencv2/core/core.hpp>
|
||||||
#include <rtabmap/core/ProgressState.h>
|
#include <rtabmap/core/ProgressState.h>
|
||||||
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
namespace rtabmap
|
namespace rtabmap
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Point type carrying xyz + intensity + ring (laser line index) + time
|
||||||
|
// (per-point acquisition offset, seconds from the scan start). Matches the
|
||||||
|
// layout expected by LIO-SAM's Velodyne feature extractor so it can be fed
|
||||||
|
// directly via util3d::laserScanFromPointCloud().
|
||||||
|
struct EIGEN_ALIGN16 PointXYZIRT
|
||||||
|
{
|
||||||
|
PCL_ADD_POINT4D;
|
||||||
|
float intensity;
|
||||||
|
std::uint16_t ring;
|
||||||
|
float time;
|
||||||
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
||||||
|
};
|
||||||
|
|
||||||
namespace util3d
|
namespace util3d
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -297,6 +311,9 @@ LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl:
|
|||||||
// return CV_32FC4 (x,y,z,I)
|
// return CV_32FC4 (x,y,z,I)
|
||||||
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const Transform & transform = Transform(), bool filterNaNs = true);
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const pcl::IndicesPtr & indices, const Transform & transform = Transform(), bool filterNaNs = true);
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const pcl::IndicesPtr & indices, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
|
// return CV_32FC6 (x,y,z,I,ring,time)
|
||||||
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<rtabmap::PointXYZIRT> & cloud, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<rtabmap::PointXYZIRT> & cloud, const pcl::IndicesPtr & indices, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
// return CV_32FC7 (x,y,z,rgb,normal_x,normal_y,normal_z)
|
// return CV_32FC7 (x,y,z,rgb,normal_x,normal_y,normal_z)
|
||||||
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZRGB> & cloud, const pcl::PointCloud<pcl::Normal> & normals, const Transform & transform = Transform(), bool filterNaNs = true);
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZRGB> & cloud, const pcl::PointCloud<pcl::Normal> & normals, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud, const Transform & transform = Transform(), bool filterNaNs = true);
|
LaserScan RTABMAP_CORE_EXPORT laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud, const Transform & transform = Transform(), bool filterNaNs = true);
|
||||||
@@ -512,6 +529,15 @@ LaserScan RTABMAP_CORE_EXPORT deskew(
|
|||||||
} // namespace util3d
|
} // namespace util3d
|
||||||
} // namespace rtabmap
|
} // namespace rtabmap
|
||||||
|
|
||||||
|
POINT_CLOUD_REGISTER_POINT_STRUCT(rtabmap::PointXYZIRT,
|
||||||
|
(float, x, x)
|
||||||
|
(float, y, y)
|
||||||
|
(float, z, z)
|
||||||
|
(float, intensity, intensity)
|
||||||
|
(std::uint16_t, ring, ring)
|
||||||
|
(float, time, time)
|
||||||
|
)
|
||||||
|
|
||||||
#include "rtabmap/core/impl/util3d.hpp"
|
#include "rtabmap/core/impl/util3d.hpp"
|
||||||
|
|
||||||
#endif /* UTIL3D_H_ */
|
#endif /* UTIL3D_H_ */
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ SET(SRC_FILES
|
|||||||
odometry/OdometryORBSLAM3.cpp
|
odometry/OdometryORBSLAM3.cpp
|
||||||
odometry/OdometryLOAM.cpp
|
odometry/OdometryLOAM.cpp
|
||||||
odometry/OdometryFLOAM.cpp
|
odometry/OdometryFLOAM.cpp
|
||||||
|
odometry/OdometryLIOSAM.cpp
|
||||||
odometry/OdometryMSCKF.cpp
|
odometry/OdometryMSCKF.cpp
|
||||||
odometry/OdometryVINSFusion.cpp
|
odometry/OdometryVINSFusion.cpp
|
||||||
odometry/OdometryOpenVINS.cpp
|
odometry/OdometryOpenVINS.cpp
|
||||||
@@ -604,6 +605,18 @@ IF(floam_FOUND)
|
|||||||
)
|
)
|
||||||
ENDIF(floam_FOUND)
|
ENDIF(floam_FOUND)
|
||||||
|
|
||||||
|
IF(lio_sam_FOUND)
|
||||||
|
SET(INCLUDE_DIRS
|
||||||
|
${INCLUDE_DIRS}
|
||||||
|
${lio_sam_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
link_directories(${lio_sam_LIBRARY_DIRS})
|
||||||
|
SET(LIBRARIES
|
||||||
|
${LIBRARIES}
|
||||||
|
lio_sam_core
|
||||||
|
)
|
||||||
|
ENDIF(lio_sam_FOUND)
|
||||||
|
|
||||||
IF(ZED_FOUND)
|
IF(ZED_FOUND)
|
||||||
SET(INCLUDE_DIRS
|
SET(INCLUDE_DIRS
|
||||||
${INCLUDE_DIRS}
|
${INCLUDE_DIRS}
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ std::string LaserScan::formatName(const Format & format)
|
|||||||
case kXYZIT:
|
case kXYZIT:
|
||||||
name = "XYZIT";
|
name = "XYZIT";
|
||||||
break;
|
break;
|
||||||
|
case kXYZIRT:
|
||||||
|
name = "XYZIRT";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
name = "Unknown";
|
name = "Unknown";
|
||||||
break;
|
break;
|
||||||
@@ -96,6 +99,7 @@ int LaserScan::channels(const Format & format)
|
|||||||
break;
|
break;
|
||||||
case kXYZNormal:
|
case kXYZNormal:
|
||||||
case kXYINormal:
|
case kXYINormal:
|
||||||
|
case kXYZIRT:
|
||||||
channels = 6;
|
channels = 6;
|
||||||
break;
|
break;
|
||||||
case kXYZINormal:
|
case kXYZINormal:
|
||||||
@@ -123,11 +127,15 @@ bool LaserScan::isScanHasRGB(const Format & format)
|
|||||||
}
|
}
|
||||||
bool LaserScan::isScanHasIntensity(const Format & format)
|
bool LaserScan::isScanHasIntensity(const Format & format)
|
||||||
{
|
{
|
||||||
return format==kXYZI || format==kXYZINormal || format == kXYI || format == kXYINormal || format==kXYZIT;
|
return format==kXYZI || format==kXYZINormal || format == kXYI || format == kXYINormal || format==kXYZIT || format==kXYZIRT;
|
||||||
}
|
}
|
||||||
bool LaserScan::isScanHasTime(const Format & format)
|
bool LaserScan::isScanHasTime(const Format & format)
|
||||||
{
|
{
|
||||||
return format==kXYZIT;
|
return format==kXYZIT || format==kXYZIRT;
|
||||||
|
}
|
||||||
|
bool LaserScan::isScanHasRing(const Format & format)
|
||||||
|
{
|
||||||
|
return format==kXYZIRT;
|
||||||
}
|
}
|
||||||
|
|
||||||
LaserScan LaserScan::backwardCompatibility(
|
LaserScan LaserScan::backwardCompatibility(
|
||||||
@@ -404,7 +412,7 @@ void LaserScan::init(
|
|||||||
UASSERT_MSG(data.channels() != 3 || (data.channels() == 3 && (format == kXYZ || format == kXYI)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
UASSERT_MSG(data.channels() != 3 || (data.channels() == 3 && (format == kXYZ || format == kXYI)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
||||||
UASSERT_MSG(data.channels() != 4 || (data.channels() == 4 && (format == kXYZI || format == kXYZRGB)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
UASSERT_MSG(data.channels() != 4 || (data.channels() == 4 && (format == kXYZI || format == kXYZRGB)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
||||||
UASSERT_MSG(data.channels() != 5 || (data.channels() == 5 && (format == kXYNormal || format == kXYZIT)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
UASSERT_MSG(data.channels() != 5 || (data.channels() == 5 && (format == kXYNormal || format == kXYZIT)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
||||||
UASSERT_MSG(data.channels() != 6 || (data.channels() == 6 && (format == kXYINormal || format == kXYZNormal)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
UASSERT_MSG(data.channels() != 6 || (data.channels() == 6 && (format == kXYINormal || format == kXYZNormal || format == kXYZIRT)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
||||||
UASSERT_MSG(data.channels() != 7 || (data.channels() == 7 && (format == kXYZRGBNormal || format == kXYZINormal)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
UASSERT_MSG(data.channels() != 7 || (data.channels() == 7 && (format == kXYZRGBNormal || format == kXYZINormal)), uFormat("format=%s", LaserScan::formatName(format).c_str()).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
#include "rtabmap/core/odometry/OdometryORBSLAM3.h"
|
#include "rtabmap/core/odometry/OdometryORBSLAM3.h"
|
||||||
#include "rtabmap/core/odometry/OdometryLOAM.h"
|
#include "rtabmap/core/odometry/OdometryLOAM.h"
|
||||||
#include "rtabmap/core/odometry/OdometryFLOAM.h"
|
#include "rtabmap/core/odometry/OdometryFLOAM.h"
|
||||||
|
#include "rtabmap/core/odometry/OdometryLIOSAM.h"
|
||||||
#include "rtabmap/core/odometry/OdometryMSCKF.h"
|
#include "rtabmap/core/odometry/OdometryMSCKF.h"
|
||||||
#include "rtabmap/core/odometry/OdometryVINSFusion.h"
|
#include "rtabmap/core/odometry/OdometryVINSFusion.h"
|
||||||
#include "rtabmap/core/odometry/OdometryOpenVINS.h"
|
#include "rtabmap/core/odometry/OdometryOpenVINS.h"
|
||||||
@@ -101,6 +102,9 @@ Odometry * Odometry::create(Odometry::Type & type, const ParametersMap & paramet
|
|||||||
case Odometry::kTypeFLOAM:
|
case Odometry::kTypeFLOAM:
|
||||||
odometry = new OdometryFLOAM(parameters);
|
odometry = new OdometryFLOAM(parameters);
|
||||||
break;
|
break;
|
||||||
|
case Odometry::kTypeLIOSAM:
|
||||||
|
odometry = new OdometryLIOSAM(parameters);
|
||||||
|
break;
|
||||||
case Odometry::kTypeMSCKF:
|
case Odometry::kTypeMSCKF:
|
||||||
odometry = new OdometryMSCKF(parameters);
|
odometry = new OdometryMSCKF(parameters);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -903,6 +903,12 @@ ParametersMap Parameters::parseArguments(int argc, char * argv[], bool onlyParam
|
|||||||
std::cout << str << std::setw(spacing - str.size()) << "true" << std::endl;
|
std::cout << str << std::setw(spacing - str.size()) << "true" << std::endl;
|
||||||
#else
|
#else
|
||||||
std::cout << str << std::setw(spacing - str.size()) << "false" << std::endl;
|
std::cout << str << std::setw(spacing - str.size()) << "false" << std::endl;
|
||||||
|
#endif
|
||||||
|
str = "With LIO-SAM:";
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
std::cout << str << std::setw(spacing - str.size()) << "true" << std::endl;
|
||||||
|
#else
|
||||||
|
std::cout << str << std::setw(spacing - str.size()) << "false" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
str = "With FOVIS:";
|
str = "With FOVIS:";
|
||||||
#ifdef RTABMAP_FOVIS
|
#ifdef RTABMAP_FOVIS
|
||||||
|
|||||||
@@ -0,0 +1,478 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Universite de Sherbrooke nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rtabmap/core/odometry/OdometryLIOSAM.h"
|
||||||
|
#include "rtabmap/core/OdometryInfo.h"
|
||||||
|
#include "rtabmap/core/util3d.h"
|
||||||
|
#include "rtabmap/utilite/ULogger.h"
|
||||||
|
#include "rtabmap/utilite/UTimer.h"
|
||||||
|
#include "rtabmap/utilite/UStl.h"
|
||||||
|
#include "rtabmap/utilite/UDirectory.h"
|
||||||
|
#include "rtabmap/utilite/UFile.h"
|
||||||
|
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
#include <LioSamCore.h>
|
||||||
|
#include <pcl/common/transforms.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rtabmap {
|
||||||
|
|
||||||
|
static ParametersMap disableDeskewing(ParametersMap params) {
|
||||||
|
// LIO-SAM performs its own internal deskewing via imageProjection.
|
||||||
|
// The base-class deskew must be disabled so that the original per-point
|
||||||
|
// timestamps reach LIO-SAM intact.
|
||||||
|
params[Parameters::kOdomDeskewing()] = "false";
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
OdometryLIOSAM::OdometryLIOSAM(const ParametersMap & parameters) :
|
||||||
|
Odometry(disableDeskewing(parameters))
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
,lioSam_(0)
|
||||||
|
,lastPose_(Transform::getIdentity())
|
||||||
|
,lost_(false)
|
||||||
|
,linVar_(Parameters::defaultOdomLIOSAMLinVar())
|
||||||
|
,angVar_(Parameters::defaultOdomLIOSAMAngVar())
|
||||||
|
,parameters_(parameters)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
Parameters::parse(parameters, Parameters::kOdomLIOSAMLinVar(), linVar_);
|
||||||
|
UASSERT(linVar_ > 0.0f);
|
||||||
|
Parameters::parse(parameters, Parameters::kOdomLIOSAMAngVar(), angVar_);
|
||||||
|
UASSERT(angVar_ > 0.0f);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
OdometryLIOSAM::~OdometryLIOSAM()
|
||||||
|
{
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
delete lioSam_;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OdometryLIOSAM::reset(const Transform & initialPose)
|
||||||
|
{
|
||||||
|
Odometry::reset(initialPose);
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
if(lioSam_)
|
||||||
|
{
|
||||||
|
lioSam_->reset();
|
||||||
|
}
|
||||||
|
lastPose_ = Transform::getIdentity();
|
||||||
|
lost_ = false;
|
||||||
|
imuLocalTransform_ = Transform();
|
||||||
|
imuBuffer_.clear();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
bool OdometryLIOSAM::init(const Transform & imuLocalTransform, const Transform & lidarLocalTransform)
|
||||||
|
{
|
||||||
|
ParamServer config;
|
||||||
|
|
||||||
|
// Check if a config file path was provided
|
||||||
|
std::string configPath;
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMConfigPath(), configPath);
|
||||||
|
if(!configPath.empty())
|
||||||
|
{
|
||||||
|
configPath = uReplaceChar(configPath, '~', UDirectory::homeDir());
|
||||||
|
if(!UFile::exists(configPath))
|
||||||
|
{
|
||||||
|
UERROR("LIO-SAM config file not found: %s", configPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UINFO("Loading LIO-SAM parameters from config file: %s", configPath.c_str());
|
||||||
|
config = loadParamsFromYaml(configPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UINFO("No LIO-SAM config file provided, using rtabmap parameters");
|
||||||
|
|
||||||
|
// Build ParamServer from individual rtabmap parameters
|
||||||
|
int sensorType = Parameters::defaultOdomLIOSAMSensor();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMSensor(), sensorType);
|
||||||
|
if(sensorType == 1)
|
||||||
|
config.sensor = SensorType::OUSTER;
|
||||||
|
else if(sensorType == 2)
|
||||||
|
config.sensor = SensorType::LIVOX;
|
||||||
|
else
|
||||||
|
config.sensor = SensorType::VELODYNE;
|
||||||
|
|
||||||
|
config.N_SCAN = Parameters::defaultOdomLIOSAMNScan();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMNScan(), config.N_SCAN);
|
||||||
|
|
||||||
|
config.Horizon_SCAN = Parameters::defaultOdomLIOSAMHorizonScan();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMHorizonScan(), config.Horizon_SCAN);
|
||||||
|
|
||||||
|
config.imuAccNoise = Parameters::defaultOdomLIOSAMImuAccNoise();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMImuAccNoise(), config.imuAccNoise);
|
||||||
|
|
||||||
|
config.imuGyrNoise = Parameters::defaultOdomLIOSAMImuGyrNoise();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMImuGyrNoise(), config.imuGyrNoise);
|
||||||
|
|
||||||
|
config.imuAccBiasN = Parameters::defaultOdomLIOSAMImuAccBiasN();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMImuAccBiasN(), config.imuAccBiasN);
|
||||||
|
|
||||||
|
config.imuGyrBiasN = Parameters::defaultOdomLIOSAMImuGyrBiasN();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMImuGyrBiasN(), config.imuGyrBiasN);
|
||||||
|
|
||||||
|
config.imuGravity = Parameters::defaultOdomLIOSAMImuGravity();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMImuGravity(), config.imuGravity);
|
||||||
|
|
||||||
|
config.edgeThreshold = Parameters::defaultOdomLIOSAMEdgeThreshold();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMEdgeThreshold(), config.edgeThreshold);
|
||||||
|
|
||||||
|
config.surfThreshold = Parameters::defaultOdomLIOSAMSurfThreshold();
|
||||||
|
Parameters::parse(parameters_, Parameters::kOdomLIOSAMSurfThreshold(), config.surfThreshold);
|
||||||
|
|
||||||
|
// Set reasonable defaults for params not exposed via rtabmap
|
||||||
|
config.downsampleRate = 1;
|
||||||
|
config.lidarMinRange = 1.0f;
|
||||||
|
config.lidarMaxRange = 1000.0f;
|
||||||
|
config.imuRPYWeight = 0.01f;
|
||||||
|
config.odometrySurfLeafSize = 0.2f;
|
||||||
|
config.mappingCornerLeafSize = 0.2f;
|
||||||
|
config.mappingSurfLeafSize = 0.4f;
|
||||||
|
config.z_tollerance = FLT_MAX;
|
||||||
|
config.rotation_tollerance = FLT_MAX;
|
||||||
|
config.numberOfCores = 4;
|
||||||
|
config.mappingProcessInterval = 0.01;
|
||||||
|
config.surroundingkeyframeAddingDistThreshold = 1.0f;
|
||||||
|
config.surroundingkeyframeAddingAngleThreshold = 0.2f;
|
||||||
|
config.surroundingKeyframeDensity = 1.0f;
|
||||||
|
config.surroundingKeyframeSearchRadius = 50.0f;
|
||||||
|
config.loopClosureEnableFlag = false; // rtabmap handles loop closures
|
||||||
|
config.loopClosureFrequency = 1.0f;
|
||||||
|
config.surroundingKeyframeSize = 50;
|
||||||
|
config.historyKeyframeSearchRadius = 10.0f;
|
||||||
|
config.historyKeyframeSearchTimeDiff = 30.0f;
|
||||||
|
config.historyKeyframeSearchNum = 25;
|
||||||
|
config.historyKeyframeFitnessScore = 0.3f;
|
||||||
|
config.globalMapVisualizationSearchRadius = 1e3f;
|
||||||
|
config.globalMapVisualizationPoseDensity = 10.0f;
|
||||||
|
config.globalMapVisualizationLeafSize = 1.0f;
|
||||||
|
config.edgeFeatureMinValidNum = 10;
|
||||||
|
config.surfFeatureMinValidNum = 100;
|
||||||
|
config.savePCD = false;
|
||||||
|
config.useImuHeadingInitialization = false;
|
||||||
|
config.useGpsElevation = false;
|
||||||
|
config.gpsCovThreshold = 2.0f;
|
||||||
|
config.poseCovThreshold = 25.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always override extrinsics from sensor local transforms when available.
|
||||||
|
// This ensures the IMU-to-lidar transform matches the actual sensor setup
|
||||||
|
// regardless of what the config file says.
|
||||||
|
// imuLocalTransform = T_base_imu (base_link -> imu_link)
|
||||||
|
// lidarLocalTransform = T_base_lidar (base_link -> lidar_link)
|
||||||
|
// LIO-SAM's imuConverter() expects T_lidar_imu:
|
||||||
|
// T_lidar_imu = T_base_lidar^{-1} * T_base_imu
|
||||||
|
if(!imuLocalTransform.isNull() && !lidarLocalTransform.isNull())
|
||||||
|
{
|
||||||
|
Transform T_lidar_imu = lidarLocalTransform.inverse() * imuLocalTransform;
|
||||||
|
Eigen::Matrix4d T = T_lidar_imu.toEigen4d();
|
||||||
|
Eigen::Matrix3d rot = T.block<3,3>(0,0);
|
||||||
|
Eigen::Vector3d trans = T.block<3,1>(0,3);
|
||||||
|
config.extRotV = {rot(0,0), rot(0,1), rot(0,2),
|
||||||
|
rot(1,0), rot(1,1), rot(1,2),
|
||||||
|
rot(2,0), rot(2,1), rot(2,2)};
|
||||||
|
config.extRPYV = config.extRotV;
|
||||||
|
config.extTransV = {trans(0), trans(1), trans(2)};
|
||||||
|
UINFO("LIO-SAM extrinsics (T_lidar_imu) computed from sensor local transforms: %s", T_lidar_imu.prettyPrint().c_str());
|
||||||
|
}
|
||||||
|
else if(config.extRotV.size() != 9 || config.extTransV.size() != 3)
|
||||||
|
{
|
||||||
|
// No valid extrinsics from sensor data or config file
|
||||||
|
UERROR("Cannot compute IMU-to-lidar extrinsics: IMU local transform %s, lidar local transform %s. "
|
||||||
|
"Both must be valid, or the config file must contain valid extrinsics.",
|
||||||
|
imuLocalTransform.isNull() ? "is null" : "is valid",
|
||||||
|
lidarLocalTransform.isNull() ? "is null" : "is valid");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UINFO("Using extrinsics from config file (sensor local transforms not available)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the global extrinsics used by imuConverter
|
||||||
|
extRot = Eigen::Map<const Eigen::Matrix<double, 3, 3, Eigen::RowMajor> >(config.extRotV.data());
|
||||||
|
extRPY = Eigen::Map<const Eigen::Matrix<double, 3, 3, Eigen::RowMajor> >(config.extRPYV.data());
|
||||||
|
extTrans = Eigen::Map<const Eigen::Matrix<double, 3, 1> >(config.extTransV.data());
|
||||||
|
extQRPY = Eigen::Quaterniond(extRPY).inverse();
|
||||||
|
|
||||||
|
lioSam_ = new lio_sam::LioSamCore(config);
|
||||||
|
|
||||||
|
// Replay buffered IMU samples
|
||||||
|
UINFO("Replaying %d buffered IMU samples into LIO-SAM", (int)imuBuffer_.size());
|
||||||
|
for(const ImuSample & s : imuBuffer_)
|
||||||
|
{
|
||||||
|
lioSam_->addImu(s.stamp, s.acc, s.gyro, s.orientation);
|
||||||
|
}
|
||||||
|
imuBuffer_.clear();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Transform OdometryLIOSAM::computeTransform(
|
||||||
|
SensorData & data,
|
||||||
|
const Transform & guess,
|
||||||
|
OdometryInfo * info)
|
||||||
|
{
|
||||||
|
Transform t;
|
||||||
|
#ifdef RTABMAP_LIOSAM
|
||||||
|
UTimer timer;
|
||||||
|
UTimer timerTotal;
|
||||||
|
|
||||||
|
// Handle async IMU data (canProcessAsyncIMU() == true means
|
||||||
|
// the base class sends IMU-only data directly to computeTransform)
|
||||||
|
if(!data.imu().empty())
|
||||||
|
{
|
||||||
|
Eigen::Quaterniond qd(
|
||||||
|
data.imu().orientation()[3], // w
|
||||||
|
data.imu().orientation()[0], // x
|
||||||
|
data.imu().orientation()[1], // y
|
||||||
|
data.imu().orientation()[2]); // z
|
||||||
|
Eigen::Vector3d acc(
|
||||||
|
data.imu().linearAcceleration()[0],
|
||||||
|
data.imu().linearAcceleration()[1],
|
||||||
|
data.imu().linearAcceleration()[2]);
|
||||||
|
Eigen::Vector3d gyro(
|
||||||
|
data.imu().angularVelocity()[0],
|
||||||
|
data.imu().angularVelocity()[1],
|
||||||
|
data.imu().angularVelocity()[2]);
|
||||||
|
|
||||||
|
// Deferred initialization: need both IMU and lidar local transforms
|
||||||
|
// to compute T_lidar_imu extrinsics for LIO-SAM.
|
||||||
|
if(!lioSam_)
|
||||||
|
{
|
||||||
|
// Cache IMU local transform when first available
|
||||||
|
if(imuLocalTransform_.isNull() && !data.imu().localTransform().isNull())
|
||||||
|
{
|
||||||
|
imuLocalTransform_ = data.imu().localTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to initialize if we have both transforms
|
||||||
|
if(!imuLocalTransform_.isNull() && !data.laserScanRaw().isEmpty() &&
|
||||||
|
!data.laserScanRaw().localTransform().isNull())
|
||||||
|
{
|
||||||
|
if(!init(imuLocalTransform_, data.laserScanRaw().localTransform()))
|
||||||
|
{
|
||||||
|
UERROR("Failed to initialize LIO-SAM");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Buffer IMU until we can initialize
|
||||||
|
ImuSample s;
|
||||||
|
s.stamp = data.stamp();
|
||||||
|
s.acc = acc;
|
||||||
|
s.gyro = gyro;
|
||||||
|
s.orientation = qd;
|
||||||
|
imuBuffer_.push_back(s);
|
||||||
|
|
||||||
|
if(data.laserScanRaw().isEmpty())
|
||||||
|
{
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lioSam_)
|
||||||
|
{
|
||||||
|
lioSam_->addImu(data.stamp(), acc, gyro, qd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMU-only: no pose to return
|
||||||
|
if(data.laserScanRaw().isEmpty())
|
||||||
|
{
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!lioSam_)
|
||||||
|
{
|
||||||
|
// A scan arrived without IMU in the same message.
|
||||||
|
// Try to init if the IMU local transform was already cached.
|
||||||
|
if(!imuLocalTransform_.isNull() && !data.laserScanRaw().isEmpty() &&
|
||||||
|
!data.laserScanRaw().localTransform().isNull())
|
||||||
|
{
|
||||||
|
if(!init(imuLocalTransform_, data.laserScanRaw().localTransform()))
|
||||||
|
{
|
||||||
|
UERROR("Failed to initialize LIO-SAM");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UDEBUG("LIO-SAM not yet initialized, waiting for IMU (have=%s) and lidar (need scan) local transforms...",
|
||||||
|
imuLocalTransform_.isNull() ? "no" : "yes");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.laserScanRaw().isEmpty())
|
||||||
|
{
|
||||||
|
UERROR("LIO-SAM requires laser scans and the current input is empty. Aborting odometry update...");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
else if(data.laserScanRaw().is2d())
|
||||||
|
{
|
||||||
|
UERROR("LIO-SAM requires 3D laser scans. Aborting odometry update...");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat covariance = cv::Mat::eye(6, 6, CV_64FC1) * 9999;
|
||||||
|
if(!lost_)
|
||||||
|
{
|
||||||
|
const LaserScan & scan = data.laserScanRaw();
|
||||||
|
if(scan.format() != LaserScan::kXYZIRT)
|
||||||
|
{
|
||||||
|
UERROR("LIO-SAM requires a scan in format %s (got %s). "
|
||||||
|
"Populate the scan via util3d::laserScanFromPointCloud<PointXYZIRT>() "
|
||||||
|
"so that per-point ring and time fields are available.",
|
||||||
|
LaserScan::formatName(LaserScan::kXYZIRT).c_str(),
|
||||||
|
scan.formatName().c_str());
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the kXYZIRT scan into the three parallel buffers LIO-SAM expects.
|
||||||
|
const int numPoints = scan.size();
|
||||||
|
const int ringOffset = scan.getRingOffset();
|
||||||
|
const int timeOffset = scan.getTimeOffset();
|
||||||
|
pcl::PointCloud<pcl::PointXYZI>::Ptr laserCloudIn(new pcl::PointCloud<pcl::PointXYZI>);
|
||||||
|
laserCloudIn->reserve(numPoints);
|
||||||
|
std::vector<int> rings;
|
||||||
|
std::vector<float> times;
|
||||||
|
rings.reserve(numPoints);
|
||||||
|
times.reserve(numPoints);
|
||||||
|
for(int i=0; i<numPoints; ++i)
|
||||||
|
{
|
||||||
|
const int row = i / scan.data().cols;
|
||||||
|
const int col = i - row * scan.data().cols;
|
||||||
|
const float * ptr = scan.data().ptr<float>(row, col);
|
||||||
|
pcl::PointXYZI pt;
|
||||||
|
pt.x = ptr[0];
|
||||||
|
pt.y = ptr[1];
|
||||||
|
pt.z = ptr[2];
|
||||||
|
pt.intensity = ptr[3];
|
||||||
|
laserCloudIn->push_back(pt);
|
||||||
|
rings.push_back(static_cast<int>(ptr[ringOffset]));
|
||||||
|
times.push_back(ptr[timeOffset]);
|
||||||
|
}
|
||||||
|
UDEBUG("Scan split: %fs, points=%d", timer.ticks(), (int)laserCloudIn->size());
|
||||||
|
|
||||||
|
// Process scan. Retrieve the deskewed (motion-compensated) cloud
|
||||||
|
// produced by LIO-SAM's image projection stage so we can propagate
|
||||||
|
// it back into SensorData: otherwise downstream consumers such as
|
||||||
|
// loop closure registration would still see the raw pre-deskew scan.
|
||||||
|
Eigen::Affine3f poseOut;
|
||||||
|
Eigen::MatrixXd covOut;
|
||||||
|
pcl::PointCloud<pcl::PointXYZI>::Ptr deskewedCloud(new pcl::PointCloud<pcl::PointXYZI>);
|
||||||
|
bool ok = lioSam_->processScan(data.stamp(), laserCloudIn, rings, times, poseOut, covOut, deskewedCloud);
|
||||||
|
UDEBUG("LIO-SAM process: %fs", timer.ticks());
|
||||||
|
|
||||||
|
if(ok)
|
||||||
|
{
|
||||||
|
// Replace the raw scan on SensorData with LIO-SAM's deskewed
|
||||||
|
// cloud so downstream stages (loop closure registration in
|
||||||
|
// particular) use the motion-compensated points instead of
|
||||||
|
// the raw pre-deskew scan. The deskewed cloud is still in the
|
||||||
|
// lidar frame, so the existing localTransform/rangeMax apply.
|
||||||
|
if(deskewedCloud && !deskewedCloud->empty())
|
||||||
|
{
|
||||||
|
const LaserScan & rawScan = data.laserScanRaw();
|
||||||
|
LaserScan deskewedScan(
|
||||||
|
util3d::laserScanFromPointCloud(*deskewedCloud),
|
||||||
|
rawScan.maxPoints(),
|
||||||
|
rawScan.rangeMax(),
|
||||||
|
rawScan.localTransform());
|
||||||
|
data.setLaserScan(deskewedScan);
|
||||||
|
UDEBUG("Replaced raw scan with deskewed cloud (%d -> %d points)",
|
||||||
|
(int)laserCloudIn->size(), (int)deskewedCloud->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform pose = Transform::fromEigen3f(poseOut);
|
||||||
|
|
||||||
|
if(!pose.isNull())
|
||||||
|
{
|
||||||
|
covariance = cv::Mat::eye(6, 6, CV_64FC1);
|
||||||
|
covariance(cv::Range(0, 3), cv::Range(0, 3)) *= linVar_;
|
||||||
|
covariance(cv::Range(3, 6), cv::Range(3, 6)) *= angVar_;
|
||||||
|
|
||||||
|
t = lastPose_.inverse() * pose; // incremental
|
||||||
|
lastPose_ = pose;
|
||||||
|
|
||||||
|
const Transform & localTransform = data.laserScanRaw().localTransform();
|
||||||
|
if(!t.isNull() && !t.isIdentity() && !localTransform.isIdentity() && !localTransform.isNull())
|
||||||
|
{
|
||||||
|
// from laser frame to base frame
|
||||||
|
t = localTransform * t * localTransform.inverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(info)
|
||||||
|
{
|
||||||
|
info->type = (int)kTypeLIOSAM;
|
||||||
|
if(covariance.cols == 6 && covariance.rows == 6 && covariance.type() == CV_64FC1)
|
||||||
|
{
|
||||||
|
info->reg.covariance = covariance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->isInfoDataFilled())
|
||||||
|
{
|
||||||
|
pcl::PointCloud<pcl::PointXYZI>::Ptr localMap = lioSam_->getLocalMap();
|
||||||
|
if(localMap && !localMap->empty())
|
||||||
|
{
|
||||||
|
info->localScanMapSize = localMap->size();
|
||||||
|
info->localScanMap = LaserScan(util3d::laserScanFromPointCloud(*localMap), 0, data.laserScanRaw().rangeMax(), data.laserScanRaw().localTransform());
|
||||||
|
}
|
||||||
|
UDEBUG("Fill info data: %fs", timer.ticks());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lost_ = true;
|
||||||
|
UWARN("LIO-SAM failed to register the latest scan, odometry should be reset.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UDEBUG("LIO-SAM processScan returned false (may be initializing)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UINFO("LIO-SAM odom update time = %fs, lost=%s", timerTotal.elapsed(), lost_ ? "true" : "false");
|
||||||
|
|
||||||
|
#else
|
||||||
|
UERROR("RTAB-Map is not built with LIO-SAM support! Select another odometry approach.");
|
||||||
|
#endif
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtabmap
|
||||||
+75
-7
@@ -1762,6 +1762,55 @@ LaserScan laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud,
|
|||||||
return laserScanFromPointCloud(cloud, pcl::IndicesPtr(), transform, filterNaNs);
|
return laserScanFromPointCloud(cloud, pcl::IndicesPtr(), transform, filterNaNs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaserScan laserScanFromPointCloud(const pcl::PointCloud<rtabmap::PointXYZIRT> & cloud, const Transform & transform, bool filterNaNs)
|
||||||
|
{
|
||||||
|
return laserScanFromPointCloud(cloud, pcl::IndicesPtr(), transform, filterNaNs);
|
||||||
|
}
|
||||||
|
|
||||||
|
LaserScan laserScanFromPointCloud(const pcl::PointCloud<rtabmap::PointXYZIRT> & cloud, const pcl::IndicesPtr & indices, const Transform & transform, bool filterNaNs)
|
||||||
|
{
|
||||||
|
// Layout: [x, y, z, intensity, ring, time] (ring cast to float, values up to
|
||||||
|
// ~16M are exactly representable so all realistic laser line counts fit).
|
||||||
|
cv::Mat laserScan;
|
||||||
|
bool nullTransform = transform.isNull() || transform.isIdentity();
|
||||||
|
Eigen::Affine3f transform3f = transform.toEigen3f();
|
||||||
|
int oi = 0;
|
||||||
|
const int total = indices.get() ? (int)indices->size() : (int)cloud.size();
|
||||||
|
laserScan = cv::Mat(1, total, CV_32FC(6));
|
||||||
|
for(int i=0; i<total; ++i)
|
||||||
|
{
|
||||||
|
int index = indices.get() ? indices->at(i) : i;
|
||||||
|
const rtabmap::PointXYZIRT & src = cloud.at(index);
|
||||||
|
if(filterNaNs && !pcl::isFinite(src))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float * ptr = laserScan.ptr<float>(0, oi++);
|
||||||
|
if(!nullTransform)
|
||||||
|
{
|
||||||
|
pcl::PointXYZ pt(src.x, src.y, src.z);
|
||||||
|
pt = pcl::transformPoint(pt, transform3f);
|
||||||
|
ptr[0] = pt.x;
|
||||||
|
ptr[1] = pt.y;
|
||||||
|
ptr[2] = pt.z;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ptr[0] = src.x;
|
||||||
|
ptr[1] = src.y;
|
||||||
|
ptr[2] = src.z;
|
||||||
|
}
|
||||||
|
ptr[3] = src.intensity;
|
||||||
|
ptr[4] = static_cast<float>(src.ring);
|
||||||
|
ptr[5] = src.time;
|
||||||
|
}
|
||||||
|
if(oi == 0)
|
||||||
|
{
|
||||||
|
return LaserScan();
|
||||||
|
}
|
||||||
|
return LaserScan(laserScan(cv::Range::all(), cv::Range(0, oi)), 0, 0.0f, LaserScan::kXYZIRT);
|
||||||
|
}
|
||||||
|
|
||||||
LaserScan laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const pcl::IndicesPtr & indices, const Transform & transform, bool filterNaNs)
|
LaserScan laserScanFromPointCloud(const pcl::PointCloud<pcl::PointXYZI> & cloud, const pcl::IndicesPtr & indices, const Transform & transform, bool filterNaNs)
|
||||||
{
|
{
|
||||||
cv::Mat laserScan;
|
cv::Mat laserScan;
|
||||||
@@ -2343,7 +2392,7 @@ pcl::PCLPointCloud2::Ptr laserScanToPointCloud2(const LaserScan & laserScan, con
|
|||||||
{
|
{
|
||||||
pcl::toPCLPointCloud2(*laserScanToPointCloud(laserScan, transform), *cloud);
|
pcl::toPCLPointCloud2(*laserScanToPointCloud(laserScan, transform), *cloud);
|
||||||
}
|
}
|
||||||
else if(laserScan.format() == LaserScan::kXYI || laserScan.format() == LaserScan::kXYZI || laserScan.format() == LaserScan::kXYZIT)
|
else if(laserScan.format() == LaserScan::kXYI || laserScan.format() == LaserScan::kXYZI || laserScan.format() == LaserScan::kXYZIT || laserScan.format() == LaserScan::kXYZIRT)
|
||||||
{
|
{
|
||||||
pcl::toPCLPointCloud2(*laserScanToPointCloudI(laserScan, transform), *cloud);
|
pcl::toPCLPointCloud2(*laserScanToPointCloudI(laserScan, transform), *cloud);
|
||||||
}
|
}
|
||||||
@@ -3807,9 +3856,11 @@ LaserScan deskew(
|
|||||||
return LaserScan();
|
return LaserScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(input.format() != LaserScan::kXYZIT)
|
if(!input.hasTime())
|
||||||
{
|
{
|
||||||
UERROR("input scan doesn't have \"time\" channel! Only format \"%s\" supported yet.", LaserScan::formatName(LaserScan::kXYZIT).c_str());
|
UERROR("input scan doesn't have a \"time\" channel! Supported formats: \"%s\", \"%s\".",
|
||||||
|
LaserScan::formatName(LaserScan::kXYZIT).c_str(),
|
||||||
|
LaserScan::formatName(LaserScan::kXYZIRT).c_str());
|
||||||
return LaserScan();
|
return LaserScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3865,7 +3916,14 @@ LaserScan deskew(
|
|||||||
double stamp;
|
double stamp;
|
||||||
UTimer processingTime;
|
UTimer processingTime;
|
||||||
double scanTime = lastStamp - firstStamp;
|
double scanTime = lastStamp - firstStamp;
|
||||||
cv::Mat output(1, input.size(), CV_32FC4); // XYZI - Dense
|
// Preserve ring when input carries it (kXYZIRT): the geometric channel is
|
||||||
|
// still meaningful after deskewing. Per-point time is zeroed because all
|
||||||
|
// points share the same pose after correction.
|
||||||
|
const bool preserveRing = input.hasRing();
|
||||||
|
const int offsetRing = input.getRingOffset();
|
||||||
|
const LaserScan::Format outputFormat = preserveRing ? LaserScan::kXYZIRT : LaserScan::kXYZI;
|
||||||
|
const int outputChannels = preserveRing ? 6 : 4;
|
||||||
|
cv::Mat output(1, input.size(), CV_32FC(outputChannels));
|
||||||
int offsetIntensity = input.getIntensityOffset();
|
int offsetIntensity = input.getIntensityOffset();
|
||||||
bool isLocalTransformIdentity = input.localTransform().isIdentity();
|
bool isLocalTransformIdentity = input.localTransform().isIdentity();
|
||||||
Transform localTransformInv = input.localTransform().inverse();
|
Transform localTransformInv = input.localTransform().inverse();
|
||||||
@@ -3904,7 +3962,12 @@ LaserScan deskew(
|
|||||||
dataPtr[0] = pt.x;
|
dataPtr[0] = pt.x;
|
||||||
dataPtr[1] = pt.y;
|
dataPtr[1] = pt.y;
|
||||||
dataPtr[2] = pt.z;
|
dataPtr[2] = pt.z;
|
||||||
dataPtr[3] = input.data().ptr<float>(v, u)[offsetIntensity];
|
dataPtr[3] = inputPtr[offsetIntensity];
|
||||||
|
if(preserveRing)
|
||||||
|
{
|
||||||
|
dataPtr[4] = inputPtr[offsetRing];
|
||||||
|
dataPtr[5] = 0.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3941,14 +4004,19 @@ LaserScan deskew(
|
|||||||
dataPtr[0] = pt.x;
|
dataPtr[0] = pt.x;
|
||||||
dataPtr[1] = pt.y;
|
dataPtr[1] = pt.y;
|
||||||
dataPtr[2] = pt.z;
|
dataPtr[2] = pt.z;
|
||||||
dataPtr[3] = input.data().ptr<float>(v, u)[offsetIntensity];
|
dataPtr[3] = inputPtr[offsetIntensity];
|
||||||
|
if(preserveRing)
|
||||||
|
{
|
||||||
|
dataPtr[4] = inputPtr[offsetRing];
|
||||||
|
dataPtr[5] = 0.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output = cv::Mat(output, cv::Range::all(), cv::Range(0, oi));
|
output = cv::Mat(output, cv::Range::all(), cv::Range(0, oi));
|
||||||
UDEBUG("Lidar deskewing time=%fs", processingTime.elapsed());
|
UDEBUG("Lidar deskewing time=%fs", processingTime.elapsed());
|
||||||
return LaserScan(output, input.maxPoints(), input.rangeMax(), LaserScan::kXYZI, input.localTransform());
|
return LaserScan(output, input.maxPoints(), input.rangeMax(), outputFormat, input.localTransform());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ private Q_SLOTS:
|
|||||||
void changeOdometryORBSLAMVocabulary();
|
void changeOdometryORBSLAMVocabulary();
|
||||||
void changeOdometryOKVISConfigPath();
|
void changeOdometryOKVISConfigPath();
|
||||||
void changeOdometryVINSFusionConfigPath();
|
void changeOdometryVINSFusionConfigPath();
|
||||||
|
void changeOdometryLIOSAMConfigPath();
|
||||||
void changeOdometryOpenVINSLeftMask();
|
void changeOdometryOpenVINSLeftMask();
|
||||||
void changeOdometryOpenVINSRightMask();
|
void changeOdometryOpenVINSRightMask();
|
||||||
void changeIcpPMConfigPath();
|
void changeIcpPMConfigPath();
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
|
|||||||
#ifndef RTABMAP_CUVSLAM
|
#ifndef RTABMAP_CUVSLAM
|
||||||
_ui->odom_strategy->setItemData(13, 0, Qt::UserRole - 1);
|
_ui->odom_strategy->setItemData(13, 0, Qt::UserRole - 1);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef RTABMAP_LIOSAM
|
||||||
|
_ui->odom_strategy->setItemData(14, 0, Qt::UserRole - 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if CV_MAJOR_VERSION < 3
|
#if CV_MAJOR_VERSION < 3
|
||||||
_ui->stereosgbm_mode->setItemData(2, 0, Qt::UserRole - 1);
|
_ui->stereosgbm_mode->setItemData(2, 0, Qt::UserRole - 1);
|
||||||
@@ -1698,6 +1701,22 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
|
|||||||
// Odometry CuVSLAM
|
// Odometry CuVSLAM
|
||||||
_ui->odom_cuvslam_multicam_mode->setObjectName(Parameters::kOdomCuVSLAMMulticamMode().c_str());
|
_ui->odom_cuvslam_multicam_mode->setObjectName(Parameters::kOdomCuVSLAMMulticamMode().c_str());
|
||||||
|
|
||||||
|
// Odometry LIO-SAM
|
||||||
|
_ui->lineEdit_OdomLIOSAMPath->setObjectName(Parameters::kOdomLIOSAMConfigPath().c_str());
|
||||||
|
connect(_ui->toolButton_OdomLIOSAMPath, SIGNAL(clicked()), this, SLOT(changeOdometryLIOSAMConfigPath()));
|
||||||
|
_ui->odom_liosam_sensor->setObjectName(Parameters::kOdomLIOSAMSensor().c_str());
|
||||||
|
_ui->odom_liosam_nscan->setObjectName(Parameters::kOdomLIOSAMNScan().c_str());
|
||||||
|
_ui->odom_liosam_horizon_scan->setObjectName(Parameters::kOdomLIOSAMHorizonScan().c_str());
|
||||||
|
_ui->odom_liosam_imu_acc_noise->setObjectName(Parameters::kOdomLIOSAMImuAccNoise().c_str());
|
||||||
|
_ui->odom_liosam_imu_gyr_noise->setObjectName(Parameters::kOdomLIOSAMImuGyrNoise().c_str());
|
||||||
|
_ui->odom_liosam_imu_acc_bias_n->setObjectName(Parameters::kOdomLIOSAMImuAccBiasN().c_str());
|
||||||
|
_ui->odom_liosam_imu_gyr_bias_n->setObjectName(Parameters::kOdomLIOSAMImuGyrBiasN().c_str());
|
||||||
|
_ui->odom_liosam_imu_gravity->setObjectName(Parameters::kOdomLIOSAMImuGravity().c_str());
|
||||||
|
_ui->odom_liosam_edge_threshold->setObjectName(Parameters::kOdomLIOSAMEdgeThreshold().c_str());
|
||||||
|
_ui->odom_liosam_surf_threshold->setObjectName(Parameters::kOdomLIOSAMSurfThreshold().c_str());
|
||||||
|
_ui->odom_liosam_linvar->setObjectName(Parameters::kOdomLIOSAMLinVar().c_str());
|
||||||
|
_ui->odom_liosam_angvar->setObjectName(Parameters::kOdomLIOSAMAngVar().c_str());
|
||||||
|
|
||||||
//StereoDense
|
//StereoDense
|
||||||
_ui->comboBox_stereoDense_strategy->setObjectName(Parameters::kStereoDenseStrategy().c_str());
|
_ui->comboBox_stereoDense_strategy->setObjectName(Parameters::kStereoDenseStrategy().c_str());
|
||||||
connect(_ui->comboBox_stereoDense_strategy, SIGNAL(currentIndexChanged(int)), _ui->stackedWidget_stereoDense, SLOT(setCurrentIndex(int)));
|
connect(_ui->comboBox_stereoDense_strategy, SIGNAL(currentIndexChanged(int)), _ui->stackedWidget_stereoDense, SLOT(setCurrentIndex(int)));
|
||||||
@@ -5572,6 +5591,7 @@ void PreferencesDialog::updateOdometryStackedIndex(int index)
|
|||||||
_ui->groupBox_odomOpenVINS->setVisible(index==10);
|
_ui->groupBox_odomOpenVINS->setVisible(index==10);
|
||||||
_ui->groupBox_odomOpen3D->setVisible(index==12);
|
_ui->groupBox_odomOpen3D->setVisible(index==12);
|
||||||
_ui->groupBox_odomCuvslam->setVisible(index==13);
|
_ui->groupBox_odomCuvslam->setVisible(index==13);
|
||||||
|
_ui->groupBox_odomLIOSAM->setVisible(index==14);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreferencesDialog::useOdomFeatures()
|
void PreferencesDialog::useOdomFeatures()
|
||||||
@@ -5677,6 +5697,23 @@ void PreferencesDialog::changeOdometryVINSFusionConfigPath()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PreferencesDialog::changeOdometryLIOSAMConfigPath()
|
||||||
|
{
|
||||||
|
QString path;
|
||||||
|
if(_ui->lineEdit_OdomLIOSAMPath->text().isEmpty())
|
||||||
|
{
|
||||||
|
path = QFileDialog::getOpenFileName(this, tr("LIO-SAM Config"), this->getWorkingDirectory(), tr("LIO-SAM config (*.yaml)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = QFileDialog::getOpenFileName(this, tr("LIO-SAM Config"), _ui->lineEdit_OdomLIOSAMPath->text(), tr("LIO-SAM config (*.yaml)"));
|
||||||
|
}
|
||||||
|
if(!path.isEmpty())
|
||||||
|
{
|
||||||
|
_ui->lineEdit_OdomLIOSAMPath->setText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PreferencesDialog::changeOdometryOpenVINSLeftMask()
|
void PreferencesDialog::changeOdometryOpenVINSLeftMask()
|
||||||
{
|
{
|
||||||
QString path;
|
QString path;
|
||||||
|
|||||||
@@ -16137,6 +16137,11 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
|
|||||||
<string>CuVSLAM</string>
|
<string>CuVSLAM</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>LIO-SAM</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
@@ -21847,6 +21852,458 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_103">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_187">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_odomLIOSAM">
|
||||||
|
<property name="title">
|
||||||
|
<string>LIO-SAM</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_188" stretch="0,0">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_info">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p>LIO-SAM: <a href="https://github.com/TixiaoShan/LIO-SAM"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/TixiaoShan/LIO-SAM</span></a></p><p>Tightly-coupled lidar-inertial odometry via smoothing and mapping. Requires 3D lidar with per-point ring and time fields (kXYZIRT scan format) and an IMU.</p><p>If a config file path is provided, the parameters below are ignored and read from the file instead.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_liosam" columnstretch="0,1">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_liosam_config">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_OdomLIOSAMPath">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Optional: path to LIO-SAM config YAML</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="toolButton_OdomLIOSAMPath">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_config">
|
||||||
|
<property name="text">
|
||||||
|
<string>Config file path (overrides parameters below).</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QComboBox" name="odom_liosam_sensor">
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Velodyne</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Ouster</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Livox</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_sensor">
|
||||||
|
<property name="text">
|
||||||
|
<string>LiDAR sensor type.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QSpinBox" name="odom_liosam_nscan">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>256</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>16</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_nscan">
|
||||||
|
<property name="text">
|
||||||
|
<string>Number of LiDAR channels (16, 32, 64, 128).</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QSpinBox" name="odom_liosam_horizon_scan">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1800</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_horizon_scan">
|
||||||
|
<property name="text">
|
||||||
|
<string>Horizontal resolution (Velodyne:1800, Ouster:512/1024/2048).</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_imu_acc_noise">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000001000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_imu_acc_noise">
|
||||||
|
<property name="text">
|
||||||
|
<string>IMU accelerometer white noise.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_imu_gyr_noise">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000001000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_imu_gyr_noise">
|
||||||
|
<property name="text">
|
||||||
|
<string>IMU gyroscope white noise.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_imu_acc_bias_n">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000001000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.000200000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_imu_acc_bias_n">
|
||||||
|
<property name="text">
|
||||||
|
<string>IMU accelerometer bias noise.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_imu_gyr_bias_n">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000001000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.000010000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.000030000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_imu_gyr_bias_n">
|
||||||
|
<property name="text">
|
||||||
|
<string>IMU gyroscope bias noise.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_imu_gravity">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>20.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>9.805110000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_imu_gravity">
|
||||||
|
<property name="text">
|
||||||
|
<string>Gravity magnitude.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_edge_threshold">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>100.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_edge_threshold">
|
||||||
|
<property name="text">
|
||||||
|
<string>Edge feature curvature threshold.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_surf_threshold">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>100.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_surf_threshold">
|
||||||
|
<property name="text">
|
||||||
|
<string>Surface feature curvature threshold.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_linvar">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_linvar">
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear output variance.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="12" column="0">
|
||||||
|
<widget class="QDoubleSpinBox" name="odom_liosam_angvar">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000100000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="12" column="1">
|
||||||
|
<widget class="QLabel" name="label_odom_liosam_angvar">
|
||||||
|
<property name="text">
|
||||||
|
<string>Angular output variance.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_93">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
<widget class="QWidget" name="page_26">
|
<widget class="QWidget" name="page_26">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_88">
|
<layout class="QVBoxLayout" name="verticalLayout_88">
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
Reference in New Issue
Block a user