mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-06 01:57:45 +08:00
* Using shadows on all texts and icons to see them better when background is white. Buffering database previews to show faster the Library. Updated how RAM usage is computed and now show max memory. Added warnings when RAM usage is low. * Added measuring tool * fixed map not shown when closing visualization * Added measure text size setting, changed look and feel of point to point * Fixed measuring point not showing * Added fix for #1401 * Addressing #1407 * Export: Added colored OBJ options * fixed index_t not existing on focal * Fixed Settings not applied after restoring to all default settings witohut restarting the app (Marker detection not working issue #1455 ) * Marker detection: Fixing wrong depth used when rgb and depth image sizes cannot be divided exactly one from the the other #1455 * Added Marker Max Range option (default 1m) * Added OBJ texture policy option (keep color on textureless polygons) * Added texture/color blending option directly in the app. Added LAZ export option. * Hiding measuring button if not mesh, dont zip if exporting to laz, updating Export XXX button based on the current context, fixed always blending texture/color on not visualization mode * updated default marker max range to 2m * bump ios app version * fixing pcl 1.8.1 build * fixed android build
4989 lines
182 KiB
C++
4989 lines
182 KiB
C++
/*
|
|
Copyright (c) 2010-2016, 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 <tango-gl/conversions.h>
|
|
|
|
#include "RTABMapApp.h"
|
|
#ifdef __ANDROID__
|
|
#include "CameraAvailability.h"
|
|
#endif
|
|
#ifdef RTABMAP_TANGO
|
|
#include "CameraTango.h"
|
|
#endif
|
|
#ifdef RTABMAP_ARCORE
|
|
#include "CameraARCore.h"
|
|
#include <media/NdkImage.h>
|
|
#endif
|
|
#ifdef RTABMAP_ARENGINE
|
|
#include "CameraAREngine.h"
|
|
#endif
|
|
|
|
#include <rtabmap/core/Rtabmap.h>
|
|
#include <rtabmap/core/util2d.h>
|
|
#include <rtabmap/core/util3d.h>
|
|
#include <rtabmap/core/util3d_transforms.h>
|
|
#include <rtabmap/core/util3d_filtering.h>
|
|
#include <rtabmap/core/util3d_surface.h>
|
|
#include <rtabmap/core/Graph.h>
|
|
#include <rtabmap/utilite/UEventsManager.h>
|
|
#include <rtabmap/utilite/UStl.h>
|
|
#include <rtabmap/utilite/UDirectory.h>
|
|
#include <rtabmap/utilite/UFile.h>
|
|
#include <opencv2/opencv_modules.hpp>
|
|
#include <rtabmap/core/util3d_surface.h>
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
#include <rtabmap/utilite/UTimer.h>
|
|
#include <rtabmap/core/ParamEvent.h>
|
|
#include <rtabmap/core/Compression.h>
|
|
#include <rtabmap/core/Optimizer.h>
|
|
#include <rtabmap/core/VWDictionary.h>
|
|
#include <rtabmap/core/Memory.h>
|
|
#include <rtabmap/core/GainCompensator.h>
|
|
#include <rtabmap/core/DBDriver.h>
|
|
#include <rtabmap/core/Recovery.h>
|
|
#include <rtabmap/core/lidar/LidarVLP16.h>
|
|
#include <pcl/common/common.h>
|
|
#include <pcl/filters/extract_indices.h>
|
|
#include <pcl/io/ply_io.h>
|
|
#include <pcl/io/obj_io.h>
|
|
#include <pcl/surface/poisson.h>
|
|
#include <pcl/surface/vtk_smoothing/vtk_mesh_quadric_decimation.h>
|
|
|
|
#ifdef RTABMAP_PDAL
|
|
#include <rtabmap/core/PDALWriter.h>
|
|
#elif defined(RTABMAP_LIBLAS)
|
|
#include <rtabmap/core/LASWriter.h>
|
|
#endif
|
|
|
|
#define LOW_RES_PIX 2
|
|
#define DEBUG_RENDERING_PERFORMANCE
|
|
|
|
const int g_optMeshId = -100;
|
|
|
|
#ifdef __ANDROID__
|
|
static JavaVM *jvm;
|
|
static jobject RTABMapActivity = 0;
|
|
#endif
|
|
|
|
#ifdef __ANDROID__
|
|
#ifndef DISABLE_LOG
|
|
//ref: https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/
|
|
static int pfd[2];
|
|
static pthread_t thr;
|
|
static void *thread_func(void*)
|
|
{
|
|
ssize_t rdsz;
|
|
char buf[128];
|
|
while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
|
|
if(buf[rdsz - 1] == '\n') --rdsz;
|
|
buf[rdsz] = 0; /* add null-terminator */
|
|
__android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, buf);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int start_logger()
|
|
{
|
|
/* make stdout line-buffered and stderr unbuffered */
|
|
setvbuf(stdout, 0, _IOLBF, 0);
|
|
setvbuf(stderr, 0, _IONBF, 0);
|
|
|
|
/* create the pipe and redirect stdout and stderr */
|
|
pipe(pfd);
|
|
dup2(pfd[1], 1);
|
|
dup2(pfd[1], 2);
|
|
|
|
/* spawn the logging thread */
|
|
if(pthread_create(&thr, 0, thread_func, 0) == -1)
|
|
return -1;
|
|
pthread_detach(thr);
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
rtabmap::ParametersMap RTABMapApp::getRtabmapParameters()
|
|
{
|
|
rtabmap::ParametersMap parameters;
|
|
|
|
parameters.insert(mappingParameters_.begin(), mappingParameters_.end());
|
|
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kKpDetectorStrategy(), "5")); // GFTT/FREAK
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kKpMaxFeatures(), std::string("200")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kGFTTQualityLevel(), std::string("0.0001")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemImagePreDecimation(), std::string(cameraColor_&&fullResolution_?"2":"1")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kBRIEFBytes(), std::string("64")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapTimeThr(), std::string("800")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapPublishLikelihood(), std::string("false")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapPublishPdf(), std::string("false")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapStartNewMapOnLoopClosure(), uBool2Str(!localizationMode_ && appendMode_ && !dataRecorderMode_)));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDAggressiveLoopThr(), "0.0"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemBinDataKept(), uBool2Str(!trajectoryMode_)));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerIterations(), "10"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), uBool2Str(!localizationMode_)));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapMaxRetrieved(), "1"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDMaxLocalRetrieved(), "0"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemCompressionParallelized(), std::string("false")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kKpParallelized(), std::string("false")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDOptimizeFromGraphEnd(), std::string("true")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kVisMinInliers(), std::string("25")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kVisPnPVarianceMedianRatio(), std::string("2")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDProximityPathMaxNeighbors(), std::string("0"))); // disable scan matching to merged nodes
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDProximityBySpace(), std::string("false"))); // just keep loop closure detection
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDLinearUpdate(), std::string("0.05")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRGBDAngularUpdate(), std::string("0.05")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMarkerLength(), std::string("0.0")));
|
|
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemUseOdomGravity(), "true"));
|
|
if(parameters.find(rtabmap::Parameters::kOptimizerStrategy()) != parameters.end())
|
|
{
|
|
if(parameters.at(rtabmap::Parameters::kOptimizerStrategy()).compare("2") == 0) // GTSAM
|
|
{
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerEpsilon(), "0.00001"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerIterations(), "10"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerGravitySigma(), "0.2"));
|
|
}
|
|
else if(parameters.at(rtabmap::Parameters::kOptimizerStrategy()).compare("1") == 0) // g2o
|
|
{
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerEpsilon(), "0.0"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerIterations(), "10"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerGravitySigma(), "0.2"));
|
|
}
|
|
else // TORO
|
|
{
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerEpsilon(), "0.00001"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerIterations(), "100"));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerGravitySigma(), "0"));
|
|
}
|
|
}
|
|
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpPointToPlane(), std::string("true")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemLaserScanNormalK(), std::string("0")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpIterations(), std::string("10")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpEpsilon(), std::string("0.001")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpMaxRotation(), std::string("0.17"))); // 10 degrees
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpMaxTranslation(), std::string("0.05")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpCorrespondenceRatio(), std::string("0.49")));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kIcpMaxCorrespondenceDistance(), std::string("0.05")));
|
|
|
|
parameters.insert(*rtabmap::Parameters::getDefaultParameters().find(rtabmap::Parameters::kKpMaxFeatures()));
|
|
parameters.insert(*rtabmap::Parameters::getDefaultParameters().find(rtabmap::Parameters::kMemRehearsalSimilarity()));
|
|
parameters.insert(*rtabmap::Parameters::getDefaultParameters().find(rtabmap::Parameters::kMemMapLabelsAdded()));
|
|
if(dataRecorderMode_)
|
|
{
|
|
// Example taken from https://github.com/introlab/rtabmap_ros/blob/master/rtabmap_launch/launch/data_recorder.launch
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kMemRehearsalSimilarity(), "1.0")); // deactivate rehearsal
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kKpMaxFeatures(), "-1")); // deactivate keypoints extraction
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapMaxRetrieved(), "0")); // deactivate global retrieval
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRGBDMaxLocalRetrieved(), "0")); // deactivate local retrieval
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kMemMapLabelsAdded(), "false")); // don't create map labels
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapMemoryThr(), "2")); // keep the WM empty
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kMemSTMSize(), "1")); // STM=1 -->
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRGBDProximityBySpace(), "false"));
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRGBDLinearUpdate(), "0"));
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRGBDAngularUpdate(), "0"));
|
|
uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kMemNotLinkedNodesKept(), std::string("true")));
|
|
}
|
|
|
|
return parameters;
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
RTABMapApp::RTABMapApp(JNIEnv* env, jobject caller_activity) :
|
|
#else //__APPLE__
|
|
RTABMapApp::RTABMapApp() :
|
|
#endif
|
|
cameraDriver_(0),
|
|
camera_(0),
|
|
sensorCaptureThread_(0),
|
|
rtabmapThread_(0),
|
|
rtabmap_(0),
|
|
logHandler_(0),
|
|
odomCloudShown_(true),
|
|
graphOptimization_(true),
|
|
nodesFiltering_(false),
|
|
localizationMode_(false),
|
|
trajectoryMode_(false),
|
|
rawScanSaved_(false),
|
|
smoothing_(true),
|
|
depthFromMotion_(false),
|
|
cameraColor_(true),
|
|
fullResolution_(false),
|
|
appendMode_(true),
|
|
useExternalLidar_(false),
|
|
maxCloudDepth_(2.5),
|
|
minCloudDepth_(0.0),
|
|
cloudDensityLevel_(1),
|
|
meshTrianglePix_(2),
|
|
meshAngleToleranceDeg_(20.0),
|
|
meshDecimationFactor_(0),
|
|
clusterRatio_(0.1),
|
|
maxGainRadius_(0.02f),
|
|
renderingTextureDecimation_(4),
|
|
backgroundColor_(0.2f),
|
|
depthConfidence_(2),
|
|
upstreamRelocalizationMaxAcc_(0.0f),
|
|
exportPointCloudFormat_("ply"),
|
|
dataRecorderMode_(false),
|
|
clearSceneOnNextRender_(false),
|
|
openingDatabase_(false),
|
|
exporting_(false),
|
|
postProcessing_(false),
|
|
filterPolygonsOnNextRender_(false),
|
|
gainCompensationOnNextRender_(0),
|
|
bilateralFilteringOnNextRender_(false),
|
|
takeScreenshotOnNextRender_(false),
|
|
cameraJustInitialized_(false),
|
|
totalPoints_(0),
|
|
totalPolygons_(0),
|
|
lastDrawnCloudsCount_(0),
|
|
renderingTime_(0.0f),
|
|
lastPostRenderEventTime_(0.0),
|
|
lastPoseEventTime_(0.0),
|
|
visualizingMesh_(false),
|
|
exportedMeshUpdated_(false),
|
|
measuresUpdated_(false),
|
|
targetPoint_(new pcl::PointCloud<pcl::PointXYZRGB>),
|
|
quadSample_(new pcl::PointCloud<pcl::PointXYZ>),
|
|
quadSamplePolygons_(2),
|
|
metricSystem_(true),
|
|
measuringTextSize_(0.05f),
|
|
snapAxisThr_(0.95),
|
|
measuringMode_(0),
|
|
addMeasureClicked_(false),
|
|
teleportClicked_(false),
|
|
removeMeasureClicked_(false),
|
|
optTextureMesh_(new pcl::TextureMesh),
|
|
optRefId_(0),
|
|
optRefPose_(0),
|
|
mapToOdom_(rtabmap::Transform::getIdentity())
|
|
|
|
{
|
|
pcl::PointXYZRGB ptWhite;
|
|
ptWhite.r = ptWhite.g = ptWhite.b = 255;
|
|
targetPoint_->push_back(ptWhite);
|
|
snapAxes_.push_back(cv::Vec3f(1,0,0));
|
|
snapAxes_.push_back(cv::Vec3f(0,1,0));
|
|
snapAxes_.push_back(cv::Vec3f(0,0,1));
|
|
|
|
float quadSize = 0.05f;
|
|
quadSample_->push_back(pcl::PointXYZ(-quadSize, -quadSize, 0.0f));
|
|
quadSample_->push_back(pcl::PointXYZ(quadSize, -quadSize, 0.0f));
|
|
quadSample_->push_back(pcl::PointXYZ(quadSize, quadSize, 0.0f));
|
|
quadSample_->push_back(pcl::PointXYZ(-quadSize, quadSize, 0.0f));
|
|
quadSamplePolygons_[0].vertices.resize(3);
|
|
quadSamplePolygons_[0].vertices[0] = 0;
|
|
quadSamplePolygons_[0].vertices[1] = 1;
|
|
quadSamplePolygons_[0].vertices[2] = 2;
|
|
quadSamplePolygons_[1].vertices.resize(3);
|
|
quadSamplePolygons_[1].vertices[0] = 0;
|
|
quadSamplePolygons_[1].vertices[1] = 2;
|
|
quadSamplePolygons_[1].vertices[2] = 3;
|
|
|
|
#ifdef __ANDROID__
|
|
env->GetJavaVM(&jvm);
|
|
RTABMapActivity = env->NewGlobalRef(caller_activity);
|
|
#endif
|
|
|
|
LOGI("RTABMapApp::RTABMapApp()");
|
|
#ifdef __ANDROID__
|
|
progressionStatus_.setJavaObjects(jvm, RTABMapActivity);
|
|
#endif
|
|
main_scene_.setBackgroundColor(backgroundColor_, backgroundColor_, backgroundColor_);
|
|
|
|
logHandler_ = new rtabmap::LogHandler();
|
|
|
|
this->registerToEventsManager();
|
|
LOGI("RTABMapApp::RTABMapApp() end");
|
|
|
|
#ifdef __ANDROID__
|
|
#ifndef DISABLE_LOG
|
|
start_logger();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
#ifndef __ANDROID__ // __APPLE__
|
|
void RTABMapApp::setupSwiftCallbacks(void * classPtr,
|
|
void(*progressCallback)(void *, int, int),
|
|
void(*initCallback)(void *, int, const char*),
|
|
void(*statsUpdatedCallback)(void *,
|
|
int, int, int, int,
|
|
float,
|
|
int, int, int, int, int ,int,
|
|
float,
|
|
int,
|
|
float,
|
|
int,
|
|
float, float, float, float,
|
|
int, int,
|
|
float, float, float, float, float, float),
|
|
void(*cameraInfoEventCallback)(void *, int, const char*, const char*))
|
|
{
|
|
swiftClassPtr_ = classPtr;
|
|
progressionStatus_.setSwiftCallback(classPtr, progressCallback);
|
|
swiftInitCallback = initCallback;
|
|
swiftStatsUpdatedCallback = statsUpdatedCallback;
|
|
swiftCameraInfoEventCallback = cameraInfoEventCallback;
|
|
}
|
|
#endif
|
|
|
|
RTABMapApp::~RTABMapApp() {
|
|
LOGI("~RTABMapApp() begin");
|
|
stopCamera();
|
|
if(rtabmapThread_)
|
|
{
|
|
rtabmapThread_->close(false);
|
|
}
|
|
delete rtabmapThread_;
|
|
delete logHandler_;
|
|
delete optRefPose_;
|
|
{
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
if(rtabmapEvents_.size())
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents_.begin(); iter!=rtabmapEvents_.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
}
|
|
rtabmapEvents_.clear();
|
|
}
|
|
LOGI("~RTABMapApp() end");
|
|
}
|
|
|
|
void RTABMapApp::setScreenRotation(int displayRotation, int cameraRotation)
|
|
{
|
|
rtabmap::ScreenRotation rotation = rtabmap::GetAndroidRotationFromColorCameraToDisplay(displayRotation, cameraRotation);
|
|
//LOGI("Set orientation: display=%d camera=%d -> %d", displayRotation, cameraRotation, (int)rotation);
|
|
main_scene_.setScreenRotation(rotation);
|
|
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_)
|
|
{
|
|
camera_->setScreenRotationAndSize(main_scene_.getScreenRotation(), main_scene_.getViewPortWidth(), main_scene_.getViewPortHeight());
|
|
}
|
|
}
|
|
|
|
int RTABMapApp::openDatabase(const std::string & databasePath, bool databaseInMemory, bool optimize, bool clearDatabase)
|
|
{
|
|
LOGW("Opening database %s (inMemory=%d, optimize=%d, clearDatabase=%d)", databasePath.c_str(), databaseInMemory?1:0, optimize?1:0, clearDatabase?1:0);
|
|
this->unregisterFromEventsManager(); // to ignore published init events when closing rtabmap
|
|
status_.first = rtabmap::RtabmapEventInit::kInitializing;
|
|
rtabmapMutex_.lock();
|
|
if(rtabmapEvents_.size())
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents_.begin(); iter!=rtabmapEvents_.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
}
|
|
rtabmapEvents_.clear();
|
|
openingDatabase_ = true;
|
|
bool restartThread = false;
|
|
if(rtabmapThread_)
|
|
{
|
|
restartThread = rtabmapThread_->isRunning();
|
|
|
|
rtabmapThread_->close(false);
|
|
delete rtabmapThread_;
|
|
rtabmapThread_ = 0;
|
|
rtabmap_ = 0;
|
|
}
|
|
|
|
totalPoints_ = 0;
|
|
totalPolygons_ = 0;
|
|
lastDrawnCloudsCount_ = 0;
|
|
renderingTime_ = 0.0f;
|
|
lastPostRenderEventTime_ = 0.0;
|
|
lastPoseEventTime_ = 0.0;
|
|
bufferedStatsData_.clear();
|
|
graphOptimization_ = true;
|
|
measuresUpdated_ = !measures_.empty();
|
|
measures_.clear();
|
|
|
|
this->registerToEventsManager();
|
|
|
|
int status = 0;
|
|
|
|
// Open visualization while we load (if there is an optimized mesh saved in database)
|
|
optTextureMesh_.reset(new pcl::TextureMesh);
|
|
optMesh_ = rtabmap::Mesh();
|
|
optTexture_ = cv::Mat();
|
|
optRefId_ = 0;
|
|
if(optRefPose_)
|
|
{
|
|
delete optRefPose_;
|
|
optRefPose_ = 0;
|
|
}
|
|
visualizingMesh_ = false;
|
|
cv::Mat cloudMat;
|
|
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons;
|
|
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
|
|
std::vector<std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > > texCoords;
|
|
#else
|
|
std::vector<std::vector<Eigen::Vector2f> > texCoords;
|
|
#endif
|
|
cv::Mat textures;
|
|
if(!databasePath.empty() && UFile::exists(databasePath) && !clearDatabase)
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading optimized cloud/mesh..."));
|
|
rtabmap::DBDriver * driver = rtabmap::DBDriver::create();
|
|
if(driver->openConnection(databasePath))
|
|
{
|
|
cloudMat = driver->loadOptimizedMesh(&polygons, &texCoords, &textures);
|
|
if(!cloudMat.empty())
|
|
{
|
|
LOGI("Open: Found optimized mesh! Visualizing it.");
|
|
optTextureMesh_ = rtabmap::util3d::assembleTextureMesh(cloudMat, polygons, texCoords, textures, true);
|
|
optMesh_ = rtabmap::Mesh();
|
|
optTexture_ = textures;
|
|
if(!optTexture_.empty())
|
|
{
|
|
LOGI("Open: Texture mesh: %dx%d.", optTexture_.cols, optTexture_.rows);
|
|
status=3;
|
|
}
|
|
else if(optTextureMesh_->tex_polygons.size())
|
|
{
|
|
LOGI("Open: Polygon mesh");
|
|
status=2;
|
|
}
|
|
else if(!optTextureMesh_->cloud.data.empty())
|
|
{
|
|
LOGI("Open: Point cloud");
|
|
status=1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGI("Open: No optimized mesh found.");
|
|
}
|
|
delete driver;
|
|
}
|
|
}
|
|
|
|
if(status > 0)
|
|
{
|
|
if(status==1)
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading optimized cloud...done!"));
|
|
}
|
|
else if(status==2)
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading optimized mesh...done!"));
|
|
}
|
|
else
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading optimized texture mesh...done!"));
|
|
}
|
|
boost::mutex::scoped_lock lockRender(renderingMutex_);
|
|
visualizingMesh_ = true;
|
|
exportedMeshUpdated_ = true;
|
|
}
|
|
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading database..."));
|
|
if(clearDatabase)
|
|
{
|
|
LOGI("Erasing database \"%s\"...", databasePath.c_str());
|
|
UFile::erase(databasePath);
|
|
}
|
|
|
|
//Rtabmap
|
|
mapToOdom_.setIdentity();
|
|
rtabmap_ = new rtabmap::Rtabmap();
|
|
rtabmap::ParametersMap parameters = getRtabmapParameters();
|
|
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kDbSqlite3InMemory(), uBool2Str(databaseInMemory && !dataRecorderMode_)));
|
|
LOGI("Initializing database...");
|
|
rtabmap_->init(parameters, databasePath);
|
|
rtabmapThread_ = new rtabmap::RtabmapThread(rtabmap_);
|
|
if(parameters.find(rtabmap::Parameters::kRtabmapDetectionRate()) != parameters.end())
|
|
{
|
|
rtabmapThread_->setDetectorRate(uStr2Float(parameters.at(rtabmap::Parameters::kRtabmapDetectionRate())));
|
|
}
|
|
|
|
// Generate all meshes
|
|
std::map<int, rtabmap::Signature> signatures;
|
|
std::map<int, rtabmap::Transform> poses;
|
|
std::multimap<int, rtabmap::Link> links;
|
|
LOGI("Loading full map from database...");
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Loading data from database..."));
|
|
rtabmap_->getGraph(
|
|
poses,
|
|
links,
|
|
true,
|
|
false, // Make sure poses are the same than optimized mesh (in case we switched RGBD/OptimizedFromGraphEnd)
|
|
&signatures,
|
|
true,
|
|
true,
|
|
true,
|
|
true);
|
|
|
|
if(signatures.size() && poses.empty())
|
|
{
|
|
LOGE("Failed to optimize the graph!");
|
|
status = -1;
|
|
}
|
|
|
|
{
|
|
LOGI("Creating the meshes (%d)....", (int)poses.size());
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
createdMeshes_.clear();
|
|
int i=0;
|
|
UTimer addTime;
|
|
rawPoses_.clear();
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=poses.begin(); iter!=poses.end() && status>=0; ++iter)
|
|
{
|
|
try
|
|
{
|
|
int id = iter->first;
|
|
if(!iter->second.isNull())
|
|
{
|
|
if(uContains(signatures, id))
|
|
{
|
|
UTimer timer;
|
|
rtabmap::SensorData data = signatures.at(id).sensorData();
|
|
rawPoses_.insert(std::make_pair(id, signatures.at(id).getPose()));
|
|
|
|
cv::Mat tmpA, depth;
|
|
data.uncompressData(&tmpA, &depth);
|
|
|
|
if(!(!data.imageRaw().empty() && !data.depthRaw().empty()) && !data.laserScanCompressed().isEmpty())
|
|
{
|
|
rtabmap::LaserScan scan;
|
|
data.uncompressData(0, 0, &scan);
|
|
}
|
|
|
|
if((!data.imageRaw().empty() && !data.depthRaw().empty()) || !data.laserScanRaw().isEmpty())
|
|
{
|
|
// Voxelize and filter depending on the previous cloud?
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty() && (!useExternalLidar_ || data.laserScanRaw().isEmpty()))
|
|
{
|
|
int meshDecimation = updateMeshDecimation(data.depthRaw().cols, data.depthRaw().rows);
|
|
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, meshDecimation, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
}
|
|
else
|
|
{
|
|
//scan
|
|
cloud = rtabmap::util3d::laserScanToPointCloudRGB(rtabmap::util3d::commonFiltering(data.laserScanRaw(), 1, minCloudDepth_, maxCloudDepth_), data.laserScanRaw().localTransform(), 255, 255, 255);
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
|
|
if(cloud->size() && indices->size())
|
|
{
|
|
std::vector<pcl::Vertices> polygons;
|
|
std::vector<pcl::Vertices> polygonsLowRes;
|
|
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
|
|
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > texCoords;
|
|
#else
|
|
std::vector<Eigen::Vector2f> texCoords;
|
|
#endif
|
|
if(cloud->isOrganized() && main_scene_.isMeshRendering() && main_scene_.isMapRendering())
|
|
{
|
|
polygons = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
#ifndef DISABLE_VTK
|
|
if(meshDecimationFactor_ > 0.0f && !polygons.empty())
|
|
{
|
|
pcl::PolygonMesh::Ptr tmpMesh(new pcl::PolygonMesh);
|
|
pcl::toPCLPointCloud2(*cloud, tmpMesh->cloud);
|
|
tmpMesh->polygons = polygons;
|
|
rtabmap::util3d::denseMeshPostProcessing<pcl::PointXYZRGB>(tmpMesh, meshDecimationFactor_, 0, cloud, 0);
|
|
if(!tmpMesh->polygons.empty())
|
|
{
|
|
if(main_scene_.isMeshTexturing() && main_scene_.isMapRendering())
|
|
{
|
|
std::map<int, rtabmap::Transform> cameraPoses;
|
|
std::map<int, rtabmap::CameraModel> cameraModels;
|
|
cameraPoses.insert(std::make_pair(0, rtabmap::Transform::getIdentity()));
|
|
cameraModels.insert(std::make_pair(0, data.cameraModels()[0]));
|
|
pcl::TextureMesh::Ptr textureMesh = rtabmap::util3d::createTextureMesh(
|
|
tmpMesh,
|
|
cameraPoses,
|
|
cameraModels,
|
|
std::map<int, cv::Mat>());
|
|
pcl::fromPCLPointCloud2(textureMesh->cloud, *cloud);
|
|
polygons = textureMesh->tex_polygons[0];
|
|
texCoords = textureMesh->tex_coordinates[0];
|
|
}
|
|
else
|
|
{
|
|
pcl::fromPCLPointCloud2(tmpMesh->cloud, *cloud);
|
|
polygons = tmpMesh->polygons;
|
|
}
|
|
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGE("Mesh decimation factor is too high (%f), returning full mesh (id=%d).", meshDecimationFactor_, data.id());
|
|
polygonsLowRes = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_+LOW_RES_PIX);
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Mesh simplication, %d polygons, %d points (%fs)", (int)polygons.size(), (int)cloud->size(), timer.ticks());
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
polygonsLowRes = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_+LOW_RES_PIX);
|
|
}
|
|
}
|
|
|
|
std::pair<std::map<int, rtabmap::Mesh>::iterator, bool> inserted = createdMeshes_.insert(std::make_pair(id, rtabmap::Mesh()));
|
|
UASSERT(inserted.second);
|
|
inserted.first->second.cloud = cloud;
|
|
inserted.first->second.indices = indices;
|
|
inserted.first->second.polygons = polygons;
|
|
inserted.first->second.polygonsLowRes = polygonsLowRes;
|
|
inserted.first->second.visible = true;
|
|
inserted.first->second.cameraModel = data.cameraModels()[0];
|
|
inserted.first->second.gains[0] = 1.0;
|
|
inserted.first->second.gains[1] = 1.0;
|
|
inserted.first->second.gains[2] = 1.0;
|
|
if((cloud->isOrganized() || !texCoords.empty()) && main_scene_.isMeshTexturing() && main_scene_.isMapRendering())
|
|
{
|
|
inserted.first->second.texCoords = texCoords;
|
|
if(renderingTextureDecimation_>1)
|
|
{
|
|
cv::Size reducedSize(data.imageRaw().cols/renderingTextureDecimation_, data.imageRaw().rows/renderingTextureDecimation_);
|
|
cv::resize(data.imageRaw(), inserted.first->second.texture, reducedSize, 0, 0, cv::INTER_LINEAR);
|
|
}
|
|
else
|
|
{
|
|
inserted.first->second.texture = data.imageRaw();
|
|
}
|
|
}
|
|
LOGI("Created cloud %d (%fs, %d points)", id, timer.ticks(), (int)cloud->size());
|
|
}
|
|
else
|
|
{
|
|
UWARN("Cloud %d is empty", id);
|
|
}
|
|
}
|
|
else if(!data.depthOrRightCompressed().empty() || !data.laserScanCompressed().isEmpty())
|
|
{
|
|
UERROR("Failed to uncompress data! (rgb=%d, depth=%d, scan=%d)", data.imageCompressed().cols, data.depthOrRightCompressed().cols, data.laserScanCompressed().size());
|
|
status=-2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Data for node %d not found", id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Pose %d is null !?", id);
|
|
}
|
|
++i;
|
|
if(addTime.elapsed() >= 4.0f)
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, uFormat("Created clouds %d/%d", i, (int)poses.size())));
|
|
addTime.restart();
|
|
}
|
|
}
|
|
catch(const UException & e)
|
|
{
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
status = -2;
|
|
}
|
|
catch (const cv::Exception & e)
|
|
{
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
status = -2;
|
|
}
|
|
catch (const std::exception & e)
|
|
{
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
status = -2;
|
|
}
|
|
}
|
|
if(status < 0)
|
|
{
|
|
createdMeshes_.clear();
|
|
rawPoses_.clear();
|
|
}
|
|
else
|
|
{
|
|
LOGI("Created %d meshes...", (int)createdMeshes_.size());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(optimize && status>=0)
|
|
{
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Visual optimization..."));
|
|
gainCompensation();
|
|
|
|
LOGI("Polygon filtering...");
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
UTimer time;
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
if(iter->second.polygons.size())
|
|
{
|
|
// filter polygons
|
|
iter->second.polygons = filterOrganizedPolygons(iter->second.polygons, iter->second.cloud->size());
|
|
}
|
|
}
|
|
}
|
|
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInfo, "Updating scene..."));
|
|
LOGI("Open: add rtabmap event to update the scene");
|
|
rtabmap::Statistics stats;
|
|
stats.addStatistic(rtabmap::Statistics::kMemoryWorking_memory_size(), (float)rtabmap_->getWMSize());
|
|
stats.addStatistic(rtabmap::Statistics::kKeypointDictionary_size(), (float)rtabmap_->getMemory()->getVWDictionary()->getVisualWords().size());
|
|
stats.addStatistic(rtabmap::Statistics::kMemoryDatabase_memory_used(), (float)rtabmap_->getMemory()->getDatabaseMemoryUsed());
|
|
stats.setPoses(poses);
|
|
stats.setConstraints(links);
|
|
rtabmapEvents_.push_back(new rtabmap::RtabmapEvent(stats));
|
|
|
|
rtabmap_->setOptimizedPoses(poses, links);
|
|
|
|
// for optimized mesh
|
|
if(poses.size())
|
|
{
|
|
// just take the last as reference
|
|
optRefId_ = poses.rbegin()->first;
|
|
optRefPose_ = new rtabmap::Transform(poses.rbegin()->second);
|
|
}
|
|
|
|
{
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_)
|
|
{
|
|
camera_->resetOrigin();
|
|
if(dataRecorderMode_)
|
|
{
|
|
// Don't update faster than we record, so that we see is what is recorded
|
|
camera_->setFrameRate(rtabmapThread_->getDetectorRate());
|
|
rtabmapThread_->setDetectorRate(0);
|
|
}
|
|
else
|
|
{
|
|
// set default 10
|
|
camera_->setFrameRate(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
UEventsManager::post(new rtabmap::RtabmapEventInit(rtabmap::RtabmapEventInit::kInitialized, ""));
|
|
|
|
if(restartThread)
|
|
{
|
|
rtabmapThread_->registerToEventsManager();
|
|
rtabmapThread_->start();
|
|
}
|
|
|
|
rtabmapMutex_.unlock();
|
|
|
|
boost::mutex::scoped_lock lockRender(renderingMutex_);
|
|
if(poses.empty() || status>0)
|
|
{
|
|
openingDatabase_ = false;
|
|
}
|
|
|
|
clearSceneOnNextRender_ = status<=0;
|
|
|
|
return status;
|
|
}
|
|
|
|
int RTABMapApp::updateMeshDecimation(int width, int height)
|
|
{
|
|
int meshDecimation = 1;
|
|
if(cloudDensityLevel_ == 3) // very low
|
|
{
|
|
if((height >= 480 || width >= 480) && width % 20 == 0 && height % 20 == 0)
|
|
{
|
|
meshDecimation = 20;
|
|
}
|
|
else if(width % 15 == 0 && height % 15 == 0)
|
|
{
|
|
meshDecimation = 15;
|
|
}
|
|
else if(width % 10 == 0 && height % 10 == 0)
|
|
{
|
|
meshDecimation = 10;
|
|
}
|
|
else if(width % 8 == 0 && height % 8 == 0)
|
|
{
|
|
meshDecimation = 8;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Could not set decimation to high (size=%dx%d)", width, height);
|
|
}
|
|
}
|
|
else if(cloudDensityLevel_ == 2) // low
|
|
{
|
|
if((height >= 480 || width >= 480) && width % 10 == 0 && height % 10 == 0)
|
|
{
|
|
meshDecimation = 10;
|
|
}
|
|
else if(width % 5 == 0 && height % 5 == 0)
|
|
{
|
|
meshDecimation = 5;
|
|
}
|
|
else if(width % 4 == 0 && height % 4 == 0)
|
|
{
|
|
meshDecimation = 4;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Could not set decimation to medium (size=%dx%d)", width, height);
|
|
}
|
|
}
|
|
else if(cloudDensityLevel_ == 1) // high
|
|
{
|
|
if((height >= 480 || width >= 480) && width % 5 == 0 && height % 5 == 0)
|
|
{
|
|
meshDecimation = 5;
|
|
}
|
|
else if(width % 3 == 0 && width % 3 == 0)
|
|
{
|
|
meshDecimation = 3;
|
|
}
|
|
else if(width % 2 == 0 && width % 2 == 0)
|
|
{
|
|
meshDecimation = 2;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Could not set decimation to low (size=%dx%d)", width, height);
|
|
}
|
|
}
|
|
// else maximum
|
|
LOGI("Set decimation to %d (image=%dx%d, density level=%d)", meshDecimation, width, height, cloudDensityLevel_);
|
|
return meshDecimation;
|
|
}
|
|
|
|
bool RTABMapApp::isBuiltWith(int cameraDriver) const
|
|
{
|
|
if(cameraDriver == 0)
|
|
{
|
|
#ifdef RTABMAP_TANGO
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if(cameraDriver == 1)
|
|
{
|
|
#ifdef RTABMAP_ARCORE
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if(cameraDriver == 2)
|
|
{
|
|
#ifdef RTABMAP_ARENGINE
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
bool RTABMapApp::startCamera(JNIEnv* env, jobject iBinder, jobject context, jobject activity, int driver)
|
|
#else // __APPLE__
|
|
bool RTABMapApp::startCamera()
|
|
#endif
|
|
{
|
|
stopCamera();
|
|
|
|
//ccapp = new computer_vision::ComputerVisionApplication();
|
|
//ccapp->OnResume(env, context, activity);
|
|
//return true;
|
|
#ifdef __ANDROID__
|
|
cameraDriver_ = driver;
|
|
#else // __APPLE__
|
|
cameraDriver_ = 3;
|
|
#endif
|
|
LOGW("startCamera() camera driver=%d", cameraDriver_);
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
|
|
if(cameraDriver_ == 0) // Tango
|
|
{
|
|
#ifdef RTABMAP_TANGO
|
|
camera_ = new rtabmap::CameraTango(cameraColor_, !cameraColor_ || fullResolution_?1:2, rawScanSaved_, smoothing_);
|
|
|
|
if (TangoService_setBinder(env, iBinder) != TANGO_SUCCESS) {
|
|
UERROR("TangoHandler::ConnectTango, TangoService_setBinder error");
|
|
delete camera_;
|
|
camera_ = 0;
|
|
return false;
|
|
}
|
|
#else
|
|
UERROR("RTAB-Map is not built with Tango support!");
|
|
#endif
|
|
}
|
|
else if(cameraDriver_ == 1)
|
|
{
|
|
#ifdef RTABMAP_ARCORE
|
|
camera_ = new rtabmap::CameraARCore(env, context, activity, depthFromMotion_, smoothing_, upstreamRelocalizationMaxAcc_);
|
|
#else
|
|
UERROR("RTAB-Map is not built with ARCore support!");
|
|
#endif
|
|
}
|
|
else if(cameraDriver_ == 2)
|
|
{
|
|
#ifdef RTABMAP_ARENGINE
|
|
camera_ = new rtabmap::CameraAREngine(env, context, activity, smoothing_, upstreamRelocalizationMaxAcc_);
|
|
#else
|
|
UERROR("RTAB-Map is not built with AREngine support!");
|
|
#endif
|
|
}
|
|
else if(cameraDriver_ == 3)
|
|
{
|
|
camera_ = new rtabmap::CameraMobile(smoothing_, upstreamRelocalizationMaxAcc_);
|
|
}
|
|
|
|
if(camera_ == 0)
|
|
{
|
|
UERROR("Unknown or not supported camera driver! %d", cameraDriver_);
|
|
return false;
|
|
}
|
|
|
|
if(rtabmapThread_ && dataRecorderMode_)
|
|
{
|
|
// Don't update faster than we record, so that we see is what is recorded
|
|
camera_->setFrameRate(rtabmapThread_->getDetectorRate());
|
|
rtabmapThread_->setDetectorRate(0);
|
|
}
|
|
|
|
if(camera_->init())
|
|
{
|
|
camera_->setScreenRotationAndSize(main_scene_.getScreenRotation(), main_scene_.getViewPortWidth(), main_scene_.getViewPortHeight());
|
|
|
|
//update mesh decimation based on camera calibration
|
|
LOGI("Cloud density level %d", cloudDensityLevel_);
|
|
|
|
LOGI("Start camera thread");
|
|
cameraJustInitialized_ = true;
|
|
if(useExternalLidar_)
|
|
{
|
|
rtabmap::LidarVLP16 * lidar = new rtabmap::LidarVLP16(boost::asio::ip::address_v4::from_string("192.168.1.201"), 2368, true);
|
|
lidar->init();
|
|
camera_->setImageRate(0); // if lidar, to get close camera synchronization
|
|
sensorCaptureThread_ = new rtabmap::SensorCaptureThread(lidar, camera_, camera_, rtabmap::Transform::getIdentity());
|
|
sensorCaptureThread_->setScanParameters(false, 1, 0.0f, 0.0f, 0.0f, 0, 0.0f, 0.0f, true);
|
|
}
|
|
else
|
|
{
|
|
sensorCaptureThread_ = new rtabmap::SensorCaptureThread(camera_);
|
|
}
|
|
sensorCaptureThread_->start();
|
|
return true;
|
|
}
|
|
UERROR("Failed camera initialization!");
|
|
return false;
|
|
}
|
|
|
|
void RTABMapApp::stopCamera()
|
|
{
|
|
LOGI("stopCamera()");
|
|
{
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(sensorCaptureThread_!=0)
|
|
{
|
|
camera_->close();
|
|
sensorCaptureThread_->join(true);
|
|
delete sensorCaptureThread_; // camera_ is closed and deleted inside
|
|
sensorCaptureThread_ = 0;
|
|
camera_ = 0;
|
|
}
|
|
}
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
delete main_scene_.background_renderer_;
|
|
main_scene_.background_renderer_ = 0;
|
|
}
|
|
}
|
|
|
|
std::vector<pcl::Vertices> RTABMapApp::filterOrganizedPolygons(
|
|
const std::vector<pcl::Vertices> & polygons,
|
|
int cloudSize) const
|
|
{
|
|
std::vector<int> vertexToCluster(cloudSize, 0);
|
|
std::map<int, std::list<int> > clusters;
|
|
int lastClusterID = 0;
|
|
|
|
for(unsigned int i=0; i<polygons.size(); ++i)
|
|
{
|
|
int clusterID = 0;
|
|
for(unsigned int j=0;j<polygons[i].vertices.size(); ++j)
|
|
{
|
|
if(vertexToCluster[polygons[i].vertices[j]]>0)
|
|
{
|
|
clusterID = vertexToCluster[polygons[i].vertices[j]];
|
|
break;
|
|
}
|
|
}
|
|
if(clusterID>0)
|
|
{
|
|
clusters.at(clusterID).push_back(i);
|
|
}
|
|
else
|
|
{
|
|
clusterID = ++lastClusterID;
|
|
std::list<int> polygons;
|
|
polygons.push_back(i);
|
|
clusters.insert(std::make_pair(clusterID, polygons));
|
|
}
|
|
for(unsigned int j=0;j<polygons[i].vertices.size(); ++j)
|
|
{
|
|
vertexToCluster[polygons[i].vertices[j]] = clusterID;
|
|
}
|
|
}
|
|
|
|
unsigned int biggestClusterSize = 0;
|
|
for(std::map<int, std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
|
|
{
|
|
//LOGD("cluster %d = %d", iter->first, (int)iter->second.size());
|
|
|
|
if(iter->second.size() > biggestClusterSize)
|
|
{
|
|
biggestClusterSize = iter->second.size();
|
|
}
|
|
}
|
|
unsigned int minClusterSize = (unsigned int)(float(biggestClusterSize)*clusterRatio_);
|
|
//LOGI("Biggest cluster %d -> minClusterSize(ratio=%f)=%d",
|
|
// biggestClusterSize, clusterRatio_, (int)minClusterSize);
|
|
|
|
std::vector<pcl::Vertices> filteredPolygons(polygons.size());
|
|
int oi = 0;
|
|
for(std::map<int, std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
|
|
{
|
|
if(iter->second.size() >= minClusterSize)
|
|
{
|
|
for(std::list<int>::iterator jter=iter->second.begin(); jter!=iter->second.end(); ++jter)
|
|
{
|
|
filteredPolygons[oi++] = polygons[*jter];
|
|
}
|
|
}
|
|
}
|
|
filteredPolygons.resize(oi);
|
|
return filteredPolygons;
|
|
}
|
|
|
|
|
|
std::vector<pcl::Vertices> RTABMapApp::filterPolygons(
|
|
const std::vector<pcl::Vertices> & polygons,
|
|
int cloudSize) const
|
|
{
|
|
// filter polygons
|
|
std::vector<std::set<int> > neighbors;
|
|
std::vector<std::set<int> > vertexToPolygons;
|
|
rtabmap::util3d::createPolygonIndexes(
|
|
polygons,
|
|
cloudSize,
|
|
neighbors,
|
|
vertexToPolygons);
|
|
std::list<std::list<int> > clusters = rtabmap::util3d::clusterPolygons(neighbors);
|
|
|
|
unsigned int biggestClusterSize = 0;
|
|
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
|
|
{
|
|
if(iter->size() > biggestClusterSize)
|
|
{
|
|
biggestClusterSize = iter->size();
|
|
}
|
|
}
|
|
unsigned int minClusterSize = (unsigned int)(float(biggestClusterSize)*clusterRatio_);
|
|
LOGI("Biggest cluster = %d -> minClusterSize(ratio=%f)=%d",
|
|
biggestClusterSize, clusterRatio_, (int)minClusterSize);
|
|
|
|
std::vector<pcl::Vertices> filteredPolygons(polygons.size());
|
|
int oi=0;
|
|
for(std::list<std::list<int> >::iterator jter=clusters.begin(); jter!=clusters.end(); ++jter)
|
|
{
|
|
if(jter->size() >= minClusterSize)
|
|
{
|
|
for(std::list<int>::iterator kter=jter->begin(); kter!=jter->end(); ++kter)
|
|
{
|
|
filteredPolygons[oi++] = polygons.at(*kter);
|
|
}
|
|
}
|
|
}
|
|
filteredPolygons.resize(oi);
|
|
return filteredPolygons;
|
|
}
|
|
|
|
// OpenGL thread
|
|
void RTABMapApp::InitializeGLContent()
|
|
{
|
|
UINFO("");
|
|
main_scene_.InitGLContent();
|
|
|
|
float v = backgroundColor_ == 0.5f?0.4f:1.0f-backgroundColor_;
|
|
main_scene_.setGridColor(v, v, v);
|
|
}
|
|
|
|
// OpenGL thread
|
|
void RTABMapApp::SetViewPort(int width, int height)
|
|
{
|
|
main_scene_.SetupViewPort(width, height);
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_)
|
|
{
|
|
camera_->setScreenRotationAndSize(main_scene_.getScreenRotation(), main_scene_.getViewPortWidth(), main_scene_.getViewPortHeight());
|
|
}
|
|
}
|
|
|
|
class PostRenderEvent : public UEvent
|
|
{
|
|
public:
|
|
PostRenderEvent(rtabmap::RtabmapEvent * event = 0) :
|
|
rtabmapEvent_(event)
|
|
{
|
|
}
|
|
~PostRenderEvent()
|
|
{
|
|
if(rtabmapEvent_!=0)
|
|
{
|
|
delete rtabmapEvent_;
|
|
}
|
|
}
|
|
virtual std::string getClassName() const {return "PostRenderEvent";}
|
|
const rtabmap::RtabmapEvent * getRtabmapEvent() const {return rtabmapEvent_;}
|
|
private:
|
|
rtabmap::RtabmapEvent * rtabmapEvent_;
|
|
};
|
|
|
|
// OpenGL thread
|
|
bool RTABMapApp::smoothMesh(int id, rtabmap::Mesh & mesh)
|
|
{
|
|
UTimer t;
|
|
// reconstruct depth image
|
|
UASSERT(mesh.indices.get() && mesh.indices->size());
|
|
cv::Mat depth = cv::Mat::zeros(mesh.cloud->height, mesh.cloud->width, CV_32FC1);
|
|
rtabmap::Transform localTransformInv = mesh.cameraModel.localTransform().inverse();
|
|
for(unsigned int i=0; i<mesh.indices->size(); ++i)
|
|
{
|
|
int index = mesh.indices->at(i);
|
|
// FastBilateralFilter works in camera frame
|
|
if(mesh.cloud->at(index).x > 0)
|
|
{
|
|
pcl::PointXYZRGB pt = rtabmap::util3d::transformPoint(mesh.cloud->at(index), localTransformInv);
|
|
depth.at<float>(index) = pt.z;
|
|
}
|
|
}
|
|
|
|
depth = rtabmap::util2d::fastBilateralFiltering(depth, 2.0f, 0.075f);
|
|
LOGI("smoothMesh() Bilateral filtering of %d, time=%fs", id, t.ticks());
|
|
|
|
if(!depth.empty() && mesh.indices->size())
|
|
{
|
|
pcl::IndicesPtr newIndices(new std::vector<int>(mesh.indices->size()));
|
|
int oi = 0;
|
|
for(unsigned int i=0; i<mesh.indices->size(); ++i)
|
|
{
|
|
int index = mesh.indices->at(i);
|
|
|
|
pcl::PointXYZRGB & pt = mesh.cloud->at(index);
|
|
pcl::PointXYZRGB newPt = rtabmap::util3d::transformPoint(mesh.cloud->at(index), localTransformInv);
|
|
if(depth.at<float>(index) > 0)
|
|
{
|
|
newPt.z = depth.at<float>(index);
|
|
newPt = rtabmap::util3d::transformPoint(newPt, mesh.cameraModel.localTransform());
|
|
newIndices->at(oi++) = index;
|
|
}
|
|
else
|
|
{
|
|
newPt.x = newPt.y = newPt.z = std::numeric_limits<float>::quiet_NaN();
|
|
}
|
|
pt.x = newPt.x;
|
|
pt.y = newPt.y;
|
|
pt.z = newPt.z;
|
|
}
|
|
newIndices->resize(oi);
|
|
mesh.indices = newIndices;
|
|
|
|
//reconstruct the mesh with smoothed surfaces
|
|
std::vector<pcl::Vertices> polygons;
|
|
if(main_scene_.isMeshRendering())
|
|
{
|
|
polygons = rtabmap::util3d::organizedFastMesh(mesh.cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
}
|
|
LOGI("smoothMesh() Reconstructing the mesh of %d, time=%fs", id, t.ticks());
|
|
mesh.polygons = polygons;
|
|
}
|
|
else
|
|
{
|
|
UERROR("smoothMesh() Failed to smooth surface %d", id);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RTABMapApp::gainCompensation(bool full)
|
|
{
|
|
UTimer tGainCompensation;
|
|
LOGI("Gain compensation...");
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr > clouds;
|
|
std::map<int, pcl::IndicesPtr> indices;
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
clouds.insert(std::make_pair(iter->first, iter->second.cloud));
|
|
indices.insert(std::make_pair(iter->first, iter->second.indices));
|
|
}
|
|
std::map<int, rtabmap::Transform> poses;
|
|
std::multimap<int, rtabmap::Link> links;
|
|
rtabmap_->getGraph(poses, links, true, true);
|
|
if(full)
|
|
{
|
|
// full compensation
|
|
links.clear();
|
|
for(std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr>::const_iterator iter=clouds.begin(); iter!=clouds.end(); ++iter)
|
|
{
|
|
int from = iter->first;
|
|
std::map<int, pcl::PointCloud<pcl::PointXYZRGB>::Ptr>::const_iterator jter = iter;
|
|
++jter;
|
|
for(;jter!=clouds.end(); ++jter)
|
|
{
|
|
int to = jter->first;
|
|
links.insert(std::make_pair(from, rtabmap::Link(from, to, rtabmap::Link::kUserClosure, poses.at(from).inverse()*poses.at(to))));
|
|
}
|
|
}
|
|
}
|
|
|
|
UASSERT(maxGainRadius_>0.0f);
|
|
rtabmap::GainCompensator compensator(maxGainRadius_, 0.0f, 0.01f, 1.0f);
|
|
if(clouds.size() > 1 && links.size())
|
|
{
|
|
compensator.feed(clouds, indices, links);
|
|
LOGI("Gain compensation... compute gain: links=%d, time=%fs", (int)links.size(), tGainCompensation.ticks());
|
|
}
|
|
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
if(!iter->second.cloud->empty())
|
|
{
|
|
if(clouds.size() > 1 && links.size())
|
|
{
|
|
compensator.getGain(iter->first, &iter->second.gains[0], &iter->second.gains[1], &iter->second.gains[2]);
|
|
LOGI("%d mesh has gain %f,%f,%f", iter->first, iter->second.gains[0], iter->second.gains[1], iter->second.gains[2]);
|
|
}
|
|
}
|
|
}
|
|
LOGI("Gain compensation... applying gain: meshes=%d, time=%fs", (int)createdMeshes_.size(), tGainCompensation.ticks());
|
|
}
|
|
|
|
// OpenGL thread
|
|
int RTABMapApp::Render()
|
|
{
|
|
std::list<rtabmap::RtabmapEvent*> rtabmapEvents;
|
|
try
|
|
{
|
|
if(sensorCaptureThread_ == 0)
|
|
{
|
|
// We are not doing continous drawing, just measure single draw
|
|
fpsTime_.restart();
|
|
}
|
|
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
UTimer time;
|
|
#endif
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
|
|
bool notifyDataLoaded = false;
|
|
bool notifyCameraStarted = false;
|
|
|
|
if(clearSceneOnNextRender_)
|
|
{
|
|
visualizingMesh_ = false;
|
|
}
|
|
|
|
// ARCore and AREngine capture should be done in opengl thread!
|
|
const float* uvsTransformed = 0;
|
|
glm::mat4 arProjectionMatrix(0);
|
|
glm::mat4 arViewMatrix(0);
|
|
rtabmap::Mesh occlusionMesh;
|
|
|
|
{
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_!=0)
|
|
{
|
|
if(cameraDriver_ <= 2)
|
|
{
|
|
camera_->updateOnRender();
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGD("Camera updateOnRender %fs", time.ticks());
|
|
#endif
|
|
// We detect if we are in measuring mode if rtabmap is not running
|
|
if(main_scene_.background_renderer_ == 0 && camera_->getTextureId() != 0 && !(rtabmapThread_ == 0 || !rtabmapThread_->isRunning()))
|
|
{
|
|
main_scene_.background_renderer_ = new BackgroundRenderer();
|
|
main_scene_.background_renderer_->InitializeGlContent(((rtabmap::CameraMobile*)camera_)->getTextureId(), cameraDriver_ <= 2);
|
|
}
|
|
if(camera_->uvsInitialized())
|
|
{
|
|
uvsTransformed = ((rtabmap::CameraMobile*)camera_)->uvsTransformed();
|
|
((rtabmap::CameraMobile*)camera_)->getVPMatrices(arViewMatrix, arProjectionMatrix);
|
|
if(graphOptimization_ && !mapToOdom_.isIdentity())
|
|
{
|
|
rtabmap::Transform mapCorrection = rtabmap::opengl_world_T_rtabmap_world * mapToOdom_ *rtabmap::rtabmap_world_T_opengl_world;
|
|
arViewMatrix = glm::inverse(rtabmap::glmFromTransform(mapCorrection)*glm::inverse(arViewMatrix));
|
|
}
|
|
}
|
|
if(!visualizingMesh_ && !dataRecorderMode_ && main_scene_.GetCameraType() == tango_gl::GestureCamera::kFirstPerson)
|
|
{
|
|
rtabmap::CameraModel occlusionModel;
|
|
cv::Mat occlusionImage = ((rtabmap::CameraMobile*)camera_)->getOcclusionImage(&occlusionModel);
|
|
|
|
if(occlusionModel.isValidForProjection())
|
|
{
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
int meshDecimation = updateMeshDecimation(occlusionImage.cols, occlusionImage.rows);
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = rtabmap::util3d::cloudFromDepth(occlusionImage, occlusionModel, meshDecimation, 0, 0, indices.get());
|
|
cloud = rtabmap::util3d::transformPointCloud(cloud, rtabmap::opengl_world_T_rtabmap_world*mapToOdom_*occlusionModel.localTransform());
|
|
occlusionMesh.cloud.reset(new pcl::PointCloud<pcl::PointXYZRGB>());
|
|
pcl::copyPointCloud(*cloud, *occlusionMesh.cloud);
|
|
occlusionMesh.indices = indices;
|
|
occlusionMesh.polygons = rtabmap::util3d::organizedFastMesh(cloud, 1.0*M_PI/180.0, false, meshTrianglePix_);
|
|
}
|
|
else if(!occlusionImage.empty())
|
|
{
|
|
UERROR("invalid occlusionModel: %f %f %f %f %dx%d", occlusionModel.fx(), occlusionModel.fy(), occlusionModel.cx(), occlusionModel.cy(), occlusionModel.imageWidth(), occlusionModel.imageHeight());
|
|
}
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGD("Update background and occlusion mesh %fs", time.ticks());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// process only pose events in visualization mode
|
|
rtabmap::Transform pose;
|
|
{
|
|
boost::mutex::scoped_lock lock(poseMutex_);
|
|
if(poseEvents_.size())
|
|
{
|
|
pose = poseEvents_.back();
|
|
poseEvents_.clear();
|
|
}
|
|
}
|
|
|
|
rtabmap::SensorEvent sensorEvent;
|
|
{
|
|
boost::mutex::scoped_lock lock(sensorMutex_);
|
|
if(sensorEvents_.size())
|
|
{
|
|
LOGI("Process sensor events");
|
|
sensorEvent = sensorEvents_.back();
|
|
sensorEvents_.clear();
|
|
if(cameraJustInitialized_)
|
|
{
|
|
notifyCameraStarted = true;
|
|
cameraJustInitialized_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!pose.isNull())
|
|
{
|
|
// update camera pose?
|
|
if(graphOptimization_ && !mapToOdom_.isIdentity())
|
|
{
|
|
main_scene_.SetCameraPose(rtabmap::opengl_world_T_rtabmap_world*mapToOdom_*pose*rtabmap::optical_T_opengl);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.SetCameraPose(rtabmap::opengl_world_T_rtabmap_world*pose*rtabmap::optical_T_opengl);
|
|
}
|
|
if(sensorCaptureThread_!=0 && cameraJustInitialized_)
|
|
{
|
|
notifyCameraStarted = true;
|
|
cameraJustInitialized_ = false;
|
|
}
|
|
lastPoseEventTime_ = UTimer::now();
|
|
}
|
|
|
|
if(visualizingMesh_)
|
|
{
|
|
if(exportedMeshUpdated_)
|
|
{
|
|
main_scene_.clear();
|
|
exportedMeshUpdated_ = false;
|
|
measuresUpdated_ = measures_.size()>0;
|
|
}
|
|
if(!main_scene_.hasCloud(g_optMeshId))
|
|
{
|
|
LOGI("Adding optimized mesh to opengl (%d points, %d polygons, %d tex_coords, materials=%d texture=%dx%d)...",
|
|
optTextureMesh_->cloud.point_step==0?0:(int)optTextureMesh_->cloud.data.size()/optTextureMesh_->cloud.point_step,
|
|
optTextureMesh_->tex_polygons.size()!=1?0:(int)optTextureMesh_->tex_polygons[0].size(),
|
|
optTextureMesh_->tex_coordinates.size()!=1?0:(int)optTextureMesh_->tex_coordinates[0].size(),
|
|
(int)optTextureMesh_->tex_materials.size(),
|
|
optTexture_.cols, optTexture_.rows);
|
|
if(optTextureMesh_->tex_polygons.size() && optTextureMesh_->tex_polygons[0].size())
|
|
{
|
|
optMesh_ = rtabmap::Mesh();
|
|
optMesh_.gains[0] = optMesh_.gains[1] = optMesh_.gains[2] = 1.0;
|
|
optMesh_.cloud.reset(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
optMesh_.normals.reset(new pcl::PointCloud<pcl::Normal>);
|
|
pcl::fromPCLPointCloud2(optTextureMesh_->cloud, *optMesh_.cloud);
|
|
pcl::fromPCLPointCloud2(optTextureMesh_->cloud, *optMesh_.normals);
|
|
bool hasColors = false;
|
|
for(unsigned int i=0; i<optTextureMesh_->cloud.fields.size(); ++i)
|
|
{
|
|
if(optTextureMesh_->cloud.fields[i].name.compare("rgb") == 0)
|
|
{
|
|
hasColors = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!hasColors)
|
|
{
|
|
std::uint8_t r = 255, g = 255, b = 255; // White
|
|
std::uint32_t rgb = ((std::uint32_t)r << 16 | (std::uint32_t)g << 8 | (std::uint32_t)b);
|
|
for(size_t i=0; i<optMesh_.cloud->size(); ++i)
|
|
{
|
|
optMesh_.cloud->at(i).rgb = *reinterpret_cast<float*>(&rgb);
|
|
}
|
|
}
|
|
optMesh_.polygons = optTextureMesh_->tex_polygons[0];
|
|
if(optTextureMesh_->tex_coordinates.size())
|
|
{
|
|
optMesh_.texCoords = optTextureMesh_->tex_coordinates[0];
|
|
optMesh_.texture = optTexture_;
|
|
}
|
|
main_scene_.addMesh(g_optMeshId, optMesh_, rtabmap::opengl_world_T_rtabmap_world, true);
|
|
}
|
|
else
|
|
{
|
|
pcl::IndicesPtr indices(new std::vector<int>); // null
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::fromPCLPointCloud2(optTextureMesh_->cloud, *cloud);
|
|
main_scene_.addCloud(g_optMeshId, cloud, indices, rtabmap::opengl_world_T_rtabmap_world);
|
|
}
|
|
|
|
if(!measures_.empty())
|
|
{
|
|
measuresUpdated_ = true;
|
|
}
|
|
}
|
|
|
|
if(camera_ != 0 && (rtabmapThread_ == 0 || !rtabmapThread_->isRunning()))
|
|
{
|
|
updateMeasuringState();
|
|
}
|
|
else
|
|
{
|
|
main_scene_.removeLine(55555);
|
|
main_scene_.removeQuad(55555);
|
|
main_scene_.removeQuad(55556);
|
|
main_scene_.removeCircle(55555);
|
|
main_scene_.removeCircle(55556);
|
|
main_scene_.removeText(55555);
|
|
main_scene_.removeCloudOrMesh(-99999);
|
|
measuringTmpPts_.clear();
|
|
measuringTmpNormals_.clear();
|
|
}
|
|
|
|
if(measuresUpdated_)
|
|
{
|
|
std::list<Measure> measures = measures_;
|
|
measuresUpdated_ = false;
|
|
main_scene_.clearLines();
|
|
main_scene_.clearTexts();
|
|
main_scene_.clearQuads();
|
|
main_scene_.clearCircles();
|
|
int lineId = 0;
|
|
int textId = 0;
|
|
int quadId = 0;
|
|
int circleId = 0;
|
|
float sphereRadius = 0.02f;
|
|
float quadSize=0.05f;
|
|
float quadAlpha = 0.3f;
|
|
|
|
tango_gl::Color color(1.0f, 0.0f, 1.0f);
|
|
tango_gl::Color xColor(1.0f, 0.0f, 0.0f);
|
|
tango_gl::Color yColor(0.0f, 1.0f, 0.0f);
|
|
tango_gl::Color zColor(0.0f, 0.0f, 1.0f);
|
|
|
|
float restrictiveSnapThr = 0.9999;
|
|
for(std::list<Measure>::iterator iter=measures.begin(); iter!=measures.end(); ++iter)
|
|
{
|
|
// Determinate color based on current snap axes
|
|
tango_gl::Color quadColor = color;
|
|
bool sameNormal = false;
|
|
if(iter->n1().dot(iter->n2()) > 0.99) // Same normal, plane to plane
|
|
{
|
|
sameNormal = true;
|
|
Eigen::Vector3f n(iter->n1()[0], iter->n1()[1], iter->n1()[2]);
|
|
float n1ProdX = n.dot(Eigen::Vector3f(snapAxes_[0][0], snapAxes_[0][1], snapAxes_[0][2]));
|
|
float n1ProdY = n.dot(Eigen::Vector3f(snapAxes_[1][0], snapAxes_[1][1], snapAxes_[1][2]));
|
|
float n1ProdZ = n.dot(Eigen::Vector3f(snapAxes_[2][0], snapAxes_[2][1], snapAxes_[2][2]));
|
|
if(fabs(n1ProdX) > restrictiveSnapThr)
|
|
{
|
|
quadColor = xColor;
|
|
}
|
|
else if(fabs(n1ProdY) > restrictiveSnapThr)
|
|
{
|
|
quadColor = yColor;
|
|
}
|
|
else if(fabs(n1ProdZ) > restrictiveSnapThr)
|
|
{
|
|
quadColor = zColor;
|
|
}
|
|
}
|
|
|
|
const Measure & m = *iter;
|
|
LOGI("dist=%f, %f,%f,%f -> %f,%f,%f", m.length(),
|
|
m.pt1().x, m.pt1().y, m.pt1().z,
|
|
m.pt2().x, m.pt2().y, m.pt2().z);
|
|
cv::Point3f pt1 = rtabmap::util3d::transformPoint(m.pt1(), rtabmap::opengl_world_T_rtabmap_world);
|
|
cv::Point3f pt2 = rtabmap::util3d::transformPoint(m.pt2(), rtabmap::opengl_world_T_rtabmap_world);
|
|
main_scene_.addLine(++lineId, pt1, pt2, quadColor);
|
|
|
|
if (fabs(iter->n1()[2]) < 0.00001 && sameNormal)
|
|
{
|
|
// Add a line so that in orthogonal view, we can see better where the lines are starting/finishing
|
|
cv::Point3f n = cv::Vec3f(0,0,1).cross(iter->n1());
|
|
|
|
n = rtabmap::util3d::transformPoint(n, rtabmap::opengl_world_T_rtabmap_world) * (quadSize/2);
|
|
cv::Point3f pa = pt1 + n;
|
|
cv::Point3f pb = pt1 - n;
|
|
main_scene_.addLine(++lineId, pa, pb, quadColor);
|
|
cv::Point3f pc = pt2 + n;
|
|
cv::Point3f pd = pt2 - n;
|
|
main_scene_.addLine(++lineId, pc, pd, quadColor);
|
|
}
|
|
|
|
float diff = m.length();
|
|
std::string text = uFormat("%0.2f m", diff);
|
|
if(!metricSystem_)
|
|
{
|
|
static const double METERS_PER_FOOT = 0.3048;
|
|
static double INCHES_PER_FOOT = 12.0;
|
|
double lengthInFeet = diff / METERS_PER_FOOT;
|
|
int feet = (int)lengthInFeet;
|
|
float inches = (lengthInFeet - feet) * INCHES_PER_FOOT;
|
|
if(feet > 0)
|
|
{
|
|
text = uFormat("%d' %0.1f\"", feet, inches);
|
|
}
|
|
else
|
|
{
|
|
text = uFormat("%0.1f\"", inches);
|
|
}
|
|
}
|
|
main_scene_.addText(++textId, text, rtabmap::Transform((pt1.x+pt2.x)/2.0f, (pt1.y+pt2.y)/2.0f, (pt1.z+pt2.z)/2.0f, 0, 0,0), measuringTextSize_, quadColor);
|
|
|
|
cv::Vec3f n1 = rtabmap::util3d::transformPoint(m.n1(), rtabmap::opengl_world_T_rtabmap_world);
|
|
cv::Vec3f n2 = rtabmap::util3d::transformPoint(m.n2(), rtabmap::opengl_world_T_rtabmap_world);
|
|
Eigen::Quaternionf q1, q2;
|
|
q1.setFromTwoVectors(Eigen::Vector3f(0,0,1), Eigen::Vector3f(n1[0],n1[1],n1[2]));
|
|
q2.setFromTwoVectors(Eigen::Vector3f(0,0,1), Eigen::Vector3f(n2[0],n2[1],n2[2]));
|
|
|
|
if(sameNormal)
|
|
{
|
|
main_scene_.addQuad(++quadId, quadSize, rtabmap::Transform(pt1.x, pt1.y, pt1.z, q1.x(), q1.y(), q1.z(), q1.w()), quadColor, quadAlpha);
|
|
main_scene_.addQuad(++quadId, quadSize, rtabmap::Transform(pt2.x, pt2.y, pt2.z, q2.x(), q2.y(), q2.z(), q2.w()), quadColor, quadAlpha);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.addCircle(++circleId, quadSize/2, rtabmap::Transform(pt1.x, pt1.y, pt1.z, q1.x(), q1.y(), q1.z(), q1.w()), quadColor, quadAlpha);
|
|
main_scene_.addCircle(++circleId, quadSize/2, rtabmap::Transform(pt2.x, pt2.y, pt2.z, q2.x(), q2.y(), q2.z(), q2.w()), quadColor, quadAlpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!openingDatabase_)
|
|
{
|
|
rtabmapMutex_.lock();
|
|
rtabmapEvents = rtabmapEvents_;
|
|
rtabmapEvents_.clear();
|
|
rtabmapMutex_.unlock();
|
|
|
|
if(rtabmapEvents.size())
|
|
{
|
|
const rtabmap::Statistics & stats = rtabmapEvents.back()->getStats();
|
|
if(!stats.mapCorrection().isNull())
|
|
{
|
|
mapToOdom_ = stats.mapCorrection();
|
|
}
|
|
|
|
std::map<int, rtabmap::Transform>::const_iterator iter = stats.poses().find(optRefId_);
|
|
if(iter != stats.poses().end() && !iter->second.isNull() && optRefPose_)
|
|
{
|
|
// adjust opt mesh pose
|
|
main_scene_.setCloudPose(g_optMeshId, rtabmap::opengl_world_T_rtabmap_world * iter->second * (*optRefPose_).inverse());
|
|
}
|
|
int fastMovement = (int)uValue(stats.data(), rtabmap::Statistics::kMemoryFast_movement(), 0.0f);
|
|
int loopClosure = (int)uValue(stats.data(), rtabmap::Statistics::kLoopAccepted_hypothesis_id(), 0.0f);
|
|
int proximityClosureId = int(uValue(stats.data(), rtabmap::Statistics::kProximitySpace_last_detection_id(), 0.0f));
|
|
int rejected = (int)uValue(stats.data(), rtabmap::Statistics::kLoopRejectedHypothesis(), 0.0f);
|
|
int landmark = (int)uValue(stats.data(), rtabmap::Statistics::kLoopLandmark_detected(), 0.0f);
|
|
if(rtabmapThread_ && rtabmapThread_->isRunning() && loopClosure>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0, 0.5f, 0); // green
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && proximityClosureId>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0.5f, 0.5f, 0); // yellow
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && landmark!=0)
|
|
{
|
|
if(rejected)
|
|
{
|
|
main_scene_.setBackgroundColor(0.5, 0.325f, 0); // dark orange
|
|
}
|
|
else
|
|
{
|
|
main_scene_.setBackgroundColor(1, 0.65f, 0); // orange
|
|
}
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && rejected>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0, 0.2f, 0); // dark green
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && fastMovement)
|
|
{
|
|
main_scene_.setBackgroundColor(0.2f, 0, 0.2f); // dark magenta
|
|
}
|
|
else
|
|
{
|
|
main_scene_.setBackgroundColor(backgroundColor_, backgroundColor_, backgroundColor_);
|
|
}
|
|
|
|
// Update markers
|
|
for(std::map<int, rtabmap::Transform>::const_iterator iter=stats.poses().begin();
|
|
iter!=stats.poses().end() && iter->first<0;
|
|
++iter)
|
|
{
|
|
int id = iter->first;
|
|
if(main_scene_.hasMarker(id))
|
|
{
|
|
//just update pose
|
|
main_scene_.setMarkerPose(id, rtabmap::opengl_world_T_rtabmap_world*iter->second);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.addMarker(id, rtabmap::opengl_world_T_rtabmap_world*iter->second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//backup state
|
|
bool isMeshRendering = main_scene_.isMeshRendering();
|
|
bool isTextureRendering = main_scene_.isMeshTexturing();
|
|
|
|
main_scene_.setMeshRendering(main_scene_.hasMesh(g_optMeshId), main_scene_.hasTexture(g_optMeshId));
|
|
|
|
main_scene_.setFrustumVisible(camera_!=0);
|
|
lastDrawnCloudsCount_ = main_scene_.Render(uvsTransformed, arViewMatrix, arProjectionMatrix);
|
|
double fpsTime = fpsTime_.ticks();
|
|
if(renderingTime_ < fpsTime)
|
|
{
|
|
renderingTime_ = fpsTime;
|
|
}
|
|
|
|
// revert state
|
|
main_scene_.setMeshRendering(isMeshRendering, isTextureRendering);
|
|
|
|
if(rtabmapEvents.size())
|
|
{
|
|
// send statistics to GUI
|
|
if(rtabmapEvents.back()->getStats().refImageId()>0 ||
|
|
!rtabmapEvents.back()->getStats().data().empty())
|
|
{
|
|
UEventsManager::post(new PostRenderEvent(rtabmapEvents.back()));
|
|
rtabmapEvents.pop_back();
|
|
}
|
|
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
rtabmapEvents.clear();
|
|
lastPostRenderEventTime_ = UTimer::now();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(main_scene_.hasCloud(g_optMeshId))
|
|
{
|
|
main_scene_.clear();
|
|
optTextureMesh_.reset(new pcl::TextureMesh);
|
|
optMesh_ = rtabmap::Mesh();
|
|
optTexture_ = cv::Mat();
|
|
}
|
|
|
|
// should be before clearSceneOnNextRender_ in case database is reset
|
|
if(!openingDatabase_)
|
|
{
|
|
rtabmapMutex_.lock();
|
|
rtabmapEvents = rtabmapEvents_;
|
|
rtabmapEvents_.clear();
|
|
rtabmapMutex_.unlock();
|
|
|
|
if(!clearSceneOnNextRender_ && rtabmapEvents.size())
|
|
{
|
|
boost::mutex::scoped_lock lockMesh(meshesMutex_);
|
|
if(createdMeshes_.size())
|
|
{
|
|
if(rtabmapEvents.front()->getStats().refImageId()>0 && rtabmapEvents.front()->getStats().refImageId() < createdMeshes_.rbegin()->first)
|
|
{
|
|
LOGI("Detected new database! new=%d old=%d", rtabmapEvents.front()->getStats().refImageId(), createdMeshes_.rbegin()->first);
|
|
clearSceneOnNextRender_ = true;
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
if(rtabmapEvents.size())
|
|
{
|
|
LOGW("begin and getting rtabmap events %fs", time.ticks());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(clearSceneOnNextRender_)
|
|
{
|
|
LOGI("Clearing all rendering data...");
|
|
sensorMutex_.lock();
|
|
sensorEvents_.clear();
|
|
sensorMutex_.unlock();
|
|
|
|
poseMutex_.lock();
|
|
poseEvents_.clear();
|
|
poseMutex_.unlock();
|
|
|
|
main_scene_.clear();
|
|
clearSceneOnNextRender_ = false;
|
|
if(!openingDatabase_)
|
|
{
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
LOGI("Clearing meshes...");
|
|
createdMeshes_.clear();
|
|
rawPoses_.clear();
|
|
}
|
|
else
|
|
{
|
|
notifyDataLoaded = true;
|
|
}
|
|
totalPoints_ = 0;
|
|
totalPolygons_ = 0;
|
|
lastDrawnCloudsCount_ = 0;
|
|
renderingTime_ = 0.0f;
|
|
lastPostRenderEventTime_ = 0.0;
|
|
lastPoseEventTime_ = 0.0;
|
|
bufferedStatsData_.clear();
|
|
measuresUpdated_ = !measures_.empty();
|
|
measures_.clear();
|
|
}
|
|
|
|
// Did we lose OpenGL context? If so, recreate the context;
|
|
std::set<int> added = main_scene_.getAddedClouds();
|
|
added.erase(-1);
|
|
if(!openingDatabase_)
|
|
{
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
unsigned int meshes = (unsigned int)createdMeshes_.size();
|
|
if(added.size() != meshes)
|
|
{
|
|
LOGI("added (%d) != meshes (%d)", (int)added.size(), meshes);
|
|
boost::mutex::scoped_lock lockRtabmap(rtabmapMutex_);
|
|
UASSERT(rtabmap_!=0);
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter=createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
if(!main_scene_.hasCloud(iter->first) && !iter->second.pose.isNull())
|
|
{
|
|
LOGI("Re-add mesh %d to OpenGL context", iter->first);
|
|
if(iter->second.cloud->isOrganized() && main_scene_.isMeshRendering() && iter->second.polygons.size() == 0)
|
|
{
|
|
iter->second.polygons = rtabmap::util3d::organizedFastMesh(iter->second.cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
iter->second.polygonsLowRes = rtabmap::util3d::organizedFastMesh(iter->second.cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_+LOW_RES_PIX);
|
|
}
|
|
|
|
if(iter->second.cloud->isOrganized() && main_scene_.isMeshTexturing())
|
|
{
|
|
cv::Mat textureRaw;
|
|
textureRaw = rtabmap::uncompressImage(rtabmap_->getMemory()->getImageCompressed(iter->first));
|
|
if(!textureRaw.empty())
|
|
{
|
|
if(renderingTextureDecimation_ > 1)
|
|
{
|
|
cv::Size reducedSize(textureRaw.cols/renderingTextureDecimation_, textureRaw.rows/renderingTextureDecimation_);
|
|
LOGD("resize image from %dx%d to %dx%d", textureRaw.cols, textureRaw.rows, reducedSize.width, reducedSize.height);
|
|
cv::resize(textureRaw, iter->second.texture, reducedSize, 0, 0, cv::INTER_LINEAR);
|
|
}
|
|
else
|
|
{
|
|
iter->second.texture = textureRaw;
|
|
}
|
|
}
|
|
}
|
|
main_scene_.addMesh(iter->first, iter->second, rtabmap::opengl_world_T_rtabmap_world*iter->second.pose, true);
|
|
main_scene_.setCloudVisible(iter->first, iter->second.visible);
|
|
|
|
iter->second.texture = cv::Mat(); // don't keep textures in memory
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(notifyDataLoaded)
|
|
{
|
|
rtabmapMutex_.lock();
|
|
rtabmapEvents = rtabmapEvents_;
|
|
rtabmapEvents_.clear();
|
|
rtabmapMutex_.unlock();
|
|
openingDatabase_ = false;
|
|
}
|
|
|
|
if(rtabmapEvents.size())
|
|
{
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Process rtabmap events %fs", time.ticks());
|
|
#else
|
|
LOGI("Process rtabmap events");
|
|
#endif
|
|
|
|
// update buffered signatures
|
|
std::map<int, rtabmap::SensorData> bufferedSensorData;
|
|
if(!dataRecorderMode_)
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
const rtabmap::Statistics & stats = (*iter)->getStats();
|
|
|
|
// Don't create mesh for the last node added if rehearsal happened or if discarded (small movement)
|
|
int smallMovement = (int)uValue(stats.data(), rtabmap::Statistics::kMemorySmall_movement(), 0.0f);
|
|
int fastMovement = (int)uValue(stats.data(), rtabmap::Statistics::kMemoryFast_movement(), 0.0f);
|
|
int rehearsalMerged = (int)uValue(stats.data(), rtabmap::Statistics::kMemoryRehearsal_merged(), 0.0f);
|
|
if(!localizationMode_ && stats.getLastSignatureData().id() > 0 &&
|
|
smallMovement == 0 && rehearsalMerged == 0 && fastMovement == 0)
|
|
{
|
|
int id = stats.getLastSignatureData().id();
|
|
const rtabmap::Signature & s = stats.getLastSignatureData();
|
|
|
|
if(!trajectoryMode_ &&
|
|
((!s.sensorData().imageRaw().empty() && !s.sensorData().depthRaw().empty()) ||
|
|
!s.sensorData().laserScanRaw().isEmpty()))
|
|
{
|
|
uInsert(bufferedSensorData, std::make_pair(id, s.sensorData()));
|
|
}
|
|
|
|
uInsert(rawPoses_, std::make_pair(id, s.getPose()));
|
|
}
|
|
|
|
int loopClosure = (int)uValue(stats.data(), rtabmap::Statistics::kLoopAccepted_hypothesis_id(), 0.0f);
|
|
int proximityClosureId = int(uValue(stats.data(), rtabmap::Statistics::kProximitySpace_last_detection_id(), 0.0f));
|
|
int rejected = (int)uValue(stats.data(), rtabmap::Statistics::kLoopRejectedHypothesis(), 0.0f);
|
|
int landmark = (int)uValue(stats.data(), rtabmap::Statistics::kLoopLandmark_detected(), 0.0f);
|
|
if(rtabmapThread_ && rtabmapThread_->isRunning() && loopClosure>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0, 0.5f, 0); // green
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && proximityClosureId>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0.5f, 0.5f, 0); // yellow
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && landmark!=0)
|
|
{
|
|
main_scene_.setBackgroundColor(1, 0.65f, 0); // orange
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && rejected>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0, 0.2f, 0); // dark green
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && rehearsalMerged>0)
|
|
{
|
|
main_scene_.setBackgroundColor(0, 0, 0.2f); // blue
|
|
}
|
|
else if(rtabmapThread_ && rtabmapThread_->isRunning() && fastMovement)
|
|
{
|
|
main_scene_.setBackgroundColor(0.2f, 0, 0.2f); // dark magenta
|
|
}
|
|
else
|
|
{
|
|
main_scene_.setBackgroundColor(backgroundColor_, backgroundColor_, backgroundColor_);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Looking for data to load (%d) %fs", (int)bufferedSensorData.size(), time.ticks());
|
|
#endif
|
|
|
|
std::map<int, rtabmap::Transform> posesWithMarkers = rtabmapEvents.back()->getStats().poses();
|
|
if(!rtabmapEvents.back()->getStats().mapCorrection().isNull())
|
|
{
|
|
mapToOdom_ = rtabmapEvents.back()->getStats().mapCorrection();
|
|
}
|
|
|
|
// Transform pose in OpenGL world
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=posesWithMarkers.begin(); iter!=posesWithMarkers.end(); ++iter)
|
|
{
|
|
if(!graphOptimization_ && !dataRecorderMode_)
|
|
{
|
|
std::map<int, rtabmap::Transform>::iterator jter = rawPoses_.find(iter->first);
|
|
if(jter != rawPoses_.end())
|
|
{
|
|
iter->second = rtabmap::opengl_world_T_rtabmap_world*jter->second;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iter->second = rtabmap::opengl_world_T_rtabmap_world*iter->second;
|
|
}
|
|
}
|
|
|
|
std::map<int, rtabmap::Transform> poses(posesWithMarkers.lower_bound(0), posesWithMarkers.end());
|
|
const std::multimap<int, rtabmap::Link> & links = rtabmapEvents.back()->getStats().constraints();
|
|
if(poses.size())
|
|
{
|
|
//update graph
|
|
main_scene_.updateGraph(poses, links);
|
|
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Update graph: %fs", time.ticks());
|
|
#endif
|
|
|
|
// update clouds
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
std::set<std::string> strIds;
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
int id = iter->first;
|
|
if(!iter->second.isNull())
|
|
{
|
|
if(main_scene_.hasCloud(id))
|
|
{
|
|
//just update pose
|
|
main_scene_.setCloudPose(id, iter->second);
|
|
main_scene_.setCloudVisible(id, true);
|
|
std::map<int, rtabmap::Mesh>::iterator meshIter = createdMeshes_.find(id);
|
|
UASSERT(meshIter!=createdMeshes_.end());
|
|
meshIter->second.pose = rtabmap::opengl_world_T_rtabmap_world.inverse()*iter->second;
|
|
meshIter->second.visible = true;
|
|
}
|
|
else
|
|
{
|
|
if(createdMeshes_.find(id) == createdMeshes_.end() &&
|
|
bufferedSensorData.find(id) != bufferedSensorData.end())
|
|
{
|
|
rtabmap::SensorData data = bufferedSensorData.at(id);
|
|
|
|
cv::Mat tmpA, depth;
|
|
data.uncompressData(&tmpA, &depth);
|
|
if(!(!data.imageRaw().empty() && !data.depthRaw().empty()) && !data.laserScanCompressed().isEmpty())
|
|
{
|
|
rtabmap::LaserScan scan;
|
|
data.uncompressData(0, 0, &scan);
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Decompressing data: %fs", time.ticks());
|
|
#endif
|
|
|
|
if((!data.imageRaw().empty() && !data.depthRaw().empty()) || !data.laserScanRaw().isEmpty())
|
|
{
|
|
// Voxelize and filter depending on the previous cloud?
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty() && (!useExternalLidar_ || data.laserScanRaw().isEmpty()))
|
|
{
|
|
int meshDecimation = updateMeshDecimation(data.depthRaw().cols, data.depthRaw().rows);
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, meshDecimation, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
}
|
|
else
|
|
{
|
|
//scan
|
|
cloud = rtabmap::util3d::laserScanToPointCloudRGB(rtabmap::util3d::commonFiltering(data.laserScanRaw(), 1, minCloudDepth_, maxCloudDepth_), data.laserScanRaw().localTransform(), 255, 255, 255);
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Creating node cloud %d (depth=%dx%d rgb=%dx%d, %fs)", id, data.depthRaw().cols, data.depthRaw().rows, data.imageRaw().cols, data.imageRaw().rows, time.ticks());
|
|
#endif
|
|
if(cloud->size() && indices->size())
|
|
{
|
|
std::vector<pcl::Vertices> polygons;
|
|
std::vector<pcl::Vertices> polygonsLowRes;
|
|
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
|
|
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > texCoords;
|
|
#else
|
|
std::vector<Eigen::Vector2f> texCoords;
|
|
#endif
|
|
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
|
|
if(cloud->isOrganized() && main_scene_.isMeshRendering() && main_scene_.isMapRendering())
|
|
{
|
|
polygons = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Creating mesh, %d polygons (%fs)", (int)polygons.size(), time.ticks());
|
|
#endif
|
|
#ifndef DISABLE_VTK
|
|
if(meshDecimationFactor_ > 0.0f && !polygons.empty())
|
|
{
|
|
pcl::PolygonMesh::Ptr tmpMesh(new pcl::PolygonMesh);
|
|
pcl::toPCLPointCloud2(*cloud, tmpMesh->cloud);
|
|
tmpMesh->polygons = polygons;
|
|
rtabmap::util3d::denseMeshPostProcessing<pcl::PointXYZRGB>(tmpMesh, meshDecimationFactor_, 0, cloud, 0);
|
|
|
|
if(!tmpMesh->polygons.empty())
|
|
{
|
|
if(main_scene_.isMeshTexturing() && main_scene_.isMapRendering())
|
|
{
|
|
std::map<int, rtabmap::Transform> cameraPoses;
|
|
std::map<int, rtabmap::CameraModel> cameraModels;
|
|
cameraPoses.insert(std::make_pair(0, rtabmap::Transform::getIdentity()));
|
|
cameraModels.insert(std::make_pair(0, data.cameraModels()[0]));
|
|
pcl::TextureMesh::Ptr textureMesh = rtabmap::util3d::createTextureMesh(
|
|
tmpMesh,
|
|
cameraPoses,
|
|
cameraModels,
|
|
std::map<int, cv::Mat>());
|
|
pcl::fromPCLPointCloud2(textureMesh->cloud, *cloud);
|
|
polygons = textureMesh->tex_polygons[0];
|
|
texCoords = textureMesh->tex_coordinates[0];
|
|
}
|
|
else
|
|
{
|
|
pcl::fromPCLPointCloud2(tmpMesh->cloud, *cloud);
|
|
polygons = tmpMesh->polygons;
|
|
}
|
|
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGE("Mesh decimation factor is too high (%f), returning full mesh (id=%d).", meshDecimationFactor_, data.id());
|
|
polygonsLowRes = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_+LOW_RES_PIX);
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Creating mesh, %d polygons (%fs)", (int)polygons.size(), time.ticks());
|
|
#endif
|
|
}
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Mesh simplication, %d polygons, %d points (%fs)", (int)polygons.size(), (int)cloud->size(), time.ticks());
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
polygonsLowRes = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_+LOW_RES_PIX);
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Creating mesh, %d polygons (%fs)", (int)polygons.size(), time.ticks());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
std::pair<std::map<int, rtabmap::Mesh>::iterator, bool> inserted = createdMeshes_.insert(std::make_pair(id, rtabmap::Mesh()));
|
|
UASSERT(inserted.second);
|
|
inserted.first->second.cloud = cloud;
|
|
inserted.first->second.indices = indices;
|
|
inserted.first->second.polygons = polygons;
|
|
inserted.first->second.polygonsLowRes = polygonsLowRes;
|
|
inserted.first->second.visible = true;
|
|
inserted.first->second.cameraModel = data.cameraModels()[0];
|
|
inserted.first->second.gains[0] = 1.0;
|
|
inserted.first->second.gains[1] = 1.0;
|
|
inserted.first->second.gains[2] = 1.0;
|
|
if((cloud->isOrganized() || !texCoords.empty()) && main_scene_.isMeshTexturing() && main_scene_.isMapRendering())
|
|
{
|
|
inserted.first->second.texCoords = texCoords;
|
|
if(renderingTextureDecimation_ > 1)
|
|
{
|
|
cv::Size reducedSize(data.imageRaw().cols/renderingTextureDecimation_, data.imageRaw().rows/renderingTextureDecimation_);
|
|
cv::resize(data.imageRaw(), inserted.first->second.texture, reducedSize, 0, 0, cv::INTER_LINEAR);
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("resize image from %dx%d to %dx%d (%fs)", data.imageRaw().cols, data.imageRaw().rows, reducedSize.width, reducedSize.height, time.ticks());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
inserted.first->second.texture = data.imageRaw();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(createdMeshes_.find(id) != createdMeshes_.end())
|
|
{
|
|
rtabmap::Mesh & mesh = createdMeshes_.at(id);
|
|
totalPoints_+=mesh.indices->size();
|
|
totalPolygons_ += mesh.polygons.size();
|
|
mesh.pose = rtabmap::opengl_world_T_rtabmap_world.inverse()*iter->second;
|
|
main_scene_.addMesh(id, mesh, iter->second, true);
|
|
#ifdef DEBUG_RENDERING_PERFORMANCE
|
|
LOGW("Adding mesh to scene: %fs", time.ticks());
|
|
#endif
|
|
mesh.texture = cv::Mat(); // don't keep textures in memory
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//filter poses?
|
|
if(poses.size() > 2)
|
|
{
|
|
if(nodesFiltering_)
|
|
{
|
|
for(std::multimap<int, rtabmap::Link>::const_iterator iter=links.begin(); iter!=links.end(); ++iter)
|
|
{
|
|
if(iter->second.type() != rtabmap::Link::kNeighbor)
|
|
{
|
|
int oldId = iter->second.to()>iter->second.from()?iter->second.from():iter->second.to();
|
|
poses.erase(oldId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!poses.empty())
|
|
{
|
|
//update cloud visibility
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
std::set<int> addedClouds = main_scene_.getAddedClouds();
|
|
for(std::set<int>::const_iterator iter=addedClouds.begin();
|
|
iter!=addedClouds.end();
|
|
++iter)
|
|
{
|
|
if(*iter > 0 && poses.find(*iter) == poses.end())
|
|
{
|
|
main_scene_.setCloudVisible(*iter, false);
|
|
std::map<int, rtabmap::Mesh>::iterator meshIter = createdMeshes_.find(*iter);
|
|
UASSERT(meshIter!=createdMeshes_.end());
|
|
meshIter->second.visible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update markers
|
|
std::set<int> addedMarkers = main_scene_.getAddedMarkers();
|
|
for(std::set<int>::const_iterator iter=addedMarkers.begin();
|
|
iter!=addedMarkers.end();
|
|
++iter)
|
|
{
|
|
if(posesWithMarkers.find(*iter) == posesWithMarkers.end())
|
|
{
|
|
main_scene_.removeMarker(*iter);
|
|
}
|
|
}
|
|
for(std::map<int, rtabmap::Transform>::const_iterator iter=posesWithMarkers.begin();
|
|
iter!=posesWithMarkers.end() && iter->first<0;
|
|
++iter)
|
|
{
|
|
int id = iter->first;
|
|
if(main_scene_.hasMarker(id))
|
|
{
|
|
//just update pose
|
|
main_scene_.setMarkerPose(id, iter->second);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.addMarker(id, iter->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dataRecorderMode_ || !rtabmapEvents.size())
|
|
{
|
|
main_scene_.setCloudVisible(-1, odomCloudShown_ && !trajectoryMode_ && sensorCaptureThread_!=0);
|
|
|
|
//just process the last one
|
|
if(!sensorEvent.info().odomPose.isNull())
|
|
{
|
|
if(odomCloudShown_ && !trajectoryMode_)
|
|
{
|
|
if((!sensorEvent.data().imageRaw().empty() && !sensorEvent.data().depthRaw().empty()) || !sensorEvent.data().laserScanRaw().isEmpty())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
if(!sensorEvent.data().imageRaw().empty() && !sensorEvent.data().depthRaw().empty() && (!useExternalLidar_ || sensorEvent.data().laserScanRaw().isEmpty()))
|
|
{
|
|
int meshDecimation = updateMeshDecimation(sensorEvent.data().depthRaw().cols, sensorEvent.data().depthRaw().rows);
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(sensorEvent.data(), meshDecimation, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
}
|
|
else
|
|
{
|
|
//scan
|
|
cloud = rtabmap::util3d::laserScanToPointCloudRGB(rtabmap::util3d::commonFiltering(sensorEvent.data().laserScanRaw(), 1, minCloudDepth_, maxCloudDepth_), sensorEvent.data().laserScanRaw().localTransform(), 255, 255, 255);
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
|
|
if(cloud->size() && indices->size())
|
|
{
|
|
LOGI("Created odom cloud (rgb=%dx%d depth=%dx%d cloud=%dx%d)",
|
|
sensorEvent.data().imageRaw().cols, sensorEvent.data().imageRaw().rows,
|
|
sensorEvent.data().depthRaw().cols, sensorEvent.data().depthRaw().rows,
|
|
(int)cloud->width, (int)cloud->height);
|
|
main_scene_.addCloud(-1, cloud, indices, rtabmap::opengl_world_T_rtabmap_world*mapToOdom_*sensorEvent.info().odomPose);
|
|
main_scene_.setCloudVisible(-1, true);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Generated cloud is empty!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Odom data images/scans are empty!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(gainCompensationOnNextRender_>0)
|
|
{
|
|
gainCompensation(gainCompensationOnNextRender_==2);
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
main_scene_.updateGains(iter->first, iter->second.gains[0], iter->second.gains[1], iter->second.gains[2]);
|
|
}
|
|
gainCompensationOnNextRender_ = 0;
|
|
notifyDataLoaded = true;
|
|
}
|
|
|
|
if(bilateralFilteringOnNextRender_)
|
|
{
|
|
LOGI("Bilateral filtering...");
|
|
bilateralFilteringOnNextRender_ = false;
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
if(iter->second.cloud->size() && iter->second.indices->size())
|
|
{
|
|
if(smoothMesh(iter->first, iter->second))
|
|
{
|
|
main_scene_.updateMesh(iter->first, iter->second);
|
|
}
|
|
}
|
|
}
|
|
notifyDataLoaded = true;
|
|
}
|
|
|
|
if(filterPolygonsOnNextRender_ && clusterRatio_>0.0f)
|
|
{
|
|
LOGI("Polygon filtering...");
|
|
filterPolygonsOnNextRender_ = false;
|
|
boost::mutex::scoped_lock lock(meshesMutex_);
|
|
UTimer time;
|
|
for(std::map<int, rtabmap::Mesh>::iterator iter = createdMeshes_.begin(); iter!=createdMeshes_.end(); ++iter)
|
|
{
|
|
if(iter->second.polygons.size())
|
|
{
|
|
// filter polygons
|
|
iter->second.polygons = filterOrganizedPolygons(iter->second.polygons, iter->second.cloud->size());
|
|
main_scene_.updateCloudPolygons(iter->first, iter->second.polygons);
|
|
}
|
|
}
|
|
notifyDataLoaded = true;
|
|
}
|
|
|
|
main_scene_.setFrustumVisible(camera_!=0);
|
|
lastDrawnCloudsCount_ = main_scene_.Render(uvsTransformed, arViewMatrix, arProjectionMatrix, occlusionMesh, true);
|
|
double fpsTime = fpsTime_.ticks();
|
|
if(renderingTime_ < fpsTime)
|
|
{
|
|
renderingTime_ = fpsTime;
|
|
}
|
|
|
|
if(rtabmapEvents.size())
|
|
{
|
|
// send statistics to GUI
|
|
LOGI("New data added to map, rendering time: %fs", renderingTime_);
|
|
if(rtabmapEvents.back()->getStats().refImageId()>0 ||
|
|
!rtabmapEvents.back()->getStats().data().empty())
|
|
{
|
|
UEventsManager::post(new PostRenderEvent(rtabmapEvents.back()));
|
|
rtabmapEvents.pop_back();
|
|
}
|
|
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
rtabmapEvents.clear();
|
|
|
|
lastPostRenderEventTime_ = UTimer::now();
|
|
|
|
if(sensorCaptureThread_!=0 && lastPoseEventTime_>0.0 && UTimer::now()-lastPoseEventTime_ > 1.0)
|
|
{
|
|
UERROR("TangoPoseEventNotReceived");
|
|
UEventsManager::post(new rtabmap::CameraInfoEvent(10, "TangoPoseEventNotReceived", uNumber2Str(UTimer::now()-lastPoseEventTime_, 6)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if(takeScreenshotOnNextRender_)
|
|
{
|
|
takeScreenshotOnNextRender_ = false;
|
|
int w = main_scene_.getViewPortWidth();
|
|
int h = main_scene_.getViewPortHeight();
|
|
cv::Mat image(h, w, CV_8UC4);
|
|
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data);
|
|
cv::flip(image, image, 0);
|
|
cv::cvtColor(image, image, cv::COLOR_RGBA2BGRA);
|
|
cv::Mat roi;
|
|
if(w>h)
|
|
{
|
|
int offset = (w-h)/2;
|
|
roi = image(cv::Range::all(), cv::Range(offset,offset+h));
|
|
}
|
|
else
|
|
{
|
|
int offset = (h-w)/2;
|
|
roi = image(cv::Range(offset,offset+w), cv::Range::all());
|
|
}
|
|
rtabmapMutex_.lock();
|
|
LOGI("Saving screenshot %dx%d...", roi.cols, roi.rows);
|
|
rtabmap_->getMemory()->savePreviewImage(roi);
|
|
rtabmapMutex_.unlock();
|
|
screenshotReady_.release();
|
|
}
|
|
|
|
if((rtabmapThread_==0 || !rtabmapThread_->isRunning()) && lastPostRenderEventTime_ > 0.0)
|
|
{
|
|
double interval = UTimer::now() - lastPostRenderEventTime_;
|
|
double updateInterval = 1.0;
|
|
if(!openingDatabase_ && rtabmapThread_)
|
|
{
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
if(rtabmapThread_ && rtabmapThread_->getDetectorRate()>0.0f)
|
|
{
|
|
updateInterval = 1.0f/rtabmapThread_->getDetectorRate();
|
|
}
|
|
}
|
|
|
|
if(interval >= updateInterval)
|
|
{
|
|
if(!openingDatabase_)
|
|
{
|
|
// don't send event when we are opening the database (init events already sent)
|
|
UEventsManager::post(new PostRenderEvent());
|
|
}
|
|
lastPostRenderEventTime_ = UTimer::now();
|
|
}
|
|
}
|
|
|
|
return notifyDataLoaded||notifyCameraStarted?1:0;
|
|
}
|
|
catch(const UException & e)
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
rtabmapEvents.clear();
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
return -2;
|
|
}
|
|
catch(const cv::Exception & e)
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
rtabmapEvents.clear();
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
return -1;
|
|
}
|
|
catch(const std::exception & e)
|
|
{
|
|
for(std::list<rtabmap::RtabmapEvent*>::iterator iter=rtabmapEvents.begin(); iter!=rtabmapEvents.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
rtabmapEvents.clear();
|
|
UERROR("Exception! msg=\"%s\"", e.what());
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::updateMeasuringState()
|
|
{
|
|
rtabmap::Transform openglCam = main_scene_.GetOpenGLCameraPose();
|
|
rtabmap::Transform rtabmapCam = rtabmap::rtabmap_world_T_opengl_world * openglCam * rtabmap::opengl_world_T_rtabmap_world;
|
|
Eigen::Vector3f origin(openglCam.x(), openglCam.y(), openglCam.z());
|
|
Eigen::Vector3f rtabmapOrigin(rtabmapCam.x(), rtabmapCam.y(), rtabmapCam.z());
|
|
rtabmap::Transform v = openglCam.rotation() * rtabmap::Transform(0,0,-1,0,0,0);
|
|
Eigen::Vector3f dir(v.x(), v.y(), v.z());
|
|
v = rtabmapCam.rotation() * rtabmap::Transform(1,0,0,0,0,0);
|
|
Eigen::Vector3f rtabmapDir(v.x(), v.y(), v.z());
|
|
tango_gl::Color color(1.0f, 0.0f, 1.0f);
|
|
tango_gl::Color xColor(1.0f, 0.0f, 0.0f); // in rtabmap world
|
|
tango_gl::Color yColor(0.0f, 1.0f, 0.0f); // in rtabmap world
|
|
tango_gl::Color zColor(0.0f, 0.0f, 1.0f); // in rtabmap world
|
|
float circleRadius = 0.025;
|
|
float quadSize=0.05f;
|
|
float quadAlpha = 0.3f;
|
|
|
|
main_scene_.removeQuad(55555);
|
|
main_scene_.removeQuad(55556);
|
|
main_scene_.removeCircle(55555);
|
|
main_scene_.removeCircle(55556);
|
|
main_scene_.removeLine(55555);
|
|
main_scene_.removeText(55555);
|
|
|
|
if(removeMeasureClicked_)
|
|
{
|
|
if(!measuringTmpPts_.empty())
|
|
{
|
|
measuringTmpPts_.clear();
|
|
measuringTmpNormals_.clear();
|
|
}
|
|
else
|
|
{
|
|
bool removed = false;
|
|
for(std::list<Measure>::iterator iter=measures_.begin(); iter!=measures_.end() && !removed; ++iter)
|
|
{
|
|
const Measure & m = *iter;
|
|
|
|
for(int i=0; i<2;++i)
|
|
{
|
|
// intersecting the quad?
|
|
cv::Point3f pt = i==0?m.pt1():m.pt2();
|
|
cv::Point3f n = i==0?m.n1():m.n2();
|
|
pt = rtabmap::util3d::transformPoint(pt, rtabmap::opengl_world_T_rtabmap_world);
|
|
n = rtabmap::util3d::transformPoint(n, rtabmap::opengl_world_T_rtabmap_world);
|
|
Eigen::Quaternionf q;
|
|
q.setFromTwoVectors(Eigen::Vector3f(0,0,-1), Eigen::Vector3f(n.x,n.y,n.z));
|
|
float dTmp;
|
|
Eigen::Vector3f nTmp;
|
|
int indexTmp;
|
|
if(rtabmap::util3d::intersectRayMesh(
|
|
origin,
|
|
dir,
|
|
*rtabmap::util3d::transformPointCloud(quadSample_, rtabmap::Transform(pt.x, pt.y, pt.z, q.x(), q.y(), q.z(), q.w())),
|
|
quadSamplePolygons_,
|
|
false,
|
|
dTmp,
|
|
nTmp,
|
|
indexTmp))
|
|
{
|
|
measures_.erase(iter);
|
|
removed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
measuresUpdated_ |= removed;
|
|
}
|
|
}
|
|
|
|
float distance =0.0f;
|
|
Eigen::Vector3f n;
|
|
int index;
|
|
if(rtabmap::util3d::intersectRayMesh(
|
|
rtabmapOrigin,
|
|
rtabmapDir,
|
|
*optMesh_.cloud,
|
|
optMesh_.polygons,
|
|
true,
|
|
distance,
|
|
n,
|
|
index))
|
|
{
|
|
|
|
Eigen::Vector3f intersectionPt = origin + dir*distance;
|
|
cv::Point3f pt(intersectionPt[0], intersectionPt[1], intersectionPt[2]);
|
|
cv::Point3f normal(n[0], n[1], n[2]); // rtabmap world
|
|
cv::Point3f normalGl = rtabmap::util3d::transformPoint(normal, rtabmap::opengl_world_T_rtabmap_world); // opengl world
|
|
tango_gl::Color quadColor = color;
|
|
float normalProdX = n.dot(Eigen::Vector3f(snapAxes_[0][0], snapAxes_[0][1], snapAxes_[0][2]));
|
|
float normalProdY = n.dot(Eigen::Vector3f(snapAxes_[1][0], snapAxes_[1][1], snapAxes_[1][2]));
|
|
float normalProdZ = n.dot(Eigen::Vector3f(snapAxes_[2][0], snapAxes_[2][1], snapAxes_[2][2]));
|
|
if(fabs(normal.x) > fabs(normal.z) && fabs(normal.y) > fabs(normal.z))
|
|
{
|
|
if(fabs(normalProdX) > snapAxisThr_)
|
|
{
|
|
normal.x = snapAxes_[0][0] * (normalProdX>0?1:-1);
|
|
normal.y = snapAxes_[0][1] * (normalProdX>0?1:-1);
|
|
normal.z = snapAxes_[0][2] * (normalProdX>0?1:-1);
|
|
quadColor = xColor;
|
|
}
|
|
else if(fabs(normalProdY) > snapAxisThr_)
|
|
{
|
|
normal.x = snapAxes_[1][0] * (normalProdY>0?1:-1);
|
|
normal.y = snapAxes_[1][1] * (normalProdY>0?1:-1);
|
|
normal.z = snapAxes_[1][2] * (normalProdY>0?1:-1);
|
|
quadColor = yColor;
|
|
}
|
|
else if(measuringMode_ == 0)
|
|
{
|
|
// We force to be aligned with xy plane
|
|
normal.z = 0;
|
|
float n = cv::norm(normal);
|
|
normal.x/=n;
|
|
normal.y/=n;
|
|
normal.z/=n;
|
|
}
|
|
}
|
|
else if(fabs(normalProdZ) > snapAxisThr_)
|
|
{
|
|
normal.x = snapAxes_[2][0] * (normalProdZ>0?1:-1);
|
|
normal.y = snapAxes_[2][1] * (normalProdZ>0?1:-1);
|
|
normal.z = snapAxes_[2][2] * (normalProdZ>0?1:-1);
|
|
quadColor = zColor;
|
|
}
|
|
normalGl = rtabmap::util3d::transformPoint(normal, rtabmap::opengl_world_T_rtabmap_world);
|
|
|
|
if(teleportClicked_)
|
|
{
|
|
camera_->resetOrigin(rtabmap::Transform(-pt.z, -pt.x, pt.y-Scene::kHeightOffset.y,0,0,0));
|
|
}
|
|
else if(addMeasureClicked_) // Add measure
|
|
{
|
|
if((measuringMode_ == 1 || measuringTmpPts_.size()==1))
|
|
{
|
|
if(measuringMode_ == 1) // Height single click
|
|
{
|
|
measuringTmpPts_.clear();
|
|
measuringTmpNormals_.clear();
|
|
|
|
normal.x=0;
|
|
normal.y=0;
|
|
normal.z=1;
|
|
normalGl = rtabmap::util3d::transformPoint(normal, rtabmap::opengl_world_T_rtabmap_world);
|
|
|
|
measuringTmpPts_.push_back(pt);
|
|
measuringTmpNormals_.push_back(normalGl);
|
|
pt.y = 0;
|
|
measuringTmpPts_.push_back(pt);
|
|
measuringTmpNormals_.push_back(normalGl);
|
|
}
|
|
else if(measuringMode_ == 0 || measuringMode_ == 2) // Plane to Plane or point to point
|
|
{
|
|
if(measuringMode_ == 0)
|
|
{
|
|
normalGl = measuringTmpNormals_.front();
|
|
|
|
// project point on line
|
|
float n = (pt-measuringTmpPts_.front()).dot(measuringTmpNormals_.front());
|
|
pt = measuringTmpPts_.front() + (measuringTmpNormals_.front() * n);
|
|
}
|
|
measuringTmpPts_.push_back(pt);
|
|
measuringTmpNormals_.push_back(normalGl);
|
|
}
|
|
|
|
// Add measure!
|
|
const cv::Point3f & pt1 = measuringTmpPts_.at(0);
|
|
const cv::Point3f & pt2 = measuringTmpPts_.at(1);
|
|
const cv::Vec3f & n1 = measuringTmpNormals_.at(0);
|
|
const cv::Vec3f & n2 = measuringTmpNormals_.at(1);
|
|
|
|
Measure measure(
|
|
rtabmap::util3d::transformPoint(pt1, rtabmap::rtabmap_world_T_opengl_world),
|
|
rtabmap::util3d::transformPoint(pt2, rtabmap::rtabmap_world_T_opengl_world),
|
|
rtabmap::util3d::transformPoint(n1, rtabmap::rtabmap_world_T_opengl_world),
|
|
rtabmap::util3d::transformPoint(n2, rtabmap::rtabmap_world_T_opengl_world));
|
|
|
|
if(measure.length()>=0.01f)
|
|
{
|
|
measures_.push_back(measure);
|
|
|
|
measuringTmpPts_.clear();
|
|
measuringTmpNormals_.clear();
|
|
|
|
measuresUpdated_ = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
measuringTmpPts_.clear();
|
|
measuringTmpNormals_.clear();
|
|
|
|
// init the first point
|
|
measuringTmpPts_.push_back(pt);
|
|
measuringTmpNormals_.push_back(normalGl);
|
|
}
|
|
}
|
|
else if(measuringMode_ >= 0)
|
|
{
|
|
// move action
|
|
if(measuringMode_ == 0 && measuringTmpNormals_.size()==1)
|
|
{
|
|
normalGl = measuringTmpNormals_.front();
|
|
normal = rtabmap::util3d::transformPoint(normalGl, rtabmap::rtabmap_world_T_opengl_world);
|
|
n = Eigen::Vector3f(normal.x, normal.y, normal.z);
|
|
float normalProdX = n.dot(Eigen::Vector3f(snapAxes_[0][0], snapAxes_[0][1], snapAxes_[0][2]));
|
|
float normalProdY = n.dot(Eigen::Vector3f(snapAxes_[1][0], snapAxes_[1][1], snapAxes_[1][2]));
|
|
float normalProdZ = n.dot(Eigen::Vector3f(snapAxes_[2][0], snapAxes_[2][1], snapAxes_[2][2]));
|
|
if(fabs(normalProdX) > snapAxisThr_)
|
|
{
|
|
quadColor = xColor;
|
|
}
|
|
else if(fabs(normalProdY) > snapAxisThr_)
|
|
{
|
|
quadColor = yColor;
|
|
}
|
|
else if(fabs(normalProdZ) > snapAxisThr_)
|
|
{
|
|
quadColor = zColor;
|
|
}
|
|
else
|
|
{
|
|
quadColor = color;
|
|
}
|
|
}
|
|
|
|
float quadWidthLeft = 0.05;
|
|
float quadWidthRight = quadWidthLeft;
|
|
float quadHeightBottom = quadWidthLeft;
|
|
float quadHeightTop = quadWidthLeft;
|
|
cv::Point3f pt2 = pt;
|
|
float lineLength = 0.0f;
|
|
// project point on line
|
|
if(measuringTmpPts_.size() == 1)
|
|
{
|
|
cv::Point3f v = pt2-measuringTmpPts_.front();
|
|
if(measuringMode_ == 0)
|
|
{
|
|
float n = v.dot(measuringTmpNormals_.front());
|
|
lineLength = fabs(n);
|
|
pt2 = measuringTmpPts_.front() + (measuringTmpNormals_.front() * n);
|
|
v = rtabmap::util3d::transformPoint(v, rtabmap::rtabmap_world_T_opengl_world);
|
|
if(!(fabs(normal.z) > fabs(normal.x) && fabs(normal.z) > fabs(normal.y)))
|
|
{
|
|
quadHeightTop = v.z>quadSize?v.z:quadSize;
|
|
quadHeightBottom = -v.z>quadSize?-v.z:quadSize;
|
|
if(fabs(normal.x) > fabs(normal.y))
|
|
{
|
|
cv::Point3f y = cv::Point3f(0,0,1).cross(normal);
|
|
float n = v.dot(y);
|
|
quadWidthRight = n>quadSize?n:quadSize;
|
|
quadWidthLeft = n<-quadSize?fabs(n):quadSize;
|
|
if(normal.x>0)
|
|
{
|
|
quadWidthLeft = n>quadSize?n:quadSize;
|
|
quadWidthRight = n<-quadSize?fabs(n):quadSize;
|
|
float tmp =quadHeightTop;
|
|
quadHeightTop= quadHeightBottom;
|
|
quadHeightBottom = tmp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cv::Point3f x = normal.cross(cv::Point3f(0,0,1));
|
|
float n = v.dot(x);
|
|
quadWidthRight = n<-quadSize?fabs(n):quadSize;
|
|
quadWidthLeft = n>quadSize?n:quadSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(normal.z>0)
|
|
{
|
|
quadHeightBottom = v.x<-quadSize?fabs(v.x):quadSize;
|
|
quadHeightTop = v.x>quadSize?v.x:quadSize;
|
|
quadWidthRight = v.y<-quadSize?fabs(v.y):quadSize;
|
|
quadWidthLeft = v.y>quadSize?v.y:quadSize;
|
|
}
|
|
else
|
|
{
|
|
quadHeightTop = v.x<-quadSize?fabs(v.x):quadSize;
|
|
quadHeightBottom = v.x>quadSize?v.x:quadSize;
|
|
quadWidthRight = v.y<-quadSize?fabs(v.y):quadSize;
|
|
quadWidthLeft = v.y>quadSize?v.y:quadSize;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lineLength = cv::norm(v);
|
|
}
|
|
|
|
std::string text = uFormat("%0.2f m", lineLength);
|
|
if(!metricSystem_)
|
|
{
|
|
static const double METERS_PER_FOOT = 0.3048;
|
|
static double INCHES_PER_FOOT = 12.0;
|
|
double lengthInFeet = lineLength / METERS_PER_FOOT;
|
|
int feet = (int)lengthInFeet;
|
|
float inches = (lengthInFeet - feet) * INCHES_PER_FOOT;
|
|
if(feet > 0)
|
|
{
|
|
text = uFormat("%d' %0.1f\"", feet, inches);
|
|
}
|
|
else
|
|
{
|
|
text = uFormat("%0.1f\"", inches);
|
|
}
|
|
}
|
|
main_scene_.addText(55555, text, rtabmap::Transform(pt.x, pt.y, pt.z, 0,0,0), 0.05f, measuringMode_ == 0?quadColor:color);
|
|
}
|
|
|
|
Eigen::Quaternionf q;
|
|
q.setFromTwoVectors(Eigen::Vector3f(0,0,1), Eigen::Vector3f(normalGl.x,normalGl.y,normalGl.z));
|
|
|
|
if(measuringTmpPts_.size() == 1 && measuringMode_ != 1)
|
|
{
|
|
const cv::Point3f & pt1 = measuringTmpPts_.at(0);
|
|
main_scene_.addLine(55555, pt1, pt2, measuringMode_ == 0?quadColor:color);
|
|
|
|
if(measuringMode_ == 2)
|
|
{
|
|
// Use respective orientation for each circle
|
|
Eigen::Quaternionf q1;
|
|
cv::Vec3f normalGL1 = measuringTmpNormals_.front();
|
|
q1.setFromTwoVectors(Eigen::Vector3f(0,0,1), Eigen::Vector3f(normalGL1[0],normalGL1[0],normalGL1[0]));
|
|
main_scene_.addCircle(55555,
|
|
circleRadius,
|
|
rtabmap::Transform(
|
|
measuringTmpPts_.front().x,
|
|
measuringTmpPts_.front().y,
|
|
measuringTmpPts_.front().z,
|
|
q1.x(),q1.y(), q1.z(), q1.w()), color, quadAlpha);
|
|
main_scene_.addCircle(55556,
|
|
circleRadius,
|
|
rtabmap::Transform(pt2.x, pt2.y, pt2.z, q.x(),q.y(), q.z(), q.w()), color, quadAlpha);
|
|
}
|
|
else
|
|
{
|
|
// Use same orientation for both quads
|
|
main_scene_.addQuad(55555,
|
|
quadSize,
|
|
rtabmap::Transform(
|
|
measuringTmpPts_.front().x,
|
|
measuringTmpPts_.front().y,
|
|
measuringTmpPts_.front().z,
|
|
q.x(),q.y(), q.z(), q.w()), quadColor, quadAlpha);
|
|
main_scene_.addQuad(55556,
|
|
quadWidthLeft,
|
|
quadWidthRight,
|
|
quadHeightBottom,
|
|
quadHeightTop,
|
|
rtabmap::Transform(pt2.x, pt2.y, pt2.z, q.x(),q.y(), q.z(), q.w()), quadColor, quadAlpha);
|
|
}
|
|
}
|
|
else if(measuringMode_ == 2)
|
|
{
|
|
main_scene_.addCircle(55555,
|
|
circleRadius,
|
|
rtabmap::Transform(pt2.x, pt2.y, pt2.z, q.x(),q.y(), q.z(), q.w()), color, quadAlpha);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.addQuad(55555,
|
|
quadSize,
|
|
rtabmap::Transform(pt2.x, pt2.y, pt2.z, q.x(),q.y(), q.z(), q.w()), quadColor, quadAlpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
Eigen::Vector3f target = origin+dir*(distance<100.0f?distance:0.5f);
|
|
rtabmap::Transform pose(target[0], target[1], target[2], 0,0,0);
|
|
if(main_scene_.hasCloud(-99999))
|
|
{
|
|
main_scene_.setCloudPose(-99999, pose);
|
|
}
|
|
else
|
|
{
|
|
main_scene_.addCloud(-99999, targetPoint_, pcl::IndicesPtr(), pose);
|
|
}
|
|
|
|
// reset states
|
|
removeMeasureClicked_ = false;
|
|
addMeasureClicked_ = false;
|
|
teleportClicked_ = false;
|
|
}
|
|
|
|
void RTABMapApp::SetCameraType(
|
|
tango_gl::GestureCamera::CameraType camera_type) {
|
|
main_scene_.SetCameraType(camera_type);
|
|
}
|
|
|
|
void RTABMapApp::OnTouchEvent(int touch_count,
|
|
tango_gl::GestureCamera::TouchEvent event,
|
|
float x0, float y0, float x1, float y1) {
|
|
main_scene_.OnTouchEvent(touch_count, event, x0, y0, x1, y1);
|
|
}
|
|
|
|
void RTABMapApp::setPausedMapping(bool paused)
|
|
{
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
main_scene_.setBackgroundColor(backgroundColor_, backgroundColor_, backgroundColor_);
|
|
}
|
|
|
|
if(rtabmapThread_)
|
|
{
|
|
if(rtabmapThread_->isRunning() && paused)
|
|
{
|
|
LOGW("Pause!");
|
|
rtabmapThread_->unregisterFromEventsManager();
|
|
rtabmapThread_->join(true);
|
|
}
|
|
else if(!rtabmapThread_->isRunning() && !paused)
|
|
{
|
|
LOGW("Resume!");
|
|
rtabmap_->triggerNewMap();
|
|
rtabmap_->parseParameters(getRtabmapParameters());
|
|
rtabmapThread_->registerToEventsManager();
|
|
rtabmapThread_->start();
|
|
}
|
|
}
|
|
}
|
|
void RTABMapApp::setOnlineBlending(bool enabled)
|
|
{
|
|
main_scene_.setBlending(enabled);
|
|
}
|
|
void RTABMapApp::setMapCloudShown(bool shown)
|
|
{
|
|
main_scene_.setMapRendering(shown);
|
|
}
|
|
void RTABMapApp::setOdomCloudShown(bool shown)
|
|
{
|
|
odomCloudShown_ = shown;
|
|
main_scene_.setTraceVisible(shown);
|
|
}
|
|
void RTABMapApp::setMeshRendering(bool enabled, bool withTexture)
|
|
{
|
|
main_scene_.setMeshRendering(enabled, withTexture);
|
|
}
|
|
void RTABMapApp::setPointSize(float value)
|
|
{
|
|
main_scene_.setPointSize(value);
|
|
}
|
|
void RTABMapApp::setFOV(float angle)
|
|
{
|
|
main_scene_.setFOV(angle);
|
|
}
|
|
void RTABMapApp::setOrthoCropFactor(float value)
|
|
{
|
|
main_scene_.setOrthoCropFactor(value);
|
|
}
|
|
void RTABMapApp::setGridRotation(float value)
|
|
{
|
|
// Update measuring snap axes
|
|
rtabmap::Transform rotation(0,0, value * DEGREE_2_RADIANS);
|
|
renderingMutex_.lock();
|
|
snapAxes_[0] = rtabmap::util3d::transformPoint(cv::Vec3f(1,0,0), rotation);
|
|
snapAxes_[1] = rtabmap::util3d::transformPoint(cv::Vec3f(0,1,0), rotation);
|
|
measuresUpdated_ = true;
|
|
renderingMutex_.unlock();
|
|
|
|
// Update grid
|
|
main_scene_.setGridRotation(value);
|
|
}
|
|
void RTABMapApp::setLighting(bool enabled)
|
|
{
|
|
main_scene_.setLighting(enabled);
|
|
}
|
|
void RTABMapApp::setBackfaceCulling(bool enabled)
|
|
{
|
|
main_scene_.setBackfaceCulling(enabled);
|
|
}
|
|
void RTABMapApp::setWireframe(bool enabled)
|
|
{
|
|
main_scene_.setWireframe(enabled);
|
|
}
|
|
void RTABMapApp::setTextureColorSeamsHidden(bool hidden)
|
|
{
|
|
main_scene_.setTextureColorSeamsHidden(hidden);
|
|
}
|
|
|
|
void RTABMapApp::setLocalizationMode(bool enabled)
|
|
{
|
|
localizationMode_ = enabled;
|
|
rtabmap::ParametersMap parameters;
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapStartNewMapOnLoopClosure(), uBool2Str(!localizationMode_ && appendMode_)));
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), uBool2Str(!localizationMode_)));
|
|
this->post(new rtabmap::ParamEvent(parameters));
|
|
}
|
|
void RTABMapApp::setTrajectoryMode(bool enabled)
|
|
{
|
|
trajectoryMode_ = enabled;
|
|
this->post(new rtabmap::ParamEvent(rtabmap::Parameters::kMemBinDataKept(), uBool2Str(!trajectoryMode_)));
|
|
}
|
|
|
|
void RTABMapApp::setGraphOptimization(bool enabled)
|
|
{
|
|
graphOptimization_ = enabled;
|
|
if((sensorCaptureThread_ == 0) && rtabmap_ && rtabmap_->getMemory()->getLastWorkingSignature()!=0)
|
|
{
|
|
std::map<int, rtabmap::Transform> poses;
|
|
std::multimap<int, rtabmap::Link> links;
|
|
rtabmap_->getGraph(poses, links, true, true);
|
|
if(poses.size())
|
|
{
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
rtabmap::Statistics stats = rtabmap_->getStatistics();
|
|
stats.setPoses(poses);
|
|
stats.setConstraints(links);
|
|
|
|
LOGI("Send rtabmap event to update graph...");
|
|
rtabmapEvents_.push_back(new rtabmap::RtabmapEvent(stats));
|
|
|
|
rtabmap_->setOptimizedPoses(poses, links);
|
|
}
|
|
}
|
|
}
|
|
void RTABMapApp::setNodesFiltering(bool enabled)
|
|
{
|
|
nodesFiltering_ = enabled;
|
|
setGraphOptimization(graphOptimization_); // this will resend the graph if paused
|
|
}
|
|
void RTABMapApp::setGraphVisible(bool visible)
|
|
{
|
|
main_scene_.setGraphVisible(visible);
|
|
main_scene_.setTraceVisible(visible);
|
|
setGraphOptimization(graphOptimization_); // this will republish the graph
|
|
}
|
|
void RTABMapApp::setGridVisible(bool visible)
|
|
{
|
|
main_scene_.setGridVisible(visible);
|
|
}
|
|
|
|
void RTABMapApp::setRawScanSaved(bool enabled)
|
|
{
|
|
if(rawScanSaved_ != enabled)
|
|
{
|
|
rawScanSaved_ = enabled;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setCameraColor(bool enabled)
|
|
{
|
|
if(cameraColor_ != enabled)
|
|
{
|
|
cameraColor_ = enabled;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setFullResolution(bool enabled)
|
|
{
|
|
if(fullResolution_ != enabled)
|
|
{
|
|
fullResolution_ = enabled;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setSmoothing(bool enabled)
|
|
{
|
|
if(smoothing_ != enabled)
|
|
{
|
|
smoothing_ = enabled;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setDepthFromMotion(bool enabled)
|
|
{
|
|
if(depthFromMotion_ != enabled)
|
|
{
|
|
depthFromMotion_ = enabled;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setAppendMode(bool enabled)
|
|
{
|
|
if(appendMode_ != enabled)
|
|
{
|
|
appendMode_ = enabled;
|
|
rtabmap::ParametersMap parameters;
|
|
parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapStartNewMapOnLoopClosure(), uBool2Str(!localizationMode_ && appendMode_)));
|
|
this->post(new rtabmap::ParamEvent(parameters));
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setUpstreamRelocalizationAccThr(float value)
|
|
{
|
|
upstreamRelocalizationMaxAcc_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setDataRecorderMode(bool enabled)
|
|
{
|
|
if(dataRecorderMode_ != enabled)
|
|
{
|
|
dataRecorderMode_ = enabled; // parameters will be set when resuming (we assume we are paused)
|
|
if(localizationMode_ && enabled)
|
|
{
|
|
localizationMode_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setMaxCloudDepth(float value)
|
|
{
|
|
maxCloudDepth_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setMinCloudDepth(float value)
|
|
{
|
|
minCloudDepth_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setCloudDensityLevel(int value)
|
|
{
|
|
cloudDensityLevel_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setMeshAngleTolerance(float value)
|
|
{
|
|
meshAngleToleranceDeg_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setMeshDecimationFactor(float value)
|
|
{
|
|
meshDecimationFactor_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setMeshTriangleSize(int value)
|
|
{
|
|
meshTrianglePix_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setClusterRatio(float value)
|
|
{
|
|
clusterRatio_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setMaxGainRadius(float value)
|
|
{
|
|
maxGainRadius_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setRenderingTextureDecimation(int value)
|
|
{
|
|
UASSERT(value>=1);
|
|
renderingTextureDecimation_ = value;
|
|
}
|
|
|
|
void RTABMapApp::setBackgroundColor(float gray)
|
|
{
|
|
backgroundColor_ = gray;
|
|
float v = backgroundColor_ == 0.5f?0.4f:1.0f-backgroundColor_;
|
|
main_scene_.setGridColor(v, v, v);
|
|
main_scene_.setBackgroundColor(backgroundColor_, backgroundColor_, backgroundColor_);
|
|
}
|
|
|
|
void RTABMapApp::setDepthConfidence(int value)
|
|
{
|
|
depthConfidence_ = value;
|
|
if(depthConfidence_>2)
|
|
{
|
|
depthConfidence_ = 2;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setExportPointCloudFormat(const std::string & format)
|
|
{
|
|
#if defined(RTABMAP_PDAL) || defined(RTABMAP_LIBLAS)
|
|
if(format == "las" || format == "laz") {
|
|
exportPointCloudFormat_ = format;
|
|
}
|
|
else
|
|
#endif
|
|
if(format != "ply") {
|
|
UERROR("Not supported point cloud format %s", format.c_str());
|
|
}
|
|
else {
|
|
exportPointCloudFormat_ = format;
|
|
}
|
|
}
|
|
|
|
int RTABMapApp::setMappingParameter(const std::string & key, const std::string & value)
|
|
{
|
|
std::string compatibleKey = key;
|
|
|
|
// Backward compatibility
|
|
std::map<std::string, std::pair<bool, std::string> >::const_iterator iter=rtabmap::Parameters::getRemovedParameters().find(key);
|
|
if(iter != rtabmap::Parameters::getRemovedParameters().end())
|
|
{
|
|
if(iter->second.first)
|
|
{
|
|
// can be migrated
|
|
compatibleKey = iter->second.second;
|
|
LOGW("Parameter name changed: \"%s\" -> \"%s\". Please update the code accordingly. Value \"%s\" is still set to the new parameter name.",
|
|
iter->first.c_str(), iter->second.second.c_str(), value.c_str());
|
|
}
|
|
else
|
|
{
|
|
if(iter->second.second.empty())
|
|
{
|
|
UERROR("Parameter \"%s\" doesn't exist anymore!",
|
|
iter->first.c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Parameter \"%s\" doesn't exist anymore! You may look at this similar parameter: \"%s\"",
|
|
iter->first.c_str(), iter->second.second.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(rtabmap::Parameters::getDefaultParameters().find(compatibleKey) != rtabmap::Parameters::getDefaultParameters().end())
|
|
{
|
|
LOGI("%s", uFormat("Setting param \"%s\" to \"%s\"", compatibleKey.c_str(), value.c_str()).c_str());
|
|
uInsert(mappingParameters_, rtabmap::ParametersPair(compatibleKey, value));
|
|
UEventsManager::post(new rtabmap::ParamEvent(this->getRtabmapParameters()));
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
UERROR(uFormat("Key \"%s\" doesn't exist!", compatibleKey.c_str()).c_str());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setGPS(const rtabmap::GPS & gps)
|
|
{
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_!=0)
|
|
{
|
|
camera_->setGPS(gps);
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::addEnvSensor(int type, float value)
|
|
{
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(camera_!=0)
|
|
{
|
|
camera_->addEnvSensor(type, value);
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::save(const std::string & databasePath)
|
|
{
|
|
LOGI("Saving database to %s", databasePath.c_str());
|
|
rtabmapThread_->unregisterFromEventsManager();
|
|
rtabmapThread_->join(true);
|
|
|
|
LOGI("Taking screenshot...");
|
|
takeScreenshotOnNextRender_ = true;
|
|
if(!screenshotReady_.acquire(1, 2000))
|
|
{
|
|
UERROR("Failed to take a screenshot after 2 sec!");
|
|
}
|
|
|
|
// save mapping parameters in the database
|
|
bool appendModeBackup = appendMode_;
|
|
if(appendMode_)
|
|
{
|
|
appendMode_ = false;
|
|
}
|
|
|
|
bool dataRecorderModeBackup = dataRecorderMode_;
|
|
if(dataRecorderMode_)
|
|
{
|
|
dataRecorderMode_ = false;
|
|
}
|
|
|
|
bool localizationModeBackup = localizationMode_;
|
|
if(localizationMode_)
|
|
{
|
|
localizationMode_ = false;
|
|
}
|
|
|
|
if(appendModeBackup || dataRecorderModeBackup || localizationModeBackup)
|
|
{
|
|
rtabmap::ParametersMap parameters = getRtabmapParameters();
|
|
rtabmap_->parseParameters(parameters);
|
|
appendMode_ = appendModeBackup;
|
|
dataRecorderMode_ = dataRecorderModeBackup;
|
|
localizationMode_ = localizationModeBackup;
|
|
}
|
|
|
|
std::map<int, rtabmap::Transform> poses = rtabmap_->getLocalOptimizedPoses();
|
|
std::multimap<int, rtabmap::Link> links = rtabmap_->getLocalConstraints();
|
|
rtabmap_->close(true, databasePath);
|
|
rtabmap_->init(getRtabmapParameters(), dataRecorderMode_?"":databasePath);
|
|
if(dataRecorderMode_)
|
|
{
|
|
clearSceneOnNextRender_ = true;
|
|
}
|
|
else
|
|
{
|
|
rtabmap_->setOptimizedPoses(poses, links);
|
|
}
|
|
}
|
|
|
|
bool RTABMapApp::recover(const std::string & from, const std::string & to)
|
|
{
|
|
std::string errorMsg;
|
|
if(!databaseRecovery(from, false, &errorMsg, &progressionStatus_))
|
|
{
|
|
LOGE("Recovery Error: %s", errorMsg.c_str());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
LOGI("Renaming %s to %s", from.c_str(), to.c_str());
|
|
if(UFile::rename(from, to) != 0)
|
|
{
|
|
LOGE("Failed renaming %s to %s", from.c_str(), to.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::cancelProcessing()
|
|
{
|
|
UWARN("Processing canceled!");
|
|
progressionStatus_.setCanceled(true);
|
|
}
|
|
|
|
bool RTABMapApp::exportMesh(
|
|
float cloudVoxelSize,
|
|
bool regenerateCloud,
|
|
bool meshing,
|
|
int textureSize,
|
|
int textureCount,
|
|
int normalK,
|
|
bool optimized,
|
|
float optimizedVoxelSize,
|
|
int optimizedDepth,
|
|
int optimizedMaxPolygons,
|
|
float optimizedColorRadius,
|
|
bool optimizedCleanWhitePolygons,
|
|
int optimizedMinClusterSize,
|
|
float optimizedMaxTextureDistance,
|
|
int optimizedMinTextureClusterSize,
|
|
int textureVertexColorPolicy,
|
|
bool blockRendering)
|
|
{
|
|
// make sure createdMeshes_ is not modified while exporting! We don't
|
|
// lock the meshesMutex_ because we want to continue rendering.
|
|
|
|
std::map<int, rtabmap::Transform> poses = rtabmap_->getLocalOptimizedPoses();
|
|
if(poses.empty())
|
|
{
|
|
// look if we just triggered new map without localizing afterward (pause/resume in append Mode)
|
|
std::multimap<int, rtabmap::Link> links;
|
|
rtabmap_->getGraph(
|
|
poses,
|
|
links,
|
|
true,
|
|
false);
|
|
if(poses.empty())
|
|
{
|
|
UERROR("Empty optimized poses!");
|
|
return false;
|
|
}
|
|
rtabmap_->setOptimizedPoses(poses, links);
|
|
}
|
|
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.lock();
|
|
main_scene_.clear();
|
|
}
|
|
|
|
exporting_ = true;
|
|
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
int totalSteps = 0;
|
|
totalSteps+=poses.size(); // assemble
|
|
if(meshing)
|
|
{
|
|
if(optimized)
|
|
{
|
|
totalSteps += poses.size(); // meshing
|
|
if(textureSize > 0)
|
|
{
|
|
totalSteps += 1; // gain
|
|
totalSteps += 1; // blending
|
|
|
|
if(optimizedMaxPolygons > 0)
|
|
{
|
|
totalSteps += 1; // decimation
|
|
}
|
|
}
|
|
|
|
totalSteps += 1; // texture/coloring
|
|
|
|
if(textureSize > 0)
|
|
{
|
|
totalSteps+=poses.size()+1; // texture cameras + apply polygons
|
|
}
|
|
}
|
|
if(textureSize>0)
|
|
{
|
|
totalSteps += poses.size()+1; // uncompress and merge textures
|
|
}
|
|
}
|
|
totalSteps += 1; // save file
|
|
|
|
progressionStatus_.reset(totalSteps);
|
|
|
|
//Assemble the meshes
|
|
if(meshing) // Mesh or Texture Mesh
|
|
{
|
|
pcl::PolygonMesh::Ptr polygonMesh(new pcl::PolygonMesh);
|
|
pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);
|
|
std::vector<std::map<int, pcl::PointXY> > vertexToPixels;
|
|
cv::Mat globalTextures;
|
|
int totalPolygons = 0;
|
|
{
|
|
if(optimized)
|
|
{
|
|
std::map<int, rtabmap::Transform> cameraPoses;
|
|
std::map<int, rtabmap::CameraModel> cameraModels;
|
|
std::map<int, cv::Mat> cameraDepths;
|
|
|
|
UTimer timer;
|
|
LOGI("Assemble clouds (%d)...", (int)poses.size());
|
|
#ifndef DISABLE_LOG
|
|
int cloudCount=0;
|
|
#endif
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mergedClouds(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=poses.begin();
|
|
iter!= poses.end();
|
|
++iter)
|
|
{
|
|
std::map<int, rtabmap::Mesh>::iterator jter = createdMeshes_.find(iter->first);
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
rtabmap::CameraModel model;
|
|
cv::Mat depth;
|
|
float gains[3];
|
|
gains[0] = gains[1] = gains[2] = 1.0f;
|
|
if(jter != createdMeshes_.end() && (jter->second.polygons.empty() || meshDecimationFactor_ == 0.0f))
|
|
{
|
|
cloud = jter->second.cloud;
|
|
indices = jter->second.indices;
|
|
model = jter->second.cameraModel;
|
|
gains[0] = jter->second.gains[0];
|
|
gains[1] = jter->second.gains[1];
|
|
gains[2] = jter->second.gains[2];
|
|
|
|
rtabmap::SensorData data = rtabmap_->getMemory()->getNodeData(iter->first, true, false, false, false);
|
|
data.uncompressData(0, &depth);
|
|
}
|
|
else
|
|
{
|
|
rtabmap::SensorData data = rtabmap_->getMemory()->getNodeData(iter->first, true, false, false, false);
|
|
data.uncompressData();
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty() && data.cameraModels().size() == 1)
|
|
{
|
|
int meshDecimation = updateMeshDecimation(data.depthRaw().cols, data.depthRaw().rows);
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, meshDecimation, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
model = data.cameraModels()[0];
|
|
depth = data.depthRaw();
|
|
}
|
|
}
|
|
if(cloud->size() && indices->size() && model.isValidForProjection())
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr transformedCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
if(optimizedVoxelSize > 0.0f)
|
|
{
|
|
transformedCloud = rtabmap::util3d::voxelize(cloud, indices, optimizedVoxelSize);
|
|
transformedCloud = rtabmap::util3d::transformPointCloud(transformedCloud, iter->second);
|
|
}
|
|
else
|
|
{
|
|
// it looks like that using only transformPointCloud with indices
|
|
// flushes the colors, so we should extract points before... maybe a too old PCL version
|
|
pcl::copyPointCloud(*cloud, *indices, *transformedCloud);
|
|
transformedCloud = rtabmap::util3d::transformPointCloud(transformedCloud, iter->second);
|
|
}
|
|
|
|
Eigen::Vector3f viewpoint( iter->second.x(), iter->second.y(), iter->second.z());
|
|
pcl::PointCloud<pcl::Normal>::Ptr normals = rtabmap::util3d::computeNormals(transformedCloud, normalK, 0.0f, viewpoint);
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
pcl::concatenateFields(*transformedCloud, *normals, *cloudWithNormals);
|
|
|
|
if(textureSize == 0 && (gains[0] != 1.0 || gains[1] != 1.0 || gains[2] != 1.0))
|
|
{
|
|
for(unsigned int i=0; i<cloudWithNormals->size(); ++i)
|
|
{
|
|
pcl::PointXYZRGBNormal & pt = cloudWithNormals->at(i);
|
|
pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gains[0])));
|
|
pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gains[1])));
|
|
pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gains[2])));
|
|
}
|
|
}
|
|
|
|
|
|
if(mergedClouds->size() == 0)
|
|
{
|
|
*mergedClouds = *cloudWithNormals;
|
|
}
|
|
else
|
|
{
|
|
*mergedClouds += *cloudWithNormals;
|
|
}
|
|
|
|
cameraPoses.insert(std::make_pair(iter->first, iter->second));
|
|
cameraModels.insert(std::make_pair(iter->first, model));
|
|
if(!depth.empty())
|
|
{
|
|
cameraDepths.insert(std::make_pair(iter->first, depth));
|
|
}
|
|
|
|
LOGI("Assembled %d points (%d/%d total=%d)", (int)cloudWithNormals->size(), ++cloudCount, (int)poses.size(), (int)mergedClouds->size());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Cloud %d not found or empty", iter->first);
|
|
}
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
progressionStatus_.increment();
|
|
}
|
|
LOGI("Assembled clouds (%d)... done! %fs (total points=%d)", (int)cameraPoses.size(), timer.ticks(), (int)mergedClouds->size());
|
|
|
|
if(mergedClouds->size()>=3)
|
|
{
|
|
if(optimizedDepth == 0)
|
|
{
|
|
Eigen::Vector4f min,max;
|
|
pcl::getMinMax3D(*mergedClouds, min, max);
|
|
float mapLength = uMax3(max[0]-min[0], max[1]-min[1], max[2]-min[2]);
|
|
optimizedDepth = 12;
|
|
for(int i=6; i<12; ++i)
|
|
{
|
|
if(mapLength/float(1<<i) < 0.03f)
|
|
{
|
|
optimizedDepth = i;
|
|
break;
|
|
}
|
|
}
|
|
LOGI("optimizedDepth=%d (map length=%f)", optimizedDepth, mapLength);
|
|
}
|
|
|
|
// Mesh reconstruction
|
|
LOGI("Mesh reconstruction...");
|
|
pcl::PolygonMesh::Ptr mesh(new pcl::PolygonMesh);
|
|
pcl::Poisson<pcl::PointXYZRGBNormal> poisson;
|
|
poisson.setDepth(optimizedDepth);
|
|
poisson.setInputCloud(mergedClouds);
|
|
poisson.reconstruct(*mesh);
|
|
LOGI("Mesh reconstruction... done! %fs (%d polygons)", timer.ticks(), (int)mesh->polygons.size());
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
|
|
progressionStatus_.increment(poses.size());
|
|
|
|
if(mesh->polygons.size())
|
|
{
|
|
totalPolygons=(int)mesh->polygons.size();
|
|
|
|
if(optimizedMaxPolygons > 0 && optimizedMaxPolygons < (int)mesh->polygons.size())
|
|
{
|
|
#ifndef DISABLE_VTK
|
|
unsigned int count = mesh->polygons.size();
|
|
float factor = 1.0f-float(optimizedMaxPolygons)/float(count);
|
|
LOGI("Mesh decimation (max polygons %d/%d -> factor=%f)...", optimizedMaxPolygons, (int)count, factor);
|
|
|
|
progressionStatus_.setMax(progressionStatus_.getMax() + optimizedMaxPolygons/10000);
|
|
|
|
pcl::PolygonMesh::Ptr output(new pcl::PolygonMesh);
|
|
pcl::MeshQuadricDecimationVTK mqd;
|
|
mqd.setTargetReductionFactor(factor);
|
|
mqd.setInputMesh(mesh);
|
|
mqd.process (*output);
|
|
mesh = output;
|
|
|
|
//mesh = rtabmap::util3d::meshDecimation(mesh, decimationFactor);
|
|
// use direct instantiation above to this fix some linker errors on android like:
|
|
// pcl::MeshQuadricDecimationVTK::performProcessing(pcl::PolygonMesh&): error: undefined reference to 'vtkQuadricDecimation::New()'
|
|
// pcl::VTKUtils::mesh2vtk(pcl::PolygonMesh const&, vtkSmartPointer<vtkPolyData>&): error: undefined reference to 'vtkFloatArray::New()'
|
|
|
|
LOGI("Mesh decimated (factor=%f) from %d to %d polygons (%fs)", factor, count, (int)mesh->polygons.size(), timer.ticks());
|
|
if(count < mesh->polygons.size())
|
|
{
|
|
UWARN("Decimated mesh has more polygons than before!");
|
|
}
|
|
#else
|
|
UWARN("RTAB-Map is not built with PCL-VTK module so mesh decimation cannot be used!");
|
|
#endif
|
|
}
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
|
|
progressionStatus_.increment();
|
|
|
|
rtabmap::util3d::denseMeshPostProcessing<pcl::PointXYZRGBNormal>(
|
|
mesh,
|
|
0.0f,
|
|
0,
|
|
mergedClouds,
|
|
optimizedColorRadius,
|
|
!(textureSize > 0 && textureVertexColorPolicy == 0),
|
|
optimizedCleanWhitePolygons,
|
|
optimizedMinClusterSize);
|
|
|
|
if(textureSize>0)
|
|
{
|
|
LOGI("Texturing... cameraPoses=%d, cameraDepths=%d", (int)cameraPoses.size(), (int)cameraDepths.size());
|
|
textureMesh = rtabmap::util3d::createTextureMesh(
|
|
mesh,
|
|
cameraPoses,
|
|
cameraModels,
|
|
cameraDepths,
|
|
optimizedMaxTextureDistance,
|
|
0.0f,
|
|
0.0f,
|
|
optimizedMinTextureClusterSize,
|
|
std::vector<float>(),
|
|
&progressionStatus_,
|
|
&vertexToPixels);
|
|
LOGI("Texturing... done! %fs", timer.ticks());
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
|
|
// Remove occluded polygons (polygons with no texture)
|
|
if(textureMesh->tex_coordinates.size() && optimizedCleanWhitePolygons)
|
|
{
|
|
LOGI("Cleanup mesh...");
|
|
rtabmap::util3d::cleanTextureMesh(*textureMesh, 0);
|
|
LOGI("Cleanup mesh... done! %fs", timer.ticks());
|
|
}
|
|
|
|
totalPolygons = 0;
|
|
for(unsigned int t=0; t<textureMesh->tex_polygons.size(); ++t)
|
|
{
|
|
totalPolygons+=textureMesh->tex_polygons[t].size();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
totalPolygons = (int)mesh->polygons.size();
|
|
polygonMesh = mesh;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Merged cloud too small (%d points) to create polygons!", (int)mergedClouds->size());
|
|
}
|
|
}
|
|
else // organized meshes
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mergedClouds(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
|
|
if(textureSize > 0)
|
|
{
|
|
textureMesh->tex_materials.resize(poses.size());
|
|
textureMesh->tex_polygons.resize(poses.size());
|
|
textureMesh->tex_coordinates.resize(poses.size());
|
|
}
|
|
|
|
int polygonsStep = 0;
|
|
int oi = 0;
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=poses.begin();
|
|
iter!= poses.end();
|
|
++iter)
|
|
{
|
|
LOGI("Assembling cloud %d (total=%d)...", iter->first, (int)poses.size());
|
|
|
|
std::map<int, rtabmap::Mesh>::iterator jter = createdMeshes_.find(iter->first);
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
std::vector<pcl::Vertices> polygons;
|
|
float gains[3] = {1.0f};
|
|
if(jter != createdMeshes_.end())
|
|
{
|
|
cloud = jter->second.cloud;
|
|
polygons= jter->second.polygons;
|
|
if(cloud->size() && polygons.size() == 0)
|
|
{
|
|
polygons = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
}
|
|
gains[0] = jter->second.gains[0];
|
|
gains[1] = jter->second.gains[1];
|
|
gains[2] = jter->second.gains[2];
|
|
}
|
|
else
|
|
{
|
|
rtabmap::SensorData data = rtabmap_->getMemory()->getNodeData(iter->first, true, false, false, false);
|
|
data.uncompressData();
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty() && data.cameraModels().size() == 1)
|
|
{
|
|
int meshDecimation = updateMeshDecimation(data.depthRaw().cols, data.depthRaw().rows);
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, meshDecimation, maxCloudDepth_, minCloudDepth_);
|
|
polygons = rtabmap::util3d::organizedFastMesh(cloud, meshAngleToleranceDeg_*M_PI/180.0, false, meshTrianglePix_);
|
|
}
|
|
}
|
|
|
|
if(cloud->size() && polygons.size())
|
|
{
|
|
// Convert organized to dense cloud
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr outputCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
std::vector<pcl::Vertices> outputPolygons;
|
|
std::vector<int> denseToOrganizedIndices = rtabmap::util3d::filterNaNPointsFromMesh(*cloud, polygons, *outputCloud, outputPolygons);
|
|
|
|
pcl::PointCloud<pcl::Normal>::Ptr normals = rtabmap::util3d::computeNormals(outputCloud, normalK);
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
pcl::concatenateFields(*outputCloud, *normals, *cloudWithNormals);
|
|
|
|
UASSERT(outputPolygons.size());
|
|
|
|
totalPolygons+=outputPolygons.size();
|
|
|
|
if(textureSize == 0)
|
|
{
|
|
// colored mesh
|
|
cloudWithNormals = rtabmap::util3d::transformPointCloud(cloudWithNormals, iter->second);
|
|
|
|
if(gains[0] != 1.0f || gains[1] != 1.0f || gains[2] != 1.0f)
|
|
{
|
|
for(unsigned int i=0; i<cloudWithNormals->size(); ++i)
|
|
{
|
|
pcl::PointXYZRGBNormal & pt = cloudWithNormals->at(i);
|
|
pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gains[0])));
|
|
pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gains[1])));
|
|
pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gains[2])));
|
|
}
|
|
}
|
|
|
|
if(mergedClouds->size() == 0)
|
|
{
|
|
*mergedClouds = *cloudWithNormals;
|
|
polygonMesh->polygons = outputPolygons;
|
|
}
|
|
else
|
|
{
|
|
rtabmap::util3d::appendMesh(*mergedClouds, polygonMesh->polygons, *cloudWithNormals, outputPolygons);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// texture mesh
|
|
size_t polygonSize = outputPolygons.front().vertices.size();
|
|
textureMesh->tex_polygons[oi].resize(outputPolygons.size());
|
|
textureMesh->tex_coordinates[oi].resize(outputPolygons.size() * polygonSize);
|
|
for(unsigned int j=0; j<outputPolygons.size(); ++j)
|
|
{
|
|
pcl::Vertices vertices = outputPolygons[j];
|
|
UASSERT(polygonSize == vertices.vertices.size());
|
|
for(unsigned int k=0; k<vertices.vertices.size(); ++k)
|
|
{
|
|
//uv
|
|
UASSERT(vertices.vertices[k] < denseToOrganizedIndices.size());
|
|
int originalVertex = denseToOrganizedIndices[vertices.vertices[k]];
|
|
textureMesh->tex_coordinates[oi][j*vertices.vertices.size()+k] = Eigen::Vector2f(
|
|
float(originalVertex % cloud->width) / float(cloud->width), // u
|
|
float(cloud->height - originalVertex / cloud->width) / float(cloud->height)); // v
|
|
|
|
vertices.vertices[k] += polygonsStep;
|
|
}
|
|
textureMesh->tex_polygons[oi][j] = vertices;
|
|
|
|
}
|
|
polygonsStep += outputCloud->size();
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr transformedCloud = rtabmap::util3d::transformPointCloud(cloudWithNormals, iter->second);
|
|
if(mergedClouds->size() == 0)
|
|
{
|
|
*mergedClouds = *transformedCloud;
|
|
}
|
|
else
|
|
{
|
|
*mergedClouds += *transformedCloud;
|
|
}
|
|
|
|
textureMesh->tex_materials[oi].tex_illum = 1;
|
|
textureMesh->tex_materials[oi].tex_name = uFormat("material_%d", iter->first);
|
|
textureMesh->tex_materials[oi].tex_file = uNumber2Str(iter->first);
|
|
++oi;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Mesh not found for mesh %d", iter->first);
|
|
}
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
progressionStatus_.increment();
|
|
}
|
|
if(textureSize == 0)
|
|
{
|
|
if(mergedClouds->size())
|
|
{
|
|
pcl::toPCLPointCloud2(*mergedClouds, polygonMesh->cloud);
|
|
}
|
|
else
|
|
{
|
|
polygonMesh->polygons.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
textureMesh->tex_materials.resize(oi);
|
|
textureMesh->tex_polygons.resize(oi);
|
|
|
|
if(mergedClouds->size())
|
|
{
|
|
pcl::toPCLPointCloud2(*mergedClouds, textureMesh->cloud);
|
|
}
|
|
}
|
|
}
|
|
|
|
// end optimized or organized
|
|
|
|
if(textureSize>0 && totalPolygons && textureMesh->tex_materials.size())
|
|
{
|
|
LOGI("Merging %d textures...", (int)textureMesh->tex_materials.size());
|
|
globalTextures = rtabmap::util3d::mergeTextures(
|
|
*textureMesh,
|
|
std::map<int, cv::Mat>(),
|
|
std::map<int, std::vector<rtabmap::CameraModel> >(),
|
|
rtabmap_->getMemory(),
|
|
0,
|
|
textureSize,
|
|
textureCount,
|
|
vertexToPixels,
|
|
true, 10.0f, true ,true, 0, 0, 0, false,
|
|
&progressionStatus_,
|
|
255,
|
|
textureVertexColorPolicy == 1);
|
|
LOGI("Merging %d textures... globalTextures=%dx%d", (int)textureMesh->tex_materials.size(),
|
|
globalTextures.cols, globalTextures.rows);
|
|
}
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
|
|
progressionStatus_.increment();
|
|
}
|
|
if(totalPolygons)
|
|
{
|
|
if(textureSize == 0)
|
|
{
|
|
UASSERT((int)polygonMesh->polygons.size() == totalPolygons);
|
|
if(polygonMesh->polygons.size())
|
|
{
|
|
// save in database
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
pcl::fromPCLPointCloud2(polygonMesh->cloud, *cloud);
|
|
cv::Mat cloudMat = rtabmap::compressData2(rtabmap::util3d::laserScanFromPointCloud(*cloud, rtabmap::Transform(), false).data()); // for database
|
|
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons(1);
|
|
polygons[0].resize(polygonMesh->polygons.size());
|
|
for(unsigned int p=0; p<polygonMesh->polygons.size(); ++p)
|
|
{
|
|
polygons[0][p] = polygonMesh->polygons[p].vertices;
|
|
}
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
|
|
rtabmap_->getMemory()->saveOptimizedMesh(cloudMat, polygons);
|
|
success = true;
|
|
}
|
|
}
|
|
else if(textureMesh->tex_materials.size())
|
|
{
|
|
bool hasColors = false;
|
|
for(unsigned int i=0; i<textureMesh->cloud.fields.size(); ++i)
|
|
{
|
|
if(textureMesh->cloud.fields[i].name.compare("rgb") == 0)
|
|
{
|
|
hasColors = true;
|
|
break;
|
|
}
|
|
}
|
|
cv::Mat cloudMat;
|
|
if(hasColors)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
|
|
pcl::fromPCLPointCloud2(textureMesh->cloud, *cloud);
|
|
cloudMat = rtabmap::compressData2(rtabmap::util3d::laserScanFromPointCloud(*cloud, rtabmap::Transform(), false).data()); // for database
|
|
}
|
|
else
|
|
{
|
|
pcl::PointCloud<pcl::PointNormal>::Ptr cloud(new pcl::PointCloud<pcl::PointNormal>);
|
|
pcl::fromPCLPointCloud2(textureMesh->cloud, *cloud);
|
|
cloudMat = rtabmap::compressData2(rtabmap::util3d::laserScanFromPointCloud(*cloud, rtabmap::Transform(), false).data()); // for database
|
|
}
|
|
|
|
// save in database
|
|
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons(textureMesh->tex_polygons.size());
|
|
for(unsigned int t=0; t<textureMesh->tex_polygons.size(); ++t)
|
|
{
|
|
polygons[t].resize(textureMesh->tex_polygons[t].size());
|
|
for(unsigned int p=0; p<textureMesh->tex_polygons[t].size(); ++p)
|
|
{
|
|
polygons[t][p] = textureMesh->tex_polygons[t][p].vertices;
|
|
}
|
|
}
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
rtabmap_->getMemory()->saveOptimizedMesh(cloudMat, polygons, textureMesh->tex_coordinates, globalTextures);
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed exporting texture mesh! There are no textures!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed exporting mesh! There are no polygons!");
|
|
}
|
|
}
|
|
else // Point cloud
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr mergedClouds(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
for(std::map<int, rtabmap::Transform>::iterator iter=poses.begin();
|
|
iter!= poses.end();
|
|
++iter)
|
|
{
|
|
std::map<int, rtabmap::Mesh>::iterator jter=createdMeshes_.find(iter->first);
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
pcl::IndicesPtr indices(new std::vector<int>);
|
|
float gains[3];
|
|
gains[0] = gains[1] = gains[2] = 1.0f;
|
|
if(regenerateCloud)
|
|
{
|
|
if(jter != createdMeshes_.end())
|
|
{
|
|
gains[0] = jter->second.gains[0];
|
|
gains[1] = jter->second.gains[1];
|
|
gains[2] = jter->second.gains[2];
|
|
}
|
|
rtabmap::SensorData data = rtabmap_->getMemory()->getNodeData(iter->first, true, true, false, false);
|
|
data.uncompressData();
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty())
|
|
{
|
|
// full resolution
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, 1, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
}
|
|
else if(!data.laserScanRaw().empty())
|
|
{
|
|
//scan
|
|
cloud = rtabmap::util3d::laserScanToPointCloudRGB(rtabmap::util3d::commonFiltering(data.laserScanRaw(), 1, minCloudDepth_, maxCloudDepth_), data.laserScanRaw().localTransform(), 255, 255, 255);
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(jter != createdMeshes_.end())
|
|
{
|
|
cloud = jter->second.cloud;
|
|
indices = jter->second.indices;
|
|
gains[0] = jter->second.gains[0];
|
|
gains[1] = jter->second.gains[1];
|
|
gains[2] = jter->second.gains[2];
|
|
}
|
|
else
|
|
{
|
|
rtabmap::SensorData data = rtabmap_->getMemory()->getNodeData(iter->first, true, true, false, false);
|
|
data.uncompressData();
|
|
if(!data.imageRaw().empty() && !data.depthRaw().empty())
|
|
{
|
|
int meshDecimation = updateMeshDecimation(data.depthRaw().cols, data.depthRaw().rows);
|
|
cloud = rtabmap::util3d::cloudRGBFromSensorData(data, meshDecimation, maxCloudDepth_, minCloudDepth_, indices.get());
|
|
}
|
|
else if(!data.laserScanRaw().empty())
|
|
{
|
|
//scan
|
|
cloud = rtabmap::util3d::laserScanToPointCloudRGB(rtabmap::util3d::commonFiltering(data.laserScanRaw(), 1, minCloudDepth_, maxCloudDepth_), data.laserScanRaw().localTransform(), 255, 255, 255);
|
|
indices->resize(cloud->size());
|
|
for(unsigned int i=0; i<cloud->size(); ++i)
|
|
{
|
|
indices->at(i) = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(cloud->size() && indices->size())
|
|
{
|
|
// Convert organized to dense cloud
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr transformedCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
|
|
if(cloudVoxelSize > 0.0f)
|
|
{
|
|
transformedCloud = rtabmap::util3d::voxelize(cloud, indices, cloudVoxelSize);
|
|
transformedCloud = rtabmap::util3d::transformPointCloud(transformedCloud, iter->second);
|
|
}
|
|
else
|
|
{
|
|
// it looks like that using only transformPointCloud with indices
|
|
// flushes the colors, so we should extract points before... maybe a too old PCL version
|
|
pcl::copyPointCloud(*cloud, *indices, *transformedCloud);
|
|
transformedCloud = rtabmap::util3d::transformPointCloud(transformedCloud, iter->second);
|
|
}
|
|
|
|
if(gains[0] != 1.0f || gains[1] != 1.0f || gains[2] != 1.0f)
|
|
{
|
|
//LOGD("cloud %d, gain=%f", iter->first, gain);
|
|
for(unsigned int i=0; i<transformedCloud->size(); ++i)
|
|
{
|
|
pcl::PointXYZRGB & pt = transformedCloud->at(i);
|
|
//LOGI("color %d = %d %d %d", i, (int)pt.r, (int)pt.g, (int)pt.b);
|
|
pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gains[0])));
|
|
pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gains[1])));
|
|
pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gains[2])));
|
|
|
|
}
|
|
}
|
|
|
|
if(mergedClouds->size() == 0)
|
|
{
|
|
*mergedClouds = *transformedCloud;
|
|
}
|
|
else
|
|
{
|
|
*mergedClouds += *transformedCloud;
|
|
}
|
|
}
|
|
|
|
if(progressionStatus_.isCanceled())
|
|
{
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
exporting_ = false;
|
|
return false;
|
|
}
|
|
progressionStatus_.increment();
|
|
}
|
|
|
|
if(mergedClouds->size())
|
|
{
|
|
if(cloudVoxelSize > 0.0f)
|
|
{
|
|
mergedClouds = rtabmap::util3d::voxelize(mergedClouds, cloudVoxelSize);
|
|
}
|
|
|
|
// save in database
|
|
{
|
|
cv::Mat cloudMat = rtabmap::compressData2(rtabmap::util3d::laserScanFromPointCloud(*mergedClouds).data()); // for database
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
rtabmap_->getMemory()->saveOptimizedMesh(cloudMat);
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Merged cloud is empty!");
|
|
}
|
|
}
|
|
|
|
progressionStatus_.finish();
|
|
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
}
|
|
catch (std::exception & e)
|
|
{
|
|
UERROR("Out of memory! %s", e.what());
|
|
|
|
if(blockRendering)
|
|
{
|
|
renderingMutex_.unlock();
|
|
}
|
|
|
|
success = false;
|
|
}
|
|
exporting_ = false;
|
|
|
|
optRefId_ = 0;
|
|
if(optRefPose_)
|
|
{
|
|
delete optRefPose_;
|
|
optRefPose_ = 0;
|
|
}
|
|
if(success && poses.size())
|
|
{
|
|
// for optimized mesh
|
|
// just take the last as reference
|
|
optRefId_ = poses.rbegin()->first;
|
|
optRefPose_ = new rtabmap::Transform(poses.rbegin()->second);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool RTABMapApp::postExportation(bool visualize)
|
|
{
|
|
LOGI("postExportation(visualize=%d)", visualize?1:0);
|
|
optTextureMesh_.reset(new pcl::TextureMesh);
|
|
optMesh_= rtabmap::Mesh();
|
|
optTexture_ = cv::Mat();
|
|
exportedMeshUpdated_ = false;
|
|
|
|
if(visualize)
|
|
{
|
|
visualizingMesh_ = false;
|
|
cv::Mat cloudMat;
|
|
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons;
|
|
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
|
|
std::vector<std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > > texCoords;
|
|
#else
|
|
std::vector<std::vector<Eigen::Vector2f> > texCoords;
|
|
#endif
|
|
cv::Mat textures;
|
|
if(rtabmap_ && rtabmap_->getMemory())
|
|
{
|
|
cloudMat = rtabmap_->getMemory()->loadOptimizedMesh(&polygons, &texCoords, &textures);
|
|
if(!cloudMat.empty())
|
|
{
|
|
LOGI("postExportation: Found optimized mesh! Visualizing it.");
|
|
optTextureMesh_ = rtabmap::util3d::assembleTextureMesh(cloudMat, polygons, texCoords, textures, true);
|
|
optMesh_ = rtabmap::Mesh();
|
|
optTexture_ = textures;
|
|
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
visualizingMesh_ = true;
|
|
exportedMeshUpdated_ = true;
|
|
}
|
|
else
|
|
{
|
|
LOGI("postExportation: No optimized mesh found.");
|
|
}
|
|
}
|
|
}
|
|
else if(visualizingMesh_)
|
|
{
|
|
rtabmapMutex_.lock();
|
|
std::map<int, rtabmap::Transform> poses = rtabmap_->getLocalOptimizedPoses();
|
|
std::multimap<int, rtabmap::Link> links = rtabmap_->getLocalConstraints();
|
|
if(poses.empty())
|
|
{
|
|
rtabmap_->getGraph(
|
|
poses,
|
|
links,
|
|
true,
|
|
true,
|
|
0,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false);
|
|
}
|
|
if(!poses.empty())
|
|
{
|
|
rtabmap::Statistics stats;
|
|
for(std::map<std::string, float>::iterator iter=bufferedStatsData_.begin(); iter!=bufferedStatsData_.end(); ++iter)
|
|
{
|
|
stats.addStatistic(iter->first, iter->second);
|
|
}
|
|
stats.setPoses(poses);
|
|
stats.setConstraints(links);
|
|
rtabmapEvents_.push_back(new rtabmap::RtabmapEvent(stats));
|
|
}
|
|
rtabmapMutex_.unlock();
|
|
|
|
visualizingMesh_ = false;
|
|
}
|
|
|
|
return visualizingMesh_;
|
|
}
|
|
|
|
bool RTABMapApp::writeExportedMesh(const std::string & directory, const std::string & name)
|
|
{
|
|
LOGI("writeExportedMesh: dir=%s name=%s", directory.c_str(), name.c_str());
|
|
exporting_ = true;
|
|
|
|
bool success = false;
|
|
|
|
pcl::PolygonMesh::Ptr polygonMesh(new pcl::PolygonMesh);
|
|
pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);
|
|
cv::Mat cloudMat;
|
|
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons;
|
|
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
|
|
std::vector<std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > > texCoords;
|
|
#else
|
|
std::vector<std::vector<Eigen::Vector2f> > texCoords;
|
|
#endif
|
|
cv::Mat textures;
|
|
if(rtabmap_ && rtabmap_->getMemory())
|
|
{
|
|
cloudMat = rtabmap_->getMemory()->loadOptimizedMesh(&polygons, &texCoords, &textures);
|
|
if(!cloudMat.empty())
|
|
{
|
|
LOGI("writeExportedMesh: Found optimized mesh!");
|
|
if(textures.empty())
|
|
{
|
|
polygonMesh = rtabmap::util3d::assemblePolygonMesh(cloudMat, polygons.size() == 1?polygons[0]:std::vector<std::vector<RTABMAP_PCL_INDEX> >());
|
|
}
|
|
else
|
|
{
|
|
textureMesh = rtabmap::util3d::assembleTextureMesh(cloudMat, polygons, texCoords, textures, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGI("writeExportedMesh: No optimized mesh found.");
|
|
}
|
|
}
|
|
|
|
if(polygonMesh->cloud.data.size())
|
|
{
|
|
#if defined(RTABMAP_PDAL) || defined(RTABMAP_LIBLAS)
|
|
if(polygonMesh->polygons.empty() && (exportPointCloudFormat_ == "las" || exportPointCloudFormat_ == "laz")) {
|
|
// Point cloud LAS
|
|
std::string filePath = directory + UDirectory::separator() + name + (exportPointCloudFormat_ == "las"? ".las" : ".laz");
|
|
LOGI("Saving las (%d vertices) to %s.", (int)polygonMesh->cloud.data.size()/polygonMesh->cloud.point_step, filePath.c_str());
|
|
pcl::PointCloud<pcl::PointXYZRGB> output;
|
|
pcl::fromPCLPointCloud2(polygonMesh->cloud, output);
|
|
#ifdef RTABMAP_PDAL
|
|
success = rtabmap::savePDALFile(filePath, output) == 0;
|
|
#else
|
|
success = rtabmap::saveLASFile(filePath, output) == 0;
|
|
#endif
|
|
if(success)
|
|
{
|
|
LOGI("Saved las to %s!", filePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed saving las to %s!", filePath.c_str());
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Point cloud PLY
|
|
std::string filePath = directory + UDirectory::separator() + name + ".ply";
|
|
LOGI("Saving ply (%d vertices, %d polygons) to %s.", (int)polygonMesh->cloud.data.size()/polygonMesh->cloud.point_step, (int)polygonMesh->polygons.size(), filePath.c_str());
|
|
success = pcl::io::savePLYFileBinary(filePath, *polygonMesh) == 0;
|
|
if(success)
|
|
{
|
|
LOGI("Saved ply to %s!", filePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed saving ply to %s!", filePath.c_str());
|
|
}
|
|
}
|
|
}
|
|
else if(textureMesh->cloud.data.size())
|
|
{
|
|
// TextureMesh OBJ
|
|
LOGD("Saving texture(s) (%d)", textures.empty()?0:textures.cols/textures.rows);
|
|
UASSERT(textures.empty() || textures.cols % textures.rows == 0);
|
|
UASSERT((int)textureMesh->tex_materials.size() == textures.cols/textures.rows);
|
|
for(unsigned int i=0; i<textureMesh->tex_materials.size(); ++i)
|
|
{
|
|
std::string baseNameNum = name;
|
|
if(textureMesh->tex_materials.size()>1)
|
|
{
|
|
baseNameNum+=uNumber2Str(i);
|
|
}
|
|
std::string fullPath = directory+UDirectory::separator()+baseNameNum+".jpg";
|
|
textureMesh->tex_materials[i].tex_file = baseNameNum+".jpg";
|
|
LOGI("Saving texture to %s.", fullPath.c_str());
|
|
success = cv::imwrite(fullPath, textures(cv::Range::all(), cv::Range(i*textures.rows, (i+1)*textures.rows)));
|
|
if(!success)
|
|
{
|
|
LOGI("Failed saving %s!", fullPath.c_str());
|
|
}
|
|
else
|
|
{
|
|
LOGI("Saved %s.", fullPath.c_str());
|
|
}
|
|
}
|
|
|
|
if(success)
|
|
{
|
|
// With Sketchfab, the OBJ models are rotated 90 degrees on x axis, so rotate -90 to have model in right position
|
|
//pcl::PointCloud<pcl::PointNormal>::Ptr cloud(new pcl::PointCloud<pcl::PointNormal>);
|
|
//pcl::fromPCLPointCloud2(textureMesh->cloud, *cloud);
|
|
//cloud = rtabmap::util3d::transformPointCloud(cloud, rtabmap::Transform(1,0,0,0, 0,0,1,0, 0,-1,0,0));
|
|
//pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
|
|
std::string filePath = directory + UDirectory::separator() + name + ".obj";
|
|
int totalPolygons = 0;
|
|
for(unsigned int i=0;i<textureMesh->tex_polygons.size(); ++i)
|
|
{
|
|
totalPolygons += textureMesh->tex_polygons[i].size();
|
|
}
|
|
LOGI("Saving obj (%d vertices, %d polygons) to %s.", (int)textureMesh->cloud.data.size()/textureMesh->cloud.point_step, totalPolygons, filePath.c_str());
|
|
success = rtabmap::util3d::saveOBJFile(filePath, *textureMesh) == 0;
|
|
|
|
if(success)
|
|
{
|
|
LOGI("Saved obj to %s!", filePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed saving obj to %s!", filePath.c_str());
|
|
}
|
|
}
|
|
}
|
|
exporting_ = false;
|
|
return success;
|
|
}
|
|
|
|
int RTABMapApp::postProcessing(int approach)
|
|
{
|
|
postProcessing_ = true;
|
|
LOGI("postProcessing begin(%d)", approach);
|
|
int returnedValue = 0;
|
|
if(rtabmap_)
|
|
{
|
|
std::map<int, rtabmap::Transform> poses;
|
|
std::multimap<int, rtabmap::Link> links;
|
|
|
|
// detect more loop closures
|
|
if(approach == -1 || approach == 2)
|
|
{
|
|
if(approach == -1)
|
|
{
|
|
progressionStatus_.reset(6);
|
|
}
|
|
returnedValue = rtabmap_->detectMoreLoopClosures(1.0f, M_PI/6.0f, approach == -1?5:1, true, true, approach==-1?&progressionStatus_:0);
|
|
if(approach == -1 && progressionStatus_.isCanceled())
|
|
{
|
|
postProcessing_ = false;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// graph optimization
|
|
if(returnedValue >=0)
|
|
{
|
|
if (approach == 1)
|
|
{
|
|
if(rtabmap::Optimizer::isAvailable(rtabmap::Optimizer::kTypeG2O))
|
|
{
|
|
std::map<int, rtabmap::Signature> signatures;
|
|
rtabmap_->getGraph(poses, links, true, true, &signatures);
|
|
|
|
rtabmap::ParametersMap param;
|
|
param.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerIterations(), "30"));
|
|
param.insert(rtabmap::ParametersPair(rtabmap::Parameters::kOptimizerEpsilon(), "0"));
|
|
rtabmap::Optimizer * sba = rtabmap::Optimizer::create(rtabmap::Optimizer::kTypeG2O, param);
|
|
poses = sba->optimizeBA(poses.rbegin()->first, poses, links, signatures);
|
|
delete sba;
|
|
}
|
|
else
|
|
{
|
|
UERROR("g2o not available!");
|
|
}
|
|
}
|
|
else if(approach!=4 && approach!=5 && approach != 7)
|
|
{
|
|
// simple graph optmimization
|
|
rtabmap_->getGraph(poses, links, true, true);
|
|
}
|
|
}
|
|
|
|
if(poses.size())
|
|
{
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
rtabmap::Statistics stats = rtabmap_->getStatistics();
|
|
stats.setPoses(poses);
|
|
stats.setConstraints(links);
|
|
|
|
LOGI("PostProcessing, sending rtabmap event to update graph...");
|
|
rtabmapEvents_.push_back(new rtabmap::RtabmapEvent(stats));
|
|
|
|
rtabmap_->setOptimizedPoses(poses, links);
|
|
}
|
|
else if(approach!=4 && approach!=5 && approach != 7)
|
|
{
|
|
returnedValue = -1;
|
|
}
|
|
|
|
if(returnedValue >=0)
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
// filter polygons
|
|
if(approach == -1 || approach == 4)
|
|
{
|
|
filterPolygonsOnNextRender_ = true;
|
|
}
|
|
|
|
// gain compensation
|
|
if(approach == -1 || approach == 5 || approach == 6)
|
|
{
|
|
gainCompensationOnNextRender_ = approach == 6 ? 2 : 1; // 2 = full, 1 = fast
|
|
}
|
|
|
|
// bilateral filtering
|
|
if(approach == 7)
|
|
{
|
|
bilateralFilteringOnNextRender_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
postProcessing_ = false;
|
|
LOGI("postProcessing end(%d) -> %d", approach, returnedValue);
|
|
return returnedValue;
|
|
}
|
|
|
|
void RTABMapApp::clearMeasures()
|
|
{
|
|
boost::mutex::scoped_lock lockRender(renderingMutex_);
|
|
measures_.clear();
|
|
measuresUpdated_ = true;
|
|
}
|
|
|
|
void RTABMapApp::setMeasuringMode(int mode)
|
|
{
|
|
if(visualizingMesh_)
|
|
{
|
|
measuringMode_ = mode;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::addMeasureButtonClicked()
|
|
{
|
|
if(visualizingMesh_)
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
addMeasureClicked_ = true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::teleportButtonClicked()
|
|
{
|
|
if(visualizingMesh_)
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
teleportClicked_ = true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::removeMeasure()
|
|
{
|
|
if(visualizingMesh_)
|
|
{
|
|
boost::mutex::scoped_lock lock(renderingMutex_);
|
|
removeMeasureClicked_ = true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setMetricSystem(bool enabled)
|
|
{
|
|
metricSystem_ = enabled;
|
|
if(measures_.size())
|
|
{
|
|
measuresUpdated_ = true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::setMeasuringTextSize(float size)
|
|
{
|
|
measuringTextSize_ = size;
|
|
if(measures_.size())
|
|
{
|
|
measuresUpdated_ = true;
|
|
}
|
|
}
|
|
|
|
void RTABMapApp::postOdometryEvent(
|
|
rtabmap::Transform pose,
|
|
float rgb_fx, float rgb_fy, float rgb_cx, float rgb_cy,
|
|
float depth_fx, float depth_fy, float depth_cx, float depth_cy,
|
|
const rtabmap::Transform & rgbFrame,
|
|
const rtabmap::Transform & depthFrame,
|
|
double stamp,
|
|
double depthStamp,
|
|
const void * yPlane, const void * uPlane, const void * vPlane, int yPlaneLen, int rgbWidth, int rgbHeight, int rgbFormat,
|
|
const void * depth, int depthLen, int depthWidth, int depthHeight, int depthFormat,
|
|
const void * conf, int confLen, int confWidth, int confHeight, int confFormat,
|
|
const float * points, int pointsLen, int pointsChannels,
|
|
rtabmap::Transform viewMatrix,
|
|
float p00, float p11, float p02, float p12, float p22, float p32, float p23,
|
|
float t0, float t1, float t2, float t3, float t4, float t5, float t6, float t7)
|
|
{
|
|
#if defined(RTABMAP_ARCORE) || defined(__APPLE__)
|
|
boost::mutex::scoped_lock lock(cameraMutex_);
|
|
if(cameraDriver_ == 3 && camera_)
|
|
{
|
|
if(pose.isNull())
|
|
{
|
|
// We are lost, trigger a new map on next update
|
|
camera_->resetOrigin();
|
|
return;
|
|
}
|
|
if(rgb_fx > 0.0f && rgb_fy > 0.0f && rgb_cx > 0.0f && rgb_cy > 0.0f && stamp > 0.0f && yPlane && vPlane && yPlaneLen == rgbWidth*rgbHeight)
|
|
{
|
|
#ifndef DISABLE_LOG
|
|
//LOGD("rgb format = %d depth format =%d ", rgbFormat, depthFormat);
|
|
#endif
|
|
#if defined(RTABMAP_ARCORE)
|
|
if(rgbFormat == AR_IMAGE_FORMAT_YUV_420_888 &&
|
|
(depth==0 || depthFormat == AIMAGE_FORMAT_DEPTH16))
|
|
#else //__APPLE__
|
|
if(rgbFormat == 875704422 &&
|
|
(depth==0 || depthFormat == 1717855600))
|
|
#endif
|
|
{
|
|
cv::Mat outputRGB;
|
|
#ifndef DISABLE_LOG
|
|
//LOGD("y=%p u=%p v=%p yLen=%d y->v=%ld", yPlane, uPlane, vPlane, yPlaneLen, (long)vPlane-(long)yPlane);
|
|
#endif
|
|
if((long)vPlane-(long)yPlane != yPlaneLen)
|
|
{
|
|
// The uv-plane is not concatenated to y plane in memory, so concatenate them
|
|
cv::Mat yuv(rgbHeight+rgbHeight/2, rgbWidth, CV_8UC1);
|
|
memcpy(yuv.data, yPlane, yPlaneLen);
|
|
memcpy(yuv.data+yPlaneLen, vPlane, rgbHeight/2*rgbWidth);
|
|
cv::cvtColor(yuv, outputRGB, cv::COLOR_YUV2BGR_NV21);
|
|
}
|
|
else
|
|
{
|
|
#ifdef __ANDROID__
|
|
cv::cvtColor(cv::Mat(rgbHeight+rgbHeight/2, rgbWidth, CV_8UC1, (void*)yPlane), outputRGB, cv::COLOR_YUV2BGR_NV21);
|
|
#else // __APPLE__
|
|
cv::cvtColor(cv::Mat(rgbHeight+rgbHeight/2, rgbWidth, CV_8UC1, (void*)yPlane), outputRGB, cv::COLOR_YUV2RGB_NV21);
|
|
#endif
|
|
}
|
|
|
|
|
|
cv::Mat outputDepth;
|
|
if(depth && depthHeight>0 && depthWidth>0)
|
|
{
|
|
#ifndef DISABLE_LOG
|
|
//LOGD("depth %dx%d len=%d", depthWidth, depthHeight, depthLen);
|
|
#endif
|
|
if(depthLen == 4*depthWidth*depthHeight)
|
|
{
|
|
// IOS
|
|
outputDepth = cv::Mat(depthHeight, depthWidth, CV_32FC1, (void*)depth).clone();
|
|
if(conf && confWidth == depthWidth && confHeight == depthHeight && confFormat == 1278226488 && depthConfidence_>0)
|
|
{
|
|
const unsigned char * confPtr = (const unsigned char *)conf;
|
|
float * depthPtr = outputDepth.ptr<float>();
|
|
int i=0;
|
|
for (int y = 0; y < outputDepth.rows; ++y)
|
|
{
|
|
for (int x = 0; x < outputDepth.cols; ++x)
|
|
{
|
|
// https://developer.apple.com/documentation/arkit/arconfidencelevel
|
|
// 0 = low
|
|
// 1 = medium
|
|
// 2 = high
|
|
if(confPtr[y*outputDepth.cols + x] < depthConfidence_)
|
|
{
|
|
depthPtr[y*outputDepth.cols + x] = 0.0f;
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(depthLen == 2*depthWidth*depthHeight)
|
|
{
|
|
// ANDROID
|
|
outputDepth = cv::Mat(depthHeight, depthWidth, CV_16UC1);
|
|
uint16_t *dataShort = (uint16_t *)depth;
|
|
for (int y = 0; y < outputDepth.rows; ++y)
|
|
{
|
|
for (int x = 0; x < outputDepth.cols; ++x)
|
|
{
|
|
uint16_t depthSample = dataShort[y*outputDepth.cols + x];
|
|
uint16_t depthRange = (depthSample & 0x1FFF); // first 3 bits are confidence
|
|
outputDepth.at<uint16_t>(y,x) = depthRange;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!outputRGB.empty())
|
|
{
|
|
// Convert in our coordinate frame
|
|
pose = rtabmap::rtabmap_world_T_opengl_world * pose * rtabmap::opengl_world_T_rtabmap_world;
|
|
|
|
// We should update the pose before querying poses for depth below (if not same stamp than rgb)
|
|
camera_->poseReceived(pose, stamp);
|
|
|
|
// Registration depth to rgb
|
|
if(!outputDepth.empty() && !depthFrame.isNull() && depth_fx!=0 && (rgbFrame != depthFrame || depthStamp!=stamp))
|
|
{
|
|
UTimer time;
|
|
rtabmap::Transform motion = rtabmap::Transform::getIdentity();
|
|
if(depthStamp != stamp)
|
|
{
|
|
// Interpolate pose
|
|
rtabmap::Transform poseRgb;
|
|
rtabmap::Transform poseDepth;
|
|
cv::Mat cov;
|
|
if(!camera_->getPose(camera_->getStampEpochOffset()+stamp, poseRgb, cov, 0.0))
|
|
{
|
|
UERROR("Could not find pose at rgb stamp %f (epoch %f)!", stamp, camera_->getStampEpochOffset()+stamp);
|
|
}
|
|
else if(!camera_->getPose(camera_->getStampEpochOffset()+depthStamp, poseDepth, cov, 0.0))
|
|
{
|
|
UERROR("Could not find pose at depth stamp %f (epoch %f) last rgb is %f!", depthStamp, camera_->getStampEpochOffset()+depthStamp, stamp);
|
|
}
|
|
else
|
|
{
|
|
#ifndef DISABLE_LOG
|
|
UDEBUG("poseRGB =%s (stamp=%f)", poseRgb.prettyPrint().c_str(), stamp);
|
|
UDEBUG("poseDepth=%s (stamp=%f)", poseDepth.prettyPrint().c_str(), depthStamp);
|
|
#endif
|
|
motion = poseRgb.inverse()*poseDepth;
|
|
// transform in camera frame
|
|
#ifndef DISABLE_LOG
|
|
UDEBUG("motion=%s", motion.prettyPrint().c_str());
|
|
#endif
|
|
motion = rtabmap::CameraModel::opticalRotation().inverse() * motion * rtabmap::CameraModel::opticalRotation();
|
|
#ifndef DISABLE_LOG
|
|
UDEBUG("motion=%s", motion.prettyPrint().c_str());
|
|
#endif
|
|
}
|
|
}
|
|
rtabmap::Transform rgbToDepth = motion*rgbFrame.inverse()*depthFrame;
|
|
float scale = (float)outputDepth.cols/(float)outputRGB.cols;
|
|
cv::Mat colorK = (cv::Mat_<double>(3,3) <<
|
|
rgb_fx*scale, 0, rgb_cx*scale,
|
|
0, rgb_fy*scale, rgb_cy*scale,
|
|
0, 0, 1);
|
|
cv::Mat depthK = (cv::Mat_<double>(3,3) <<
|
|
depth_fx, 0, depth_cx,
|
|
0, depth_fy, depth_cy,
|
|
0, 0, 1);
|
|
outputDepth = rtabmap::util2d::registerDepth(outputDepth, depthK, outputDepth.size(), colorK, rgbToDepth);
|
|
#ifndef DISABLE_LOG
|
|
UDEBUG("Depth registration time: %fs", time.elapsed());
|
|
#endif
|
|
}
|
|
|
|
rtabmap::CameraModel model = rtabmap::CameraModel(rgb_fx, rgb_fy, rgb_cx, rgb_cy, camera_->getDeviceTColorCamera(), 0, cv::Size(rgbWidth, rgbHeight));
|
|
#ifndef DISABLE_LOG
|
|
//LOGI("pointCloudData size=%d", pointsLen);
|
|
#endif
|
|
if(!fullResolution_)
|
|
{
|
|
outputRGB = rtabmap::util2d::decimate(outputRGB, 2);
|
|
model = model.scaled(1.0/double(2));
|
|
}
|
|
|
|
std::vector<cv::KeyPoint> kpts;
|
|
std::vector<cv::Point3f> kpts3;
|
|
rtabmap::LaserScan scan;
|
|
if(points && pointsLen>0)
|
|
{
|
|
cv::Mat pointsMat(1, pointsLen, CV_32FC(pointsChannels), (void*)points);
|
|
if(outputDepth.empty())
|
|
{
|
|
int kptsSize = fullResolution_ ? 12 : 6;
|
|
scan = rtabmap::CameraMobile::scanFromPointCloudData(pointsMat, pose, model, outputRGB, &kpts, &kpts3, kptsSize);
|
|
}
|
|
else
|
|
{
|
|
// We will recompute features if depth is available
|
|
scan = rtabmap::CameraMobile::scanFromPointCloudData(pointsMat, pose, model, outputRGB);
|
|
}
|
|
}
|
|
|
|
if(!outputDepth.empty())
|
|
{
|
|
rtabmap::CameraModel depthModel = model.scaled(float(outputDepth.cols) / float(model.imageWidth()));
|
|
depthModel.setLocalTransform(pose*model.localTransform());
|
|
camera_->setOcclusionImage(outputDepth, depthModel);
|
|
}
|
|
|
|
rtabmap::SensorData data(scan, outputRGB, outputDepth, model, 0, stamp);
|
|
data.setFeatures(kpts, kpts3, cv::Mat());
|
|
glm::mat4 projectionMatrix(0);
|
|
projectionMatrix[0][0] = p00;
|
|
projectionMatrix[1][1] = p11;
|
|
projectionMatrix[2][0] = p02;
|
|
projectionMatrix[2][1] = p12;
|
|
projectionMatrix[2][2] = p22;
|
|
projectionMatrix[2][3] = p32;
|
|
projectionMatrix[3][2] = p23;
|
|
glm::mat4 viewMatrixMat = rtabmap::glmFromTransform(viewMatrix);
|
|
float texCoords[8];
|
|
texCoords[0] = t0;
|
|
texCoords[1] = t1;
|
|
texCoords[2] = t2;
|
|
texCoords[3] = t3;
|
|
texCoords[4] = t4;
|
|
texCoords[5] = t5;
|
|
texCoords[6] = t6;
|
|
texCoords[7] = t7;
|
|
camera_->update(data, pose, viewMatrixMat, projectionMatrix, main_scene_.GetCameraType() == tango_gl::GestureCamera::kFirstPerson?texCoords:0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Missing image information! fx=%f fy=%f cx=%f cy=%f stamp=%f yPlane=%d vPlane=%d yPlaneLen=%d rgbWidth=%d rgbHeight=%d",
|
|
rgb_fx, rgb_fy, rgb_cx, rgb_cy, stamp, yPlane?1:0, vPlane?1:0, yPlaneLen, rgbWidth, rgbHeight);
|
|
}
|
|
}
|
|
#else
|
|
UERROR("Not built with ARCore or iOS!");
|
|
#endif
|
|
}
|
|
|
|
bool RTABMapApp::handleEvent(UEvent * event)
|
|
{
|
|
if(sensorCaptureThread_!=0)
|
|
{
|
|
// called from events manager thread, so protect the data
|
|
if(event->getClassName().compare("SensorEvent") == 0)
|
|
{
|
|
LOGI("Received SensorEvent!");
|
|
if(sensorMutex_.try_lock())
|
|
{
|
|
sensorEvents_.clear();
|
|
sensorEvents_.push_back(*((rtabmap::SensorEvent*)(event)));
|
|
sensorMutex_.unlock();
|
|
}
|
|
}
|
|
if(event->getClassName().compare("RtabmapEvent") == 0)
|
|
{
|
|
LOGI("Received RtabmapEvent event! status=%d", status_.first);
|
|
if(status_.first == rtabmap::RtabmapEventInit::kInitialized)
|
|
{
|
|
boost::mutex::scoped_lock lock(rtabmapMutex_);
|
|
rtabmapEvents_.push_back((rtabmap::RtabmapEvent*)event);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
LOGW("Received RtabmapEvent event but ignoring it while we are initializing...status=%d", status_.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(event->getClassName().compare("PoseEvent") == 0)
|
|
{
|
|
if(poseMutex_.try_lock())
|
|
{
|
|
poseEvents_.clear();
|
|
poseEvents_.push_back(((rtabmap::PoseEvent*)event)->pose());
|
|
poseMutex_.unlock();
|
|
}
|
|
}
|
|
|
|
if(event->getClassName().compare("CameraInfoEvent") == 0)
|
|
{
|
|
rtabmap::CameraInfoEvent * tangoEvent = (rtabmap::CameraInfoEvent*)event;
|
|
|
|
// Call JAVA callback with tango event msg
|
|
bool success = false;
|
|
#ifdef __ANDROID__
|
|
if(jvm && RTABMapActivity)
|
|
{
|
|
JNIEnv *env = 0;
|
|
jint rs = jvm->AttachCurrentThread(&env, NULL);
|
|
if(rs == JNI_OK && env)
|
|
{
|
|
jclass clazz = env->GetObjectClass(RTABMapActivity);
|
|
if(clazz)
|
|
{
|
|
jmethodID methodID = env->GetMethodID(clazz, "cameraEventCallback", "(ILjava/lang/String;Ljava/lang/String;)V" );
|
|
if(methodID)
|
|
{
|
|
env->CallVoidMethod(RTABMapActivity, methodID,
|
|
tangoEvent->type(),
|
|
env->NewStringUTF(tangoEvent->key().c_str()),
|
|
env->NewStringUTF(tangoEvent->value().c_str()));
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
jvm->DetachCurrentThread();
|
|
}
|
|
#else
|
|
if(swiftClassPtr_)
|
|
{
|
|
std::function<void()> actualCallback = [&](){
|
|
swiftCameraInfoEventCallback(swiftClassPtr_, tangoEvent->type(), tangoEvent->key().c_str(), tangoEvent->value().c_str());
|
|
};
|
|
actualCallback();
|
|
success = true;
|
|
}
|
|
#endif
|
|
if(!success)
|
|
{
|
|
UERROR("Failed to call RTABMapActivity::tangoEventCallback");
|
|
}
|
|
}
|
|
|
|
if(event->getClassName().compare("RtabmapEventInit") == 0)
|
|
{
|
|
status_.first = ((rtabmap::RtabmapEventInit*)event)->getStatus();
|
|
status_.second = ((rtabmap::RtabmapEventInit*)event)->getInfo();
|
|
LOGI("Received RtabmapEventInit! Status=%d info=%s", (int)status_.first, status_.second.c_str());
|
|
|
|
// Call JAVA callback with init msg
|
|
bool success = false;
|
|
#ifdef __ANDROID__
|
|
if(jvm && RTABMapActivity)
|
|
{
|
|
JNIEnv *env = 0;
|
|
jint rs = jvm->AttachCurrentThread(&env, NULL);
|
|
if(rs == JNI_OK && env)
|
|
{
|
|
jclass clazz = env->GetObjectClass(RTABMapActivity);
|
|
if(clazz)
|
|
{
|
|
jmethodID methodID = env->GetMethodID(clazz, "rtabmapInitEventCallback", "(ILjava/lang/String;)V" );
|
|
if(methodID)
|
|
{
|
|
env->CallVoidMethod(RTABMapActivity, methodID,
|
|
status_.first,
|
|
env->NewStringUTF(status_.second.c_str()));
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
jvm->DetachCurrentThread();
|
|
}
|
|
#else
|
|
if(swiftClassPtr_)
|
|
{
|
|
std::function<void()> actualCallback = [&](){
|
|
swiftInitCallback(swiftClassPtr_, status_.first, status_.second.c_str());
|
|
};
|
|
actualCallback();
|
|
success = true;
|
|
}
|
|
#endif
|
|
if(!success)
|
|
{
|
|
UERROR("Failed to call RTABMapActivity::rtabmapInitEventsCallback");
|
|
}
|
|
}
|
|
|
|
if(event->getClassName().compare("PostRenderEvent") == 0)
|
|
{
|
|
LOGI("Received PostRenderEvent!");
|
|
|
|
int loopClosureId = 0;
|
|
int featuresExtracted = 0;
|
|
if(((PostRenderEvent*)event)->getRtabmapEvent())
|
|
{
|
|
LOGI("Received PostRenderEvent! has getRtabmapEvent");
|
|
|
|
const rtabmap::Statistics & stats = ((PostRenderEvent*)event)->getRtabmapEvent()->getStats();
|
|
loopClosureId = stats.loopClosureId()>0?stats.loopClosureId():stats.proximityDetectionId()>0?stats.proximityDetectionId():0;
|
|
featuresExtracted = stats.getLastSignatureData().getWords().size();
|
|
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryWorking_memory_size(), uValue(stats.data(), rtabmap::Statistics::kMemoryWorking_memory_size(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryShort_time_memory_size(), uValue(stats.data(), rtabmap::Statistics::kMemoryShort_time_memory_size(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kKeypointDictionary_size(), uValue(stats.data(), rtabmap::Statistics::kKeypointDictionary_size(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kTimingTotal(), uValue(stats.data(), rtabmap::Statistics::kTimingTotal(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopHighest_hypothesis_id(), uValue(stats.data(), rtabmap::Statistics::kLoopHighest_hypothesis_id(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryDatabase_memory_used(), uValue(stats.data(), rtabmap::Statistics::kMemoryDatabase_memory_used(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopVisual_inliers(), uValue(stats.data(), rtabmap::Statistics::kLoopVisual_inliers(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopVisual_matches(), uValue(stats.data(), rtabmap::Statistics::kLoopVisual_matches(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopRejectedHypothesis(), uValue(stats.data(), rtabmap::Statistics::kLoopRejectedHypothesis(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopOptimization_max_error(), uValue(stats.data(), rtabmap::Statistics::kLoopOptimization_max_error(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopOptimization_max_error_ratio(), uValue(stats.data(), rtabmap::Statistics::kLoopOptimization_max_error_ratio(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryRehearsal_sim(), uValue(stats.data(), rtabmap::Statistics::kMemoryRehearsal_sim(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopHighest_hypothesis_value(), uValue(stats.data(), rtabmap::Statistics::kLoopHighest_hypothesis_value(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryDistance_travelled(), uValue(stats.data(), rtabmap::Statistics::kMemoryDistance_travelled(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kMemoryFast_movement(), uValue(stats.data(), rtabmap::Statistics::kMemoryFast_movement(), 0.0f)));
|
|
uInsert(bufferedStatsData_, std::make_pair<std::string, float>(rtabmap::Statistics::kLoopLandmark_detected(), uValue(stats.data(), rtabmap::Statistics::kLoopLandmark_detected(), 0.0f)));
|
|
}
|
|
// else use last data
|
|
|
|
int nodes = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryWorking_memory_size(), 0.0f) +
|
|
uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryShort_time_memory_size(), 0.0f);
|
|
int words = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kKeypointDictionary_size(), 0.0f);
|
|
float updateTime = uValue(bufferedStatsData_, rtabmap::Statistics::kTimingTotal(), 0.0f);
|
|
int highestHypId = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kLoopHighest_hypothesis_id(), 0.0f);
|
|
int databaseMemoryUsed = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryDatabase_memory_used(), 0.0f);
|
|
int inliers = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kLoopVisual_inliers(), 0.0f);
|
|
int matches = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kLoopVisual_matches(), 0.0f);
|
|
int rejected = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kLoopRejectedHypothesis(), 0.0f);
|
|
float optimizationMaxError = uValue(bufferedStatsData_, rtabmap::Statistics::kLoopOptimization_max_error(), 0.0f);
|
|
float optimizationMaxErrorRatio = uValue(bufferedStatsData_, rtabmap::Statistics::kLoopOptimization_max_error_ratio(), 0.0f);
|
|
float rehearsalValue = uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryRehearsal_sim(), 0.0f);
|
|
float hypothesis = uValue(bufferedStatsData_, rtabmap::Statistics::kLoopHighest_hypothesis_value(), 0.0f);
|
|
float distanceTravelled = uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryDistance_travelled(), 0.0f);
|
|
int fastMovement = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kMemoryFast_movement(), 0.0f);
|
|
int landmarkDetected = (int)uValue(bufferedStatsData_, rtabmap::Statistics::kLoopLandmark_detected(), 0.0f);
|
|
rtabmap::Transform currentPose = main_scene_.GetCameraPose();
|
|
float x=0.0f,y=0.0f,z=0.0f,roll=0.0f,pitch=0.0f,yaw=0.0f;
|
|
if(!currentPose.isNull())
|
|
{
|
|
currentPose.getTranslationAndEulerAngles(x,y,z,roll,pitch,yaw);
|
|
}
|
|
|
|
// Call JAVA callback with some stats
|
|
UINFO("Send statistics to GUI");
|
|
bool success = false;
|
|
#ifdef __ANDROID__
|
|
if(jvm && RTABMapActivity)
|
|
{
|
|
JNIEnv *env = 0;
|
|
jint rs = jvm->AttachCurrentThread(&env, NULL);
|
|
if(rs == JNI_OK && env)
|
|
{
|
|
jclass clazz = env->GetObjectClass(RTABMapActivity);
|
|
if(clazz)
|
|
{
|
|
jmethodID methodID = env->GetMethodID(clazz, "updateStatsCallback", "(IIIIFIIIIIIFIFIFFFFIIFFFFFF)V" );
|
|
if(methodID)
|
|
{
|
|
env->CallVoidMethod(RTABMapActivity, methodID,
|
|
nodes,
|
|
words,
|
|
totalPoints_,
|
|
totalPolygons_,
|
|
updateTime,
|
|
loopClosureId,
|
|
highestHypId,
|
|
databaseMemoryUsed,
|
|
inliers,
|
|
matches,
|
|
featuresExtracted,
|
|
hypothesis,
|
|
lastDrawnCloudsCount_,
|
|
renderingTime_>0.0f?1.0f/renderingTime_:0.0f,
|
|
rejected,
|
|
rehearsalValue,
|
|
optimizationMaxError,
|
|
optimizationMaxErrorRatio,
|
|
distanceTravelled,
|
|
fastMovement,
|
|
landmarkDetected,
|
|
x,
|
|
y,
|
|
z,
|
|
roll,
|
|
pitch,
|
|
yaw);
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
jvm->DetachCurrentThread();
|
|
}
|
|
#else // __APPLE__
|
|
if(swiftClassPtr_)
|
|
{
|
|
std::function<void()> actualCallback = [&](){
|
|
swiftStatsUpdatedCallback(swiftClassPtr_,
|
|
nodes,
|
|
words,
|
|
totalPoints_,
|
|
totalPolygons_,
|
|
updateTime,
|
|
loopClosureId,
|
|
highestHypId,
|
|
databaseMemoryUsed,
|
|
inliers,
|
|
matches,
|
|
featuresExtracted,
|
|
hypothesis,
|
|
lastDrawnCloudsCount_,
|
|
renderingTime_>0.0f?1.0f/renderingTime_:0.0f,
|
|
rejected,
|
|
rehearsalValue,
|
|
optimizationMaxError,
|
|
optimizationMaxErrorRatio,
|
|
distanceTravelled,
|
|
fastMovement,
|
|
landmarkDetected,
|
|
x,
|
|
y,
|
|
z,
|
|
roll,
|
|
pitch,
|
|
yaw);
|
|
};
|
|
actualCallback();
|
|
success = true;
|
|
}
|
|
#endif
|
|
if(!success)
|
|
{
|
|
UERROR("Failed to call RTABMapActivity::updateStatsCallback");
|
|
}
|
|
renderingTime_ = 0.0f;
|
|
}
|
|
return false;
|
|
}
|
|
|