$search
00001 00009 /* 00010 * Copyright (c) 2010-2012, A. Hornung, University of Freiburg 00011 * All rights reserved. 00012 * 00013 * Redistribution and use in source and binary forms, with or without 00014 * modification, are permitted provided that the following conditions are met: 00015 * 00016 * * Redistributions of source code must retain the above copyright 00017 * notice, this list of conditions and the following disclaimer. 00018 * * Redistributions in binary form must reproduce the above copyright 00019 * notice, this list of conditions and the following disclaimer in the 00020 * documentation and/or other materials provided with the distribution. 00021 * * Neither the name of the University of Freiburg nor the names of its 00022 * contributors may be used to endorse or promote products derived from 00023 * this software without specific prior written permission. 00024 * 00025 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00026 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00027 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00028 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00029 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00030 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00031 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00032 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00033 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00034 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00035 * POSSIBILITY OF SUCH DAMAGE. 00036 */ 00037 00038 #include <octomap_server/OctomapServer.h> 00039 00040 using namespace octomap; 00041 00042 namespace octomap_server{ 00043 00044 OctomapServer::OctomapServer(const std::string& filename) 00045 : m_nh(), 00046 m_pointCloudSub(NULL), 00047 m_tfPointCloudSub(NULL), 00048 m_octoMap(NULL), 00049 m_maxRange(-1.0), 00050 m_worldFrameId("/map"), m_baseFrameId("base_footprint"), 00051 m_useHeightMap(true), 00052 m_colorFactor(0.8), 00053 m_latchedTopics(false), 00054 m_res(0.05), 00055 m_treeDepth(0), 00056 m_maxTreeDepth(0), 00057 m_probHit(0.7), m_probMiss(0.4), 00058 m_thresMin(0.12), m_thresMax(0.97), 00059 m_pointcloudMinZ(-std::numeric_limits<double>::max()), 00060 m_pointcloudMaxZ(std::numeric_limits<double>::max()), 00061 m_occupancyMinZ(-std::numeric_limits<double>::max()), 00062 m_occupancyMaxZ(std::numeric_limits<double>::max()), 00063 m_minSizeX(0.0), m_minSizeY(0.0), 00064 m_filterSpeckles(false), m_filterGroundPlane(false), 00065 m_groundFilterDistance(0.04), m_groundFilterAngle(0.15), m_groundFilterPlaneDistance(0.07) 00066 { 00067 ros::NodeHandle private_nh("~"); 00068 private_nh.param("frame_id", m_worldFrameId, m_worldFrameId); 00069 private_nh.param("base_frame_id", m_baseFrameId, m_baseFrameId); 00070 private_nh.param("height_map", m_useHeightMap, m_useHeightMap); 00071 private_nh.param("color_factor", m_colorFactor, m_colorFactor); 00072 00073 private_nh.param("pointcloud_min_z", m_pointcloudMinZ,m_pointcloudMinZ); 00074 private_nh.param("pointcloud_max_z", m_pointcloudMaxZ,m_pointcloudMaxZ); 00075 private_nh.param("occupancy_min_z", m_occupancyMinZ,m_occupancyMinZ); 00076 private_nh.param("occupancy_max_z", m_occupancyMaxZ,m_occupancyMaxZ); 00077 private_nh.param("min_x_size", m_minSizeX,m_minSizeX); 00078 private_nh.param("min_y_size", m_minSizeY,m_minSizeY); 00079 00080 private_nh.param("filter_speckles", m_filterSpeckles, m_filterSpeckles); 00081 private_nh.param("filter_ground", m_filterGroundPlane, m_filterGroundPlane); 00082 // distance of points from plane for RANSAC 00083 private_nh.param("ground_filter/distance", m_groundFilterDistance, m_groundFilterDistance); 00084 // angular derivation of found plane: 00085 private_nh.param("ground_filter/angle", m_groundFilterAngle, m_groundFilterAngle); 00086 // distance of found plane from z=0 to be detected as ground (e.g. to exclude tables) 00087 private_nh.param("ground_filter/plane_distance", m_groundFilterPlaneDistance, m_groundFilterPlaneDistance); 00088 00089 private_nh.param("sensor_model/max_range", m_maxRange, m_maxRange); 00090 00091 private_nh.param("resolution", m_res, m_res); 00092 private_nh.param("sensor_model/hit", m_probHit, m_probHit); 00093 private_nh.param("sensor_model/miss", m_probMiss, m_probMiss); 00094 private_nh.param("sensor_model/min", m_thresMin, m_thresMin); 00095 private_nh.param("sensor_model/max", m_thresMax, m_thresMax); 00096 00097 00098 // initialize octomap object & params 00099 m_octoMap = new OcTreeROS(m_res); 00100 m_octoMap->octree.setProbHit(m_probHit); 00101 m_octoMap->octree.setProbMiss(m_probMiss); 00102 m_octoMap->octree.setClampingThresMin(m_thresMin); 00103 m_octoMap->octree.setClampingThresMax(m_thresMax); 00104 m_treeDepth = m_octoMap->octree.getTreeDepth(); 00105 m_maxTreeDepth = m_treeDepth; 00106 m_gridmap.info.resolution = m_res; 00107 00108 00109 double r, g, b, a; 00110 private_nh.param("color/r", r, 0.0); 00111 private_nh.param("color/g", g, 0.0); 00112 private_nh.param("color/b", b, 1.0); 00113 private_nh.param("color/a", a, 1.0); 00114 m_color.r = r; 00115 m_color.g = g; 00116 m_color.b = b; 00117 m_color.a = a; 00118 00119 bool staticMap = false; 00120 if (filename != "") 00121 staticMap = true; 00122 00123 m_latchedTopics = staticMap; 00124 private_nh.param("latch", m_latchedTopics, m_latchedTopics); 00125 00126 m_markerPub = m_nh.advertise<visualization_msgs::MarkerArray>("occupied_cells_vis_array", 1, m_latchedTopics); 00127 m_binaryMapPub = m_nh.advertise<octomap_ros::OctomapBinary>("octomap_binary", 1, m_latchedTopics); 00128 m_pointCloudPub = m_nh.advertise<sensor_msgs::PointCloud2>("octomap_point_cloud_centers", 1, m_latchedTopics); 00129 m_collisionObjectPub = m_nh.advertise<arm_navigation_msgs::CollisionObject>("octomap_collision_object", 1, m_latchedTopics); 00130 m_mapPub = m_nh.advertise<nav_msgs::OccupancyGrid>("map", 5, m_latchedTopics); 00131 m_cmapPub = m_nh.advertise<arm_navigation_msgs::CollisionMap>("collision_map_out", 1, m_latchedTopics); 00132 00133 00134 // a filename to load is set => distribute a static map latched 00135 if (staticMap){ 00136 if (!openFile(filename)) 00137 exit(-1); 00138 00139 } else { // otherwise: do scan integration 00140 00141 00142 00143 m_pointCloudSub = new message_filters::Subscriber<sensor_msgs::PointCloud2> (m_nh, "cloud_in", 5); 00144 m_tfPointCloudSub = new tf::MessageFilter<sensor_msgs::PointCloud2> (*m_pointCloudSub, m_tfListener, m_worldFrameId, 5); 00145 m_tfPointCloudSub->registerCallback(boost::bind(&OctomapServer::insertCloudCallback, this, _1)); 00146 } 00147 00148 m_octomapService = m_nh.advertiseService("octomap_binary", &OctomapServer::serviceCallback, this); 00149 m_clearBBXService = private_nh.advertiseService("clear_bbx", &OctomapServer::clearBBXSrv, this); 00150 m_resetService = private_nh.advertiseService("reset", &OctomapServer::resetSrv, this); 00151 00152 dynamic_reconfigure::Server<OctomapServerConfig>::CallbackType f; 00153 00154 f = boost::bind(&OctomapServer::reconfigureCallback, this, _1, _2); 00155 m_reconfigureServer.setCallback(f); 00156 00157 if (staticMap) 00158 publishAll(); 00159 } 00160 00161 OctomapServer::~OctomapServer(){ 00162 if (m_tfPointCloudSub) 00163 delete m_tfPointCloudSub; 00164 00165 if (m_pointCloudSub) 00166 delete m_pointCloudSub; 00167 00168 if (m_octoMap) 00169 delete m_octoMap; 00170 00171 } 00172 00173 bool OctomapServer::openFile(const std::string& filename){ 00174 if (!m_octoMap->octree.readBinary(filename)){ 00175 // TODO: read .ot through factory 00176 00177 return false; 00178 } 00179 00180 ROS_INFO("Octomap file %s loaded (%zu nodes).", filename.c_str(),m_octoMap->octree.size()); 00181 00182 m_treeDepth = m_octoMap->octree.getTreeDepth(); 00183 m_maxTreeDepth = m_treeDepth; 00184 m_res = m_octoMap->octree.getResolution(); 00185 m_gridmap.info.resolution = m_res; 00186 00187 return true; 00188 00189 } 00190 00191 void OctomapServer::insertCloudCallback(const sensor_msgs::PointCloud2::ConstPtr& cloud){ 00192 ros::WallTime startTime = ros::WallTime::now(); 00193 00194 00195 // 00196 // ground filtering in base frame 00197 // 00198 PCLPointCloud pc; // input cloud for filtering and ground-detection 00199 pcl::fromROSMsg(*cloud, pc); 00200 00201 tf::StampedTransform sensorToWorldTf; 00202 try { 00203 m_tfListener.lookupTransform(m_worldFrameId, cloud->header.frame_id, cloud->header.stamp, sensorToWorldTf); 00204 } catch(tf::TransformException& ex){ 00205 ROS_ERROR_STREAM( "Transform error of sensor data: " << ex.what() << ", quitting callback"); 00206 return; 00207 } 00208 00209 Eigen::Matrix4f sensorToWorld; 00210 pcl_ros::transformAsMatrix(sensorToWorldTf, sensorToWorld); 00211 00212 00213 // set up filter for height range, also removes NANs: 00214 pcl::PassThrough<pcl::PointXYZ> pass; 00215 pass.setFilterFieldName("z"); 00216 pass.setFilterLimits(m_pointcloudMinZ, m_pointcloudMaxZ); 00217 00218 PCLPointCloud pc_ground; // segmented ground plane 00219 PCLPointCloud pc_nonground; // everything else 00220 00221 if (m_filterGroundPlane){ 00222 tf::StampedTransform sensorToBaseTf, baseToWorldTf; 00223 try{ 00224 m_tfListener.waitForTransform(m_baseFrameId, cloud->header.frame_id, cloud->header.stamp, ros::Duration(0.2)); 00225 m_tfListener.lookupTransform(m_baseFrameId, cloud->header.frame_id, cloud->header.stamp, sensorToBaseTf); 00226 m_tfListener.lookupTransform(m_worldFrameId, m_baseFrameId, cloud->header.stamp, baseToWorldTf); 00227 00228 00229 }catch(tf::TransformException& ex){ 00230 ROS_ERROR_STREAM( "Transform error for ground plane filter: " << ex.what() << ", quitting callback.\n" 00231 "You need to set the base_frame_id or disable filter_ground."); 00232 } 00233 00234 00235 Eigen::Matrix4f sensorToBase, baseToWorld; 00236 pcl_ros::transformAsMatrix(sensorToBaseTf, sensorToBase); 00237 pcl_ros::transformAsMatrix(baseToWorldTf, baseToWorld); 00238 00239 // transform pointcloud from sensor frame to fixed robot frame 00240 pcl::transformPointCloud(pc, pc, sensorToBase); 00241 pass.setInputCloud(pc.makeShared()); 00242 pass.filter(pc); 00243 filterGroundPlane(pc, pc_ground, pc_nonground); 00244 00245 // transform clouds to world frame for insertion 00246 pcl::transformPointCloud(pc_ground, pc_ground, baseToWorld); 00247 pcl::transformPointCloud(pc_nonground, pc_nonground, baseToWorld); 00248 } else { 00249 // directly transform to map frame: 00250 pcl::transformPointCloud(pc, pc, sensorToWorld); 00251 00252 // just filter height range: 00253 pass.setInputCloud(pc.makeShared()); 00254 pass.filter(pc); 00255 00256 pc_nonground = pc; 00257 // pc_nonground is empty without ground segmentation 00258 pc_ground.header = pc.header; 00259 pc_nonground.header = pc.header; 00260 } 00261 00262 00263 insertScan(sensorToWorldTf.getOrigin(), pc_ground, pc_nonground); 00264 00265 double total_elapsed = (ros::WallTime::now() - startTime).toSec(); 00266 ROS_DEBUG("Pointcloud insertion in OctomapServer done (%zu+%zu pts (ground/nonground), %f sec)", pc_ground.size(), pc_nonground.size(), total_elapsed); 00267 00268 publishAll(cloud->header.stamp); 00269 } 00270 00271 void OctomapServer::insertScan(const tf::Point& sensorOriginTf, const PCLPointCloud& ground, const PCLPointCloud& nonground){ 00272 point3d sensorOrigin = pointTfToOctomap(sensorOriginTf); 00273 00274 // instead of direct scan insertion, compute update to filter ground: 00275 KeySet free_cells, occupied_cells; 00276 // insert ground points only as free: 00277 for (PCLPointCloud::const_iterator it = ground.begin(); it != ground.end(); ++it){ 00278 point3d point(it->x, it->y, it->z); 00279 // maxrange check 00280 if ((m_maxRange > 0.0) && ((point - sensorOrigin).norm() > m_maxRange) ) { 00281 point = sensorOrigin + (point - sensorOrigin).normalized() * m_maxRange; 00282 } 00283 00284 // only clear space (ground points) 00285 if (m_octoMap->octree.computeRayKeys(sensorOrigin, point, m_keyRay)){ 00286 free_cells.insert(m_keyRay.begin(), m_keyRay.end()); 00287 } 00288 } 00289 00290 // all other points: free on ray, occupied on endpoint: 00291 for (PCLPointCloud::const_iterator it = nonground.begin(); it != nonground.end(); ++it){ 00292 point3d point(it->x, it->y, it->z); 00293 // maxrange check 00294 if ((m_maxRange < 0.0) || ((point - sensorOrigin).norm() <= m_maxRange) ) { 00295 00296 // free cells 00297 if (m_octoMap->octree.computeRayKeys(sensorOrigin, point, m_keyRay)){ 00298 free_cells.insert(m_keyRay.begin(), m_keyRay.end()); 00299 } 00300 // occupied endpoint 00301 OcTreeKey key; 00302 if (m_octoMap->octree.genKey(point, key)){ 00303 occupied_cells.insert(key); 00304 } 00305 } else {// ray longer than maxrange:; 00306 point3d new_end = sensorOrigin + (point - sensorOrigin).normalized() * m_maxRange; 00307 if (m_octoMap->octree.computeRayKeys(sensorOrigin, new_end, m_keyRay)){ 00308 free_cells.insert(m_keyRay.begin(), m_keyRay.end()); 00309 } 00310 } 00311 } 00312 00313 // mark free cells only if not seen occupied in this cloud 00314 for(KeySet::iterator it = free_cells.begin(), end=free_cells.end(); it!= end; ++it){ 00315 if (occupied_cells.find(*it) == occupied_cells.end()){ 00316 m_octoMap->octree.updateNode(*it, false); 00317 } 00318 } 00319 00320 // now mark all occupied cells: 00321 for (KeySet::iterator it = occupied_cells.begin(), end=free_cells.end(); it!= end; it++) { 00322 m_octoMap->octree.updateNode(*it, true); 00323 } 00324 00325 // TODO: eval lazy+updateInner vs. proper insertion 00326 // non-lazy by default (updateInnerOccupancy() too slow for large maps) 00327 //m_octoMap->octree.updateInnerOccupancy(); 00328 00329 // prune by default: 00330 m_octoMap->octree.prune(); 00331 00332 00333 } 00334 00335 00336 void OctomapServer::publishAll(const ros::Time& rostime){ 00337 ros::WallTime startTime = ros::WallTime::now(); 00338 size_t octomapSize = m_octoMap->octree.size(); 00339 // TODO: estimate num occ. voxels for size of arrays (reserve) 00340 if (octomapSize <= 1){ 00341 ROS_WARN("Nothing to publish, octree is empty"); 00342 return; 00343 } 00344 00345 bool publishCollisionMap = (m_latchedTopics || m_cmapPub.getNumSubscribers() > 0); 00346 bool publishCollisionObject = (m_latchedTopics || m_collisionObjectPub.getNumSubscribers() > 0); 00347 bool publishMarkerArray = (m_latchedTopics || m_markerPub.getNumSubscribers() > 0); 00348 bool publishPointCloud = (m_latchedTopics || m_pointCloudPub.getNumSubscribers() > 0); 00349 bool publishOctoMap = (m_latchedTopics || m_binaryMapPub.getNumSubscribers() > 0); 00350 00351 // init collision object: 00352 arm_navigation_msgs::CollisionObject collisionObject; 00353 collisionObject.header.frame_id = m_worldFrameId; 00354 collisionObject.header.stamp = rostime; 00355 collisionObject.id = "map"; 00356 arm_navigation_msgs::OrientedBoundingBox collObjBox; 00357 collObjBox.axis.x = collObjBox.axis.y = 0.0; 00358 collObjBox.axis.z = 1.0; 00359 collObjBox.angle = 0.0; 00360 00361 //init collision map: 00362 arm_navigation_msgs::CollisionMap collisionMap; 00363 collisionMap.header.frame_id = m_worldFrameId; 00364 collisionMap.header.stamp = rostime; 00365 arm_navigation_msgs::Shape collObjShape; 00366 collObjShape.type = arm_navigation_msgs::Shape::BOX; 00367 collObjShape.dimensions.resize(3); 00368 00369 geometry_msgs::Pose pose; 00370 pose.orientation = tf::createQuaternionMsgFromYaw(0.0); 00371 00372 // init markers: 00373 visualization_msgs::MarkerArray occupiedNodesVis; 00374 // each array stores all cubes of a different size, one for each depth level: 00375 occupiedNodesVis.markers.resize(m_treeDepth+1); 00376 00377 // init pointcloud: 00378 pcl::PointCloud<pcl::PointXYZ> pclCloud; 00379 00380 // call pre-traversal hook: 00381 handlePreNodeTraversal(rostime); 00382 00383 // now, traverse all leafs in the tree: 00384 for (OcTreeROS::OcTreeType::iterator it = m_octoMap->octree.begin(m_maxTreeDepth), 00385 end = m_octoMap->octree.end(); it != end; ++it) 00386 { 00387 00388 // call general hook: 00389 handleNode(it); 00390 00391 if (m_octoMap->octree.isNodeOccupied(*it)){ 00392 double z = it.getZ(); 00393 if (z > m_occupancyMinZ && z < m_occupancyMaxZ) 00394 { 00395 double size = it.getSize(); 00396 double x = it.getX(); 00397 double y = it.getY(); 00398 octomap::OcTreeKey nKey = it.getKey(); 00399 // Ignore speckles in the map: 00400 if (m_filterSpeckles && (it.getDepth() == m_treeDepth +1) && isSpeckleNode(nKey)){ 00401 ROS_DEBUG("Ignoring single speckle at (%f,%f,%f)", x, y, z); 00402 continue; 00403 } // else: current octree node is no speckle, send it out 00404 00405 handleOccupiedNode(it); 00406 00407 00408 // create collision object: 00409 if (publishCollisionObject){ 00410 collObjShape.dimensions[0] = collObjShape.dimensions[1] = collObjShape.dimensions[2] = size; 00411 collisionObject.shapes.push_back(collObjShape); 00412 pose.position.x = x; 00413 pose.position.y = y; 00414 pose.position.z = z; 00415 collisionObject.poses.push_back(pose); 00416 } 00417 00418 if (publishCollisionMap){ 00419 collObjBox.extents.x = collObjBox.extents.y = collObjBox.extents.z = size; 00420 00421 collObjBox.center.x = x; 00422 collObjBox.center.y = y; 00423 collObjBox.center.z = z; 00424 00425 collisionMap.boxes.push_back(collObjBox); 00426 00427 } 00428 00429 //create marker: 00430 if (publishMarkerArray){ 00431 unsigned idx = it.getDepth(); 00432 assert(idx < occupiedNodesVis.markers.size()); 00433 00434 geometry_msgs::Point cubeCenter; 00435 cubeCenter.x = x; 00436 cubeCenter.y = y; 00437 cubeCenter.z = z; 00438 00439 occupiedNodesVis.markers[idx].points.push_back(cubeCenter); 00440 if (m_useHeightMap){ 00441 double minX, minY, minZ, maxX, maxY, maxZ; 00442 m_octoMap->octree.getMetricMin(minX, minY, minZ); 00443 m_octoMap->octree.getMetricMax(maxX, maxY, maxZ); 00444 00445 double h = (1.0 - std::min(std::max((cubeCenter.z-minZ)/ (maxZ - minZ), 0.0), 1.0)) *m_colorFactor; 00446 occupiedNodesVis.markers[idx].colors.push_back(heightMapColor(h)); 00447 } 00448 } 00449 00450 // insert into pointcloud: 00451 if (publishPointCloud) 00452 pclCloud.push_back(pcl::PointXYZ(x, y, z)); 00453 00454 } 00455 } else{ // node not occupied => mark as free in 2D map if unknown so far 00456 handleFreeNode(it); 00457 } 00458 } 00459 00460 // call post-traversal hook: 00461 handlePostNodeTraversal(rostime); 00462 00463 // finish MarkerArray: 00464 if (publishMarkerArray){ 00465 for (unsigned i= 0; i < occupiedNodesVis.markers.size(); ++i){ 00466 double size = m_octoMap->octree.getNodeSize(i); 00467 00468 occupiedNodesVis.markers[i].header.frame_id = m_worldFrameId; 00469 occupiedNodesVis.markers[i].header.stamp = rostime; 00470 occupiedNodesVis.markers[i].ns = "map"; 00471 occupiedNodesVis.markers[i].id = i; 00472 occupiedNodesVis.markers[i].type = visualization_msgs::Marker::CUBE_LIST; 00473 occupiedNodesVis.markers[i].scale.x = size; 00474 occupiedNodesVis.markers[i].scale.y = size; 00475 occupiedNodesVis.markers[i].scale.z = size; 00476 occupiedNodesVis.markers[i].color = m_color; 00477 00478 00479 if (occupiedNodesVis.markers[i].points.size() > 0) 00480 occupiedNodesVis.markers[i].action = visualization_msgs::Marker::ADD; 00481 else 00482 occupiedNodesVis.markers[i].action = visualization_msgs::Marker::DELETE; 00483 } 00484 00485 00486 m_markerPub.publish(occupiedNodesVis); 00487 } 00488 00489 // finish pointcloud: 00490 if (publishPointCloud){ 00491 sensor_msgs::PointCloud2 cloud; 00492 pcl::toROSMsg (pclCloud, cloud); 00493 cloud.header.frame_id = m_worldFrameId; 00494 cloud.header.stamp = rostime; 00495 m_pointCloudPub.publish(cloud); 00496 } 00497 00498 if (publishCollisionObject) 00499 m_collisionObjectPub.publish(collisionObject); 00500 00501 if (publishCollisionMap) 00502 m_cmapPub.publish(collisionMap); 00503 00504 if (publishOctoMap) 00505 publishMap(rostime); 00506 00507 double total_elapsed = (ros::WallTime::now() - startTime).toSec(); 00508 ROS_DEBUG("Map publishing in OctomapServer took %f sec", total_elapsed); 00509 00510 } 00511 00512 00513 bool OctomapServer::serviceCallback(octomap_ros::GetOctomap::Request &req, 00514 octomap_ros::GetOctomap::Response &res) 00515 { 00516 ROS_INFO("Sending map data on service request"); 00517 res.map.header.frame_id = m_worldFrameId; 00518 res.map.header.stamp = ros::Time::now(); 00519 octomap::octomapMapToMsgData(m_octoMap->octree, res.map.data); 00520 00521 return true; 00522 } 00523 00524 bool OctomapServer::clearBBXSrv(octomap_ros::ClearBBXRegionRequest& req, octomap_ros::ClearBBXRegionRequest& resp){ 00525 OcTreeROS::OcTreeType::leaf_bbx_iterator it, end; 00526 point3d min = pointMsgToOctomap(req.min); 00527 point3d max = pointMsgToOctomap(req.max); 00528 00529 for(OcTreeROS::OcTreeType::leaf_bbx_iterator it = m_octoMap->octree.begin_leafs_bbx(min,max), 00530 end=m_octoMap->octree.end_leafs_bbx(); it!= end; ++it){ 00531 00532 it->setLogOdds(octomap::logodds(m_thresMin)); 00533 // m_octoMap->octree.updateNode(it.getKey(), -6.0f); 00534 } 00535 // TODO: eval which is faster (setLogOdds+updateInner or updateNode) 00536 m_octoMap->octree.updateInnerOccupancy(); 00537 00538 publishAll(ros::Time::now()); 00539 00540 return true; 00541 } 00542 00543 bool OctomapServer::resetSrv(std_srvs::Empty::Request& req, std_srvs::Empty::Response& resp) { 00544 visualization_msgs::MarkerArray occupiedNodesVis; 00545 occupiedNodesVis.markers.resize(m_treeDepth +1); 00546 ros::Time rostime = ros::Time::now(); 00547 m_octoMap->octree.clear(); 00548 00549 ROS_INFO("Cleared octomap"); 00550 publishMap(rostime); 00551 for (unsigned i= 0; i < occupiedNodesVis.markers.size(); ++i){ 00552 00553 occupiedNodesVis.markers[i].header.frame_id = m_worldFrameId; 00554 occupiedNodesVis.markers[i].header.stamp = rostime; 00555 occupiedNodesVis.markers[i].ns = "map"; 00556 occupiedNodesVis.markers[i].id = i; 00557 occupiedNodesVis.markers[i].type = visualization_msgs::Marker::CUBE_LIST; 00558 occupiedNodesVis.markers[i].action = visualization_msgs::Marker::DELETE; 00559 } 00560 00561 00562 m_markerPub.publish(occupiedNodesVis); 00563 return true; 00564 } 00565 00566 void OctomapServer::publishMap(const ros::Time& rostime) const{ 00567 00568 octomap_ros::OctomapBinary map; 00569 map.header.frame_id = m_worldFrameId; 00570 map.header.stamp = rostime; 00571 00572 octomap::octomapMapToMsgData(m_octoMap->octree, map.data); 00573 00574 m_binaryMapPub.publish(map); 00575 } 00576 00577 00578 void OctomapServer::filterGroundPlane(const PCLPointCloud& pc, PCLPointCloud& ground, PCLPointCloud& nonground) const{ 00579 ground.header = pc.header; 00580 nonground.header = pc.header; 00581 00582 if (pc.size() < 50){ 00583 ROS_WARN("Pointcloud in OctomapServer too small, skipping ground plane extraction"); 00584 nonground = pc; 00585 } else { 00586 // plane detection for ground plane removal: 00587 pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); 00588 pcl::PointIndices::Ptr inliers (new pcl::PointIndices); 00589 00590 // Create the segmentation object and set up: 00591 pcl::SACSegmentation<pcl::PointXYZ> seg; 00592 seg.setOptimizeCoefficients (true); 00593 // TODO: maybe a filtering based on the surface normals might be more robust / accurate? 00594 seg.setModelType(pcl::SACMODEL_PERPENDICULAR_PLANE); 00595 seg.setMethodType(pcl::SAC_RANSAC); 00596 seg.setMaxIterations(200); 00597 seg.setDistanceThreshold (m_groundFilterDistance); 00598 seg.setAxis(Eigen::Vector3f(0,0,1)); 00599 seg.setEpsAngle(m_groundFilterAngle); 00600 00601 00602 PCLPointCloud cloud_filtered(pc); 00603 // Create the filtering object 00604 pcl::ExtractIndices<pcl::PointXYZ> extract; 00605 bool groundPlaneFound = false; 00606 00607 while(cloud_filtered.size() > 10 && !groundPlaneFound){ 00608 seg.setInputCloud(cloud_filtered.makeShared()); 00609 seg.segment (*inliers, *coefficients); 00610 if (inliers->indices.size () == 0){ 00611 ROS_WARN("No plane found in cloud."); 00612 00613 break; 00614 } 00615 00616 extract.setInputCloud(cloud_filtered.makeShared()); 00617 extract.setIndices(inliers); 00618 00619 if (std::abs(coefficients->values.at(3)) < m_groundFilterPlaneDistance){ 00620 ROS_DEBUG("Ground plane found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(), 00621 coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3)); 00622 extract.setNegative (false); 00623 extract.filter (ground); 00624 00625 // remove ground points from full pointcloud: 00626 // workaround for PCL bug: 00627 if(inliers->indices.size() != cloud_filtered.size()){ 00628 extract.setNegative(true); 00629 PCLPointCloud cloud_out; 00630 extract.filter(cloud_out); 00631 nonground += cloud_out; 00632 cloud_filtered = cloud_out; 00633 } 00634 00635 groundPlaneFound = true; 00636 } else{ 00637 ROS_DEBUG("Horizontal plane (not ground) found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(), 00638 coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3)); 00639 pcl::PointCloud<pcl::PointXYZ> cloud_out; 00640 extract.setNegative (false); 00641 extract.filter(cloud_out); 00642 nonground +=cloud_out; 00643 // debug 00644 // pcl::PCDWriter writer; 00645 // writer.write<pcl::PointXYZ>("nonground_plane.pcd",cloud_out, false); 00646 00647 // remove current plane from scan for next iteration: 00648 // workaround for PCL bug: 00649 if(inliers->indices.size() != cloud_filtered.size()){ 00650 extract.setNegative(true); 00651 cloud_out.points.clear(); 00652 extract.filter(cloud_out); 00653 cloud_filtered = cloud_out; 00654 } else{ 00655 cloud_filtered.points.clear(); 00656 } 00657 } 00658 00659 } 00660 // TODO: also do this if overall starting pointcloud too small? 00661 if (!groundPlaneFound){ // no plane found or remaining points too small 00662 ROS_WARN("No ground plane found in scan"); 00663 00664 // do a rough fitlering on height to prevent spurious obstacles 00665 pcl::PassThrough<pcl::PointXYZ> second_pass; 00666 second_pass.setFilterFieldName("z"); 00667 second_pass.setFilterLimits(-m_groundFilterPlaneDistance, m_groundFilterPlaneDistance); 00668 second_pass.setInputCloud(pc.makeShared()); 00669 second_pass.filter(ground); 00670 00671 second_pass.setFilterLimitsNegative (true); 00672 second_pass.filter(nonground); 00673 } 00674 00675 // debug: 00676 // pcl::PCDWriter writer; 00677 // if (pc_ground.size() > 0) 00678 // writer.write<pcl::PointXYZ>("ground.pcd",pc_ground, false); 00679 // if (pc_nonground.size() > 0) 00680 // writer.write<pcl::PointXYZ>("nonground.pcd",pc_nonground, false); 00681 00682 } 00683 00684 00685 } 00686 00687 void OctomapServer::handlePreNodeTraversal(const ros::Time& rostime){ 00688 m_publish2DMap = (m_latchedTopics || m_mapPub.getNumSubscribers() > 0); 00689 00690 00691 if (m_publish2DMap){ 00692 // init projected 2D map: 00693 m_gridmap.header.frame_id = m_worldFrameId; 00694 m_gridmap.header.stamp = rostime; 00695 bool sizeChanged = false; 00696 00697 // TODO: move most of this stuff into c'tor and init map only once (adjust if size changes) 00698 double minX, minY, minZ, maxX, maxY, maxZ; 00699 m_octoMap->octree.getMetricMin(minX, minY, minZ); 00700 m_octoMap->octree.getMetricMax(maxX, maxY, maxZ); 00701 00702 octomap::point3d minPt(minX, minY, minZ); 00703 octomap::point3d maxPt(maxX, maxY, maxZ); 00704 octomap::OcTreeKey minKey, maxKey, curKey; 00705 if (!m_octoMap->octree.genKey(minPt, minKey)){ 00706 ROS_ERROR("Could not create min OcTree key at %f %f %f", minPt.x(), minPt.y(), minPt.z()); 00707 return; 00708 } 00709 00710 if (!m_octoMap->octree.genKey(maxPt, maxKey)){ 00711 ROS_ERROR("Could not create max OcTree key at %f %f %f", maxPt.x(), maxPt.y(), maxPt.z()); 00712 return; 00713 } 00714 m_octoMap->octree.genKeyAtDepth(minKey, m_maxTreeDepth, minKey); 00715 m_octoMap->octree.genKeyAtDepth(maxKey, m_maxTreeDepth, maxKey); 00716 00717 ROS_DEBUG("MinKey: %d %d %d / MaxKey: %d %d %d", minKey[0], minKey[1], minKey[2], maxKey[0], maxKey[1], maxKey[2]); 00718 00719 // add padding if requested (= new min/maxPts in x&y): 00720 double halfPaddedX = 0.5*m_minSizeX; 00721 double halfPaddedY = 0.5*m_minSizeY; 00722 minX = std::min(minX, -halfPaddedX); 00723 maxX = std::max(maxX, halfPaddedX); 00724 minY = std::min(minY, -halfPaddedY); 00725 maxY = std::max(maxY, halfPaddedY); 00726 minPt = octomap::point3d(minX, minY, minZ); 00727 maxPt = octomap::point3d(maxX, maxY, maxZ); 00728 00729 OcTreeKey paddedMaxKey; 00730 if (!m_octoMap->octree.genKey(minPt, m_paddedMinKey)){ 00731 ROS_ERROR("Could not create padded min OcTree key at %f %f %f", minPt.x(), minPt.y(), minPt.z()); 00732 return; 00733 } 00734 if (!m_octoMap->octree.genKey(maxPt, paddedMaxKey)){ 00735 ROS_ERROR("Could not create padded max OcTree key at %f %f %f", maxPt.x(), maxPt.y(), maxPt.z()); 00736 return; 00737 } 00738 m_octoMap->octree.genKeyAtDepth(m_paddedMinKey, m_maxTreeDepth, m_paddedMinKey); 00739 m_octoMap->octree.genKeyAtDepth(paddedMaxKey, m_maxTreeDepth, paddedMaxKey); 00740 00741 ROS_DEBUG("Padded MinKey: %d %d %d / padded MaxKey: %d %d %d", m_paddedMinKey[0], m_paddedMinKey[1], m_paddedMinKey[2], paddedMaxKey[0], paddedMaxKey[1], paddedMaxKey[2]); 00742 assert(paddedMaxKey[0] >= maxKey[0] && paddedMaxKey[1] >= maxKey[1]); 00743 00744 m_multires2DScale = 1 << (m_treeDepth - m_maxTreeDepth); 00745 unsigned int newWidth = (paddedMaxKey[0] - m_paddedMinKey[0])/m_multires2DScale +1; 00746 unsigned int newHeight = (paddedMaxKey[1] - m_paddedMinKey[1])/m_multires2DScale +1; 00747 00748 if (newWidth != m_gridmap.info.width || newHeight != m_gridmap.info.height) 00749 sizeChanged = true; 00750 00751 m_gridmap.info.width = newWidth; 00752 m_gridmap.info.height = newHeight; 00753 int mapOriginX = minKey[0] - m_paddedMinKey[0]; 00754 int mapOriginY = minKey[1] - m_paddedMinKey[1]; 00755 assert(mapOriginX >= 0 && mapOriginY >= 0); 00756 00757 // might not exactly be min / max of octree: 00758 octomap::point3d origin; 00759 m_octoMap->octree.genCoords(m_paddedMinKey, m_treeDepth, origin); 00760 double gridRes = m_octoMap->octree.getNodeSize(m_maxTreeDepth); 00761 m_gridmap.info.resolution = gridRes; 00762 m_gridmap.info.origin.position.x = origin.x() - gridRes*0.5; 00763 m_gridmap.info.origin.position.y = origin.y() - gridRes*0.5; 00764 if (m_maxTreeDepth != m_treeDepth){ 00765 m_gridmap.info.origin.position.x -= m_res/2.0; 00766 m_gridmap.info.origin.position.y -= m_res/2.0; 00767 00768 } 00769 00770 00771 if (sizeChanged){ 00772 ROS_INFO("2D grid map size changed to %d x %d", m_gridmap.info.width, m_gridmap.info.height); 00773 m_gridmap.data.clear(); 00774 // init to unknown: 00775 m_gridmap.data.resize(m_gridmap.info.width * m_gridmap.info.height, -1); 00776 } 00777 00778 00779 } 00780 00781 } 00782 00783 void OctomapServer::handlePostNodeTraversal(const ros::Time& rostime){ 00784 00785 if (m_publish2DMap) 00786 m_mapPub.publish(m_gridmap); 00787 00788 } 00789 00790 void OctomapServer::handleOccupiedNode(const OcTreeROS::OcTreeType::iterator& it){ 00791 00792 // update 2D map (occupied always overrides): 00793 if (m_publish2DMap){ 00794 if (it.getDepth() == m_maxTreeDepth){ 00795 int i = (it.getKey()[0] - m_paddedMinKey[0])/m_multires2DScale; 00796 int j = (it.getKey()[1] - m_paddedMinKey[1])/m_multires2DScale; 00797 m_gridmap.data[m_gridmap.info.width*j + i] = 100; 00798 } else{ 00799 int intSize = 1 << (m_maxTreeDepth - it.getDepth()); 00800 octomap::OcTreeKey minKey=it.getIndexKey(); 00801 for(int dx=0; dx < intSize; dx++){ 00802 int i = (minKey[0]+dx - m_paddedMinKey[0])/m_multires2DScale; 00803 for(int dy=0; dy < intSize; dy++){ 00804 int j = (minKey[1]+dy - m_paddedMinKey[1])/m_multires2DScale; 00805 m_gridmap.data[m_gridmap.info.width*j + i] = 100; 00806 } 00807 } 00808 } 00809 } 00810 00811 } 00812 00813 void OctomapServer::handleFreeNode(const OcTreeROS::OcTreeType::iterator& it){ 00814 00815 if (m_publish2DMap){ 00816 if (it.getDepth() == m_maxTreeDepth){ 00817 int i = (it.getKey()[0] - m_paddedMinKey[0])/m_multires2DScale; 00818 int j = (it.getKey()[1] - m_paddedMinKey[1])/m_multires2DScale; 00819 if (m_gridmap.data[m_gridmap.info.width*j + i] == -1){ 00820 m_gridmap.data[m_gridmap.info.width*j + i] = 0; 00821 } 00822 } else{ 00823 int intSize = 1 << (m_treeDepth - it.getDepth()); 00824 octomap::OcTreeKey minKey=it.getIndexKey(); 00825 for(int dx=0; dx < intSize; dx++){ 00826 int i = (minKey[0]+dx - m_paddedMinKey[0])/m_multires2DScale; 00827 for(int dy=0; dy < intSize; dy++){ 00828 int j = (minKey[1]+dy - m_paddedMinKey[1])/m_multires2DScale; 00829 if (m_gridmap.data[m_gridmap.info.width*j + i] == -1){ 00830 m_gridmap.data[m_gridmap.info.width*j + i] = 0; 00831 } 00832 } 00833 } 00834 } 00835 } 00836 } 00837 00838 00839 00840 bool OctomapServer::isSpeckleNode(const OcTreeKey&nKey) const { 00841 OcTreeKey key; 00842 bool neighborFound = false; 00843 for (key[2] = nKey[2] - 1; !neighborFound && key[2] <= nKey[2] + 1; ++key[2]){ 00844 for (key[1] = nKey[1] - 1; !neighborFound && key[1] <= nKey[1] + 1; ++key[1]){ 00845 for (key[0] = nKey[0] - 1; !neighborFound && key[0] <= nKey[0] + 1; ++key[0]){ 00846 if (key != nKey){ 00847 OcTreeNode* node = m_octoMap->octree.search(key); 00848 if (node && m_octoMap->octree.isNodeOccupied(node)){ 00849 // we have a neighbor => break! 00850 neighborFound = true; 00851 } 00852 } 00853 } 00854 } 00855 } 00856 00857 return neighborFound; 00858 } 00859 00860 void OctomapServer::reconfigureCallback(octomap_server::OctomapServerConfig& config, uint32_t level){ 00861 if (m_maxTreeDepth != unsigned(config.max_depth)){ 00862 m_maxTreeDepth = unsigned(config.max_depth); 00863 00864 if (m_latchedTopics) 00865 publishAll(); 00866 00867 } 00868 00869 00870 } 00871 00872 std_msgs::ColorRGBA OctomapServer::heightMapColor(double h) const { 00873 00874 std_msgs::ColorRGBA color; 00875 color.a = 1.0; 00876 // blend over HSV-values (more colors) 00877 00878 double s = 1.0; 00879 double v = 1.0; 00880 00881 h -= floor(h); 00882 h *= 6; 00883 int i; 00884 double m, n, f; 00885 00886 i = floor(h); 00887 f = h - i; 00888 if (!(i & 1)) 00889 f = 1 - f; // if i is even 00890 m = v * (1 - s); 00891 n = v * (1 - s * f); 00892 00893 switch (i) { 00894 case 6: 00895 case 0: 00896 color.r = v; color.g = n; color.b = m; 00897 break; 00898 case 1: 00899 color.r = n; color.g = v; color.b = m; 00900 break; 00901 case 2: 00902 color.r = m; color.g = v; color.b = n; 00903 break; 00904 case 3: 00905 color.r = m; color.g = n; color.b = v; 00906 break; 00907 case 4: 00908 color.r = n; color.g = m; color.b = v; 00909 break; 00910 case 5: 00911 color.r = v; color.g = m; color.b = n; 00912 break; 00913 default: 00914 color.r = 1; color.g = 0.5; color.b = 0.5; 00915 break; 00916 } 00917 00918 return color; 00919 } 00920 } 00921 00922 00923