00001 #define PCL_NO_PRECOMPILE
00002 #include "cloud_accumulator.h"
00003 #include <limits>
00004 #include <pcl_ros/point_cloud.h>
00005 #include <pcl/point_types.h>
00006 #include <pcl_conversions/pcl_conversions.h>
00007 #include <pcl/point_cloud.h>
00008 #include <pcl/visualization/pcl_visualizer.h>
00009 #include <pcl/common/transforms.h>
00010 #include <pcl/filters/passthrough.h>
00011 #include <pcl/filters/voxel_grid.h>
00012 #include <pcl/io/pcd_io.h>
00013 #include <boost/lexical_cast.hpp>
00014
00015 namespace rc
00016 {
00017
00018
00019
00020 inline geometry_msgs::TransformStamped myPoseStampedMsgToTF(const geometry_msgs::PoseStamped & msg)
00021 {
00022 geometry_msgs::TransformStamped bt;
00023 bt.transform.rotation = msg.pose.orientation;
00024 bt.transform.translation.x = msg.pose.position.x;
00025 bt.transform.translation.y = msg.pose.position.y;
00026 bt.transform.translation.z = msg.pose.position.z;
00027 bt.header.stamp = msg.header.stamp;
00028 bt.header.frame_id = msg.header.frame_id;
00029 bt.child_frame_id = "camera";
00030 return bt;
00031 }
00032
00033
00034 inline Eigen::Affine3f toAffine(const geometry_msgs::TransformStamped& tf_stamped)
00035 {
00036 const geometry_msgs::Vector3& t = tf_stamped.transform.translation;
00037 const geometry_msgs::Quaternion& q = tf_stamped.transform.rotation;
00038
00039 Eigen::Affine3f result = Eigen::Affine3f::Identity();
00040 result.translation() << t.x, t.y, t.z;
00041 Eigen::Quaternionf rot(q.w, q.x, q.y, q.z);
00042 result.rotate(rot);
00043 return result;
00044 }
00045
00046
00048 void voxelGridFilter(double size, pointcloud_t::Ptr input, pointcloud_t::Ptr& output)
00049 {
00050 if(size <= 0.0){ output = input; }
00051 else
00052 {
00053 pcl::VoxelGrid<point_t> vgf;
00054 vgf.setInputCloud(input);
00055 vgf.setLeafSize(size, size, size);
00056 vgf.filter(*output);
00057 }
00058 }
00059
00062 pointcloud_t::Ptr distanceFiltered(double min, double max, const pointcloud_t::ConstPtr input)
00063 {
00064 pointcloud_t::Ptr output(new pointcloud_t());
00065 if(min >= max){ *output = *input; }
00066 else
00067 {
00068 pcl::PassThrough<point_t> pass;
00069 pass.setInputCloud(input);
00070 pass.setFilterFieldName("z");
00071 pass.setFilterLimits(min, max);
00072 pass.filter(*output);
00073 }
00074 return output;
00075 }
00076
00077
00078 CloudAccumulator::CloudAccumulator(double voxelgrid_size_display,
00079 double voxelgrid_size_save,
00080 double min_distance,
00081 double max_distance,
00082 std::string output_filename,
00083 bool keep_high_res,
00084 bool start_paused)
00085 : tf_buffer_(ros::Duration(std::numeric_limits<int>::max(), 0)),
00086 num_poses_(0),
00087 display_cloud_(new pointcloud_t()),
00088 voxelgrid_size_display_(voxelgrid_size_display),
00089 voxelgrid_size_save_(voxelgrid_size_save),
00090 min_distance_(min_distance),
00091 max_distance_(max_distance),
00092 output_filename_(output_filename),
00093 keep_high_res_(keep_high_res),
00094 pause_(start_paused),
00095 viewer_("Roboception Cloud Viewer"),
00096 viewer_thread_(boost::ref(viewer_))
00097 {
00098 viewer_.addCloudToViewer(display_cloud_);
00099 ROS_INFO("Distance filter range: %.2fm - %.2fm", min_distance_, max_distance_);
00100 ROS_INFO("Voxel grid filter for display: %.3fm", voxelgrid_size_display_);
00101 ROS_INFO("Voxel grid filter for saving: %.3fm", voxelgrid_size_save_);
00102 }
00103
00104
00105 void CloudAccumulator::pointCloudCallback(const pointcloud_t::ConstPtr& pointcloud)
00106 {
00107 if(pause_){ return; }
00108 static int counter = 0;
00109 ++counter;
00110 ROS_INFO_THROTTLE(10, "Received %d point cloud%s", counter, counter == 1 ? "" : "s");
00111 double timestamp = pointcloud->header.stamp * 1e-6;
00112 ROS_DEBUG("Storing cloud (%.3f)", timestamp);
00113
00114 pointcloud_t::Ptr cropped_cloud = distanceFiltered(min_distance_, max_distance_, pointcloud);
00115 if(keep_high_res_) { clouds_.push_back(cropped_cloud); }
00116
00117 const Eigen::Affine3f transformation = lookupTransform(timestamp);
00118 if(transformation.matrix()(0,0) == transformation.matrix()(0,0))
00119 {
00120 pointcloud_t::Ptr transformed_cloud(new pointcloud_t());
00121 pcl::transformPointCloud(*cropped_cloud, *transformed_cloud, transformation);
00122
00123 boost::mutex::scoped_lock guard(display_cloud_mutex_);
00124 *transformed_cloud += *display_cloud_;
00125
00126 voxelGridFilter(voxelgrid_size_display_, transformed_cloud, display_cloud_);
00127 viewer_.updateCloudInViewer(display_cloud_);
00128 ROS_INFO_ONCE("Change view to start displaying the clouds.");
00129 }
00130 else if(num_poses_ == 0)
00131 {
00132 ROS_INFO_THROTTLE(5,"Cannot transform current point cloud: No poses were received yet. "
00133 "Is Stereo INS or SLAM running?");
00134 }
00135 else {
00136 ROS_INFO_THROTTLE(5,"Cannot transform current point cloud: Pose not (yet) received\n"
00137 "Current cloud stamp: %.3f\n"
00138 "Latest pose stamp: %.3f", timestamp, last_pose_stamp_.toSec());
00139 }
00140 }
00141
00142
00143 void CloudAccumulator::trajectoryCallback(const nav_msgs::PathConstPtr& path)
00144 {
00145 ROS_INFO("Received Trajectory with %zu poses", path->poses.size());
00146 boost::mutex::scoped_lock guard(tf_buffer_mutex_);
00147 tf_buffer_.clear();
00148 num_poses_ = path->poses.size();
00149 for(size_t i = 0; i < path->poses.size(); ++i)
00150
00151 {
00152 tf_buffer_.setTransform(myPoseStampedMsgToTF(path->poses[i]), "trajectory");
00153 }
00154 }
00155
00156
00157 void CloudAccumulator::poseCallback(const geometry_msgs::PoseStampedConstPtr& current_pose)
00158 {
00159 if(pause_){ return; }
00160 ROS_INFO_ONCE("Received first live pose");
00161 boost::mutex::scoped_lock guard(tf_buffer_mutex_);
00162 tf_buffer_.setTransform(myPoseStampedMsgToTF(*current_pose), "current_pose");
00163 last_pose_stamp_ = current_pose->header.stamp;
00164 ++num_poses_;
00165 }
00166
00167
00168 Eigen::Affine3f CloudAccumulator::lookupTransform(double timestamp)
00169 {
00170 ros::Time rosstamp; rosstamp.fromSec(timestamp);
00171
00172 try
00173 {
00174 ros::Time endtime = ros::Time::now() + ros::Duration(0.25);
00175 do
00176 {
00177 {
00178 boost::mutex::scoped_lock guard(tf_buffer_mutex_);
00179 if(tf_buffer_.canTransform("world", "camera", rosstamp))
00180 {
00181 geometry_msgs::TransformStamped pose;
00182 Eigen::Affine3f result = toAffine(tf_buffer_.lookupTransform("world", "camera", rosstamp));
00183 return result;
00184 }
00185 }
00186 usleep(10000);
00187 }
00188 while(endtime > ros::Time::now());
00189 }
00190 catch(tf2::LookupException ex) { ROS_WARN("%s", ex.what()); }
00191 catch(tf2::ExtrapolationException ex) { ROS_WARN("%s", ex.what()); }
00192
00193 Eigen::Affine3f invalid_result;
00194 invalid_result.matrix().setConstant(std::numeric_limits<double>::quiet_NaN());
00195 return invalid_result;
00196 }
00197
00198
00199 pointcloud_t::Ptr CloudAccumulator::rebuildCloud()
00200 {
00201 ROS_INFO("(Re-)merging all stored clouds with latest poses.");
00202 int counter = 0;
00203 pointcloud_t::Ptr merged(new pointcloud_t());
00204 for(size_t i = 0; i < clouds_.size(); ++i)
00205
00206 {
00207 double timestamp = clouds_[i]->header.stamp * 1e-6;
00208 const Eigen::Affine3f transformation = lookupTransform(timestamp);
00209 if(transformation.matrix()(0,0) == transformation.matrix()(0,0))
00210 {
00211 ++counter;
00212 pointcloud_t transformed_cloud;
00213 pcl::transformPointCloud(*clouds_[i], transformed_cloud, transformation);
00214 *merged += transformed_cloud;
00215 if(counter % 10 == 0){ ROS_INFO("Merged %d of %zu clouds.", counter, clouds_.size()); }
00216 }
00217 else { ROS_DEBUG("Skipped cloud at %.3f in merge, cannot get its position", timestamp); }
00218 }
00219 ROS_INFO("Merged %d/%zu clouds.", counter, clouds_.size());
00220
00221 pointcloud_t::Ptr voxel_filtered(new pointcloud_t());
00222 voxelGridFilter(voxelgrid_size_save_, merged, voxel_filtered);
00223 return voxel_filtered;
00224 }
00225
00226
00227 bool CloudAccumulator::saveCloud(std_srvs::Empty::Request &req, std_srvs::Empty::Request &resp)
00228 {
00229 if(keep_high_res_)
00230 {
00231 pointcloud_t::Ptr merged = rebuildCloud();
00232 pcl::io::savePCDFileASCII(output_filename_, *merged);
00233
00234
00235
00236 boost::mutex::scoped_lock guard(display_cloud_mutex_);
00237 display_cloud_ = merged;
00238 viewer_.addCloudToViewer(display_cloud_);
00239 }
00240 else { pcl::io::savePCDFileASCII(output_filename_, *display_cloud_); }
00241
00242 ROS_INFO("Saved point cloud to %s", output_filename_.c_str());
00243 return true;
00244 }
00245
00246
00247 bool CloudAccumulator::togglePause(std_srvs::Empty::Request &req, std_srvs::Empty::Request &resp)
00248 {
00249 pause_ = !pause_;
00250 return true;
00251 }
00252
00253 }