/* Copyright (c) 2010-2014, Mathieu Labbe - IntRoLab - Universite de Sherbrooke All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Universite de Sherbrooke nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "rtabmap/core/Graph.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rtabmap { namespace graph { bool exportPoses( const std::string & filePath, int format, // 0=Raw, 1=RGBD-SLAM, 2=KITTI, 3=TORO, 4=g2o const std::map & poses, const std::multimap & constraints, // required for formats 3 and 4 const std::map & stamps, // required for format 1 bool g2oRobust) // optional for format 4 { UDEBUG("%s", filePath.c_str()); std::string tmpPath = filePath; if(format==3) // TORO { if(UFile::getExtension(tmpPath).empty()) { tmpPath+=".graph"; } return OptimizerTORO::saveGraph(tmpPath, poses, constraints); } else if(format == 4) // g2o { if(UFile::getExtension(tmpPath).empty()) { tmpPath+=".g2o"; } #ifdef WITH_G2O return OptimizerG2O::saveGraph(tmpPath, poses, constraints, g2oRobust); #else UERROR("Cannot export in g2o format because RTAB-Map is not built with g2o support!"); return false; #endif } else { if(UFile::getExtension(tmpPath).empty()) { tmpPath+=".txt"; } if(format == 1) { if(stamps.size() != poses.size()) { UERROR("When exporting poses to format 1 (RGBD-SLAM), stamps and poses maps should have the same size!"); return false; } } FILE* fout = 0; #ifdef _MSC_VER fopen_s(&fout, tmpPath.c_str(), "w"); #else fout = fopen(tmpPath.c_str(), "w"); #endif if(fout) { for(std::map::const_iterator iter=poses.begin(); iter!=poses.end(); ++iter) { if(format == 1) // rgbd-slam format { // put the pose in rgbd-slam world reference Transform t( 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, 0); Transform pose = t.inverse() * iter->second; t = Transform( 0, 0, 1, 0, -1, 0, 0, 0, 0,-1, 0, 0); pose = t.inverse() * pose * t; // Format: stamp x y z qx qy qz qw Eigen::Quaternionf q = pose.getQuaternionf(); UASSERT(uContains(stamps, iter->first)); fprintf(fout, "%f %f %f %f %f %f %f %f\n", stamps.at(iter->first), pose.x(), pose.y(), pose.z(), q.x(), q.y(), q.z(), q.w()); } else // default / KITTI format { Transform pose = iter->second; if(format == 2) { // for KITTI, we need to remove optical rotation // z pointing front, x left, y down Transform t( 0, 0, 1, 0, -1, 0, 0, 0, 0,-1, 0, 0); pose = t.inverse() * pose * t; } // Format: r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz const float * p = (const float *)pose.data(); fprintf(fout, "%f", p[0]); for(int i=1; i & poses, std::multimap * constraints, // optional for formats 3 and 4 std::map * stamps) // optional for format 1 { UDEBUG("%s format=%d", filePath.c_str(), format); if(format==3) // TORO { std::multimap constraintsTmp; if(OptimizerTORO::loadGraph(filePath, poses, constraintsTmp)) { if(constraints) { *constraints = constraintsTmp; } return true; } return false; } else if(format == 4) // g2o { std::multimap constraintsTmp; UERROR("Cannot import from g2o format because it is not yet supported!"); return false; } else { std::ifstream file; file.open(filePath.c_str(), std::ifstream::in); if(!file.good()) { return false; } int id=1; GeodeticCoords origin; Transform originPose; while(file.good()) { std::string str; std::getline(file, str); if(str.empty() || str.at(0) == '#' || str.at(0) == '%') { continue; } if(format == 8) // Karlsruhe format { std::vector strList = uListToVector(uSplit(str)); if(strList.size() == 10) { // timestamp,lat,lon,alt,x,y,z,roll,pitch,yaw // stamp is 1252400096825289728, transform to 1252400096.825289728 double stamp = uStr2Double(strList[0].insert(10, 1, '.')); double x = uStr2Double(strList[4]); double y = uStr2Double(strList[5]); double z = uStr2Double(strList[6]); double roll = uStr2Double(strList[8]); double pitch = uStr2Double(strList[7]); double yaw = -(uStr2Double(strList[9])-M_PI_2); if(stamps) { stamps->insert(std::make_pair(id, stamp)); } Transform pose = Transform(x,y,z,roll,pitch,yaw); if(poses.size() == 0) { originPose = pose.inverse(); } pose = originPose * pose; // transform in local coordinate where first value is the origin poses.insert(std::make_pair(id, pose)); } else { UERROR("Error parsing \"%s\" with Karlsruhe format (should have 10 values)", str.c_str()); } } else if(format == 7) // St Lucia format { std::vector strList = uListToVector(uSplit(str)); if(strList.size() == 12) { // Data Type //0=Timestamp (seconds) //1=Timestamp (millisec) //2=Latitude (deg) //3=Longitude (deg) //4=Altitude (m) //5=Height AMSL (m) //6=vENU X //7=vENU Y //8=vENU Z //9=Roll (rad) //10=Pitch (rad) //11=Yaw (rad) // the millisec str needs 0-padding if size < 6 std::string millisecStr = strList[1]; while(millisecStr.size() < 6) { millisecStr = "0" + millisecStr; } double stamp = uStr2Double(strList[0] + "." + millisecStr); // conversion GPS to local coordinate XYZ double longitude = uStr2Double(strList[2]); double latitude = uStr2Double(strList[3]); double altitude = uStr2Double(strList[4]); if(poses.empty()) { origin = GeodeticCoords(longitude, latitude, altitude); } cv::Point3d coordENU = GeodeticCoords(longitude, latitude, altitude).toENU_WGS84(origin); double roll = uStr2Double(strList[10]); double pitch = uStr2Double(strList[9]); double yaw = -(uStr2Double(strList[11])-M_PI_2); if(stamps) { stamps->insert(std::make_pair(id, stamp)); } poses.insert(std::make_pair(id, Transform(coordENU.x,coordENU.y,coordENU.z, roll,pitch,yaw))); } else { UERROR("Error parsing \"%s\" with St Lucia format (should have 12 values, e.g., 101215_153851_Ins0.log)", str.c_str()); } } else if(format == 6) // MALAGA URBAN format { std::vector strList = uListToVector(uSplit(str)); if(strList.size() == 25) { // 0=Time // Lat Lon Alt fix #sats speed dir // 8=Local_X // 9=Local_Y // 10=Local_Z // rawlog_ID Geocen_X Geocen_Y Geocen_Z GPS_X GPS_Y GPS_Z GPS_VX GPS_VY GPS_VZ Local_VX Local_VY Local_VZ SAT_Time double stamp = uStr2Double(strList[0]); double x = uStr2Double(strList[8]); double y = uStr2Double(strList[9]); double z = uStr2Double(strList[10]); if(stamps) { stamps->insert(std::make_pair(id, stamp)); } float yaw = 0.0f; if(uContains(poses, id-1)) { // set yaw depending on successive poses Transform & previousPose = poses.at(id-1); yaw = atan2(y-previousPose.y(),x-previousPose.x()); previousPose = Transform(previousPose.x(), previousPose.y(), yaw); } poses.insert(std::make_pair(id, Transform(x,y,z,0,0,yaw))); } else { UERROR("Error parsing \"%s\" with Malaga Urban format (should have 25 values, *_GPS.txt)", str.c_str()); } } else if(format == 5) // NewCollege format { std::vector strList = uListToVector(uSplit(str)); if(strList.size() == 3) { if( uIsNumber(uReplaceChar(strList[0], ' ', "")) && uIsNumber(uReplaceChar(strList[1], ' ', "")) && uIsNumber(uReplaceChar(strList[2], ' ', "")) && (strList.size()==3 || uIsNumber(uReplaceChar(strList[3], ' ', "")))) { double stamp = uStr2Double(uReplaceChar(strList[0], ' ', "")); double x = uStr2Double(uReplaceChar(strList[1], ' ', "")); double y = uStr2Double(uReplaceChar(strList[2], ' ', "")); if(stamps) { stamps->insert(std::make_pair(id, stamp)); } float yaw = 0.0f; if(uContains(poses, id-1)) { // set yaw depending on successive poses Transform & previousPose = poses.at(id-1); yaw = atan2(y-previousPose.y(),x-previousPose.x()); previousPose = Transform(previousPose.x(), previousPose.y(), yaw); } Transform pose = Transform(x,y,0,0,0,yaw); if(poses.size() == 0) { originPose = pose.inverse(); } pose = originPose * pose; // transform in local coordinate where first value is the origin poses.insert(std::make_pair(id, pose)); } else { UDEBUG("Not valid values detected: \"%s\"", str.c_str()); } } else { UERROR("Error parsing \"%s\" with NewCollege format (should have 3 values: stamp x y)", str.c_str()); } } else if(format == 1) // rgbd-slam format { std::list strList = uSplit(str); if(strList.size() == 8) { double stamp = uStr2Double(strList.front()); strList.pop_front(); str = uJoin(strList, " "); Transform pose = Transform::fromString(str); if(pose.isNull()) { UWARN("Null transform read!? line parsed: \"%s\"", str.c_str()); } else { if(stamps) { stamps->insert(std::make_pair(id, stamp)); } // we need to remove optical rotation // z pointing front, x left, y down Transform t( 0, 0, 1, 0, -1, 0, 0, 0, 0,-1, 0, 0); pose = t * pose * t.inverse(); t = Transform( 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, 0); pose = t*pose; poses.insert(std::make_pair(id, pose)); } } else { UERROR("Error parsing \"%s\" with RGBD-SLAM format (should have 8 values: stamp x y z qw qx qy qz)", str.c_str()); } } else // default / KITTI format { Transform pose = Transform::fromString(str); if(format == 2) { // for KITTI, we need to remove optical rotation // z pointing front, x left, y down Transform t( 0, 0, 1, 0, -1, 0, 0, 0, 0,-1, 0, 0); pose = t * pose * t.inverse(); } poses.insert(std::make_pair(id, pose)); } ++id; } file.close(); return true; } return false; } //////////////////////////////////////////// // Graph utilities //////////////////////////////////////////// std::multimap::iterator findLink( std::multimap & links, int from, int to, bool checkBothWays) { std::multimap::iterator iter = links.find(from); while(iter != links.end() && iter->first == from) { if(iter->second.to() == to) { return iter; } ++iter; } if(checkBothWays) { // let's try to -> from iter = links.find(to); while(iter != links.end() && iter->first == to) { if(iter->second.to() == from) { return iter; } ++iter; } } return links.end(); } std::multimap::iterator findLink( std::multimap & links, int from, int to, bool checkBothWays) { std::multimap::iterator iter = links.find(from); while(iter != links.end() && iter->first == from) { if(iter->second == to) { return iter; } ++iter; } if(checkBothWays) { // let's try to -> from iter = links.find(to); while(iter != links.end() && iter->first == to) { if(iter->second == from) { return iter; } ++iter; } } return links.end(); } std::multimap::const_iterator findLink( const std::multimap & links, int from, int to, bool checkBothWays) { std::multimap::const_iterator iter = links.find(from); while(iter != links.end() && iter->first == from) { if(iter->second.to() == to) { return iter; } ++iter; } if(checkBothWays) { // let's try to -> from iter = links.find(to); while(iter != links.end() && iter->first == to) { if(iter->second.to() == from) { return iter; } ++iter; } } return links.end(); } std::multimap::const_iterator findLink( const std::multimap & links, int from, int to, bool checkBothWays) { std::multimap::const_iterator iter = links.find(from); while(iter != links.end() && iter->first == from) { if(iter->second == to) { return iter; } ++iter; } if(checkBothWays) { // let's try to -> from iter = links.find(to); while(iter != links.end() && iter->first == to) { if(iter->second == from) { return iter; } ++iter; } } return links.end(); } std::map frustumPosesFiltering( const std::map & poses, const Transform & cameraPose, float horizontalFOV, // in degrees, xfov = atan((image_width/2)/fx)*2 float verticalFOV, // in degrees, yfov = atan((image_height/2)/fy)*2 float nearClipPlaneDistance, float farClipPlaneDistance, bool negative) { std::map output; if(poses.size()) { pcl::PointCloud::Ptr cloud(new pcl::PointCloud); std::vector ids(poses.size()); cloud->resize(poses.size()); ids.resize(poses.size()); int oi=0; for(std::map::const_iterator iter=poses.begin(); iter!=poses.end(); ++iter) { if(!iter->second.isNull()) { (*cloud)[oi] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); ids[oi++] = iter->first; } } cloud->resize(oi); ids.resize(oi); pcl::IndicesPtr indices = util3d::frustumFiltering( cloud, pcl::IndicesPtr(new std::vector), cameraPose, horizontalFOV, verticalFOV, nearClipPlaneDistance, farClipPlaneDistance, negative); for(unsigned int i=0; isize(); ++i) { output.insert(*poses.find(ids[indices->at(i)])); } } return output; } std::map radiusPosesFiltering( const std::map & poses, float radius, float angle, bool keepLatest) { if(poses.size() > 1 && radius > 0.0f) { pcl::PointCloud::Ptr cloud(new pcl::PointCloud); cloud->resize(poses.size()); int i=0; for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter, ++i) { (*cloud)[i] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); UASSERT_MSG(pcl::isFinite((*cloud)[i]), uFormat("Invalid pose (%d) %s", iter->first, iter->second.prettyPrint().c_str()).c_str()); } // radius filtering std::vector names = uKeys(poses); std::vector transforms = uValues(poses); pcl::search::KdTree::Ptr tree (new pcl::search::KdTree (false)); tree->setInputCloud(cloud); std::set indicesChecked; std::set indicesKept; for(unsigned int i=0; isize(); ++i) { if(indicesChecked.find(i) == indicesChecked.end()) { std::vector kIndices; std::vector kDistances; tree->radiusSearch(cloud->at(i), radius, kIndices, kDistances); std::set cloudIndices; const Transform & currentT = transforms.at(i); Eigen::Vector3f vA = currentT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); for(unsigned int j=0; j 0.0f) { const Transform & checkT = transforms.at(kIndices[j]); // same orientation? Eigen::Vector3f vB = checkT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); double a = pcl::getAngle3D(Eigen::Vector4f(vA[0], vA[1], vA[2], 0), Eigen::Vector4f(vB[0], vB[1], vB[2], 0)); if(a <= angle) { cloudIndices.insert(kIndices[j]); } } else { cloudIndices.insert(kIndices[j]); } } } if(keepLatest) { bool lastAdded = false; for(std::set::reverse_iterator iter = cloudIndices.rbegin(); iter!=cloudIndices.rend(); ++iter) { if(!lastAdded) { indicesKept.insert(*iter); lastAdded = true; } indicesChecked.insert(*iter); } } else { bool firstAdded = false; for(std::set::iterator iter = cloudIndices.begin(); iter!=cloudIndices.end(); ++iter) { if(!firstAdded) { indicesKept.insert(*iter); firstAdded = true; } indicesChecked.insert(*iter); } } } } //pcl::IndicesPtr indicesOut(new std::vector); //indicesOut->insert(indicesOut->end(), indicesKept.begin(), indicesKept.end()); UINFO("Cloud filtered In = %d, Out = %d", cloud->size(), indicesKept.size()); //pcl::io::savePCDFile("duplicateIn.pcd", *cloud); //pcl::io::savePCDFile("duplicateOut.pcd", *cloud, *indicesOut); std::map keptPoses; for(std::set::iterator iter = indicesKept.begin(); iter!=indicesKept.end(); ++iter) { keptPoses.insert(std::make_pair(names.at(*iter), transforms.at(*iter))); } return keptPoses; } else { return poses; } } std::multimap radiusPosesClustering(const std::map & poses, float radius, float angle) { std::multimap clusters; if(poses.size() > 1 && radius > 0.0f) { pcl::PointCloud::Ptr cloud(new pcl::PointCloud); cloud->resize(poses.size()); int i=0; for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter, ++i) { (*cloud)[i] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); UASSERT_MSG(pcl::isFinite((*cloud)[i]), uFormat("Invalid pose (%d) %s", iter->first, iter->second.prettyPrint().c_str()).c_str()); } // radius clustering (nearest neighbors) std::vector ids = uKeys(poses); std::vector transforms = uValues(poses); pcl::search::KdTree::Ptr tree (new pcl::search::KdTree (false)); tree->setInputCloud(cloud); for(unsigned int i=0; isize(); ++i) { std::vector kIndices; std::vector kDistances; tree->radiusSearch(cloud->at(i), radius, kIndices, kDistances); std::set cloudIndices; const Transform & currentT = transforms.at(i); Eigen::Vector3f vA = currentT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); for(unsigned int j=0; j 0.0f) { const Transform & checkT = transforms.at(kIndices[j]); // same orientation? Eigen::Vector3f vB = checkT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); double a = pcl::getAngle3D(Eigen::Vector4f(vA[0], vA[1], vA[2], 0), Eigen::Vector4f(vB[0], vB[1], vB[2], 0)); if(a <= angle) { clusters.insert(std::make_pair(ids[i], ids[kIndices[j]])); } } else { clusters.insert(std::make_pair(ids[i], ids[kIndices[j]])); } } } } } return clusters; } void reduceGraph( const std::map & poses, const std::multimap & links, std::multimap & hyperNodes, // std::multimap & hyperLinks) { UINFO("Input: poses=%d links=%d", (int)poses.size(), (int)links.size()); UTimer timer; std::map posesToHyperNodes; std::map > clusterloopClosureLinks; { std::multimap bidirectionalLoopClosureLinks; for(std::multimap::const_iterator jter=links.begin(); jter!=links.end(); ++jter) { if(jter->second.type() != Link::kNeighbor && jter->second.type() != Link::kNeighborMerged && jter->second.userDataCompressed().empty()) { if(uContains(poses, jter->second.from()) && uContains(poses, jter->second.to())) { UASSERT_MSG(graph::findLink(links, jter->second.to(), jter->second.from(), false) == links.end(), "Input links should be unique!"); bidirectionalLoopClosureLinks.insert(std::make_pair(jter->second.from(), jter->second)); //bidirectionalLoopClosureLinks.insert(std::make_pair(jter->second.to(), jter->second.inverse())); } } } UINFO("Clustering hyper nodes..."); // largest ID to smallest ID for(std::map::const_reverse_iterator iter=poses.rbegin(); iter!=poses.rend(); ++iter) { if(posesToHyperNodes.find(iter->first) == posesToHyperNodes.end()) { int hyperNodeId = iter->first; std::list loopClosures; std::set loopClosuresAdded; loopClosures.push_back(iter->first); std::multimap clusterLinks; while(loopClosures.size()) { int id = loopClosures.front(); loopClosures.pop_front(); UASSERT(posesToHyperNodes.find(id) == posesToHyperNodes.end()); posesToHyperNodes.insert(std::make_pair(id, hyperNodeId)); hyperNodes.insert(std::make_pair(hyperNodeId, id)); for(std::multimap::const_iterator jter=bidirectionalLoopClosureLinks.find(id); jter!=bidirectionalLoopClosureLinks.end() && jter->first==id; ++jter) { if(posesToHyperNodes.find(jter->second.to()) == posesToHyperNodes.end() && loopClosuresAdded.find(jter->second.to()) == loopClosuresAdded.end()) { loopClosures.push_back(jter->second.to()); loopClosuresAdded.insert(jter->second.to()); clusterLinks.insert(*jter); clusterLinks.insert(std::make_pair(jter->second.to(), jter->second.inverse())); if(jter->second.from() < jter->second.to()) { UWARN("Child to Parent link? %d->%d (type=%d)", jter->second.from(), jter->second.to(), jter->second.type()); } } } } UASSERT(clusterloopClosureLinks.find(hyperNodeId) == clusterloopClosureLinks.end()); clusterloopClosureLinks.insert(std::make_pair(hyperNodeId, clusterLinks)); UDEBUG("Created hyper node %d with %d children (%f%%)", hyperNodeId, (int)loopClosuresAdded.size(), float(posesToHyperNodes.size())/float(poses.size())*100.0f); } } UINFO("Clustering hyper nodes... done! (%f s)", timer.ticks()); } UINFO("Creating hyper links..."); int i=0; for(std::multimap::const_reverse_iterator jter=links.rbegin(); jter!=links.rend(); ++jter) { if((jter->second.type() == Link::kNeighbor || jter->second.type() == Link::kNeighborMerged || !jter->second.userDataCompressed().empty()) && uContains(poses, jter->second.from()) && uContains(poses, jter->second.to())) { UASSERT_MSG(uContains(posesToHyperNodes, jter->second.from()), uFormat("%d->%d (type=%d)", jter->second.from(), jter->second.to(), jter->second.type()).c_str()); UASSERT_MSG(uContains(posesToHyperNodes, jter->second.to()), uFormat("%d->%d (type=%d)", jter->second.from(), jter->second.to(), jter->second.type()).c_str()); int hyperNodeIDFrom = posesToHyperNodes.at(jter->second.from()); int hyperNodeIDTo = posesToHyperNodes.at(jter->second.to()); // ignore links inside a hyper node if(hyperNodeIDFrom != hyperNodeIDTo) { std::multimap::iterator tmpIter = graph::findLink(hyperLinks, hyperNodeIDFrom, hyperNodeIDTo); if(tmpIter!=hyperLinks.end() && hyperNodeIDFrom == jter->second.from() && hyperNodeIDTo == jter->second.to() && tmpIter->second.type() > Link::kNeighbor && jter->second.type() == Link::kNeighbor) { // neighbor links have priority, so remove the previously added link hyperLinks.erase(tmpIter); } // only add unique link between two hyper nodes (keeping only the more recent) if(graph::findLink(hyperLinks, hyperNodeIDFrom, hyperNodeIDTo) == hyperLinks.end()) { UASSERT(clusterloopClosureLinks.find(hyperNodeIDFrom) != clusterloopClosureLinks.end()); UASSERT(clusterloopClosureLinks.find(hyperNodeIDTo) != clusterloopClosureLinks.end()); std::multimap tmpLinks = clusterloopClosureLinks.at(hyperNodeIDFrom); tmpLinks.insert(clusterloopClosureLinks.at(hyperNodeIDTo).begin(), clusterloopClosureLinks.at(hyperNodeIDTo).end()); tmpLinks.insert(std::make_pair(jter->second.from(), jter->second)); std::list path = computePath(tmpLinks, hyperNodeIDFrom, hyperNodeIDTo, false, true); UASSERT_MSG(path.size()>1, uFormat("path.size()=%d, hyperNodeIDFrom=%d, hyperNodeIDTo=%d", (int)path.size(), hyperNodeIDFrom, hyperNodeIDTo).c_str()); if(path.size() > 10) { UWARN("Large path! %d nodes", (int)path.size()); std::stringstream stream; for(std::list::const_iterator iter=path.begin(); iter!=path.end();++iter) { if(iter!=path.begin()) { stream << ","; } stream << *iter; } UWARN("Path = [%s]", stream.str().c_str()); } // create the hyperlink std::list::iterator iter=path.begin(); int from = *iter; ++iter; int to = *iter; std::multimap::const_iterator foundIter = graph::findLink(tmpLinks, from, to, false); UASSERT(foundIter != tmpLinks.end()); Link hyperLink = foundIter->second; ++iter; from = to; for(; iter!=path.end(); ++iter) { to = *iter; std::multimap::const_iterator foundIter = graph::findLink(tmpLinks, from, to, false); UASSERT(foundIter != tmpLinks.end()); hyperLink = hyperLink.merge(foundIter->second, jter->second.type()); from = to; } UASSERT(hyperLink.from() == hyperNodeIDFrom); UASSERT(hyperLink.to() == hyperNodeIDTo); hyperLinks.insert(std::make_pair(hyperNodeIDFrom, hyperLink)); UDEBUG("Created hyper link %d->%d (%f%%)", hyperLink.from(), hyperLink.to(), float(i)/float(links.size())*100.0f); if(hyperLink.transform().getNorm() > jter->second.transform().getNorm()+1) { UWARN("Large hyper link %d->%d (%f m)! original %d->%d (%f m)", hyperLink.from(), hyperLink.to(), hyperLink.transform().getNorm(), jter->second.from(), jter->second.to(), jter->second.transform().getNorm()); } } } } ++i; } UINFO("Creating hyper links... done! (%f s)", timer.ticks()); UINFO("Output: poses=%d links=%d", (int)uUniqueKeys(hyperNodes).size(), (int)links.size()); } class Node { public: Node(int id, int fromId, const rtabmap::Transform & pose) : id_(id), costSoFar_(0.0f), distToEnd_(0.0f), fromId_(fromId), closed_(false), pose_(pose) {} int id() const {return id_;} int fromId() const {return fromId_;} bool isClosed() const {return closed_;} bool isOpened() const {return !closed_;} float costSoFar() const {return costSoFar_;} // Dijkstra cost float distToEnd() const {return distToEnd_;} // Breath-first cost float totalCost() const {return costSoFar_ + distToEnd_;} // A* cost rtabmap::Transform pose() const {return pose_;} float distFrom(const rtabmap::Transform & pose) const { return pose_.getDistance(pose); // use sqrt distance } void setClosed(bool closed) {closed_ = closed;} void setFromId(int fromId) {fromId_ = fromId;} void setCostSoFar(float costSoFar) {costSoFar_ = costSoFar;} void setDistToEnd(float distToEnd) {distToEnd_ = distToEnd;} void setPose(const Transform & pose) {pose_ = pose;} private: int id_; float costSoFar_; float distToEnd_; int fromId_; bool closed_; rtabmap::Transform pose_; }; typedef std::pair Pair; // first is id, second is cost struct Order { bool operator()(Pair const& a, Pair const& b) const { return a.second > b.second; } }; // A* std::list > computePath( const std::map & poses, const std::multimap & links, int from, int to, bool updateNewCosts) { std::list > path; int startNode = from; int endNode = to; rtabmap::Transform endPose = poses.at(endNode); std::map nodes; nodes.insert(std::make_pair(startNode, Node(startNode, 0, poses.at(startNode)))); std::priority_queue, Order> pq; std::multimap pqmap; if(updateNewCosts) { pqmap.insert(std::make_pair(0, startNode)); } else { pq.push(Pair(startNode, 0)); } while((updateNewCosts && pqmap.size()) || (!updateNewCosts && pq.size())) { Node * currentNode; if(updateNewCosts) { currentNode = &nodes.find(pqmap.begin()->second)->second; pqmap.erase(pqmap.begin()); } else { currentNode = &nodes.find(pq.top().first)->second; pq.pop(); } currentNode->setClosed(true); if(currentNode->id() == endNode) { while(currentNode->id()!=startNode) { path.push_front(std::make_pair(currentNode->id(), currentNode->pose())); currentNode = &nodes.find(currentNode->fromId())->second; } path.push_front(std::make_pair(startNode, poses.at(startNode))); break; } // lookup neighbors for(std::multimap::const_iterator iter = links.find(currentNode->id()); iter!=links.end() && iter->first == currentNode->id(); ++iter) { std::map::iterator nodeIter = nodes.find(iter->second); if(nodeIter == nodes.end()) { std::map::const_iterator poseIter = poses.find(iter->second); if(poseIter == poses.end()) { UERROR("Next pose %d (from %d) should be found in poses! Ignoring it!", iter->second, iter->first); } else { Node n(iter->second, currentNode->id(), poseIter->second); n.setCostSoFar(currentNode->costSoFar() + currentNode->distFrom(poseIter->second)); n.setDistToEnd(n.distFrom(endPose)); nodes.insert(std::make_pair(iter->second, n)); if(updateNewCosts) { pqmap.insert(std::make_pair(n.totalCost(), n.id())); } else { pq.push(Pair(n.id(), n.totalCost())); } } } else if(updateNewCosts && nodeIter->second.isOpened()) { float newCostSoFar = currentNode->costSoFar() + currentNode->distFrom(nodeIter->second.pose()); if(nodeIter->second.costSoFar() > newCostSoFar) { // update the cost in the priority queue for(std::multimap::iterator mapIter=pqmap.begin(); mapIter!=pqmap.end(); ++mapIter) { if(mapIter->second == nodeIter->first) { pqmap.erase(mapIter); nodeIter->second.setCostSoFar(newCostSoFar); pqmap.insert(std::make_pair(nodeIter->second.totalCost(), nodeIter->first)); break; } } } } } } return path; } // Dijksta std::list RTABMAP_EXP computePath( const std::multimap & links, int from, int to, bool updateNewCosts, bool useSameCostForAllLinks) { std::list path; int startNode = from; int endNode = to; std::map nodes; nodes.insert(std::make_pair(startNode, Node(startNode, 0, Transform()))); std::priority_queue, Order> pq; std::multimap pqmap; if(updateNewCosts) { pqmap.insert(std::make_pair(0, startNode)); } else { pq.push(Pair(startNode, 0)); } while((updateNewCosts && pqmap.size()) || (!updateNewCosts && pq.size())) { Node * currentNode; if(updateNewCosts) { currentNode = &nodes.find(pqmap.begin()->second)->second; pqmap.erase(pqmap.begin()); } else { currentNode = &nodes.find(pq.top().first)->second; pq.pop(); } currentNode->setClosed(true); if(currentNode->id() == endNode) { while(currentNode->id()!=startNode) { path.push_front(currentNode->id()); currentNode = &nodes.find(currentNode->fromId())->second; } path.push_front(startNode); break; } // lookup neighbors for(std::multimap::const_iterator iter = links.find(currentNode->id()); iter!=links.end() && iter->first == currentNode->id(); ++iter) { std::map::iterator nodeIter = nodes.find(iter->second.to()); float cost = 1; if(!useSameCostForAllLinks) { cost = iter->second.transform().getNorm(); } if(nodeIter == nodes.end()) { Node n(iter->second.to(), currentNode->id(), Transform()); n.setCostSoFar(currentNode->costSoFar() + cost); nodes.insert(std::make_pair(iter->second.to(), n)); if(updateNewCosts) { pqmap.insert(std::make_pair(n.totalCost(), n.id())); } else { pq.push(Pair(n.id(), n.totalCost())); } } else if(!useSameCostForAllLinks && updateNewCosts && nodeIter->second.isOpened()) { float newCostSoFar = currentNode->costSoFar() + cost; if(nodeIter->second.costSoFar() > newCostSoFar) { // update the cost in the priority queue for(std::multimap::iterator mapIter=pqmap.begin(); mapIter!=pqmap.end(); ++mapIter) { if(mapIter->second == nodeIter->first) { pqmap.erase(mapIter); nodeIter->second.setCostSoFar(newCostSoFar); pqmap.insert(std::make_pair(nodeIter->second.totalCost(), nodeIter->first)); break; } } } } } } return path; } // return path starting from "fromId" (Identity pose for the first node) std::list > computePath( int fromId, int toId, const Memory * memory, bool lookInDatabase, bool updateNewCosts, float linearVelocity, // m/sec float angularVelocity) // rad/sec { UASSERT(memory!=0); UASSERT(fromId>=0); UASSERT(toId>=0); std::list > path; UDEBUG("fromId=%d, toId=%d, lookInDatabase=%d, updateNewCosts=%d, linearVelocity=%f, angularVelocity=%f", fromId, toId, lookInDatabase?1:0, updateNewCosts?1:0, linearVelocity, angularVelocity); std::multimap allLinks; if(lookInDatabase) { // Faster to load all links in one query UTimer t; allLinks = memory->getAllLinks(lookInDatabase); UINFO("getting all %d links time = %f s", (int)allLinks.size(), t.ticks()); } //dijkstra int startNode = fromId; int endNode = toId; std::map nodes; nodes.insert(std::make_pair(startNode, Node(startNode, 0, Transform::getIdentity()))); std::priority_queue, Order> pq; std::multimap pqmap; if(updateNewCosts) { pqmap.insert(std::make_pair(0, startNode)); } else { pq.push(Pair(startNode, 0)); } while((updateNewCosts && pqmap.size()) || (!updateNewCosts && pq.size())) { Node * currentNode; if(updateNewCosts) { currentNode = &nodes.find(pqmap.begin()->second)->second; pqmap.erase(pqmap.begin()); } else { currentNode = &nodes.find(pq.top().first)->second; pq.pop(); } currentNode->setClosed(true); if(currentNode->id() == endNode) { while(currentNode->id()!=startNode) { path.push_front(std::make_pair(currentNode->id(), currentNode->pose())); currentNode = &nodes.find(currentNode->fromId())->second; } path.push_front(std::make_pair(startNode, currentNode->pose())); break; } // lookup neighbors std::map links; if(allLinks.size() == 0) { links = memory->getLinks(currentNode->id(), lookInDatabase); } else { for(std::multimap::const_iterator iter = allLinks.lower_bound(currentNode->id()); iter!=allLinks.end() && iter->first == currentNode->id(); ++iter) { links.insert(std::make_pair(iter->second.to(), iter->second)); } } for(std::map::const_iterator iter = links.begin(); iter!=links.end(); ++iter) { Transform nextPose = currentNode->pose()*iter->second.transform(); float cost = 0.0f; if(linearVelocity <= 0.0f && angularVelocity <= 0.0f) { // use distance only cost = iter->second.transform().getNorm(); } else // use time { if(linearVelocity > 0.0f) { cost += iter->second.transform().getNorm()/linearVelocity; } if(angularVelocity > 0.0f) { Eigen::Vector4f v1 = Eigen::Vector4f(nextPose.x()-currentNode->pose().x(), nextPose.y()-currentNode->pose().y(), nextPose.z()-currentNode->pose().z(), 1.0f); Eigen::Vector4f v2 = nextPose.rotation().toEigen4f()*Eigen::Vector4f(1,0,0,1); float angle = pcl::getAngle3D(v1, v2); cost += angle / angularVelocity; } } std::map::iterator nodeIter = nodes.find(iter->first); if(nodeIter == nodes.end()) { Node n(iter->second.to(), currentNode->id(), nextPose); n.setCostSoFar(currentNode->costSoFar() + cost); nodes.insert(std::make_pair(iter->second.to(), n)); if(updateNewCosts) { pqmap.insert(std::make_pair(n.totalCost(), n.id())); } else { pq.push(Pair(n.id(), n.totalCost())); } } else if(updateNewCosts && nodeIter->second.isOpened()) { float newCostSoFar = currentNode->costSoFar() + cost; if(nodeIter->second.costSoFar() > newCostSoFar) { // update pose with new link nodeIter->second.setPose(nextPose); // update the cost in the priority queue for(std::multimap::iterator mapIter=pqmap.begin(); mapIter!=pqmap.end(); ++mapIter) { if(mapIter->second == nodeIter->first) { pqmap.erase(mapIter); nodeIter->second.setCostSoFar(newCostSoFar); pqmap.insert(std::make_pair(nodeIter->second.totalCost(), nodeIter->first)); break; } } } } } } // Debugging stuff if(ULogger::level() == ULogger::kDebug) { std::stringstream stream; std::vector linkTypes(Link::kUndef, 0); std::list >::const_iterator previousIter = path.end(); float length = 0.0f; for(std::list >::const_iterator iter=path.begin(); iter!=path.end();++iter) { if(iter!=path.begin()) { stream << ","; } if(previousIter!=path.end()) { //UDEBUG("current %d = %s", iter->first, iter->second.prettyPrint().c_str()); if(allLinks.size()) { std::multimap::iterator jter = graph::findLink(allLinks, previousIter->first, iter->first); if(jter != allLinks.end()) { //Transform nextPose = iter->second; //Eigen::Vector4f v1 = Eigen::Vector4f(nextPose.x()-previousIter->second.x(), nextPose.y()-previousIter->second.y(), nextPose.z()-previousIter->second.z(), 1.0f); //Eigen::Vector4f v2 = nextPose.rotation().toEigen4f()*Eigen::Vector4f(1,0,0,1); //float angle = pcl::getAngle3D(v1, v2); //float cost = angle ; //UDEBUG("v1=%f,%f,%f v2=%f,%f,%f a=%f", v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], cost); UASSERT(jter->second.type() >= Link::kNeighbor && jter->second.type()second.type()]; stream << "[" << jter->second.type() << "]"; length += jter->second.transform().getNorm(); } } } stream << iter->first; previousIter=iter; } UDEBUG("Path (%f m) = [%s]", length, stream.str().c_str()); std::stringstream streamB; for(unsigned int i=0; i 0) { streamB << " "; } streamB << i << "=" << linkTypes[i]; } UDEBUG("Link types = %s", streamB.str().c_str()); } return path; } int findNearestNode( const std::map & nodes, const rtabmap::Transform & targetPose) { int id = 0; if(nodes.size() && !targetPose.isNull()) { pcl::PointCloud::Ptr cloud(new pcl::PointCloud); cloud->resize(nodes.size()); std::vector ids(nodes.size()); int oi = 0; for(std::map::const_iterator iter = nodes.begin(); iter!=nodes.end(); ++iter) { (*cloud)[oi] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); ids[oi++] = iter->first; } std::map foundNodes; if(cloud->size()) { pcl::search::KdTree::Ptr kdTree(new pcl::search::KdTree); kdTree->setInputCloud(cloud); std::vector ind; std::vector dist; pcl::PointXYZ pt(targetPose.x(), targetPose.y(), targetPose.z()); kdTree->nearestKSearch(pt, 1, ind, dist); if(ind.size() && dist.size() && ind[0] >= 0) { UDEBUG("Nearest node = %d: %f", ids[ind[0]], dist[0]); id = ids[ind[0]]; } } } return id; } // return , excluding query std::map getNodesInRadius( int nodeId, const std::map & nodes, float radius) { UASSERT(uContains(nodes, nodeId)); std::map foundNodes; if(nodes.size() <= 1) { return foundNodes; } pcl::PointCloud::Ptr cloud(new pcl::PointCloud); cloud->resize(nodes.size()); std::vector ids(nodes.size()); int oi = 0; for(std::map::const_iterator iter = nodes.begin(); iter!=nodes.end(); ++iter) { if(iter->first != nodeId) { (*cloud)[oi] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); UASSERT_MSG(pcl::isFinite((*cloud)[oi]), uFormat("Invalid pose (%d) %s", iter->first, iter->second.prettyPrint().c_str()).c_str()); ids[oi] = iter->first; ++oi; } } cloud->resize(oi); ids.resize(oi); Transform fromT = nodes.at(nodeId); if(cloud->size()) { pcl::search::KdTree::Ptr kdTree(new pcl::search::KdTree); kdTree->setInputCloud(cloud); std::vector ind; std::vector sqrdDist; pcl::PointXYZ pt(fromT.x(), fromT.y(), fromT.z()); kdTree->radiusSearch(pt, radius, ind, sqrdDist, 0); for(unsigned int i=0; i=0) { foundNodes.insert(std::make_pair(ids[ind[i]], sqrdDist[i])); } } } UDEBUG("found nodes=%d", (int)foundNodes.size()); return foundNodes; } // return , excluding query std::map getPosesInRadius( int nodeId, const std::map & nodes, float radius, float angle) { UASSERT(uContains(nodes, nodeId)); std::map foundNodes; if(nodes.size() <= 1) { return foundNodes; } pcl::PointCloud::Ptr cloud(new pcl::PointCloud); cloud->resize(nodes.size()); std::vector ids(nodes.size()); int oi = 0; for(std::map::const_iterator iter = nodes.begin(); iter!=nodes.end(); ++iter) { if(iter->first != nodeId) { (*cloud)[oi] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z()); UASSERT_MSG(pcl::isFinite((*cloud)[oi]), uFormat("Invalid pose (%d) %s", iter->first, iter->second.prettyPrint().c_str()).c_str()); ids[oi] = iter->first; ++oi; } } cloud->resize(oi); ids.resize(oi); Transform fromT = nodes.at(nodeId); if(cloud->size()) { pcl::search::KdTree::Ptr kdTree(new pcl::search::KdTree); kdTree->setInputCloud(cloud); std::vector ind; std::vector sqrdDist; pcl::PointXYZ pt(fromT.x(), fromT.y(), fromT.z()); kdTree->radiusSearch(pt, radius, ind, sqrdDist, 0); Eigen::Vector3f vA = fromT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); for(unsigned int i=0; i=0) { if(angle > 0.0f) { const Transform & checkT = nodes.at(ids[ind[i]]); // same orientation? Eigen::Vector3f vB = checkT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0); double a = pcl::getAngle3D(Eigen::Vector4f(vA[0], vA[1], vA[2], 0), Eigen::Vector4f(vB[0], vB[1], vB[2], 0)); if(a <= angle) { foundNodes.insert(std::make_pair(ids[ind[i]], nodes.at(ids[ind[i]]))); } } else { foundNodes.insert(std::make_pair(ids[ind[i]], nodes.at(ids[ind[i]]))); } } } } UDEBUG("found nodes=%d", (int)foundNodes.size()); return foundNodes; } float computePathLength( const std::vector > & path, unsigned int fromIndex, unsigned int toIndex) { float length = 0.0f; if(path.size() > 1) { UASSERT(fromIndex < path.size() && toIndex < path.size() && fromIndex <= toIndex); if(fromIndex >= toIndex) { toIndex = (unsigned int)path.size()-1; } float x=0, y=0, z=0; for(unsigned int i=fromIndex; i > getPaths( std::map poses, const std::multimap & links) { std::list > paths; if(poses.size() && links.size()) { // Segment poses connected only by neighbor links while(poses.size()) { std::map path; for(std::map::iterator iter=poses.begin(); iter!=poses.end();) { std::multimap::const_iterator jter = findLink(links, path.rbegin()->first, iter->first); if(path.size() == 0 || (jter != links.end() && (jter->second.type() == Link::kNeighbor || jter->second.type() == Link::kNeighborMerged))) { path.insert(*iter); poses.erase(iter++); } else { break; } } UASSERT(path.size()); paths.push_back(path); } } return paths; } } /* namespace graph */ } /* namespace rtabmap */