laser_scan_matcher.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011, Ivan Dryanovski, William Morris
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the CCNY Robotics Lab nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /* This package uses Canonical Scan Matcher [1], written by
31  * Andrea Censi
32  *
33  * [1] A. Censi, "An ICP variant using a point-to-line metric"
34  * Proceedings of the IEEE International Conference
35  * on Robotics and Automation (ICRA), 2008
36  */
37 
40 #include <boost/assign.hpp>
41 
42 namespace scan_tools
43 {
44 
46  nh_(nh),
47  nh_private_(nh_private),
48  initialized_(false),
49  received_imu_(false),
50  received_odom_(false),
51  received_vel_(false)
52 {
53  ROS_INFO("Starting LaserScanMatcher");
54 
55  // **** init parameters
56 
57  initParams();
58 
59  // **** state variables
60 
61  f2b_.setIdentity();
63  input_.laser[0] = 0.0;
64  input_.laser[1] = 0.0;
65  input_.laser[2] = 0.0;
66 
67  // Initialize output_ vectors as Null for error-checking
68  output_.cov_x_m = 0;
69  output_.dx_dy1_m = 0;
70  output_.dx_dy2_m = 0;
71 
72  // **** publishers
73 
74  if (publish_pose_)
75  {
76  pose_publisher_ = nh_.advertise<geometry_msgs::Pose2D>(
77  "pose2D", 5);
78  }
79 
81  {
82  pose_stamped_publisher_ = nh_.advertise<geometry_msgs::PoseStamped>(
83  "pose_stamped", 5);
84  }
85 
87  {
88  pose_with_covariance_publisher_ = nh_.advertise<geometry_msgs::PoseWithCovariance>(
89  "pose_with_covariance", 5);
90  }
91 
93  {
94  pose_with_covariance_stamped_publisher_ = nh_.advertise<geometry_msgs::PoseWithCovarianceStamped>(
95  "pose_with_covariance_stamped", 5);
96  }
97 
98  // *** subscribers
99 
100  if (use_cloud_input_)
101  {
103  "cloud", 1, &LaserScanMatcher::cloudCallback, this);
104  }
105  else
106  {
108  "scan", 1, &LaserScanMatcher::scanCallback, this);
109  }
110 
111  if (use_imu_)
112  {
114  "imu/data", 1, &LaserScanMatcher::imuCallback, this);
115  }
116  if (use_odom_)
117  {
119  "odom", 1, &LaserScanMatcher::odomCallback, this);
120  }
121  if (use_vel_)
122  {
123  if (stamped_vel_)
125  "vel", 1, &LaserScanMatcher::velStmpCallback, this);
126  else
128  "vel", 1, &LaserScanMatcher::velCallback, this);
129  }
130 }
131 
133 {
134  ROS_INFO("Destroying LaserScanMatcher");
135 }
136 
138 {
139  if (!nh_private_.getParam ("base_frame", base_frame_))
140  base_frame_ = "base_link";
141  if (!nh_private_.getParam ("fixed_frame", fixed_frame_))
142  fixed_frame_ = "world";
143 
144  // **** input type - laser scan, or point clouds?
145  // if false, will subscribe to LaserScan msgs on /scan.
146  // if true, will subscribe to PointCloud2 msgs on /cloud
147 
148  if (!nh_private_.getParam ("use_cloud_input", use_cloud_input_))
149  use_cloud_input_= false;
150 
151  if (use_cloud_input_)
152  {
153  if (!nh_private_.getParam ("cloud_range_min", cloud_range_min_))
154  cloud_range_min_ = 0.1;
155  if (!nh_private_.getParam ("cloud_range_max", cloud_range_max_))
156  cloud_range_max_ = 50.0;
157  if (!nh_private_.getParam ("cloud_res", cloud_res_))
158  cloud_res_ = 0.05;
159 
162  }
163 
164  // **** keyframe params: when to generate the keyframe scan
165  // if either is set to 0, reduces to frame-to-frame matching
166 
167  if (!nh_private_.getParam ("kf_dist_linear", kf_dist_linear_))
168  kf_dist_linear_ = 0.10;
169  if (!nh_private_.getParam ("kf_dist_angular", kf_dist_angular_))
170  kf_dist_angular_ = 10.0 * (M_PI / 180.0);
171 
173 
174  // **** What predictions are available to speed up the ICP?
175  // 1) imu - [theta] from imu yaw angle - /imu topic
176  // 2) odom - [x, y, theta] from wheel odometry - /odom topic
177  // 3) vel - [x, y, theta] from velocity predictor - see alpha-beta predictors - /vel topic
178  // If more than one is enabled, priority is imu > odom > vel
179 
180  if (!nh_private_.getParam ("use_imu", use_imu_))
181  use_imu_ = true;
182  if (!nh_private_.getParam ("use_odom", use_odom_))
183  use_odom_ = true;
184  if (!nh_private_.getParam ("use_vel", use_vel_))
185  use_vel_ = false;
186 
187  // **** Are velocity input messages stamped?
188  // if false, will subscribe to Twist msgs on /vel
189  // if true, will subscribe to TwistStamped msgs on /vel
190  if (!nh_private_.getParam ("stamped_vel", stamped_vel_))
191  stamped_vel_ = false;
192 
193  // **** How to publish the output?
194  // tf (fixed_frame->base_frame),
195  // pose message (pose of base frame in the fixed frame)
196 
197  if (!nh_private_.getParam ("publish_tf", publish_tf_))
198  publish_tf_ = true;
199  if (!nh_private_.getParam ("publish_pose", publish_pose_))
200  publish_pose_ = true;
201  if (!nh_private_.getParam ("publish_pose_stamped", publish_pose_stamped_))
202  publish_pose_stamped_ = false;
203  if (!nh_private_.getParam ("publish_pose_with_covariance", publish_pose_with_covariance_))
205  if (!nh_private_.getParam ("publish_pose_with_covariance_stamped", publish_pose_with_covariance_stamped_))
207 
208  if (!nh_private_.getParam("position_covariance", position_covariance_))
209  {
210  position_covariance_.resize(3);
211  std::fill(position_covariance_.begin(), position_covariance_.end(), 1e-9);
212  }
213 
214  if (!nh_private_.getParam("orientation_covariance", orientation_covariance_))
215  {
216  orientation_covariance_.resize(3);
217  std::fill(orientation_covariance_.begin(), orientation_covariance_.end(), 1e-9);
218  }
219  // **** CSM parameters - comments copied from algos.h (by Andrea Censi)
220 
221  // Maximum angular displacement between scans
222  if (!nh_private_.getParam ("max_angular_correction_deg", input_.max_angular_correction_deg))
224 
225  // Maximum translation between scans (m)
226  if (!nh_private_.getParam ("max_linear_correction", input_.max_linear_correction))
228 
229  // Maximum ICP cycle iterations
230  if (!nh_private_.getParam ("max_iterations", input_.max_iterations))
231  input_.max_iterations = 10;
232 
233  // A threshold for stopping (m)
234  if (!nh_private_.getParam ("epsilon_xy", input_.epsilon_xy))
235  input_.epsilon_xy = 0.000001;
236 
237  // A threshold for stopping (rad)
238  if (!nh_private_.getParam ("epsilon_theta", input_.epsilon_theta))
239  input_.epsilon_theta = 0.000001;
240 
241  // Maximum distance for a correspondence to be valid
242  if (!nh_private_.getParam ("max_correspondence_dist", input_.max_correspondence_dist))
244 
245  // Noise in the scan (m)
246  if (!nh_private_.getParam ("sigma", input_.sigma))
247  input_.sigma = 0.010;
248 
249  // Use smart tricks for finding correspondences.
250  if (!nh_private_.getParam ("use_corr_tricks", input_.use_corr_tricks))
252 
253  // Restart: Restart if error is over threshold
254  if (!nh_private_.getParam ("restart", input_.restart))
255  input_.restart = 0;
256 
257  // Restart: Threshold for restarting
258  if (!nh_private_.getParam ("restart_threshold_mean_error", input_.restart_threshold_mean_error))
260 
261  // Restart: displacement for restarting. (m)
262  if (!nh_private_.getParam ("restart_dt", input_.restart_dt))
263  input_.restart_dt = 1.0;
264 
265  // Restart: displacement for restarting. (rad)
266  if (!nh_private_.getParam ("restart_dtheta", input_.restart_dtheta))
267  input_.restart_dtheta = 0.1;
268 
269  // Max distance for staying in the same clustering
270  if (!nh_private_.getParam ("clustering_threshold", input_.clustering_threshold))
272 
273  // Number of neighbour rays used to estimate the orientation
274  if (!nh_private_.getParam ("orientation_neighbourhood", input_.orientation_neighbourhood))
276 
277  // If 0, it's vanilla ICP
278  if (!nh_private_.getParam ("use_point_to_line_distance", input_.use_point_to_line_distance))
280 
281  // Discard correspondences based on the angles
282  if (!nh_private_.getParam ("do_alpha_test", input_.do_alpha_test))
283  input_.do_alpha_test = 0;
284 
285  // Discard correspondences based on the angles - threshold angle, in degrees
286  if (!nh_private_.getParam ("do_alpha_test_thresholdDeg", input_.do_alpha_test_thresholdDeg))
288 
289  // Percentage of correspondences to consider: if 0.9,
290  // always discard the top 10% of correspondences with more error
291  if (!nh_private_.getParam ("outliers_maxPerc", input_.outliers_maxPerc))
292  input_.outliers_maxPerc = 0.90;
293 
294  // Parameters describing a simple adaptive algorithm for discarding.
295  // 1) Order the errors.
296  // 2) Choose the percentile according to outliers_adaptive_order.
297  // (if it is 0.7, get the 70% percentile)
298  // 3) Define an adaptive threshold multiplying outliers_adaptive_mult
299  // with the value of the error at the chosen percentile.
300  // 4) Discard correspondences over the threshold.
301  // This is useful to be conservative; yet remove the biggest errors.
302  if (!nh_private_.getParam ("outliers_adaptive_order", input_.outliers_adaptive_order))
304 
305  if (!nh_private_.getParam ("outliers_adaptive_mult", input_.outliers_adaptive_mult))
307 
308  // If you already have a guess of the solution, you can compute the polar angle
309  // of the points of one scan in the new position. If the polar angle is not a monotone
310  // function of the readings index, it means that the surface is not visible in the
311  // next position. If it is not visible, then we don't use it for matching.
312  if (!nh_private_.getParam ("do_visibility_test", input_.do_visibility_test))
314 
315  // no two points in laser_sens can have the same corr.
316  if (!nh_private_.getParam ("outliers_remove_doubles", input_.outliers_remove_doubles))
318 
319  // If 1, computes the covariance of ICP using the method http://purl.org/censi/2006/icpcov
320  if (!nh_private_.getParam ("do_compute_covariance", input_.do_compute_covariance))
322 
323  // Checks that find_correspondences_tricks gives the right answer
324  if (!nh_private_.getParam ("debug_verify_tricks", input_.debug_verify_tricks))
326 
327  // If 1, the field 'true_alpha' (or 'alpha') in the first scan is used to compute the
328  // incidence beta, and the factor (1/cos^2(beta)) used to weight the correspondence.");
329  if (!nh_private_.getParam ("use_ml_weights", input_.use_ml_weights))
331 
332  // If 1, the field 'readings_sigma' in the second scan is used to weight the
333  // correspondence by 1/sigma^2
334  if (!nh_private_.getParam ("use_sigma_weights", input_.use_sigma_weights))
336 }
337 
338 void LaserScanMatcher::imuCallback(const sensor_msgs::Imu::ConstPtr& imu_msg)
339 {
340  boost::mutex::scoped_lock(mutex_);
341  latest_imu_msg_ = *imu_msg;
342  if (!received_imu_)
343  {
344  last_used_imu_msg_ = *imu_msg;
345  received_imu_ = true;
346  }
347 }
348 
349 void LaserScanMatcher::odomCallback(const nav_msgs::Odometry::ConstPtr& odom_msg)
350 {
351  boost::mutex::scoped_lock(mutex_);
352  latest_odom_msg_ = *odom_msg;
353  if (!received_odom_)
354  {
355  last_used_odom_msg_ = *odom_msg;
356  received_odom_ = true;
357  }
358 }
359 
360 void LaserScanMatcher::velCallback(const geometry_msgs::Twist::ConstPtr& twist_msg)
361 {
362  boost::mutex::scoped_lock(mutex_);
363  latest_vel_msg_ = *twist_msg;
364 
365  received_vel_ = true;
366 }
367 
368 void LaserScanMatcher::velStmpCallback(const geometry_msgs::TwistStamped::ConstPtr& twist_msg)
369 {
370  boost::mutex::scoped_lock(mutex_);
371  latest_vel_msg_ = twist_msg->twist;
372 
373  received_vel_ = true;
374 }
375 
376 void LaserScanMatcher::cloudCallback (const PointCloudT::ConstPtr& cloud)
377 {
378  // **** if first scan, cache the tf from base to the scanner
379 
380  std_msgs::Header cloud_header = pcl_conversions::fromPCL(cloud->header);
381 
382  if (!initialized_)
383  {
384  // cache the static tf from base to laser
385  if (!getBaseToLaserTf(cloud_header.frame_id))
386  {
387  ROS_WARN("Skipping scan");
388  return;
389  }
390 
392  last_icp_time_ = cloud_header.stamp;
393  initialized_ = true;
394  }
395 
396  LDP curr_ldp_scan;
397  PointCloudToLDP(cloud, curr_ldp_scan);
398  processScan(curr_ldp_scan, cloud_header.stamp);
399 }
400 
401 void LaserScanMatcher::scanCallback (const sensor_msgs::LaserScan::ConstPtr& scan_msg)
402 {
403  // **** if first scan, cache the tf from base to the scanner
404 
405  if (!initialized_)
406  {
407  createCache(scan_msg); // caches the sin and cos of all angles
408 
409  // cache the static tf from base to laser
410  if (!getBaseToLaserTf(scan_msg->header.frame_id))
411  {
412  ROS_WARN("Skipping scan");
413  return;
414  }
415 
416  laserScanToLDP(scan_msg, prev_ldp_scan_);
417  last_icp_time_ = scan_msg->header.stamp;
418  initialized_ = true;
419  }
420 
421  LDP curr_ldp_scan;
422  laserScanToLDP(scan_msg, curr_ldp_scan);
423  processScan(curr_ldp_scan, scan_msg->header.stamp);
424 }
425 
426 void LaserScanMatcher::processScan(LDP& curr_ldp_scan, const ros::Time& time)
427 {
429 
430  // CSM is used in the following way:
431  // The scans are always in the laser frame
432  // The reference scan (prevLDPcan_) has a pose of [0, 0, 0]
433  // The new scan (currLDPScan) has a pose equal to the movement
434  // of the laser in the laser frame since the last scan
435  // The computed correction is then propagated using the tf machinery
436 
437  prev_ldp_scan_->odometry[0] = 0.0;
438  prev_ldp_scan_->odometry[1] = 0.0;
439  prev_ldp_scan_->odometry[2] = 0.0;
440 
441  prev_ldp_scan_->estimate[0] = 0.0;
442  prev_ldp_scan_->estimate[1] = 0.0;
443  prev_ldp_scan_->estimate[2] = 0.0;
444 
445  prev_ldp_scan_->true_pose[0] = 0.0;
446  prev_ldp_scan_->true_pose[1] = 0.0;
447  prev_ldp_scan_->true_pose[2] = 0.0;
448 
450  input_.laser_sens = curr_ldp_scan;
451 
452  // **** estimated change since last scan
453 
454  double dt = (time - last_icp_time_).toSec();
455  double pr_ch_x, pr_ch_y, pr_ch_a;
456  getPrediction(pr_ch_x, pr_ch_y, pr_ch_a, dt);
457 
458  // the predicted change of the laser's position, in the fixed frame
459 
460  tf::Transform pr_ch;
461  createTfFromXYTheta(pr_ch_x, pr_ch_y, pr_ch_a, pr_ch);
462 
463  // account for the change since the last kf, in the fixed frame
464 
465  pr_ch = pr_ch * (f2b_ * f2b_kf_.inverse());
466 
467  // the predicted change of the laser's position, in the laser frame
468 
469  tf::Transform pr_ch_l;
470  pr_ch_l = laser_to_base_ * f2b_.inverse() * pr_ch * f2b_ * base_to_laser_ ;
471 
472  input_.first_guess[0] = pr_ch_l.getOrigin().getX();
473  input_.first_guess[1] = pr_ch_l.getOrigin().getY();
474  input_.first_guess[2] = tf::getYaw(pr_ch_l.getRotation());
475 
476  // If they are non-Null, free covariance gsl matrices to avoid leaking memory
477  if (output_.cov_x_m)
478  {
479  gsl_matrix_free(output_.cov_x_m);
480  output_.cov_x_m = 0;
481  }
482  if (output_.dx_dy1_m)
483  {
484  gsl_matrix_free(output_.dx_dy1_m);
485  output_.dx_dy1_m = 0;
486  }
487  if (output_.dx_dy2_m)
488  {
489  gsl_matrix_free(output_.dx_dy2_m);
490  output_.dx_dy2_m = 0;
491  }
492 
493  // *** scan match - using point to line icp from CSM
494 
495  sm_icp(&input_, &output_);
496  tf::Transform corr_ch;
497 
498  if (output_.valid)
499  {
500 
501  // the correction of the laser's position, in the laser frame
502  tf::Transform corr_ch_l;
503  createTfFromXYTheta(output_.x[0], output_.x[1], output_.x[2], corr_ch_l);
504 
505  // the correction of the base's position, in the base frame
506  corr_ch = base_to_laser_ * corr_ch_l * laser_to_base_;
507 
508  // update the pose in the world frame
509  f2b_ = f2b_kf_ * corr_ch;
510 
511  // **** publish
512 
513  if (publish_pose_)
514  {
515  // unstamped Pose2D message
516  geometry_msgs::Pose2D::Ptr pose_msg;
517  pose_msg = boost::make_shared<geometry_msgs::Pose2D>();
518  pose_msg->x = f2b_.getOrigin().getX();
519  pose_msg->y = f2b_.getOrigin().getY();
520  pose_msg->theta = tf::getYaw(f2b_.getRotation());
521  pose_publisher_.publish(pose_msg);
522  }
524  {
525  // stamped Pose message
526  geometry_msgs::PoseStamped::Ptr pose_stamped_msg;
527  pose_stamped_msg = boost::make_shared<geometry_msgs::PoseStamped>();
528 
529  pose_stamped_msg->header.stamp = time;
530  pose_stamped_msg->header.frame_id = fixed_frame_;
531 
532  tf::poseTFToMsg(f2b_, pose_stamped_msg->pose);
533 
534  pose_stamped_publisher_.publish(pose_stamped_msg);
535  }
537  {
538  // unstamped PoseWithCovariance message
539  geometry_msgs::PoseWithCovariance::Ptr pose_with_covariance_msg;
540  pose_with_covariance_msg = boost::make_shared<geometry_msgs::PoseWithCovariance>();
541  tf::poseTFToMsg(f2b_, pose_with_covariance_msg->pose);
542 
544  {
545  pose_with_covariance_msg->covariance = boost::assign::list_of
546  (gsl_matrix_get(output_.cov_x_m, 0, 0)) (0) (0) (0) (0) (0)
547  (0) (gsl_matrix_get(output_.cov_x_m, 0, 1)) (0) (0) (0) (0)
548  (0) (0) (static_cast<double>(position_covariance_[2])) (0) (0) (0)
549  (0) (0) (0) (static_cast<double>(orientation_covariance_[0])) (0) (0)
550  (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[1])) (0)
551  (0) (0) (0) (0) (0) (gsl_matrix_get(output_.cov_x_m, 0, 2));
552  }
553  else
554  {
555  pose_with_covariance_msg->covariance = boost::assign::list_of
556  (static_cast<double>(position_covariance_[0])) (0) (0) (0) (0) (0)
557  (0) (static_cast<double>(position_covariance_[1])) (0) (0) (0) (0)
558  (0) (0) (static_cast<double>(position_covariance_[2])) (0) (0) (0)
559  (0) (0) (0) (static_cast<double>(orientation_covariance_[0])) (0) (0)
560  (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[1])) (0)
561  (0) (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[2]));
562  }
563 
564  pose_with_covariance_publisher_.publish(pose_with_covariance_msg);
565  }
567  {
568  // stamped Pose message
569  geometry_msgs::PoseWithCovarianceStamped::Ptr pose_with_covariance_stamped_msg;
570  pose_with_covariance_stamped_msg = boost::make_shared<geometry_msgs::PoseWithCovarianceStamped>();
571 
572  pose_with_covariance_stamped_msg->header.stamp = time;
573  pose_with_covariance_stamped_msg->header.frame_id = fixed_frame_;
574 
575  tf::poseTFToMsg(f2b_, pose_with_covariance_stamped_msg->pose.pose);
576 
578  {
579  pose_with_covariance_stamped_msg->pose.covariance = boost::assign::list_of
580  (gsl_matrix_get(output_.cov_x_m, 0, 0)) (0) (0) (0) (0) (0)
581  (0) (gsl_matrix_get(output_.cov_x_m, 0, 1)) (0) (0) (0) (0)
582  (0) (0) (static_cast<double>(position_covariance_[2])) (0) (0) (0)
583  (0) (0) (0) (static_cast<double>(orientation_covariance_[0])) (0) (0)
584  (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[1])) (0)
585  (0) (0) (0) (0) (0) (gsl_matrix_get(output_.cov_x_m, 0, 2));
586  }
587  else
588  {
589  pose_with_covariance_stamped_msg->pose.covariance = boost::assign::list_of
590  (static_cast<double>(position_covariance_[0])) (0) (0) (0) (0) (0)
591  (0) (static_cast<double>(position_covariance_[1])) (0) (0) (0) (0)
592  (0) (0) (static_cast<double>(position_covariance_[2])) (0) (0) (0)
593  (0) (0) (0) (static_cast<double>(orientation_covariance_[0])) (0) (0)
594  (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[1])) (0)
595  (0) (0) (0) (0) (0) (static_cast<double>(orientation_covariance_[2]));
596  }
597 
598  pose_with_covariance_stamped_publisher_.publish(pose_with_covariance_stamped_msg);
599  }
600 
601  if (publish_tf_)
602  {
603  tf::StampedTransform transform_msg (f2b_, time, fixed_frame_, base_frame_);
604  tf_broadcaster_.sendTransform (transform_msg);
605  }
606  }
607  else
608  {
609  corr_ch.setIdentity();
610  ROS_WARN("Error in scan matching");
611  }
612 
613  // **** swap old and new
614 
615  if (newKeyframeNeeded(corr_ch))
616  {
617  // generate a keyframe
619  prev_ldp_scan_ = curr_ldp_scan;
620  f2b_kf_ = f2b_;
621  }
622  else
623  {
624  ld_free(curr_ldp_scan);
625  }
626 
627  last_icp_time_ = time;
628 
629  // **** statistics
630 
631  double dur = (ros::WallTime::now() - start).toSec() * 1e3;
632  ROS_DEBUG("Scan matcher total duration: %.1f ms", dur);
633 }
634 
636 {
637  if (fabs(tf::getYaw(d.getRotation())) > kf_dist_angular_) return true;
638 
639  double x = d.getOrigin().getX();
640  double y = d.getOrigin().getY();
641  if (x*x + y*y > kf_dist_linear_sq_) return true;
642 
643  return false;
644 }
645 
646 void LaserScanMatcher::PointCloudToLDP(const PointCloudT::ConstPtr& cloud,
647  LDP& ldp)
648 {
649  double max_d2 = cloud_res_ * cloud_res_;
650 
651  PointCloudT cloud_f;
652 
653  cloud_f.points.push_back(cloud->points[0]);
654 
655  for (unsigned int i = 1; i < cloud->points.size(); ++i)
656  {
657  const PointT& pa = cloud_f.points[cloud_f.points.size() - 1];
658  const PointT& pb = cloud->points[i];
659 
660  double dx = pa.x - pb.x;
661  double dy = pa.y - pb.y;
662  double d2 = dx*dx + dy*dy;
663 
664  if (d2 > max_d2)
665  {
666  cloud_f.points.push_back(pb);
667  }
668  }
669 
670  unsigned int n = cloud_f.points.size();
671 
672  ldp = ld_alloc_new(n);
673 
674  for (unsigned int i = 0; i < n; i++)
675  {
676  // calculate position in laser frame
677  if (is_nan(cloud_f.points[i].x) || is_nan(cloud_f.points[i].y))
678  {
679  ROS_WARN("Laser Scan Matcher: Cloud input contains NaN values. \
680  Please use a filtered cloud input.");
681  }
682  else
683  {
684  double r = sqrt(cloud_f.points[i].x * cloud_f.points[i].x +
685  cloud_f.points[i].y * cloud_f.points[i].y);
686 
687  if (r > cloud_range_min_ && r < cloud_range_max_)
688  {
689  ldp->valid[i] = 1;
690  ldp->readings[i] = r;
691  }
692  else
693  {
694  ldp->valid[i] = 0;
695  ldp->readings[i] = -1; // for invalid range
696  }
697  }
698 
699  ldp->theta[i] = atan2(cloud_f.points[i].y, cloud_f.points[i].x);
700  ldp->cluster[i] = -1;
701  }
702 
703  ldp->min_theta = ldp->theta[0];
704  ldp->max_theta = ldp->theta[n-1];
705 
706  ldp->odometry[0] = 0.0;
707  ldp->odometry[1] = 0.0;
708  ldp->odometry[2] = 0.0;
709 
710  ldp->true_pose[0] = 0.0;
711  ldp->true_pose[1] = 0.0;
712  ldp->true_pose[2] = 0.0;
713 }
714 
715 void LaserScanMatcher::laserScanToLDP(const sensor_msgs::LaserScan::ConstPtr& scan_msg,
716  LDP& ldp)
717 {
718  unsigned int n = scan_msg->ranges.size();
719  ldp = ld_alloc_new(n);
720 
721  for (unsigned int i = 0; i < n; i++)
722  {
723  // calculate position in laser frame
724 
725  double r = scan_msg->ranges[i];
726 
727  if (r > scan_msg->range_min && r < scan_msg->range_max)
728  {
729  // fill in laser scan data
730 
731  ldp->valid[i] = 1;
732  ldp->readings[i] = r;
733  }
734  else
735  {
736  ldp->valid[i] = 0;
737  ldp->readings[i] = -1; // for invalid range
738  }
739 
740  ldp->theta[i] = scan_msg->angle_min + i * scan_msg->angle_increment;
741 
742  ldp->cluster[i] = -1;
743  }
744 
745  ldp->min_theta = ldp->theta[0];
746  ldp->max_theta = ldp->theta[n-1];
747 
748  ldp->odometry[0] = 0.0;
749  ldp->odometry[1] = 0.0;
750  ldp->odometry[2] = 0.0;
751 
752  ldp->true_pose[0] = 0.0;
753  ldp->true_pose[1] = 0.0;
754  ldp->true_pose[2] = 0.0;
755 }
756 
757 void LaserScanMatcher::createCache (const sensor_msgs::LaserScan::ConstPtr& scan_msg)
758 {
759  a_cos_.clear();
760  a_sin_.clear();
761 
762  for (unsigned int i = 0; i < scan_msg->ranges.size(); ++i)
763  {
764  double angle = scan_msg->angle_min + i * scan_msg->angle_increment;
765  a_cos_.push_back(cos(angle));
766  a_sin_.push_back(sin(angle));
767  }
768 
769  input_.min_reading = scan_msg->range_min;
770  input_.max_reading = scan_msg->range_max;
771 }
772 
773 bool LaserScanMatcher::getBaseToLaserTf (const std::string& frame_id)
774 {
776 
777  tf::StampedTransform base_to_laser_tf;
778  try
779  {
781  base_frame_, frame_id, t, ros::Duration(1.0));
783  base_frame_, frame_id, t, base_to_laser_tf);
784  }
785  catch (tf::TransformException ex)
786  {
787  ROS_WARN("Could not get initial transform from base to laser frame, %s", ex.what());
788  return false;
789  }
790  base_to_laser_ = base_to_laser_tf;
792 
793  return true;
794 }
795 
796 // returns the predicted change in pose (in fixed frame)
797 // since the last time we did icp
798 void LaserScanMatcher::getPrediction(double& pr_ch_x, double& pr_ch_y,
799  double& pr_ch_a, double dt)
800 {
801  boost::mutex::scoped_lock(mutex_);
802 
803  // **** base case - no input available, use zero-motion model
804  pr_ch_x = 0.0;
805  pr_ch_y = 0.0;
806  pr_ch_a = 0.0;
807 
808  // **** use velocity (for example from ab-filter)
809  if (use_vel_)
810  {
811  pr_ch_x = dt * latest_vel_msg_.linear.x;
812  pr_ch_y = dt * latest_vel_msg_.linear.y;
813  pr_ch_a = dt * latest_vel_msg_.angular.z;
814 
815  if (pr_ch_a >= M_PI) pr_ch_a -= 2.0 * M_PI;
816  else if (pr_ch_a < -M_PI) pr_ch_a += 2.0 * M_PI;
817  }
818 
819  // **** use wheel odometry
820  if (use_odom_ && received_odom_)
821  {
822  pr_ch_x = latest_odom_msg_.pose.pose.position.x -
823  last_used_odom_msg_.pose.pose.position.x;
824 
825  pr_ch_y = latest_odom_msg_.pose.pose.position.y -
826  last_used_odom_msg_.pose.pose.position.y;
827 
828  pr_ch_a = tf::getYaw(latest_odom_msg_.pose.pose.orientation) -
829  tf::getYaw(last_used_odom_msg_.pose.pose.orientation);
830 
831  if (pr_ch_a >= M_PI) pr_ch_a -= 2.0 * M_PI;
832  else if (pr_ch_a < -M_PI) pr_ch_a += 2.0 * M_PI;
833 
835  }
836 
837  // **** use imu
838  if (use_imu_ && received_imu_)
839  {
840  pr_ch_a = tf::getYaw(latest_imu_msg_.orientation) -
841  tf::getYaw(last_used_imu_msg_.orientation);
842 
843  if (pr_ch_a >= M_PI) pr_ch_a -= 2.0 * M_PI;
844  else if (pr_ch_a < -M_PI) pr_ch_a += 2.0 * M_PI;
845 
847  }
848 }
849 
851  double x, double y, double theta, tf::Transform& t)
852 {
853  t.setOrigin(tf::Vector3(x, y, 0.0));
854  tf::Quaternion q;
855  q.setRPY(0.0, 0.0, theta);
856  t.setRotation(q);
857 }
858 
859 } // namespace scan_tools
void velCallback(const geometry_msgs::Twist::ConstPtr &twist_msg)
int do_alpha_test
int *restrict valid
void PointCloudToLDP(const PointCloudT::ConstPtr &cloud, LDP &ldp)
TFSIMD_FORCE_INLINE void setRotation(const Quaternion &q)
double true_pose[3]
void sm_icp(struct sm_params *input, struct sm_result *output)
pcl::PointCloud< PointT > PointCloudT
void publish(const boost::shared_ptr< M > &message) const
std::vector< double > position_covariance_
int outliers_remove_doubles
double max_theta
double min_theta
double odometry[3]
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
ros::Publisher pose_with_covariance_stamped_publisher_
static double getYaw(const Quaternion &bt_q)
int *restrict cluster
int use_ml_weights
#define false
double epsilon_xy
int max_iterations
double max_angular_correction_deg
int orientation_neighbourhood
double *restrict theta
ros::Publisher pose_with_covariance_publisher_
TFSIMD_FORCE_INLINE const tfScalar & getY() const
double clustering_threshold
double max_linear_correction
TFSIMD_FORCE_INLINE const tfScalar & y() const
#define ROS_WARN(...)
void imuCallback(const sensor_msgs::Imu::ConstPtr &imu_msg)
double max_reading
double outliers_adaptive_mult
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
#define M_PI
void odomCallback(const nav_msgs::Odometry::ConstPtr &odom_msg)
void setIdentity()
bool waitForTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration &timeout, const ros::Duration &polling_sleep_duration=ros::Duration(0.01), std::string *error_msg=NULL) const
int use_corr_tricks
double restart_dt
void cloudCallback(const PointCloudT::ConstPtr &cloud)
double sigma
gsl_matrix * dx_dy2_m
void ld_free(LDP ld)
int use_sigma_weights
double restart_threshold_mean_error
void fromPCL(const pcl::uint64_t &pcl_stamp, ros::Time &stamp)
double *restrict readings
double max_correspondence_dist
void createCache(const sensor_msgs::LaserScan::ConstPtr &scan_msg)
double outliers_maxPerc
std::vector< double > a_sin_
void setRPY(const tfScalar &roll, const tfScalar &pitch, const tfScalar &yaw)
#define ROS_INFO(...)
nav_msgs::Odometry last_used_odom_msg_
void sendTransform(const StampedTransform &transform)
LaserScanMatcher(ros::NodeHandle nh, ros::NodeHandle nh_private)
void processScan(LDP &curr_ldp_scan, const ros::Time &time)
TFSIMD_FORCE_INLINE const tfScalar & x() const
Transform inverse() const
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
int do_visibility_test
double do_alpha_test_thresholdDeg
void laserScanToLDP(const sensor_msgs::LaserScan::ConstPtr &scan_msg, LDP &ldp)
void createTfFromXYTheta(double x, double y, double theta, tf::Transform &t)
nav_msgs::Odometry latest_odom_msg_
LDP laser_sens
TFSIMD_FORCE_INLINE Vector3 & getOrigin()
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
double min_reading
double restart_dtheta
double first_guess[3]
double laser[3]
static void poseTFToMsg(const Pose &bt, geometry_msgs::Pose &msg)
std::vector< double > orientation_covariance_
static WallTime now()
void velStmpCallback(const geometry_msgs::TwistStamped::ConstPtr &twist_msg)
Quaternion getRotation() const
double estimate[3]
bool getParam(const std::string &key, std::string &s) const
int is_nan(double v)
tf::TransformBroadcaster tf_broadcaster_
std::vector< double > a_cos_
double x[3]
TFSIMD_FORCE_INLINE const tfScalar & getX() const
static Time now()
int do_compute_covariance
geometry_msgs::Twist latest_vel_msg_
void getPrediction(double &pr_ch_x, double &pr_ch_y, double &pr_ch_a, double dt)
bool getBaseToLaserTf(const std::string &frame_id)
double outliers_adaptive_order
double epsilon_theta
gsl_matrix * cov_x_m
LDP ld_alloc_new(int nrays)
bool newKeyframeNeeded(const tf::Transform &d)
int use_point_to_line_distance
TFSIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
void scanCallback(const sensor_msgs::LaserScan::ConstPtr &scan_msg)
gsl_matrix * dx_dy1_m
int debug_verify_tricks
#define ROS_DEBUG(...)
tf::TransformListener tf_listener_


laser_scan_matcher
Author(s): Ivan Dryanovski , William Morris, Andrea Censi
autogenerated on Mon Jun 10 2019 15:08:40