Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multi-sensor scenarios #232

Merged
merged 22 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0cad647
Fix TF tree usage in ROS 2 wrapper
nachovizzo Oct 3, 2023
e9d6784
Transform the pose instead of the pointcloud
nachovizzo Oct 13, 2023
98e71fb
Merge remote-tracking branch 'origin/main' into nacho/fix_ros_tf_tree…
nachovizzo Oct 13, 2023
e065ee9
remove include
nachovizzo Oct 13, 2023
8ea67c6
Remove default base_frame parameter value
nachovizzo Oct 19, 2023
73a98d3
Remove unused variable
nachovizzo Oct 19, 2023
79e5a3f
Do not reuse pose_msg.pose
nachovizzo Oct 19, 2023
a8e5229
I'm not ever sure why we did that in first instance
nachovizzo Oct 19, 2023
1ff44c3
Be more explicit about the stamps and frame_id of the headers
nachovizzo Oct 19, 2023
70d972d
Extract publishing mechanism to a new function (#246)
nachovizzo Oct 19, 2023
505ff8f
Convert class member function to more useful utility
nachovizzo Oct 19, 2023
5e17edc
Bring back debugging clouds to ros driver
nachovizzo Oct 19, 2023
2cfcab5
re-arange logic on when and when not to publish clouds
nachovizzo Oct 19, 2023
ee94752
fix lofic
nachovizzo Oct 19, 2023
22b7f92
Update rviz2
nachovizzo Oct 19, 2023
b45fde3
Loosing my mind already with this debugging info
nachovizzo Oct 19, 2023
8a46189
Backport tf multisensor fix (#245)
playertr Oct 20, 2023
20417f9
Fix style
nachovizzo Oct 20, 2023
e7be78c
Remove sophus from build system
nachovizzo Oct 20, 2023
7137e88
Remove unnecessary alias
nachovizzo Oct 20, 2023
4b61214
Merge branch 'main' into nacho/fix_ros_tf_tree_usage
tizianoGuadagnino Oct 27, 2023
a28cbb8
Merge branch 'main' into nacho/fix_ros_tf_tree_usage
tizianoGuadagnino Oct 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ if("$ENV{ROS_VERSION}" STREQUAL "1")
COMPONENTS geometry_msgs
nav_msgs
sensor_msgs
geometry_msgs
roscpp
rosbag
std_msgs
Expand Down Expand Up @@ -84,6 +85,7 @@ elseif("$ENV{ROS_VERSION}" STREQUAL "2")
rclcpp_components
nav_msgs
sensor_msgs
geometry_msgs
tf2_ros)

rclcpp_components_register_node(odometry_component PLUGIN "kiss_icp_ros::OdometryServer" EXECUTABLE odometry_node)
Expand Down
9 changes: 4 additions & 5 deletions ros/launch/odometry.launch
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
<arg name="bagfile" default=""/>
<arg name="visualize" default="true"/>
<arg name="odom_frame" default="odom"/>
<arg name="child_frame" default="base_link"/>
<arg name="base_frame" default=""/>
<arg name="topic" default=""/>
<arg name="publish_odom_tf" default="true"/>
<arg name="publish_alias_tf" default="true"/>
<arg name="publish_odom_tf" default="false"/>

<!-- KISS-ICP paramaters -->
<arg name="deskew" default="false"/>
Expand All @@ -20,9 +19,9 @@
<!-- ROS params -->
<remap from="pointcloud_topic" to="$(arg topic)"/>
<param name="odom_frame" value="$(arg odom_frame)"/>
<param name="child_frame" value="$(arg child_frame)"/>
<param name="base_frame" value="$(arg base_frame)"/>
<param name="publish_odom_tf" value="$(arg publish_odom_tf)"/>
<param name="publish_alias_tf" value="$(arg publish_alias_tf)"/>
<param name="visualize" value="$(arg visualize)"/>
<!-- KISS-ICP params -->
<param name="max_range" value="$(arg max_range)"/>
<param name="min_range" value="$(arg min_range)"/>
Expand Down
7 changes: 3 additions & 4 deletions ros/launch/odometry.launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ def generate_launch_description():
DeclareLaunchArgument("bagfile", default_value=""),
DeclareLaunchArgument("visualize", default_value="true"),
DeclareLaunchArgument("odom_frame", default_value="odom"),
DeclareLaunchArgument("child_frame", default_value="base_link"),
DeclareLaunchArgument("base_frame", default_value=""),
DeclareLaunchArgument("publish_odom_tf", default_value="true"),
DeclareLaunchArgument("publish_alias_tf", default_value="true"),
# KISS-ICP parameters
DeclareLaunchArgument("deskew", default_value="false"),
DeclareLaunchArgument("max_range", default_value="100.0"),
Expand All @@ -60,7 +59,7 @@ def generate_launch_description():
parameters=[
{
"odom_frame": LaunchConfiguration("odom_frame"),
"child_frame": LaunchConfiguration("child_frame"),
"base_frame": LaunchConfiguration("base_frame"),
"max_range": LaunchConfiguration("max_range"),
"min_range": LaunchConfiguration("min_range"),
"deskew": LaunchConfiguration("deskew"),
Expand All @@ -69,7 +68,7 @@ def generate_launch_description():
"initial_threshold": 2.0,
"min_motion_th": 0.1,
"publish_odom_tf": LaunchConfiguration("publish_odom_tf"),
"publish_alias_tf": LaunchConfiguration("publish_alias_tf"),
"visualize": LaunchConfiguration("visualize"),
}
],
),
Expand Down
171 changes: 104 additions & 67 deletions ros/ros1/OdometryServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ using utils::GetTimestamps;
using utils::PointCloud2ToEigen;

OdometryServer::OdometryServer(const ros::NodeHandle &nh, const ros::NodeHandle &pnh)
: nh_(nh), pnh_(pnh) {
pnh_.param("child_frame", child_frame_, child_frame_);
: nh_(nh), pnh_(pnh), tf2_listener_(tf2_ros::TransformListener(tf2_buffer_)) {
pnh_.param("base_frame", base_frame_, base_frame_);
pnh_.param("odom_frame", odom_frame_, odom_frame_);
pnh_.param("publish_alias_tf", publish_alias_tf_, true);
pnh_.param("publish_odom_tf", publish_odom_tf_, true);
pnh_.param("publish_odom_tf", publish_odom_tf_, false);
pnh_.param("visualize", publish_debug_clouds_, publish_debug_clouds_);
pnh_.param("max_range", config_.max_range, config_.max_range);
pnh_.param("min_range", config_.min_range, config_.min_range);
pnh_.param("deskew", config_.deskew, config_.deskew);
Expand All @@ -76,102 +76,139 @@ OdometryServer::OdometryServer(const ros::NodeHandle &nh, const ros::NodeHandle
&OdometryServer::RegisterFrame, this);

// Initialize publishers
odom_publisher_ = pnh_.advertise<nav_msgs::Odometry>("odometry", queue_size_);
frame_publisher_ = pnh_.advertise<sensor_msgs::PointCloud2>("frame", queue_size_);
kpoints_publisher_ = pnh_.advertise<sensor_msgs::PointCloud2>("keypoints", queue_size_);
map_publisher_ = pnh_.advertise<sensor_msgs::PointCloud2>("local_map", queue_size_);

// Initialize trajectory publisher
path_msg_.header.frame_id = odom_frame_;
traj_publisher_ = pnh_.advertise<nav_msgs::Path>("trajectory", queue_size_);

// Broadcast a static transformation that links with identity the specified base link to the
// pointcloud_frame, basically to always be able to visualize the frame in rviz
if (publish_alias_tf_ && child_frame_ != "base_link") {
static tf2_ros::StaticTransformBroadcaster br;
geometry_msgs::TransformStamped alias_transform_msg;
alias_transform_msg.header.stamp = ros::Time::now();
alias_transform_msg.transform.translation.x = 0.0;
alias_transform_msg.transform.translation.y = 0.0;
alias_transform_msg.transform.translation.z = 0.0;
alias_transform_msg.transform.rotation.x = 0.0;
alias_transform_msg.transform.rotation.y = 0.0;
alias_transform_msg.transform.rotation.z = 0.0;
alias_transform_msg.transform.rotation.w = 1.0;
alias_transform_msg.header.frame_id = child_frame_;
alias_transform_msg.child_frame_id = "base_link";
br.sendTransform(alias_transform_msg);
odom_publisher_ = pnh_.advertise<nav_msgs::Odometry>("/kiss/odometry", queue_size_);
traj_publisher_ = pnh_.advertise<nav_msgs::Path>("/kiss/trajectory", queue_size_);
if (publish_debug_clouds_) {
frame_publisher_ = pnh_.advertise<sensor_msgs::PointCloud2>("/kiss/frame", queue_size_);
kpoints_publisher_ =
pnh_.advertise<sensor_msgs::PointCloud2>("/kiss/keypoints", queue_size_);
map_publisher_ = pnh_.advertise<sensor_msgs::PointCloud2>("/kiss/local_map", queue_size_);
}
// Initialize the transform buffer
tf2_buffer_.setUsingDedicatedThread(true);
path_msg_.header.frame_id = odom_frame_;

// publish odometry msg
ROS_INFO("KISS-ICP ROS 1 Odometry Node Initialized");
}

Sophus::SE3d OdometryServer::LookupTransform(const std::string &target_frame,
const std::string &source_frame) const {
std::string err_msg;
if (tf2_buffer_._frameExists(source_frame) && //
tf2_buffer_._frameExists(target_frame) && //
tf2_buffer_.canTransform(target_frame, source_frame, ros::Time(0), &err_msg)) {
try {
auto tf = tf2_buffer_.lookupTransform(target_frame, source_frame, ros::Time(0));
return tf2::transformToSophus(tf);
} catch (tf2::TransformException &ex) {
ROS_WARN("%s", ex.what());
}
}
ROS_WARN("Failed to find tf between %s and %s. Reason=%s", target_frame.c_str(),
source_frame.c_str(), err_msg.c_str());
return {};
}

void OdometryServer::RegisterFrame(const sensor_msgs::PointCloud2::ConstPtr &msg) {
const auto cloud_frame_id = msg->header.frame_id;
const auto points = PointCloud2ToEigen(msg);
const auto timestamps = [&]() -> std::vector<double> {
if (!config_.deskew) return {};
return GetTimestamps(msg);
}();
const auto egocentric_estimation = (base_frame_.empty() || base_frame_ == cloud_frame_id);

// Register frame, main entry point to KISS-ICP pipeline
const auto &[frame, keypoints] = odometry_.RegisterFrame(points, timestamps);

// PublishPose
const auto pose = odometry_.poses().back();
// Compute the pose using KISS, ego-centric to the LiDAR
const Sophus::SE3d kiss_pose = odometry_.poses().back();

// If necessary, transform the ego-centric pose to the specified base_link/base_footprint frame
const auto pose = [&]() -> Sophus::SE3d {
if (egocentric_estimation) return kiss_pose;
const Sophus::SE3d cloud2base = LookupTransform(base_frame_, cloud_frame_id);
return cloud2base * kiss_pose * cloud2base.inverse();
}();

// Spit the current estimated pose to ROS msgs
PublishOdometry(pose, msg->header.stamp, cloud_frame_id);

// Publishing this clouds is a bit costly, so do it only if we are debugging
if (publish_debug_clouds_) {
PublishClouds(frame, keypoints, msg->header.stamp, cloud_frame_id);
}
}

// Convert from Eigen to ROS types
const Eigen::Vector3d t_current = pose.translation();
const Eigen::Quaterniond q_current = pose.unit_quaternion();
void OdometryServer::PublishOdometry(const Sophus::SE3d &pose,
const ros::Time &stamp,
const std::string &cloud_frame_id) {
// Header for point clouds and stuff seen from desired odom_frame

// Broadcast the tf
if (publish_odom_tf_) {
geometry_msgs::TransformStamped transform_msg;
transform_msg.header.stamp = msg->header.stamp;
transform_msg.header.stamp = stamp;
transform_msg.header.frame_id = odom_frame_;
transform_msg.child_frame_id = child_frame_;
transform_msg.transform.rotation.x = q_current.x();
transform_msg.transform.rotation.y = q_current.y();
transform_msg.transform.rotation.z = q_current.z();
transform_msg.transform.rotation.w = q_current.w();
transform_msg.transform.translation.x = t_current.x();
transform_msg.transform.translation.y = t_current.y();
transform_msg.transform.translation.z = t_current.z();
transform_msg.child_frame_id = base_frame_.empty() ? cloud_frame_id : base_frame_;
transform_msg.transform = tf2::sophusToTransform(pose);
tf_broadcaster_.sendTransform(transform_msg);
}

// publish trajectory msg
geometry_msgs::PoseStamped pose_msg;
pose_msg.pose.orientation.x = q_current.x();
pose_msg.pose.orientation.y = q_current.y();
pose_msg.pose.orientation.z = q_current.z();
pose_msg.pose.orientation.w = q_current.w();
pose_msg.pose.position.x = t_current.x();
pose_msg.pose.position.y = t_current.y();
pose_msg.pose.position.z = t_current.z();
pose_msg.header.stamp = msg->header.stamp;
pose_msg.header.stamp = stamp;
pose_msg.header.frame_id = odom_frame_;
pose_msg.pose = tf2::sophusToPose(pose);
path_msg_.poses.push_back(pose_msg);
traj_publisher_.publish(path_msg_);

// publish odometry msg
auto odom_msg = std::make_unique<nav_msgs::Odometry>();
odom_msg->header = pose_msg.header;
odom_msg->child_frame_id = child_frame_;
odom_msg->pose.pose = pose_msg.pose;
odom_publisher_.publish(*std::move(odom_msg));

// Publish KISS-ICP internal data, just for debugging
auto frame_header = msg->header;
frame_header.frame_id = child_frame_;
frame_publisher_.publish(*std::move(EigenToPointCloud2(frame, frame_header)));
kpoints_publisher_.publish(*std::move(EigenToPointCloud2(keypoints, frame_header)));

// Map is referenced to the odometry_frame
auto local_map_header = msg->header;
local_map_header.frame_id = odom_frame_;
map_publisher_.publish(*std::move(EigenToPointCloud2(odometry_.LocalMap(), local_map_header)));
nav_msgs::Odometry odom_msg;
odom_msg.header.stamp = stamp;
odom_msg.header.frame_id = odom_frame_;
odom_msg.pose.pose = tf2::sophusToPose(pose);
odom_publisher_.publish(odom_msg);
}

void OdometryServer::PublishClouds(const std::vector<Eigen::Vector3d> frame,
const std::vector<Eigen::Vector3d> keypoints,
const ros::Time &stamp,
const std::string &cloud_frame_id) {
std_msgs::Header odom_header;
odom_header.stamp = stamp;
odom_header.frame_id = odom_frame_;

// Publish map
const auto kiss_map = odometry_.LocalMap();

if (!publish_odom_tf_) {
// debugging happens in an egocentric world
std_msgs::Header cloud_header;
cloud_header.stamp = stamp;
cloud_header.frame_id = cloud_frame_id;

frame_publisher_.publish(*EigenToPointCloud2(frame, cloud_header));
kpoints_publisher_.publish(*EigenToPointCloud2(keypoints, cloud_header));
map_publisher_.publish(*EigenToPointCloud2(kiss_map, odom_header));

return;
}

// If transmitting to tf tree we know where the clouds are exactly
const auto cloud2odom = LookupTransform(odom_frame_, cloud_frame_id);
frame_publisher_.publish(*EigenToPointCloud2(frame, cloud2odom, odom_header));
kpoints_publisher_.publish(*EigenToPointCloud2(keypoints, cloud2odom, odom_header));

if (!base_frame_.empty()) {
const Sophus::SE3d cloud2base = LookupTransform(base_frame_, cloud_frame_id);
map_publisher_.publish(*EigenToPointCloud2(kiss_map, cloud2base, odom_header));
} else {
map_publisher_.publish(*EigenToPointCloud2(kiss_map, odom_header));
}
}

} // namespace kiss_icp_ros

int main(int argc, char **argv) {
Expand Down
29 changes: 25 additions & 4 deletions ros/ros1/OdometryServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
#include <nav_msgs/Path.h>
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <tf2_ros/buffer.h>
#include <tf2_ros/transform_broadcaster.h>
#include <tf2_ros/transform_listener.h>

#include <string>

namespace kiss_icp_ros {

Expand All @@ -42,34 +46,51 @@ class OdometryServer {
/// Register new frame
void RegisterFrame(const sensor_msgs::PointCloud2::ConstPtr &msg);

/// Stream the estimated pose to ROS
void PublishOdometry(const Sophus::SE3d &pose,
const ros::Time &stamp,
const std::string &cloud_frame_id);

/// Stream the debugging point clouds for visualization (if required)
void PublishClouds(const std::vector<Eigen::Vector3d> frame,
const std::vector<Eigen::Vector3d> keypoints,
const ros::Time &stamp,
const std::string &cloud_frame_id);

/// Utility function to compute transformation using tf tree
Sophus::SE3d LookupTransform(const std::string &target_frame,
const std::string &source_frame) const;

/// Ros node stuff
ros::NodeHandle nh_;
ros::NodeHandle pnh_;
int queue_size_{1};

/// Tools for broadcasting TFs.
tf2_ros::TransformBroadcaster tf_broadcaster_;
tf2_ros::Buffer tf2_buffer_;
tf2_ros::TransformListener tf2_listener_;
bool publish_odom_tf_;
bool publish_alias_tf_;
bool publish_debug_clouds_;

/// Data subscribers.
ros::Subscriber pointcloud_sub_;

/// Data publishers.
ros::Publisher odom_publisher_;
ros::Publisher traj_publisher_;
nav_msgs::Path path_msg_;
ros::Publisher frame_publisher_;
ros::Publisher kpoints_publisher_;
ros::Publisher map_publisher_;
ros::Publisher traj_publisher_;
nav_msgs::Path path_msg_;

/// KISS-ICP
kiss_icp::pipeline::KissICP odometry_;
kiss_icp::pipeline::KISSConfig config_;

/// Global/map coordinate frame.
std::string odom_frame_{"odom"};
std::string child_frame_{"base_link"};
std::string base_frame_{};
};

} // namespace kiss_icp_ros
Loading
Loading