From 70f40c6ef2bec287f9a41fe889182bb4370367cf Mon Sep 17 00:00:00 2001 From: Damon Kohler Date: Fri, 9 Sep 2016 14:20:10 +0200 Subject: [PATCH] Adds publishing of the scan matched point could. (#53) --- .../configuration_files/demo_2d.rviz | 35 +- .../configuration_files/demo_3d.rviz | 30 ++ .../configuration_files/demo_turtlebot.rviz | 317 ++++++++++++++++++ cartographer_ros/launch/demo_turtlebot.launch | 29 ++ .../src/cartographer_node_main.cc | 42 ++- cartographer_ros/src/msg_conversion.cc | 70 ++-- cartographer_ros/src/msg_conversion.h | 7 +- 7 files changed, 491 insertions(+), 39 deletions(-) create mode 100644 cartographer_ros/configuration_files/demo_turtlebot.rviz create mode 100644 cartographer_ros/launch/demo_turtlebot.launch diff --git a/cartographer_ros/configuration_files/demo_2d.rviz b/cartographer_ros/configuration_files/demo_2d.rviz index 596031c..b4a213c 100644 --- a/cartographer_ros/configuration_files/demo_2d.rviz +++ b/cartographer_ros/configuration_files/demo_2d.rviz @@ -19,8 +19,9 @@ Panels: Property Tree Widget: Expanded: - /Submaps1 + - /PointCloud21 Splitter Ratio: 0.600671 - Tree Height: 817 + Tree Height: 821 - Class: rviz/Selection Name: Selection - Class: rviz/Tool Properties @@ -39,7 +40,7 @@ Panels: Experimental: false Name: Time SyncMode: 0 - SyncSource: "" + SyncSource: PointCloud2 Visualization Manager: Class: "" Displays: @@ -139,6 +140,36 @@ Visualization Manager: Tracking frame: base_link Unreliable: false Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/PointCloud2 + Color: 0; 255; 0 + Color Transformer: FlatColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.05 + Style: Flat Squares + Topic: /scan_matched_points2 + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true Enabled: true Global Options: Background Color: 100; 100; 100 diff --git a/cartographer_ros/configuration_files/demo_3d.rviz b/cartographer_ros/configuration_files/demo_3d.rviz index 0f84c32..174daf7 100644 --- a/cartographer_ros/configuration_files/demo_3d.rviz +++ b/cartographer_ros/configuration_files/demo_3d.rviz @@ -202,6 +202,36 @@ Visualization Manager: Tracking frame: base_link Unreliable: false Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 8.77916 + Min Value: 4.51287 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/PointCloud2 + Color: 0; 255; 0 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 20 + Selectable: true + Size (Pixels): 3 + Size (m): 0.05 + Style: Flat Squares + Topic: /scan_matched_points2 + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: false Enabled: true Global Options: Background Color: 100; 100; 100 diff --git a/cartographer_ros/configuration_files/demo_turtlebot.rviz b/cartographer_ros/configuration_files/demo_turtlebot.rviz new file mode 100644 index 0000000..93cf3d4 --- /dev/null +++ b/cartographer_ros/configuration_files/demo_turtlebot.rviz @@ -0,0 +1,317 @@ +# Copyright 2016 The Cartographer Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Panels: + - Class: rviz/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Submaps1 + - /PointCloud21 + Splitter Ratio: 0.600671 + Tree Height: 821 + - Class: rviz/Selection + Name: Selection + - Class: rviz/Tool Properties + Expanded: + - /2D Pose Estimate1 + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.588679 + - Class: rviz/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: PointCloud2 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.03 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 100 + Reference Frame: + Value: true + - Class: rviz/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + base_footprint: + Value: true + base_link: + Value: true + camera_depth_frame: + Value: true + camera_depth_optical_frame: + Value: true + camera_link: + Value: true + camera_rgb_frame: + Value: true + camera_rgb_optical_frame: + Value: true + caster_back_link: + Value: true + caster_front_link: + Value: true + cliff_sensor_front_link: + Value: true + cliff_sensor_left_link: + Value: true + cliff_sensor_right_link: + Value: true + gyro_link: + Value: true + laser: + Value: true + laser_mount_link: + Value: true + map: + Value: true + odom: + Value: true + plate_bottom_link: + Value: true + plate_middle_link: + Value: true + plate_top_link: + Value: true + pole_bottom_0_link: + Value: true + pole_bottom_1_link: + Value: true + pole_bottom_2_link: + Value: true + pole_bottom_3_link: + Value: true + pole_bottom_4_link: + Value: true + pole_bottom_5_link: + Value: true + pole_kinect_0_link: + Value: true + pole_kinect_1_link: + Value: true + pole_middle_0_link: + Value: true + pole_middle_1_link: + Value: true + pole_middle_2_link: + Value: true + pole_middle_3_link: + Value: true + pole_top_0_link: + Value: true + pole_top_1_link: + Value: true + pole_top_2_link: + Value: true + pole_top_3_link: + Value: true + wheel_left_link: + Value: true + wheel_right_link: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: true + Tree: + map: + odom: + base_footprint: + base_link: + camera_rgb_frame: + camera_depth_frame: + camera_depth_optical_frame: + {} + camera_link: + {} + camera_rgb_optical_frame: + {} + caster_back_link: + {} + caster_front_link: + {} + cliff_sensor_front_link: + {} + cliff_sensor_left_link: + {} + cliff_sensor_right_link: + {} + gyro_link: + {} + laser_mount_link: + laser: + {} + plate_bottom_link: + {} + plate_middle_link: + {} + plate_top_link: + {} + pole_bottom_0_link: + {} + pole_bottom_1_link: + {} + pole_bottom_2_link: + {} + pole_bottom_3_link: + {} + pole_bottom_4_link: + {} + pole_bottom_5_link: + {} + pole_kinect_0_link: + {} + pole_kinect_1_link: + {} + pole_middle_0_link: + {} + pole_middle_1_link: + {} + pole_middle_2_link: + {} + pole_middle_3_link: + {} + pole_top_0_link: + {} + pole_top_1_link: + {} + pole_top_2_link: + {} + pole_top_3_link: + {} + wheel_left_link: + {} + wheel_right_link: + {} + Update Interval: 0 + Value: true + - Class: Submaps + Enabled: true + Map frame: map + Name: Submaps + Submap query service: /cartographer/submap_query + Topic: /cartographer/submap_list + Tracking frame: base_link + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz/PointCloud2 + Color: 0; 255; 0 + Color Transformer: FlatColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.05 + Style: Flat Squares + Topic: /scan_matched_points2 + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 100; 100; 100 + Fixed Frame: map + Frame Rate: 30 + Name: root + Tools: + - Class: rviz/Interact + Hide Inactive Objects: true + - Class: rviz/MoveCamera + - Class: rviz/Select + - Class: rviz/FocusCamera + - Class: rviz/Measure + - Class: rviz/SetInitialPose + Topic: /initialpose + - Class: rviz/SetGoal + Topic: /move_base_simple/goal + - Class: rviz/PublishPoint + Single click: true + Topic: /clicked_point + Value: true + Views: + Current: + Angle: 0 + Class: rviz/TopDownOrtho + Enable Stereo Rendering: + Stereo Eye Separation: 0.06 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Name: Current View + Near Clip Distance: 0.01 + Scale: 10 + Target Frame: + Value: TopDownOrtho (rviz) + X: 0 + Y: 0 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1123 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000001c5000003c0fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000041000003c0000000dd00fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000003c0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a005600690065007700730100000041000003c0000000b000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000077e0000003efc0100000002fb0000000800540069006d006501000000000000077e000002f600fffffffb0000000800540069006d006501000000000000045000000000000000000000049e000003c000000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1918 + X: 0 + Y: 24 diff --git a/cartographer_ros/launch/demo_turtlebot.launch b/cartographer_ros/launch/demo_turtlebot.launch new file mode 100644 index 0000000..6eff567 --- /dev/null +++ b/cartographer_ros/launch/demo_turtlebot.launch @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/cartographer_ros/src/cartographer_node_main.cc b/cartographer_ros/src/cartographer_node_main.cc index b8d2f75..e3f0750 100644 --- a/cartographer_ros/src/cartographer_node_main.cc +++ b/cartographer_ros/src/cartographer_node_main.cc @@ -43,6 +43,7 @@ #include "cartographer/mapping_3d/local_trajectory_builder_options.h" #include "cartographer/mapping_3d/sparse_pose_graph.h" #include "cartographer/sensor/laser.h" +#include "cartographer/sensor/point_cloud.h" #include "cartographer/sensor/proto/sensor.pb.h" #include "cartographer/transform/rigid_transform.h" #include "cartographer/transform/transform.h" @@ -97,6 +98,7 @@ constexpr char kPointCloud2Topic[] = "/points2"; constexpr char kImuTopic[] = "/imu"; constexpr char kOdometryTopic[] = "/odom"; constexpr char kOccupancyGridTopic[] = "/map"; +constexpr char kScanMatchedPointCloudTopic[] = "/scan_matched_points2"; struct NodeOptions { carto::mapping::proto::MapBuilderOptions map_builder_options; @@ -206,7 +208,8 @@ class Node { ::cartographer_ros_msgs::SubmapQuery::Response& response); void PublishSubmapList(const ::ros::WallTimerEvent& timer_event); - void PublishPose(const ::ros::WallTimerEvent& timer_event); + void PublishPoseAndScanMatchedPointCloud( + const ::ros::WallTimerEvent& timer_event); void SpinOccupancyGridThreadForever(); const NodeOptions options_; @@ -226,6 +229,7 @@ class Node { ::ros::Subscriber odometry_subscriber_; ::ros::Publisher submap_list_publisher_; ::ros::ServiceServer submap_query_server_; + ::ros::Publisher scan_matched_point_cloud_publisher_; tf2_ros::Buffer tf_buffer_; tf2_ros::TransformListener tf_; @@ -426,12 +430,16 @@ void Node::Initialize() { std::thread(&Node::SpinOccupancyGridThreadForever, this); } + scan_matched_point_cloud_publisher_ = + node_handle_.advertise( + kScanMatchedPointCloudTopic, 10); + wall_timers_.push_back(node_handle_.createWallTimer( ::ros::WallDuration(options_.submap_publish_period_sec), &Node::PublishSubmapList, this)); wall_timers_.push_back(node_handle_.createWallTimer( - ::ros::WallDuration(options_.pose_publish_period_sec), &Node::PublishPose, - this)); + ::ros::WallDuration(options_.pose_publish_period_sec), + &Node::PublishPoseAndScanMatchedPointCloud, this)); } bool Node::HandleSubmapQuery( @@ -508,21 +516,26 @@ void Node::PublishSubmapList(const ::ros::WallTimerEvent& timer_event) { submap_list_publisher_.publish(ros_submap_list); } -void Node::PublishPose(const ::ros::WallTimerEvent& timer_event) { +void Node::PublishPoseAndScanMatchedPointCloud( + const ::ros::WallTimerEvent& timer_event) { carto::common::MutexLocker lock(&mutex_); - const Rigid3d tracking_to_local = - map_builder_.GetTrajectoryBuilder(kTrajectoryBuilderId) - ->pose_estimate() - .pose; + const carto::mapping::GlobalTrajectoryBuilderInterface::PoseEstimate + last_pose_estimate = + map_builder_.GetTrajectoryBuilder(kTrajectoryBuilderId) + ->pose_estimate(); + if (carto::common::ToUniversal(last_pose_estimate.time) < 0) { + return; + } + + const Rigid3d tracking_to_local = last_pose_estimate.pose; const carto::mapping::Submaps* submaps = map_builder_.GetTrajectoryBuilder(kTrajectoryBuilderId)->submaps(); const Rigid3d local_to_map = map_builder_.sparse_pose_graph()->GetLocalToGlobalTransform(*submaps); const Rigid3d tracking_to_map = local_to_map * tracking_to_local; - const ::ros::Time now = ::ros::Time::now(); geometry_msgs::TransformStamped stamped_transform; - stamped_transform.header.stamp = now; + stamped_transform.header.stamp = ToRos(last_pose_estimate.time); stamped_transform.header.frame_id = options_.map_frame; stamped_transform.child_frame_id = options_.odom_frame; @@ -537,7 +550,8 @@ void Node::PublishPose(const ::ros::WallTimerEvent& timer_event) { } else { try { const Rigid3d tracking_to_odom = - LookupToTrackingTransformOrThrow(FromRos(now), options_.odom_frame) + LookupToTrackingTransformOrThrow(last_pose_estimate.time, + options_.odom_frame) .inverse(); const Rigid3d odom_to_map = tracking_to_map * tracking_to_odom.inverse(); stamped_transform.transform = ToGeometryMsgTransform(odom_to_map); @@ -547,6 +561,12 @@ void Node::PublishPose(const ::ros::WallTimerEvent& timer_event) { << options_.odom_frame << ": " << ex.what(); } } + + scan_matched_point_cloud_publisher_.publish(ToPointCloud2Message( + carto::common::ToUniversal(last_pose_estimate.time), options_.tracking_frame, + carto::sensor::TransformPointCloud( + last_pose_estimate.point_cloud, + tracking_to_local.inverse().cast()))); } void Node::SpinOccupancyGridThreadForever() { diff --git a/cartographer_ros/src/msg_conversion.cc b/cartographer_ros/src/msg_conversion.cc index 2f1126c..dbf2991 100644 --- a/cartographer_ros/src/msg_conversion.cc +++ b/cartographer_ros/src/msg_conversion.cc @@ -37,6 +37,7 @@ #include "time_conversion.h" namespace cartographer_ros { + namespace { // The ros::sensor_msgs::PointCloud2 binary data contains 4 floats for each @@ -62,6 +63,35 @@ void ToMessage(const ::cartographer::transform::proto::Quaterniond& proto, quaternion->z = proto.z(); } +sensor_msgs::PointCloud2 PreparePointCloud2Message(const int64 timestamp, + const string& frame_id, + const int num_points) { + sensor_msgs::PointCloud2 msg; + msg.header.stamp = ToRos(::cartographer::common::FromUniversal(timestamp)); + msg.header.frame_id = frame_id; + msg.height = 1; + msg.width = num_points; + msg.fields.resize(3); + msg.fields[0].name = "x"; + msg.fields[0].offset = 0; + msg.fields[0].datatype = 7; + msg.fields[0].count = 1; + msg.fields[1].name = "y"; + msg.fields[1].offset = 4; + msg.fields[1].datatype = 7; + msg.fields[1].count = 1; + msg.fields[2].name = "z"; + msg.fields[2].offset = 8; + msg.fields[2].datatype = 7; + msg.fields[2].count = 1; + msg.is_bigendian = false; + msg.point_step = 16; + msg.row_step = 16 * msg.width; + msg.is_dense = true; + msg.data.resize(16 * num_points); + return msg; +} + } // namespace sensor_msgs::MultiEchoLaserScan ToMultiEchoLaserScanMessage( @@ -149,42 +179,32 @@ sensor_msgs::LaserScan ToLaserScan( return msg; } +sensor_msgs::PointCloud2 ToPointCloud2Message( + const int64 timestamp, const string& frame_id, + const ::cartographer::sensor::PointCloud& point_cloud) { + auto msg = PreparePointCloud2Message(timestamp, frame_id, point_cloud.size()); + ::ros::serialization::OStream stream(msg.data.data(), msg.data.size()); + for (const auto& point : point_cloud) { + stream.next(point.x()); + stream.next(point.y()); + stream.next(point.z()); + stream.next(kPointCloudComponentFourMagic); + } + return msg; +} + sensor_msgs::PointCloud2 ToPointCloud2Message( const int64 timestamp, const string& frame_id, const ::cartographer::sensor::proto::LaserFan3D& laser_scan_3d) { CHECK(::cartographer::transform::ToEigen(laser_scan_3d.origin()).norm() == 0) << "Trying to convert a laser_scan_3d that is not at the origin."; - sensor_msgs::PointCloud2 msg; - msg.header.stamp = ToRos(::cartographer::common::FromUniversal(timestamp)); - msg.header.frame_id = frame_id; - const auto& point_cloud = laser_scan_3d.point_cloud(); CHECK_EQ(point_cloud.x_size(), point_cloud.y_size()); CHECK_EQ(point_cloud.x_size(), point_cloud.z_size()); const auto num_points = point_cloud.x_size(); - msg.height = 1; - msg.width = num_points; - msg.fields.resize(3); - msg.fields[0].name = "x"; - msg.fields[0].offset = 0; - msg.fields[0].datatype = 7; - msg.fields[0].count = 1; - msg.fields[1].name = "y"; - msg.fields[1].offset = 4; - msg.fields[1].datatype = 7; - msg.fields[1].count = 1; - msg.fields[2].name = "z"; - msg.fields[2].offset = 8; - msg.fields[2].datatype = 7; - msg.fields[2].count = 1; - msg.is_bigendian = false; - msg.point_step = 16; - msg.row_step = 16 * msg.width; - msg.is_dense = true; - msg.data.resize(16 * num_points); - + auto msg = PreparePointCloud2Message(timestamp, frame_id, num_points); ::ros::serialization::OStream stream(msg.data.data(), msg.data.size()); for (int i = 0; i < num_points; ++i) { stream.next(point_cloud.x(i)); diff --git a/cartographer_ros/src/msg_conversion.h b/cartographer_ros/src/msg_conversion.h index 12f691f..1e5f976 100644 --- a/cartographer_ros/src/msg_conversion.h +++ b/cartographer_ros/src/msg_conversion.h @@ -19,6 +19,7 @@ #include "cartographer/common/port.h" #include "cartographer/kalman_filter/pose_tracker.h" +#include "cartographer/sensor/point_cloud.h" #include "cartographer/sensor/proto/sensor.pb.h" #include "cartographer/transform/rigid_transform.h" #include "geometry_msgs/Pose.h" @@ -34,7 +35,7 @@ namespace cartographer_ros { -// Only uses first echo of each beam. +// Returns a laser scan consisting of the first echo of each beam. sensor_msgs::LaserScan ToLaserScan( int64 timestamp, const string& frame_id, const ::cartographer::sensor::proto::LaserScan& laser_scan); @@ -46,6 +47,10 @@ sensor_msgs::MultiEchoLaserScan ToMultiEchoLaserScanMessage( sensor_msgs::Imu ToImuMessage(int64 timestamp, const string& frame_id, const ::cartographer::sensor::proto::Imu& imu); +sensor_msgs::PointCloud2 ToPointCloud2Message( + int64 timestamp, const string& frame_id, + const ::cartographer::sensor::PointCloud& point_cloud); + sensor_msgs::PointCloud2 ToPointCloud2Message( int64 timestamp, const string& frame_id, const ::cartographer::sensor::proto::LaserFan3D& laser_scan_3d);