HumanoidLocalization.cpp
Go to the documentation of this file.
1 /*
2  * 6D localization for humanoid robots
3  *
4  * Copyright 2009-2012 Armin Hornung, University of Freiburg
5  * http://www.ros.org/wiki/humanoid_localization
6  *
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
22 #include <iostream>
23 #include <pcl/keypoints/uniform_sampling.h>
24 
25 #include <pcl_ros/transforms.h>
26 
27 #if PCL_VERSION_COMPARE(>=,1,7,0)
29 #endif
30 
31 // simple timing benchmark output
32 #define _BENCH_TIME 0
33 
34 namespace humanoid_localization{
36 :
37 m_rngEngine(randomSeed),
38 m_rngNormal(m_rngEngine, NormalDistributionT(0.0, 1.0)),
39 m_rngUniform(m_rngEngine, UniformDistributionT(0.0, 1.0)),
40 m_nh(),m_privateNh("~"),
41 m_odomFrameId("odom"), m_targetFrameId("odom"), m_baseFrameId("torso"), m_baseFootprintId("base_footprint"), m_globalFrameId("map"),
42 m_useRaycasting(true), m_initFromTruepose(false), m_numParticles(500),
43 m_sensorSampleDist(0.2),
44 m_nEffFactor(1.0), m_minParticleWeight(0.0),
45 m_bestParticleIdx(-1), m_lastIMUMsgBuffer(5),
46 m_bestParticleAsMean(true),
47 m_receivedSensorData(false), m_initialized(false), m_initGlobal(false), m_paused(false),
48 m_syncedTruepose(false),
49 m_observationThresholdTrans(0.1), m_observationThresholdRot(M_PI/6),
50 m_observationThresholdHeadYawRot(0.5), m_observationThresholdHeadPitchRot(0.3),
51 m_temporalSamplingRange(0.1), m_transformTolerance(0.1),
52 m_groundFilterPointCloud(true), m_groundFilterDistance(0.04),
53 m_groundFilterAngle(0.15), m_groundFilterPlaneDistance(0.07),
54 m_sensorSampleDistGroundFactor(3),
55 m_headYawRotationLastScan(0.0), m_headPitchRotationLastScan(0.0),
56 m_useIMU(false),
57 m_constrainMotionZ (false), m_constrainMotionRP(false), m_useTimer(false), m_timerPeriod(0.1)
58 {
59 
61  // raycasting or endpoint model?
63 
64  m_privateNh.param("odom_frame_id", m_odomFrameId, m_odomFrameId);
65  m_privateNh.param("target_frame_id", m_targetFrameId, m_targetFrameId);
66  m_privateNh.param("base_frame_id", m_baseFrameId, m_baseFrameId);
68  m_privateNh.param("global_frame_id", m_globalFrameId, m_globalFrameId);
70  m_privateNh.param("init_global", m_initGlobal, m_initGlobal);
73  m_privateNh.param("neff_factor", m_nEffFactor, m_nEffFactor);
75 
76  m_privateNh.param("initial_pose/x", m_initPose(0), 0.0);
77  m_privateNh.param("initial_pose/y", m_initPose(1), 0.0);
78  m_privateNh.param("initial_pose/z", m_initPose(2), 0.32); // hip height when standing
79  m_privateNh.param("initial_pose/roll", m_initPose(3), 0.0);
80  m_privateNh.param("initial_pose/pitch", m_initPose(4), 0.0);
81  m_privateNh.param("initial_pose/yaw", m_initPose(5), 0.0);
82  m_privateNh.param("initial_pose_real_zrp", m_initPoseRealZRP, false);
83 
84  m_privateNh.param("initial_std/x", m_initNoiseStd(0), 0.1); // 0.1
85  m_privateNh.param("initial_std/y", m_initNoiseStd(1), 0.1); // 0.1
86  m_privateNh.param("initial_std/z", m_initNoiseStd(2), 0.02); // 0.02
87  m_privateNh.param("initial_std/roll", m_initNoiseStd(3), 0.04); // 0.04
88  m_privateNh.param("initial_std/pitch", m_initNoiseStd(4), 0.04); // 0.04
89  m_privateNh.param("initial_std_yaw", m_initNoiseStd(5), M_PI/12); // M_PI/12
90 
91  if (m_privateNh.hasParam("num_sensor_beams"))
92  ROS_WARN("Parameter \"num_sensor_beams\" is no longer used, use \"sensor_sampling_dist\" instead");
93 
94  // laser observation model parameters:
95  m_privateNh.param("sensor_sampling_dist", m_sensorSampleDist, m_sensorSampleDist);
96  m_privateNh.param("max_range", m_filterMaxRange, 30.0);
97  m_privateNh.param("min_range", m_filterMinRange, 0.05);
98  ROS_DEBUG("Using a range filter of %f to %f", m_filterMinRange, m_filterMaxRange);
99 
106 
107  m_privateNh.param("use_imu", m_useIMU, m_useIMU);
108  m_privateNh.param("constrain_motion_z", m_constrainMotionZ, m_constrainMotionZ);
109  m_privateNh.param("constrain_motion_rp", m_constrainMotionRP, m_constrainMotionRP);
110 
111  // point cloud observation model parameters
114  m_privateNh.param("ground_filter_angle", m_groundFilterAngle, m_groundFilterAngle);
116  m_privateNh.param("sensor_sampling_dist_ground_factor", m_sensorSampleDistGroundFactor, m_sensorSampleDistGroundFactor);
117 
118  m_privateNh.param("use_timer", m_useTimer, m_useTimer);
119  m_privateNh.param("timer_period", m_timerPeriod, m_timerPeriod);
120 
121  // motion model parameters
122 
124 
125  if (m_useRaycasting){
128  } else{
129 #ifndef SKIP_ENDPOINT_MODEL
130  //m_mapModel = boost::shared_ptr<MapModel>(new DistanceMap(&m_privateNh));
133 #else
134  ROS_FATAL("EndpointModel not compiled due to missing dynamicEDT3D");
135  exit(-1);
136 #endif
137  }
138 
139 
140  m_particles.resize(m_numParticles);
141  m_poseArray.poses.resize(m_numParticles);
142  m_poseArray.header.frame_id = m_globalFrameId;
144 
145 
146  // publishers can be advertised first, before needed:
147  m_posePub = m_nh.advertise<geometry_msgs::PoseWithCovarianceStamped>("pose", 10);
148  m_poseEvalPub = m_nh.advertise<geometry_msgs::PoseWithCovarianceStamped>("pose_eval", 10);
149  m_poseOdomPub = m_privateNh.advertise<geometry_msgs::PoseStamped>("pose_odom_sync", 10);
150  m_poseArrayPub = m_privateNh.advertise<geometry_msgs::PoseArray>("particlecloud", 10);
151  m_bestPosePub = m_privateNh.advertise<geometry_msgs::PoseArray>("best_particle", 10);
152  m_nEffPub = m_privateNh.advertise<std_msgs::Float32>("n_eff", 10);
153  m_filteredPointCloudPub = m_privateNh.advertise<sensor_msgs::PointCloud2>("filtered_cloud", 1);
154 
155 
156  //TODO Propagate particles independent of sensor callback
157  reset();
158 
159  // ROS subscriptions last:
161 
162  // subscription on laser, tf message filter
165  m_laserFilter->registerCallback(boost::bind(&HumanoidLocalization::laserCallback, this, _1));
166 
167  // subscription on point cloud, tf message filter
170  m_pointCloudFilter->registerCallback(boost::bind(&HumanoidLocalization::pointCloudCallback, this, _1));
171 
172  // subscription on init pose, tf message filter
175  m_initPoseFilter->registerCallback(boost::bind(&HumanoidLocalization::initPoseCallback, this, _1));
176 
177 
181 
182  if (m_useIMU)
183  m_imuSub = m_nh.subscribe("imu", 5, &HumanoidLocalization::imuCallback, this);
184  if (m_useTimer)
185  {
187  ROS_INFO("Using timer with a period of %4f s", m_timerPeriod);
188  }
189 
190  ROS_INFO("NaoLocalization initialized with %d particles.", m_numParticles);
191 }
192 
194 
195  delete m_laserFilter;
196  delete m_laserSub;
197 
198  delete m_pointCloudFilter;
199  delete m_pointCloudSub;
200 
201  delete m_initPoseFilter;
202  delete m_initPoseSub;
203 
204 }
205 
207  ros::Time transformExpiration = e.current_real + ros::Duration(m_transformTolerance);
208  tf::StampedTransform tmp_tf_stamped(m_latest_transform, transformExpiration, m_globalFrameId, m_targetFrameId);
209  m_tfBroadcaster.sendTransform(tmp_tf_stamped);
210 }
211 
212 
214 
215 #if defined(_BENCH_TIME)
216  ros::WallTime startTime = ros::WallTime::now();
217 #endif
218 
219  if (m_initGlobal){
220  this->initGlobal();
221  } else {
222  geometry_msgs::PoseWithCovarianceStampedPtr posePtr(new geometry_msgs::PoseWithCovarianceStamped());
223 
224  if (m_initFromTruepose){ // useful for evaluation, when ground truth available:
225  geometry_msgs::PoseStamped truePose;
226  tf::Stamped<tf::Pose> truePoseTF;
227  tf::Stamped<tf::Pose> ident (tf::Transform(tf::createIdentityQuaternion(), tf::Vector3(0,0,0)), ros::Time::now(), "torso_real"); // TODO: param
228 
229  ros::Time lookupTime = ros::Time::now();
230  while(m_nh.ok() && !m_tfListener.waitForTransform(m_globalFrameId, ident.frame_id_, lookupTime, ros::Duration(1.0))){
231  ROS_WARN("Waiting for transform %s --> %s for ground truth initialization failed, trying again...", m_globalFrameId.c_str(), ident.frame_id_.c_str());
232  lookupTime = ros::Time::now();
233  }
234  ident.stamp_ = lookupTime;
235 
236  m_tfListener.transformPose(m_globalFrameId, ident, truePoseTF);
237  tf::poseStampedTFToMsg(truePoseTF, truePose);
238  tf::poseTFToMsg(truePoseTF, posePtr->pose.pose);
239  posePtr->header = truePose.header;
240 
241 
242  // initial covariance acc. to params
243  for(int j=0; j < 6; ++j){
244  for (int i = 0; i < 6; ++i){
245  if (i == j)
246  posePtr->pose.covariance.at(i*6 +j) = m_initNoiseStd(i) * m_initNoiseStd(i);
247  else
248  posePtr->pose.covariance.at(i*6 +j) = 0.0;
249  }
250  }
251 
252  } else{
253  posePtr.reset(new geometry_msgs::PoseWithCovarianceStamped());
254  for (int i = 0; i < 6; ++i){
255  posePtr->pose.covariance.at(i*6 +i) = m_initNoiseStd(i) * m_initNoiseStd(i);
256  }
257 
258  double roll, pitch, z;
259  initZRP(z, roll, pitch);
260 
261 
262 
263  posePtr->pose.pose.position.x = m_initPose(0);
264  posePtr->pose.pose.position.y = m_initPose(1);
265  posePtr->pose.pose.position.z = z;
266  tf::Quaternion quat;
267  quat.setRPY(roll, pitch, m_initPose(5));
268  tf::quaternionTFToMsg(quat, posePtr->pose.pose.orientation);
269 
270  }
271 
272  this->initPoseCallback(posePtr);
273 
274  }
275 
276 
277 
278 
279 
280 #if defined(_BENCH_TIME)
281  double dt = (ros::WallTime::now() - startTime).toSec();
282  ROS_INFO_STREAM("Initialization of "<< m_numParticles << " particles took "
283  << dt << "s (="<<dt/m_numParticles<<"s/particle)");
284 #endif
285 
286 
287 }
288 
289 
290 void HumanoidLocalization::initZRP(double& z, double& roll, double& pitch){
291  if(m_initPoseRealZRP) {
292  // Get latest pose height
293  tf::Stamped<tf::Pose> lastOdomPose;
294  double poseHeight;
295  if(m_motionModel->getLastOdomPose(lastOdomPose) &&
296  lookupPoseHeight(lastOdomPose.stamp_, poseHeight)) {
297  z = poseHeight;
298  } else {
299  ROS_WARN("Could not determine current pose height, falling back to init_pose_z");
300  z = m_initPose(2);
301  }
302 
303  // Get latest roll and pitch
304  if(!m_lastIMUMsgBuffer.empty()) {
305  getRP(m_lastIMUMsgBuffer.back().orientation, roll, pitch);
306  } else {
307  ROS_WARN("Could not determine current roll and pitch, falling back to init_pose_{roll,pitch}");
308  roll = m_initPose(3);
309  pitch = m_initPose(4);
310  }
311  } else {
312  // Use pose height, roll and pitch from init_pose_{z,roll,pitch} parameters
313  z = m_initPose(2);
314  roll = m_initPose(3);
315  pitch = m_initPose(4);
316  }
317 
318 
319 }
320 void HumanoidLocalization::laserCallback(const sensor_msgs::LaserScanConstPtr& msg){
321  ROS_DEBUG("Laser received (time: %f)", msg->header.stamp.toSec());
322 
323  if (!m_initialized){
324  ROS_WARN("Localization not initialized yet, skipping laser callback.");
325  return;
326  }
327 
328  double timediff = (msg->header.stamp - m_lastLaserTime).toSec();
329  if (m_receivedSensorData && timediff < 0){
330  ROS_WARN("Ignoring received laser data that is %f s older than previous data!", timediff);
331  return;
332  }
333 
334 
336  tf::Stamped<tf::Pose> odomPose;
337  // check if odometry available, skip scan if not.
338  if (!m_motionModel->lookupOdomPose(msg->header.stamp, odomPose))
339  return;
340 
341 
342  bool sensor_integrated = false;
343  if (!m_paused && (!m_receivedSensorData || isAboveMotionThreshold(odomPose))) {
344 
345  // convert laser to point cloud first:
346  PointCloud pc_filtered;
347  std::vector<float> laserRangesSparse;
348  prepareLaserPointCloud(msg, pc_filtered, laserRangesSparse);
349 
350  sensor_integrated = localizeWithMeasurement(pc_filtered, laserRangesSparse, msg->range_max);
351 
352  }
353 
354  if(!sensor_integrated){ // no laser integration: propagate particles forward by full interval
355 
356  // relative odom transform to last odomPose
357  tf::Transform odomTransform = m_motionModel->computeOdomTransform(odomPose);
358  m_motionModel->applyOdomTransform(m_particles, odomTransform);
359  constrainMotion(odomPose);
360  }
361  else
362  {
363  m_lastLocalizedPose = odomPose;
364  }
365 
366  m_motionModel->storeOdomPose(odomPose);
367  publishPoseEstimate(msg->header.stamp, sensor_integrated);
368  m_lastLaserTime = msg->header.stamp;
369 }
370 
372  // skip if nothing to do:
374  return;
375 
376  // reset z according to current odomPose:
377  double z = odomPose.getOrigin().getZ();
378  double odomRoll, odomPitch, uselessYaw;
379  odomPose.getBasis().getRPY(odomRoll, odomPitch, uselessYaw);
380 
381 #pragma omp parallel for
382  for (unsigned i=0; i < m_particles.size(); ++i){
383  if (m_constrainMotionZ){
384  tf::Vector3 pos = m_particles[i].pose.getOrigin();
385  double floor_z = m_mapModel->getFloorHeight(m_particles[i].pose);
386  pos.setZ(z+floor_z);
387  m_particles[i].pose.setOrigin(pos);
388  }
389 
390  if (m_constrainMotionRP){
391  double yaw = tf::getYaw(m_particles[i].pose.getRotation());
392  m_particles[i].pose.setRotation(tf::createQuaternionFromRPY(odomRoll, odomPitch, yaw));
393 
394  }
395  }
396 }
397 
399  tf::Transform odomTransform = m_lastLocalizedPose.inverse() * odomPose;
400 
401  double yaw, pitch, roll;
402  odomTransform.getBasis().getRPY(roll, pitch, yaw);
403 
404  return (odomTransform.getOrigin().length() >= m_observationThresholdTrans
405  || std::abs(yaw) >= m_observationThresholdRot);
406 }
407 
408 bool HumanoidLocalization::localizeWithMeasurement(const PointCloud& pc_filtered, const std::vector<float>& ranges, double max_range){
409  ros::WallTime startTime = ros::WallTime::now();
410 #if PCL_VERSION_COMPARE(>=,1,7,0)
411  ros::Time t = pcl_conversions::fromPCL(pc_filtered.header).stamp;
412 #else
413  ros::Time t = pc_filtered.header.stamp;
414 #endif
415  // apply motion model with temporal sampling:
416  m_motionModel->applyOdomTransformTemporal(m_particles, t, m_temporalSamplingRange);
417 
418  // constrain to ground plane, if desired:
420  if (!m_motionModel->lookupOdomPose(t, odomPose))
421  return false;
422  constrainMotion(odomPose);
423 
424  // transformation from torso frame to sensor
425  // this takes the latest tf, assumes that torso to sensor did not change over temp. sampling!
426  tf::StampedTransform localSensorFrame;
427  if (!m_motionModel->lookupLocalTransform(pc_filtered.header.frame_id, t, localSensorFrame))
428  return false;
429 
430  tf::Transform torsoToSensor(localSensorFrame.inverse());
431 
432 //### Particles in log-form from here...
433  toLogForm();
434 
435  // skip pose integration if z, roll and pitch constrained to floor by odometry
437  bool imuMsgOk = false;
438  double angleX, angleY;
439  if(m_useIMU) {
440  ros::Time imuStamp;
441  imuMsgOk = getImuMsg(t, imuStamp, angleX, angleY);
442  } else {
443  tf::Stamped<tf::Pose> lastOdomPose;
444  if(m_motionModel->lookupOdomPose(t, lastOdomPose)) {
445  double dropyaw;
446  lastOdomPose.getBasis().getRPY(angleX, angleY, dropyaw);
447  imuMsgOk = true;
448  }
449  }
450 
451  tf::StampedTransform footprintToTorso;
452  // integrated pose (z, roll, pitch) meas. only if data OK:
453  if(imuMsgOk) {
454  if (!m_motionModel->lookupLocalTransform(m_baseFootprintId, t, footprintToTorso)) {
455  ROS_WARN("Could not obtain pose height in localization, skipping Pose integration");
456  } else {
457  m_observationModel->integratePoseMeasurement(m_particles, angleX, angleY, footprintToTorso);
458  }
459  } else {
460  ROS_WARN("Could not obtain roll and pitch measurement, skipping Pose integration");
461  }
462  }
463 
464  m_filteredPointCloudPub.publish(pc_filtered);
465  m_observationModel->integrateMeasurement(m_particles, pc_filtered, ranges, max_range, torsoToSensor);
466 
467  // TODO: verify poses before measurements, ignore particles then
468  m_mapModel->verifyPoses(m_particles);
469 
470  // normalize weights and transform back from log:
472  //### Particles back in regular form now
473 
474  double nEffParticles = nEff();
475 
476  std_msgs::Float32 nEffMsg;
477  nEffMsg.data = nEffParticles;
478  m_nEffPub.publish(nEffMsg);
479 
480  if (nEffParticles <= m_nEffFactor*m_particles.size()){ // selective resampling
481  ROS_INFO("Resampling, nEff=%f, numParticles=%zd", nEffParticles, m_particles.size());
482  resample();
483  } else {
484  ROS_INFO("Skipped resampling, nEff=%f, numParticles=%zd", nEffParticles, m_particles.size());
485  }
486 
487  m_receivedSensorData = true;
488 
489  double dt = (ros::WallTime::now() - startTime).toSec();
490  ROS_INFO_STREAM("Observations for "<< m_numParticles << " particles took "
491  << dt << "s (="<<dt/m_numParticles<<"s/particle)");
492 
493  return true;
494 }
495 
496 void HumanoidLocalization::prepareLaserPointCloud(const sensor_msgs::LaserScanConstPtr& laser, PointCloud& pc, std::vector<float>& ranges) const{
497  unsigned numBeams = laser->ranges.size();
498  // skip every n-th scan:
499  //unsigned step = computeBeamStep(numBeams);
500  // build complete pointcloud:
501  unsigned step = 1;
502 
503 
504 
505  // prepare laser message:
506  unsigned int numBeamsSkipped = 0;
507 
508  // range_min of laser is also used to filter out wrong messages:
509  double laserMin = std::max(double(laser->range_min), m_filterMinRange);
510 
511  // (range_max readings stay, will be used in the sensor model)
512 
513  ranges.reserve(50);
514 
515  // build a point cloud
516 #if PCL_VERSION_COMPARE(>=,1,7,0)
517  pcl_conversions::toPCL(laser->header, pc.header);
518 #else
519  pc.header = laser->header;
520 #endif
521  pc.points.reserve(50);
522  for (unsigned beam_idx = 0; beam_idx < numBeams; beam_idx+= step){
523  float range = laser->ranges[beam_idx];
524  if (range >= laserMin && range <= m_filterMaxRange){
525  double laserAngle = laser->angle_min + beam_idx * laser->angle_increment;
526  tf::Transform laserAngleRotation(tf::Quaternion(tf::Vector3(0.0, 0.0, 1.0), laserAngle));
527  tf::Vector3 laserEndpointTrans(range, 0.0, 0.0);
528  tf::Vector3 pt(laserAngleRotation * laserEndpointTrans);
529 
530  pc.points.push_back(pcl::PointXYZ(pt.x(), pt.y(), pt.z()));
531  ranges.push_back(range);
532 
533  } else{
534  numBeamsSkipped++;
535  }
536 
537  }
538  pc.height = 1;
539  pc.width = pc.points.size();
540  pc.is_dense = true;
541 
542  // uniform sampling:
543  pcl::UniformSampling<pcl::PointXYZ> uniformSampling;
544  pcl::PointCloud<pcl::PointXYZ>::Ptr cloudPtr;
545  cloudPtr.reset(new pcl::PointCloud<pcl::PointXYZ> (pc));
546  uniformSampling.setInputCloud(cloudPtr);
547  uniformSampling.setRadiusSearch(m_sensorSampleDist);
548  pcl::PointCloud<int> sampledIndices;
549  uniformSampling.compute(sampledIndices);
550  pcl::copyPointCloud(*cloudPtr, sampledIndices.points, pc);
551  // adjust "ranges" array to contain the same points:
552  std::vector<float> rangesSparse;
553  rangesSparse.resize(sampledIndices.size());
554  for (size_t i = 0; i < rangesSparse.size(); ++i){
555  rangesSparse[i] = ranges[sampledIndices.points[i]];
556  }
557  ranges = rangesSparse;
558  ROS_INFO("Laser PointCloud subsampled: %zu from %zu (%u out of valid range)", pc.size(), cloudPtr->size(), numBeamsSkipped);
559 }
560 
561 int HumanoidLocalization::filterUniform(const PointCloud & cloud_in, PointCloud & cloud_out, int numSamples) const{
562  int numPoints = static_cast<int>(cloud_in.size() );
563  numSamples = std::min( numSamples, numPoints);
564  std::vector<unsigned int> indices;
565  indices.reserve( numPoints );
566  for (int i=0; i<numPoints; ++i)
567  indices.push_back(i);
568  random_shuffle ( indices.begin(), indices.end());
569 
570  cloud_out.reserve( cloud_out.size() + numSamples );
571  for ( int i = 0; i < numSamples; ++i)
572  {
573  cloud_out.push_back( cloud_in.at(indices[i]));
574  }
575  return numSamples;
576 }
577 
578 
579 
580 void HumanoidLocalization::filterGroundPlane(const PointCloud& pc, PointCloud& ground, PointCloud& nonground, double groundFilterDistance, double groundFilterAngle, double groundFilterPlaneDistance){
581  ground.header = pc.header;
582  nonground.header = pc.header;
583 
584  if (pc.size() < 50){
585  ROS_WARN("Pointcloud in HumanoidLocalization::filterGroundPlane too small, skipping ground plane extraction");
586  nonground = pc;
587  } else {
588  // plane detection for ground plane removal:
589  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
590  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
591 
592  // Create the segmentation object and set up:
593  pcl::SACSegmentation<pcl::PointXYZ> seg;
594  seg.setOptimizeCoefficients (true);
595  // TODO: maybe a filtering based on the surface normals might be more robust / accurate?
596  seg.setModelType(pcl::SACMODEL_PERPENDICULAR_PLANE);
597  seg.setMethodType(pcl::SAC_RANSAC);
598  seg.setMaxIterations(200);
599  seg.setDistanceThreshold (groundFilterDistance);
600  seg.setAxis(Eigen::Vector3f(0,0,1));
601  seg.setEpsAngle(groundFilterAngle);
602 
603 
604  PointCloud cloud_filtered(pc);
605  // Create the filtering object
606  pcl::ExtractIndices<pcl::PointXYZ> extract;
607  bool groundPlaneFound = false;
608 
609  while(cloud_filtered.size() > 10 && !groundPlaneFound){
610  seg.setInputCloud(cloud_filtered.makeShared());
611  seg.segment (*inliers, *coefficients);
612  if (inliers->indices.size () == 0){
613  ROS_INFO("PCL segmentation did not find any plane.");
614 
615  break;
616  }
617 
618  extract.setInputCloud(cloud_filtered.makeShared());
619  extract.setIndices(inliers);
620 
621  if (std::abs(coefficients->values.at(3)) < groundFilterPlaneDistance){
622  ROS_DEBUG("Ground plane found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(),
623  coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3));
624  extract.setNegative (false);
625  extract.filter (ground);
626 
627  // remove ground points from full pointcloud:
628  // workaround for PCL bug:
629  if(inliers->indices.size() != cloud_filtered.size()){
630  extract.setNegative(true);
631  PointCloud cloud_out;
632  extract.filter(cloud_out);
633  nonground += cloud_out;
634  cloud_filtered = cloud_out;
635  }
636 
637  groundPlaneFound = true;
638  } else{
639  ROS_DEBUG("Horizontal plane (not ground) found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(),
640  coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3));
641  pcl::PointCloud<pcl::PointXYZ> cloud_out;
642  extract.setNegative (false);
643  extract.filter(cloud_out);
644  nonground +=cloud_out;
645  // debug
646  // pcl::PCDWriter writer;
647  // writer.write<pcl::PointXYZ>("nonground_plane.pcd",cloud_out, false);
648 
649  // remove current plane from scan for next iteration:
650  // workaround for PCL bug:
651  if(inliers->indices.size() != cloud_filtered.size()){
652  extract.setNegative(true);
653  cloud_out.points.clear();
654  extract.filter(cloud_out);
655  cloud_filtered = cloud_out;
656  } else{
657  cloud_filtered.points.clear();
658  }
659  }
660 
661  }
662  // TODO: also do this if overall starting pointcloud too small?
663  if (!groundPlaneFound){ // no plane found or remaining points too small
664  ROS_WARN("No ground plane found in scan");
665 
666  // do a rough fitlering on height to prevent spurious obstacles
667  pcl::PassThrough<pcl::PointXYZ> second_pass;
668  second_pass.setFilterFieldName("z");
669  second_pass.setFilterLimits(-groundFilterPlaneDistance, groundFilterPlaneDistance);
670  second_pass.setInputCloud(pc.makeShared());
671  second_pass.filter(ground);
672 
673  second_pass.setFilterLimitsNegative (true);
674  second_pass.filter(nonground);
675  }
676 
677  // debug:
678  // pcl::PCDWriter writer;
679  // if (pc_ground.size() > 0)
680  // writer.write<pcl::PointXYZ>("ground.pcd",pc_ground, false);
681  // if (pc_nonground.size() > 0)
682  // writer.write<pcl::PointXYZ>("nonground.pcd",pc_nonground, false);
683  }
684 }
685 
686 
687 
688 void HumanoidLocalization::prepareGeneralPointCloud(const sensor_msgs::PointCloud2::ConstPtr& msg, PointCloud& pc, std::vector<float>& ranges) const{
689 
690  pc.clear();
691  // lookup Transfrom Sensor to BaseFootprint
692  tf::StampedTransform sensorToBaseFootprint;
693  try{
694  m_tfListener.waitForTransform(m_baseFootprintId, msg->header.frame_id, msg->header.stamp, ros::Duration(0.2));
695  m_tfListener.lookupTransform(m_baseFootprintId, msg->header.frame_id, msg->header.stamp, sensorToBaseFootprint);
696 
697 
698  }catch(tf::TransformException& ex){
699  ROS_ERROR_STREAM( "Transform error for pointCloudCallback: " << ex.what() << ", quitting callback.\n");
700  return;
701  }
702 
703  /*** filter PointCloud and fill pc and ranges ***/
704 
705  // pass-through filter to get rid of near and far ranges
706  pcl::PassThrough<pcl::PointXYZ> pass;
707  pcl::PointCloud<pcl::PointXYZ>::Ptr pcd_tmp(new pcl::PointCloud<pcl::PointXYZ>());
708 #if PCL_VERSION_COMPARE(>=,1,7,0)
709  pcl::PCLPointCloud2 pcd2_tmp;
710  pcl_conversions::toPCL(*msg, pcd2_tmp);
711  pcl::fromPCLPointCloud2(pcd2_tmp, *pcd_tmp);
712 #else
713  pcl::fromROSMsg(*msg, *pcd_tmp);
714 #endif
715  pass.setInputCloud (pcd_tmp);
716  pass.setFilterFieldName ("z");
717  pass.setFilterLimits (m_filterMinRange, m_filterMaxRange);
718  pass.filter (pc);
719 
720  // identify ground plane
721  PointCloud ground, nonground;
723  {
724  Eigen::Matrix4f matSensorToBaseFootprint, matBaseFootprintToSensor;
725  pcl_ros::transformAsMatrix(sensorToBaseFootprint, matSensorToBaseFootprint);
726  pcl_ros::transformAsMatrix(sensorToBaseFootprint.inverse(), matBaseFootprintToSensor);
727  // TODO:Why transform the point cloud and not just the normal vector?
728  pcl::transformPointCloud(pc, pc, matSensorToBaseFootprint );
730 
731  // clear pc again and refill it based on classification
732  pc.clear();
733  pcl::PointCloud<int> sampledIndices;
734 
735  int numFloorPoints = 0;
736  if (ground.size() > 0){ // check for 0 size, otherwise PCL crashes
737  // transform clouds back to sensor for integration
738  pcl::transformPointCloud(ground, ground, matBaseFootprintToSensor);
740  pcl::copyPointCloud(ground, sampledIndices.points, pc);
741  numFloorPoints = sampledIndices.size();
742  }
743 
744  //int numNonFloorPoints = filterUniform( nonground, pc, m_numNonFloorPoints );
745  int numNonFloorPoints = 0;
746  if (nonground.size() > 0){ // check for 0 size, otherwise PCL crashes
747  // transform clouds back to sensor for integration
748  pcl::transformPointCloud(nonground, nonground, matBaseFootprintToSensor);
749  voxelGridSampling( nonground, sampledIndices, m_sensorSampleDist);
750  pcl::copyPointCloud( nonground, sampledIndices.points, nonground);
751  numNonFloorPoints = sampledIndices.size();
752  pc += nonground;
753  }
754 
755  //TODO improve sampling?
756 
757 
758  ROS_INFO("PointCloudGroundFiltering done. Added %d non-ground points and %d ground points (from %zu). Cloud size is %zu", numNonFloorPoints, numFloorPoints, ground.size(), pc.size());
759  // create sparse ranges..
760  ranges.resize(pc.size());
761  for (unsigned int i=0; i<pc.size(); ++i)
762  {
763  pcl::PointXYZ p = pc.at(i);
764  ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
765  }
766 
767  }
768  else
769  {
770  ROS_INFO("Starting uniform sampling");
771  //ROS_ERROR("No ground filtering is not implemented yet!");
772  // uniform sampling:
773  pcl::PointCloud<int> sampledIndices;
774  voxelGridSampling(pc, sampledIndices, m_sensorSampleDist);
775  pcl::copyPointCloud(pc, sampledIndices.points, pc);
776 
777  // adjust "ranges" array to contain the same points:
778  ranges.resize(sampledIndices.size());
779  for (size_t i = 0; i < ranges.size(); ++i){
780  pcl::PointXYZ p = pc[i];
781  ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
782  //rangesSparse[i] = ranges[sampledIndices.points[i]];
783  //ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
784  }
785  ROS_INFO("Done.");
786 
787 
788  }
789  return;
790 
791 }
792 
793 void HumanoidLocalization::voxelGridSampling(const PointCloud & pc, pcl::PointCloud<int> & sampledIndices, double search_radius) const
794 {
795  pcl::UniformSampling<pcl::PointXYZ> uniformSampling;
796  pcl::PointCloud<pcl::PointXYZ>::Ptr cloudPtr;
797  cloudPtr.reset(new pcl::PointCloud<pcl::PointXYZ> (pc)); // TODO: Check if this is a shallow copy..
798  uniformSampling.setInputCloud(cloudPtr);
799  uniformSampling.setRadiusSearch(search_radius);
800  uniformSampling.compute(sampledIndices);
801 }
802 
803 void HumanoidLocalization::pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr& msg) {
804  ROS_DEBUG("PointCloud received (time: %f)", msg->header.stamp.toSec());
805 
806  if (!m_initialized){
807  ROS_WARN("Loclization not initialized yet, skipping PointCloud callback.");
808  return;
809  }
810 
811  double timediff = (msg->header.stamp - m_lastPointCloudTime).toSec();
812  if (m_receivedSensorData && timediff < 0){
813  ROS_WARN("Ignoring received PointCloud data that is %f s older than previous data!", timediff);
814  return;
815  }
816 
817 
819  tf::Stamped<tf::Pose> odomPose;
820  // check if odometry available, skip scan if not.
821  if (!m_motionModel->lookupOdomPose(msg->header.stamp, odomPose))
822  return;
823 
824 
825  bool sensor_integrated = false;
826 
827  // TODO #1: Make this nicer: head rotations for integration check
828  // TODO #2: Initialization of m_headYawRotationLastScan, etc needs to be set correctly
829  bool isAboveHeadMotionThreshold = false;
830  double headYaw, headPitch, headRoll;
831  tf::StampedTransform torsoToSensor;
832  if (!m_motionModel->lookupLocalTransform(msg->header.frame_id, msg->header.stamp, torsoToSensor))
833  return; //TODO: should we apply applyOdomTransformTemporal, before returning
834 
835  // TODO #3: Invert transform?: tf::Transform torsoToSensor(localSensorFrame.inverse());
836 
837  torsoToSensor.getBasis().getRPY(headRoll, headPitch, headYaw);
838  double headYawRotationSinceScan = std::abs(headYaw - m_headYawRotationLastScan);
839  double headPitchRotationSinceScan = std::abs(headPitch - m_headPitchRotationLastScan);
840 
841  if (headYawRotationSinceScan>= m_observationThresholdHeadYawRot || headPitchRotationSinceScan >= m_observationThresholdHeadPitchRot)
842  isAboveHeadMotionThreshold = true;
843  // end #1
844 
845  if (!m_paused && (!m_receivedSensorData || isAboveHeadMotionThreshold || isAboveMotionThreshold(odomPose))) {
846 
847  // convert laser to point cloud first:
848  PointCloud pc_filtered;
849  std::vector<float> rangesSparse;
850  prepareGeneralPointCloud(msg, pc_filtered, rangesSparse);
851 
852  double maxRange = 10.0; // TODO #4: What is a maxRange for pointClouds? NaN? maxRange is expected to be a double and integrateMeasurement checks rangesSparse[i] > maxRange
853  ROS_DEBUG("Updating Pose Estimate from a PointCloud with %zu points and %zu ranges", pc_filtered.size(), rangesSparse.size());
854  sensor_integrated = localizeWithMeasurement(pc_filtered, rangesSparse, maxRange);
855 
856  }
857  if(!sensor_integrated){ // no observation necessary: propagate particles forward by full interval
858  // relative odom transform to last odomPose
859  tf::Transform odomTransform = m_motionModel->computeOdomTransform(odomPose);
860  m_motionModel->applyOdomTransform(m_particles, odomTransform);
861  constrainMotion(odomPose);
862  }
863  else{
864  m_lastLocalizedPose = odomPose;
865  // TODO #1
866  m_headYawRotationLastScan = headYaw;
867  m_headPitchRotationLastScan = headPitch;
868  }
869 
870  m_motionModel->storeOdomPose(odomPose);
871  publishPoseEstimate(msg->header.stamp, sensor_integrated);
872  m_lastPointCloudTime = msg->header.stamp;
873  ROS_DEBUG("PointCloud callback complete.");
874 }
875 
876 void HumanoidLocalization::imuCallback(const sensor_msgs::ImuConstPtr& msg){
877  m_lastIMUMsgBuffer.push_back(*msg);
878 }
879 
880 bool HumanoidLocalization::getImuMsg(const ros::Time& stamp, ros::Time& imuStamp, double& angleX, double& angleY) const {
881  if(m_lastIMUMsgBuffer.empty())
882  return false;
883 
884  typedef boost::circular_buffer<sensor_msgs::Imu>::const_iterator ItT;
885  const double maxAge = 0.2;
886  double closestOlderStamp = std::numeric_limits<double>::max();
887  double closestNewerStamp = std::numeric_limits<double>::max();
888  ItT closestOlder = m_lastIMUMsgBuffer.end(), closestNewer = m_lastIMUMsgBuffer.end();
889  for(ItT it = m_lastIMUMsgBuffer.begin(); it != m_lastIMUMsgBuffer.end(); it++) {
890  const double age = (stamp - it->header.stamp).toSec();
891  if(age >= 0.0 && age < closestOlderStamp) {
892  closestOlderStamp = age;
893  closestOlder = it;
894  } else if(age < 0.0 && -age < closestNewerStamp) {
895  closestNewerStamp = -age;
896  closestNewer = it;
897  }
898  }
899 
900  if(closestOlderStamp < maxAge && closestNewerStamp < maxAge && closestOlderStamp + closestNewerStamp > 0.0) {
901  // Linear interpolation
902  const double weightOlder = closestNewerStamp / (closestNewerStamp + closestOlderStamp);
903  const double weightNewer = 1.0 - weightOlder;
904  imuStamp = ros::Time(weightOlder * closestOlder->header.stamp.toSec()
905  + weightNewer * closestNewer->header.stamp.toSec());
906  double olderX, olderY, newerX, newerY;
907  getRP(closestOlder->orientation, olderX, olderY);
908  getRP(closestNewer->orientation, newerX, newerY);
909  angleX = weightOlder * olderX + weightNewer * newerX;
910  angleY = weightOlder * olderY + weightNewer * newerY;
911  ROS_DEBUG("Msg: %.3f, Interpolate [%.3f .. %.3f .. %.3f]\n", stamp.toSec(), closestOlder->header.stamp.toSec(),
912  imuStamp.toSec(), closestNewer->header.stamp.toSec());
913  return true;
914  } else if(closestOlderStamp < maxAge || closestNewerStamp < maxAge) {
915  // Return closer one
916  ItT it = (closestOlderStamp < closestNewerStamp) ? closestOlder : closestNewer;
917  imuStamp = it->header.stamp;
918  getRP(it->orientation, angleX, angleY);
919  return true;
920  } else {
921  if(closestOlderStamp < closestNewerStamp)
922  ROS_WARN("Closest IMU message is %.2f seconds too old, skipping pose integration", closestOlderStamp);
923  else
924  ROS_WARN("Closest IMU message is %.2f seconds too new, skipping pose integration", closestNewerStamp);
925  return false;
926  }
927 }
928 
929 void HumanoidLocalization::initPoseCallback(const geometry_msgs::PoseWithCovarianceStampedConstPtr& msg){
930  tf::Pose pose;
931  tf::poseMsgToTF(msg->pose.pose, pose);
932 
933  if (msg->header.frame_id != m_globalFrameId){
934  ROS_WARN("Frame ID of \"initialpose\" (%s) is different from the global frame %s", msg->header.frame_id.c_str(), m_globalFrameId.c_str());
935  }
936 
937  std::vector<double> heights;
938  double poseHeight = 0.0;
939  if (std::abs(pose.getOrigin().getZ()) < 0.01){
940  m_mapModel->getHeightlist(pose.getOrigin().getX(), pose.getOrigin().getY(), 0.6, heights);
941  if (heights.size() == 0){
942  ROS_WARN("No ground level to stand on found at map position, assuming 0");
943  heights.push_back(0.0);
944  }
945 
946  bool poseHeightOk = false;
947  if(m_initPoseRealZRP) {
948  ros::Time stamp(msg->header.stamp);
949  if(stamp.isZero()) {
950  // Header stamp is not set (e.g. RViz), use stamp from latest pose message instead
951  tf::Stamped<tf::Pose> lastOdomPose;
952  m_motionModel->getLastOdomPose(lastOdomPose);
953  stamp = lastOdomPose.stamp_;
954  }
955  poseHeightOk = lookupPoseHeight(stamp, poseHeight);
956  if(!poseHeightOk) {
957  ROS_WARN("Could not determine current pose height, falling back to init_pose_z");
958  }
959  }
960  if(!poseHeightOk) {
961  ROS_INFO("Use pose height from init_pose_z");
962  poseHeight = m_initPose(2);
963  }
964  }
965 
966 
967  Matrix6d initCov;
968  if ((std::abs(msg->pose.covariance.at(6*0+0) - 0.25) < 0.1) && (std::abs(msg->pose.covariance.at(6*1+1) -0.25) < 0.1)
969  && (std::abs(msg->pose.covariance.at(6*3+3) - M_PI/12.0 * M_PI/12.0)<0.1)){
970  ROS_INFO("Covariance originates from RViz, using default parameters instead");
971  initCov = Matrix6d::Zero();
972  initCov.diagonal() = m_initNoiseStd.cwiseProduct(m_initNoiseStd);
973 
974  // manually set r&p, rviz values are 0
975  bool ok = false;
976  const double yaw = tf::getYaw(pose.getRotation());
977  if(m_initPoseRealZRP) {
978  bool useOdometry = true;
979  if(m_useIMU) {
980  if(m_lastIMUMsgBuffer.empty()) {
981  ROS_WARN("Could not determine current roll and pitch because IMU message buffer is empty.");
982  } else {
983  double roll, pitch;
984  if(msg->header.stamp.isZero()) {
985  // Header stamp is not set (e.g. RViz), use stamp from latest IMU message instead
986  getRP(m_lastIMUMsgBuffer.back().orientation, roll, pitch);
987  ok = true;
988  } else {
989  ros::Time imuStamp;
990  ok = getImuMsg(msg->header.stamp, imuStamp, roll, pitch);
991  }
992  if(ok) {
993  ROS_INFO("roll and pitch not set in initPoseCallback, use IMU values (roll = %f, pitch = %f) instead", roll, pitch);
994  pose.setRotation(tf::createQuaternionFromRPY(roll, pitch, yaw));
995  useOdometry = false;
996  } else {
997  ROS_WARN("Could not determine current roll and pitch from IMU, falling back to odometry roll and pitch");
998  useOdometry = true;
999  }
1000  }
1001  }
1002 
1003  if(useOdometry) {
1004  double roll, pitch, dropyaw;
1005  tf::Stamped<tf::Pose> lastOdomPose;
1006  ok = m_motionModel->getLastOdomPose(lastOdomPose);
1007  if(ok) {
1008  lastOdomPose.getBasis().getRPY(roll, pitch, dropyaw);
1009  pose.setRotation(tf::createQuaternionFromRPY(roll, pitch, yaw));
1010  ROS_INFO("roll and pitch not set in initPoseCallback, use odometry values (roll = %f, pitch = %f) instead", roll, pitch);
1011  } else {
1012  ROS_WARN("Could not determine current roll and pitch from odometry, falling back to init_pose_{roll,pitch} parameters");
1013  }
1014  }
1015  }
1016 
1017  if(!ok) {
1018  ROS_INFO("roll and pitch not set in initPoseCallback, use init_pose_{roll,pitch} parameters instead");
1020  }
1021  } else{
1022  for(int j=0; j < initCov.cols(); ++j){
1023  for (int i = 0; i < initCov.rows(); ++i){
1024  initCov(i,j) = msg->pose.covariance.at(i*initCov.cols() +j);
1025  }
1026  }
1027  }
1028 
1029  // sample from initial pose covariance:
1030  Matrix6d initCovL = initCov.llt().matrixL();
1031  tf::Transform transformNoise; // transformation on original pose from noise
1032  unsigned idx = 0;
1033  for(Particles::iterator it = m_particles.begin(); it != m_particles.end(); ++it){
1034  Vector6d poseNoise;
1035  for (unsigned i = 0; i < 6; ++i){
1036  poseNoise(i) = m_rngNormal();
1037  }
1038  Vector6d poseCovNoise = initCovL * poseNoise; // is now drawn according to covariance noise
1039  // if a variance is set to 0 => no noise!
1040  for (unsigned i = 0; i < 6; ++i){
1041  if (std::abs(initCov(i,i)) < 0.00001)
1042  poseCovNoise(i) = 0.0;
1043  }
1044 
1045 
1046  transformNoise.setOrigin(tf::Vector3(poseCovNoise(0), poseCovNoise(1), poseCovNoise(2)));
1047  tf::Quaternion q;
1048  q.setRPY(poseCovNoise(3), poseCovNoise(4),poseCovNoise(5));
1049 
1050  transformNoise.setRotation(q);
1051  it->pose = pose;
1052 
1053  if (heights.size() > 0){
1054  // distribute particles evenly between levels:
1055  it->pose.getOrigin().setZ(heights.at(int(double(idx)/m_particles.size() * heights.size())) + poseHeight);
1056  }
1057 
1058  it->pose *= transformNoise;
1059 
1060  it->weight = 1.0/m_particles.size();
1061  idx++;
1062  }
1063 
1064  ROS_INFO("Pose reset around mean (%f %f %f)", pose.getOrigin().getX(), pose.getOrigin().getY(), pose.getOrigin().getZ());
1065 
1066  // reset internal state:
1067  m_motionModel->reset();
1068  // force integration of next laser data:
1069  m_receivedSensorData = false;
1070  m_initialized = true;
1071 
1072 
1073  // Fix "0" time warning (when initializing pose from RViz)
1074  ros::Time stampPublish = msg->header.stamp;
1075  if (stampPublish.isZero()){
1076  tf::Stamped<tf::Pose> lastOdomPose;
1077  m_motionModel->getLastOdomPose(lastOdomPose);
1078  stampPublish = lastOdomPose.stamp_;
1079  if (stampPublish.isZero())
1080  stampPublish = ros::Time::now();
1081 
1082  }
1083 
1084  publishPoseEstimate(stampPublish, false);
1085 }
1086 
1087 
1088 
1089 
1090 bool HumanoidLocalization::globalLocalizationCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
1091 {
1092 
1093  initGlobal();
1094 
1095  return true;
1096 }
1097 
1099 
1100  double wmin = std::numeric_limits<double>::max();
1101  double wmax = -std::numeric_limits<double>::max();
1102 
1103  for (unsigned i=0; i < m_particles.size(); ++i){
1104  double weight = m_particles[i].weight;
1105  assert (!isnan(weight));
1106  if (weight < wmin)
1107  wmin = weight;
1108  if (weight > wmax){
1109  wmax = weight;
1110  m_bestParticleIdx = i;
1111  }
1112  }
1113  if (wmin > wmax){
1114  ROS_ERROR_STREAM("Error in weights: min=" << wmin <<", max="<<wmax<<", 1st particle weight="<< m_particles[1].weight<< std::endl);
1115 
1116  }
1117 
1118  double min_normalized_value;
1119  if (m_minParticleWeight > 0.0)
1120  min_normalized_value = std::max(log(m_minParticleWeight), wmin - wmax);
1121  else
1122  min_normalized_value = wmin - wmax;
1123 
1124  double max_normalized_value = 0.0; // = log(1.0);
1125  double dn = max_normalized_value-min_normalized_value;
1126  double dw = wmax-wmin;
1127  if (dw == 0.0) dw = 1;
1128  double scale = dn/dw;
1129  if (scale < 0.0){
1130  ROS_WARN("normalizeWeights: scale is %f < 0, dw=%f, dn=%f", scale, dw, dn );
1131  }
1132  double offset = -wmax*scale;
1133  double weights_sum = 0.0;
1134 
1135 #pragma omp parallel
1136  {
1137 
1138 #pragma omp for
1139  for (unsigned i = 0; i < m_particles.size(); ++i){
1140  double w = m_particles[i].weight;
1141  w = exp(scale*w+offset);
1142  assert(!isnan(w));
1143  m_particles[i].weight = w;
1144 #pragma omp atomic
1145  weights_sum += w;
1146  }
1147 
1148  assert(weights_sum > 0.0);
1149  // normalize sum to 1:
1150 #pragma omp for
1151  for (unsigned i = 0; i < m_particles.size(); ++i){
1152  m_particles[i].weight /= weights_sum;
1153  }
1154 
1155  }
1156 }
1157 
1159  double cumWeight=0.0;
1160 
1161  //compute the cumulative weights
1162  for (Particles::const_iterator it = m_particles.begin(); it != m_particles.end(); ++it){
1163  cumWeight += it->weight;
1164  }
1165 
1166  return cumWeight;
1167 }
1168 
1169 void HumanoidLocalization::resample(unsigned numParticles){
1170 
1171  if (numParticles <= 0)
1172  numParticles = m_numParticles;
1173 
1174  //compute the interval
1175  double interval=getCumParticleWeight()/numParticles;
1176 
1177  //compute the initial target weight
1178  double target=interval*m_rngUniform();
1179 
1180  //compute the resampled indexes
1181  double cumWeight=0;
1182  std::vector<unsigned> indices(numParticles);
1183 
1184  unsigned n=0;
1185  for (unsigned i = 0; i < m_particles.size(); ++i){
1186  cumWeight += m_particles[i].weight;
1187  while(cumWeight > target && n < numParticles){
1188  if (m_bestParticleIdx >= 0 && i == unsigned(m_bestParticleIdx)){
1189  m_bestParticleIdx = n;
1190  }
1191 
1192  indices[n++]=i;
1193  target+=interval;
1194  }
1195  }
1196  // indices now contains the indices to draw from the particles distribution
1197 
1198  Particles oldParticles = m_particles;
1199  m_particles.resize(numParticles);
1200  m_poseArray.poses.resize(numParticles);
1201  double newWeight = 1.0/numParticles;
1202 #pragma omp parallel for
1203  for (unsigned i = 0; i < numParticles; ++i){
1204  m_particles[i].pose = oldParticles[indices[i]].pose;
1205  m_particles[i].weight = newWeight;
1206  }
1207 }
1208 
1210  ROS_INFO("Initializing with uniform distribution");
1211 
1212  double roll, pitch, z;
1213  initZRP(z, roll, pitch);
1214 
1215  m_mapModel->initGlobal(m_particles, z, roll, pitch, m_initNoiseStd, m_rngUniform, m_rngNormal);
1216 
1217 
1218  ROS_INFO("Global localization done");
1219  m_motionModel->reset();
1220  m_receivedSensorData = false;
1221  m_initialized = true;
1222 
1224 
1225 }
1226 
1227 void HumanoidLocalization::publishPoseEstimate(const ros::Time& time, bool publish_eval){
1228 
1230  // send all hypotheses as arrows:
1232 
1233  m_poseArray.header.stamp = time;
1234 
1235  if (m_poseArray.poses.size() != m_particles.size())
1236  m_poseArray.poses.resize(m_particles.size());
1237 
1238 #pragma omp parallel for
1239  for (unsigned i = 0; i < m_particles.size(); ++i){
1240  tf::poseTFToMsg(m_particles[i].pose, m_poseArray.poses[i]);
1241  }
1242 
1244 
1246  // send best particle as pose and one array:
1248  geometry_msgs::PoseWithCovarianceStamped p;
1249  p.header.stamp = time;
1250  p.header.frame_id = m_globalFrameId;
1251 
1252  tf::Pose bestParticlePose;
1254  bestParticlePose = getMeanParticlePose();
1255  else
1256  bestParticlePose = getBestParticlePose();
1257 
1258  tf::poseTFToMsg(bestParticlePose,p.pose.pose);
1259  m_posePub.publish(p);
1260 
1261  if (publish_eval){
1263  }
1264 
1265  geometry_msgs::PoseArray bestPose;
1266  bestPose.header = p.header;
1267  bestPose.poses.resize(1);
1268  tf::poseTFToMsg(bestParticlePose, bestPose.poses[0]);
1269  m_bestPosePub.publish(bestPose);
1270 
1272  // send incremental odom pose (synced to localization)
1274  tf::Stamped<tf::Pose> lastOdomPose;
1275  if (m_motionModel->getLastOdomPose(lastOdomPose)){
1276  geometry_msgs::PoseStamped odomPoseMsg;
1277  tf::poseStampedTFToMsg(lastOdomPose, odomPoseMsg);
1278  m_poseOdomPub.publish(odomPoseMsg);
1279  }
1280 
1281 
1283  // Send tf target->map (where target is typically odom)
1284  tf::Stamped<tf::Pose> targetToMapTF;
1285  try{
1286  tf::Stamped<tf::Pose> baseToMapTF(bestParticlePose.inverse(),time, m_baseFrameId);
1287  m_tfListener.transformPose(m_targetFrameId, baseToMapTF, targetToMapTF); // typically target == odom
1288  } catch (const tf::TransformException& e){
1289  ROS_WARN("Failed to subtract base to %s transform, will not publish pose estimate: %s", m_targetFrameId.c_str(), e.what());
1290  return;
1291  }
1292 
1293  tf::Transform latestTF(tf::Quaternion(targetToMapTF.getRotation()), tf::Point(targetToMapTF.getOrigin()));
1294 
1295  // We want to send a transform that is good up until a
1296  // tolerance time so that odom can be used
1297  // see ROS amcl_node
1298 
1299  ros::Duration transformTolerance(m_transformTolerance);
1300  ros::Time transformExpiration = (time + transformTolerance);
1301 
1302  tf::StampedTransform tmp_tf_stamped(latestTF.inverse(), transformExpiration, m_globalFrameId, m_targetFrameId);
1303  m_latest_transform = tmp_tf_stamped;
1304 
1305  m_tfBroadcaster.sendTransform(tmp_tf_stamped);
1306 
1307 }
1308 
1310  if (m_bestParticleIdx < 0 || m_bestParticleIdx >= m_numParticles){
1311  ROS_WARN("Index (%d) of best particle not valid, using 0 instead", m_bestParticleIdx);
1312  return 0;
1313  }
1314 
1315  return m_bestParticleIdx;
1316 }
1317 
1319  return m_particles.at(particleIdx).pose;
1320 }
1321 
1324 }
1325 
1327  tf::Pose meanPose = tf::Pose::getIdentity();
1328 
1329  double totalWeight = 0.0;
1330 
1331  meanPose.setBasis(tf::Matrix3x3(0,0,0,0,0,0,0,0,0));
1332  for (Particles::const_iterator it = m_particles.begin(); it != m_particles.end(); ++it){
1333  meanPose.getOrigin() += it->pose.getOrigin() * it->weight;
1334  meanPose.getBasis()[0] += it->pose.getBasis()[0];
1335  meanPose.getBasis()[1] += it->pose.getBasis()[1];
1336  meanPose.getBasis()[2] += it->pose.getBasis()[2];
1337  totalWeight += it->weight;
1338  }
1339  assert(!isnan(totalWeight));
1340 
1341  //assert(totalWeight == 1.0);
1342 
1343  // just in case weights are not normalized:
1344  meanPose.getOrigin() /= totalWeight;
1345  // TODO: only rough estimate of mean rotation, asserts normalized weights!
1346  meanPose.getBasis() = meanPose.getBasis().scaled(tf::Vector3(1.0/m_numParticles, 1.0/m_numParticles, 1.0/m_numParticles));
1347 
1348  // Apparently we need to normalize again
1349  meanPose.setRotation(meanPose.getRotation().normalized());
1350 
1351  return meanPose;
1352 }
1353 
1355 
1356  double sqrWeights=0.0;
1357  for (Particles::const_iterator it=m_particles.begin(); it!=m_particles.end(); ++it){
1358  sqrWeights+=(it->weight * it->weight);
1359  }
1360 
1361  if (sqrWeights > 0.0)
1362  return 1./sqrWeights;
1363  else
1364  return 0.0;
1365 }
1366 
1368  // TODO: linear offset needed?
1369 #pragma omp parallel for
1370  for (unsigned i = 0; i < m_particles.size(); ++i){
1371  assert(m_particles[i].weight > 0.0);
1372  m_particles[i].weight = log(m_particles[i].weight);
1373  }
1374 }
1375 
1376 void HumanoidLocalization::pauseLocalizationCallback(const std_msgs::BoolConstPtr& msg){
1377  if (msg->data){
1378  if (!m_paused){
1379  m_paused = true;
1380  ROS_INFO("Localization paused");
1381  } else{
1382  ROS_WARN("Received a msg to pause localizatzion, but is already paused.");
1383  }
1384  } else{
1385  if (m_paused){
1386  m_paused = false;
1387  ROS_INFO("Localization resumed");
1388  // force laser integration:
1389  m_receivedSensorData = false;
1390  } else {
1391  ROS_WARN("Received a msg to resume localization, is not paused.");
1392  }
1393 
1394  }
1395 
1396 }
1397 
1398 bool HumanoidLocalization::pauseLocalizationSrvCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
1399 {
1400  if (!m_paused){
1401  m_paused = true;
1402  ROS_INFO("Localization paused");
1403  } else{
1404  ROS_WARN("Received a request to pause localizatzion, but is already paused.");
1405  }
1406 
1407  return true;
1408 }
1409 
1410 bool HumanoidLocalization::resumeLocalizationSrvCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
1411 {
1412  if (m_paused){
1413  m_paused = false;
1414  ROS_INFO("Localization resumed");
1415  // force next laser integration:
1416  m_receivedSensorData = false;
1417  } else {
1418  ROS_WARN("Received a request to resume localization, but is not paused.");
1419  }
1420 
1421  return true;
1422 }
1423 
1424 bool HumanoidLocalization::lookupPoseHeight(const ros::Time& t, double& poseHeight) const{
1426  if (m_motionModel->lookupLocalTransform(m_baseFootprintId, t, tf)){
1427  poseHeight = tf.getOrigin().getZ();
1428  return true;
1429  } else
1430  return false;
1431 }
1432 
1433 }
1434 
bool pauseLocalizationSrvCallback(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
pause localization by service call
double timediff(const timeval &start, const timeval &stop)
virtual void pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr &msg)
static tf::Quaternion createQuaternionFromRPY(double roll, double pitch, double yaw)
TFSIMD_FORCE_INLINE void setRotation(const Quaternion &q)
static void poseMsgToTF(const geometry_msgs::Pose &msg, Pose &bt)
#define ROS_FATAL(...)
boost::uniform_real UniformDistributionT
void initPoseCallback(const geometry_msgs::PoseWithCovarianceStampedConstPtr &msg)
void publish(const boost::shared_ptr< M > &message) const
bool resumeLocalizationSrvCallback(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
unpause localization by service call
double m_headPitchRotationLastScan
absolute, summed pitch angle since last measurement integraton
bool isAboveMotionThreshold(const tf::Pose &odomTransform)
double m_headYawRotationLastScan
absolute, summed yaw angle since last measurement integraton
tf::MessageFilter< sensor_msgs::PointCloud2 > * m_pointCloudFilter
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
int filterUniform(const PointCloud &cloud_in, PointCloud &cloud_out, int numSamples) const
void prepareLaserPointCloud(const sensor_msgs::LaserScanConstPtr &laser, PointCloud &pc, std::vector< float > &ranges) const
unsigned getBestParticleIdx() const
Returns index of particle with highest weight (log or normal scale)
bool m_useTimer
< True = do not estimate roll and pitch, directly use odometry pose
TFSIMD_FORCE_INLINE Matrix3x3 & getBasis()
static void poseStampedTFToMsg(const Stamped< Pose > &bt, geometry_msgs::PoseStamped &msg)
static double getYaw(const Quaternion &bt_q)
bool lookupPoseHeight(const ros::Time &t, double &poseHeight) const
double getCumParticleWeight() const
cumulative weight of all particles (=1 when normalized)
void transformAsMatrix(const tf::Transform &bt, Eigen::Matrix4f &out_mat)
void voxelGridSampling(const PointCloud &pc, pcl::PointCloud< int > &sampledIndices, double searchRadius) const
TFSIMD_FORCE_INLINE void setZ(tfScalar z)
void publishPoseEstimate(const ros::Time &time, bool publish_eval)
tf::MessageFilter< sensor_msgs::LaserScan > * m_laserFilter
void fromROSMsg(const sensor_msgs::PointCloud2 &cloud, pcl::PointCloud< T > &pcl_cloud)
TFSIMD_FORCE_INLINE const tfScalar & getY() const
#define M_PI
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
message_filters::Subscriber< sensor_msgs::LaserScan > * m_laserSub
TFSIMD_FORCE_INLINE const tfScalar & getZ() const
#define ROS_WARN(...)
static tf::Quaternion createIdentityQuaternion()
void prepareGeneralPointCloud(const sensor_msgs::PointCloud2::ConstPtr &msg, PointCloud &pc, std::vector< float > &ranges) const
bool m_useIMU
True = use IMU for initialization and observation models, false = use orientation from odometry...
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
tf::Pose getMeanParticlePose() const
Returns the 6D pose of the weighted mean particle.
boost::shared_ptr< ObservationModel > m_observationModel
TFSIMD_FORCE_INLINE const tfScalar & x() const
void fromPCL(const pcl::uint64_t &pcl_stamp, ros::Time &stamp)
boost::normal_distribution NormalDistributionT
Boost RNG distribution:
TFSIMD_FORCE_INLINE const tfScalar & z() const
Eigen::Matrix< double, 6, 6 > Matrix6d
void setRPY(const tfScalar &roll, const tfScalar &pitch, const tfScalar &yaw)
void getRPY(tfScalar &roll, tfScalar &pitch, tfScalar &yaw, unsigned int solution_number=1) const
tf::MessageFilter< geometry_msgs::PoseWithCovarianceStamped > * m_initPoseFilter
Matrix3x3 scaled(const Vector3 &s) const
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
message_filters::Subscriber< sensor_msgs::PointCloud2 > * m_pointCloudSub
static void quaternionTFToMsg(const Quaternion &bt, geometry_msgs::Quaternion &msg)
void initGlobal()
function call for global initialization (called by globalLocalizationCallback)
message_filters::Subscriber< geometry_msgs::PoseWithCovarianceStamped > * m_initPoseSub
TFSIMD_FORCE_INLINE const tfScalar & y() const
boost::circular_buffer< sensor_msgs::Imu > m_lastIMUMsgBuffer
void sendTransform(const StampedTransform &transform)
Eigen::Matrix< double, 6, 1 > Vector6d
Transform inverse() const
boost::shared_ptr< MotionModel > m_motionModel
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
void setData(const tf::Transform &input)
tf::Pose getParticlePose(unsigned particleIdx) const
Returns the 6D pose of a particle.
UniformGeneratorT m_rngUniform
uniform distribution [0:1]
TFSIMD_FORCE_INLINE void setBasis(const Matrix3x3 &basis)
bool globalLocalizationCallback(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
TFSIMD_FORCE_INLINE Vector3 & getOrigin()
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
TFSIMD_FORCE_INLINE const tfScalar & z() const
ros::Time stamp_
TFSIMD_FORCE_INLINE const tfScalar & w() const
unsigned int step
bool localizeWithMeasurement(const PointCloud &pc_filtered, const std::vector< float > &ranges, double max_range)
bool m_constrainMotionRP
< True = do not estimate height, directly use odometry pose
tf::Pose getBestParticlePose() const
Returns the 6D pose of the best particle (highest weight)
static void poseTFToMsg(const Pose &bt, geometry_msgs::Pose &msg)
#define ROS_INFO_STREAM(args)
void subscribe(ros::NodeHandle &nh, const std::string &topic, uint32_t queue_size, const ros::TransportHints &transport_hints=ros::TransportHints(), ros::CallbackQueueInterface *callback_queue=0)
std::string frame_id_
static WallTime now()
void transformPose(const std::string &target_frame, const geometry_msgs::PoseStamped &stamped_in, geometry_msgs::PoseStamped &stamped_out) const
Quaternion getRotation() const
bool getImuMsg(const ros::Time &stamp, ros::Time &imuStamp, double &angleX, double &angleY) const
TFSIMD_FORCE_INLINE const tfScalar & getX() const
static Time now()
static const Transform & getIdentity()
virtual void laserCallback(const sensor_msgs::LaserScanConstPtr &msg)
bool ok() const
void initZRP(double &z, double &roll, double &pitch)
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
static void filterGroundPlane(const PointCloud &pc, PointCloud &ground, PointCloud &nonground, double groundFilterDistance, double groundFilterAngle, double groundFilterPlaneDistance)
std::vector< Particle > Particles
pcl::PointCloud< pcl::PointXYZ > PointCloud
tf::Pose m_lastLocalizedPose
sensor data last integrated at this odom pose, to check if moved enough since then ...
TFSIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
void imuCallback(const sensor_msgs::ImuConstPtr &msg)
static void getRP(const geometry_msgs::Quaternion &msg_q, double &roll, double &pitch)
void pauseLocalizationCallback(const std_msgs::BoolConstPtr &msg)
void toPCL(const ros::Time &stamp, pcl::uint64_t &pcl_stamp)
Quaternion normalized() const
TFSIMD_FORCE_INLINE tfScalar length() const
NormalGeneratorT m_rngNormal
standard normal distribution
Connection registerCallback(const C &callback)
#define ROS_DEBUG(...)


humanoid_localization
Author(s): Armin Hornung, Stefan Osswald, Daniel Maier
autogenerated on Mon Jun 10 2019 13:38:31