OptimizerTORO.cpp
Go to the documentation of this file.
00001 /*
00002 Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
00003 All rights reserved.
00004 
00005 Redistribution and use in source and binary forms, with or without
00006 modification, are permitted provided that the following conditions are met:
00007     * Redistributions of source code must retain the above copyright
00008       notice, this list of conditions and the following disclaimer.
00009     * Redistributions in binary form must reproduce the above copyright
00010       notice, this list of conditions and the following disclaimer in the
00011       documentation and/or other materials provided with the distribution.
00012     * Neither the name of the Universite de Sherbrooke nor the
00013       names of its contributors may be used to endorse or promote products
00014       derived from this software without specific prior written permission.
00015 
00016 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00017 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00018 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00019 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
00020 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00021 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00022 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00023 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00024 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00025 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00026 */
00027 #include "rtabmap/core/Graph.h"
00028 
00029 #include <rtabmap/utilite/ULogger.h>
00030 #include <rtabmap/utilite/UStl.h>
00031 #include <rtabmap/utilite/UMath.h>
00032 #include <rtabmap/utilite/UConversion.h>
00033 #include <rtabmap/utilite/UTimer.h>
00034 #include <set>
00035 
00036 #include <rtabmap/core/OptimizerTORO.h>
00037 
00038 #ifdef RTABMAP_TORO
00039 #include "toro3d/treeoptimizer3.hh"
00040 #include "toro3d/treeoptimizer2.hh"
00041 #endif
00042 
00043 namespace rtabmap {
00044 
00045 bool OptimizerTORO::available()
00046 {
00047 #ifdef RTABMAP_TORO
00048         return true;
00049 #else
00050         return false;
00051 #endif
00052 }
00053 
00054 std::map<int, Transform> OptimizerTORO::optimize(
00055                 int rootId,
00056                 const std::map<int, Transform> & poses,
00057                 const std::multimap<int, Link> & edgeConstraints,
00058                 cv::Mat & outputCovariance,
00059                 std::list<std::map<int, Transform> > * intermediateGraphes, // contains poses after tree init to last one before the end
00060                 double * finalError,
00061                 int * iterationsDone)
00062 {
00063         outputCovariance = cv::Mat::eye(6,6,CV_64FC1);
00064         std::map<int, Transform> optimizedPoses;
00065 #ifdef RTABMAP_TORO
00066         UDEBUG("Optimizing graph (pose=%d constraints=%d)...", (int)poses.size(), (int)edgeConstraints.size());
00067         if(edgeConstraints.size()>=1 && poses.size()>=2 && iterations() > 0)
00068         {
00069                 // Apply TORO optimization
00070                 AISNavigation::TreeOptimizer2 pg2;
00071                 AISNavigation::TreeOptimizer3 pg3;
00072                 pg2.verboseLevel = 0;
00073                 pg3.verboseLevel = 0;
00074 
00075                 UDEBUG("fill poses to TORO...");
00076                 if(isSlam2d())
00077                 {
00078                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00079                         {
00080                                 UASSERT(!iter->second.isNull());
00081                                 AISNavigation::TreePoseGraph2::Pose p(iter->second.x(), iter->second.y(), iter->second.theta());
00082                                 AISNavigation::TreePoseGraph2::Vertex* v = pg2.addVertex(iter->first, p);
00083                                 UASSERT_MSG(v != 0, uFormat("cannot insert vertex %d!?", iter->first).c_str());
00084                         }
00085                 }
00086                 else
00087                 {
00088                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00089                         {
00090                                 UASSERT(!iter->second.isNull());
00091                                 float x,y,z, roll,pitch,yaw;
00092                                 iter->second.getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
00093                                 AISNavigation::TreePoseGraph3::Pose p(x, y, z, roll, pitch, yaw);
00094                                 AISNavigation::TreePoseGraph3::Vertex* v = pg3.addVertex(iter->first, p);
00095                                 UASSERT_MSG(v != 0, uFormat("cannot insert vertex %d!?", iter->first).c_str());
00096                                 v->transformation=AISNavigation::TreePoseGraph3::Transformation(p);
00097                         }
00098 
00099                 }
00100 
00101                 UDEBUG("fill edges to TORO...");
00102                 if(isSlam2d())
00103                 {
00104                         for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
00105                         {
00106                                 UASSERT(!iter->second.transform().isNull());
00107                                 AISNavigation::TreePoseGraph2::Pose p(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta());
00108                                 AISNavigation::TreePoseGraph2::InformationMatrix inf;
00109                                 //Identity:
00110                                 if(isCovarianceIgnored())
00111                                 {
00112                                         inf.values[0][0] = 1.0; inf.values[0][1] = 0.0; inf.values[0][2] = 0.0; // x
00113                                         inf.values[1][0] = 0.0; inf.values[1][1] = 1.0; inf.values[1][2] = 0.0; // y
00114                                         inf.values[2][0] = 0.0; inf.values[2][1] = 0.0; inf.values[2][2] = 1.0; // theta/yaw
00115                                 }
00116                                 else
00117                                 {
00118                                         inf.values[0][0] = iter->second.infMatrix().at<double>(0,0); // x-x
00119                                         inf.values[0][1] = iter->second.infMatrix().at<double>(0,1); // x-y
00120                                         inf.values[0][2] = iter->second.infMatrix().at<double>(0,5); // x-theta
00121                                         inf.values[1][0] = iter->second.infMatrix().at<double>(1,0); // y-x
00122                                         inf.values[1][1] = iter->second.infMatrix().at<double>(1,1); // y-y
00123                                         inf.values[1][2] = iter->second.infMatrix().at<double>(1,5); // y-theta
00124                                         inf.values[2][0] = iter->second.infMatrix().at<double>(5,0); // theta-x
00125                                         inf.values[2][1] = iter->second.infMatrix().at<double>(5,1); // theta-y
00126                                         inf.values[2][2] = iter->second.infMatrix().at<double>(5,5); // theta-theta
00127                                 }
00128 
00129                                 int id1 = iter->second.from();
00130                                 int id2 = iter->second.to();
00131                                 if(id1 != id2)
00132                                 {
00133                                         AISNavigation::TreePoseGraph2::Vertex* v1=pg2.vertex(id1);
00134                                         AISNavigation::TreePoseGraph2::Vertex* v2=pg2.vertex(id2);
00135                                         UASSERT(v1 != 0);
00136                                         UASSERT(v2 != 0);
00137                                         AISNavigation::TreePoseGraph2::Transformation t(p);
00138                                         if (!pg2.addEdge(v1, v2, t, inf))
00139                                         {
00140                                                 UERROR("Map: Edge already exits between nodes %d and %d, skipping", id1, id2);
00141                                         }
00142                                 }
00143                                 //else // not supporting pose prior
00144                         }
00145                 }
00146                 else
00147                 {
00148                         for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
00149                         {
00150                                 UASSERT(!iter->second.transform().isNull());
00151                                 float x,y,z, roll,pitch,yaw;
00152                                 iter->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
00153 
00154                                 AISNavigation::TreePoseGraph3::Pose p(x, y, z, roll, pitch, yaw);
00155                                 AISNavigation::TreePoseGraph3::InformationMatrix inf = DMatrix<double>::I(6);
00156                                 if(!isCovarianceIgnored())
00157                                 {
00158                                         memcpy(inf[0], iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
00159                                 }
00160 
00161                                 int id1 = iter->second.from();
00162                                 int id2 = iter->second.to();
00163                                 if(id1 != id2)
00164                                 {
00165                                         AISNavigation::TreePoseGraph3::Vertex* v1=pg3.vertex(id1);
00166                                         AISNavigation::TreePoseGraph3::Vertex* v2=pg3.vertex(id2);
00167                                         UASSERT(v1 != 0);
00168                                         UASSERT(v2 != 0);
00169                                         AISNavigation::TreePoseGraph3::Transformation t(p);
00170                                         if (!pg3.addEdge(v1, v2, t, inf))
00171                                         {
00172                                                 UERROR("Map: Edge already exits between nodes %d and %d, skipping", id1, id2);
00173                                         }
00174                                 }
00175                                 //else // not supporting pose prior
00176                         }
00177                 }
00178                 UDEBUG("buildMST... root=%d", rootId);
00179                 UASSERT(uContains(poses, rootId));
00180                 if(isSlam2d())
00181                 {
00182                         pg2.buildMST(rootId); // pg.buildSimpleTree();
00183                         //UDEBUG("initializeOnTree()");
00184                         //pg2.initializeOnTree();
00185                         UDEBUG("initializeTreeParameters()");
00186                         pg2.initializeTreeParameters();
00187                         UDEBUG("Building TORO tree... (if a crash happens just after this msg, "
00188                                    "TORO is not able to find the root of the graph!)");
00189                         pg2.initializeOptimization();
00190                 }
00191                 else
00192                 {
00193                         pg3.buildMST(rootId); // pg.buildSimpleTree();
00194                         //UDEBUG("initializeOnTree()");
00195                         //pg3.initializeOnTree();
00196                         UDEBUG("initializeTreeParameters()");
00197                         pg3.initializeTreeParameters();
00198                         UDEBUG("Building TORO tree... (if a crash happens just after this msg, "
00199                                    "TORO is not able to find the root of the graph!)");
00200                         pg3.initializeOptimization();
00201                 }
00202 
00203                 UINFO("Initial error = %f", pg2.error());
00204                 UINFO("TORO optimizing begin (iterations=%d)", iterations());
00205                 double lastError = 0;
00206                 int i=0;
00207                 UTimer timer;
00208                 for (; i<iterations(); i++)
00209                 {
00210                         if(intermediateGraphes && i>0)
00211                         {
00212                                 std::map<int, Transform> tmpPoses;
00213                                 if(isSlam2d())
00214                                 {
00215                                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00216                                         {
00217                                                 AISNavigation::TreePoseGraph2::Vertex* v=pg2.vertex(iter->first);
00218                                                 float roll, pitch, yaw;
00219                                                 iter->second.getEulerAngles(roll, pitch, yaw);
00220                                                 Transform newPose(v->pose.x(), v->pose.y(), iter->second.z(), roll, pitch, v->pose.theta());
00221 
00222                                                 UASSERT_MSG(!newPose.isNull(), uFormat("Optimized pose %d is null!?!?", iter->first).c_str());
00223                                                 tmpPoses.insert(std::pair<int, Transform>(iter->first, newPose));
00224                                         }
00225                                 }
00226                                 else
00227                                 {
00228                                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00229                                         {
00230                                                 AISNavigation::TreePoseGraph3::Vertex* v=pg3.vertex(iter->first);
00231                                                 AISNavigation::TreePoseGraph3::Pose pose=v->transformation.toPoseType();
00232                                                 Transform newPose(pose.x(), pose.y(), pose.z(), pose.roll(), pose.pitch(), pose.yaw());
00233 
00234                                                 UASSERT_MSG(!newPose.isNull(), uFormat("Optimized pose %d is null!?!?", iter->first).c_str());
00235                                                 tmpPoses.insert(std::pair<int, Transform>(iter->first, newPose));
00236                                         }
00237                                 }
00238                                 intermediateGraphes->push_back(tmpPoses);
00239                         }
00240 
00241                         double error = 0;
00242                         if(isSlam2d())
00243                         {
00244                                 pg2.iterate();
00245 
00246                                 // compute the error and dump it
00247                                 error=pg2.error();
00248                                 UDEBUG("iteration %d global error=%f error/constraint=%f", i, error, error/pg2.edges.size());
00249                         }
00250                         else
00251                         {
00252                                 pg3.iterate();
00253 
00254                                 // compute the error and dump it
00255                                 double mte, mre, are, ate;
00256                                 error=pg3.error(&mre, &mte, &are, &ate);
00257                                 UDEBUG("i %d RotGain=%f global error=%f error/constraint=%f",
00258                                                 i, pg3.getRotGain(), error, error/pg3.edges.size());
00259                         }
00260 
00261                         // early stop condition
00262                         double errorDelta = lastError - error;
00263                         if(i>0 && errorDelta < this->epsilon())
00264                         {
00265                                 if(errorDelta < 0)
00266                                 {
00267                                         UDEBUG("Negative improvement?! Ignore and continue optimizing... (%f < %f)", errorDelta, this->epsilon());
00268                                 }
00269                                 else
00270                                 {
00271                                         UINFO("Stop optimizing, not enough improvement (%f < %f)", errorDelta, this->epsilon());
00272                                         break;
00273                                 }
00274                         }
00275                         else if(i==0 && error < this->epsilon())
00276                         {
00277                                 UINFO("Stop optimizing, error is already under epsilon (%f < %f)", error, this->epsilon());
00278                                 break;
00279                         }
00280                         lastError = error;
00281                 }
00282                 if(finalError)
00283                 {
00284                         *finalError = lastError;
00285                 }
00286                 if(iterationsDone)
00287                 {
00288                         *iterationsDone = i;
00289                 }
00290                 UINFO("TORO optimizing end (%d iterations done, error=%f, time = %f s)", i, lastError, timer.ticks());
00291 
00292                 if(isSlam2d())
00293                 {
00294                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00295                         {
00296                                 AISNavigation::TreePoseGraph2::Vertex* v=pg2.vertex(iter->first);
00297                                 float roll, pitch, yaw;
00298                                 iter->second.getEulerAngles(roll, pitch, yaw);
00299                                 Transform newPose(v->pose.x(), v->pose.y(), iter->second.z(), roll, pitch, v->pose.theta());
00300 
00301                                 UASSERT_MSG(!newPose.isNull(), uFormat("Optimized pose %d is null!?!?", iter->first).c_str());
00302                                 optimizedPoses.insert(std::pair<int, Transform>(iter->first, newPose));
00303                         }
00304                 }
00305                 else
00306                 {
00307                         for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00308                         {
00309                                 AISNavigation::TreePoseGraph3::Vertex* v=pg3.vertex(iter->first);
00310                                 AISNavigation::TreePoseGraph3::Pose pose=v->transformation.toPoseType();
00311                                 Transform newPose(pose.x(), pose.y(), pose.z(), pose.roll(), pose.pitch(), pose.yaw());
00312 
00313                                 UASSERT_MSG(!newPose.isNull(), uFormat("Optimized pose %d is null!?!?", iter->first).c_str());
00314                                 optimizedPoses.insert(std::pair<int, Transform>(iter->first, newPose));
00315                         }
00316                 }
00317 
00318                 // TORO doesn't compute marginals...
00319         }
00320         else if(poses.size() == 1 || iterations() <= 0)
00321         {
00322                 optimizedPoses = poses;
00323         }
00324         else
00325         {
00326                 UWARN("This method should be called at least with 1 pose!");
00327         }
00328         UDEBUG("Optimizing graph...end!");
00329 #else
00330         UERROR("Not built with TORO support!");
00331 #endif
00332         return optimizedPoses;
00333 }
00334 
00335 bool OptimizerTORO::saveGraph(
00336                 const std::string & fileName,
00337                 const std::map<int, Transform> & poses,
00338                 const std::multimap<int, Link> & edgeConstraints)
00339 {
00340         FILE * file = 0;
00341 
00342 #ifdef _MSC_VER
00343         fopen_s(&file, fileName.c_str(), "w");
00344 #else
00345         file = fopen(fileName.c_str(), "w");
00346 #endif
00347 
00348         if(file)
00349         {
00350                 // VERTEX3 id x y z phi theta psi
00351                 for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00352                 {
00353                         float x,y,z, yaw,pitch,roll;
00354                         iter->second.getTranslationAndEulerAngles(x,y,z, roll, pitch, yaw);
00355                         fprintf(file, "VERTEX3 %d %f %f %f %f %f %f\n",
00356                                         iter->first,
00357                                         x,
00358                                         y,
00359                                         z,
00360                                         roll,
00361                                         pitch,
00362                                         yaw);
00363                 }
00364 
00365                 //EDGE3 observed_vertex_id observing_vertex_id x y z roll pitch yaw inf_11 inf_12 .. inf_16 inf_22 .. inf_66
00366                 for(std::multimap<int, Link>::const_iterator iter = edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
00367                 {
00368                         float x,y,z, yaw,pitch,roll;
00369                         iter->second.transform().getTranslationAndEulerAngles(x,y,z, roll, pitch, yaw);
00370                         fprintf(file, "EDGE3 %d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
00371                                         iter->second.from(),
00372                                         iter->second.to(),
00373                                         x,
00374                                         y,
00375                                         z,
00376                                         roll,
00377                                         pitch,
00378                                         yaw,
00379                                         iter->second.infMatrix().at<double>(0,0),
00380                                         iter->second.infMatrix().at<double>(0,1),
00381                                         iter->second.infMatrix().at<double>(0,2),
00382                                         iter->second.infMatrix().at<double>(0,3),
00383                                         iter->second.infMatrix().at<double>(0,4),
00384                                         iter->second.infMatrix().at<double>(0,5),
00385                                         iter->second.infMatrix().at<double>(1,1),
00386                                         iter->second.infMatrix().at<double>(1,2),
00387                                         iter->second.infMatrix().at<double>(1,3),
00388                                         iter->second.infMatrix().at<double>(1,4),
00389                                         iter->second.infMatrix().at<double>(1,5),
00390                                         iter->second.infMatrix().at<double>(2,2),
00391                                         iter->second.infMatrix().at<double>(2,3),
00392                                         iter->second.infMatrix().at<double>(2,4),
00393                                         iter->second.infMatrix().at<double>(2,5),
00394                                         iter->second.infMatrix().at<double>(3,3),
00395                                         iter->second.infMatrix().at<double>(3,4),
00396                                         iter->second.infMatrix().at<double>(3,5),
00397                                         iter->second.infMatrix().at<double>(4,4),
00398                                         iter->second.infMatrix().at<double>(4,5),
00399                                         iter->second.infMatrix().at<double>(5,5));
00400                 }
00401                 UINFO("Graph saved to %s", fileName.c_str());
00402                 fclose(file);
00403         }
00404         else
00405         {
00406                 UERROR("Cannot save to file %s", fileName.c_str());
00407                 return false;
00408         }
00409         return true;
00410 }
00411 
00412 bool OptimizerTORO::loadGraph(
00413                 const std::string & fileName,
00414                 std::map<int, Transform> & poses,
00415                 std::multimap<int, Link> & edgeConstraints)
00416 {
00417         FILE * file = 0;
00418 #ifdef _MSC_VER
00419         fopen_s(&file, fileName.c_str(), "r");
00420 #else
00421         file = fopen(fileName.c_str(), "r");
00422 #endif
00423 
00424         if(file)
00425         {
00426                 char line[400];
00427                 while ( fgets (line , 400 , file) != NULL )
00428                 {
00429                         std::vector<std::string> strList = uListToVector(uSplit(uReplaceChar(line, '\n', ' '), ' '));
00430                         if(strList.size() == 8)
00431                         {
00432                                 //VERTEX3
00433                                 int id = atoi(strList[1].c_str());
00434                                 float x = uStr2Float(strList[2]);
00435                                 float y = uStr2Float(strList[3]);
00436                                 float z = uStr2Float(strList[4]);
00437                                 float roll = uStr2Float(strList[5]);
00438                                 float pitch = uStr2Float(strList[6]);
00439                                 float yaw = uStr2Float(strList[7]);
00440                                 Transform pose(x, y, z, roll, pitch, yaw);
00441                                 if(poses.find(id) == poses.end())
00442                                 {
00443                                         poses.insert(std::make_pair(id, pose));
00444                                 }
00445                                 else
00446                                 {
00447                                         UFATAL("Pose %d already added", id);
00448                                 }
00449                         }
00450                         else if(strList.size() == 30)
00451                         {
00452                                 //EDGE3
00453                                 int idFrom = atoi(strList[1].c_str());
00454                                 int idTo = atoi(strList[2].c_str());
00455                                 float x = uStr2Float(strList[3]);
00456                                 float y = uStr2Float(strList[4]);
00457                                 float z = uStr2Float(strList[5]);
00458                                 float roll = uStr2Float(strList[6]);
00459                                 float pitch = uStr2Float(strList[7]);
00460                                 float yaw = uStr2Float(strList[8]);
00461                                 cv::Mat informationMatrix(6,6,CV_64FC1);
00462                                 informationMatrix.at<double>(3,3) = uStr2Float(strList[9]);
00463                                 informationMatrix.at<double>(4,4) = uStr2Float(strList[15]);
00464                                 informationMatrix.at<double>(5,5) = uStr2Float(strList[20]);
00465                                 UASSERT_MSG(informationMatrix.at<double>(3,3) > 0.0 && informationMatrix.at<double>(4,4) > 0.0 && informationMatrix.at<double>(5,5) > 0.0, uFormat("Information matrix should not be null! line=\"%s\"", line).c_str());
00466                                 informationMatrix.at<double>(0,0) = uStr2Float(strList[24]);
00467                                 informationMatrix.at<double>(1,1) = uStr2Float(strList[27]);
00468                                 informationMatrix.at<double>(2,2) = uStr2Float(strList[29]);
00469                                 UASSERT_MSG(informationMatrix.at<double>(0,0) > 0.0 && informationMatrix.at<double>(1,1) > 0.0 && informationMatrix.at<double>(2,2) > 0.0, uFormat("Information matrix should not be null! line=\"%s\"", line).c_str());
00470                                 Transform transform(x, y, z, roll, pitch, yaw);
00471                                 if(poses.find(idFrom) != poses.end() && poses.find(idTo) != poses.end())
00472                                 {
00473                                         //Link type is unknown
00474                                         Link link(idFrom, idTo, Link::kUndef, transform, informationMatrix);
00475                                         edgeConstraints.insert(std::pair<int, Link>(idFrom, link));
00476                                 }
00477                                 else
00478                                 {
00479                                         UFATAL("Referred poses from the link not exist!");
00480                                 }
00481                         }
00482                         else if(strList.size())
00483                         {
00484                                 UFATAL("Error parsing graph file %s on line \"%s\" (strList.size()=%d)", fileName.c_str(), line, (int)strList.size());
00485                         }
00486                 }
00487 
00488                 UINFO("Graph loaded from %s", fileName.c_str());
00489                 fclose(file);
00490         }
00491         else
00492         {
00493                 UERROR("Cannot open file %s", fileName.c_str());
00494                 return false;
00495         }
00496         return true;
00497 }
00498 
00499 } /* namespace rtabmap */


rtabmap
Author(s): Mathieu Labbe
autogenerated on Thu Jun 6 2019 21:59:21