mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-02 17:40:23 +08:00
1650 lines
46 KiB
C++
1650 lines
46 KiB
C++
/*
|
|
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 <rtabmap/utilite/ULogger.h>
|
|
#include <rtabmap/utilite/UStl.h>
|
|
#include <rtabmap/utilite/UMath.h>
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
#include <rtabmap/utilite/UTimer.h>
|
|
#include <rtabmap/utilite/UFile.h>
|
|
#include <rtabmap/core/GeodeticCoords.h>
|
|
#include <rtabmap/core/Memory.h>
|
|
#include <rtabmap/core/util3d_filtering.h>
|
|
#include <pcl/search/kdtree.h>
|
|
#include <pcl/common/eigen.h>
|
|
#include <pcl/common/common.h>
|
|
#include <set>
|
|
#include <queue>
|
|
#include <fstream>
|
|
|
|
#include <rtabmap/core/OptimizerTORO.h>
|
|
#include <rtabmap/core/OptimizerG2O.h>
|
|
|
|
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<int, Transform> & poses,
|
|
const std::multimap<int, Link> & constraints, // required for formats 3 and 4
|
|
const std::map<int, double> & 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<int, Transform>::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<pose.size(); i++)
|
|
{
|
|
fprintf(fout, " %f", p[i]);
|
|
}
|
|
fprintf(fout, "\n");
|
|
}
|
|
}
|
|
fclose(fout);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool importPoses(
|
|
const std::string & filePath,
|
|
int format, // 0=Raw, 1=RGBD-SLAM, 2=KITTI, 3=TORO, 4=g2o, 5=NewCollege(t,x,y), 6=Malaga Urban GPS, 7=St Lucia INS, 8=Karlsruhe
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> * constraints, // optional for formats 3 and 4
|
|
std::map<int, double> * stamps) // optional for format 1
|
|
{
|
|
UDEBUG("%s format=%d", filePath.c_str(), format);
|
|
if(format==3) // TORO
|
|
{
|
|
std::multimap<int, Link> constraintsTmp;
|
|
if(OptimizerTORO::loadGraph(filePath, poses, constraintsTmp))
|
|
{
|
|
if(constraints)
|
|
{
|
|
*constraints = constraintsTmp;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else if(format == 4) // g2o
|
|
{
|
|
std::multimap<int, Link> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<int, Link>::iterator findLink(
|
|
std::multimap<int, Link> & links,
|
|
int from,
|
|
int to,
|
|
bool checkBothWays)
|
|
{
|
|
std::multimap<int, Link>::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<int, int>::iterator findLink(
|
|
std::multimap<int, int> & links,
|
|
int from,
|
|
int to,
|
|
bool checkBothWays)
|
|
{
|
|
std::multimap<int, int>::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<int, Link>::const_iterator findLink(
|
|
const std::multimap<int, Link> & links,
|
|
int from,
|
|
int to,
|
|
bool checkBothWays)
|
|
{
|
|
std::multimap<int, Link>::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<int, int>::const_iterator findLink(
|
|
const std::multimap<int, int> & links,
|
|
int from,
|
|
int to,
|
|
bool checkBothWays)
|
|
{
|
|
std::multimap<int, int>::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<int, Transform> frustumPosesFiltering(
|
|
const std::map<int, Transform> & 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<int, Transform> output;
|
|
|
|
if(poses.size())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
std::vector<int> ids(poses.size());
|
|
|
|
cloud->resize(poses.size());
|
|
ids.resize(poses.size());
|
|
int oi=0;
|
|
for(std::map<int, rtabmap::Transform>::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<int>),
|
|
cameraPose,
|
|
horizontalFOV,
|
|
verticalFOV,
|
|
nearClipPlaneDistance,
|
|
farClipPlaneDistance,
|
|
negative);
|
|
|
|
|
|
for(unsigned int i=0; i<indices->size(); ++i)
|
|
{
|
|
output.insert(*poses.find(ids[indices->at(i)]));
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
std::map<int, Transform> radiusPosesFiltering(
|
|
const std::map<int, Transform> & poses,
|
|
float radius,
|
|
float angle,
|
|
bool keepLatest)
|
|
{
|
|
if(poses.size() > 1 && radius > 0.0f)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(poses.size());
|
|
int i=0;
|
|
for(std::map<int, Transform>::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<int> names = uKeys(poses);
|
|
std::vector<Transform> transforms = uValues(poses);
|
|
|
|
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> (false));
|
|
tree->setInputCloud(cloud);
|
|
std::set<int> indicesChecked;
|
|
std::set<int> indicesKept;
|
|
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
if(indicesChecked.find(i) == indicesChecked.end())
|
|
{
|
|
std::vector<int> kIndices;
|
|
std::vector<float> kDistances;
|
|
tree->radiusSearch(cloud->at(i), radius, kIndices, kDistances);
|
|
|
|
std::set<int> cloudIndices;
|
|
const Transform & currentT = transforms.at(i);
|
|
Eigen::Vector3f vA = currentT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0);
|
|
for(unsigned int j=0; j<kIndices.size(); ++j)
|
|
{
|
|
if(indicesChecked.find(kIndices[j]) == indicesChecked.end())
|
|
{
|
|
if(angle > 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<int>::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<int>::iterator iter = cloudIndices.begin(); iter!=cloudIndices.end(); ++iter)
|
|
{
|
|
if(!firstAdded)
|
|
{
|
|
indicesKept.insert(*iter);
|
|
firstAdded = true;
|
|
}
|
|
indicesChecked.insert(*iter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//pcl::IndicesPtr indicesOut(new std::vector<int>);
|
|
//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<int, Transform> keptPoses;
|
|
for(std::set<int>::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<int, int> radiusPosesClustering(const std::map<int, Transform> & poses, float radius, float angle)
|
|
{
|
|
std::multimap<int, int> clusters;
|
|
if(poses.size() > 1 && radius > 0.0f)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(poses.size());
|
|
int i=0;
|
|
for(std::map<int, Transform>::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<int> ids = uKeys(poses);
|
|
std::vector<Transform> transforms = uValues(poses);
|
|
|
|
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> (false));
|
|
tree->setInputCloud(cloud);
|
|
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
std::vector<int> kIndices;
|
|
std::vector<float> kDistances;
|
|
tree->radiusSearch(cloud->at(i), radius, kIndices, kDistances);
|
|
|
|
std::set<int> cloudIndices;
|
|
const Transform & currentT = transforms.at(i);
|
|
Eigen::Vector3f vA = currentT.toEigen3f().rotation()*Eigen::Vector3f(1,0,0);
|
|
for(unsigned int j=0; j<kIndices.size(); ++j)
|
|
{
|
|
if((int)i != kIndices[j])
|
|
{
|
|
if(angle > 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<int, Transform> & poses,
|
|
const std::multimap<int, Link> & links,
|
|
std::multimap<int, int> & hyperNodes, //<parent ID, child ID>
|
|
std::multimap<int, Link> & hyperLinks)
|
|
{
|
|
UINFO("Input: poses=%d links=%d", (int)poses.size(), (int)links.size());
|
|
UTimer timer;
|
|
std::map<int, int> posesToHyperNodes;
|
|
std::map<int, std::multimap<int, Link> > clusterloopClosureLinks;
|
|
|
|
{
|
|
std::multimap<int, Link> bidirectionalLoopClosureLinks;
|
|
for(std::multimap<int, Link>::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<int, Transform>::const_reverse_iterator iter=poses.rbegin(); iter!=poses.rend(); ++iter)
|
|
{
|
|
if(posesToHyperNodes.find(iter->first) == posesToHyperNodes.end())
|
|
{
|
|
int hyperNodeId = iter->first;
|
|
std::list<int> loopClosures;
|
|
std::set<int> loopClosuresAdded;
|
|
loopClosures.push_back(iter->first);
|
|
std::multimap<int, Link> 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<int, Link>::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<int, Link>::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<int, Link>::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<int, Link> 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<int> 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<int>::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<int>::iterator iter=path.begin();
|
|
int from = *iter;
|
|
++iter;
|
|
int to = *iter;
|
|
std::multimap<int, Link>::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<int, Link>::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<int, float> 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<std::pair<int, Transform> > computePath(
|
|
const std::map<int, rtabmap::Transform> & poses,
|
|
const std::multimap<int, int> & links,
|
|
int from,
|
|
int to,
|
|
bool updateNewCosts)
|
|
{
|
|
std::list<std::pair<int, Transform> > path;
|
|
|
|
int startNode = from;
|
|
int endNode = to;
|
|
rtabmap::Transform endPose = poses.at(endNode);
|
|
std::map<int, Node> nodes;
|
|
nodes.insert(std::make_pair(startNode, Node(startNode, 0, poses.at(startNode))));
|
|
std::priority_queue<Pair, std::vector<Pair>, Order> pq;
|
|
std::multimap<float, int> 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<int, int>::const_iterator iter = links.find(currentNode->id());
|
|
iter!=links.end() && iter->first == currentNode->id();
|
|
++iter)
|
|
{
|
|
std::map<int, Node>::iterator nodeIter = nodes.find(iter->second);
|
|
if(nodeIter == nodes.end())
|
|
{
|
|
std::map<int, rtabmap::Transform>::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<float, int>::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<int> RTABMAP_EXP computePath(
|
|
const std::multimap<int, Link> & links,
|
|
int from,
|
|
int to,
|
|
bool updateNewCosts,
|
|
bool useSameCostForAllLinks)
|
|
{
|
|
std::list<int> path;
|
|
|
|
int startNode = from;
|
|
int endNode = to;
|
|
std::map<int, Node> nodes;
|
|
nodes.insert(std::make_pair(startNode, Node(startNode, 0, Transform())));
|
|
std::priority_queue<Pair, std::vector<Pair>, Order> pq;
|
|
std::multimap<float, int> 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<int, Link>::const_iterator iter = links.find(currentNode->id());
|
|
iter!=links.end() && iter->first == currentNode->id();
|
|
++iter)
|
|
{
|
|
std::map<int, Node>::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<float, int>::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<std::pair<int, Transform> > 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<std::pair<int, Transform> > 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<int, Link> 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<int, Node> nodes;
|
|
nodes.insert(std::make_pair(startNode, Node(startNode, 0, Transform::getIdentity())));
|
|
std::priority_queue<Pair, std::vector<Pair>, Order> pq;
|
|
std::multimap<float, int> 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<int, Link> links;
|
|
if(allLinks.size() == 0)
|
|
{
|
|
links = memory->getLinks(currentNode->id(), lookInDatabase);
|
|
}
|
|
else
|
|
{
|
|
for(std::multimap<int, Link>::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<int, Link>::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<int, Node>::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<float, int>::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<int> linkTypes(Link::kUndef, 0);
|
|
std::list<std::pair<int, Transform> >::const_iterator previousIter = path.end();
|
|
float length = 0.0f;
|
|
for(std::list<std::pair<int, Transform> >::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<int, Link>::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()<Link::kUndef);
|
|
++linkTypes[jter->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<linkTypes.size(); ++i)
|
|
{
|
|
if(i > 0)
|
|
{
|
|
streamB << " ";
|
|
}
|
|
streamB << i << "=" << linkTypes[i];
|
|
}
|
|
UDEBUG("Link types = %s", streamB.str().c_str());
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
int findNearestNode(
|
|
const std::map<int, rtabmap::Transform> & nodes,
|
|
const rtabmap::Transform & targetPose)
|
|
{
|
|
int id = 0;
|
|
if(nodes.size() && !targetPose.isNull())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(nodes.size());
|
|
std::vector<int> ids(nodes.size());
|
|
int oi = 0;
|
|
for(std::map<int, Transform>::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<int, float> foundNodes;
|
|
if(cloud->size())
|
|
{
|
|
pcl::search::KdTree<pcl::PointXYZ>::Ptr kdTree(new pcl::search::KdTree<pcl::PointXYZ>);
|
|
kdTree->setInputCloud(cloud);
|
|
std::vector<int> ind;
|
|
std::vector<float> 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 <id, sqrd distance>, excluding query
|
|
std::map<int, float> getNodesInRadius(
|
|
int nodeId,
|
|
const std::map<int, Transform> & nodes,
|
|
float radius)
|
|
{
|
|
UASSERT(uContains(nodes, nodeId));
|
|
std::map<int, float> foundNodes;
|
|
if(nodes.size() <= 1)
|
|
{
|
|
return foundNodes;
|
|
}
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(nodes.size());
|
|
std::vector<int> ids(nodes.size());
|
|
int oi = 0;
|
|
for(std::map<int, Transform>::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<pcl::PointXYZ>::Ptr kdTree(new pcl::search::KdTree<pcl::PointXYZ>);
|
|
kdTree->setInputCloud(cloud);
|
|
std::vector<int> ind;
|
|
std::vector<float> sqrdDist;
|
|
pcl::PointXYZ pt(fromT.x(), fromT.y(), fromT.z());
|
|
kdTree->radiusSearch(pt, radius, ind, sqrdDist, 0);
|
|
for(unsigned int i=0; i<ind.size(); ++i)
|
|
{
|
|
if(ind[i] >=0)
|
|
{
|
|
foundNodes.insert(std::make_pair(ids[ind[i]], sqrdDist[i]));
|
|
}
|
|
}
|
|
}
|
|
UDEBUG("found nodes=%d", (int)foundNodes.size());
|
|
return foundNodes;
|
|
}
|
|
|
|
// return <id, Transform>, excluding query
|
|
std::map<int, Transform> getPosesInRadius(
|
|
int nodeId,
|
|
const std::map<int, Transform> & nodes,
|
|
float radius,
|
|
float angle)
|
|
{
|
|
UASSERT(uContains(nodes, nodeId));
|
|
std::map<int, Transform> foundNodes;
|
|
if(nodes.size() <= 1)
|
|
{
|
|
return foundNodes;
|
|
}
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(nodes.size());
|
|
std::vector<int> ids(nodes.size());
|
|
int oi = 0;
|
|
for(std::map<int, Transform>::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<pcl::PointXYZ>::Ptr kdTree(new pcl::search::KdTree<pcl::PointXYZ>);
|
|
kdTree->setInputCloud(cloud);
|
|
std::vector<int> ind;
|
|
std::vector<float> 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<ind.size(); ++i)
|
|
{
|
|
if(ind[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<std::pair<int, Transform> > & 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<toIndex-1; ++i)
|
|
{
|
|
x += fabs(path[i].second.x() - path[i+1].second.x());
|
|
y += fabs(path[i].second.y() - path[i+1].second.y());
|
|
z += fabs(path[i].second.z() - path[i+1].second.z());
|
|
}
|
|
length = sqrt(x*x + y*y + z*z);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
// return all paths linked only by neighbor links
|
|
std::list<std::map<int, Transform> > getPaths(
|
|
std::map<int, Transform> poses,
|
|
const std::multimap<int, Link> & links)
|
|
{
|
|
std::list<std::map<int, Transform> > paths;
|
|
if(poses.size() && links.size())
|
|
{
|
|
// Segment poses connected only by neighbor links
|
|
while(poses.size())
|
|
{
|
|
std::map<int, Transform> path;
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end();)
|
|
{
|
|
std::multimap<int, Link>::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 */
|