Skip to content

Commit 5afd9a8

Browse files
author
plnegre
committed
New node: pointcloud_mapper_for_slam
1 parent 02ca688 commit 5afd9a8

File tree

1 file changed

+298
-0
lines changed

1 file changed

+298
-0
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
#include <ros/ros.h>
2+
#include <iostream>
3+
#include <fstream>
4+
#include <pcl_ros/point_cloud.h>
5+
#include <pcl_ros/transforms.h>
6+
#include <tf/transform_listener.h>
7+
#include <pcl/point_types.h>
8+
#include <pcl/filters/voxel_grid.h>
9+
#include <pcl/filters/passthrough.h>
10+
#include <pcl/filters/statistical_outlier_removal.h>
11+
#include <sys/stat.h>
12+
13+
/**
14+
* Stores incoming point clouds in a map transforming
15+
* each cloud to a global fixed frame using tf.
16+
*/
17+
class PointCloudMapper
18+
{
19+
public:
20+
21+
typedef pcl::PointXYZRGB Point;
22+
typedef pcl::PointCloud<pcl::PointXYZRGB> PointCloud;
23+
24+
PointCloudMapper() :
25+
nh_(), nh_priv_("~")
26+
{
27+
// Read the parameters from the parameter server (set defaults)
28+
nh_priv_.param("graph_vertices_file", graph_vertices_file_, std::string("graph_vertices.txt"));
29+
nh_priv_.param("graph_blocking_file", graph_blocking_file_, std::string(".block.txt"));
30+
nh_priv_.param("x_filter_min", x_filter_min_, -2.0);
31+
nh_priv_.param("x_filter_max", x_filter_max_, 2.0);
32+
nh_priv_.param("y_filter_min", y_filter_min_, -2.0);
33+
nh_priv_.param("y_filter_max", y_filter_max_, 2.0);
34+
nh_priv_.param("z_filter_min", z_filter_min_, 0.2);
35+
nh_priv_.param("z_filter_max", z_filter_max_, 2.0);
36+
nh_priv_.param("voxel_size", voxel_size_, 0.1);
37+
nh_priv_.param("mean_k", mean_k_, 50);
38+
nh_priv_.param("std_dev_thresh", std_dev_thresh_, 1.0);
39+
nh_priv_.param("filter_map", filter_map_, true);
40+
41+
cloud_sub_ = nh_.subscribe<sensor_msgs::PointCloud2>("input", 10, &PointCloudMapper::callback, this);
42+
bool latched = true;
43+
cloud_pub_ = nh_priv_.advertise<PointCloud>("output", 1, latched);
44+
pub_timer_ = nh_.createWallTimer(ros::WallDuration(3.0),
45+
&PointCloudMapper::timerCallback, this);
46+
}
47+
48+
void callback(const sensor_msgs::PointCloud2ConstPtr& cloud)
49+
{
50+
// Copy the cloud message
51+
sensor_msgs::PointCloud2 cur_cloud = *cloud;
52+
53+
// Filter map if required
54+
if(filter_map_)
55+
{
56+
// Get the cloud
57+
PointCloud pcl_cloud;
58+
pcl::fromROSMsg(cur_cloud, pcl_cloud);
59+
60+
// Filter
61+
PointCloud::Ptr cloud_downsampled = filter(pcl_cloud.makeShared());
62+
pcl_cloud = *cloud_downsampled;
63+
64+
// Convert again to
65+
pcl::toROSMsg(pcl_cloud, cur_cloud);
66+
67+
}
68+
// Insert the cloud into the list
69+
pointcloud_list_.push_back(cur_cloud);
70+
}
71+
72+
void timerCallback(const ros::WallTimerEvent& event)
73+
{
74+
// Detect subscribers
75+
if (cloud_pub_.getNumSubscribers() > 0)
76+
{
77+
// Initialize point cloud
78+
PointCloud accumulated_cloud;
79+
80+
// Wait until the blocking file disappear
81+
while(fileExists(graph_blocking_file_.c_str()))
82+
{
83+
ROS_WARN_THROTTLE(2, "[PointCloudMapper:] Graph blocking file detected, waiting...");
84+
ros::Duration(0.5).sleep();
85+
}
86+
ROS_INFO("[PointCloudMapper:] Processing SLAM graph and pointclouds.");
87+
88+
// Read the file
89+
std::vector<OdomMsg> graph_vertices;
90+
std::ifstream file(graph_vertices_file_.c_str());
91+
for(std::string line; std::getline(file, line);)
92+
{
93+
// Get the line values
94+
std::string s;
95+
std::istringstream f(line);
96+
std::vector<std::string> line_values;
97+
while (std::getline(f, s, ','))
98+
line_values.push_back(s);
99+
100+
// Convert string values to real
101+
if(line_values.size() == 12)
102+
{
103+
OdomMsg odom_msg;
104+
105+
std::ostringstream s_t, s_x, s_y, s_z, s_qx, s_qy, s_qz, s_qw;
106+
s_t << std::fixed << std::setprecision(9) << line_values.at(0);
107+
s_x << std::fixed << std::setprecision(7) << line_values.at(5);
108+
s_y << std::fixed << std::setprecision(7) << line_values.at(6);
109+
s_z << std::fixed << std::setprecision(7) << line_values.at(7);
110+
s_qx << std::fixed << std::setprecision(7) << line_values.at(8);
111+
s_qy << std::fixed << std::setprecision(7) << line_values.at(9);
112+
s_qz << std::fixed << std::setprecision(7) << line_values.at(10);
113+
s_qw << std::fixed << std::setprecision(7) << line_values.at(11);
114+
115+
odom_msg.timestamp = boost::lexical_cast<double>(s_t.str());
116+
odom_msg.id = boost::lexical_cast<int>(line_values.at(1));
117+
odom_msg.fixed_frame = line_values.at(3);
118+
odom_msg.base_link_frame = line_values.at(4);
119+
odom_msg.x = boost::lexical_cast<double>(s_x.str());
120+
odom_msg.y = boost::lexical_cast<double>(s_y.str());
121+
odom_msg.z = boost::lexical_cast<double>(s_z.str());
122+
odom_msg.qx = boost::lexical_cast<double>(s_qx.str());
123+
odom_msg.qy = boost::lexical_cast<double>(s_qy.str());
124+
odom_msg.qz = boost::lexical_cast<double>(s_qz.str());
125+
odom_msg.qw = boost::lexical_cast<double>(s_qw.str());
126+
graph_vertices.push_back(odom_msg);
127+
}
128+
else
129+
{
130+
ROS_WARN_STREAM("[PointCloudMapper:] Line not processed: " << line);
131+
}
132+
}
133+
134+
// Transform the point clouds
135+
for (unsigned int i=0; i<pointcloud_list_.size(); i++)
136+
{
137+
// Get the current cloud
138+
sensor_msgs::PointCloud2 cloud = pointcloud_list_.at(i);
139+
double cloud_time = cloud.header.stamp.toSec();
140+
141+
// Search the corresponding timestamp in the graph
142+
int idx_graph = -1;
143+
double min_time_diff = graph_vertices[0].timestamp;
144+
for (unsigned int j=0; j<graph_vertices.size(); j++)
145+
{
146+
double time_diff = fabs(graph_vertices[j].timestamp - cloud_time);
147+
if(time_diff < min_time_diff)
148+
{
149+
min_time_diff = time_diff;
150+
idx_graph = j;
151+
}
152+
}
153+
154+
// Found sync?
155+
double eps = 1e-1;
156+
if (min_time_diff < eps)
157+
{
158+
ROS_DEBUG_STREAM("[PointCloudMapper:] Time sync found between pointcloud " <<
159+
i << " and graph vertex " << idx_graph << ".");
160+
161+
// Build the tf
162+
tf::Vector3 t(graph_vertices[idx_graph].x,
163+
graph_vertices[idx_graph].y,
164+
graph_vertices[idx_graph].z);
165+
tf::Quaternion q(graph_vertices[idx_graph].qx,
166+
graph_vertices[idx_graph].qy,
167+
graph_vertices[idx_graph].qz,
168+
graph_vertices[idx_graph].qw);
169+
tf::Transform transform(q, t);
170+
171+
// Transform pointcloud
172+
PointCloud pcl_cloud, transformed_cloud;
173+
pcl::fromROSMsg(pointcloud_list_.at(i), pcl_cloud);
174+
pcl_ros::transformPointCloud(pcl_cloud, transformed_cloud, transform);
175+
176+
// Accumulate points
177+
accumulated_cloud += transformed_cloud;
178+
}
179+
}
180+
181+
// If points...
182+
if(accumulated_cloud.points.size() > 0)
183+
cloud_pub_.publish(accumulated_cloud);
184+
else
185+
ROS_INFO("[PointCloudMapper:] Nothing to add...");
186+
}
187+
}
188+
189+
PointCloud::Ptr filter(PointCloud::Ptr cloud)
190+
{
191+
// NAN and limit filtering
192+
PointCloud::Ptr cloud_filtered_ptr(new PointCloud);
193+
pcl::PassThrough<Point> pass_;
194+
195+
// X-filtering
196+
pass_.setFilterFieldName("x");
197+
pass_.setFilterLimits(x_filter_min_, x_filter_max_);
198+
pass_.setInputCloud(cloud);
199+
pass_.filter(*cloud_filtered_ptr);
200+
201+
// Y-filtering
202+
pass_.setFilterFieldName("y");
203+
pass_.setFilterLimits(y_filter_min_, y_filter_max_);
204+
pass_.setInputCloud(cloud_filtered_ptr);
205+
pass_.filter(*cloud_filtered_ptr);
206+
207+
// Z-filtering
208+
pass_.setFilterFieldName("z");
209+
pass_.setFilterLimits(z_filter_min_, z_filter_max_);
210+
pass_.setInputCloud(cloud);
211+
pass_.filter(*cloud_filtered_ptr);
212+
213+
// Downsampling using voxel grid
214+
pcl::VoxelGrid<Point> grid_;
215+
PointCloud::Ptr cloud_downsampled_ptr(new PointCloud);
216+
double plane_detection_voxel_size_ = voxel_size_;
217+
218+
grid_.setLeafSize(plane_detection_voxel_size_,
219+
plane_detection_voxel_size_,
220+
plane_detection_voxel_size_);
221+
grid_.setDownsampleAllData(true);
222+
grid_.setInputCloud(cloud_filtered_ptr);
223+
grid_.filter(*cloud_downsampled_ptr);
224+
225+
// Statistical outlier removal
226+
pcl::StatisticalOutlierRemoval<Point> sor;
227+
sor.setInputCloud(cloud_downsampled_ptr);
228+
sor.setMeanK(mean_k_);
229+
sor.setStddevMulThresh(std_dev_thresh_);
230+
sor.filter(*cloud_downsampled_ptr);
231+
232+
return cloud_downsampled_ptr;
233+
}
234+
235+
bool fileExists(const char* filename)
236+
{
237+
struct stat info;
238+
int ret = -1;
239+
240+
//get the file attributes
241+
ret = stat(filename, &info);
242+
if(ret == 0)
243+
return true;
244+
else
245+
return false;
246+
}
247+
248+
private:
249+
struct OdomMsg
250+
{
251+
double timestamp;
252+
int id;
253+
std::string fixed_frame;
254+
std::string base_link_frame;
255+
double x;
256+
double y;
257+
double z;
258+
double qx;
259+
double qy;
260+
double qz;
261+
double qw;
262+
};
263+
264+
// Node parameters
265+
std::string graph_vertices_file_;
266+
std::string graph_blocking_file_;
267+
268+
// Filter parameters
269+
double x_filter_min_;
270+
double x_filter_max_;
271+
double y_filter_min_;
272+
double y_filter_max_;
273+
double z_filter_min_;
274+
double z_filter_max_;
275+
double voxel_size_;
276+
double std_dev_thresh_;
277+
int mean_k_;
278+
bool filter_map_;
279+
280+
ros::NodeHandle nh_;
281+
ros::NodeHandle nh_priv_;
282+
283+
ros::Subscriber cloud_sub_;
284+
ros::Publisher cloud_pub_;
285+
286+
ros::WallTimer pub_timer_;
287+
288+
std::vector<sensor_msgs::PointCloud2> pointcloud_list_;
289+
};
290+
291+
int main(int argc, char **argv)
292+
{
293+
ros::init(argc, argv, "cloud_mapper");
294+
PointCloudMapper mapper;
295+
ros::spin();
296+
return 0;
297+
}
298+

0 commit comments

Comments
 (0)