mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-02 01:20:25 +08:00
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:
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
314
guilib/src/EditDepthArea.cpp
Normal file
314
guilib/src/EditDepthArea.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
88
guilib/src/EditDepthArea.h
Normal file
88
guilib/src/EditDepthArea.h
Normal 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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user