|
| 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