Updated detect more loop closures with optimization check to accept (like in dbviewer). DBViewer: added Depth image edition dialog. GainCompensator: now doing it on 3 channels separatly. Export: removed gain's alpha option, added max polygons option, added brightness/contrast auto balance option

This commit is contained in:
matlabbe
2017-03-30 15:33:48 -04:00
parent a928a0b404
commit 767b29d5a3
20 changed files with 1695 additions and 551 deletions

View File

@@ -47,6 +47,7 @@ class Ui_DatabaseViewer;
class QGraphicsScene;
class QGraphicsView;
class QLabel;
class QDialog;
namespace rtabmap
{
@@ -56,6 +57,7 @@ class SensorData;
class CloudViewer;
class OctoMap;
class ExportCloudsDialog;
class EditDepthArea;
class RTABMAPGUI_EXP DatabaseViewer : public QMainWindow
{
@@ -79,6 +81,8 @@ private slots:
void writeSettings();
void configModified();
void openDatabase();
void updateStatistics();
void editDepthImage();
void generateGraph();
void exportDatabase();
void extractImages();
@@ -153,8 +157,8 @@ private:
std::multimap<int, rtabmap::Link> updateLinksWithModifications(
const std::multimap<int, rtabmap::Link> & edgeConstraints);
void updateLoopClosuresSlider(int from = 0, int to = 0);
void refineConstraint(int from, int to, bool silent, bool updateGraph);
bool addConstraint(int from, int to, bool silent, bool updateGraph);
void refineConstraint(int from, int to, bool silent);
bool addConstraint(int from, int to, bool silent);
private:
Ui_DatabaseViewer * ui_;
@@ -183,8 +187,11 @@ private:
std::map<int, std::pair<float, cv::Point3f> > localMapsInfo_; // <cell size, viewpoint>
std::map<int, std::pair<cv::Mat, cv::Mat> > generatedLocalMaps_; // <ground, obstacles>
std::map<int, std::pair<float, cv::Point3f> > generatedLocalMapsInfo_; // <cell size, viewpoint>
std::map<int, cv::Mat> modifiedDepthImages_;
OctoMap * octomap_;
ExportCloudsDialog * exportDialog_;
QDialog * editDepthDialog_;
EditDepthArea * editDepthArea_;
bool savedMaximized_;
bool firstCall_;

View File

@@ -54,6 +54,7 @@ public:
void setMaximumSteps(int steps);
void setAutoClose(bool on, int delayedClosingTimeMsec = -1);
void setCancelButtonVisible(bool visible);
bool isCanceled() const {return _canceled;}
signals:
void canceled();
@@ -69,6 +70,7 @@ public slots:
private slots:
void closeDialog();
void cancel();
private:
QLabel * _text;
@@ -79,6 +81,7 @@ private:
QCheckBox * _closeWhenDoneCheckBox;
QString _endMessage;
int _delayedClosingTime; // sec
bool _canceled;
};
}

View File

@@ -30,6 +30,7 @@ SET(headers_ui
./DepthCalibrationDialog.h
./3rdParty/QMultiComboBox.h
./TexturingState.h
./EditDepthArea.h
)
SET(uis
@@ -93,6 +94,7 @@ SET(SRC_FILES
./ExportScansDialog.cpp
./MapVisibilityWidget.cpp
./GraphViewer.cpp
./EditDepthArea.cpp
./CreateSimpleCalibrationDialog.cpp
./ParametersToolBox.cpp
./DepthCalibrationDialog.cpp

View File

@@ -70,12 +70,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "rtabmap/core/OccupancyGrid.h"
#include "rtabmap/gui/DataRecorder.h"
#include "ExportCloudsDialog.h"
#include "EditDepthArea.h"
#include "rtabmap/core/SensorData.h"
#include "rtabmap/core/GainCompensator.h"
#include "ExportDialog.h"
#include "rtabmap/gui/ProgressDialog.h"
#include "ParametersToolBox.h"
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/filters/voxel_grid.h>
@@ -93,6 +93,7 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
dbDriver_(0),
octomap_(0),
exportDialog_(new ExportCloudsDialog(this)),
editDepthDialog_(new QDialog(this)),
savedMaximized_(false),
firstCall_(true),
iniFilePath_(ini)
@@ -114,6 +115,20 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
connect(ui_->comboBox_logger_level, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLoggerLevel()));
connect(ui_->checkBox_verticalLayout, SIGNAL(stateChanged(int)), this, SLOT(setupMainLayout(int)));
editDepthDialog_->resize(640, 480);
QVBoxLayout * vLayout = new QVBoxLayout(editDepthDialog_);
editDepthArea_ = new EditDepthArea(editDepthDialog_);
vLayout->setContentsMargins(0,0,0,0);
vLayout->setSpacing(0);
vLayout->addWidget(editDepthArea_, 1);
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel | QDialogButtonBox::Reset, Qt::Horizontal, editDepthDialog_);
vLayout->addWidget(buttonBox);
connect(buttonBox, SIGNAL(accepted()), editDepthDialog_, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), editDepthDialog_, SLOT(reject()));
connect(buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), editDepthArea_, SLOT(resetChanges()));
editDepthDialog_->setLayout(vLayout);
editDepthDialog_->setWindowTitle(tr("Edit Depth Image"));
QString title("RTAB-Map Database Viewer[*]");
this->setWindowTitle(title);
@@ -173,6 +188,7 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
uInsert(parameters, Parameters::getDefaultParameters("StereoBM"));
uInsert(parameters, Parameters::getDefaultParameters("Grid"));
parameters.insert(*Parameters::getDefaultParameters().find(Parameters::kRGBDOptimizeMaxError()));
parameters.insert(*Parameters::getDefaultParameters().find(Parameters::kRGBDLoopClosureReextractFeatures()));
ui_->parameters_toolbox->setupUi(parameters);
exportDialog_->setObjectName("ExportCloudsDialog");
this->readSettings();
@@ -192,6 +208,8 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
ui_->menuView->addAction(ui_->dockWidget_statistics->toggleViewAction());
connect(ui_->dockWidget_graphView->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateGraphView()));
connect(ui_->dockWidget_occupancyGridView->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateGraphView()));
connect(ui_->dockWidget_statistics->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateStatistics()));
connect(ui_->parameters_toolbox, SIGNAL(parametersChanged(const QStringList &)), this, SLOT(notifyParametersChanged(const QStringList &)));
@@ -203,6 +221,7 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
connect(ui_->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase()));
connect(ui_->actionExport, SIGNAL(triggered()), this, SLOT(exportDatabase()));
connect(ui_->actionExtract_images, SIGNAL(triggered()), this, SLOT(extractImages()));
connect(ui_->actionEdit_depth_image, SIGNAL(triggered()), this, SLOT(editDepthImage()));
connect(ui_->actionGenerate_graph_dot, SIGNAL(triggered()), this, SLOT(generateGraph()));
connect(ui_->actionGenerate_local_graph_dot, SIGNAL(triggered()), this, SLOT(generateLocalGraph()));
connect(ui_->actionGenerate_TORO_graph_graph, SIGNAL(triggered()), this, SLOT(generateTOROGraph()));
@@ -474,16 +493,19 @@ void DatabaseViewer::readSettings()
ui_->doubleSpinBox_icp_minDepth->setValue(settings.value("minDepth", ui_->doubleSpinBox_icp_minDepth->value()).toDouble());
ui_->checkBox_icp_from_depth->setChecked(settings.value("icpFromDepth", ui_->checkBox_icp_from_depth->isChecked()).toBool());
settings.endGroup();
// Visual parameters
settings.beginGroup("visual");
ui_->doubleSpinBox_detectMore_radius->setValue(settings.value("detectMoreRadius", ui_->doubleSpinBox_detectMore_radius->value()).toDouble());
ui_->doubleSpinBox_detectMore_angle->setValue(settings.value("detectMoreAngle", ui_->doubleSpinBox_detectMore_angle->value()).toDouble());
ui_->spinBox_detectMore_iterations->setValue(settings.value("detectMoreIterations", ui_->spinBox_detectMore_iterations->value()).toInt());
settings.endGroup();
settings.endGroup(); // DatabaseViewer
// Use same parameters used by RTAB-Map
settings.beginGroup("Gui");
exportDialog_->loadSettings(settings);
settings.beginGroup("PostProcessingDialog");
ui_->doubleSpinBox_detectMore_radius->setValue(settings.value("cluster_radius", ui_->doubleSpinBox_detectMore_radius->value()).toDouble());
ui_->doubleSpinBox_detectMore_angle->setValue(settings.value("cluster_angle", ui_->doubleSpinBox_detectMore_angle->value()).toDouble());
ui_->spinBox_detectMore_iterations->setValue(settings.value("iterations", ui_->spinBox_detectMore_iterations->value()).toInt());
settings.endGroup();
settings.endGroup();
ParametersMap parameters;
Parameters::readINI(path.toStdString(), parameters);
@@ -561,16 +583,17 @@ void DatabaseViewer::writeSettings()
settings.setValue("icpFromDepth", ui_->checkBox_icp_from_depth->isChecked());
settings.endGroup();
// save Visual parameters
settings.beginGroup("visual");
settings.setValue("detectMoreRadius", ui_->doubleSpinBox_detectMore_radius->value());
settings.setValue("detectMoreAngle", ui_->doubleSpinBox_detectMore_angle->value());
settings.setValue("detectMoreIterations", ui_->spinBox_detectMore_iterations->value());
settings.endGroup();
settings.endGroup(); // DatabaseViewer
exportDialog_->saveSettings(settings);
// Use same parameters used by RTAB-Map
settings.beginGroup("Gui");
exportDialog_->saveSettings(settings, exportDialog_->objectName());
settings.beginGroup("PostProcessingDialog");
settings.setValue("cluster_radius", ui_->doubleSpinBox_detectMore_radius->value());
settings.setValue("cluster_angle", ui_->doubleSpinBox_detectMore_angle->value());
settings.setValue("iterations", ui_->spinBox_detectMore_iterations->value());
settings.endGroup();
settings.endGroup();
ParametersMap parameters = ui_->parameters_toolbox->getParameters();
for(ParametersMap::iterator iter=parameters.begin(); iter!=parameters.end();)
@@ -629,6 +652,7 @@ bool DatabaseViewer::openDatabase(const QString & path)
ui_->actionGenerate_TORO_graph_graph->setEnabled(false);
ui_->actionGenerate_g2o_graph_g2o->setEnabled(false);
ui_->checkBox_showOptimized->setEnabled(false);
ui_->toolBox_statistics->clear();
databaseFileName_.clear();
}
@@ -1201,6 +1225,7 @@ void DatabaseViewer::updateIds()
linksAdded_.clear();
linksRefined_.clear();
linksRemoved_.clear();
ui_->toolBox_statistics->clear();
ui_->label_optimizeFrom->setText(tr("Optimize from"));
std::multimap<int, Link> links;
dbDriver_->getAllLinks(links, true);
@@ -1214,7 +1239,6 @@ void DatabaseViewer::updateIds()
dbDriver_->getAllNodeIds(idsWithoutBad, false, true);
int badcountInLTM = 0;
int badCountInGraph = 0;
double firstStamp = 0.0;
for(int i=0; i<ids_.size(); ++i)
{
idToIndex_.insert(ids_[i], i);
@@ -1227,17 +1251,6 @@ void DatabaseViewer::updateIds()
dbDriver_->getNodeInfo(ids_[i], p, mapId, w, l, s, g);
mapIds_.insert(std::make_pair(ids_[i], mapId));
double stamp=0.0;
std::map<std::string, float> statistics = dbDriver_->getStatistics(ids_[i], stamp);
if(firstStamp==0.0)
{
firstStamp = stamp;
}
for(std::map<std::string, float>::iterator iter=statistics.begin(); iter!=statistics.end(); ++iter)
{
ui_->toolBox_statistics->updateStat(iter->first.c_str(), float(stamp-firstStamp), iter->second, true);
}
if(i>0)
{
if(mapIds_.at(ids_[i-1]) == mapId)
@@ -1309,6 +1322,12 @@ void DatabaseViewer::updateIds()
}
UINFO("Loaded %d ids, %d poses and %d links", (int)ids_.size(), (int)poses_.size(), (int)links_.size());
if(ids_.size() && ui_->toolBox_statistics->isVisible())
{
UINFO("Update statistics...");
updateStatistics();
}
UINFO("Update database info...");
ui_->textEdit_info->clear();
ui_->textEdit_info->append(tr("Version:\t\t%1").arg(dbDriver_->getDatabaseVersion().c_str()));
@@ -1450,6 +1469,52 @@ void DatabaseViewer::updateIds()
}
}
void DatabaseViewer::updateStatistics()
{
if(dbDriver_)
{
ui_->toolBox_statistics->clear();
double firstStamp = 0.0;
for(int i=0; i<ids_.size(); ++i)
{
double stamp=0.0;
std::map<std::string, float> statistics = dbDriver_->getStatistics(ids_[i], stamp);
if(firstStamp==0.0)
{
firstStamp = stamp;
}
for(std::map<std::string, float>::iterator iter=statistics.begin(); iter!=statistics.end(); ++iter)
{
ui_->toolBox_statistics->updateStat(iter->first.c_str(), float(stamp-firstStamp), iter->second, true);
}
}
}
}
void DatabaseViewer::editDepthImage()
{
if(dbDriver_ && ids_.size())
{
int id = ids_.at(ui_->horizontalSlider_A->value());
SensorData data;
dbDriver_->getNodeData(id, data, true, false, false, false);
data.uncompressData();
if(!data.depthRaw().empty())
{
editDepthArea_->setImage(data.depthRaw(), data.imageRaw());
if(editDepthDialog_->exec() == QDialog::Accepted && editDepthArea_->isModified())
{
cv::Mat depth = editDepthArea_->getModifiedImage();
UASSERT(data.depthRaw().type() == depth.type());
UASSERT(data.depthRaw().cols == depth.cols);
UASSERT(data.depthRaw().rows == depth.rows);
dbDriver_->updateDepthImage(id, depth);
this->update3dView();
}
}
}
}
void DatabaseViewer::generateGraph()
{
if(!dbDriver_)
@@ -2070,6 +2135,12 @@ void DatabaseViewer::detectMoreLoopClosures()
const std::map<int, Transform> & optimizedPoses = graphes_.back();
rtabmap::ProgressDialog * progressDialog = new rtabmap::ProgressDialog(this);
progressDialog->setAttribute(Qt::WA_DeleteOnClose);
progressDialog->setMaximumSteps(1);
progressDialog->setCancelButtonVisible(true);
progressDialog->show();
int iterations = ui_->spinBox_detectMore_iterations->value();
UASSERT(iterations > 0);
int added = 0;
@@ -2081,8 +2152,18 @@ void DatabaseViewer::detectMoreLoopClosures()
optimizedPoses,
ui_->doubleSpinBox_detectMore_radius->value(),
ui_->doubleSpinBox_detectMore_angle->value()*CV_PI/180.0);
progressDialog->setMaximumSteps(progressDialog->maximumSteps()+(int)clusters.size());
progressDialog->appendText(tr("Looking for more loop closures, clusters found %1 clusters.").arg(clusters.size()));
QApplication::processEvents();
if(progressDialog->isCanceled())
{
break;
}
std::set<int> addedLinks;
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!= clusters.end(); ++iter)
int i=0;
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!= clusters.end() && !progressDialog->isCanceled(); ++iter, ++i)
{
int from = iter->first;
int to = iter->second;
@@ -2099,27 +2180,36 @@ void DatabaseViewer::detectMoreLoopClosures()
addedLinks.find(from) == addedLinks.end() && addedLinks.find(to) == addedLinks.end())
{
checkedLoopClosures.insert(std::make_pair(from, to));
if(addConstraint(from, to, true, false))
if(addConstraint(from, to, true))
{
UINFO("Added new loop closure between %d and %d.", from, to);
++added;
addedLinks.insert(from);
addedLinks.insert(to);
progressDialog->appendText(tr("Detected loop closure %1->%2! (%3/%4)").arg(from).arg(to).arg(i+1).arg(clusters.size()));
}
QApplication::processEvents();
}
}
progressDialog->incrementStep();
}
UINFO("Iteration %d/%d: added %d loop closures.", n+1, iterations, (int)addedLinks.size()/2);
progressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!").arg(n+1).arg(iterations).arg(addedLinks.size()/2));
if(addedLinks.size() == 0)
{
break;
}
}
if(added)
{
this->updateGraphView();
}
UINFO("Total added %d loop closures.", added);
progressDialog->appendText(tr("Total new loop closures detected=%1").arg(added));
progressDialog->setValue(progressDialog->maximumSteps());
}
void DatabaseViewer::refineAllNeighborLinks()
@@ -2134,7 +2224,7 @@ void DatabaseViewer::refineAllNeighborLinks()
{
int from = neighborLinks_[i].from();
int to = neighborLinks_[i].to();
this->refineConstraint(neighborLinks_[i].from(), neighborLinks_[i].to(), true, false);
this->refineConstraint(neighborLinks_[i].from(), neighborLinks_[i].to(), true);
progressDialog.appendText(tr("Refined link %1->%2 (%3/%4)").arg(from).arg(to).arg(i+1).arg(neighborLinks_.size()));
progressDialog.incrementStep();
@@ -2159,7 +2249,7 @@ void DatabaseViewer::refineAllLoopClosureLinks()
{
int from = loopLinks_[i].from();
int to = loopLinks_[i].to();
this->refineConstraint(loopLinks_[i].from(), loopLinks_[i].to(), true, false);
this->refineConstraint(loopLinks_[i].from(), loopLinks_[i].to(), true);
progressDialog.appendText(tr("Refined link %1->%2 (%3/%4)").arg(from).arg(to).arg(i+1).arg(loopLinks_.size()));
progressDialog.incrementStep();
@@ -4218,10 +4308,10 @@ void DatabaseViewer::refineConstraint()
{
int from = ids_.at(ui_->horizontalSlider_A->value());
int to = ids_.at(ui_->horizontalSlider_B->value());
refineConstraint(from, to, false, true);
refineConstraint(from, to, false);
}
void DatabaseViewer::refineConstraint(int from, int to, bool silent, bool updateGraph)
void DatabaseViewer::refineConstraint(int from, int to, bool silent)
{
if(from == to)
{
@@ -4370,13 +4460,13 @@ void DatabaseViewer::refineConstraint(int from, int to, bool silent, bool update
{
linksRefined_.insert(std::make_pair(newLink.from(), newLink));
if(updateGraph)
if(!silent)
{
this->updateGraphView();
}
}
if(ui_->dockWidget_constraints->isVisible())
if(!silent && ui_->dockWidget_constraints->isVisible())
{
this->updateConstraintView(newLink, true);
}
@@ -4394,10 +4484,10 @@ void DatabaseViewer::addConstraint()
{
int from = ids_.at(ui_->horizontalSlider_A->value());
int to = ids_.at(ui_->horizontalSlider_B->value());
addConstraint(from, to, false, true);
addConstraint(from, to, false);
}
bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGraph)
bool DatabaseViewer::addConstraint(int from, int to, bool silent)
{
bool switchedIds = false;
if(from == to)
@@ -4421,24 +4511,40 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
UASSERT(!containsLink(linksRefined_, from, to));
ParametersMap parameters = ui_->parameters_toolbox->getParameters();
Registration * reg = Registration::create(parameters);
Transform t;
RegistrationInfo info;
// Add sensor data to generate features
SensorData dataFrom;
dbDriver_->getNodeData(from, dataFrom);
dataFrom.uncompressData();
SensorData dataTo;
dbDriver_->getNodeData(to, dataTo);
dataTo.uncompressData();
std::list<int> ids;
ids.push_back(from);
ids.push_back(to);
std::list<Signature*> signatures;
dbDriver_->loadSignatures(ids, signatures);
if(signatures.size() != 2)
{
for(std::list<Signature*>::iterator iter=signatures.begin(); iter!=signatures.end(); ++iter)
{
delete *iter;
return false;
}
}
Signature * fromS = *signatures.begin();
Signature * toS = *signatures.rbegin();
bool reextractVisualFeatures = uStr2Bool(parameters.at(Parameters::kRGBDLoopClosureReextractFeatures()));
if(reg->isScanRequired() ||
reg->isUserDataRequired() ||
reextractVisualFeatures)
{
// Add sensor data to generate features
dbDriver_->getNodeData(from, fromS->sensorData(), reextractVisualFeatures, reg->isScanRequired(), reg->isUserDataRequired(), false);
fromS->sensorData().uncompressData();
dbDriver_->getNodeData(to, toS->sensorData());
toS->sensorData().uncompressData();
}
UDEBUG("");
Registration * reg = Registration::create(parameters);
Signature fromS(dataFrom);
Signature toS(dataTo);
t = reg->computeTransformationMod(fromS, toS, Transform(), &info);
t = reg->computeTransformationMod(*fromS, *toS, Transform(), &info);
delete reg;
UDEBUG("");
@@ -4446,13 +4552,13 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
{
if(switchedIds)
{
ui_->graphicsView_A->setFeatures(toS.getWords(), dataTo.depthRaw());
ui_->graphicsView_B->setFeatures(fromS.getWords(), dataFrom.depthRaw());
ui_->graphicsView_A->setFeatures(toS->getWords(), toS->sensorData().depthRaw());
ui_->graphicsView_B->setFeatures(fromS->getWords(), fromS->sensorData().depthRaw());
}
else
{
ui_->graphicsView_A->setFeatures(fromS.getWords(), dataFrom.depthRaw());
ui_->graphicsView_B->setFeatures(toS.getWords(), dataTo.depthRaw());
ui_->graphicsView_A->setFeatures(fromS->getWords(), fromS->sensorData().depthRaw());
ui_->graphicsView_B->setFeatures(toS->getWords(), toS->sensorData().depthRaw());
}
updateWordsMatching();
}
@@ -4475,6 +4581,11 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
tr("Add link"),
tr("Cannot find a transformation between nodes %1 and %2: %3").arg(from).arg(to).arg(info.rejectedMsg.c_str()));
}
for(std::list<Signature*>::iterator iter=signatures.begin(); iter!=signatures.end(); ++iter)
{
delete *iter;
}
}
else if(containsLink(linksRemoved_, from, to))
{
@@ -4553,7 +4664,7 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
{
UINFO("Max optimization linear error = %f m (link %d->%d)", maxLinearError, maxLinearLink->from(), maxLinearLink->to());
}
if(maxLinearLink)
if(maxAngularLink)
{
UINFO("Max optimization angular error = %f deg (link %d->%d)", maxAngularError*180.0f/M_PI, maxAngularLink->from(), maxAngularLink->to());
}
@@ -4569,8 +4680,8 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
maxLinearLink->from(),
maxLinearLink->to(),
maxAngularError*180.0f/M_PI,
maxAngularLink->from(),
maxAngularLink->to(),
maxAngularLink?maxAngularLink->from():0,
maxAngularLink?maxAngularLink->to():0,
Parameters::kRGBDOptimizeMaxError().c_str(),
maxOptimizationError);
}
@@ -4606,9 +4717,9 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
{
linksAdded_.insert(std::make_pair(newLink.from(), newLink));
}
updateLoopClosuresSlider(from, to);
if(updateGraph)
if(!silent)
{
updateLoopClosuresSlider(from, to);
this->updateGraphView();
}
}

View File

@@ -0,0 +1,314 @@
/*
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 <QWidget>
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#include <QAction>
#include <QInputDialog>
#include "EditDepthArea.h"
#include <rtabmap/utilite/ULogger.h>
#include <rtabmap/utilite/UCv2Qt.h>
namespace rtabmap {
EditDepthArea::EditDepthArea(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
modified_ = false;
scribbling_ = false;
myPenWidth_ = 10;
menu_ = new QMenu(tr(""), this);
showRGB_ = menu_->addAction(tr("Show RGB Image"));
showRGB_->setCheckable(true);
showRGB_->setChecked(true);
removeCluster_ = menu_->addAction(tr("Remove Cluster"));
setPenWidth_ = menu_->addAction(tr("Set Pen Width..."));
resetChanges_ = menu_->addAction(tr("Reset Changes"));
}
void EditDepthArea::setImage(const cv::Mat &depth, const cv::Mat & rgb)
{
UASSERT(!depth.empty());
UASSERT(depth.type() == CV_32FC1 ||
depth.type() == CV_16UC1);
originalImage_ = depth;
image_ = uCvMat2QImage(depth).convertToFormat(QImage::Format_RGB32);
imageRGB_ = QImage();
if(!rgb.empty())
{
imageRGB_ = uCvMat2QImage(rgb);
if( depth.cols != rgb.cols ||
depth.rows != rgb.rows)
{
// scale rgb to depth
imageRGB_ = imageRGB_.scaled(image_.size());
}
}
showRGB_->setEnabled(!imageRGB_.isNull());
modified_ = false;
update();
}
cv::Mat EditDepthArea::getModifiedImage() const
{
cv::Mat modifiedImage = originalImage_.clone();
if(modified_)
{
UASSERT(image_.width() == modifiedImage.cols &&
image_.height() == modifiedImage.rows);
UASSERT(modifiedImage.type() == CV_32FC1 ||
modifiedImage.type() == CV_16UC1);
for(int j=0; j<image_.height(); ++j)
{
for(int i=0; i<image_.width(); ++i)
{
if(qRed(image_.pixel(i, j)) == 0 &&
qGreen(image_.pixel(i, j)) == 0 &&
qBlue(image_.pixel(i, j)) == 0)
{
if(modifiedImage.type() == CV_32FC1)
{
modifiedImage.at<float>(j,i) = 0.0f;
}
else // CV_16UC1
{
modifiedImage.at<unsigned short>(j,i) = 0;
}
}
}
}
}
return modifiedImage;
}
void EditDepthArea::setPenWidth(int newWidth)
{
myPenWidth_ = newWidth;
}
void EditDepthArea::resetChanges()
{
image_ = uCvMat2QImage(originalImage_).convertToFormat(QImage::Format_RGB32);
modified_ = false;
update();
}
void EditDepthArea::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
lastPoint_.setX((event->pos().x()-offsetX)/scale);
lastPoint_.setY((event->pos().y()-offsetY)/scale);
scribbling_ = true;
}
}
void EditDepthArea::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && scribbling_)
{
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint to;
to.setX((event->pos().x()-offsetX)/scale);
to.setY((event->pos().y()-offsetY)/scale);
drawLineTo(to);
}
}
void EditDepthArea::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && scribbling_) {
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint to;
to.setX((event->pos().x()-offsetX)/scale);
to.setY((event->pos().y()-offsetY)/scale);
drawLineTo(to);
scribbling_ = false;
}
}
void EditDepthArea::computeScaleOffsets(const QRect & targetRect, float & scale, float & offsetX, float & offsetY) const
{
scale = 1.0f;
offsetX = 0.0f;
offsetY = 0.0f;
if(!image_.isNull())
{
float w = image_.width();
float h = image_.height();
float widthRatio = float(targetRect.width()) / w;
float heightRatio = float(targetRect.height()) / h;
//printf("w=%f, h=%f, wR=%f, hR=%f, sW=%d, sH=%d\n", w, h, widthRatio, heightRatio, this->rect().width(), this->rect().height());
if(widthRatio < heightRatio)
{
scale = widthRatio;
}
else
{
scale = heightRatio;
}
//printf("ratio=%f\n",ratio);
w *= scale;
h *= scale;
if(w < targetRect.width())
{
offsetX = (targetRect.width() - w)/2.0f;
}
if(h < targetRect.height())
{
offsetY = (targetRect.height() - h)/2.0f;
}
//printf("offsetX=%f, offsetY=%f\n",offsetX, offsetY);
}
}
void EditDepthArea::paintEvent(QPaintEvent *event)
{
//Scale
float ratio, offsetX, offsetY;
this->computeScaleOffsets(event->rect(), ratio, offsetX, offsetY);
QPainter painter(this);
painter.translate(offsetX, offsetY);
painter.scale(ratio, ratio);
if(showRGB_->isChecked() && !imageRGB_.isNull())
{
painter.setOpacity(0.5);
painter.drawImage(QPoint(0,0), imageRGB_);
}
painter.drawImage(QPoint(0,0), image_);
}
void EditDepthArea::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}
void floodfill(QImage & image, const cv::Mat & depthImage, int x, int y, float lastDepthValue, float error)
{
if(x>=0 && x<depthImage.cols &&
y>=0 && y<depthImage.rows)
{
float currentValue;
if(depthImage.type() == CV_32FC1)
{
currentValue = depthImage.at<float>(y, x);
}
else
{
currentValue = float(depthImage.at<unsigned short>(y, x))/1000.0f;
}
QRgb rgb = image.pixel(x,y);
if(qRed(rgb) == 0 && qGreen(rgb) == 0 == qBlue(rgb) == 0)
{
return;
}
if(currentValue == 0.0f)
{
return;
}
if(lastDepthValue>=0.0f && fabs(lastDepthValue - currentValue) > error*lastDepthValue)
{
return;
}
image.setPixel(x, y, 0);
floodfill(image, depthImage, x, y+1, currentValue, error);
floodfill(image, depthImage, x, y-1, currentValue, error);
floodfill(image, depthImage, x-1, y, currentValue, error);
floodfill(image, depthImage, x+1, y, currentValue, error);
}
}
void EditDepthArea::contextMenuEvent(QContextMenuEvent * e)
{
QAction * action = menu_->exec(e->globalPos());
if(action == showRGB_)
{
this->update();
}
else if(action == removeCluster_)
{
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint pixel;
pixel.setX((e->pos().x()-offsetX)/scale);
pixel.setY((e->pos().y()-offsetY)/scale);
if(pixel.x()>=0 && pixel.x() < originalImage_.cols &&
pixel.y()>=0 && pixel.y() < originalImage_.rows)
{
floodfill(image_, originalImage_, pixel.x(), pixel.y(), -1.0f, 0.02f);
}
modified_=true;
this->update();
}
else if(action == setPenWidth_)
{
bool ok;
int width = QInputDialog::getInt(this, tr("Set Pen Width"), tr("Width:"), penWidth(), 1, 99, 1, &ok);
if(ok)
{
myPenWidth_ = width;
}
}
else if(action == resetChanges_)
{
this->resetChanges();
}
}
void EditDepthArea::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image_);
painter.setPen(QPen(Qt::black, myPenWidth_, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.drawLine(lastPoint_, endPoint);
modified_ = true;
update();
lastPoint_ = endPoint;
}
}

View File

@@ -0,0 +1,88 @@
/*
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.
*/
#ifndef EDITDEPTHAREA_H
#define EDITDEPTHAREA_H
#include <QColor>
#include <QImage>
#include <QPoint>
#include <QWidget>
#include <opencv2/opencv.hpp>
class QMenu;
class QAction;
namespace rtabmap {
class EditDepthArea : public QWidget
{
Q_OBJECT
public:
EditDepthArea(QWidget *parent = 0);
void setImage(const cv::Mat & depth, const cv::Mat & rgb = cv::Mat());
cv::Mat getModifiedImage() const;
bool isModified() const {return modified_;}
void setPenWidth(int newWidth);
int penWidth() const { return myPenWidth_; }
public slots:
void resetChanges();
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent * e) override;
private:
void drawLineTo(const QPoint &endPoint);
void computeScaleOffsets(const QRect & targetRect, float & scale, float & offsetX, float & offsetY) const;
bool modified_;
bool scribbling_;
int myPenWidth_;
QImage imageRGB_;
QImage image_;
cv::Mat originalImage_;
QPoint lastPoint_;
QMenu * menu_;
QAction * showRGB_;
QAction * removeCluster_;
QAction * resetChanges_;
QAction * setPenWidth_;
};
}
#endif

View File

@@ -132,9 +132,10 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->checkBox_gainCompensation, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_gainRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_gainOverlap, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_gainAlpha, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_gainBeta, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_gainLinkedLocationsOnly, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_gainFull, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_textureBrightnessContrastRatioLow, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_textureBrightnessContrastRatioHigh, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_meshing, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_meshing, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor()));
@@ -143,6 +144,8 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->doubleSpinBox_gp3Mu, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_meshDecimationFactor, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_meshDecimationFactor, SIGNAL(valueChanged(double)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->spinBox_meshMaxPolygons, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_meshMaxPolygons, SIGNAL(valueChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_transferColorRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_cleanMesh, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_mesh_minClusterSize, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
@@ -176,6 +179,9 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
#ifdef DISABLE_VTK
_ui->doubleSpinBox_meshDecimationFactor->setEnabled(false);
_ui->spinBox_meshMaxPolygons->setEnabled(false);
_ui->label_meshDecimation->setEnabled(false);
_ui->label_meshMaxPolygons->setEnabled(false);
#endif
}
@@ -251,14 +257,14 @@ void ExportCloudsDialog::saveSettings(QSettings & settings, const QString & grou
settings.setValue("gain", _ui->checkBox_gainCompensation->isChecked());
settings.setValue("gain_radius", _ui->doubleSpinBox_gainRadius->value());
settings.setValue("gain_overlap", _ui->doubleSpinBox_gainOverlap->value());
settings.setValue("gain_alpha", _ui->doubleSpinBox_gainAlpha->value());
settings.setValue("gain_beta", _ui->doubleSpinBox_gainBeta->value());
settings.setValue("gain_linked_locations", _ui->checkBox_gainLinkedLocationsOnly->isChecked());
settings.setValue("gain_full", _ui->checkBox_gainFull->isChecked());
settings.setValue("mesh", _ui->checkBox_meshing->isChecked());
settings.setValue("mesh_radius", _ui->doubleSpinBox_gp3Radius->value());
settings.setValue("mesh_mu", _ui->doubleSpinBox_gp3Mu->value());
settings.setValue("mesh_decimation_factor", _ui->doubleSpinBox_meshDecimationFactor->value());
settings.setValue("mesh_max_polygons", _ui->spinBox_meshMaxPolygons->value());
settings.setValue("mesh_color_radius", _ui->doubleSpinBox_transferColorRadius->value());
settings.setValue("mesh_clean", _ui->checkBox_cleanMesh->isChecked());
settings.setValue("mesh_min_cluster_size", _ui->spinBox_mesh_minClusterSize->value());
@@ -273,6 +279,8 @@ void ExportCloudsDialog::saveSettings(QSettings & settings, const QString & grou
settings.setValue("mesh_textureCameraFiltering", _ui->checkBox_cameraFilter->isChecked());
settings.setValue("mesh_textureCameraFilteringRadius", _ui->doubleSpinBox_cameraFilterRadius->value());
settings.setValue("mesh_textureCameraFilteringAngle", _ui->doubleSpinBox_cameraFilterAngle->value());
settings.setValue("mesh_textureBrightnessConstrastRatioLow", _ui->spinBox_textureBrightnessContrastRatioLow->value());
settings.setValue("mesh_textureBrightnessConstrastRatioHigh", _ui->spinBox_textureBrightnessContrastRatioHigh->value());
settings.setValue("mesh_angle_tolerance", _ui->doubleSpinBox_mesh_angleTolerance->value());
@@ -344,14 +352,14 @@ void ExportCloudsDialog::loadSettings(QSettings & settings, const QString & grou
_ui->checkBox_gainCompensation->setChecked(settings.value("gain", _ui->checkBox_gainCompensation->isChecked()).toBool());
_ui->doubleSpinBox_gainRadius->setValue(settings.value("gain_radius", _ui->doubleSpinBox_gainRadius->value()).toDouble());
_ui->doubleSpinBox_gainOverlap->setValue(settings.value("gain_overlap", _ui->doubleSpinBox_gainOverlap->value()).toDouble());
_ui->doubleSpinBox_gainAlpha->setValue(settings.value("gain_alpha", _ui->doubleSpinBox_gainAlpha->value()).toDouble());
_ui->doubleSpinBox_gainBeta->setValue(settings.value("gain_beta", _ui->doubleSpinBox_gainBeta->value()).toDouble());
_ui->checkBox_gainLinkedLocationsOnly->setChecked(settings.value("gain_linked_locations", _ui->checkBox_gainLinkedLocationsOnly->isChecked()).toBool());
_ui->checkBox_gainFull->setChecked(settings.value("gain_full", _ui->checkBox_gainFull->isChecked()).toBool());
_ui->checkBox_meshing->setChecked(settings.value("mesh", _ui->checkBox_meshing->isChecked()).toBool());
_ui->doubleSpinBox_gp3Radius->setValue(settings.value("mesh_radius", _ui->doubleSpinBox_gp3Radius->value()).toDouble());
_ui->doubleSpinBox_gp3Mu->setValue(settings.value("mesh_mu", _ui->doubleSpinBox_gp3Mu->value()).toDouble());
_ui->doubleSpinBox_meshDecimationFactor->setValue(settings.value("mesh_decimation_factor",_ui->doubleSpinBox_meshDecimationFactor->value()).toDouble());
_ui->spinBox_meshMaxPolygons->setValue(settings.value("mesh_max_polygons",_ui->spinBox_meshMaxPolygons->value()).toDouble());
_ui->doubleSpinBox_transferColorRadius->setValue(settings.value("mesh_color_radius",_ui->doubleSpinBox_transferColorRadius->value()).toDouble());
_ui->checkBox_cleanMesh->setChecked(settings.value("mesh_clean",_ui->checkBox_cleanMesh->isChecked()).toBool());
_ui->spinBox_mesh_minClusterSize->setValue(settings.value("mesh_min_cluster_size", _ui->spinBox_mesh_minClusterSize->value()).toInt());
@@ -366,6 +374,8 @@ void ExportCloudsDialog::loadSettings(QSettings & settings, const QString & grou
_ui->checkBox_cameraFilter->setChecked(settings.value("mesh_textureCameraFiltering", _ui->checkBox_cameraFilter->isChecked()).toBool());
_ui->doubleSpinBox_cameraFilterRadius->setValue(settings.value("mesh_textureCameraFilteringRadius", _ui->doubleSpinBox_cameraFilterRadius->value()).toDouble());
_ui->doubleSpinBox_cameraFilterAngle->setValue(settings.value("mesh_textureCameraFilteringAngle", _ui->doubleSpinBox_cameraFilterAngle->value()).toDouble());
_ui->spinBox_textureBrightnessContrastRatioLow->setValue(settings.value("mesh_textureBrightnessConstrastRatioLow", _ui->spinBox_textureBrightnessContrastRatioLow->value()).toDouble());
_ui->spinBox_textureBrightnessContrastRatioHigh->setValue(settings.value("mesh_textureBrightnessConstrastRatioHigh", _ui->spinBox_textureBrightnessContrastRatioHigh->value()).toDouble());
_ui->doubleSpinBox_mesh_angleTolerance->setValue(settings.value("mesh_angle_tolerance", _ui->doubleSpinBox_mesh_angleTolerance->value()).toDouble());
_ui->checkBox_mesh_quad->setChecked(settings.value("mesh_quad", _ui->checkBox_mesh_quad->isChecked()).toBool());
@@ -433,15 +443,15 @@ void ExportCloudsDialog::restoreDefaults()
_ui->checkBox_gainCompensation->setChecked(false);
_ui->doubleSpinBox_gainRadius->setValue(0.02);
_ui->doubleSpinBox_gainOverlap->setValue(0.05);
_ui->doubleSpinBox_gainAlpha->setValue(0.01);
_ui->doubleSpinBox_gainOverlap->setValue(0.0);
_ui->doubleSpinBox_gainBeta->setValue(10);
_ui->checkBox_gainLinkedLocationsOnly->setChecked(true);
_ui->checkBox_gainFull->setChecked(false);
_ui->checkBox_meshing->setChecked(false);
_ui->doubleSpinBox_gp3Radius->setValue(0.2);
_ui->doubleSpinBox_gp3Mu->setValue(2.5);
_ui->doubleSpinBox_meshDecimationFactor->setValue(0.0);
_ui->spinBox_meshMaxPolygons->setValue(0);
_ui->doubleSpinBox_transferColorRadius->setValue(0.025);
_ui->checkBox_cleanMesh->setChecked(true);
_ui->spinBox_mesh_minClusterSize->setValue(0);
@@ -456,6 +466,8 @@ void ExportCloudsDialog::restoreDefaults()
_ui->checkBox_cameraFilter->setChecked(false);
_ui->doubleSpinBox_cameraFilterRadius->setValue(0.1);
_ui->doubleSpinBox_cameraFilterAngle->setValue(30);
_ui->spinBox_textureBrightnessContrastRatioLow->setValue(0);
_ui->spinBox_textureBrightnessContrastRatioHigh->setValue(0);
_ui->doubleSpinBox_mesh_angleTolerance->setValue(15.0);
_ui->checkBox_mesh_quad->setChecked(false);
@@ -482,8 +494,12 @@ void ExportCloudsDialog::updateReconstructionFlavor()
_ui->checkBox_smoothing->setVisible(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->checkBox_smoothing->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_denseReconstruction->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
#ifndef DISABLE_VTK
_ui->doubleSpinBox_meshDecimationFactor->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_meshDecimation->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->spinBox_meshMaxPolygons->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_meshMaxPolygons->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
#endif
_ui->groupBox_organized->setVisible(_ui->comboBox_pipeline->currentIndex() == 0);
_ui->groupBox_regenerate->setVisible(_ui->checkBox_regenerate->isChecked());
@@ -511,6 +527,7 @@ void ExportCloudsDialog::updateReconstructionFlavor()
_ui->checkBox_poisson_outputPolygons->setDisabled(
_ui->checkBox_binary->isEnabled() ||
_ui->doubleSpinBox_meshDecimationFactor->value()!=0.0 ||
_ui->spinBox_meshMaxPolygons->value()!=0 ||
_ui->checkBox_textureMapping->isChecked());
_ui->checkBox_cleanMesh->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
@@ -952,9 +969,9 @@ bool ExportCloudsDialog::getExportedClouds(
if(_ui->checkBox_gainCompensation->isChecked() && clouds.size() > 1)
{
UASSERT(_compensator == 0);
_compensator = new GainCompensator(_ui->doubleSpinBox_gainRadius->value(), _ui->doubleSpinBox_gainOverlap->value(), _ui->doubleSpinBox_gainAlpha->value(), _ui->doubleSpinBox_gainBeta->value());
_compensator = new GainCompensator(_ui->doubleSpinBox_gainRadius->value(), _ui->doubleSpinBox_gainOverlap->value(), 0.01, _ui->doubleSpinBox_gainBeta->value());
if(!_ui->checkBox_gainLinkedLocationsOnly->isChecked())
if(_ui->checkBox_gainFull->isChecked())
{
_progressDialog->appendText(tr("Full gain compensation of %1 clouds...").arg(clouds.size()));
}
@@ -966,7 +983,7 @@ bool ExportCloudsDialog::getExportedClouds(
uSleep(100);
QApplication::processEvents();
if(!_ui->checkBox_gainLinkedLocationsOnly->isChecked())
if(_ui->checkBox_gainFull->isChecked())
{
std::multimap<int, Link> allLinks;
for(std::map<int, std::pair<pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr, pcl::IndicesPtr> >::const_iterator iter=clouds.begin(); iter!=clouds.end(); ++iter)
@@ -1407,85 +1424,13 @@ bool ExportCloudsDialog::getExportedClouds(
pcl::toPCLPointCloud2(*mergedClouds, mesh->cloud);
mesh->polygons = mergedPolygons;
if(_ui->doubleSpinBox_meshDecimationFactor->isEnabled() &&
_ui->doubleSpinBox_meshDecimationFactor->value() > 0.0)
{
unsigned int count = mesh->polygons.size();
_progressDialog->appendText(tr("Mesh decimation (factor=%1) from %2 polygons...").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count));
QApplication::processEvents();
mesh = util3d::meshDecimation(mesh, (float)_ui->doubleSpinBox_meshDecimationFactor->value());
_progressDialog->appendText(tr("Mesh decimated (factor=%1) from %2 to %3 polygons").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count).arg(mesh->polygons.size()));
if(count < mesh->polygons.size())
{
_progressDialog->appendText(tr("Decimated mesh has more polygons than before!"), Qt::darkYellow);
_progressDialog->setAutoClose(false);
}
QApplication::processEvents();
if(_ui->doubleSpinBox_transferColorRadius->value() >= 0.0 &&
(!_ui->checkBox_textureMapping->isEnabled() || !_ui->checkBox_textureMapping->isChecked()))
{
_progressDialog->appendText(tr("Transferring color from point cloud to mesh..."));
QApplication::processEvents();
// transfer color from point cloud to mesh
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true));
tree->setInputCloud(mergedClouds);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr coloredCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::fromPCLPointCloud2(mesh->cloud, *coloredCloud);
for(unsigned int i=0; i<coloredCloud->size(); ++i)
{
std::vector<int> kIndices;
std::vector<float> kDistances;
pcl::PointXYZRGBNormal pt;
pt.x = coloredCloud->at(i).x;
pt.y = coloredCloud->at(i).y;
pt.z = coloredCloud->at(i).z;
if(_ui->doubleSpinBox_transferColorRadius->value() > 0.0)
{
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
}
else
{
tree->nearestKSearch(pt, 1, kIndices, kDistances);
}
if(kIndices.size())
{
//compute average color
int r=0;
int g=0;
int b=0;
int a=0;
for(unsigned int j=0; j<kIndices.size(); ++j)
{
r+=(int)mergedClouds->at(kIndices[j]).r;
g+=(int)mergedClouds->at(kIndices[j]).g;
b+=(int)mergedClouds->at(kIndices[j]).b;
a+=(int)mergedClouds->at(kIndices[j]).a;
}
coloredCloud->at(i).r = r/kIndices.size();
coloredCloud->at(i).g = g/kIndices.size();
coloredCloud->at(i).b = b/kIndices.size();
coloredCloud->at(i).a = a/kIndices.size();
}
else
{
//white
coloredCloud->at(i).r = coloredCloud->at(i).g = coloredCloud->at(i).b = 255;
}
}
pcl::toPCLPointCloud2(*coloredCloud, mesh->cloud);
}
}
meshes.insert(std::make_pair(0, mesh));
_progressDialog->incrementStep();
QApplication::processEvents();
}
}
else
else // dense pipeline
{
if(_ui->comboBox_meshingApproach->currentIndex() == 0)
{
@@ -1534,101 +1479,173 @@ bool ExportCloudsDialog::getExportedClouds(
_progressDialog->appendText(tr("Mesh %1 created with %2 polygons (%3/%4).").arg(iter->first).arg(mesh->polygons.size()).arg(++i).arg(clouds.size()));
QApplication::processEvents();
if(_ui->doubleSpinBox_meshDecimationFactor->isEnabled() &&
_ui->doubleSpinBox_meshDecimationFactor->value() > 0.0)
if(mesh->polygons.size()>0)
{
unsigned int count = mesh->polygons.size();
_progressDialog->appendText(tr("Mesh decimation (factor=%1) from %2 polygons...").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
mesh = util3d::meshDecimation(mesh, (float)_ui->doubleSpinBox_meshDecimationFactor->value());
_progressDialog->appendText(tr("Mesh decimated (factor=%1) from %2 to %3 polygons").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count).arg(mesh->polygons.size()));
if(count < mesh->polygons.size())
double meshDecimationFactor = 0.0;
if(_ui->doubleSpinBox_meshDecimationFactor->isEnabled() &&
_ui->doubleSpinBox_meshDecimationFactor->value() > 0.0)
{
_progressDialog->appendText(tr("Decimated mesh %1 has more polygons than before!").arg(iter->first), Qt::darkYellow);
_progressDialog->setAutoClose(false);
meshDecimationFactor = _ui->doubleSpinBox_meshDecimationFactor->value();
}
QApplication::processEvents();
lostColors = true;
}
if(lostColors &&
_ui->doubleSpinBox_transferColorRadius->value() >= 0.0 &&
(!_ui->checkBox_textureMapping->isEnabled() || !_ui->checkBox_textureMapping->isChecked()))
{
_progressDialog->appendText(tr("Transferring color from point cloud to mesh..."));
QApplication::processEvents();
// transfer color from point cloud to mesh
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true));
tree->setInputCloud(iter->second);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr coloredCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::fromPCLPointCloud2(mesh->cloud, *coloredCloud);
std::vector<bool> coloredPts(coloredCloud->size());
for(unsigned int i=0; i<coloredCloud->size(); ++i)
if(_ui->spinBox_meshMaxPolygons->isEnabled() &&
_ui->spinBox_meshMaxPolygons->value() > 0)
{
std::vector<int> kIndices;
std::vector<float> kDistances;
pcl::PointXYZRGBNormal pt;
pt.x = coloredCloud->at(i).x;
pt.y = coloredCloud->at(i).y;
pt.z = coloredCloud->at(i).z;
if(_ui->doubleSpinBox_transferColorRadius->value() > 0.0)
double factor = 1.0-double(_ui->spinBox_meshMaxPolygons->value())/double(mesh->polygons.size());
if(factor > meshDecimationFactor)
{
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
meshDecimationFactor = factor;
}
else
}
if(meshDecimationFactor > 0.0)
{
unsigned int count = mesh->polygons.size();
_progressDialog->appendText(tr("Mesh decimation (factor=%1) from %2 polygons...").arg(meshDecimationFactor).arg(count));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
mesh = util3d::meshDecimation(mesh, (float)meshDecimationFactor);
_progressDialog->appendText(tr("Mesh decimated (factor=%1) from %2 to %3 polygons").arg(meshDecimationFactor).arg(count).arg(mesh->polygons.size()));
if(count < mesh->polygons.size())
{
tree->nearestKSearch(pt, 1, kIndices, kDistances);
_progressDialog->appendText(tr("Decimated mesh %1 has more polygons than before!").arg(iter->first), Qt::darkYellow);
_progressDialog->setAutoClose(false);
}
if(kIndices.size())
QApplication::processEvents();
lostColors = true;
}
if(lostColors &&
_ui->doubleSpinBox_transferColorRadius->value() >= 0.0 &&
(!_ui->checkBox_textureMapping->isEnabled() || !_ui->checkBox_textureMapping->isChecked()))
{
_progressDialog->appendText(tr("Transferring color from point cloud to mesh..."));
QApplication::processEvents();
// transfer color from point cloud to mesh
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true));
tree->setInputCloud(iter->second);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr coloredCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::fromPCLPointCloud2(mesh->cloud, *coloredCloud);
std::vector<bool> coloredPts(coloredCloud->size());
for(unsigned int i=0; i<coloredCloud->size(); ++i)
{
//compute average color
int r=0;
int g=0;
int b=0;
int a=0;
for(unsigned int j=0; j<kIndices.size(); ++j)
std::vector<int> kIndices;
std::vector<float> kDistances;
pcl::PointXYZRGBNormal pt;
pt.x = coloredCloud->at(i).x;
pt.y = coloredCloud->at(i).y;
pt.z = coloredCloud->at(i).z;
if(_ui->doubleSpinBox_transferColorRadius->value() > 0.0)
{
r+=(int)iter->second->at(kIndices[j]).r;
g+=(int)iter->second->at(kIndices[j]).g;
b+=(int)iter->second->at(kIndices[j]).b;
a+=(int)iter->second->at(kIndices[j]).a;
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
}
else
{
tree->nearestKSearch(pt, 1, kIndices, kDistances);
}
if(kIndices.size())
{
//compute average color
int r=0;
int g=0;
int b=0;
int a=0;
for(unsigned int j=0; j<kIndices.size(); ++j)
{
r+=(int)iter->second->at(kIndices[j]).r;
g+=(int)iter->second->at(kIndices[j]).g;
b+=(int)iter->second->at(kIndices[j]).b;
a+=(int)iter->second->at(kIndices[j]).a;
}
coloredCloud->at(i).r = r/kIndices.size();
coloredCloud->at(i).g = g/kIndices.size();
coloredCloud->at(i).b = b/kIndices.size();
coloredCloud->at(i).a = a/kIndices.size();
coloredPts.at(i) = true;
}
else
{
//white
coloredCloud->at(i).r = coloredCloud->at(i).g = coloredCloud->at(i).b = 255;
coloredPts.at(i) = false;
}
coloredCloud->at(i).r = r/kIndices.size();
coloredCloud->at(i).g = g/kIndices.size();
coloredCloud->at(i).b = b/kIndices.size();
coloredCloud->at(i).a = a/kIndices.size();
coloredPts.at(i) = true;
}
else
pcl::toPCLPointCloud2(*coloredCloud, mesh->cloud);
// remove polygons with no color
if(_ui->checkBox_cleanMesh->isChecked())
{
//white
coloredCloud->at(i).r = coloredCloud->at(i).g = coloredCloud->at(i).b = 255;
coloredPts.at(i) = false;
std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
int oi=0;
for(unsigned int i=0; i<mesh->polygons.size(); ++i)
{
bool coloredPolygon = true;
for(unsigned int j=0; j<mesh->polygons[i].vertices.size(); ++j)
{
if(!coloredPts.at(mesh->polygons[i].vertices[j]))
{
coloredPolygon = false;
break;
}
}
if(coloredPolygon)
{
filteredPolygons[oi++] = mesh->polygons[i];
}
}
filteredPolygons.resize(oi);
mesh->polygons = filteredPolygons;
}
}
pcl::toPCLPointCloud2(*coloredCloud, mesh->cloud);
// remove polygons with no color
if(_ui->checkBox_cleanMesh->isChecked())
else if(lostColors &&
_ui->doubleSpinBox_transferColorRadius->value() > 0.0 &&
_ui->checkBox_cleanMesh->isChecked() &&
(_ui->checkBox_textureMapping->isEnabled() && _ui->checkBox_textureMapping->isChecked()))
{
_progressDialog->appendText(tr("Removing polygons too far from the cloud..."));
QApplication::processEvents();
// transfer color from point cloud to mesh
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true));
tree->setInputCloud(iter->second);
pcl::PointCloud<pcl::PointXYZ>::Ptr optimizedCloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(mesh->cloud, *optimizedCloud);
std::vector<bool> closePts(optimizedCloud->size());
for(unsigned int i=0; i<optimizedCloud->size(); ++i)
{
std::vector<int> kIndices;
std::vector<float> kDistances;
pcl::PointXYZRGBNormal pt;
pt.x = optimizedCloud->at(i).x;
pt.y = optimizedCloud->at(i).y;
pt.z = optimizedCloud->at(i).z;
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
if(kIndices.size())
{
closePts.at(i) = true;
}
else
{
closePts.at(i) = false;
}
}
// remove far polygons
std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
int oi=0;
for(unsigned int i=0; i<mesh->polygons.size(); ++i)
{
bool coloredPolygon = true;
bool keepPolygon = true;
for(unsigned int j=0; j<mesh->polygons[i].vertices.size(); ++j)
{
if(!coloredPts.at(mesh->polygons[i].vertices[j]))
if(!closePts.at(mesh->polygons[i].vertices[j]))
{
coloredPolygon = false;
keepPolygon = false;
break;
}
}
if(coloredPolygon)
if(keepPolygon)
{
filteredPolygons[oi++] = mesh->polygons[i];
}
@@ -1636,127 +1653,76 @@ bool ExportCloudsDialog::getExportedClouds(
filteredPolygons.resize(oi);
mesh->polygons = filteredPolygons;
}
}
else if(lostColors &&
_ui->doubleSpinBox_transferColorRadius->value() > 0.0 &&
_ui->checkBox_cleanMesh->isChecked() &&
(_ui->checkBox_textureMapping->isEnabled() && _ui->checkBox_textureMapping->isChecked()))
{
_progressDialog->appendText(tr("Removing polygons too far from the cloud..."));
QApplication::processEvents();
// transfer color from point cloud to mesh
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true));
tree->setInputCloud(iter->second);
pcl::PointCloud<pcl::PointXYZ>::Ptr optimizedCloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(mesh->cloud, *optimizedCloud);
std::vector<bool> closePts(optimizedCloud->size());
for(unsigned int i=0; i<optimizedCloud->size(); ++i)
if(_ui->spinBox_mesh_minClusterSize->value() &&
!(_ui->checkBox_textureMapping->isEnabled() &&
_ui->checkBox_textureMapping->isChecked() &&
_ui->checkBox_cleanMesh->isChecked()))
{
std::vector<int> kIndices;
std::vector<float> kDistances;
pcl::PointXYZRGBNormal pt;
pt.x = optimizedCloud->at(i).x;
pt.y = optimizedCloud->at(i).y;
pt.z = optimizedCloud->at(i).z;
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
if(kIndices.size())
_progressDialog->appendText(tr("Filter small polygon clusters..."));
QApplication::processEvents();
// filter polygons
std::vector<std::set<int> > neighbors;
std::vector<std::set<int> > vertexToPolygons;
util3d::createPolygonIndexes(mesh->polygons,
mesh->cloud.height*mesh->cloud.width,
neighbors,
vertexToPolygons);
std::list<std::list<int> > clusters = util3d::clusterPolygons(
neighbors,
_ui->spinBox_mesh_minClusterSize->value()<0?0:_ui->spinBox_mesh_minClusterSize->value());
std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
if(_ui->spinBox_mesh_minClusterSize->value() < 0)
{
closePts.at(i) = true;
// only keep the biggest cluster
std::list<std::list<int> >::iterator biggestClusterIndex = clusters.end();
unsigned int biggestClusterSize = 0;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
if(iter->size() > biggestClusterSize)
{
biggestClusterIndex = iter;
biggestClusterSize = iter->size();
}
}
if(biggestClusterIndex != clusters.end())
{
int oi=0;
for(std::list<int>::iterator jter=biggestClusterIndex->begin(); jter!=biggestClusterIndex->end(); ++jter)
{
filteredPolygons[oi++] = mesh->polygons.at(*jter);
}
filteredPolygons.resize(oi);
}
}
else
{
closePts.at(i) = false;
}
}
// remove far polygons
std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
int oi=0;
for(unsigned int i=0; i<mesh->polygons.size(); ++i)
{
bool keepPolygon = true;
for(unsigned int j=0; j<mesh->polygons[i].vertices.size(); ++j)
{
if(!closePts.at(mesh->polygons[i].vertices[j]))
{
keepPolygon = false;
break;
}
}
if(keepPolygon)
{
filteredPolygons[oi++] = mesh->polygons[i];
}
}
filteredPolygons.resize(oi);
mesh->polygons = filteredPolygons;
}
if(_ui->spinBox_mesh_minClusterSize->value() &&
!(_ui->checkBox_textureMapping->isEnabled() &&
_ui->checkBox_textureMapping->isChecked() &&
_ui->checkBox_cleanMesh->isChecked()))
{
_progressDialog->appendText(tr("Filter small polygon clusters..."));
QApplication::processEvents();
// filter polygons
std::vector<std::set<int> > neighbors;
std::vector<std::set<int> > vertexToPolygons;
util3d::createPolygonIndexes(mesh->polygons,
mesh->cloud.height*mesh->cloud.width,
neighbors,
vertexToPolygons);
std::list<std::list<int> > clusters = util3d::clusterPolygons(
neighbors,
_ui->spinBox_mesh_minClusterSize->value()<0?0:_ui->spinBox_mesh_minClusterSize->value());
std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
if(_ui->spinBox_mesh_minClusterSize->value() < 0)
{
// only keep the biggest cluster
std::list<std::list<int> >::iterator biggestClusterIndex = clusters.end();
unsigned int biggestClusterSize = 0;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
if(iter->size() > biggestClusterSize)
{
biggestClusterIndex = iter;
biggestClusterSize = iter->size();
}
}
if(biggestClusterIndex != clusters.end())
{
int oi=0;
for(std::list<int>::iterator jter=biggestClusterIndex->begin(); jter!=biggestClusterIndex->end(); ++jter)
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
filteredPolygons[oi++] = mesh->polygons.at(*jter);
for(std::list<int>::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
{
filteredPolygons[oi++] = mesh->polygons.at(*jter);
}
}
filteredPolygons.resize(oi);
}
}
else
{
int oi=0;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
for(std::list<int>::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
{
filteredPolygons[oi++] = mesh->polygons.at(*jter);
}
}
filteredPolygons.resize(oi);
int before = (int)mesh->polygons.size();
mesh->polygons = filteredPolygons;
_progressDialog->appendText(tr("Filtered %1 polygons.").arg(before-(int)mesh->polygons.size()));
}
int before = (int)mesh->polygons.size();
mesh->polygons = filteredPolygons;
_progressDialog->appendText(tr("Filtered %1 polygons.").arg(before-(int)mesh->polygons.size()));
QApplication::processEvents();
meshes.insert(std::make_pair(iter->first, mesh));
}
else
{
_progressDialog->appendText(tr("No polygons created for cloud %d!").arg(iter->first), Qt::darkYellow);
_progressDialog->setAutoClose(false);
}
meshes.insert(std::make_pair(iter->first, mesh));
_progressDialog->incrementStep(_ui->checkBox_assemble->isChecked()?poses.size():1);
QApplication::processEvents();
@@ -2754,9 +2720,11 @@ cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<in
int cols = float(textureSize)/(scale*imageSize.width);
globalTexture = cv::Mat(textureSize, textureSize, imageType, cv::Scalar::all(255));
cv::Mat globalTextureMask = cv::Mat(textureSize, textureSize, CV_8UC1, cv::Scalar::all(0));
// make a blank texture
cv::Mat emptyImage(int(imageSize.height*scale), int(imageSize.width*scale), imageType, cv::Scalar::all(255));
cv::Mat emptyImageMask(int(imageSize.height*scale), int(imageSize.width*scale), CV_8UC1, cv::Scalar::all(255));
int oi=0;
for(int t=0; t<(int)textures.size(); ++t)
{
@@ -2798,6 +2766,7 @@ cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<in
}
UASSERT(resizedImage.type() == globalTexture.type());
resizedImage.copyTo(globalTexture(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
emptyImageMask.copyTo(globalTextureMask(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
}
else
{
@@ -2806,6 +2775,10 @@ cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<in
++oi;
}
}
if(_ui->spinBox_textureBrightnessContrastRatioLow->value() > 0 || _ui->spinBox_textureBrightnessContrastRatioHigh->value() > 0)
{
globalTexture = util2d::brightnessAndContrastAuto(globalTexture, globalTextureMask, (float)_ui->spinBox_textureBrightnessContrastRatioLow->value(), (float)_ui->spinBox_textureBrightnessContrastRatioHigh->value());
}
}
}
}

View File

@@ -4164,6 +4164,21 @@ void MainWindow::editDatabase()
QString path = QFileDialog::getOpenFileName(this, tr("Edit database..."), _preferencesDialog->getWorkingDirectory(), tr("RTAB-Map database files (*.db)"));
if(!path.isEmpty())
{
{
// copy database settings to tmp ini file
QSettings dbSettingsIn(_preferencesDialog->getIniFilePath(), QSettings::IniFormat);
QSettings dbSettingsOut(_preferencesDialog->getTmpIniFilePath(), QSettings::IniFormat);
dbSettingsIn.beginGroup("DatabaseViewer");
dbSettingsOut.beginGroup("DatabaseViewer");
QStringList keys = dbSettingsIn.childKeys();
for(QStringList::iterator iter = keys.begin(); iter!=keys.end(); ++iter)
{
dbSettingsOut.setValue(*iter, dbSettingsIn.value(*iter));
}
dbSettingsIn.endGroup();
dbSettingsOut.endGroup();
}
DatabaseViewer * viewer = new DatabaseViewer(_preferencesDialog->getTmpIniFilePath(), this);
viewer->setWindowModality(Qt::WindowModal);
viewer->setAttribute(Qt::WA_DeleteOnClose, true);
@@ -4718,7 +4733,11 @@ void MainWindow::postProcessing()
ParametersMap parameters = _preferencesDialog->getAllParameters();
Optimizer * optimizer = Optimizer::create(parameters);
bool optimizeFromGraphEnd = Parameters::defaultRGBDOptimizeFromGraphEnd();
float optimizeMaxError = Parameters::defaultRGBDOptimizeMaxError();
int optimizeIterations = Parameters::defaultOptimizerIterations();
Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), optimizeFromGraphEnd);
Parameters::parse(parameters, Parameters::kRGBDOptimizeMaxError(), optimizeMaxError);
Parameters::parse(parameters, Parameters::kOptimizerIterations(), optimizeIterations);
bool warn = false;
int loopClosuresAdded = 0;
@@ -4801,9 +4820,6 @@ void MainWindow::postProcessing()
delete registration;
if(!transform.isNull())
{
UINFO("Added new loop closure between %d and %d.", from, to);
addedLinks.insert(from);
addedLinks.insert(to);
if(!transform.isIdentity())
{
// normalize variance
@@ -4812,10 +4828,124 @@ void MainWindow::postProcessing()
info.varianceLin = info.varianceLin>0.0f?info.varianceLin:0.0001f; // epsilon if exact transform
info.varianceAng = info.varianceAng>0.0f?info.varianceAng:0.0001f; // epsilon if exact transform
}
_currentLinksMap.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, transform, info.varianceAng, info.varianceLin)));
++loopClosuresAdded;
_initProgressDialog->appendText(tr("Detected loop closure %1->%2! (%3/%4)").arg(from).arg(to).arg(i+1).arg(clusters.size()));
QApplication::processEvents();
//optimize the graph to see if the new constraint is globally valid
bool updateConstraint = true;
if(optimizeMaxError > 0.0f && optimizeIterations > 0)
{
int fromId = from;
int mapId = _currentMapIds.at(from);
// use first node of the map containing from
for(std::map<int, int>::iterator iter=_currentMapIds.begin(); iter!=_currentMapIds.end(); ++iter)
{
if(iter->second == mapId && odomPoses.find(iter->first)!=odomPoses.end())
{
fromId = iter->first;
break;
}
}
std::multimap<int, Link> linksIn = _currentLinksMap;
linksIn.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, transform, info.varianceAng, info.varianceLin)));
const Link * maxLinearLink = 0;
const Link * maxAngularLink = 0;
float maxLinearError = 0.0f;
float maxAngularError = 0.0f;
std::map<int, Transform> poses;
std::multimap<int, Link> links;
UASSERT(odomPoses.find(fromId) != odomPoses.end());
UASSERT_MSG(odomPoses.find(from) != odomPoses.end(), uFormat("id=%d poses=%d links=%d", from, (int)poses.size(), (int)links.size()).c_str());
UASSERT_MSG(odomPoses.find(to) != odomPoses.end(), uFormat("id=%d poses=%d links=%d", to, (int)poses.size(), (int)links.size()).c_str());
optimizer->getConnectedGraph(fromId, odomPoses, linksIn, poses, links);
UASSERT(poses.find(fromId) != poses.end());
UASSERT_MSG(poses.find(from) != poses.end(), uFormat("id=%d poses=%d links=%d", from, (int)poses.size(), (int)links.size()).c_str());
UASSERT_MSG(poses.find(to) != poses.end(), uFormat("id=%d poses=%d links=%d", to, (int)poses.size(), (int)links.size()).c_str());
UASSERT(graph::findLink(links, from, to) != links.end());
poses = optimizer->optimize(fromId, poses, links);
std::string msg;
if(poses.size())
{
for(std::multimap<int, Link>::iterator iter=links.begin(); iter!=links.end(); ++iter)
{
// ignore links with high variance
if(iter->second.transVariance() <= 1.0)
{
UASSERT(poses.find(iter->second.from())!=poses.end());
UASSERT(poses.find(iter->second.to())!=poses.end());
Transform t1 = poses.at(iter->second.from());
Transform t2 = poses.at(iter->second.to());
UASSERT(!t1.isNull() && !t2.isNull());
Transform t = t1.inverse()*t2;
float linearError = uMax3(
fabs(iter->second.transform().x() - t.x()),
fabs(iter->second.transform().y() - t.y()),
fabs(iter->second.transform().z() - t.z()));
Eigen::Vector3f vA = t1.toEigen3f().rotation()*Eigen::Vector3f(1,0,0);
Eigen::Vector3f vB = t2.toEigen3f().rotation()*Eigen::Vector3f(1,0,0);
float angularError = pcl::getAngle3D(Eigen::Vector4f(vA[0], vA[1], vA[2], 0), Eigen::Vector4f(vB[0], vB[1], vB[2], 0));
if(linearError > maxLinearError)
{
maxLinearError = linearError;
maxLinearLink = &iter->second;
}
if(angularError > maxAngularError)
{
maxAngularError = angularError;
maxAngularLink = &iter->second;
}
}
}
if(maxLinearLink)
{
UINFO("Max optimization linear error = %f m (link %d->%d)", maxLinearError, maxLinearLink->from(), maxLinearLink->to());
}
if(maxAngularLink)
{
UINFO("Max optimization angular error = %f deg (link %d->%d)", maxAngularError*180.0f/M_PI, maxAngularLink->from(), maxAngularLink->to());
}
if(maxLinearError > optimizeMaxError)
{
msg = uFormat("Rejecting edge %d->%d because "
"graph error is too large after optimization (%f m for edge %d->%d, %f deg for edge %d->%d). "
"\"%s\" is %f m.",
from,
to,
maxLinearError,
maxLinearLink->from(),
maxLinearLink->to(),
maxAngularError*180.0f/M_PI,
maxAngularLink?maxAngularLink->from():0,
maxAngularLink?maxAngularLink->to():0,
Parameters::kRGBDOptimizeMaxError().c_str(),
optimizeMaxError);
}
}
else
{
msg = uFormat("Rejecting edge %d->%d because graph optimization has failed!",
from,
to);
}
if(!msg.empty())
{
UWARN("%s", msg.c_str());
_initProgressDialog->appendText(tr("%s").arg(msg.c_str()));
QApplication::processEvents();
updateConstraint = false;
}
}
if(updateConstraint)
{
UINFO("Added new loop closure between %d and %d.", from, to);
addedLinks.insert(from);
addedLinks.insert(to);
_currentLinksMap.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, transform, info.varianceAng, info.varianceLin)));
++loopClosuresAdded;
_initProgressDialog->appendText(tr("Detected loop closure %1->%2! (%3/%4)").arg(from).arg(to).arg(i+1).arg(clusters.size()));
QApplication::processEvents();
}
}
}
}
@@ -4823,8 +4953,7 @@ void MainWindow::postProcessing()
}
_initProgressDialog->incrementStep();
}
_initProgressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!")
.arg(n+1).arg(detectLoopClosureIterations).arg(addedLinks.size()/2));
_initProgressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!").arg(n+1).arg(detectLoopClosureIterations).arg(addedLinks.size()/2));
if(addedLinks.size() == 0)
{
break;

View File

@@ -2571,6 +2571,13 @@ void PreferencesDialog::saveWindowGeometry(const QWidget * window)
settings.setValue("geometry", window->saveGeometry());
settings.endGroup(); // "windowName"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(window->objectName());
settingsTmp.setValue("geometry", window->saveGeometry());
settingsTmp.endGroup(); // "windowName"
settingsTmp.endGroup(); // rtabmap
}
}
@@ -2589,6 +2596,13 @@ void PreferencesDialog::loadWindowGeometry(QWidget * window)
}
settings.endGroup(); // "windowName"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(window->objectName());
settingsTmp.setValue("geometry", window->saveGeometry());
settingsTmp.endGroup(); // "windowName"
settingsTmp.endGroup(); // rtabmap
}
}
@@ -2606,6 +2620,15 @@ void PreferencesDialog::saveMainWindowState(const QMainWindow * mainWindow)
settings.setValue("status_bar", mainWindow->statusBar()->isVisible());
settings.endGroup(); // "MainWindow"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(mainWindow->objectName());
settingsTmp.setValue("state", mainWindow->saveState());
settingsTmp.setValue("maximized", mainWindow->isMaximized());
settingsTmp.setValue("status_bar", mainWindow->statusBar()->isVisible());
settingsTmp.endGroup(); // "MainWindow"
settingsTmp.endGroup(); // rtabmap
}
}
@@ -2629,6 +2652,15 @@ void PreferencesDialog::loadMainWindowState(QMainWindow * mainWindow, bool & ma
mainWindow->statusBar()->setVisible(statusBarShown);
settings.endGroup(); // "MainWindow"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(mainWindow->objectName());
settingsTmp.setValue("state", mainWindow->saveState());
settingsTmp.setValue("maximized", maximized);
settingsTmp.setValue("status_bar", statusBarShown);
settingsTmp.endGroup(); // "MainWindow"
settingsTmp.endGroup(); // rtabmap
}
}
@@ -2640,6 +2672,10 @@ void PreferencesDialog::saveWidgetState(const QWidget * widget)
settings.beginGroup("Gui");
settings.beginGroup(widget->objectName());
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(widget->objectName());
const CloudViewer * cloudViewer = qobject_cast<const CloudViewer*>(widget);
const ImageView * imageView = qobject_cast<const ImageView*>(widget);
const ExportCloudsDialog * exportCloudsDialog = qobject_cast<const ExportCloudsDialog*>(widget);
@@ -2652,34 +2688,42 @@ void PreferencesDialog::saveWidgetState(const QWidget * widget)
if(cloudViewer)
{
cloudViewer->saveSettings(settings);
cloudViewer->saveSettings(settingsTmp);
}
else if(imageView)
{
imageView->saveSettings(settings);
imageView->saveSettings(settingsTmp);
}
else if(exportCloudsDialog)
{
exportCloudsDialog->saveSettings(settings);
exportCloudsDialog->saveSettings(settingsTmp);
}
else if(exportScansDialog)
{
exportScansDialog->saveSettings(settings);
exportScansDialog->saveSettings(settingsTmp);
}
else if(postProcessingDialog)
{
postProcessingDialog->saveSettings(settings);
postProcessingDialog->saveSettings(settingsTmp);
}
else if(graphViewer)
{
graphViewer->saveSettings(settings);
graphViewer->saveSettings(settingsTmp);
}
else if(calibrationDialog)
{
calibrationDialog->saveSettings(settings);
calibrationDialog->saveSettings(settingsTmp);
}
else if(depthCalibrationDialog)
{
depthCalibrationDialog->saveSettings(settings);
depthCalibrationDialog->saveSettings(settingsTmp);
}
else
{
@@ -2688,6 +2732,8 @@ void PreferencesDialog::saveWidgetState(const QWidget * widget)
settings.endGroup(); // "name"
settings.endGroup(); // Gui
settingsTmp.endGroup();
settingsTmp.endGroup();
}
}
@@ -2700,6 +2746,10 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
settings.beginGroup("Gui");
settings.beginGroup(widget->objectName());
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(widget->objectName());
CloudViewer * cloudViewer = qobject_cast<CloudViewer*>(widget);
ImageView * imageView = qobject_cast<ImageView*>(widget);
ExportCloudsDialog * exportCloudsDialog = qobject_cast<ExportCloudsDialog*>(widget);
@@ -2712,34 +2762,42 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
if(cloudViewer)
{
cloudViewer->loadSettings(settings);
cloudViewer->saveSettings(settingsTmp);
}
else if(imageView)
{
imageView->loadSettings(settings);
imageView->saveSettings(settingsTmp);
}
else if(exportCloudsDialog)
{
exportCloudsDialog->loadSettings(settings);
exportCloudsDialog->saveSettings(settingsTmp);
}
else if(exportScansDialog)
{
exportScansDialog->loadSettings(settings);
exportScansDialog->saveSettings(settingsTmp);
}
else if(postProcessingDialog)
{
postProcessingDialog->loadSettings(settings);
postProcessingDialog->saveSettings(settingsTmp);
}
else if(graphViewer)
{
graphViewer->loadSettings(settings);
graphViewer->saveSettings(settingsTmp);
}
else if(calibrationDialog)
{
calibrationDialog->loadSettings(settings);
calibrationDialog->saveSettings(settingsTmp);
}
else if(depthCalibrationDialog)
{
depthCalibrationDialog->loadSettings(settings);
depthCalibrationDialog->saveSettings(settingsTmp);
}
else
{
@@ -2748,6 +2806,8 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
settings.endGroup(); //"name"
settings.endGroup(); // Gui
settingsTmp.endGroup(); //"name"
settingsTmp.endGroup(); // Gui
}
}
@@ -2760,6 +2820,13 @@ void PreferencesDialog::saveCustomConfig(const QString & section, const QString
settings.setValue(key, value);
settings.endGroup(); // "section"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(section);
settingsTmp.setValue(key, value);
settingsTmp.endGroup(); // "section"
settingsTmp.endGroup(); // rtabmap
}
QString PreferencesDialog::loadCustomConfig(const QString & section, const QString & key)
@@ -2771,6 +2838,14 @@ QString PreferencesDialog::loadCustomConfig(const QString & section, const QStri
value = settings.value(key, QString()).toString();
settings.endGroup(); // "section"
settings.endGroup(); // rtabmap
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(section);
settingsTmp.setValue(key, value);
settingsTmp.endGroup(); // "section"
settingsTmp.endGroup(); // rtabmap
return value;
}
@@ -2855,6 +2930,11 @@ void PreferencesDialog::selectSourceDriver(Src src)
}
}
bool sortCallback(const std::string & a, const std::string & b)
{
return uStrNumCmp(a,b) < 0;
}
void PreferencesDialog::selectSourceDatabase()
{
QString dir = _ui->source_database_lineEdit_path->text();
@@ -2883,6 +2963,21 @@ void PreferencesDialog::selectSourceDatabase()
_ui->general_spinBox_imagesBufferSize->setValue(0);
}
}
if(paths.size() > 1)
{
std::vector<std::string> vFileNames(paths.size());
for(int i=0; i<paths.size(); ++i)
{
vFileNames[i] = paths[i].toStdString();
}
std::sort(vFileNames.begin(), vFileNames.end(), sortCallback);
for(int i=0; i<paths.size(); ++i)
{
paths[i] = vFileNames[i].c_str();
}
}
_ui->source_database_lineEdit_path->setText(paths.size()==1?paths.front():paths.join(";"));
_ui->source_spinBox_databaseStartPos->setValue(0);
_ui->source_spinBox_database_cameraIndex->setValue(-1);

View File

@@ -42,7 +42,8 @@ namespace rtabmap {
ProgressDialog::ProgressDialog(QWidget *parent, Qt::WindowFlags flags) :
QDialog(parent, flags),
_delayedClosingTime(1)
_delayedClosingTime(1),
_canceled(false)
{
_text = new QLabel(this);
_text->setWordWrap(true);
@@ -62,7 +63,7 @@ ProgressDialog::ProgressDialog(QWidget *parent, Qt::WindowFlags flags) :
_endMessage = "Finished!";
this->clear();
connect(_closeButton, SIGNAL(clicked()), this, SLOT(close()));
connect(_cancelButton, SIGNAL(clicked()), this, SIGNAL(canceled()));
connect(_cancelButton, SIGNAL(clicked()), this, SLOT(cancel()));
QVBoxLayout * layout = new QVBoxLayout(this);
layout->addWidget(_text);
@@ -146,15 +147,15 @@ void ProgressDialog::incrementStep(int steps)
void ProgressDialog::clear()
{
_text->clear();
_progressBar->reset();
_detailedText->clear();
_closeButton->setEnabled(false);
resetProgress();
}
void ProgressDialog::resetProgress()
{
_progressBar->reset();
_closeButton->setEnabled(false);
_canceled = false;
}
void ProgressDialog::closeDialog()
@@ -169,6 +170,7 @@ void ProgressDialog::closeEvent(QCloseEvent *event)
{
if(_progressBar->value() == _progressBar->maximum())
{
_canceled = false;
event->accept();
}
else
@@ -177,4 +179,10 @@ void ProgressDialog::closeEvent(QCloseEvent *event)
}
}
void ProgressDialog::cancel()
{
_canceled = true;
emit canceled();
}
}

View File

@@ -518,6 +518,8 @@
<addaction name="actionRegenerate_local_grid_maps"/>
<addaction name="actionRegenerate_local_grid_maps_selected"/>
<addaction name="separator"/>
<addaction name="actionEdit_depth_image"/>
<addaction name="separator"/>
<addaction name="actionReset_all_changes"/>
<addaction name="separator"/>
<addaction name="actionView_3D_map"/>
@@ -583,7 +585,7 @@
<string>Odom Frame</string>
</property>
<property name="checked">
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@@ -987,7 +989,7 @@
<item>
<widget class="QToolBox" name="toolBox">
<property name="currentIndex">
<number>1</number>
<number>2</number>
</property>
<widget class="QWidget" name="page_3">
<property name="geometry">
@@ -995,7 +997,7 @@
<x>0</x>
<y>0</y>
<width>324</width>
<height>188</height>
<height>207</height>
</rect>
</property>
<attribute name="label">
@@ -1461,8 +1463,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>201</width>
<height>126</height>
<width>283</width>
<height>222</height>
</rect>
</property>
<attribute name="label">
@@ -1484,7 +1486,7 @@
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.300000000000000</double>
<double>1.000000000000000</double>
</property>
</widget>
</item>
@@ -1530,7 +1532,7 @@
<number>100</number>
</property>
<property name="value">
<number>1</number>
<number>5</number>
</property>
</widget>
</item>
@@ -1561,8 +1563,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>186</width>
<height>496</height>
<width>268</width>
<height>304</height>
</rect>
</property>
<attribute name="label">
@@ -1845,7 +1847,7 @@
<string>Odom Frame</string>
</property>
<property name="checked">
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@@ -2134,6 +2136,11 @@
<string>Regenerate local grid maps (selected)...</string>
</property>
</action>
<action name="actionEdit_depth_image">
<property name="text">
<string>Edit depth image...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -23,9 +23,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-1800</y>
<y>-1454</y>
<width>773</width>
<height>3200</height>
<height>3289</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
@@ -1065,26 +1065,45 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_12" columnstretch="0,1">
<item row="2" column="1">
<widget class="QLabel" name="label_binaryFile_4">
<property name="text">
<string>Alpha</string>
<item row="1" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_gainOverlap">
<property name="suffix">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="checkBox_gainFull">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_gainBeta">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>0</number>
<number>1</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
<double>0.100000000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
@@ -1107,10 +1126,10 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="2" column="1">
<widget class="QLabel" name="label_binaryFile_5">
<property name="text">
<string>Beta</string>
<string>Beta. The lower, the higher the compensation is but images are darker. When texturing, brightness and contrast balance below can be activated to re-increase brightness (e.g., set high ratio to 20%).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -1139,28 +1158,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_gainAlpha">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.001000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.010000000000000</double>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_binaryFile_6">
<property name="text">
@@ -1171,45 +1168,16 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_gainOverlap">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QLabel" name="label_binaryFile_7">
<property name="text">
<string>Do compensation just between linked locations. Otherwise, a full compensation between all locations is done (longer to do but quality is better).</string>
<string>Do full compensation between all locations (longer to do but quality is better). Otherwise, compensation is done only between linked locations.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_gainLinkedLocationsOnly">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -1224,60 +1192,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<layout class="QGridLayout" name="gridLayout_10" columnstretch="0,1">
<item row="3" column="1">
<widget class="QLabel" name="label_textureMapping">
<property name="text">
<string>Texture mapping. Images of the cameras will be projected on the mesh(es). Output is a *.obj format. Available on Export or when clouds are not assembled.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="comboBox_meshingApproach">
<item>
<property name="text">
<string>Fast GP3</string>
</property>
</item>
<item>
<property name="text">
<string>Poisson</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="checkBox_textureMapping">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_transferColorRadius">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>-1.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QLabel" name="label_meshClean">
<property name="text">
<string>Clean mesh from polygons without color or texture.</string>
@@ -1287,30 +1202,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QSpinBox" name="spinBox_mesh_minClusterSize">
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_cleanMesh">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Minimum polygon cluster size (-1 means that only the biggest cluster is kept).</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_denseReconstruction">
<property name="text">
@@ -1340,7 +1231,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QLabel" name="label_meshDecimation_2">
<property name="text">
<string>Transferring color radius. Radius used to transfer color from original cloud to resampled reconstructed surface (e.g., Poisson or mesh decimation). Negative means disabled, 0 means take the nearest point.</string>
@@ -1360,6 +1251,112 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_textureMapping">
<property name="text">
<string>Texture mapping. Images of the cameras will be projected on the mesh(es). Output is a *.obj format. Available on Export or when clouds are not assembled.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="comboBox_meshingApproach">
<item>
<property name="text">
<string>Fast GP3</string>
</property>
</item>
<item>
<property name="text">
<string>Poisson</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_textureMapping">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_transferColorRadius">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>-1.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QSpinBox" name="spinBox_mesh_minClusterSize">
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="checkBox_cleanMesh">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Minimum polygon cluster size (-1 means that only the biggest cluster is kept).</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_meshMaxPolygons">
<property name="text">
<string>Maximum polygons (0=no max). Another way to do mesh quadric decimation, the factor will be computed depending on the polygons generated.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QSpinBox" name="spinBox_meshMaxPolygons">
<property name="suffix">
<string/>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>10000000</number>
</property>
<property name="singleStep">
<number>50000</number>
</property>
<property name="value">
<number>9</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
@@ -1376,6 +1373,16 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<layout class="QGridLayout" name="gridLayout_15" columnstretch="0,1">
<item row="4" column="1">
<widget class="QLabel" name="label_meshingTextureSize_3">
<property name="text">
<string>Camera filtering. By comparing poses in the same area, only one camera in a fixed radius and angle is used for texturing.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_meshingTextureFormat">
<property name="text">
@@ -1459,28 +1466,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_meshingTextureMaxDistance">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>3.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_meshingTextureSize_2">
<property name="text">
@@ -1491,23 +1476,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_cameraFilter">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_meshingTextureSize_3">
<property name="text">
<string>Camera filtering. By comparing poses in the same area, only one camera in a fixed radius and angle is used for texturing.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_meshingTextureSize_4">
<property name="text">
@@ -1528,6 +1496,81 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_meshingTextureMaxDistance">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>3.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_cameraFilter">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_meshingTextureSize_5">
<property name="text">
<string>Brightness and contrast balance low ratio. Only used when textures are merged.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QSpinBox" name="spinBox_textureBrightnessContrastRatioLow">
<property name="suffix">
<string> %</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>49</number>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_meshingTextureSize_6">
<property name="text">
<string>Brightness and contrast balance high ratio. Only used when textures are merged.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QSpinBox" name="spinBox_textureBrightnessContrastRatioHigh">
<property name="suffix">
<string> %</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>49</number>
</property>
</widget>
</item>
</layout>
</item>
<item>