diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c46d59..203d597 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## v2.1.6 - 2024-12-25
+- [new feature] support for random num of pointcloud input sources
+- [bug fix] a single `tf::TransformListener` is sufficient
+- improve safety and efficiency for buffer process
+- replace `boost::thread` with `std::thread`
+
## v2.1.5 - 2024-12-24
- [upgrade] apply multithread to process several pointclouds parallely
- [upgrade] store pointclouds with `std::deque`
-- support for one single pointcloud input
## v2.1.4 - 2024-12-24
- [upgrade] set `queue_size` of `Subscriber` to `boost::thread::hardware_concurrency()`
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d3c201..d52d5da 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,14 +6,12 @@ set(CMAKE_CXX_FLAGS "-std=c++17")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g")
set(PYTHON_EXECUTABLE "/usr/bin/python3.8")
-find_package(Eigen3 REQUIRED)
find_package(PCL 1.7 REQUIRED)
find_package(octomap REQUIRED)
find_package(Boost REQUIRED)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
- message_filters
tf
pcl_ros
std_msgs
@@ -34,7 +32,6 @@ include_directories(
SYSTEM
include
${catkin_INCLUDE_DIRS}
- ${Eigen3_INCLUDE_DIRS}
${PCL_INCLUDE_DIRS}
${OCTOMAP_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
diff --git a/README.md b/README.md
index e371a8f..6f9c23a 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
![Static Badge](https://img.shields.io/badge/Python-3.8.10-3776AB?logo=python)
![Static Badge](https://img.shields.io/badge/Ubuntu-20.04.6-E95420?logo=ubuntu)
-A ROS package for mapping via octomap.
+A ROS package to process PointCloud and build OctoMap
## Installation
@@ -25,7 +25,30 @@ EGO-Planner等规划器要求点云发布在world坐标系下,因此需要将
rosrun easondrone_mapping pub_pcl_world.py
```
-## merge pointcloud
+## Merge Multiple sensor_msgs::PointCloud2
+
+### Config Parameters
+
+```yaml
+pc2_topics_in:
+ - /livox/lidar
+ - /realsense/depth_camera/depth/points
+timeout: 0.5
+
+tf_duration: 0.05
+
+# The Iterative Closest Point algorithm
+icp_enable: true
+icp_max_iter: 1
+icp_tf_epsilon: 1e-8
+icp_euclidean_fitness_epsilon: 1e-5
+icp_max_corr_d: 0.05
+
+pc2_topic_out: /sensor/pc2_out
+pc2_frame_out: base_link
+```
+
+### Launch ROS Node
```xml
@@ -35,13 +58,15 @@ rosrun easondrone_mapping pub_pcl_world.py
```
-## 建立地图
+## Build OctoMap
+
+### Launch ROS Node
```bash
roslaunch easondrone_mapping octomap_server.launch
```
-## 保存地图
+### Save Map
> You are trying to invoke `octomap_saver` as an argument to the `octomap_server` node. However, `octomap_saver` is a node of its own, so you only have to start it from a separate terminal while `octomap_server` is running. Check the documentation at http://wiki.ros.org/octomap_server#octomap_saver
@@ -59,9 +84,9 @@ rosrun octomap_server octomap_saver -f ~/easondrone_ws/reconstruct/easondrone_ma
- ⭐️ https://github.com/OctoMap/octomap_mapping/blob/kinetic-devel/octomap_server/src/OctomapServer.cpp
- ⭐️ https://groups.google.com/g/octomap/c/ZyfNzcuGlY0?pli=1
-## 查看地图
+### 查看地图
-### 方法1:octovis
+方法1:octovis
也可以借助`octovis`工具查看
@@ -69,7 +94,7 @@ rosrun octomap_server octomap_saver -f ~/easondrone_ws/reconstruct/easondrone_ma
octovis ~/easondrone_ws/reconstruct/easondrone_mapping/map.bt
```
-### 方法2:rviz
+方法2:rviz
一种方法是在`rviz`中查看
@@ -86,7 +111,7 @@ rosrun octomap_server octomap_server_node ~/easondrone_ws/reconstruct/easondrone
- [在ROS中将点云(PointCloud2)生成Octomap,rviz可视化显示](https://blog.csdn.net/qq_41816368/article/details/133929136)
- ⭐️ [octomap in rviz and occupancy grids in 3D maps](https://robotics.stackexchange.com/questions/41362/octomap-in-rviz-and-occupancy-grids-in-3d-maps)
-## 加载地图
+### 加载地图
参考:
- ⭐ [how to use octomap_server?](https://answers.ros.org/question/361841/how-to-use-octomap_server/)
diff --git a/config/easondrone.rviz b/config/easondrone.rviz
index 14c3124..0d06b85 100644
--- a/config/easondrone.rviz
+++ b/config/easondrone.rviz
@@ -347,7 +347,7 @@ Visualization Manager:
Size (Pixels): 3
Size (m): 0.009999999776482582
Style: Points
- Topic: /sensor/pcl2_out
+ Topic: /sensor/pc2_out
Unreliable: false
Use Fixed Frame: true
Use rainbow: true
diff --git a/config/merge_pcl.yaml b/config/merge_pcl.yaml
index ef62cd2..afef2dc 100644
--- a/config/merge_pcl.yaml
+++ b/config/merge_pcl.yaml
@@ -1,9 +1,9 @@
-pcl2_source_num: 2
-pcl2_topic_0: /livox/lidar
-pcl2_frame_0: laser_livox
-pcl2_topic_1: /realsense/depth_camera/depth/points
-pcl2_frame_0: realsense_camera_frame
-tf_duration: 0.1
-pcl2_topic_out: /sensor/pcl2_out
-pcl2_frame_out: base_link
-timeout: 0.1
+pc2_topics_in:
+ - /livox/lidar
+ - /realsense/depth_camera/depth/points
+timeout: 0.5
+
+tf_duration: 0.05
+
+pc2_topic_out: /sensor/pc2_out
+pc2_frame_out: base_link
diff --git a/include/merge_pcl/merge_pcl.h b/include/merge_pcl/merge_pcl.h
index f746e87..bfac6be 100644
--- a/include/merge_pcl/merge_pcl.h
+++ b/include/merge_pcl/merge_pcl.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -16,47 +17,45 @@
#include
#include
#include
-#include
#include
-#include
#include
+typedef pcl::PointXYZ PointT;
+typedef pcl::PointCloud PointCloudT;
+
using namespace std;
class PointCloudMerger {
public:
- PointCloudMerger();
+ PointCloudMerger(ros::NodeHandle nh);
~PointCloudMerger() = default;
// Method to process buffers
void processBuffers();
private:
- ros::NodeHandle nh;
-
- int pcl2_source_num;
+ // Subscribers
+ std::vector pc2_topics_in;
size_t queue_size;
- double timeout;
+ std::vector pc2_subs;
- std::string pcl2_topic_0, pcl2_frame_0;
- ros::Subscriber pcl2_sub_0;
- void pcl2Callback0(const sensor_msgs::PointCloud2ConstPtr& pcl2);
+ double timeout;
- std::string pcl2_topic_1, pcl2_frame_1;
- ros::Subscriber pcl2_sub_1;
- void pcl2Callback1(const sensor_msgs::PointCloud2ConstPtr& pcl2);
+ // Transform listener
+ double tf_duration;
+ tf::TransformListener tf_listener;
// Buffers to store point clouds
- std::deque::Ptr> buffer_0;
- std::deque::Ptr> buffer_1;
+ std::deque pcT_buffer;
std::mutex buffer_mutex;
- // Transform listeners
- tf::TransformListener listener0, listener1;
- double tf_duration;
+ // Publisher
+ std::string pc2_topic_out;
+ std::string pc2_frame_out;
+ ros::Publisher pc2_pub;
- std::string pcl2_topic_out, pcl2_frame_out;
- ros::Publisher pcl2_pub;
+ // Unified callback for point cloud subscribers
+ void pc2Callback(const sensor_msgs::PointCloud2ConstPtr& pc2, const size_t index);
};
#endif //MERGE_PCL_H
diff --git a/launch/octomap_server.launch b/launch/octomap_server.launch
index 5032ab4..ee11399 100644
--- a/launch/octomap_server.launch
+++ b/launch/octomap_server.launch
@@ -1,15 +1,10 @@
-
-
-
-
-
-
+
diff --git a/package.xml b/package.xml
index 8eeb33a..5dfa265 100644
--- a/package.xml
+++ b/package.xml
@@ -1,9 +1,9 @@
easondrone_mapping
- 2.1.5
+ 2.1.6
- A ROS package for mapping via octomap
+ A ROS package to process PointCloud and build OctoMap
Eason Hua
@@ -20,7 +20,6 @@
nav_msgs
pcl_ros
octomap
- message_filters
tf
tf2_ros
tf2_sensor_msgs
diff --git a/src/merge_pcl/merge_pcl.cpp b/src/merge_pcl/merge_pcl.cpp
index c6c26b2..5238e39 100644
--- a/src/merge_pcl/merge_pcl.cpp
+++ b/src/merge_pcl/merge_pcl.cpp
@@ -2,119 +2,106 @@
using namespace std;
-PointCloudMerger::PointCloudMerger() : nh("~") {
- nh.param("pcl2_source_num", pcl2_source_num, 2);
- queue_size = boost::thread::hardware_concurrency();
+PointCloudMerger::PointCloudMerger(ros::NodeHandle nh){
+ nh.getParam("pc2_topics_in", pc2_topics_in);
nh.param("timeout", timeout, 0.5);
- nh.param("pcl2_topic_0", pcl2_topic_0, "");
- nh.param("pcl2_frame_0", pcl2_frame_0, "");
- pcl2_sub_0 = nh.subscribe
- (pcl2_topic_0, queue_size, &PointCloudMerger::pcl2Callback0, this);
+ queue_size = std::thread::hardware_concurrency();
- nh.param("pcl2_topic_1", pcl2_topic_1, "");
- nh.param("pcl2_frame_1", pcl2_frame_1, "");
- pcl2_sub_1 = nh.subscribe
- (pcl2_topic_1, queue_size, &PointCloudMerger::pcl2Callback1, this);
+ for (size_t i = 0; i < pc2_topics_in.size(); ++i) {
+ string pc2_topic_in = pc2_topics_in[i];
+ PointCloudT::Ptr pcT(new PointCloudT());
+ pcT_buffer.push_back(pcT);
- nh.param("tf_duration", tf_duration, 0.1);
- listener0.waitForTransform(pcl2_frame_out, pcl2_frame_0, ros::Time(0), ros::Duration(tf_duration));
- listener1.waitForTransform(pcl2_frame_out, pcl2_frame_1, ros::Time(0), ros::Duration(tf_duration));
+ pc2_subs.emplace_back(
+ nh.subscribe(
+ pc2_topic_in, queue_size,
+ boost::bind(&PointCloudMerger::pc2Callback, this, _1, i)
+ )
+ );
+ }
+
+ nh.param("tf_duration", tf_duration, 0.05);
- nh.param("pcl2_topic_out", pcl2_topic_out, "");
- nh.param("pcl2_frame_out", pcl2_frame_out, "");
- pcl2_pub = nh.advertise
- (pcl2_topic_out, queue_size);
+ nh.param("pc2_topic_out", pc2_topic_out, "");
+ nh.param("pc2_frame_out", pc2_frame_out, "");
+ pc2_pub = nh.advertise(pc2_topic_out, queue_size);
- ROS_INFO("[merge_pcl] Initialized!");
+ ROS_INFO("merge_pcl node initialized success!");
}
-void PointCloudMerger::pcl2Callback0(const sensor_msgs::PointCloud2ConstPtr& pcl2) {
- pcl::PointCloud::Ptr pclxyz_0(new pcl::PointCloud());
- pcl::fromROSMsg(*pcl2, *pclxyz_0);
-
- if (pcl2->header.frame_id != pcl2_frame_out) {
- try {
- pcl_ros::transformPointCloud(pcl2_frame_out, *pclxyz_0, *pclxyz_0, listener0);
- }
- catch (...) {
- ROS_ERROR("Transform failed for pcl2_0!");
- return;
- }
- }
+void PointCloudMerger::pc2Callback(const sensor_msgs::PointCloud2ConstPtr& pc2_msg, const size_t index) {
+ PointCloudT::Ptr pcT(new PointCloudT());
+ pcl::fromROSMsg(*pc2_msg, *pcT);
- std::lock_guard lock(buffer_mutex);
- buffer_0.push_back(pclxyz_0);
- if (buffer_0.size() > queue_size) {
- buffer_0.pop_front();
+ if (pcT->empty()) {
+ PCL_ERROR("Empty pointcloud received! (frame_id %s)\n", pc2_msg->header.frame_id);
+ return;
}
-}
-
-void PointCloudMerger::pcl2Callback1(const sensor_msgs::PointCloud2ConstPtr& pcl2) {
- pcl::PointCloud::Ptr pclxyz_1(new pcl::PointCloud());
- pcl::fromROSMsg(*pcl2, *pclxyz_1);
-
- if (pcl2->header.frame_id != pcl2_frame_out) {
- try {
- pcl_ros::transformPointCloud(pcl2_frame_out, *pclxyz_1, *pclxyz_1, listener1);
- }
- catch (...) {
- ROS_ERROR("Transform failed for pcl2_1!");
- return;
+ else{
+ if (pc2_msg->header.frame_id != pc2_frame_out) {
+ try {
+ pcl_ros::transformPointCloud(pc2_frame_out, *pcT, *pcT, tf_listener);
+ }
+ catch (...) {
+ ROS_ERROR("Transform failure for pointcloud! (frame_id %s)", pc2_msg->header.frame_id);
+ return;
+ }
}
- }
- std::lock_guard lock(buffer_mutex);
- buffer_1.push_back(pclxyz_1);
- if (buffer_1.size() > queue_size) {
- buffer_1.pop_front();
- }
+ {
+ std::lock_guard lock(buffer_mutex);
+ *pcT_buffer[index] = *pcT;
+ }
+ }
}
void PointCloudMerger::processBuffers() {
while (ros::ok()) {
- pcl::PointCloud::Ptr pclxyz_0, pclxyz_1, pclxyz_output;
-
{
std::lock_guard lock(buffer_mutex);
- if (!buffer_0.empty()) {
- pclxyz_0 = buffer_0.front();
- buffer_0.pop_front();
- }
- if (!buffer_1.empty()) {
- pclxyz_1 = buffer_1.front();
- buffer_1.pop_front();
- }
- }
- if (pclxyz_0 || pclxyz_1) {
- pclxyz_output.reset(new pcl::PointCloud());
- if(pclxyz_0){
- *pclxyz_output += *pclxyz_0;
+ if(pcT_buffer.empty()){
+ PCL_ERROR("All pointclouds are empty! If this continues, be careful!\n");
}
- if(pclxyz_1){
- *pclxyz_output += *pclxyz_1;
+ else{
+ PointCloudT::Ptr pcT_out(new PointCloudT());
+
+ for (const auto& pcT : pcT_buffer) {
+ if (pcT->empty()) {
+ continue;
+ }
+ else if(pcT_out->empty()){
+ *pcT_out += *pcT;
+ }
+ else{
+ // TODO: Optional ICP
+ *pcT_out += *pcT;
+ }
+ }
+
+ sensor_msgs::PointCloud2 pc2_out;
+ pcl::toROSMsg(*pcT_out, pc2_out);
+ pc2_out.header.frame_id = pc2_frame_out;
+
+ pc2_pub.publish(pc2_out);
}
-
- sensor_msgs::PointCloud2 pcl2_output;
- pcl2_output.header.frame_id = pcl2_frame_out;
- pcl::toROSMsg(*pclxyz_output, pcl2_output);
-
- pcl2_pub.publish(pcl2_output);
}
- ros::Duration(tf_duration).sleep(); // Small delay to avoid excessive CPU usage
+ // Small delay to avoid excessive CPU usage
+ ros::Duration(tf_duration).sleep();
}
}
int main(int argc, char** argv) {
ros::init(argc, argv, "merge_pcl");
- PointCloudMerger pc_merger;
+ ros::NodeHandle nh("~");
+
+ PointCloudMerger merge_pcl(nh);
- boost::thread processing_thread(&PointCloudMerger::processBuffers, &pc_merger);
+ std::thread process_thread(&PointCloudMerger::processBuffers, &merge_pcl);
ros::spin();
- processing_thread.join();
return 0;
}