test costmap + cam depth
This commit is contained in:
59
CMakeLists.txt
Normal file
59
CMakeLists.txt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.0.2)
|
||||||
|
project(depth_local_costmap_noetic_test LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
find_package(catkin REQUIRED COMPONENTS
|
||||||
|
data_convert
|
||||||
|
geometry_msgs
|
||||||
|
nav_msgs
|
||||||
|
robot_depth_image_proc
|
||||||
|
robot_costmap_2d
|
||||||
|
robot_cpp
|
||||||
|
robot_geometry_msgs
|
||||||
|
robot_sensor_msgs
|
||||||
|
robot_time
|
||||||
|
roscpp
|
||||||
|
sensor_msgs
|
||||||
|
tf2_ros
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(TF3_LIBRARY
|
||||||
|
NAMES tf3
|
||||||
|
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT TF3_LIBRARY)
|
||||||
|
message(FATAL_ERROR "tf3 library not found")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
catkin_package()
|
||||||
|
|
||||||
|
add_executable(depth_local_costmap_noetic_test_node
|
||||||
|
src/depth_local_costmap_noetic_test_node.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(depth_local_costmap_noetic_test_node
|
||||||
|
PRIVATE
|
||||||
|
${catkin_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(depth_local_costmap_noetic_test_node
|
||||||
|
PRIVATE
|
||||||
|
${catkin_LIBRARIES}
|
||||||
|
${TF3_LIBRARY}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(depth_local_costmap_noetic_test_node
|
||||||
|
${catkin_EXPORTED_TARGETS}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS depth_local_costmap_noetic_test_node
|
||||||
|
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(DIRECTORY config launch
|
||||||
|
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
|
||||||
|
)
|
||||||
73
README.md
Normal file
73
README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# depth_local_costmap_noetic_test
|
||||||
|
|
||||||
|
Package ROS Noetic C++ tối thiểu để chạy **local costmap only** bằng stack T800:
|
||||||
|
|
||||||
|
- `/home/duongtd/T800_ws/src/AMR_T800/pnkx_nav_core/src/Libraries/costmap_2d`
|
||||||
|
- package catkin: `robot_costmap_2d`
|
||||||
|
|
||||||
|
Node test nhận dữ liệu ROS Noetic và convert sang message `robot_*` trước khi feed trực tiếp vào `VoxelLayer` bằng `layer->dataCallBack(...)`.
|
||||||
|
|
||||||
|
Luồng dữ liệu chính:
|
||||||
|
|
||||||
|
- `/camera/depth/points_proc` -> `robot_sensor_msgs::PointCloud2` -> source `pc_marking`
|
||||||
|
- `/camera/depth/image_raw` + `/camera/depth/camera_info` -> `robot_sensor_msgs::DepthCameraData::ConstPtr` -> source `pc_clearing`
|
||||||
|
|
||||||
|
`/camera/depth/data` không cần là ROS topic thật trong gói test này; nó là tên source logic để match với config `robot_costmap_2d`, giống contract trong `sensor_converter.cpp`.
|
||||||
|
|
||||||
|
Topic phụ được node subscribe để log kiểm tra dữ liệu:
|
||||||
|
|
||||||
|
- `/camera_right/depth/camera_info`
|
||||||
|
- `/odom`
|
||||||
|
- `/tf`
|
||||||
|
- `/tf_static`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/duongtd/T800_ws
|
||||||
|
catkin_make --pkg depth_local_costmap_noetic_test
|
||||||
|
source devel/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chạy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
roslaunch depth_local_costmap_noetic_test depth_local_costmap_test.launch
|
||||||
|
```
|
||||||
|
|
||||||
|
Nếu robot dùng `base_footprint`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
roslaunch depth_local_costmap_noetic_test depth_local_costmap_test.launch base_frame:=base_footprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Nếu hệ thống chỉ có `/odom` nhưng chưa publish TF `odom -> base_link`, bật bridge tạm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
roslaunch depth_local_costmap_noetic_test depth_local_costmap_test.launch publish_odom_tf:=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Không bật `publish_odom_tf` nếu đã có node khác publish TF này.
|
||||||
|
|
||||||
|
## Kiểm tra runtime
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rostopic echo -n1 /camera/depth/points_proc/header
|
||||||
|
rostopic echo -n1 /camera/depth/image_raw/header
|
||||||
|
rostopic echo -n1 /camera/depth/camera_info/header
|
||||||
|
rosrun tf tf_echo odom base_link
|
||||||
|
rostopic list | grep local_costmap
|
||||||
|
```
|
||||||
|
|
||||||
|
Frame của `/camera/depth/points_proc` và `/camera/depth/image_raw` phải transform được về `odom`; nếu không, obstacle/voxel layer sẽ drop dữ liệu.
|
||||||
|
|
||||||
|
Log runtime cần thấy:
|
||||||
|
|
||||||
|
- `Fed depth cloud to robot_costmap_2d`
|
||||||
|
- `Fed DepthCameraData to robot_costmap_2d`
|
||||||
|
|
||||||
|
Config local cho `robot_costmap_2d` nằm trong thư mục `config/` của package này. Launch file set:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<env name="PNKX_NAV_CORE_CONFIG_DIR" value="$(find depth_local_costmap_noetic_test)"/>
|
||||||
|
```
|
||||||
31
config/costmap_params.yaml
Normal file
31
config/costmap_params.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
robot_costmap_2d:
|
||||||
|
global_frame: odom
|
||||||
|
robot_base_frame: base_link
|
||||||
|
rolling_window: true
|
||||||
|
track_unknown_space: false
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- name: voxel_layer
|
||||||
|
type: VoxelLayer
|
||||||
|
- name: inflation_layer
|
||||||
|
type: InflationLayer
|
||||||
|
|
||||||
|
library_path: /home/duongtd/T800_ws/devel/lib/libplugins.so
|
||||||
|
|
||||||
|
footprint:
|
||||||
|
- [0.30, 0.30]
|
||||||
|
- [0.30, -0.30]
|
||||||
|
- [-0.30, -0.30]
|
||||||
|
- [-0.30, 0.30]
|
||||||
|
|
||||||
|
transform_tolerance: 0.5
|
||||||
|
performance_metrics_enabled: false
|
||||||
|
performance_metrics_period: 5.0
|
||||||
|
update_frequency: 5.0
|
||||||
|
width: 6.0
|
||||||
|
height: 6.0
|
||||||
|
resolution: 0.05
|
||||||
|
origin_x: 0.0
|
||||||
|
origin_y: 0.0
|
||||||
|
footprint_padding: 0.0
|
||||||
|
robot_radius: 0.0
|
||||||
5
config/inflation_layer_params.yaml
Normal file
5
config/inflation_layer_params.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
inflation_layer:
|
||||||
|
enabled: true
|
||||||
|
inflate_unknown: false
|
||||||
|
cost_scaling_factor: 3.0
|
||||||
|
inflation_radius: 0.45
|
||||||
39
config/obstacle_layer_params.yaml
Normal file
39
config/obstacle_layer_params.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
obstacle_layer:
|
||||||
|
track_unknown_space: false
|
||||||
|
transform_tolerance: 0.5
|
||||||
|
footprint_clearing_enabled: true
|
||||||
|
max_obstacle_height: 1.5
|
||||||
|
combination_method: 1
|
||||||
|
|
||||||
|
observation_sources: pc_marking pc_clearing
|
||||||
|
|
||||||
|
pc_marking:
|
||||||
|
topic: /camera/depth/points_proc
|
||||||
|
data_type: PointCloud2
|
||||||
|
clearing: false
|
||||||
|
marking: true
|
||||||
|
inf_is_valid: false
|
||||||
|
observation_persistence: 0.0
|
||||||
|
expected_update_rate: 0.5
|
||||||
|
obstacle_range: 2.5
|
||||||
|
raytrace_range: 3.0
|
||||||
|
min_obstacle_height: 0.10
|
||||||
|
max_obstacle_height: 1.0
|
||||||
|
frustum_clearing_enabled: false
|
||||||
|
|
||||||
|
pc_clearing:
|
||||||
|
topic: /camera/depth/data
|
||||||
|
data_type: DepthCameraData
|
||||||
|
clearing: false
|
||||||
|
marking: true
|
||||||
|
inf_is_valid: false
|
||||||
|
observation_persistence: 0.0
|
||||||
|
expected_update_rate: 0.5
|
||||||
|
obstacle_range: 2.5
|
||||||
|
raytrace_range: 3.5
|
||||||
|
min_obstacle_height: 0.10
|
||||||
|
max_obstacle_height: 1.0
|
||||||
|
frustum_clearing_enabled: true
|
||||||
|
frustum_clearing_pixel_step: 8
|
||||||
|
frustum_min_range: 0.20
|
||||||
|
frustum_max_range: 3.5
|
||||||
15
config/voxel_layer_params.yaml
Normal file
15
config/voxel_layer_params.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
voxel_layer:
|
||||||
|
enabled: true
|
||||||
|
footprint_clearing_enabled: true
|
||||||
|
max_obstacle_height: 1.5
|
||||||
|
origin_z: 0.0
|
||||||
|
z_resolution: 0.15
|
||||||
|
z_voxels: 8
|
||||||
|
unknown_threshold: 15.0
|
||||||
|
mark_threshold: 0
|
||||||
|
combination_method: 1
|
||||||
|
frustum_clearing_enabled: false
|
||||||
|
frustum_clearing_pixel_step: 8
|
||||||
|
frustum_min_range: 0.20
|
||||||
|
frustum_max_range: 3.5
|
||||||
|
frustum_depth_camera_topic: /camera/depth/data
|
||||||
47
launch/depth_local_costmap_test.launch
Normal file
47
launch/depth_local_costmap_test.launch
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<launch>
|
||||||
|
<arg name="base_frame" default="base_link"/>
|
||||||
|
<arg name="global_frame" default="odom"/>
|
||||||
|
<arg name="depth_cloud_topic" default="/camera/depth/points_proc"/>
|
||||||
|
<arg name="depth_image_topic" default="/camera/depth/image_raw"/>
|
||||||
|
<arg name="camera_info_topic" default="/camera/depth/camera_info"/>
|
||||||
|
<arg name="depth_camera_data_topic" default="/camera/depth/data"/>
|
||||||
|
<arg name="camera_right_info_topic" default="/camera_right/depth/camera_info"/>
|
||||||
|
<arg name="odom_topic" default="/odom"/>
|
||||||
|
<arg name="publish_odom_tf" default="false"/>
|
||||||
|
|
||||||
|
<node pkg="depth_local_costmap_noetic_test"
|
||||||
|
type="depth_local_costmap_noetic_test_node"
|
||||||
|
name="depth_local_costmap_noetic_test"
|
||||||
|
output="screen">
|
||||||
|
<env name="PNKX_NAV_CORE_CONFIG_DIR" value="$(find depth_local_costmap_noetic_test)"/>
|
||||||
|
|
||||||
|
<param name="base_frame" value="$(arg base_frame)"/>
|
||||||
|
<param name="publish_odom_tf" value="$(arg publish_odom_tf)"/>
|
||||||
|
<param name="odom_topic" value="$(arg odom_topic)"/>
|
||||||
|
<param name="depth_cloud_topic" value="$(arg depth_cloud_topic)"/>
|
||||||
|
<param name="depth_image_topic" value="$(arg depth_image_topic)"/>
|
||||||
|
<param name="camera_info_topic" value="$(arg camera_info_topic)"/>
|
||||||
|
<param name="depth_camera_data_topic" value="$(arg depth_camera_data_topic)"/>
|
||||||
|
<param name="camera_right_info_topic" value="$(arg camera_right_info_topic)"/>
|
||||||
|
|
||||||
|
<param name="local_costmap/robot_base_frame" value="$(arg base_frame)"/>
|
||||||
|
<param name="local_costmap/global_frame" value="$(arg global_frame)"/>
|
||||||
|
<param name="local_costmap/voxel_layer/observation_sources" value="pc_marking pc_clearing"/>
|
||||||
|
|
||||||
|
<param name="local_costmap/voxel_layer/pc_marking/topic" value="$(arg depth_cloud_topic)"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_marking/data_type" value="PointCloud2"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_marking/clearing" value="false"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_marking/marking" value="true"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_marking/frustum_clearing_enabled" value="false"/>
|
||||||
|
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/topic" value="$(arg depth_camera_data_topic)"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/data_type" value="DepthCameraData"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/clearing" value="false"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/marking" value="true"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/frustum_clearing_enabled" value="true"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/frustum_clearing_pixel_step" value="8"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/frustum_min_range" value="0.20"/>
|
||||||
|
<param name="local_costmap/voxel_layer/pc_clearing/frustum_max_range" value="3.5"/>
|
||||||
|
<param name="local_costmap/voxel_layer/frustum_depth_camera_topic" value="$(arg depth_camera_data_topic)"/>
|
||||||
|
</node>
|
||||||
|
</launch>
|
||||||
24
package.xml
Normal file
24
package.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<package format="2">
|
||||||
|
<name>depth_local_costmap_noetic_test</name>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<description>Minimal ROS Noetic C++ local test package for the T800 robot_costmap_2d stack.</description>
|
||||||
|
|
||||||
|
<maintainer email="duongtd@example.com">duongtd</maintainer>
|
||||||
|
<license>BSD</license>
|
||||||
|
|
||||||
|
<buildtool_depend>catkin</buildtool_depend>
|
||||||
|
|
||||||
|
<depend>data_convert</depend>
|
||||||
|
<depend>geometry_msgs</depend>
|
||||||
|
<depend>nav_msgs</depend>
|
||||||
|
<depend>robot_depth_image_proc</depend>
|
||||||
|
<depend>robot_costmap_2d</depend>
|
||||||
|
<depend>robot_cpp</depend>
|
||||||
|
<depend>robot_geometry_msgs</depend>
|
||||||
|
<depend>robot_sensor_msgs</depend>
|
||||||
|
<depend>robot_time</depend>
|
||||||
|
<depend>roscpp</depend>
|
||||||
|
<depend>sensor_msgs</depend>
|
||||||
|
<depend>tf2_ros</depend>
|
||||||
|
</package>
|
||||||
461
src/depth_local_costmap_noetic_test_node.cpp
Normal file
461
src/depth_local_costmap_noetic_test_node.cpp
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
#include <robot_costmap_2d/costmap_2d.h>
|
||||||
|
#include <robot_costmap_2d/costmap_2d_robot.h>
|
||||||
|
#include <robot_costmap_2d/layer.h>
|
||||||
|
|
||||||
|
#include <geometry_msgs/TransformStamped.h>
|
||||||
|
#include <nav_msgs/Odometry.h>
|
||||||
|
#include <robot_depth_image_proc/ros_message_conversions.h>
|
||||||
|
#include <robot/robot.h>
|
||||||
|
#include <robot_geometry_msgs/PoseStamped.h>
|
||||||
|
#include <robot_sensor_msgs/CameraInfo.h>
|
||||||
|
#include <robot_sensor_msgs/DepthCameraData.h>
|
||||||
|
#include <robot_sensor_msgs/Image.h>
|
||||||
|
#include <robot_sensor_msgs/PointCloud2.h>
|
||||||
|
#include <robot_sensor_msgs/PointField.h>
|
||||||
|
#include <ros/ros.h>
|
||||||
|
#include <sensor_msgs/CameraInfo.h>
|
||||||
|
#include <sensor_msgs/Image.h>
|
||||||
|
#include <sensor_msgs/PointCloud2.h>
|
||||||
|
#include <tf2/exceptions.h>
|
||||||
|
#include <tf2_ros/buffer.h>
|
||||||
|
#include <tf2_ros/transform_broadcaster.h>
|
||||||
|
#include <tf2_ros/transform_listener.h>
|
||||||
|
#include <tf3/buffer_core.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <exception>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
robot_sensor_msgs::PointCloud2 toRobotPointCloud2(const sensor_msgs::PointCloud2& msg)
|
||||||
|
{
|
||||||
|
robot_sensor_msgs::PointCloud2 robot_pc;
|
||||||
|
|
||||||
|
robot_pc.header.seq = msg.header.seq;
|
||||||
|
robot_pc.header.stamp = robot::Time(msg.header.stamp.toSec());
|
||||||
|
robot_pc.header.frame_id = msg.header.frame_id;
|
||||||
|
robot_pc.height = msg.height;
|
||||||
|
robot_pc.width = msg.width;
|
||||||
|
|
||||||
|
robot_pc.fields.clear();
|
||||||
|
robot_pc.fields.reserve(msg.fields.size());
|
||||||
|
for (const auto& field : msg.fields)
|
||||||
|
{
|
||||||
|
robot_sensor_msgs::PointField robot_field;
|
||||||
|
robot_field.name = field.name;
|
||||||
|
robot_field.offset = field.offset;
|
||||||
|
robot_field.datatype = field.datatype;
|
||||||
|
robot_field.count = field.count;
|
||||||
|
robot_pc.fields.push_back(robot_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_pc.is_bigendian = msg.is_bigendian;
|
||||||
|
robot_pc.point_step = msg.point_step;
|
||||||
|
robot_pc.row_step = msg.row_step;
|
||||||
|
robot_pc.is_dense = msg.is_dense;
|
||||||
|
robot_pc.data = msg.data;
|
||||||
|
return robot_pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isVoxelLayer(const robot_costmap_2d::Layer& layer)
|
||||||
|
{
|
||||||
|
return layer.getType() == robot_costmap_2d::LayerType::VOXEL_LAYER ||
|
||||||
|
layer.getName() == "local_costmap/voxel_layer";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename MessageT>
|
||||||
|
void feedVoxelLayers(robot_costmap_2d::Costmap2DROBOT& local_costmap,
|
||||||
|
const MessageT& message,
|
||||||
|
const std::string& topic)
|
||||||
|
{
|
||||||
|
if (local_costmap.getLayeredCostmap() == nullptr ||
|
||||||
|
local_costmap.getLayeredCostmap()->getPlugins() == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& layer : *local_costmap.getLayeredCostmap()->getPlugins())
|
||||||
|
{
|
||||||
|
if (layer && isVoxelLayer(*layer))
|
||||||
|
layer->dataCallBack<MessageT>(message, topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RosTfToTf3Bridge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RosTfToTf3Bridge(std::shared_ptr<tf2_ros::Buffer> tf2_buffer, tf3::BufferCore& tf3_buffer)
|
||||||
|
: tf2_buffer_(std::move(tf2_buffer))
|
||||||
|
, tf3_buffer_(tf3_buffer)
|
||||||
|
, worker_(&RosTfToTf3Bridge::run, this)
|
||||||
|
{
|
||||||
|
tf3_buffer_.setUsingDedicatedThread(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RosTfToTf3Bridge()
|
||||||
|
{
|
||||||
|
stop_ = true;
|
||||||
|
if (worker_.joinable())
|
||||||
|
worker_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TfEdge
|
||||||
|
{
|
||||||
|
std::string parent;
|
||||||
|
std::string child;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<TfEdge> parseEdges(const std::string& tree)
|
||||||
|
{
|
||||||
|
std::vector<TfEdge> edges;
|
||||||
|
std::istringstream stream(tree);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(stream, line))
|
||||||
|
{
|
||||||
|
const std::size_t frame_pos = line.find("Frame ");
|
||||||
|
const std::size_t parent_pos = line.find(" exists with parent ");
|
||||||
|
if (frame_pos == std::string::npos || parent_pos == std::string::npos)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string child = line.substr(frame_pos + 6, parent_pos - (frame_pos + 6));
|
||||||
|
std::string parent = line.substr(parent_pos + 20);
|
||||||
|
if (!parent.empty() && parent.back() == '.')
|
||||||
|
parent.pop_back();
|
||||||
|
if (!parent.empty() && !child.empty())
|
||||||
|
edges.push_back({parent, child});
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
ros::Rate rate(50.0);
|
||||||
|
|
||||||
|
while (ros::ok() && !stop_)
|
||||||
|
{
|
||||||
|
if (!tf2_buffer_)
|
||||||
|
{
|
||||||
|
rate.sleep();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<TfEdge> edges = parseEdges(tf2_buffer_->allFramesAsString());
|
||||||
|
for (const auto& edge : edges)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!tf2_buffer_->canTransform(edge.parent, edge.child, ros::Time(0), ros::Duration(0.01)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const geometry_msgs::TransformStamped ros_tf =
|
||||||
|
tf2_buffer_->lookupTransform(edge.parent, edge.child, ros::Time(0));
|
||||||
|
|
||||||
|
tf3::TransformStampedMsg tf3_msg;
|
||||||
|
tf3_msg.header.stamp = tf3::Time::now();
|
||||||
|
tf3_msg.header.frame_id = ros_tf.header.frame_id;
|
||||||
|
tf3_msg.child_frame_id = ros_tf.child_frame_id;
|
||||||
|
tf3_msg.transform.translation.x = ros_tf.transform.translation.x;
|
||||||
|
tf3_msg.transform.translation.y = ros_tf.transform.translation.y;
|
||||||
|
tf3_msg.transform.translation.z = ros_tf.transform.translation.z;
|
||||||
|
tf3_msg.transform.rotation.x = ros_tf.transform.rotation.x;
|
||||||
|
tf3_msg.transform.rotation.y = ros_tf.transform.rotation.y;
|
||||||
|
tf3_msg.transform.rotation.z = ros_tf.transform.rotation.z;
|
||||||
|
tf3_msg.transform.rotation.w = ros_tf.transform.rotation.w;
|
||||||
|
tf3_buffer_.setTransform(tf3_msg, "ros_tf_bridge");
|
||||||
|
}
|
||||||
|
catch (const tf2::TransformException& ex)
|
||||||
|
{
|
||||||
|
ROS_WARN_THROTTLE(5.0, "TF bridge failed %s -> %s: %s",
|
||||||
|
edge.parent.c_str(),
|
||||||
|
edge.child.c_str(),
|
||||||
|
ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rate.sleep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<tf2_ros::Buffer> tf2_buffer_;
|
||||||
|
tf3::BufferCore& tf3_buffer_;
|
||||||
|
std::atomic<bool> stop_{false};
|
||||||
|
std::thread worker_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OdomTfBridge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OdomTfBridge(ros::NodeHandle& private_nh, const std::string& base_frame)
|
||||||
|
: base_frame_(base_frame)
|
||||||
|
{
|
||||||
|
private_nh.param("odom_topic", odom_topic_, std::string("/odom"));
|
||||||
|
odom_sub_ = private_nh.subscribe(odom_topic_, 10, &OdomTfBridge::odomCallback, this);
|
||||||
|
ROS_WARN("Publishing ROS TF from %s. Keep disabled if another node already publishes odom -> %s.",
|
||||||
|
odom_topic_.c_str(),
|
||||||
|
base_frame_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void odomCallback(const nav_msgs::Odometry::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
if (!msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
geometry_msgs::TransformStamped transform;
|
||||||
|
transform.header = msg->header;
|
||||||
|
transform.child_frame_id = msg->child_frame_id.empty() ? base_frame_ : msg->child_frame_id;
|
||||||
|
transform.transform.translation.x = msg->pose.pose.position.x;
|
||||||
|
transform.transform.translation.y = msg->pose.pose.position.y;
|
||||||
|
transform.transform.translation.z = msg->pose.pose.position.z;
|
||||||
|
transform.transform.rotation = msg->pose.pose.orientation;
|
||||||
|
tf_broadcaster_.sendTransform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base_frame_;
|
||||||
|
std::string odom_topic_;
|
||||||
|
ros::Subscriber odom_sub_;
|
||||||
|
tf2_ros::TransformBroadcaster tf_broadcaster_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DepthCloudFeeder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DepthCloudFeeder(ros::NodeHandle& private_nh, robot_costmap_2d::Costmap2DROBOT& local_costmap)
|
||||||
|
: local_costmap_(local_costmap)
|
||||||
|
{
|
||||||
|
private_nh.param("depth_cloud_topic", depth_cloud_topic_, std::string("/camera/depth/points_proc"));
|
||||||
|
depth_cloud_sub_ =
|
||||||
|
private_nh.subscribe(depth_cloud_topic_, 1, &DepthCloudFeeder::depthCloudCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void depthCloudCallback(const sensor_msgs::PointCloud2::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
if (!msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const robot_sensor_msgs::PointCloud2 robot_cloud = toRobotPointCloud2(*msg);
|
||||||
|
feedVoxelLayers(local_costmap_, robot_cloud, depth_cloud_topic_);
|
||||||
|
|
||||||
|
ROS_INFO_THROTTLE(5.0, "Fed depth cloud to robot_costmap_2d: %ux%u frame=%s",
|
||||||
|
msg->width,
|
||||||
|
msg->height,
|
||||||
|
msg->header.frame_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_costmap_2d::Costmap2DROBOT& local_costmap_;
|
||||||
|
std::string depth_cloud_topic_;
|
||||||
|
ros::Subscriber depth_cloud_sub_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DepthCameraDataFeeder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DepthCameraDataFeeder(ros::NodeHandle& private_nh, robot_costmap_2d::Costmap2DROBOT& local_costmap)
|
||||||
|
: local_costmap_(local_costmap)
|
||||||
|
{
|
||||||
|
private_nh.param("depth_image_topic", depth_image_topic_, std::string("/camera/depth/image_raw"));
|
||||||
|
private_nh.param("camera_info_topic", camera_info_topic_, std::string("/camera/depth/camera_info"));
|
||||||
|
private_nh.param("depth_camera_data_topic",
|
||||||
|
depth_camera_data_topic_,
|
||||||
|
std::string("/camera/depth/data"));
|
||||||
|
|
||||||
|
camera_info_sub_ =
|
||||||
|
private_nh.subscribe(camera_info_topic_, 1, &DepthCameraDataFeeder::cameraInfoCallback, this);
|
||||||
|
depth_image_sub_ =
|
||||||
|
private_nh.subscribe(depth_image_topic_, 1, &DepthCameraDataFeeder::depthImageCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cameraInfoCallback(const sensor_msgs::CameraInfo::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
if (!msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(camera_info_mutex_);
|
||||||
|
camera_info_ = depth_image_proc::toRobotCameraInfo(*msg);
|
||||||
|
has_camera_info_ = true;
|
||||||
|
|
||||||
|
ROS_INFO_THROTTLE(5.0, "Cached camera_info for depth clearing: %ux%u frame=%s",
|
||||||
|
msg->width,
|
||||||
|
msg->height,
|
||||||
|
msg->header.frame_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void depthImageCallback(const sensor_msgs::Image::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
if (!msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
robot_sensor_msgs::CameraInfo camera_info;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(camera_info_mutex_);
|
||||||
|
if (!has_camera_info_)
|
||||||
|
{
|
||||||
|
ROS_WARN_THROTTLE(5.0,
|
||||||
|
"Waiting for %s before feeding DepthCameraData to robot_costmap_2d",
|
||||||
|
camera_info_topic_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera_info = camera_info_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera_info.K[0] <= 0.0 || camera_info.K[4] <= 0.0)
|
||||||
|
{
|
||||||
|
ROS_ERROR_THROTTLE(5.0, "Invalid depth camera intrinsics on %s", camera_info_topic_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->encoding != "16UC1" && msg->encoding != "mono16" && msg->encoding != "32FC1")
|
||||||
|
{
|
||||||
|
ROS_ERROR_THROTTLE(
|
||||||
|
5.0,
|
||||||
|
"Unsupported depth encoding [%s], expected 16UC1/mono16/32FC1",
|
||||||
|
msg->encoding.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_sensor_msgs::DepthCameraData::Ptr depth_camera_data(new robot_sensor_msgs::DepthCameraData());
|
||||||
|
depth_camera_data->depth = depth_image_proc::toRobotImage(*msg);
|
||||||
|
depth_camera_data->camera_info = camera_info;
|
||||||
|
depth_camera_data->header = depth_camera_data->depth.header;
|
||||||
|
if (depth_camera_data->header.frame_id.empty())
|
||||||
|
depth_camera_data->header.frame_id = depth_camera_data->camera_info.header.frame_id;
|
||||||
|
|
||||||
|
robot_sensor_msgs::DepthCameraData::ConstPtr const_depth_camera_data = depth_camera_data;
|
||||||
|
feedVoxelLayers(local_costmap_, const_depth_camera_data, depth_camera_data_topic_);
|
||||||
|
|
||||||
|
ROS_INFO_THROTTLE(5.0, "Fed DepthCameraData to robot_costmap_2d: %ux%u encoding=%s frame=%s topic=%s",
|
||||||
|
msg->width,
|
||||||
|
msg->height,
|
||||||
|
msg->encoding.c_str(),
|
||||||
|
depth_camera_data->header.frame_id.c_str(),
|
||||||
|
depth_camera_data_topic_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_costmap_2d::Costmap2DROBOT& local_costmap_;
|
||||||
|
std::string depth_image_topic_;
|
||||||
|
std::string camera_info_topic_;
|
||||||
|
std::string depth_camera_data_topic_;
|
||||||
|
robot_sensor_msgs::CameraInfo camera_info_;
|
||||||
|
bool has_camera_info_{false};
|
||||||
|
std::mutex camera_info_mutex_;
|
||||||
|
ros::Subscriber depth_image_sub_;
|
||||||
|
ros::Subscriber camera_info_sub_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RightCameraInfoWatchdog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RightCameraInfoWatchdog(ros::NodeHandle& private_nh)
|
||||||
|
{
|
||||||
|
std::string camera_right_info_topic;
|
||||||
|
private_nh.param("camera_right_info_topic",
|
||||||
|
camera_right_info_topic,
|
||||||
|
std::string("/camera_right/depth/camera_info"));
|
||||||
|
|
||||||
|
camera_right_info_sub_ =
|
||||||
|
private_nh.subscribe(camera_right_info_topic, 1, &RightCameraInfoWatchdog::cameraRightInfoCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cameraRightInfoCallback(const sensor_msgs::CameraInfo::ConstPtr& msg)
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
ROS_INFO_THROTTLE(5.0, "Right camera info OK: %ux%u frame=%s",
|
||||||
|
msg->width,
|
||||||
|
msg->height,
|
||||||
|
msg->header.frame_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ros::Subscriber camera_right_info_sub_;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ros::init(argc, argv, "depth_local_costmap_noetic_test");
|
||||||
|
|
||||||
|
ros::NodeHandle private_nh("~");
|
||||||
|
ros::AsyncSpinner spinner(2);
|
||||||
|
spinner.start();
|
||||||
|
|
||||||
|
std::string base_frame;
|
||||||
|
bool publish_odom_tf = false;
|
||||||
|
private_nh.param("base_frame", base_frame, std::string("base_link"));
|
||||||
|
private_nh.param("publish_odom_tf", publish_odom_tf, false);
|
||||||
|
|
||||||
|
auto tf2_buffer = std::make_shared<tf2_ros::Buffer>(ros::Duration(10.0));
|
||||||
|
tf2_ros::TransformListener tf2_listener(*tf2_buffer);
|
||||||
|
tf3::BufferCore tf3_buffer;
|
||||||
|
RosTfToTf3Bridge tf_bridge(tf2_buffer, tf3_buffer);
|
||||||
|
|
||||||
|
std::unique_ptr<OdomTfBridge> odom_tf_bridge;
|
||||||
|
if (publish_odom_tf)
|
||||||
|
odom_tf_bridge = std::make_unique<OdomTfBridge>(private_nh, base_frame);
|
||||||
|
|
||||||
|
RightCameraInfoWatchdog right_camera_info_watchdog(private_nh);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
local_costmap.start();
|
||||||
|
|
||||||
|
ROS_INFO("T800 robot_costmap_2d local costmap test started.");
|
||||||
|
|
||||||
|
ros::Rate rate(2.0);
|
||||||
|
while (ros::ok())
|
||||||
|
{
|
||||||
|
robot_geometry_msgs::PoseStamped robot_pose;
|
||||||
|
const bool pose_ok = local_costmap.getRobotPose(robot_pose);
|
||||||
|
const robot_costmap_2d::Costmap2D* costmap = local_costmap.getCostmap();
|
||||||
|
|
||||||
|
if (costmap == nullptr)
|
||||||
|
{
|
||||||
|
ROS_WARN_THROTTLE(2.0, "robot_costmap_2d local costmap pointer is null");
|
||||||
|
}
|
||||||
|
else if (!pose_ok)
|
||||||
|
{
|
||||||
|
ROS_WARN_THROTTLE(
|
||||||
|
2.0,
|
||||||
|
"robot_costmap_2d has no robot pose. Check TF: odom -> %s and cloud frame -> odom.",
|
||||||
|
base_frame.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ROS_INFO_THROTTLE(
|
||||||
|
2.0,
|
||||||
|
"robot_costmap_2d OK: size=%ux%u res=%.3f origin=(%.2f, %.2f) robot=(%.2f, %.2f)",
|
||||||
|
costmap->getSizeInCellsX(),
|
||||||
|
costmap->getSizeInCellsY(),
|
||||||
|
costmap->getResolution(),
|
||||||
|
costmap->getOriginX(),
|
||||||
|
costmap->getOriginY(),
|
||||||
|
robot_pose.pose.position.x,
|
||||||
|
robot_pose.pose.position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
rate.sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
local_costmap.stop();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ROS_FATAL("depth_local_costmap_noetic_test failed: %s", e.what());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user