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

This commit is contained in:
matlabbe
2017-03-31 18:56:52 -04:00
parent a928a0b404
commit 767b29d5a3
20 changed files with 1695 additions and 551 deletions
+5
View File
@@ -93,6 +93,7 @@ public:
const cv::Mat & obstacles, const cv::Mat & obstacles,
float cellSize, float cellSize,
const cv::Point3f & viewpoint); const cv::Point3f & viewpoint);
void updateDepthImage(int nodeId, const cv::Mat & image);
public: public:
void addInfoAfterRun(int stMemSize, int lastSignAdded, int processMemUsed, int databaseMemUsed, int dictionarySize, const ParametersMap & parameters) const; void addInfoAfterRun(int stMemSize, int lastSignAdded, int processMemUsed, int databaseMemUsed, int dictionarySize, const ParametersMap & parameters) const;
@@ -181,6 +182,10 @@ private:
float cellSize, float cellSize,
const cv::Point3f & viewpoint) const = 0; const cv::Point3f & viewpoint) const = 0;
virtual void updateDepthImageQuery(
int nodeId,
const cv::Mat & image) const = 0;
virtual void addStatisticsQuery(const Statistics & statistics) const = 0; virtual void addStatisticsQuery(const Statistics & statistics) const = 0;
// Load objects // Load objects
+6
View File
@@ -142,6 +142,12 @@ cv::Mat RTABMAP_EXP fastBilateralFiltering(
float sigmaR = 0.05f, float sigmaR = 0.05f,
bool earlyDivision = false); bool earlyDivision = false);
cv::Mat RTABMAP_EXP brightnessAndContrastAuto(
const cv::Mat & src,
const cv::Mat & mask,
float clipLowHistPercent=0,
float clipHighHistPercent=0);
} // namespace util3d } // namespace util3d
} // namespace rtabmap } // namespace rtabmap
+9 -1
View File
@@ -434,6 +434,15 @@ void DBDriver::updateOccupancyGrid(
_dbSafeAccessMutex.unlock(); _dbSafeAccessMutex.unlock();
} }
void DBDriver::updateDepthImage(int nodeId, const cv::Mat & image)
{
_dbSafeAccessMutex.lock();
this->updateDepthImageQuery(
nodeId,
image);
_dbSafeAccessMutex.unlock();
}
void DBDriver::load(VWDictionary * dictionary) const void DBDriver::load(VWDictionary * dictionary) const
{ {
_dbSafeAccessMutex.lock(); _dbSafeAccessMutex.lock();
@@ -455,7 +464,6 @@ void DBDriver::loadSignatures(const std::list<int> & signIds,
UDEBUG(""); UDEBUG("");
// look up in the trash before the database // look up in the trash before the database
std::list<int> ids = signIds; std::list<int> ids = signIds;
std::list<Signature*>::iterator sIter;
bool valueFound = false; bool valueFound = false;
_trashesMutex.lock(); _trashesMutex.lock();
{ {
+86 -2
View File
@@ -3202,7 +3202,7 @@ void DBDriverSqlite3::updateOccupancyGridQuery(
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str()); UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
// Save occupancy grid // Save occupancy grid
stepOccupancyGrid(ppStmt, stepOccupancyGridUpdate(ppStmt,
nodeId, nodeId,
ground, ground,
obstacles, obstacles,
@@ -3217,6 +3217,37 @@ void DBDriverSqlite3::updateOccupancyGridQuery(
} }
} }
void DBDriverSqlite3::updateDepthImageQuery(
int nodeId,
const cv::Mat & image) const
{
UDEBUG("");
if(_ppDb)
{
std::string type;
UTimer timer;
timer.start();
int rc = SQLITE_OK;
sqlite3_stmt * ppStmt = 0;
// Create query
std::string query = queryStepDepthUpdate();
rc = sqlite3_prepare_v2(_ppDb, query.c_str(), -1, &ppStmt, 0);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
// Save depth
stepDepthUpdate(ppStmt,
nodeId,
image);
// Finalize (delete) the statement
rc = sqlite3_finalize(ppStmt);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
UDEBUG("Time=%fs", timer.ticks());
}
}
void DBDriverSqlite3::addStatisticsQuery(const Statistics & statistics) const void DBDriverSqlite3::addStatisticsQuery(const Statistics & statistics) const
{ {
UDEBUG("Ref ID = %d", statistics.refImageId()); UDEBUG("Ref ID = %d", statistics.refImageId());
@@ -3502,6 +3533,59 @@ void DBDriverSqlite3::stepDepth(sqlite3_stmt * ppStmt, const SensorData & sensor
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str()); UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
} }
std::string DBDriverSqlite3::queryStepDepthUpdate() const
{
if(uStrNumCmp(_version, "0.10.0") < 0)
{
return "UPDATE Depth SET data=? WHERE id=?;";
}
else
{
return "UPDATE Data SET depth=? WHERE id=?;";
}
}
void DBDriverSqlite3::stepDepthUpdate(sqlite3_stmt * ppStmt, int nodeId, const cv::Mat & image) const
{
if(!ppStmt)
{
UFATAL("");
}
int rc = SQLITE_OK;
int index = 1;
cv::Mat imageCompressed;
if(!image.empty() && (image.type()!=CV_8UC1 || image.rows > 1))
{
// compress
imageCompressed = compressImage2(image, ".png");
}
else
{
imageCompressed = image;
}
if(!imageCompressed.empty())
{
rc = sqlite3_bind_blob(ppStmt, index++, imageCompressed.data, (int)imageCompressed.cols, SQLITE_STATIC);
}
else
{
rc = sqlite3_bind_zeroblob(ppStmt, index++, 4);
}
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
//id
rc = sqlite3_bind_int(ppStmt, index++, nodeId);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
//step
rc=sqlite3_step(ppStmt);
UASSERT_MSG(rc == SQLITE_DONE, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
rc = sqlite3_reset(ppStmt);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
}
std::string DBDriverSqlite3::queryStepSensorData() const std::string DBDriverSqlite3::queryStepSensorData() const
{ {
UASSERT(uStrNumCmp(_version, "0.10.0") >= 0); UASSERT(uStrNumCmp(_version, "0.10.0") >= 0);
@@ -3951,7 +4035,7 @@ std::string DBDriverSqlite3::queryStepOccupancyGridUpdate() const
UASSERT(uStrNumCmp(_version, "0.11.10") >= 0); UASSERT(uStrNumCmp(_version, "0.11.10") >= 0);
return "UPDATE Data SET ground_cells=?, obstacle_cells=?, cell_size=?, view_point_x=?, view_point_y=?, view_point_z=? WHERE id=?;"; return "UPDATE Data SET ground_cells=?, obstacle_cells=?, cell_size=?, view_point_x=?, view_point_y=?, view_point_z=? WHERE id=?;";
} }
void DBDriverSqlite3::stepOccupancyGrid(sqlite3_stmt * ppStmt, void DBDriverSqlite3::stepOccupancyGridUpdate(sqlite3_stmt * ppStmt,
int nodeId, int nodeId,
const cv::Mat & ground, const cv::Mat & ground,
const cv::Mat & obstacles, const cv::Mat & obstacles,
+7 -1
View File
@@ -84,6 +84,10 @@ private:
float cellSize, float cellSize,
const cv::Point3f & viewpoint) const; const cv::Point3f & viewpoint) const;
virtual void updateDepthImageQuery(
int nodeId,
const cv::Mat & image) const;
virtual void addStatisticsQuery(const Statistics & statistics) const; virtual void addStatisticsQuery(const Statistics & statistics) const;
// Load objects // Load objects
@@ -107,6 +111,7 @@ private:
std::string queryStepNode() const; std::string queryStepNode() const;
std::string queryStepImage() const; std::string queryStepImage() const;
std::string queryStepDepth() const; std::string queryStepDepth() const;
std::string queryStepDepthUpdate() const;
std::string queryStepSensorData() const; std::string queryStepSensorData() const;
std::string queryStepLinkUpdate() const; std::string queryStepLinkUpdate() const;
std::string queryStepLink() const; std::string queryStepLink() const;
@@ -119,11 +124,12 @@ private:
int id, int id,
const cv::Mat & imageBytes) const; const cv::Mat & imageBytes) const;
void stepDepth(sqlite3_stmt * ppStmt, const SensorData & sensorData) const; void stepDepth(sqlite3_stmt * ppStmt, const SensorData & sensorData) const;
void stepDepthUpdate(sqlite3_stmt * ppStmt, int nodeId, const cv::Mat & imageCompressed) const;
void stepSensorData(sqlite3_stmt * ppStmt, const SensorData & sensorData) const; void stepSensorData(sqlite3_stmt * ppStmt, const SensorData & sensorData) const;
void stepLink(sqlite3_stmt * ppStmt, const Link & link) const; void stepLink(sqlite3_stmt * ppStmt, const Link & link) const;
void stepWordsChanged(sqlite3_stmt * ppStmt, int signatureId, int oldWordId, int newWordId) const; void stepWordsChanged(sqlite3_stmt * ppStmt, int signatureId, int oldWordId, int newWordId) const;
void stepKeypoint(sqlite3_stmt * ppStmt, int signatureId, int wordId, const cv::KeyPoint & kp, const cv::Point3f & pt, const cv::Mat & descriptor) const; void stepKeypoint(sqlite3_stmt * ppStmt, int signatureId, int wordId, const cv::KeyPoint & kp, const cv::Point3f & pt, const cv::Mat & descriptor) const;
void stepOccupancyGrid(sqlite3_stmt * ppStmt, void stepOccupancyGridUpdate(sqlite3_stmt * ppStmt,
int nodeId, int nodeId,
const cv::Mat & ground, const cv::Mat & ground,
const cv::Mat & obstacles, const cv::Mat & obstacles,
+77 -14
View File
@@ -141,6 +141,10 @@ void feedImpl(
cv::Mat_<int> N(num_images, num_images); N.setTo(0); cv::Mat_<int> N(num_images, num_images); N.setTo(0);
cv::Mat_<double> I(num_images, num_images); I.setTo(0); cv::Mat_<double> I(num_images, num_images); I.setTo(0);
cv::Mat_<double> IR(num_images, num_images); IR.setTo(0);
cv::Mat_<double> IG(num_images, num_images); IG.setTo(0);
cv::Mat_<double> IB(num_images, num_images); IB.setTo(0);
// make id to index map // make id to index map
idToIndex.clear(); idToIndex.clear();
std::vector<int> indexToId(clouds.size()); std::vector<int> indexToId(clouds.size());
@@ -264,14 +268,17 @@ void feedImpl(
} }
UDEBUG("%d->%d: correspondences = %d", iter->second.from(), iter->second.to(), (int)correspondences.size()); UDEBUG("%d->%d: correspondences = %d", iter->second.from(), iter->second.to(), (int)correspondences.size());
if((minOverlap <= 0.0 && correspondences.size()) || if(correspondences.size() && (minOverlap <= 0.0 ||
(double(correspondences.size()) / double(clouds.at(iter->second.from())->size()) >= minOverlap && (double(correspondences.size()) / double(clouds.at(iter->second.from())->size()) >= minOverlap &&
double(correspondences.size()) / double(clouds.at(iter->second.to())->size()) >= minOverlap)) double(correspondences.size()) / double(clouds.at(iter->second.to())->size()) >= minOverlap)))
{ {
int i = idToIndex.at(iter->second.from()); int i = idToIndex.at(iter->second.from());
int j = idToIndex.at(iter->second.to()); int j = idToIndex.at(iter->second.to());
double Isum1 = 0, Isum2 = 0; double Isum1 = 0, Isum2 = 0;
double IRsum1 = 0, IRsum2 = 0;
double IGsum1 = 0, IGsum2 = 0;
double IBsum1 = 0, IBsum2 = 0;
for (unsigned int c = 0; c < correspondences.size(); ++c) for (unsigned int c = 0; c < correspondences.size(); ++c)
{ {
const PointT & pt1 = cloudFrom->at(correspondences.at(c).index_match); const PointT & pt1 = cloudFrom->at(correspondences.at(c).index_match);
@@ -279,10 +286,24 @@ void feedImpl(
Isum1 += std::sqrt(static_cast<double>(sqr(pt1.r) + sqr(pt1.g) + sqr(pt1.b))); Isum1 += std::sqrt(static_cast<double>(sqr(pt1.r) + sqr(pt1.g) + sqr(pt1.b)));
Isum2 += std::sqrt(static_cast<double>(sqr(pt2.r) + sqr(pt2.g) + sqr(pt2.b))); Isum2 += std::sqrt(static_cast<double>(sqr(pt2.r) + sqr(pt2.g) + sqr(pt2.b)));
IRsum1 += static_cast<double>(pt1.r);
IRsum2 += static_cast<double>(pt2.r);
IGsum1 += static_cast<double>(pt1.g);
IGsum2 += static_cast<double>(pt2.g);
IBsum1 += static_cast<double>(pt1.b);
IBsum2 += static_cast<double>(pt2.b);
} }
N(i, j) = N(j, i) = correspondences.size(); N(i, j) = N(j, i) = correspondences.size();
I(i, j) = Isum1 / N(i, j); I(i, j) = Isum1 / N(i, j);
I(j, i) = Isum2 / N(i, j); I(j, i) = Isum2 / N(i, j);
IR(i, j) = IRsum1 / N(i, j);
IR(j, i) = IRsum2 / N(i, j);
IG(i, j) = IGsum1 / N(i, j);
IG(j, i) = IGsum2 / N(i, j);
IB(i, j) = IBsum1 / N(i, j);
IB(j, i) = IBsum2 / N(i, j);
} }
} }
} }
@@ -291,25 +312,52 @@ void feedImpl(
cv::Mat_<double> A(num_images, num_images); A.setTo(0); cv::Mat_<double> A(num_images, num_images); A.setTo(0);
cv::Mat_<double> b(num_images, 1); b.setTo(0); cv::Mat_<double> b(num_images, 1); b.setTo(0);
cv::Mat_<double> AR(num_images, num_images); AR.setTo(0);
cv::Mat_<double> AG(num_images, num_images); AG.setTo(0);
cv::Mat_<double> AB(num_images, num_images); AB.setTo(0);
for (int i = 0; i < num_images; ++i) for (int i = 0; i < num_images; ++i)
{ {
for (int j = 0; j < num_images; ++j) for (int j = 0; j < num_images; ++j)
{ {
b(i, 0) += beta * N(i, j); b(i, 0) += beta * N(i, j);
A(i, i) += beta * N(i, j); A(i, i) += beta * N(i, j);
AR(i, i) += beta * N(i, j);
AG(i, i) += beta * N(i, j);
AB(i, i) += beta * N(i, j);
if (j == i) continue; if (j == i) continue;
A(i, i) += 2 * alpha * I(i, j) * I(i, j) * N(i, j); A(i, i) += 2 * alpha * I(i, j) * I(i, j) * N(i, j);
A(i, j) -= 2 * alpha * I(i, j) * I(j, i) * N(i, j); A(i, j) -= 2 * alpha * I(i, j) * I(j, i) * N(i, j);
AR(i, i) += 2 * alpha * IR(i, j) * IR(i, j) * N(i, j);
AR(i, j) -= 2 * alpha * IR(i, j) * IR(j, i) * N(i, j);
AG(i, i) += 2 * alpha * IG(i, j) * IG(i, j) * N(i, j);
AG(i, j) -= 2 * alpha * IG(i, j) * IG(j, i) * N(i, j);
AB(i, i) += 2 * alpha * IB(i, j) * IB(i, j) * N(i, j);
AB(i, j) -= 2 * alpha * IB(i, j) * IB(j, i) * N(i, j);
} }
} }
gains = cv::Mat_<double>(); cv::Mat_<double> gainsGray, gainsR, gainsG, gainsB;
cv::solve(A, b, gains); cv::solve(A, b, gainsGray);
cv::solve(AR, b, gainsR);
cv::solve(AG, b, gainsG);
cv::solve(AB, b, gainsB);
gains = cv::Mat_<double>(gainsGray.rows, 4);
gainsGray.copyTo(gains.col(0));
gainsR.copyTo(gains.col(1));
gainsG.copyTo(gains.col(2));
gainsB.copyTo(gains.col(3));
if(ULogger::kInfo) if(ULogger::kInfo)
{ {
for(int i=0; i<gains.rows; ++i) for(int i=0; i<gains.rows; ++i)
{ {
UINFO("Gain index=%d (id=%d) = %f", i, indexToId[i], gains(i, 0)); UINFO("Gain index=%d (id=%d) = %f (%f,%f,%f)", i, indexToId[i], gains(i, 0), gains(i, 1), gains(i, 2), gains(i, 3));
} }
} }
} }
@@ -349,16 +397,18 @@ void applyImpl(
const pcl::IndicesPtr & indices, const pcl::IndicesPtr & indices,
const cv::Mat_<double> & gains) const cv::Mat_<double> & gains)
{ {
double gain = gains(index, 0); double gainR = gains(index, 1);
UDEBUG("index=%d gain=%f", index, gain); double gainG = gains(index, 2);
double gainB = gains(index, 3);
UDEBUG("index=%d gain=%f (%f,%f,%f)", index, gains(index, 0), gainR, gainG, gainB);
if(indices->size()) if(indices->size())
{ {
for(unsigned int i=0; i<indices->size(); ++i) for(unsigned int i=0; i<indices->size(); ++i)
{ {
PointT & pt = cloud->at(indices->at(i)); PointT & pt = cloud->at(indices->at(i));
pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gain))); pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gainR)));
pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gain))); pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gainG)));
pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gain))); pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gainB)));
} }
} }
else else
@@ -366,9 +416,9 @@ void applyImpl(
for(unsigned int i=0; i<cloud->size(); ++i) for(unsigned int i=0; i<cloud->size(); ++i)
{ {
PointT & pt = cloud->at(i); PointT & pt = cloud->at(i);
pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gain))); pt.r = uchar(std::max(0.0, std::min(255.0, double(pt.r) * gainR)));
pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gain))); pt.g = uchar(std::max(0.0, std::min(255.0, double(pt.g) * gainG)));
pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gain))); pt.b = uchar(std::max(0.0, std::min(255.0, double(pt.b) * gainB)));
} }
} }
} }
@@ -402,7 +452,20 @@ void GainCompensator::apply(
cv::Mat & image) const cv::Mat & image) const
{ {
UASSERT_MSG(uContains(idToIndex_, id), uFormat("id=%d idToIndex_.size()=%d", id, (int)idToIndex_.size()).c_str()); UASSERT_MSG(uContains(idToIndex_, id), uFormat("id=%d idToIndex_.size()=%d", id, (int)idToIndex_.size()).c_str());
cv::multiply(image, gains_(idToIndex_.at(id), 0), image); if(image.channels() == 1)
{
cv::multiply(image, gains_(idToIndex_.at(id), 0), image);
}
else if(image.channels()>=3)
{
std::vector<cv::Mat> channels;
cv::split(image, channels);
// assuming BGR
cv::multiply(channels[0], gains_(idToIndex_.at(id), 3), channels[0]);
cv::multiply(channels[1], gains_(idToIndex_.at(id), 2), channels[1]);
cv::multiply(channels[2], gains_(idToIndex_.at(id), 1), channels[2]);
cv::merge(channels, image);
}
} }
double GainCompensator::getGain(int id) const double GainCompensator::getGain(int id) const
+115 -6
View File
@@ -49,6 +49,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <pcl/search/kdtree.h> #include <pcl/search/kdtree.h>
#include <pcl/filters/crop_box.h> #include <pcl/filters/crop_box.h>
#include <pcl/io/pcd_io.h> #include <pcl/io/pcd_io.h>
#include <pcl/common/common.h>
#include <stdlib.h> #include <stdlib.h>
#include <set> #include <set>
@@ -3488,12 +3489,120 @@ int Rtabmap::detectMoreLoopClosures(float clusterRadius, float clusterAngle, int
if(!t.isNull()) if(!t.isNull())
{ {
UINFO("Added new loop closure between %d and %d.", from, to); bool updateConstraints = true;
addedLinks.insert(from); if(_optimizationMaxLinearError > 0.0f)
addedLinks.insert(to); {
links.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, t, info.varianceAng, info.varianceLin))); //optimize the graph to see if the new constraint is globally valid
loopClosuresAdded.push_back(Link(from, to, Link::kUserClosure, t, info.varianceAng, info.varianceLin));
UINFO("Detected loop closure %d->%d! (%d/%d)", from, to, i+1, (int)clusters.size()); int fromId = from;
int mapId = signatures.at(from).mapId();
// use first node of the map containing from
for(std::map<int, Signature>::iterator iter=signatures.begin(); iter!=signatures.end(); ++iter)
{
if(iter->second.mapId() == mapId)
{
fromId = iter->first;
break;
}
}
std::multimap<int, Link> linksIn = links;
linksIn.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, t, info.varianceAng, info.varianceLin)));
const Link * maxLinearLink = 0;
const Link * maxAngularLink = 0;
float maxLinearError = 0.0f;
float maxAngularError = 0.0f;
std::map<int, Transform> optimizedPoses;
std::multimap<int, Link> 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());
_graphOptimizer->getConnectedGraph(fromId, poses, linksIn, optimizedPoses, links);
UASSERT(optimizedPoses.find(fromId) != optimizedPoses.end());
UASSERT_MSG(optimizedPoses.find(from) != optimizedPoses.end(), uFormat("id=%d poses=%d links=%d", from, (int)optimizedPoses.size(), (int)links.size()).c_str());
UASSERT_MSG(optimizedPoses.find(to) != optimizedPoses.end(), uFormat("id=%d poses=%d links=%d", to, (int)optimizedPoses.size(), (int)links.size()).c_str());
UASSERT(graph::findLink(links, from, to) != links.end());
optimizedPoses = _graphOptimizer->optimize(fromId, optimizedPoses, links);
std::string msg;
if(optimizedPoses.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(optimizedPoses.find(iter->second.from())!=optimizedPoses.end());
UASSERT(optimizedPoses.find(iter->second.to())!=optimizedPoses.end());
Transform t1 = optimizedPoses.at(iter->second.from());
Transform t2 = optimizedPoses.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 > _optimizationMaxLinearError)
{
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(),
_optimizationMaxLinearError);
}
}
else
{
msg = uFormat("Rejecting edge %d->%d because graph optimization has failed!",
from,
to);
}
if(!msg.empty())
{
UWARN("%s", msg.c_str());
updateConstraints = false;
}
}
if(updateConstraints)
{
UINFO("Added new loop closure between %d and %d.", from, to);
addedLinks.insert(from);
addedLinks.insert(to);
links.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, t, info.varianceAng, info.varianceLin)));
loopClosuresAdded.push_back(Link(from, to, Link::kUserClosure, t, info.varianceAng, info.varianceLin));
UINFO("Detected loop closure %d->%d! (%d/%d)", from, to, i+1, (int)clusters.size());
}
} }
} }
} }
+83
View File
@@ -1909,6 +1909,89 @@ cv::Mat fastBilateralFiltering(const cv::Mat & depth, float sigmaS, float sigmaR
return output; return output;
} }
/**
* \brief Automatic brightness and contrast optimization with optional histogram clipping
* \param [in]src Input image GRAY or BGR or BGRA
* \param [out]dst Destination image
* \param clipHistPercent cut wings of histogram at given percent typical=>1, 0=>Disabled
* \note In case of BGRA image, we won't touch the transparency
* See http://answers.opencv.org/question/75510/how-to-make-auto-adjustmentsbrightness-and-contrast-for-image-android-opencv-image-correction/
*/
cv::Mat brightnessAndContrastAuto(const cv::Mat &src, const cv::Mat & mask, float clipLowHistPercent, float clipHighHistPercent)
{
CV_Assert(clipLowHistPercent >= 0 && clipHighHistPercent>=0);
CV_Assert((src.type() == CV_8UC1) || (src.type() == CV_8UC3) || (src.type() == CV_8UC4));
int histSize = 256;
float alpha, beta;
double minGray = 0, maxGray = 0;
//to calculate grayscale histogram
cv::Mat gray;
if (src.type() == CV_8UC1) gray = src;
else if (src.type() == CV_8UC3) cvtColor(src, gray, CV_BGR2GRAY);
else if (src.type() == CV_8UC4) cvtColor(src, gray, CV_BGRA2GRAY);
if (clipLowHistPercent == 0 && clipHighHistPercent == 0)
{
// keep full available range
cv::minMaxLoc(gray, &minGray, &maxGray, 0, 0, mask);
}
else
{
cv::Mat hist; //the grayscale histogram
float range[] = { 0, 256 };
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
calcHist(&gray, 1, 0, mask, hist, 1, &histSize, &histRange, uniform, accumulate);
// calculate cumulative distribution from the histogram
std::vector<float> accumulator(histSize);
accumulator[0] = hist.at<float>(0);
for (int i = 1; i < histSize; i++)
{
accumulator[i] = accumulator[i - 1] + hist.at<float>(i);
}
// locate points that cuts at required value
float max = accumulator.back();
clipLowHistPercent *= (max / 100.0); //make percent as absolute
clipHighHistPercent *= (max / 100.0); //make percent as absolute
// locate left cut
minGray = 0;
while (accumulator[minGray] < clipLowHistPercent)
minGray++;
// locate right cut
maxGray = histSize - 1;
while (accumulator[maxGray] >= (max - clipHighHistPercent))
maxGray--;
}
// current range
float inputRange = maxGray - minGray;
alpha = (histSize - 1) / inputRange; // alpha expands current range to histsize range
beta = -minGray * alpha; // beta shifts current range so that minGray will go to 0
UINFO("minGray=%f maxGray=%f alpha=%f beta=%f", minGray, maxGray, alpha, beta);
cv::Mat dst;
// Apply brightness and contrast normalization
// convertTo operates with saurate_cast
src.convertTo(dst, -1, alpha, beta);
// restore alpha channel from source
if (dst.type() == CV_8UC4)
{
int from_to[] = { 3, 3};
cv::mixChannels(&src, 4, &dst,1, from_to, 1);
}
return dst;
}
} }
} }
+9 -2
View File
@@ -47,6 +47,7 @@ class Ui_DatabaseViewer;
class QGraphicsScene; class QGraphicsScene;
class QGraphicsView; class QGraphicsView;
class QLabel; class QLabel;
class QDialog;
namespace rtabmap namespace rtabmap
{ {
@@ -56,6 +57,7 @@ class SensorData;
class CloudViewer; class CloudViewer;
class OctoMap; class OctoMap;
class ExportCloudsDialog; class ExportCloudsDialog;
class EditDepthArea;
class RTABMAPGUI_EXP DatabaseViewer : public QMainWindow class RTABMAPGUI_EXP DatabaseViewer : public QMainWindow
{ {
@@ -79,6 +81,8 @@ private slots:
void writeSettings(); void writeSettings();
void configModified(); void configModified();
void openDatabase(); void openDatabase();
void updateStatistics();
void editDepthImage();
void generateGraph(); void generateGraph();
void exportDatabase(); void exportDatabase();
void extractImages(); void extractImages();
@@ -153,8 +157,8 @@ private:
std::multimap<int, rtabmap::Link> updateLinksWithModifications( std::multimap<int, rtabmap::Link> updateLinksWithModifications(
const std::multimap<int, rtabmap::Link> & edgeConstraints); const std::multimap<int, rtabmap::Link> & edgeConstraints);
void updateLoopClosuresSlider(int from = 0, int to = 0); void updateLoopClosuresSlider(int from = 0, int to = 0);
void refineConstraint(int from, int to, bool silent, bool updateGraph); void refineConstraint(int from, int to, bool silent);
bool addConstraint(int from, int to, bool silent, bool updateGraph); bool addConstraint(int from, int to, bool silent);
private: private:
Ui_DatabaseViewer * ui_; 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<float, cv::Point3f> > localMapsInfo_; // <cell size, viewpoint>
std::map<int, std::pair<cv::Mat, cv::Mat> > generatedLocalMaps_; // <ground, obstacles> 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, std::pair<float, cv::Point3f> > generatedLocalMapsInfo_; // <cell size, viewpoint>
std::map<int, cv::Mat> modifiedDepthImages_;
OctoMap * octomap_; OctoMap * octomap_;
ExportCloudsDialog * exportDialog_; ExportCloudsDialog * exportDialog_;
QDialog * editDepthDialog_;
EditDepthArea * editDepthArea_;
bool savedMaximized_; bool savedMaximized_;
bool firstCall_; bool firstCall_;
@@ -54,6 +54,7 @@ public:
void setMaximumSteps(int steps); void setMaximumSteps(int steps);
void setAutoClose(bool on, int delayedClosingTimeMsec = -1); void setAutoClose(bool on, int delayedClosingTimeMsec = -1);
void setCancelButtonVisible(bool visible); void setCancelButtonVisible(bool visible);
bool isCanceled() const {return _canceled;}
signals: signals:
void canceled(); void canceled();
@@ -69,6 +70,7 @@ public slots:
private slots: private slots:
void closeDialog(); void closeDialog();
void cancel();
private: private:
QLabel * _text; QLabel * _text;
@@ -79,6 +81,7 @@ private:
QCheckBox * _closeWhenDoneCheckBox; QCheckBox * _closeWhenDoneCheckBox;
QString _endMessage; QString _endMessage;
int _delayedClosingTime; // sec int _delayedClosingTime; // sec
bool _canceled;
}; };
} }
+2
View File
@@ -30,6 +30,7 @@ SET(headers_ui
./DepthCalibrationDialog.h ./DepthCalibrationDialog.h
./3rdParty/QMultiComboBox.h ./3rdParty/QMultiComboBox.h
./TexturingState.h ./TexturingState.h
./EditDepthArea.h
) )
SET(uis SET(uis
@@ -93,6 +94,7 @@ SET(SRC_FILES
./ExportScansDialog.cpp ./ExportScansDialog.cpp
./MapVisibilityWidget.cpp ./MapVisibilityWidget.cpp
./GraphViewer.cpp ./GraphViewer.cpp
./EditDepthArea.cpp
./CreateSimpleCalibrationDialog.cpp ./CreateSimpleCalibrationDialog.cpp
./ParametersToolBox.cpp ./ParametersToolBox.cpp
./DepthCalibrationDialog.cpp ./DepthCalibrationDialog.cpp
+169 -58
View File
@@ -70,12 +70,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "rtabmap/core/OccupancyGrid.h" #include "rtabmap/core/OccupancyGrid.h"
#include "rtabmap/gui/DataRecorder.h" #include "rtabmap/gui/DataRecorder.h"
#include "ExportCloudsDialog.h" #include "ExportCloudsDialog.h"
#include "EditDepthArea.h"
#include "rtabmap/core/SensorData.h" #include "rtabmap/core/SensorData.h"
#include "rtabmap/core/GainCompensator.h" #include "rtabmap/core/GainCompensator.h"
#include "ExportDialog.h" #include "ExportDialog.h"
#include "rtabmap/gui/ProgressDialog.h" #include "rtabmap/gui/ProgressDialog.h"
#include "ParametersToolBox.h" #include "ParametersToolBox.h"
#include <pcl/io/pcd_io.h> #include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h> #include <pcl/io/ply_io.h>
#include <pcl/filters/voxel_grid.h> #include <pcl/filters/voxel_grid.h>
@@ -93,6 +93,7 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
dbDriver_(0), dbDriver_(0),
octomap_(0), octomap_(0),
exportDialog_(new ExportCloudsDialog(this)), exportDialog_(new ExportCloudsDialog(this)),
editDepthDialog_(new QDialog(this)),
savedMaximized_(false), savedMaximized_(false),
firstCall_(true), firstCall_(true),
iniFilePath_(ini) 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_->comboBox_logger_level, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLoggerLevel()));
connect(ui_->checkBox_verticalLayout, SIGNAL(stateChanged(int)), this, SLOT(setupMainLayout(int))); 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[*]"); QString title("RTAB-Map Database Viewer[*]");
this->setWindowTitle(title); this->setWindowTitle(title);
@@ -173,6 +188,7 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
uInsert(parameters, Parameters::getDefaultParameters("StereoBM")); uInsert(parameters, Parameters::getDefaultParameters("StereoBM"));
uInsert(parameters, Parameters::getDefaultParameters("Grid")); uInsert(parameters, Parameters::getDefaultParameters("Grid"));
parameters.insert(*Parameters::getDefaultParameters().find(Parameters::kRGBDOptimizeMaxError())); parameters.insert(*Parameters::getDefaultParameters().find(Parameters::kRGBDOptimizeMaxError()));
parameters.insert(*Parameters::getDefaultParameters().find(Parameters::kRGBDLoopClosureReextractFeatures()));
ui_->parameters_toolbox->setupUi(parameters); ui_->parameters_toolbox->setupUi(parameters);
exportDialog_->setObjectName("ExportCloudsDialog"); exportDialog_->setObjectName("ExportCloudsDialog");
this->readSettings(); this->readSettings();
@@ -192,6 +208,8 @@ DatabaseViewer::DatabaseViewer(const QString & ini, QWidget * parent) :
ui_->menuView->addAction(ui_->dockWidget_statistics->toggleViewAction()); ui_->menuView->addAction(ui_->dockWidget_statistics->toggleViewAction());
connect(ui_->dockWidget_graphView->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateGraphView())); connect(ui_->dockWidget_graphView->toggleViewAction(), SIGNAL(triggered()), this, SLOT(updateGraphView()));
connect(ui_->dockWidget_occupancyGridView->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 &))); 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_->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase()));
connect(ui_->actionExport, SIGNAL(triggered()), this, SLOT(exportDatabase())); connect(ui_->actionExport, SIGNAL(triggered()), this, SLOT(exportDatabase()));
connect(ui_->actionExtract_images, SIGNAL(triggered()), this, SLOT(extractImages())); 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_graph_dot, SIGNAL(triggered()), this, SLOT(generateGraph()));
connect(ui_->actionGenerate_local_graph_dot, SIGNAL(triggered()), this, SLOT(generateLocalGraph())); connect(ui_->actionGenerate_local_graph_dot, SIGNAL(triggered()), this, SLOT(generateLocalGraph()));
connect(ui_->actionGenerate_TORO_graph_graph, SIGNAL(triggered()), this, SLOT(generateTOROGraph())); 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_->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()); ui_->checkBox_icp_from_depth->setChecked(settings.value("icpFromDepth", ui_->checkBox_icp_from_depth->isChecked()).toBool());
settings.endGroup(); 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 settings.endGroup(); // DatabaseViewer
// Use same parameters used by RTAB-Map
settings.beginGroup("Gui");
exportDialog_->loadSettings(settings); 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; ParametersMap parameters;
Parameters::readINI(path.toStdString(), parameters); Parameters::readINI(path.toStdString(), parameters);
@@ -561,16 +583,17 @@ void DatabaseViewer::writeSettings()
settings.setValue("icpFromDepth", ui_->checkBox_icp_from_depth->isChecked()); settings.setValue("icpFromDepth", ui_->checkBox_icp_from_depth->isChecked());
settings.endGroup(); 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 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(); ParametersMap parameters = ui_->parameters_toolbox->getParameters();
for(ParametersMap::iterator iter=parameters.begin(); iter!=parameters.end();) 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_TORO_graph_graph->setEnabled(false);
ui_->actionGenerate_g2o_graph_g2o->setEnabled(false); ui_->actionGenerate_g2o_graph_g2o->setEnabled(false);
ui_->checkBox_showOptimized->setEnabled(false); ui_->checkBox_showOptimized->setEnabled(false);
ui_->toolBox_statistics->clear();
databaseFileName_.clear(); databaseFileName_.clear();
} }
@@ -1201,6 +1225,7 @@ void DatabaseViewer::updateIds()
linksAdded_.clear(); linksAdded_.clear();
linksRefined_.clear(); linksRefined_.clear();
linksRemoved_.clear(); linksRemoved_.clear();
ui_->toolBox_statistics->clear();
ui_->label_optimizeFrom->setText(tr("Optimize from")); ui_->label_optimizeFrom->setText(tr("Optimize from"));
std::multimap<int, Link> links; std::multimap<int, Link> links;
dbDriver_->getAllLinks(links, true); dbDriver_->getAllLinks(links, true);
@@ -1214,7 +1239,6 @@ void DatabaseViewer::updateIds()
dbDriver_->getAllNodeIds(idsWithoutBad, false, true); dbDriver_->getAllNodeIds(idsWithoutBad, false, true);
int badcountInLTM = 0; int badcountInLTM = 0;
int badCountInGraph = 0; int badCountInGraph = 0;
double firstStamp = 0.0;
for(int i=0; i<ids_.size(); ++i) for(int i=0; i<ids_.size(); ++i)
{ {
idToIndex_.insert(ids_[i], i); idToIndex_.insert(ids_[i], i);
@@ -1227,17 +1251,6 @@ void DatabaseViewer::updateIds()
dbDriver_->getNodeInfo(ids_[i], p, mapId, w, l, s, g); dbDriver_->getNodeInfo(ids_[i], p, mapId, w, l, s, g);
mapIds_.insert(std::make_pair(ids_[i], mapId)); 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(i>0)
{ {
if(mapIds_.at(ids_[i-1]) == mapId) 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()); 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..."); UINFO("Update database info...");
ui_->textEdit_info->clear(); ui_->textEdit_info->clear();
ui_->textEdit_info->append(tr("Version:\t\t%1").arg(dbDriver_->getDatabaseVersion().c_str())); 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() void DatabaseViewer::generateGraph()
{ {
if(!dbDriver_) if(!dbDriver_)
@@ -2070,6 +2135,12 @@ void DatabaseViewer::detectMoreLoopClosures()
const std::map<int, Transform> & optimizedPoses = graphes_.back(); 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(); int iterations = ui_->spinBox_detectMore_iterations->value();
UASSERT(iterations > 0); UASSERT(iterations > 0);
int added = 0; int added = 0;
@@ -2081,8 +2152,18 @@ void DatabaseViewer::detectMoreLoopClosures()
optimizedPoses, optimizedPoses,
ui_->doubleSpinBox_detectMore_radius->value(), ui_->doubleSpinBox_detectMore_radius->value(),
ui_->doubleSpinBox_detectMore_angle->value()*CV_PI/180.0); 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; 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 from = iter->first;
int to = iter->second; int to = iter->second;
@@ -2099,27 +2180,36 @@ void DatabaseViewer::detectMoreLoopClosures()
addedLinks.find(from) == addedLinks.end() && addedLinks.find(to) == addedLinks.end()) addedLinks.find(from) == addedLinks.end() && addedLinks.find(to) == addedLinks.end())
{ {
checkedLoopClosures.insert(std::make_pair(from, to)); 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); UINFO("Added new loop closure between %d and %d.", from, to);
++added; ++added;
addedLinks.insert(from); addedLinks.insert(from);
addedLinks.insert(to); 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); 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) if(addedLinks.size() == 0)
{ {
break; break;
} }
} }
if(added) if(added)
{ {
this->updateGraphView(); this->updateGraphView();
} }
UINFO("Total added %d loop closures.", added); 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() void DatabaseViewer::refineAllNeighborLinks()
@@ -2134,7 +2224,7 @@ void DatabaseViewer::refineAllNeighborLinks()
{ {
int from = neighborLinks_[i].from(); int from = neighborLinks_[i].from();
int to = neighborLinks_[i].to(); 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.appendText(tr("Refined link %1->%2 (%3/%4)").arg(from).arg(to).arg(i+1).arg(neighborLinks_.size()));
progressDialog.incrementStep(); progressDialog.incrementStep();
@@ -2159,7 +2249,7 @@ void DatabaseViewer::refineAllLoopClosureLinks()
{ {
int from = loopLinks_[i].from(); int from = loopLinks_[i].from();
int to = loopLinks_[i].to(); 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.appendText(tr("Refined link %1->%2 (%3/%4)").arg(from).arg(to).arg(i+1).arg(loopLinks_.size()));
progressDialog.incrementStep(); progressDialog.incrementStep();
@@ -4218,10 +4308,10 @@ void DatabaseViewer::refineConstraint()
{ {
int from = ids_.at(ui_->horizontalSlider_A->value()); int from = ids_.at(ui_->horizontalSlider_A->value());
int to = ids_.at(ui_->horizontalSlider_B->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) 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)); linksRefined_.insert(std::make_pair(newLink.from(), newLink));
if(updateGraph) if(!silent)
{ {
this->updateGraphView(); this->updateGraphView();
} }
} }
if(ui_->dockWidget_constraints->isVisible()) if(!silent && ui_->dockWidget_constraints->isVisible())
{ {
this->updateConstraintView(newLink, true); this->updateConstraintView(newLink, true);
} }
@@ -4394,10 +4484,10 @@ void DatabaseViewer::addConstraint()
{ {
int from = ids_.at(ui_->horizontalSlider_A->value()); int from = ids_.at(ui_->horizontalSlider_A->value());
int to = ids_.at(ui_->horizontalSlider_B->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; bool switchedIds = false;
if(from == to) if(from == to)
@@ -4421,24 +4511,40 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
UASSERT(!containsLink(linksRefined_, from, to)); UASSERT(!containsLink(linksRefined_, from, to));
ParametersMap parameters = ui_->parameters_toolbox->getParameters(); ParametersMap parameters = ui_->parameters_toolbox->getParameters();
Registration * reg = Registration::create(parameters);
Transform t; Transform t;
RegistrationInfo info; RegistrationInfo info;
// Add sensor data to generate features std::list<int> ids;
SensorData dataFrom; ids.push_back(from);
dbDriver_->getNodeData(from, dataFrom); ids.push_back(to);
dataFrom.uncompressData(); std::list<Signature*> signatures;
SensorData dataTo; dbDriver_->loadSignatures(ids, signatures);
dbDriver_->getNodeData(to, dataTo); if(signatures.size() != 2)
dataTo.uncompressData(); {
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(""); t = reg->computeTransformationMod(*fromS, *toS, Transform(), &info);
Registration * reg = Registration::create(parameters);
Signature fromS(dataFrom);
Signature toS(dataTo);
t = reg->computeTransformationMod(fromS, toS, Transform(), &info);
delete reg; delete reg;
UDEBUG(""); UDEBUG("");
@@ -4446,13 +4552,13 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
{ {
if(switchedIds) if(switchedIds)
{ {
ui_->graphicsView_A->setFeatures(toS.getWords(), dataTo.depthRaw()); ui_->graphicsView_A->setFeatures(toS->getWords(), toS->sensorData().depthRaw());
ui_->graphicsView_B->setFeatures(fromS.getWords(), dataFrom.depthRaw()); ui_->graphicsView_B->setFeatures(fromS->getWords(), fromS->sensorData().depthRaw());
} }
else else
{ {
ui_->graphicsView_A->setFeatures(fromS.getWords(), dataFrom.depthRaw()); ui_->graphicsView_A->setFeatures(fromS->getWords(), fromS->sensorData().depthRaw());
ui_->graphicsView_B->setFeatures(toS.getWords(), dataTo.depthRaw()); ui_->graphicsView_B->setFeatures(toS->getWords(), toS->sensorData().depthRaw());
} }
updateWordsMatching(); updateWordsMatching();
} }
@@ -4475,6 +4581,11 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
tr("Add link"), tr("Add link"),
tr("Cannot find a transformation between nodes %1 and %2: %3").arg(from).arg(to).arg(info.rejectedMsg.c_str())); 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)) 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()); 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()); 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->from(),
maxLinearLink->to(), maxLinearLink->to(),
maxAngularError*180.0f/M_PI, maxAngularError*180.0f/M_PI,
maxAngularLink->from(), maxAngularLink?maxAngularLink->from():0,
maxAngularLink->to(), maxAngularLink?maxAngularLink->to():0,
Parameters::kRGBDOptimizeMaxError().c_str(), Parameters::kRGBDOptimizeMaxError().c_str(),
maxOptimizationError); maxOptimizationError);
} }
@@ -4606,9 +4717,9 @@ bool DatabaseViewer::addConstraint(int from, int to, bool silent, bool updateGra
{ {
linksAdded_.insert(std::make_pair(newLink.from(), newLink)); linksAdded_.insert(std::make_pair(newLink.from(), newLink));
} }
updateLoopClosuresSlider(from, to); if(!silent)
if(updateGraph)
{ {
updateLoopClosuresSlider(from, to);
this->updateGraphView(); this->updateGraphView();
} }
} }
+314
View File
@@ -0,0 +1,314 @@
/*
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Universite de Sherbrooke nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <QWidget>
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#include <QAction>
#include <QInputDialog>
#include "EditDepthArea.h"
#include <rtabmap/utilite/ULogger.h>
#include <rtabmap/utilite/UCv2Qt.h>
namespace rtabmap {
EditDepthArea::EditDepthArea(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
modified_ = false;
scribbling_ = false;
myPenWidth_ = 10;
menu_ = new QMenu(tr(""), this);
showRGB_ = menu_->addAction(tr("Show RGB Image"));
showRGB_->setCheckable(true);
showRGB_->setChecked(true);
removeCluster_ = menu_->addAction(tr("Remove Cluster"));
setPenWidth_ = menu_->addAction(tr("Set Pen Width..."));
resetChanges_ = menu_->addAction(tr("Reset Changes"));
}
void EditDepthArea::setImage(const cv::Mat &depth, const cv::Mat & rgb)
{
UASSERT(!depth.empty());
UASSERT(depth.type() == CV_32FC1 ||
depth.type() == CV_16UC1);
originalImage_ = depth;
image_ = uCvMat2QImage(depth).convertToFormat(QImage::Format_RGB32);
imageRGB_ = QImage();
if(!rgb.empty())
{
imageRGB_ = uCvMat2QImage(rgb);
if( depth.cols != rgb.cols ||
depth.rows != rgb.rows)
{
// scale rgb to depth
imageRGB_ = imageRGB_.scaled(image_.size());
}
}
showRGB_->setEnabled(!imageRGB_.isNull());
modified_ = false;
update();
}
cv::Mat EditDepthArea::getModifiedImage() const
{
cv::Mat modifiedImage = originalImage_.clone();
if(modified_)
{
UASSERT(image_.width() == modifiedImage.cols &&
image_.height() == modifiedImage.rows);
UASSERT(modifiedImage.type() == CV_32FC1 ||
modifiedImage.type() == CV_16UC1);
for(int j=0; j<image_.height(); ++j)
{
for(int i=0; i<image_.width(); ++i)
{
if(qRed(image_.pixel(i, j)) == 0 &&
qGreen(image_.pixel(i, j)) == 0 &&
qBlue(image_.pixel(i, j)) == 0)
{
if(modifiedImage.type() == CV_32FC1)
{
modifiedImage.at<float>(j,i) = 0.0f;
}
else // CV_16UC1
{
modifiedImage.at<unsigned short>(j,i) = 0;
}
}
}
}
}
return modifiedImage;
}
void EditDepthArea::setPenWidth(int newWidth)
{
myPenWidth_ = newWidth;
}
void EditDepthArea::resetChanges()
{
image_ = uCvMat2QImage(originalImage_).convertToFormat(QImage::Format_RGB32);
modified_ = false;
update();
}
void EditDepthArea::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
lastPoint_.setX((event->pos().x()-offsetX)/scale);
lastPoint_.setY((event->pos().y()-offsetY)/scale);
scribbling_ = true;
}
}
void EditDepthArea::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && scribbling_)
{
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint to;
to.setX((event->pos().x()-offsetX)/scale);
to.setY((event->pos().y()-offsetY)/scale);
drawLineTo(to);
}
}
void EditDepthArea::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && scribbling_) {
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint to;
to.setX((event->pos().x()-offsetX)/scale);
to.setY((event->pos().y()-offsetY)/scale);
drawLineTo(to);
scribbling_ = false;
}
}
void EditDepthArea::computeScaleOffsets(const QRect & targetRect, float & scale, float & offsetX, float & offsetY) const
{
scale = 1.0f;
offsetX = 0.0f;
offsetY = 0.0f;
if(!image_.isNull())
{
float w = image_.width();
float h = image_.height();
float widthRatio = float(targetRect.width()) / w;
float heightRatio = float(targetRect.height()) / h;
//printf("w=%f, h=%f, wR=%f, hR=%f, sW=%d, sH=%d\n", w, h, widthRatio, heightRatio, this->rect().width(), this->rect().height());
if(widthRatio < heightRatio)
{
scale = widthRatio;
}
else
{
scale = heightRatio;
}
//printf("ratio=%f\n",ratio);
w *= scale;
h *= scale;
if(w < targetRect.width())
{
offsetX = (targetRect.width() - w)/2.0f;
}
if(h < targetRect.height())
{
offsetY = (targetRect.height() - h)/2.0f;
}
//printf("offsetX=%f, offsetY=%f\n",offsetX, offsetY);
}
}
void EditDepthArea::paintEvent(QPaintEvent *event)
{
//Scale
float ratio, offsetX, offsetY;
this->computeScaleOffsets(event->rect(), ratio, offsetX, offsetY);
QPainter painter(this);
painter.translate(offsetX, offsetY);
painter.scale(ratio, ratio);
if(showRGB_->isChecked() && !imageRGB_.isNull())
{
painter.setOpacity(0.5);
painter.drawImage(QPoint(0,0), imageRGB_);
}
painter.drawImage(QPoint(0,0), image_);
}
void EditDepthArea::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}
void floodfill(QImage & image, const cv::Mat & depthImage, int x, int y, float lastDepthValue, float error)
{
if(x>=0 && x<depthImage.cols &&
y>=0 && y<depthImage.rows)
{
float currentValue;
if(depthImage.type() == CV_32FC1)
{
currentValue = depthImage.at<float>(y, x);
}
else
{
currentValue = float(depthImage.at<unsigned short>(y, x))/1000.0f;
}
QRgb rgb = image.pixel(x,y);
if(qRed(rgb) == 0 && qGreen(rgb) == 0 == qBlue(rgb) == 0)
{
return;
}
if(currentValue == 0.0f)
{
return;
}
if(lastDepthValue>=0.0f && fabs(lastDepthValue - currentValue) > error*lastDepthValue)
{
return;
}
image.setPixel(x, y, 0);
floodfill(image, depthImage, x, y+1, currentValue, error);
floodfill(image, depthImage, x, y-1, currentValue, error);
floodfill(image, depthImage, x-1, y, currentValue, error);
floodfill(image, depthImage, x+1, y, currentValue, error);
}
}
void EditDepthArea::contextMenuEvent(QContextMenuEvent * e)
{
QAction * action = menu_->exec(e->globalPos());
if(action == showRGB_)
{
this->update();
}
else if(action == removeCluster_)
{
float scale, offsetX, offsetY;
computeScaleOffsets(rect(), scale, offsetX, offsetY);
QPoint pixel;
pixel.setX((e->pos().x()-offsetX)/scale);
pixel.setY((e->pos().y()-offsetY)/scale);
if(pixel.x()>=0 && pixel.x() < originalImage_.cols &&
pixel.y()>=0 && pixel.y() < originalImage_.rows)
{
floodfill(image_, originalImage_, pixel.x(), pixel.y(), -1.0f, 0.02f);
}
modified_=true;
this->update();
}
else if(action == setPenWidth_)
{
bool ok;
int width = QInputDialog::getInt(this, tr("Set Pen Width"), tr("Width:"), penWidth(), 1, 99, 1, &ok);
if(ok)
{
myPenWidth_ = width;
}
}
else if(action == resetChanges_)
{
this->resetChanges();
}
}
void EditDepthArea::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image_);
painter.setPen(QPen(Qt::black, myPenWidth_, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.drawLine(lastPoint_, endPoint);
modified_ = true;
update();
lastPoint_ = endPoint;
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Universite de Sherbrooke nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef EDITDEPTHAREA_H
#define EDITDEPTHAREA_H
#include <QColor>
#include <QImage>
#include <QPoint>
#include <QWidget>
#include <opencv2/opencv.hpp>
class QMenu;
class QAction;
namespace rtabmap {
class EditDepthArea : public QWidget
{
Q_OBJECT
public:
EditDepthArea(QWidget *parent = 0);
void setImage(const cv::Mat & depth, const cv::Mat & rgb = cv::Mat());
cv::Mat getModifiedImage() const;
bool isModified() const {return modified_;}
void setPenWidth(int newWidth);
int penWidth() const { return myPenWidth_; }
public slots:
void resetChanges();
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent * e) override;
private:
void drawLineTo(const QPoint &endPoint);
void computeScaleOffsets(const QRect & targetRect, float & scale, float & offsetX, float & offsetY) const;
bool modified_;
bool scribbling_;
int myPenWidth_;
QImage imageRGB_;
QImage image_;
cv::Mat originalImage_;
QPoint lastPoint_;
QMenu * menu_;
QAction * showRGB_;
QAction * removeCluster_;
QAction * resetChanges_;
QAction * setPenWidth_;
};
}
#endif
+234 -261
View File
@@ -132,9 +132,10 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->checkBox_gainCompensation, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor())); connect(_ui->checkBox_gainCompensation, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_gainRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged())); connect(_ui->doubleSpinBox_gainRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_gainOverlap, 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->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, SIGNAL(configChanged()));
connect(_ui->checkBox_meshing, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor())); 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_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, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_meshDecimationFactor, SIGNAL(valueChanged(double)), this, SLOT(updateReconstructionFlavor())); 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->doubleSpinBox_transferColorRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_cleanMesh, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged())); connect(_ui->checkBox_cleanMesh, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_mesh_minClusterSize, SIGNAL(valueChanged(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 #ifdef DISABLE_VTK
_ui->doubleSpinBox_meshDecimationFactor->setEnabled(false); _ui->doubleSpinBox_meshDecimationFactor->setEnabled(false);
_ui->spinBox_meshMaxPolygons->setEnabled(false);
_ui->label_meshDecimation->setEnabled(false);
_ui->label_meshMaxPolygons->setEnabled(false);
#endif #endif
} }
@@ -251,14 +257,14 @@ void ExportCloudsDialog::saveSettings(QSettings & settings, const QString & grou
settings.setValue("gain", _ui->checkBox_gainCompensation->isChecked()); settings.setValue("gain", _ui->checkBox_gainCompensation->isChecked());
settings.setValue("gain_radius", _ui->doubleSpinBox_gainRadius->value()); settings.setValue("gain_radius", _ui->doubleSpinBox_gainRadius->value());
settings.setValue("gain_overlap", _ui->doubleSpinBox_gainOverlap->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_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", _ui->checkBox_meshing->isChecked());
settings.setValue("mesh_radius", _ui->doubleSpinBox_gp3Radius->value()); settings.setValue("mesh_radius", _ui->doubleSpinBox_gp3Radius->value());
settings.setValue("mesh_mu", _ui->doubleSpinBox_gp3Mu->value()); settings.setValue("mesh_mu", _ui->doubleSpinBox_gp3Mu->value());
settings.setValue("mesh_decimation_factor", _ui->doubleSpinBox_meshDecimationFactor->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_color_radius", _ui->doubleSpinBox_transferColorRadius->value());
settings.setValue("mesh_clean", _ui->checkBox_cleanMesh->isChecked()); settings.setValue("mesh_clean", _ui->checkBox_cleanMesh->isChecked());
settings.setValue("mesh_min_cluster_size", _ui->spinBox_mesh_minClusterSize->value()); 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_textureCameraFiltering", _ui->checkBox_cameraFilter->isChecked());
settings.setValue("mesh_textureCameraFilteringRadius", _ui->doubleSpinBox_cameraFilterRadius->value()); settings.setValue("mesh_textureCameraFilteringRadius", _ui->doubleSpinBox_cameraFilterRadius->value());
settings.setValue("mesh_textureCameraFilteringAngle", _ui->doubleSpinBox_cameraFilterAngle->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()); 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->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_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_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->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->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_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_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->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->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->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()); _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->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_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->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->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()); _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->checkBox_gainCompensation->setChecked(false);
_ui->doubleSpinBox_gainRadius->setValue(0.02); _ui->doubleSpinBox_gainRadius->setValue(0.02);
_ui->doubleSpinBox_gainOverlap->setValue(0.05); _ui->doubleSpinBox_gainOverlap->setValue(0.0);
_ui->doubleSpinBox_gainAlpha->setValue(0.01);
_ui->doubleSpinBox_gainBeta->setValue(10); _ui->doubleSpinBox_gainBeta->setValue(10);
_ui->checkBox_gainLinkedLocationsOnly->setChecked(true); _ui->checkBox_gainFull->setChecked(false);
_ui->checkBox_meshing->setChecked(false); _ui->checkBox_meshing->setChecked(false);
_ui->doubleSpinBox_gp3Radius->setValue(0.2); _ui->doubleSpinBox_gp3Radius->setValue(0.2);
_ui->doubleSpinBox_gp3Mu->setValue(2.5); _ui->doubleSpinBox_gp3Mu->setValue(2.5);
_ui->doubleSpinBox_meshDecimationFactor->setValue(0.0); _ui->doubleSpinBox_meshDecimationFactor->setValue(0.0);
_ui->spinBox_meshMaxPolygons->setValue(0);
_ui->doubleSpinBox_transferColorRadius->setValue(0.025); _ui->doubleSpinBox_transferColorRadius->setValue(0.025);
_ui->checkBox_cleanMesh->setChecked(true); _ui->checkBox_cleanMesh->setChecked(true);
_ui->spinBox_mesh_minClusterSize->setValue(0); _ui->spinBox_mesh_minClusterSize->setValue(0);
@@ -456,6 +466,8 @@ void ExportCloudsDialog::restoreDefaults()
_ui->checkBox_cameraFilter->setChecked(false); _ui->checkBox_cameraFilter->setChecked(false);
_ui->doubleSpinBox_cameraFilterRadius->setValue(0.1); _ui->doubleSpinBox_cameraFilterRadius->setValue(0.1);
_ui->doubleSpinBox_cameraFilterAngle->setValue(30); _ui->doubleSpinBox_cameraFilterAngle->setValue(30);
_ui->spinBox_textureBrightnessContrastRatioLow->setValue(0);
_ui->spinBox_textureBrightnessContrastRatioHigh->setValue(0);
_ui->doubleSpinBox_mesh_angleTolerance->setValue(15.0); _ui->doubleSpinBox_mesh_angleTolerance->setValue(15.0);
_ui->checkBox_mesh_quad->setChecked(false); _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->setVisible(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->checkBox_smoothing->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1); _ui->checkBox_smoothing->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_denseReconstruction->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->doubleSpinBox_meshDecimationFactor->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_meshDecimation->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_organized->setVisible(_ui->comboBox_pipeline->currentIndex() == 0);
_ui->groupBox_regenerate->setVisible(_ui->checkBox_regenerate->isChecked()); _ui->groupBox_regenerate->setVisible(_ui->checkBox_regenerate->isChecked());
@@ -511,6 +527,7 @@ void ExportCloudsDialog::updateReconstructionFlavor()
_ui->checkBox_poisson_outputPolygons->setDisabled( _ui->checkBox_poisson_outputPolygons->setDisabled(
_ui->checkBox_binary->isEnabled() || _ui->checkBox_binary->isEnabled() ||
_ui->doubleSpinBox_meshDecimationFactor->value()!=0.0 || _ui->doubleSpinBox_meshDecimationFactor->value()!=0.0 ||
_ui->spinBox_meshMaxPolygons->value()!=0 ||
_ui->checkBox_textureMapping->isChecked()); _ui->checkBox_textureMapping->isChecked());
_ui->checkBox_cleanMesh->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1); _ui->checkBox_cleanMesh->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
@@ -952,9 +969,9 @@ bool ExportCloudsDialog::getExportedClouds(
if(_ui->checkBox_gainCompensation->isChecked() && clouds.size() > 1) if(_ui->checkBox_gainCompensation->isChecked() && clouds.size() > 1)
{ {
UASSERT(_compensator == 0); 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())); _progressDialog->appendText(tr("Full gain compensation of %1 clouds...").arg(clouds.size()));
} }
@@ -966,7 +983,7 @@ bool ExportCloudsDialog::getExportedClouds(
uSleep(100); uSleep(100);
QApplication::processEvents(); QApplication::processEvents();
if(!_ui->checkBox_gainLinkedLocationsOnly->isChecked()) if(_ui->checkBox_gainFull->isChecked())
{ {
std::multimap<int, Link> allLinks; 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) 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); pcl::toPCLPointCloud2(*mergedClouds, mesh->cloud);
mesh->polygons = mergedPolygons; 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)); meshes.insert(std::make_pair(0, mesh));
_progressDialog->incrementStep(); _progressDialog->incrementStep();
QApplication::processEvents(); QApplication::processEvents();
} }
} }
else else // dense pipeline
{ {
if(_ui->comboBox_meshingApproach->currentIndex() == 0) 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())); _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(); QApplication::processEvents();
if(_ui->doubleSpinBox_meshDecimationFactor->isEnabled() && if(mesh->polygons.size()>0)
_ui->doubleSpinBox_meshDecimationFactor->value() > 0.0)
{ {
unsigned int count = mesh->polygons.size(); double meshDecimationFactor = 0.0;
_progressDialog->appendText(tr("Mesh decimation (factor=%1) from %2 polygons...").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count)); if(_ui->doubleSpinBox_meshDecimationFactor->isEnabled() &&
QApplication::processEvents(); _ui->doubleSpinBox_meshDecimationFactor->value() > 0.0)
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())
{ {
_progressDialog->appendText(tr("Decimated mesh %1 has more polygons than before!").arg(iter->first), Qt::darkYellow); meshDecimationFactor = _ui->doubleSpinBox_meshDecimationFactor->value();
_progressDialog->setAutoClose(false);
} }
QApplication::processEvents(); if(_ui->spinBox_meshMaxPolygons->isEnabled() &&
lostColors = true; _ui->spinBox_meshMaxPolygons->value() > 0)
}
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)
{ {
std::vector<int> kIndices; double factor = 1.0-double(_ui->spinBox_meshMaxPolygons->value())/double(mesh->polygons.size());
std::vector<float> kDistances; if(factor > meshDecimationFactor)
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); 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 std::vector<int> kIndices;
int r=0; std::vector<float> kDistances;
int g=0; pcl::PointXYZRGBNormal pt;
int b=0; pt.x = coloredCloud->at(i).x;
int a=0; pt.y = coloredCloud->at(i).y;
for(unsigned int j=0; j<kIndices.size(); ++j) pt.z = coloredCloud->at(i).z;
if(_ui->doubleSpinBox_transferColorRadius->value() > 0.0)
{ {
r+=(int)iter->second->at(kIndices[j]).r; tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances);
g+=(int)iter->second->at(kIndices[j]).g; }
b+=(int)iter->second->at(kIndices[j]).b; else
a+=(int)iter->second->at(kIndices[j]).a; {
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 std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
coloredCloud->at(i).r = coloredCloud->at(i).g = coloredCloud->at(i).b = 255; int oi=0;
coloredPts.at(i) = false; 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); else if(lostColors &&
_ui->doubleSpinBox_transferColorRadius->value() > 0.0 &&
// remove polygons with no color _ui->checkBox_cleanMesh->isChecked() &&
if(_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()); std::vector<pcl::Vertices> filteredPolygons(mesh->polygons.size());
int oi=0; int oi=0;
for(unsigned int i=0; i<mesh->polygons.size(); ++i) 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) 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; break;
} }
} }
if(coloredPolygon) if(keepPolygon)
{ {
filteredPolygons[oi++] = mesh->polygons[i]; filteredPolygons[oi++] = mesh->polygons[i];
} }
@@ -1636,127 +1653,76 @@ bool ExportCloudsDialog::getExportedClouds(
filteredPolygons.resize(oi); filteredPolygons.resize(oi);
mesh->polygons = filteredPolygons; 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 if(_ui->spinBox_mesh_minClusterSize->value() &&
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGBNormal>(true)); !(_ui->checkBox_textureMapping->isEnabled() &&
tree->setInputCloud(iter->second); _ui->checkBox_textureMapping->isChecked() &&
pcl::PointCloud<pcl::PointXYZ>::Ptr optimizedCloud(new pcl::PointCloud<pcl::PointXYZ>); _ui->checkBox_cleanMesh->isChecked()))
pcl::fromPCLPointCloud2(mesh->cloud, *optimizedCloud);
std::vector<bool> closePts(optimizedCloud->size());
for(unsigned int i=0; i<optimizedCloud->size(); ++i)
{ {
std::vector<int> kIndices; _progressDialog->appendText(tr("Filter small polygon clusters..."));
std::vector<float> kDistances; QApplication::processEvents();
pcl::PointXYZRGBNormal pt;
pt.x = optimizedCloud->at(i).x; // filter polygons
pt.y = optimizedCloud->at(i).y; std::vector<std::set<int> > neighbors;
pt.z = optimizedCloud->at(i).z; std::vector<std::set<int> > vertexToPolygons;
tree->radiusSearch(pt, _ui->doubleSpinBox_transferColorRadius->value(), kIndices, kDistances); util3d::createPolygonIndexes(mesh->polygons,
if(kIndices.size()) 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 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; 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); filteredPolygons.resize(oi);
} }
}
else int before = (int)mesh->polygons.size();
{ mesh->polygons = filteredPolygons;
int oi=0;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter) _progressDialog->appendText(tr("Filtered %1 polygons.").arg(before-(int)mesh->polygons.size()));
{
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(); meshes.insert(std::make_pair(iter->first, mesh));
mesh->polygons = filteredPolygons; }
else
_progressDialog->appendText(tr("Filtered %1 polygons.").arg(before-(int)mesh->polygons.size())); {
QApplication::processEvents(); _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); _progressDialog->incrementStep(_ui->checkBox_assemble->isChecked()?poses.size():1);
QApplication::processEvents(); QApplication::processEvents();
@@ -2754,9 +2720,11 @@ cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<in
int cols = float(textureSize)/(scale*imageSize.width); int cols = float(textureSize)/(scale*imageSize.width);
globalTexture = cv::Mat(textureSize, textureSize, imageType, cv::Scalar::all(255)); 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 // make a blank texture
cv::Mat emptyImage(int(imageSize.height*scale), int(imageSize.width*scale), imageType, cv::Scalar::all(255)); 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; int oi=0;
for(int t=0; t<(int)textures.size(); ++t) 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()); UASSERT(resizedImage.type() == globalTexture.type());
resizedImage.copyTo(globalTexture(cv::Rect(u, v, resizedImage.cols, resizedImage.rows))); resizedImage.copyTo(globalTexture(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
emptyImageMask.copyTo(globalTextureMask(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
} }
else else
{ {
@@ -2806,6 +2775,10 @@ cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<in
++oi; ++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());
}
} }
} }
} }
+138 -9
View File
@@ -4164,6 +4164,21 @@ void MainWindow::editDatabase()
QString path = QFileDialog::getOpenFileName(this, tr("Edit database..."), _preferencesDialog->getWorkingDirectory(), tr("RTAB-Map database files (*.db)")); QString path = QFileDialog::getOpenFileName(this, tr("Edit database..."), _preferencesDialog->getWorkingDirectory(), tr("RTAB-Map database files (*.db)"));
if(!path.isEmpty()) 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); DatabaseViewer * viewer = new DatabaseViewer(_preferencesDialog->getTmpIniFilePath(), this);
viewer->setWindowModality(Qt::WindowModal); viewer->setWindowModality(Qt::WindowModal);
viewer->setAttribute(Qt::WA_DeleteOnClose, true); viewer->setAttribute(Qt::WA_DeleteOnClose, true);
@@ -4718,7 +4733,11 @@ void MainWindow::postProcessing()
ParametersMap parameters = _preferencesDialog->getAllParameters(); ParametersMap parameters = _preferencesDialog->getAllParameters();
Optimizer * optimizer = Optimizer::create(parameters); Optimizer * optimizer = Optimizer::create(parameters);
bool optimizeFromGraphEnd = Parameters::defaultRGBDOptimizeFromGraphEnd(); bool optimizeFromGraphEnd = Parameters::defaultRGBDOptimizeFromGraphEnd();
float optimizeMaxError = Parameters::defaultRGBDOptimizeMaxError();
int optimizeIterations = Parameters::defaultOptimizerIterations();
Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), optimizeFromGraphEnd); Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), optimizeFromGraphEnd);
Parameters::parse(parameters, Parameters::kRGBDOptimizeMaxError(), optimizeMaxError);
Parameters::parse(parameters, Parameters::kOptimizerIterations(), optimizeIterations);
bool warn = false; bool warn = false;
int loopClosuresAdded = 0; int loopClosuresAdded = 0;
@@ -4801,9 +4820,6 @@ void MainWindow::postProcessing()
delete registration; delete registration;
if(!transform.isNull()) if(!transform.isNull())
{ {
UINFO("Added new loop closure between %d and %d.", from, to);
addedLinks.insert(from);
addedLinks.insert(to);
if(!transform.isIdentity()) if(!transform.isIdentity())
{ {
// normalize variance // normalize variance
@@ -4812,10 +4828,124 @@ void MainWindow::postProcessing()
info.varianceLin = info.varianceLin>0.0f?info.varianceLin:0.0001f; // epsilon if exact transform 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 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; //optimize the graph to see if the new constraint is globally valid
_initProgressDialog->appendText(tr("Detected loop closure %1->%2! (%3/%4)").arg(from).arg(to).arg(i+1).arg(clusters.size())); bool updateConstraint = true;
QApplication::processEvents(); 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->incrementStep();
} }
_initProgressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!") _initProgressDialog->appendText(tr("Iteration %1/%2: Detected %3 loop closures!").arg(n+1).arg(detectLoopClosureIterations).arg(addedLinks.size()/2));
.arg(n+1).arg(detectLoopClosureIterations).arg(addedLinks.size()/2));
if(addedLinks.size() == 0) if(addedLinks.size() == 0)
{ {
break; break;
+95
View File
@@ -2571,6 +2571,13 @@ void PreferencesDialog::saveWindowGeometry(const QWidget * window)
settings.setValue("geometry", window->saveGeometry()); settings.setValue("geometry", window->saveGeometry());
settings.endGroup(); // "windowName" settings.endGroup(); // "windowName"
settings.endGroup(); // rtabmap 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(); // "windowName"
settings.endGroup(); // rtabmap 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.setValue("status_bar", mainWindow->statusBar()->isVisible());
settings.endGroup(); // "MainWindow" settings.endGroup(); // "MainWindow"
settings.endGroup(); // rtabmap 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); mainWindow->statusBar()->setVisible(statusBarShown);
settings.endGroup(); // "MainWindow" settings.endGroup(); // "MainWindow"
settings.endGroup(); // rtabmap 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("Gui");
settings.beginGroup(widget->objectName()); 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 CloudViewer * cloudViewer = qobject_cast<const CloudViewer*>(widget);
const ImageView * imageView = qobject_cast<const ImageView*>(widget); const ImageView * imageView = qobject_cast<const ImageView*>(widget);
const ExportCloudsDialog * exportCloudsDialog = qobject_cast<const ExportCloudsDialog*>(widget); const ExportCloudsDialog * exportCloudsDialog = qobject_cast<const ExportCloudsDialog*>(widget);
@@ -2652,34 +2688,42 @@ void PreferencesDialog::saveWidgetState(const QWidget * widget)
if(cloudViewer) if(cloudViewer)
{ {
cloudViewer->saveSettings(settings); cloudViewer->saveSettings(settings);
cloudViewer->saveSettings(settingsTmp);
} }
else if(imageView) else if(imageView)
{ {
imageView->saveSettings(settings); imageView->saveSettings(settings);
imageView->saveSettings(settingsTmp);
} }
else if(exportCloudsDialog) else if(exportCloudsDialog)
{ {
exportCloudsDialog->saveSettings(settings); exportCloudsDialog->saveSettings(settings);
exportCloudsDialog->saveSettings(settingsTmp);
} }
else if(exportScansDialog) else if(exportScansDialog)
{ {
exportScansDialog->saveSettings(settings); exportScansDialog->saveSettings(settings);
exportScansDialog->saveSettings(settingsTmp);
} }
else if(postProcessingDialog) else if(postProcessingDialog)
{ {
postProcessingDialog->saveSettings(settings); postProcessingDialog->saveSettings(settings);
postProcessingDialog->saveSettings(settingsTmp);
} }
else if(graphViewer) else if(graphViewer)
{ {
graphViewer->saveSettings(settings); graphViewer->saveSettings(settings);
graphViewer->saveSettings(settingsTmp);
} }
else if(calibrationDialog) else if(calibrationDialog)
{ {
calibrationDialog->saveSettings(settings); calibrationDialog->saveSettings(settings);
calibrationDialog->saveSettings(settingsTmp);
} }
else if(depthCalibrationDialog) else if(depthCalibrationDialog)
{ {
depthCalibrationDialog->saveSettings(settings); depthCalibrationDialog->saveSettings(settings);
depthCalibrationDialog->saveSettings(settingsTmp);
} }
else else
{ {
@@ -2688,6 +2732,8 @@ void PreferencesDialog::saveWidgetState(const QWidget * widget)
settings.endGroup(); // "name" settings.endGroup(); // "name"
settings.endGroup(); // Gui settings.endGroup(); // Gui
settingsTmp.endGroup();
settingsTmp.endGroup();
} }
} }
@@ -2700,6 +2746,10 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
settings.beginGroup("Gui"); settings.beginGroup("Gui");
settings.beginGroup(widget->objectName()); settings.beginGroup(widget->objectName());
QSettings settingsTmp(getTmpIniFilePath(), QSettings::IniFormat);
settingsTmp.beginGroup("Gui");
settingsTmp.beginGroup(widget->objectName());
CloudViewer * cloudViewer = qobject_cast<CloudViewer*>(widget); CloudViewer * cloudViewer = qobject_cast<CloudViewer*>(widget);
ImageView * imageView = qobject_cast<ImageView*>(widget); ImageView * imageView = qobject_cast<ImageView*>(widget);
ExportCloudsDialog * exportCloudsDialog = qobject_cast<ExportCloudsDialog*>(widget); ExportCloudsDialog * exportCloudsDialog = qobject_cast<ExportCloudsDialog*>(widget);
@@ -2712,34 +2762,42 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
if(cloudViewer) if(cloudViewer)
{ {
cloudViewer->loadSettings(settings); cloudViewer->loadSettings(settings);
cloudViewer->saveSettings(settingsTmp);
} }
else if(imageView) else if(imageView)
{ {
imageView->loadSettings(settings); imageView->loadSettings(settings);
imageView->saveSettings(settingsTmp);
} }
else if(exportCloudsDialog) else if(exportCloudsDialog)
{ {
exportCloudsDialog->loadSettings(settings); exportCloudsDialog->loadSettings(settings);
exportCloudsDialog->saveSettings(settingsTmp);
} }
else if(exportScansDialog) else if(exportScansDialog)
{ {
exportScansDialog->loadSettings(settings); exportScansDialog->loadSettings(settings);
exportScansDialog->saveSettings(settingsTmp);
} }
else if(postProcessingDialog) else if(postProcessingDialog)
{ {
postProcessingDialog->loadSettings(settings); postProcessingDialog->loadSettings(settings);
postProcessingDialog->saveSettings(settingsTmp);
} }
else if(graphViewer) else if(graphViewer)
{ {
graphViewer->loadSettings(settings); graphViewer->loadSettings(settings);
graphViewer->saveSettings(settingsTmp);
} }
else if(calibrationDialog) else if(calibrationDialog)
{ {
calibrationDialog->loadSettings(settings); calibrationDialog->loadSettings(settings);
calibrationDialog->saveSettings(settingsTmp);
} }
else if(depthCalibrationDialog) else if(depthCalibrationDialog)
{ {
depthCalibrationDialog->loadSettings(settings); depthCalibrationDialog->loadSettings(settings);
depthCalibrationDialog->saveSettings(settingsTmp);
} }
else else
{ {
@@ -2748,6 +2806,8 @@ void PreferencesDialog::loadWidgetState(QWidget * widget)
settings.endGroup(); //"name" settings.endGroup(); //"name"
settings.endGroup(); // Gui 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.setValue(key, value);
settings.endGroup(); // "section" settings.endGroup(); // "section"
settings.endGroup(); // rtabmap 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) 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(); value = settings.value(key, QString()).toString();
settings.endGroup(); // "section" settings.endGroup(); // "section"
settings.endGroup(); // rtabmap 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; 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() void PreferencesDialog::selectSourceDatabase()
{ {
QString dir = _ui->source_database_lineEdit_path->text(); QString dir = _ui->source_database_lineEdit_path->text();
@@ -2883,6 +2963,21 @@ void PreferencesDialog::selectSourceDatabase()
_ui->general_spinBox_imagesBufferSize->setValue(0); _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_database_lineEdit_path->setText(paths.size()==1?paths.front():paths.join(";"));
_ui->source_spinBox_databaseStartPos->setValue(0); _ui->source_spinBox_databaseStartPos->setValue(0);
_ui->source_spinBox_database_cameraIndex->setValue(-1); _ui->source_spinBox_database_cameraIndex->setValue(-1);
+12 -4
View File
@@ -42,7 +42,8 @@ namespace rtabmap {
ProgressDialog::ProgressDialog(QWidget *parent, Qt::WindowFlags flags) : ProgressDialog::ProgressDialog(QWidget *parent, Qt::WindowFlags flags) :
QDialog(parent, flags), QDialog(parent, flags),
_delayedClosingTime(1) _delayedClosingTime(1),
_canceled(false)
{ {
_text = new QLabel(this); _text = new QLabel(this);
_text->setWordWrap(true); _text->setWordWrap(true);
@@ -62,7 +63,7 @@ ProgressDialog::ProgressDialog(QWidget *parent, Qt::WindowFlags flags) :
_endMessage = "Finished!"; _endMessage = "Finished!";
this->clear(); this->clear();
connect(_closeButton, SIGNAL(clicked()), this, SLOT(close())); 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); QVBoxLayout * layout = new QVBoxLayout(this);
layout->addWidget(_text); layout->addWidget(_text);
@@ -146,15 +147,15 @@ void ProgressDialog::incrementStep(int steps)
void ProgressDialog::clear() void ProgressDialog::clear()
{ {
_text->clear(); _text->clear();
_progressBar->reset();
_detailedText->clear(); _detailedText->clear();
_closeButton->setEnabled(false); resetProgress();
} }
void ProgressDialog::resetProgress() void ProgressDialog::resetProgress()
{ {
_progressBar->reset(); _progressBar->reset();
_closeButton->setEnabled(false); _closeButton->setEnabled(false);
_canceled = false;
} }
void ProgressDialog::closeDialog() void ProgressDialog::closeDialog()
@@ -169,6 +170,7 @@ void ProgressDialog::closeEvent(QCloseEvent *event)
{ {
if(_progressBar->value() == _progressBar->maximum()) if(_progressBar->value() == _progressBar->maximum())
{ {
_canceled = false;
event->accept(); event->accept();
} }
else else
@@ -177,4 +179,10 @@ void ProgressDialog::closeEvent(QCloseEvent *event)
} }
} }
void ProgressDialog::cancel()
{
_canceled = true;
emit canceled();
}
} }
+17 -10
View File
@@ -518,6 +518,8 @@
<addaction name="actionRegenerate_local_grid_maps"/> <addaction name="actionRegenerate_local_grid_maps"/>
<addaction name="actionRegenerate_local_grid_maps_selected"/> <addaction name="actionRegenerate_local_grid_maps_selected"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionEdit_depth_image"/>
<addaction name="separator"/>
<addaction name="actionReset_all_changes"/> <addaction name="actionReset_all_changes"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionView_3D_map"/> <addaction name="actionView_3D_map"/>
@@ -583,7 +585,7 @@
<string>Odom Frame</string> <string>Odom Frame</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -987,7 +989,7 @@
<item> <item>
<widget class="QToolBox" name="toolBox"> <widget class="QToolBox" name="toolBox">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>2</number>
</property> </property>
<widget class="QWidget" name="page_3"> <widget class="QWidget" name="page_3">
<property name="geometry"> <property name="geometry">
@@ -995,7 +997,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>324</width> <width>324</width>
<height>188</height> <height>207</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@@ -1461,8 +1463,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>201</width> <width>283</width>
<height>126</height> <height>222</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@@ -1484,7 +1486,7 @@
<double>0.100000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="value"> <property name="value">
<double>0.300000000000000</double> <double>1.000000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
@@ -1530,7 +1532,7 @@
<number>100</number> <number>100</number>
</property> </property>
<property name="value"> <property name="value">
<number>1</number> <number>5</number>
</property> </property>
</widget> </widget>
</item> </item>
@@ -1561,8 +1563,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>186</width> <width>268</width>
<height>496</height> <height>304</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@@ -1845,7 +1847,7 @@
<string>Odom Frame</string> <string>Odom Frame</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -2134,6 +2136,11 @@
<string>Regenerate local grid maps (selected)...</string> <string>Regenerate local grid maps (selected)...</string>
</property> </property>
</action> </action>
<action name="actionEdit_depth_image">
<property name="text">
<string>Edit depth image...</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
+226 -183
View File
@@ -23,9 +23,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-1800</y> <y>-1454</y>
<width>773</width> <width>773</width>
<height>3200</height> <height>3289</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_13"> <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> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout_12" columnstretch="0,1"> <layout class="QGridLayout" name="gridLayout_12" columnstretch="0,1">
<item row="2" column="1"> <item row="1" column="0">
<widget class="QLabel" name="label_binaryFile_4"> <widget class="QDoubleSpinBox" name="doubleSpinBox_gainOverlap">
<property name="text"> <property name="suffix">
<string>Alpha</string> <string/>
</property> </property>
<property name="wordWrap"> <property name="decimals">
<bool>true</bool> <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> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <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"> <widget class="QDoubleSpinBox" name="doubleSpinBox_gainBeta">
<property name="suffix"> <property name="suffix">
<string/> <string/>
</property> </property>
<property name="decimals"> <property name="decimals">
<number>0</number> <number>1</number>
</property> </property>
<property name="minimum"> <property name="minimum">
<double>1.000000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="maximum"> <property name="maximum">
<double>1000.000000000000000</double> <double>1000.000000000000000</double>
@@ -1107,10 +1126,10 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="2" column="1">
<widget class="QLabel" name="label_binaryFile_5"> <widget class="QLabel" name="label_binaryFile_5">
<property name="text"> <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>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -1139,28 +1158,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </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"> <item row="1" column="1">
<widget class="QLabel" name="label_binaryFile_6"> <widget class="QLabel" name="label_binaryFile_6">
<property name="text"> <property name="text">
@@ -1171,45 +1168,16 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="3" column="1">
<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">
<widget class="QLabel" name="label_binaryFile_7"> <widget class="QLabel" name="label_binaryFile_7">
<property name="text"> <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>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBox_gainLinkedLocationsOnly">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@@ -1224,60 +1192,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<layout class="QVBoxLayout" name="verticalLayout_15"> <layout class="QVBoxLayout" name="verticalLayout_15">
<item> <item>
<layout class="QGridLayout" name="gridLayout_10" columnstretch="0,1"> <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,1">
<item row="3" column="1"> <item row="5" 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">
<widget class="QLabel" name="label_meshClean"> <widget class="QLabel" name="label_meshClean">
<property name="text"> <property name="text">
<string>Clean mesh from polygons without color or texture.</string> <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> </property>
</widget> </widget>
</item> </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"> <item row="0" column="1">
<widget class="QLabel" name="label_denseReconstruction"> <widget class="QLabel" name="label_denseReconstruction">
<property name="text"> <property name="text">
@@ -1340,7 +1231,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="3" column="1">
<widget class="QLabel" name="label_meshDecimation_2"> <widget class="QLabel" name="label_meshDecimation_2">
<property name="text"> <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> <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> </property>
</widget> </widget>
</item> </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> </layout>
</item> </item>
<item> <item>
@@ -1376,6 +1373,16 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<layout class="QVBoxLayout" name="verticalLayout_16"> <layout class="QVBoxLayout" name="verticalLayout_16">
<item> <item>
<layout class="QGridLayout" name="gridLayout_15" columnstretch="0,1"> <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"> <item row="0" column="1">
<widget class="QLabel" name="label_meshingTextureFormat"> <widget class="QLabel" name="label_meshingTextureFormat">
<property name="text"> <property name="text">
@@ -1459,28 +1466,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </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"> <item row="2" column="1">
<widget class="QLabel" name="label_meshingTextureSize_2"> <widget class="QLabel" name="label_meshingTextureSize_2">
<property name="text"> <property name="text">
@@ -1491,23 +1476,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </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"> <item row="3" column="1">
<widget class="QLabel" name="label_meshingTextureSize_4"> <widget class="QLabel" name="label_meshingTextureSize_4">
<property name="text"> <property name="text">
@@ -1528,6 +1496,81 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </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> </layout>
</item> </item>
<item> <item>