diff --git a/CMakeLists.txt b/CMakeLists.txt index 3046d1e..d7feef5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,6 @@ install(TARGETS depth_local_costmap_noetic_test_node RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} ) -install(DIRECTORY config launch +install(DIRECTORY config launch rviz DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} ) diff --git a/README.md b/README.md index c9edd32..395afc5 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ source devel/setup.bash roslaunch depth_local_costmap_noetic_test depth_local_costmap_test.launch ``` +Launch mặc định mở RViz với config sẵn. Nếu chỉ muốn chạy node không mở GUI: + +```bash +roslaunch depth_local_costmap_noetic_test depth_local_costmap_test.launch rviz:=false +``` + Nếu robot dùng `base_footprint`: ```bash @@ -55,6 +61,8 @@ Không bật `publish_odom_tf` nếu đã có node khác publish TF này. rostopic echo -n1 /camera/depth/points_proc/header rostopic echo -n1 /camera/depth/image_raw/header rostopic echo -n1 /camera/depth/camera_info/header +rostopic echo -n1 /local_costmap/costmap/info +rostopic echo -n1 /local_costmap/costmap/footprint/header rosrun tf tf_echo odom base_link rostopic list | grep local_costmap ``` @@ -65,6 +73,12 @@ Log runtime cần thấy: - `Fed depth cloud to robot_costmap_2d` - `Fed DepthCameraData to robot_costmap_2d` +- `Published local costmap for RViz` + +Các topic RViz giống hướng `amr_control::AmrPublisher`: + +- `/local_costmap/costmap` kiểu `nav_msgs/OccupancyGrid` +- `/local_costmap/costmap/footprint` kiểu `geometry_msgs/PolygonStamped` Config local cho `robot_costmap_2d` nằm trong thư mục `config/config/` của package này khi export: diff --git a/launch/depth_local_costmap_test.launch b/launch/depth_local_costmap_test.launch index c978f06..5f56132 100644 --- a/launch/depth_local_costmap_test.launch +++ b/launch/depth_local_costmap_test.launch @@ -8,6 +8,8 @@ + + + @@ -56,4 +59,10 @@ + + diff --git a/package.xml b/package.xml index d21be72..034c0f7 100644 --- a/package.xml +++ b/package.xml @@ -21,4 +21,5 @@ roscpp sensor_msgs tf2_ros + rviz diff --git a/rviz/depth_local_costmap.rviz b/rviz/depth_local_costmap.rviz new file mode 100644 index 0000000..0f8b066 --- /dev/null +++ b/rviz/depth_local_costmap.rviz @@ -0,0 +1,107 @@ +Panels: + - Class: rviz/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Local Costmap1 + - /Local Footprint1 + Splitter Ratio: 0.5 + Tree Height: 560 + - Class: rviz/Views + Expanded: + - /Current View1 + Name: Views +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: 20 + Reference Frame: + Value: true + - Class: rviz/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 0.4 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: true + Tree: + {} + Update Interval: 0 + Value: true + - Alpha: 0.75 + Class: rviz/Map + Color Scheme: costmap + Draw Behind: false + Enabled: true + Name: Local Costmap + Topic: /local_costmap/costmap + Unreliable: false + Use Timestamp: false + Value: true + - Alpha: 1 + Class: rviz/Polygon + Color: 25; 255; 0 + Enabled: true + Name: Local Footprint + Topic: /local_costmap/costmap/footprint + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: odom + 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 + Value: true + Views: + Current: + Class: rviz/TopDownOrtho + Name: Current View + Scale: 12 + Target Frame: + X: 0 + Y: 0 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 900 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000100000000000001f40000032efc0200000001fb000000100044006900730070006c006100790073010000003d0000032e000000c900ffffff000003840000032e00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Views: + collapsed: false + Width: 1200 + X: 80 + Y: 60 diff --git a/src/depth_local_costmap_noetic_test_node.cpp b/src/depth_local_costmap_noetic_test_node.cpp index 5ec4175..feb12bf 100644 --- a/src/depth_local_costmap_noetic_test_node.cpp +++ b/src/depth_local_costmap_noetic_test_node.cpp @@ -1,9 +1,13 @@ #include #include +#include #include +#include +#include #include #include +#include #include #include #include @@ -23,6 +27,7 @@ #include #include +#include #include #include #include @@ -31,8 +36,14 @@ #include #include +#include + namespace { +constexpr double kOccupancyScale = + 99.0 / static_cast(robot_costmap_2d::INSCRIBED_INFLATED_OBSTACLE - + robot_costmap_2d::FREE_SPACE); + robot_sensor_msgs::PointCloud2 toRobotPointCloud2(const sensor_msgs::PointCloud2& msg) { robot_sensor_msgs::PointCloud2 robot_pc; @@ -63,10 +74,31 @@ robot_sensor_msgs::PointCloud2 toRobotPointCloud2(const sensor_msgs::PointCloud2 return robot_pc; } +int8_t toOccupancyValue(const unsigned char cost) +{ + if (cost == robot_costmap_2d::NO_INFORMATION) + return -1; + if (cost >= robot_costmap_2d::INSCRIBED_INFLATED_OBSTACLE) + return 100; + if (cost <= robot_costmap_2d::FREE_SPACE) + return 0; + + const double scaled = static_cast(cost - robot_costmap_2d::FREE_SPACE) * kOccupancyScale; + return static_cast(std::max(0.0, std::min(99.0, std::round(scaled)))); +} + +double yawFromQuaternion(const robot_geometry_msgs::Quaternion& q) +{ + const double siny_cosp = 2.0 * (q.w * q.z + q.x * q.y); + const double cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z); + return std::atan2(siny_cosp, cosy_cosp); +} + bool isVoxelLayer(const robot_costmap_2d::Layer& layer) { return layer.getType() == robot_costmap_2d::LayerType::VOXEL_LAYER || - layer.getName() == "local_costmap/voxel_layer"; + layer.getName() == "local_costmap/voxel_layer" || + layer.getName() == "local_costmap/obstacles"; } template @@ -379,6 +411,105 @@ private: ros::Subscriber camera_right_info_sub_; }; + +class LocalCostmapRvizPublisher +{ +public: + explicit LocalCostmapRvizPublisher(ros::NodeHandle& private_nh) + { + private_nh.param("local_costmap_topic", local_costmap_topic_, std::string("/local_costmap/costmap")); + private_nh.param("local_footprint_topic", + local_footprint_topic_, + local_costmap_topic_ + "/footprint"); + + costmap_pub_ = private_nh.advertise(local_costmap_topic_, 1); + footprint_pub_ = private_nh.advertise(local_footprint_topic_, 1); + + ROS_INFO("Publishing local costmap for RViz: %s", local_costmap_topic_.c_str()); + ROS_INFO("Publishing local footprint for RViz: %s", local_footprint_topic_.c_str()); + } + + void publish(robot_costmap_2d::Costmap2DROBOT& local_costmap, + const bool pose_ok, + const robot_geometry_msgs::PoseStamped& robot_pose) + { + publishCostmap(local_costmap); + if (pose_ok) + publishFootprint(local_costmap, robot_pose); + } + +private: + void publishCostmap(robot_costmap_2d::Costmap2DROBOT& local_costmap) + { + robot_costmap_2d::Costmap2D* costmap = local_costmap.getCostmap(); + if (costmap == nullptr) + return; + + nav_msgs::OccupancyGrid grid; + grid.header.stamp = ros::Time::now(); + grid.header.frame_id = local_costmap.getGlobalFrameID(); + grid.info.map_load_time = grid.header.stamp; + + { + boost::unique_lock lock(*costmap->getMutex()); + + const unsigned int size_x = costmap->getSizeInCellsX(); + const unsigned int size_y = costmap->getSizeInCellsY(); + grid.info.resolution = costmap->getResolution(); + grid.info.width = size_x; + grid.info.height = size_y; + grid.info.origin.position.x = costmap->getOriginX(); + grid.info.origin.position.y = costmap->getOriginY(); + grid.info.origin.position.z = 0.0; + grid.info.origin.orientation.w = 1.0; + + const unsigned char* char_map = costmap->getCharMap(); + grid.data.resize(static_cast(size_x) * size_y); + for (std::size_t index = 0; index < grid.data.size(); ++index) + grid.data[index] = toOccupancyValue(char_map[index]); + } + + costmap_pub_.publish(grid); + ROS_INFO_THROTTLE(5.0, "Published local costmap for RViz: %ux%u frame=%s topic=%s", + grid.info.width, + grid.info.height, + grid.header.frame_id.c_str(), + local_costmap_topic_.c_str()); + } + + void publishFootprint(robot_costmap_2d::Costmap2DROBOT& local_costmap, + const robot_geometry_msgs::PoseStamped& robot_pose) + { + const robot_geometry_msgs::Polygon footprint = local_costmap.getRobotFootprintPolygon(); + if (footprint.points.empty()) + return; + + const double yaw = yawFromQuaternion(robot_pose.pose.orientation); + const double cos_yaw = std::cos(yaw); + const double sin_yaw = std::sin(yaw); + + geometry_msgs::PolygonStamped footprint_msg; + footprint_msg.header.stamp = ros::Time::now(); + footprint_msg.header.frame_id = local_costmap.getGlobalFrameID(); + footprint_msg.polygon.points.reserve(footprint.points.size()); + + for (const auto& point : footprint.points) + { + geometry_msgs::Point32 transformed; + transformed.x = static_cast(robot_pose.pose.position.x + point.x * cos_yaw - point.y * sin_yaw); + transformed.y = static_cast(robot_pose.pose.position.y + point.x * sin_yaw + point.y * cos_yaw); + transformed.z = static_cast(robot_pose.pose.position.z + point.z); + footprint_msg.polygon.points.push_back(transformed); + } + + footprint_pub_.publish(footprint_msg); + } + + std::string local_costmap_topic_; + std::string local_footprint_topic_; + ros::Publisher costmap_pub_; + ros::Publisher footprint_pub_; +}; } // namespace int main(int argc, char** argv) @@ -391,8 +522,11 @@ int main(int argc, char** argv) std::string base_frame; bool publish_odom_tf = false; + double publish_rate = 5.0; private_nh.param("base_frame", base_frame, std::string("base_link")); private_nh.param("publish_odom_tf", publish_odom_tf, false); + private_nh.param("publish_rate", publish_rate, publish_rate); + publish_rate = std::max(0.5, publish_rate); auto tf2_buffer = std::make_shared(ros::Duration(10.0)); tf2_ros::TransformListener tf2_listener(*tf2_buffer); @@ -410,11 +544,12 @@ int main(int argc, char** argv) robot_costmap_2d::Costmap2DROBOT local_costmap("local_costmap", tf3_buffer); DepthCloudFeeder depth_cloud_feeder(private_nh, local_costmap); DepthCameraDataFeeder depth_camera_data_feeder(private_nh, local_costmap); + LocalCostmapRvizPublisher rviz_publisher(private_nh); local_costmap.start(); ROS_INFO("T800 robot_costmap_2d local costmap test started."); - ros::Rate rate(2.0); + ros::Rate rate(publish_rate); while (ros::ok()) { robot_geometry_msgs::PoseStamped robot_pose; @@ -446,6 +581,7 @@ int main(int argc, char** argv) robot_pose.pose.position.y); } + rviz_publisher.publish(local_costmap, pose_ok, robot_pose); rate.sleep(); }