Export: Added merging textures option (default true)

This commit is contained in:
matlabbe
2017-01-19 11:20:48 -05:00
parent 0a98df922e
commit 496319a2b0
5 changed files with 667 additions and 282 deletions

View File

@@ -246,6 +246,9 @@ pcl::PointCloud<pcl::PointXYZRGB>::Ptr RTABMAP_EXP concatenateClouds(
pcl::TextureMesh::Ptr RTABMAP_EXP concatenateTextureMeshes(
const std::list<pcl::TextureMesh::Ptr> & meshes);
void RTABMAP_EXP concatenateTextureMaterials(
pcl::TextureMesh & mesh, const cv::Size & imageSize, int textureSize, float & scale, std::vector<bool> * materialsKept=0);
/**
* @brief Concatenate a vector of indices to a single vector.
*

View File

@@ -1694,59 +1694,186 @@ pcl::TextureMesh::Ptr concatenateTextureMeshes(const std::list<pcl::TextureMesh:
std::map<std::string, int> addedMaterials; //<file, index>
for(std::list<pcl::TextureMesh::Ptr>::const_iterator iter = meshes.begin(); iter!=meshes.end(); ++iter)
{
// append point cloud
int polygonStep = output->cloud.height * output->cloud.width;
pcl::PCLPointCloud2 tmp;
pcl::concatenatePointCloud(output->cloud, iter->get()->cloud, tmp);
output->cloud = tmp;
UASSERT((*iter)->tex_polygons.size() == (*iter)->tex_coordinates.size() &&
(*iter)->tex_polygons.size() == (*iter)->tex_materials.size());
int materialCount = (*iter)->tex_polygons.size();
for(int i=0; i<materialCount; ++i)
if((*iter)->cloud.point_step &&
(*iter)->cloud.data.size()/(*iter)->cloud.point_step &&
(*iter)->tex_polygons.size() &&
(*iter)->tex_coordinates.size())
{
std::map<std::string, int>::iterator jter = addedMaterials.find((*iter)->tex_materials[i].tex_file);
int index;
if(jter != addedMaterials.end())
{
index = jter->second;
}
else
{
addedMaterials.insert(std::make_pair((*iter)->tex_materials[i].tex_file, output->tex_materials.size()));
index = output->tex_materials.size();
output->tex_materials.push_back((*iter)->tex_materials[i]);
output->tex_materials.back().tex_name = uFormat("material_%d", index);
output->tex_polygons.resize(output->tex_polygons.size() + 1);
output->tex_coordinates.resize(output->tex_coordinates.size() + 1);
}
// append point cloud
int polygonStep = output->cloud.height * output->cloud.width;
pcl::PCLPointCloud2 tmp;
pcl::concatenatePointCloud(output->cloud, iter->get()->cloud, tmp);
output->cloud = tmp;
// update and append polygon indices
int oi = output->tex_polygons[index].size();
output->tex_polygons[index].resize(output->tex_polygons[index].size() + (*iter)->tex_polygons[i].size());
for(unsigned int j=0; j<(*iter)->tex_polygons[i].size(); ++j)
UASSERT((*iter)->tex_polygons.size() == (*iter)->tex_coordinates.size() &&
(*iter)->tex_polygons.size() == (*iter)->tex_materials.size());
int materialCount = (*iter)->tex_polygons.size();
for(int i=0; i<materialCount; ++i)
{
pcl::Vertices polygon = (*iter)->tex_polygons[i][j];
for(unsigned int k=0; k<polygon.vertices.size(); ++k)
std::map<std::string, int>::iterator jter = addedMaterials.find((*iter)->tex_materials[i].tex_file);
int index;
if(jter != addedMaterials.end())
{
polygon.vertices[k] += polygonStep;
index = jter->second;
}
else
{
addedMaterials.insert(std::make_pair((*iter)->tex_materials[i].tex_file, output->tex_materials.size()));
index = output->tex_materials.size();
output->tex_materials.push_back((*iter)->tex_materials[i]);
output->tex_materials.back().tex_name = uFormat("material_%d", index);
output->tex_polygons.resize(output->tex_polygons.size() + 1);
output->tex_coordinates.resize(output->tex_coordinates.size() + 1);
}
output->tex_polygons[index][oi+j] = polygon;
}
// append uv coordinates
oi = output->tex_coordinates[index].size();
output->tex_coordinates[index].resize(output->tex_coordinates[index].size() + (*iter)->tex_coordinates[i].size());
for(unsigned int j=0; j<(*iter)->tex_coordinates[i].size(); ++j)
{
output->tex_coordinates[index][oi+j] = (*iter)->tex_coordinates[i][j];
// update and append polygon indices
int oi = output->tex_polygons[index].size();
output->tex_polygons[index].resize(output->tex_polygons[index].size() + (*iter)->tex_polygons[i].size());
for(unsigned int j=0; j<(*iter)->tex_polygons[i].size(); ++j)
{
pcl::Vertices polygon = (*iter)->tex_polygons[i][j];
for(unsigned int k=0; k<polygon.vertices.size(); ++k)
{
polygon.vertices[k] += polygonStep;
}
output->tex_polygons[index][oi+j] = polygon;
}
// append uv coordinates
oi = output->tex_coordinates[index].size();
output->tex_coordinates[index].resize(output->tex_coordinates[index].size() + (*iter)->tex_coordinates[i].size());
for(unsigned int j=0; j<(*iter)->tex_coordinates[i].size(); ++j)
{
output->tex_coordinates[index][oi+j] = (*iter)->tex_coordinates[i][j];
}
}
}
}
return output;
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
void concatenateTextureMaterials(pcl::TextureMesh & mesh, const cv::Size & imageSize, int textureSize, float & scale, std::vector<bool> * materialsKept)
{
UASSERT(textureSize>0 && imageSize.width>0 && imageSize.height>0);
int materials = 0;
for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
{
if(mesh.tex_polygons.size())
{
++materials;
}
}
if(materials)
{
int w = imageSize.width; // 640
int h = imageSize.height; // 480
int g = gcd(w,h); // 160
int a = w/g; // 4=640/160
int b = h/g; // 3=480/160
UDEBUG("w=%d h=%d g=%d a=%d b=%d", w, h, g, a, b);
int colCount = 0;
int rowCount = 0;
float factor = 0.1f;
float epsilon = 0.001f;
scale = 1.0f;
while(colCount*rowCount < materials || (factor == 0.1f || scale > 1.0f))
{
// first run try scale = 1 (no scaling)
if(factor!=0.1f)
{
scale = float(textureSize)/float(w*b*factor);
}
colCount = float(textureSize)/(scale*float(w));
rowCount = float(textureSize)/(scale*float(h));
factor+=epsilon; // search the maximum perfect fit
}
UDEBUG("materials=%d col=%d row=%d factor=%f scale=%f", materials, colCount, rowCount, factor-epsilon, scale);
UASSERT(mesh.tex_coordinates.size() == mesh.tex_materials.size() && mesh.tex_polygons.size() == mesh.tex_materials.size());
// prepare size
int totalPolygons = 0;
int totalCoordinates = 0;
for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
{
if(mesh.tex_polygons[i].size())
{
totalPolygons+=mesh.tex_polygons[i].size();
totalCoordinates+=mesh.tex_coordinates[i].size();
}
}
std::vector<pcl::Vertices> newPolygons(totalPolygons);
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > newCoordinates(totalCoordinates); // UV coordinates
#else
std::vector<Eigen::Vector2f> newCoordinates(totalCoordinates); // UV coordinates
#endif
int pi = 0;
int ci = 0;
int ti=0;
float scaledHeight = float(int(scale*float(h)))/float(textureSize);
float scaledWidth = float(int(scale*float(w)))/float(textureSize);
float lowerBorderSize = 1.0f - scaledHeight*float(rowCount);
UDEBUG("scaledWidth=%f scaledHeight=%f lowerBorderSize=%f", scaledWidth, scaledHeight, lowerBorderSize);
if(materialsKept)
{
materialsKept->resize(mesh.tex_materials.size(), 0);
}
for(unsigned int t=0; t<mesh.tex_materials.size(); ++t)
{
if(mesh.tex_polygons[t].size())
{
int row = ti/colCount;
int col = ti%colCount;
float offsetU = scaledWidth * float(col);
float offsetV = scaledHeight * float((rowCount - 1) - row) + lowerBorderSize;
// Texture coords have lower-left origin
for(unsigned int i=0; i<mesh.tex_polygons[t].size(); ++i)
{
newPolygons[pi++] = mesh.tex_polygons[t].at(i);
}
for(unsigned int i=0; i<mesh.tex_coordinates[t].size(); ++i)
{
const Eigen::Vector2f & v = mesh.tex_coordinates[t].at(i);
if(v[0] >= 0 && v[1] >=0)
{
newCoordinates[ci][0] = v[0]*scaledWidth + offsetU;
newCoordinates[ci][1] = v[1]*scaledHeight + offsetV;
}
else
{
newCoordinates[ci] = v;
}
++ci;
}
++ti;
if(materialsKept)
{
materialsKept->at(t) = true;
}
}
}
pcl::TexMaterial m = mesh.tex_materials.front();
mesh.tex_materials.clear();
m.tex_file = "texture";
m.tex_name = "material";
mesh.tex_materials.push_back(m);
mesh.tex_coordinates.clear();
mesh.tex_coordinates.push_back(newCoordinates);
mesh.tex_polygons.clear();
mesh.tex_polygons.push_back(newPolygons);
}
}
pcl::IndicesPtr concatenate(const std::vector<pcl::IndicesPtr> & indices)
{
//compute total size

View File

@@ -80,7 +80,7 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->comboBox_pipeline, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->comboBox_pipeline, SIGNAL(currentIndexChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->comboBox_meshingApproach, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->comboBox_meshingApproach, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDenseReconstruction()));
connect(_ui->comboBox_meshingApproach, SIGNAL(currentIndexChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->groupBox_regenerate, SIGNAL(clicked(bool)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_decimation, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
@@ -101,7 +101,7 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->spinBox_filteringMinNeighbors, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_assemble, SIGNAL(clicked(bool)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_assemble, SIGNAL(clicked(bool)), this, SLOT(updateTexturingAvailability()));
connect(_ui->checkBox_assemble, SIGNAL(clicked(bool)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_voxelSize_assembled, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->groupBox_subtraction, SIGNAL(clicked(bool)), this, SIGNAL(configChanged()));
@@ -129,15 +129,17 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->checkBox_gainLinkedLocationsOnly, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->groupBox_meshing, SIGNAL(clicked(bool)), this, SIGNAL(configChanged()));
connect(_ui->groupBox_meshing, SIGNAL(toggled(bool)), this, SIGNAL(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_gp3Radius, 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, SLOT(updatePoissonOutputPolygonsAvailability()));
connect(_ui->doubleSpinBox_meshDecimationFactor, SIGNAL(valueChanged(double)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_transferColorRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_cleanMesh, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_textureMapping, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_textureMapping, SIGNAL(stateChanged(int)), this, SLOT(updateTexturingAvailability()));
connect(_ui->checkBox_textureMapping, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->comboBox_meshingTextureFormat, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->comboBox_meshingTextureSize, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_poisson_outputPolygons, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_poisson_manifold, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
@@ -178,26 +180,6 @@ void ExportCloudsDialog::updateMLSGrpVisibility()
_ui->groupBox_4->setVisible(_ui->comboBox_upsamplingMethod->currentIndex() == 3);
_ui->groupBox_5->setVisible(_ui->comboBox_upsamplingMethod->currentIndex() == 4);
}
void ExportCloudsDialog::updateTexturingAvailability()
{
updateTexturingAvailability(_ui->checkBox_binary->isEnabled());
_ui->comboBox_meshingApproach->setCurrentIndex(_ui->checkBox_assemble->isChecked()?1:0);
_ui->comboBox_meshingApproach->setItemData(1, _ui->checkBox_assemble->isChecked()?1 | 32:0,Qt::UserRole - 1);
}
void ExportCloudsDialog::updateTexturingAvailability(bool isExporting)
{
_ui->checkBox_textureMapping->setEnabled(!_ui->checkBox_assemble->isChecked() || isExporting);
_ui->label_textureMapping->setEnabled(_ui->checkBox_textureMapping->isEnabled());
_ui->comboBox_meshingTextureFormat->setEnabled(_ui->label_textureMapping->isEnabled());
_ui->label_meshingTextureFormat->setEnabled(_ui->label_textureMapping->isEnabled());
updatePoissonOutputPolygonsAvailability();
}
void ExportCloudsDialog::updatePoissonOutputPolygonsAvailability()
{
_ui->checkBox_poisson_outputPolygons->setDisabled(
_ui->checkBox_binary->isEnabled() ||
_ui->doubleSpinBox_meshDecimationFactor->value()!=0.0);
}
void ExportCloudsDialog::cancel()
{
@@ -269,6 +251,7 @@ void ExportCloudsDialog::saveSettings(QSettings & settings, const QString & grou
settings.setValue("mesh_texture", _ui->checkBox_textureMapping->isChecked());
settings.setValue("mesh_textureFormat", _ui->comboBox_meshingTextureFormat->currentIndex());
settings.setValue("mesh_textureSize", _ui->comboBox_meshingTextureSize->currentIndex());
settings.setValue("mesh_angle_tolerance", _ui->doubleSpinBox_mesh_angleTolerance->value());
settings.setValue("mesh_quad", _ui->checkBox_mesh_quad->isChecked());
@@ -355,6 +338,7 @@ void ExportCloudsDialog::loadSettings(QSettings & settings, const QString & grou
_ui->checkBox_textureMapping->setChecked(settings.value("mesh_texture", _ui->checkBox_textureMapping->isChecked()).toBool());
_ui->comboBox_meshingTextureFormat->setCurrentIndex(settings.value("mesh_textureFormat", _ui->comboBox_meshingTextureFormat->currentIndex()).toInt());
_ui->comboBox_meshingTextureSize->setCurrentIndex(settings.value("mesh_textureSize", _ui->comboBox_meshingTextureSize->currentIndex()).toInt());
_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());
@@ -371,7 +355,6 @@ void ExportCloudsDialog::loadSettings(QSettings & settings, const QString & grou
_ui->doubleSpinBox_poisson_scale->setValue(settings.value("poisson_scale", _ui->doubleSpinBox_poisson_scale->value()).toDouble());
updateReconstructionFlavor();
updateTexturingAvailability();
updateMLSGrpVisibility();
if(!group.isEmpty())
@@ -429,16 +412,17 @@ void ExportCloudsDialog::restoreDefaults()
_ui->checkBox_gainLinkedLocationsOnly->setChecked(true);
_ui->groupBox_meshing->setChecked(false);
_ui->doubleSpinBox_gp3Radius->setValue(0.04);
_ui->doubleSpinBox_gp3Radius->setValue(0.2);
_ui->doubleSpinBox_gp3Mu->setValue(2.5);
_ui->doubleSpinBox_meshDecimationFactor->setValue(0.0);
_ui->doubleSpinBox_transferColorRadius->setValue(0.05);
_ui->doubleSpinBox_transferColorRadius->setValue(0.2);
_ui->checkBox_cleanMesh->setChecked(true);
_ui->comboBox_meshingApproach->setCurrentIndex(1);
_ui->checkBox_textureMapping->setChecked(false);
_ui->comboBox_meshingTextureFormat->setCurrentIndex(0);
_ui->comboBox_meshingTextureSize->setCurrentIndex(4); // 2048
_ui->doubleSpinBox_mesh_angleTolerance->setValue(15.0);
_ui->checkBox_mesh_quad->setChecked(false);
@@ -455,7 +439,6 @@ void ExportCloudsDialog::restoreDefaults()
_ui->doubleSpinBox_poisson_scale->setValue(1.1);
updateReconstructionFlavor();
updateTexturingAvailability();
updateMLSGrpVisibility();
this->update();
@@ -465,18 +448,28 @@ void ExportCloudsDialog::updateReconstructionFlavor()
{
_ui->groupBox_mls->setVisible(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->groupBox_mls->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->comboBox_meshingApproach->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_denseReconstruction->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->groupBox_organized->setVisible(_ui->comboBox_pipeline->currentIndex() == 0);
updateDenseReconstruction();
}
void ExportCloudsDialog::updateDenseReconstruction()
{
// dense texturing options
_ui->groupBox_gp3->setVisible(_ui->comboBox_pipeline->currentIndex() == 1 && _ui->comboBox_meshingApproach->currentIndex()==0);
_ui->groupBox_poisson->setVisible(_ui->comboBox_pipeline->currentIndex() == 1 && _ui->comboBox_meshingApproach->currentIndex()==1);
if(_ui->groupBox_meshing->isChecked())
{
_ui->comboBox_meshingApproach->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->comboBox_meshingApproach->setCurrentIndex(_ui->checkBox_assemble->isChecked()?1:0);
_ui->comboBox_meshingApproach->setItemData(1, _ui->checkBox_assemble->isChecked()?1 | 32:0,Qt::UserRole - 1);
_ui->checkBox_poisson_outputPolygons->setDisabled(
_ui->checkBox_binary->isEnabled() ||
_ui->doubleSpinBox_meshDecimationFactor->value()!=0.0 ||
_ui->checkBox_textureMapping->isChecked());
_ui->spinBox_mesh_minClusterSize->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
_ui->label_meshClean->setEnabled(_ui->comboBox_pipeline->currentIndex() == 1);
}
}
void ExportCloudsDialog::selectDistortionModel()
@@ -503,7 +496,7 @@ void ExportCloudsDialog::setSaveButton()
_ui->checkBox_mesh_quad->setVisible(false);
_ui->checkBox_mesh_quad->setEnabled(false);
_ui->label_quad->setVisible(false);
updateTexturingAvailability(true);
updateReconstructionFlavor();
}
void ExportCloudsDialog::setOkButton()
@@ -516,7 +509,7 @@ void ExportCloudsDialog::setOkButton()
_ui->checkBox_mesh_quad->setVisible(true);
_ui->checkBox_mesh_quad->setEnabled(true);
_ui->label_quad->setVisible(true);
updateTexturingAvailability(false);
updateReconstructionFlavor();
}
void ExportCloudsDialog::enableRegeneration(bool enabled)
@@ -644,32 +637,77 @@ void ExportCloudsDialog::viewClouds(
window->show();
_progressDialog->appendText(tr("Opening visualizer..."));
QApplication::processEvents();
uSleep(500);
QApplication::processEvents();
if(textureMeshes.size())
{
QString prefix = "tmp_textures";
removeDirRecursively(workingDirectory+QDir::separator()+prefix);
QDir(workingDirectory).mkdir(prefix);
cv::Size imageSize;
for(std::map<int, pcl::TextureMesh::Ptr>::iterator iter = textureMeshes.begin(); iter!=textureMeshes.end(); ++iter)
{
_progressDialog->appendText(tr("Viewing the mesh %1 (%2 polygons)...").arg(iter->first).arg(iter->second->tex_polygons.size()?iter->second->tex_polygons[0].size():0));
_progressDialog->incrementStep();
// save tmp textures
for(unsigned int i=0;i<iter->second->tex_materials.size(); ++i)
pcl::TextureMesh::Ptr mesh = iter->second;
// As CloudViewer is not supporting more than one texture per mesh, merge them all by default
cv::Mat globalTexture;
if(mesh->tex_materials.size() > 1)
{
if(!iter->second->tex_materials[i].tex_file.empty())
globalTexture = mergeTextures(*mesh, cachedSignatures);
}
// VTK issue:
// tex_coordinates should be linked to points, not
// polygon vertices. Points linked to multiple different TCoords (different textures) should
// be duplicated.
for(unsigned int t=0; t<mesh->tex_coordinates.size(); ++t)
{
UASSERT(mesh->tex_polygons[t].size());
pcl::PointCloud<pcl::PointXYZ>::Ptr originalCloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(mesh->cloud, *originalCloud);
// make a cloud with as many points than polygon vertices
unsigned int nPoints = mesh->tex_coordinates[t].size();
UASSERT(nPoints== mesh->tex_polygons[t].size()*mesh->tex_polygons[t][0].vertices.size()); // assuming polygon size is constant!
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
cloud->resize(nPoints);
unsigned int oi=0;
for(unsigned int i=0; i<mesh->tex_polygons[t].size(); ++i)
{
pcl::Vertices & vertices = mesh->tex_polygons[t][i];
for(int j=0; j<vertices.vertices.size(); ++j)
{
UASSERT(oi < cloud->size());
UASSERT(vertices.vertices[j] < originalCloud->size());
cloud->at(oi) = originalCloud->at(vertices.vertices[j]);
vertices.vertices[j] = oi; // new vertice index
++oi;
}
}
pcl::toPCLPointCloud2(*cloud, mesh->cloud);
}
// save tmp textures
cv::Size imageSize;
for(unsigned int i=0;i<mesh->tex_materials.size(); ++i)
{
if(!mesh->tex_materials[i].tex_file.empty())
{
// absolute path
QString fullPath = workingDirectory+QDir::separator()+prefix+QDir::separator()+QString(iter->second->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
QString fullPath = workingDirectory+QDir::separator()+prefix+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
if(!QFileInfo(fullPath).exists())
{
if(uIsInteger(iter->second->tex_materials[i].tex_file, false))
if(uIsInteger(mesh->tex_materials[i].tex_file, false))
{
int textureId = uStr2Int(iter->second->tex_materials[i].tex_file);
int textureId = uStr2Int(mesh->tex_materials[i].tex_file);
UASSERT(cachedSignatures.contains(textureId) && !cachedSignatures.value(textureId).sensorData().imageCompressed().empty());
cv::Mat image;
cachedSignatures.value(textureId).sensorData().uncompressDataConst(&image, 0);
@@ -683,7 +721,7 @@ void ExportCloudsDialog::viewClouds(
if(!cv::imwrite(fullPath.toStdString(), image))
{
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(iter->second->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
@@ -693,19 +731,28 @@ void ExportCloudsDialog::viewClouds(
cv::Mat image = cv::Mat::ones(imageSize, CV_8UC1)*255;
cv::imwrite(fullPath.toStdString(), image);
}
else if(!globalTexture.empty())
{
if(!cv::imwrite(fullPath.toStdString(), globalTexture))
{
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
else
{
UWARN("Ignored texture %s (no image size set yet)", iter->second->tex_materials[i].tex_file.c_str());
UWARN("Ignored texture %s (no image size set yet)", mesh->tex_materials[i].tex_file.c_str());
}
}
iter->second->tex_materials[i].tex_file=fullPath.toStdString();
mesh->tex_materials[i].tex_file=fullPath.toStdString();
}
}
bool isRGB = false;
for(unsigned int i=0; i<iter->second->cloud.fields.size(); ++i)
for(unsigned int i=0; i<mesh->cloud.fields.size(); ++i)
{
if(iter->second->cloud.fields[i].name.compare("rgb") == 0)
if(mesh->cloud.fields[i].name.compare("rgb") == 0)
{
isRGB=true;
break;
@@ -713,13 +760,13 @@ void ExportCloudsDialog::viewClouds(
}
if(isRGB)
{
viewer->addCloudTextureMesh(uFormat("mesh%d",iter->first), iter->second, iter->first>0?poses.at(iter->first):Transform::getIdentity());
viewer->addCloudTextureMesh(uFormat("mesh%d",iter->first), mesh, iter->first>0?poses.at(iter->first):Transform::getIdentity());
}
else
{
viewer->addCloudTextureMesh(uFormat("mesh%d",iter->first), iter->second, iter->first>0?poses.at(iter->first):Transform::getIdentity());
viewer->addCloudTextureMesh(uFormat("mesh%d",iter->first), mesh, iter->first>0?poses.at(iter->first):Transform::getIdentity());
}
_progressDialog->appendText(tr("Viewing the mesh %1 (%2 polygons)... done.").arg(iter->first).arg(iter->second->tex_polygons.size()?iter->second->tex_polygons[0].size():0));
_progressDialog->appendText(tr("Viewing the mesh %1 (%2 polygons)... done.").arg(iter->first).arg(mesh->tex_polygons.size()?mesh->tex_polygons[0].size():0));
QApplication::processEvents();
}
}
@@ -991,9 +1038,14 @@ bool ExportCloudsDialog::getExportedClouds(
.arg(assembledCloud->size())
.arg(_ui->doubleSpinBox_voxelSize_assembled->value()));
QApplication::processEvents();
int before = assembledCloud->size();
assembledCloud = util3d::voxelize(
assembledCloud,
_ui->doubleSpinBox_voxelSize_assembled->value());
_progressDialog->appendText(tr("Voxelize cloud (%1 points, voxel size = %2 m)...done! (%3 points)")
.arg(before)
.arg(_ui->doubleSpinBox_voxelSize_assembled->value())
.arg(assembledCloud->size()));
}
clouds.clear();
@@ -1052,6 +1104,8 @@ bool ExportCloudsDialog::getExportedClouds(
_progressDialog->appendText(tr("Smoothing (MLS) the cloud (%1 points)...").arg(cloudWithNormals->size()));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
if(_canceled)
{
return false;
@@ -1119,6 +1173,7 @@ bool ExportCloudsDialog::getExportedClouds(
{
_progressDialog->appendText(tr("Organized fast mesh... "));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mergedClouds(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
@@ -1185,6 +1240,13 @@ bool ExportCloudsDialog::getExportedClouds(
int before = polygons.size();
polygons = filteredPolygons;
if(oi == 0)
{
std::string msg = uFormat("All %d polygons filtered after polygon cluster filtering. Cluster minimum size is %d.", before, _ui->spinBox_mesh_minClusterSize->value());
_progressDialog->appendText(msg.c_str());
UWARN(msg.c_str());
}
_progressDialog->appendText(tr("Filtered %1 polygons.").arg(before-oi));
QApplication::processEvents();
}
@@ -1348,6 +1410,7 @@ bool ExportCloudsDialog::getExportedClouds(
_progressDialog->appendText(tr("Poisson surface reconstruction..."));
}
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
int i=0;
@@ -1391,6 +1454,8 @@ bool ExportCloudsDialog::getExportedClouds(
unsigned int count = mesh->polygons.size();
_progressDialog->appendText(tr("Mesh decimation (factor=%1) from %2 polygons...").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
mesh = util3d::meshDecimation(mesh, (float)_ui->doubleSpinBox_meshDecimationFactor->value());
_progressDialog->appendText(tr("Mesh decimated (factor=%1) from %2 to %3 polygons").arg(_ui->doubleSpinBox_meshDecimationFactor->value()).arg(count).arg(mesh->polygons.size()));
@@ -1524,6 +1589,11 @@ bool ExportCloudsDialog::getExportedClouds(
UDEBUG("texture mapping=%d", _ui->checkBox_textureMapping->isEnabled() && _ui->checkBox_textureMapping->isChecked()?1:0);
if(_ui->checkBox_textureMapping->isEnabled() && _ui->checkBox_textureMapping->isChecked())
{
_progressDialog->appendText(tr("Texturing..."));
QApplication::processEvents();
uSleep(100);
QApplication::processEvents();
int i=0;
for(std::map<int, pcl::PolygonMesh::Ptr>::iterator iter=meshes.begin();
iter!= meshes.end();
@@ -1579,38 +1649,20 @@ bool ExportCloudsDialog::getExportedClouds(
if(textureMesh->tex_polygons.size() && textureMesh->tex_polygons[0].size())
{
textureMesh->tex_coordinates.resize(1);
if(!_ui->checkBox_mesh_quad->isEnabled()) // disabled -> we are exporting to file
//tex_coordinates should be linked to polygon vertices
int polygonSize = textureMesh->tex_polygons[0][0].vertices.size();
textureMesh->tex_coordinates[0].resize(polygonSize*textureMesh->tex_polygons[0].size());
for(unsigned int i=0; i<textureMesh->tex_polygons[0].size(); ++i)
{
UDEBUG("");
// When saving to file, tex_coordinates should be linked to polygon vertices, not points
int polygonSize = textureMesh->tex_polygons[0][0].vertices.size();
textureMesh->tex_coordinates[0].resize(polygonSize*textureMesh->tex_polygons[0].size());
for(unsigned int i=0; i<textureMesh->tex_polygons[0].size(); ++i)
{
const pcl::Vertices & vertices = textureMesh->tex_polygons[0][i];
UASSERT(polygonSize == (int)vertices.vertices.size());
for(int k=0; k<polygonSize; ++k)
{
//uv
UASSERT(vertices.vertices[k] < oter->second.size());
int originalVertex = oter->second[vertices.vertices[k]];
textureMesh->tex_coordinates[0][i*polygonSize+k] = Eigen::Vector2f(
float(originalVertex % w) / float(w), // u
float(h - originalVertex / w) / float(h)); // v
}
}
}
else
{
UDEBUG("");
int nPoints = textureMesh->cloud.data.size()/textureMesh->cloud.point_step;
textureMesh->tex_coordinates[0].resize(nPoints);
for(int i=0; i<nPoints; ++i)
const pcl::Vertices & vertices = textureMesh->tex_polygons[0][i];
UASSERT(polygonSize == (int)vertices.vertices.size());
for(int k=0; k<polygonSize; ++k)
{
//uv
UASSERT(i < (int)oter->second.size());
int originalVertex = oter->second[i];
textureMesh->tex_coordinates[0][i] = Eigen::Vector2f(
UASSERT(vertices.vertices[k] < oter->second.size());
int originalVertex = oter->second[vertices.vertices[k]];
textureMesh->tex_coordinates[0][i*polygonSize+k] = Eigen::Vector2f(
float(originalVertex % w) / float(w), // u
float(h - originalVertex / w) / float(h)); // v
}
@@ -1691,6 +1743,12 @@ bool ExportCloudsDialog::getExportedClouds(
validPolygons.insert(*jter);
}
}
if(validPolygons.size() == 0)
{
std::string msg = uFormat("All %d polygons filtered after polygon cluster filtering. Cluster minimum size is %d.",totalSize, _ui->spinBox_mesh_minClusterSize->value());
_progressDialog->appendText(msg.c_str());
UWARN(msg.c_str());
}
// for each texture
unsigned int allPolygonsIndex = 0;
@@ -1703,28 +1761,38 @@ bool ExportCloudsDialog::getExportedClouds(
#else
std::vector<Eigen::Vector2f> filteredCoordinates(textureMesh->tex_coordinates[t].size());
#endif
int oi=0;
unsigned int polygonSize = 0;
if(textureMesh->tex_polygons[t].size())
{
polygonSize = textureMesh->tex_polygons[t][0].vertices.size();
// make index polygon to coordinate
std::vector<unsigned int> polygonToCoord(textureMesh->tex_polygons[t].size());
unsigned int totalCoord = 0;
for(unsigned int i=0; i<textureMesh->tex_polygons[t].size(); ++i)
{
polygonToCoord[i] = totalCoord;
totalCoord+=textureMesh->tex_polygons[t][i].vertices.size();
}
UASSERT_MSG(totalCoord == textureMesh->tex_coordinates[t].size(), uFormat("%d vs %d", totalCoord, (int)textureMesh->tex_coordinates[t].size()).c_str());
UASSERT_MSG(filteredCoordinates.size() == textureMesh->tex_polygons[t].size()*polygonSize, uFormat("%d vs %d (polygon size=%d)", (int)filteredCoordinates.size(), (int)textureMesh->tex_polygons[t].size(), (int)polygonSize).c_str());
int oi=0;
int ci=0;
for(unsigned int i=0; i<textureMesh->tex_polygons[t].size(); ++i)
{
if(validPolygons.find(allPolygonsIndex) != validPolygons.end())
{
filteredPolygons[oi] = textureMesh->tex_polygons[t].at(i);
for(unsigned int j=0; j<polygonSize; ++j)
for(unsigned int j=0; j<filteredPolygons[oi].vertices.size(); ++j)
{
filteredCoordinates[oi*polygonSize + j] = textureMesh->tex_coordinates[t][i*polygonSize + j];
UASSERT(polygonToCoord[i] < textureMesh->tex_coordinates[t].size());
filteredCoordinates[ci] = textureMesh->tex_coordinates[t][polygonToCoord[i]+j];
++ci;
}
++oi;
}
++allPolygonsIndex;
}
filteredPolygons.resize(oi);
filteredCoordinates.resize(oi*polygonSize);
filteredCoordinates.resize(ci);
textureMesh->tex_polygons[t] = filteredPolygons;
textureMesh->tex_coordinates[t] = filteredCoordinates;
}
@@ -1734,48 +1802,6 @@ bool ExportCloudsDialog::getExportedClouds(
QApplication::processEvents();
}
}
else if(!_ui->checkBox_binary->isEnabled() && // not enabled -> we are not exporting to file
textureMesh->tex_coordinates.size() == 2)
{
// Remove occluded texture to avoid doing multi-texturing (buggy)
textureMesh->tex_coordinates.pop_back();
textureMesh->tex_polygons.pop_back();
textureMesh->tex_materials.pop_back();
}
if(!_ui->checkBox_binary->isEnabled() && // not enabled -> we are not exporting to file
textureMesh->tex_coordinates.size())
{
UDEBUG("");
// for each texture
for(unsigned int t=0; t<textureMesh->tex_coordinates.size(); ++t)
{
// When not saving to file, tex_coordinates should be linked to points, not polygon vertices
int nPoints = textureMesh->cloud.data.size()/textureMesh->cloud.point_step;
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > tmpCoordinates = textureMesh->tex_coordinates[t];
#else
std::vector<Eigen::Vector2f> tmpCoordinates = textureMesh->tex_coordinates[t];
#endif
textureMesh->tex_coordinates[t].clear();
textureMesh->tex_coordinates[t].resize(nPoints, Eigen::Vector2f(-1.0f, -1.0f));
int polygonSize = textureMesh->tex_polygons[t][0].vertices.size();
UASSERT(textureMesh->tex_polygons[t].size() == tmpCoordinates.size()/polygonSize);
for(unsigned int i=0; i<textureMesh->tex_polygons[t].size(); ++i)
{
const pcl::Vertices & vertices = textureMesh->tex_polygons[t][i];
UASSERT(polygonSize == (int)vertices.vertices.size());
for(int j=0; j<polygonSize; ++j)
{
//uv
UASSERT((int)vertices.vertices[j] < nPoints);
UASSERT(i*polygonSize+j < tmpCoordinates.size());
textureMesh->tex_coordinates[t][vertices.vertices[j]] = tmpCoordinates[i*polygonSize+j];
}
}
}
}
}
textureMeshes.insert(std::make_pair(iter->first, textureMesh));
@@ -2330,6 +2356,112 @@ void ExportCloudsDialog::saveMeshes(
}
}
cv::Mat ExportCloudsDialog::mergeTextures(pcl::TextureMesh & mesh, const QMap<int, Signature> & cachedSignatures) const
{
//get texture size, if disabled use default 1024
int textureSize = 1024;
if(_ui->comboBox_meshingTextureSize->currentIndex() > 0)
{
textureSize = 128 << _ui->comboBox_meshingTextureSize->currentIndex(); // start at 256
}
UDEBUG("textureSize = %d", textureSize);
cv::Mat globalTexture;
if(mesh.tex_materials.size() > 1)
{
std::vector<int> textures(mesh.tex_materials.size(), -1);
cv::Size imageSize;
int imageType=CV_8UC1;
UDEBUG("");
bool mergeTextures = true;
for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
{
if(!mesh.tex_materials[i].tex_file.empty() &&
mesh.tex_polygons[i].size() &&
uIsInteger(mesh.tex_materials[i].tex_file, false))
{
int textureId = uStr2Int(mesh.tex_materials[i].tex_file);
textures[i] = textureId;
QMap<int, Signature>::const_iterator iter = cachedSignatures.find(textureId);
UASSERT(iter!=cachedSignatures.end() && !iter->sensorData().imageCompressed().empty());
cv::Size tmpImageSize;
if(iter->sensorData().cameraModels().size()==1 &&
iter->sensorData().cameraModels()[0].imageHeight()>0 &&
iter->sensorData().cameraModels()[0].imageWidth()>0)
{
tmpImageSize = iter->sensorData().cameraModels()[0].imageSize();
if(imageSize.height == 0 && imageSize.width == 0)
{
// just for the first image, get the type, assuming all others have the same type
cv::Mat image;
iter->sensorData().uncompressDataConst(&image, 0);
UASSERT(!image.empty());
imageType = image.type();
}
}
else // backward compatibility for image size not set in CameraModel
{
cv::Mat image;
iter->sensorData().uncompressDataConst(&image, 0);
UASSERT(!image.empty());
tmpImageSize = image.size();
}
if(imageSize.width>0 && imageSize.height>0 && imageSize.width != tmpImageSize.width)
{
UWARN("All images should have the same dimensions to merge the textures!");
mergeTextures = false;
break;
}
imageSize = tmpImageSize;
}
}
if(mergeTextures && textures.size() && imageSize.height>0 && imageSize.width>0)
{
float scale = 0.0f;
UDEBUG("");
util3d::concatenateTextureMaterials(mesh, imageSize, textureSize, scale);
if(scale && mesh.tex_materials.size()==1)
{
int cols = float(textureSize)/(scale*imageSize.width);
globalTexture = cv::Mat(textureSize, textureSize, imageType, cv::Scalar::all(255));
// make a blank texture
cv::Mat emptyImage(int(imageSize.height*scale), int(imageSize.width*scale), imageType, cv::Scalar::all(255));
for(int i=0; i<(int)textures.size(); ++i)
{
int u = i%cols * emptyImage.cols;
int v = i/cols * emptyImage.rows;
UASSERT(u < textureSize-emptyImage.cols);
UASSERT(v < textureSize-emptyImage.rows);
if(textures[i]>=0)
{
QMap<int, Signature>::const_iterator iter = cachedSignatures.find(textures[i]);
UASSERT(iter!=cachedSignatures.end() && !iter->sensorData().imageCompressed().empty());
cv::Mat image;
iter->sensorData().uncompressDataConst(&image, 0);
UASSERT(!image.empty());
cv::Mat resizedImage;
cv::resize(image, resizedImage, emptyImage.size(), 0.0f, 0.0f, cv::INTER_AREA);
if(_ui->groupBox_gain->isChecked() && _compensator && _compensator->getIndex(textures[i]) >= 0)
{
_compensator->apply(textures[i], resizedImage);
}
UASSERT(resizedImage.type() == globalTexture.type());
resizedImage.copyTo(globalTexture(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
}
else
{
emptyImage.copyTo(globalTexture(cv::Rect(u, v, emptyImage.cols, emptyImage.rows)));
}
}
}
}
}
return globalTexture;
}
void ExportCloudsDialog::saveTextureMeshes(
const QString & workingDirectory,
const std::map<int, Transform> & poses,
@@ -2355,16 +2487,35 @@ void ExportCloudsDialog::saveTextureMeshes(
}
pcl::TextureMesh::Ptr mesh = meshes.begin()->second;
removeDirRecursively(QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName());
QDir(QFileInfo(path).absoluteDir().absolutePath()).mkdir(QFileInfo(path).baseName());
cv::Mat globalTexture;
bool texturesMerged = _ui->comboBox_meshingTextureSize->isEnabled() && _ui->comboBox_meshingTextureSize->currentIndex() > 0;
if(texturesMerged && mesh->tex_materials.size()>1)
{
globalTexture = mergeTextures(*mesh, cachedSignatures);
}
bool singleTexture = mesh->tex_materials.size() == 1;
if(!singleTexture)
{
removeDirRecursively(QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName());
QDir(QFileInfo(path).absoluteDir().absolutePath()).mkdir(QFileInfo(path).baseName());
}
cv::Size imageSize;
for(unsigned int i=0; i<mesh->tex_materials.size(); ++i)
{
if(!mesh->tex_materials[i].tex_file.empty())
{
// absolute path
QString fullPath = QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName()+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
QString fullPath;
if(singleTexture)
{
mesh->tex_materials[i].tex_file = QFileInfo(path).baseName().toStdString();
fullPath = QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
}
else
{
fullPath = QFileInfo(path).absoluteDir().absolutePath()+QDir::separator()+QFileInfo(path).baseName()+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
}
if(!QFileInfo(fullPath).exists())
{
if(uIsInteger(mesh->tex_materials[i].tex_file, false))
@@ -2393,13 +2544,29 @@ void ExportCloudsDialog::saveTextureMeshes(
cv::Mat image = cv::Mat::ones(imageSize, CV_8UC1)*255;
cv::imwrite(fullPath.toStdString(), image);
}
else if(!globalTexture.empty())
{
if(!cv::imwrite(fullPath.toStdString(), globalTexture))
{
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
else
{
UWARN("Ignored texture %s (no image size set yet)", mesh->tex_materials[i].tex_file.c_str());
}
}
// relative path
mesh->tex_materials[i].tex_file=(QFileInfo(path).baseName()+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
if(singleTexture)
{
mesh->tex_materials[i].tex_file=(QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
}
else
{
mesh->tex_materials[i].tex_file=(QFileInfo(path).baseName()+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
}
}
}
@@ -2439,50 +2606,82 @@ void ExportCloudsDialog::saveTextureMeshes(
if(iter->second->tex_materials.size())
{
pcl::TextureMesh::Ptr mesh = iter->second;
removeDirRecursively(path+QDir::separator()+currentPrefix);
QDir(path).mkdir(currentPrefix);
cv::Mat globalTexture;
bool texturesMerged = _ui->comboBox_meshingTextureSize->isEnabled() && _ui->comboBox_meshingTextureSize->currentIndex() > 0;
if(texturesMerged && mesh->tex_materials.size()>1)
{
globalTexture = mergeTextures(*mesh, cachedSignatures);
}
bool singleTexture = mesh->tex_materials.size() == 1;
if(!singleTexture)
{
removeDirRecursively(path+QDir::separator()+currentPrefix);
QDir(path).mkdir(currentPrefix);
}
cv::Size imageSize;
for(unsigned int i=0;i<mesh->tex_materials.size(); ++i)
{
if(!mesh->tex_materials[i].tex_file.empty())
{
// absolute path
QString fullPath = path+QDir::separator()+currentPrefix+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
if(!QFileInfo(fullPath).exists())
QString fullPath;
if(singleTexture)
{
if(uIsInteger(mesh->tex_materials[i].tex_file, false))
mesh->tex_materials[i].tex_file = uNumber2Str(iter->first);
fullPath = path+QDir::separator()+prefix + QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
}
else
{
fullPath = path+QDir::separator()+currentPrefix+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText();
}
if(uIsInteger(mesh->tex_materials[i].tex_file, false))
{
int textureId = uStr2Int(mesh->tex_materials[i].tex_file);
UASSERT(cachedSignatures.contains(textureId) && !cachedSignatures.value(textureId).sensorData().imageCompressed().empty());
cv::Mat image;
cachedSignatures.value(textureId).sensorData().uncompressDataConst(&image, 0);
UASSERT(!image.empty());
imageSize = image.size();
if(_ui->groupBox_gain->isChecked() && _compensator && _compensator->getIndex(textureId) >= 0)
{
int textureId = uStr2Int(mesh->tex_materials[i].tex_file);
UASSERT(cachedSignatures.contains(textureId) && !cachedSignatures.value(textureId).sensorData().imageCompressed().empty());
cv::Mat image;
cachedSignatures.value(textureId).sensorData().uncompressDataConst(&image, 0);
UASSERT(!image.empty());
imageSize = image.size();
if(_ui->groupBox_gain->isChecked() && _compensator && _compensator->getIndex(textureId) >= 0)
{
_compensator->apply(textureId, image);
}
_compensator->apply(textureId, image);
}
if(!cv::imwrite(fullPath.toStdString(), image))
{
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
else if(imageSize.height && imageSize.width)
if(!cv::imwrite(fullPath.toStdString(), image))
{
// make a blank texture
cv::Mat image = cv::Mat::ones(imageSize, CV_8UC1)*255;
cv::imwrite(fullPath.toStdString(), image);
}
else
{
UWARN("Ignored texture %s (no image size set yet)", mesh->tex_materials[i].tex_file.c_str());
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
else if(imageSize.height && imageSize.width)
{
// make a blank texture
cv::Mat image = cv::Mat::ones(imageSize, CV_8UC1)*255;
cv::imwrite(fullPath.toStdString(), image);
}
else if(!globalTexture.empty())
{
if(!cv::imwrite(fullPath.toStdString(), globalTexture))
{
_progressDialog->appendText(tr("Failed saving texture \"%1\" to \"%2\".")
.arg(mesh->tex_materials[i].tex_file.c_str()).arg(fullPath), Qt::darkRed);
_progressDialog->setAutoClose(false);
}
}
else
{
UWARN("Ignored texture %s (no image size set yet)", mesh->tex_materials[i].tex_file.c_str());
}
// relative path
mesh->tex_materials[i].tex_file=(currentPrefix+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
if(singleTexture)
{
mesh->tex_materials[i].tex_file=(prefix+ QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
}
else
{
mesh->tex_materials[i].tex_file=(currentPrefix+QDir::separator()+QString(mesh->tex_materials[i].tex_file.c_str())+_ui->comboBox_meshingTextureFormat->currentText()).toStdString();
}
}
}
pcl::PointCloud<pcl::PointNormal>::Ptr tmp(new pcl::PointCloud<pcl::PointNormal>);

View File

@@ -88,11 +88,8 @@ public slots:
private slots:
void updateReconstructionFlavor();
void updateDenseReconstruction();
void selectDistortionModel();
void updateMLSGrpVisibility();
void updateTexturingAvailability();
void updatePoissonOutputPolygonsAvailability();
void cancel();
private:
@@ -115,11 +112,11 @@ private:
void saveClouds(const QString & workingDirectory, const std::map<int, Transform> & poses, const std::map<int, pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr> & clouds, bool binaryMode = true);
void saveMeshes(const QString & workingDirectory, const std::map<int, Transform> & poses, const std::map<int, pcl::PolygonMesh::Ptr> & meshes, bool binaryMode = true);
void saveTextureMeshes(const QString & workingDirectory, const std::map<int, Transform> & poses, std::map<int, pcl::TextureMesh::Ptr> & textureMeshes, const QMap<int, Signature> & cachedSignatures);
cv::Mat mergeTextures(pcl::TextureMesh & mesh, const QMap<int, Signature> & cachedSignatures) const;
void setSaveButton();
void setOkButton();
void enableRegeneration(bool enabled);
void updateTexturingAvailability(bool isExporting);
private:
Ui_ExportCloudsDialog * _ui;

View File

@@ -23,9 +23,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-1430</y>
<y>-1356</y>
<width>773</width>
<height>2774</height>
<height>2832</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
@@ -1105,34 +1105,17 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<layout class="QGridLayout" name="gridLayout_10" columnstretch="0,1">
<item row="6" column="0">
<widget class="QSpinBox" name="spinBox_mesh_minClusterSize">
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_meshDecimation">
<property name="text">
<string>Mesh quadric decimation factor (0=no decimation). Used to reduce the number of polygons. Higher the factor, lower the output resolution (less polygons). Can be used only when dense reconstruction flavor is selected.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_denseReconstruction">
<property name="text">
<string>Surface reconstruction approach. Can be used only when dense reconstruction flavor is selected. Poisson is available only when clouds are assembled.</string>
<string>Surface reconstruction approach. Can be used only when dense reconstruction flavor is selected. Poisson is available when clouds are assembled.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QLabel" name="label_meshDecimation_2">
<property name="text">
<string>Radius used to transfer color from original cloud to resampled reconstructed surface (e.g., Poisson or mesh decimation). 0 means disabled.</string>
@@ -1142,27 +1125,83 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_textureMapping">
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_textureMapping">
<property name="text">
<string>Texture mapping. Images of the cameras will be projected on the mesh(es). Output is a *.obj format. Only available on Export or when clouds are not assembled.</string>
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_meshDecimationFactor">
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>0.990000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_meshClean">
<property name="text">
<string>Clean mesh from polygons without color or texture.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_16">
<item row="2" column="1">
<widget class="QLabel" name="label_meshingTextureFormat">
<property name="text">
<string>Min polygon cluster size</string>
<string>Texture format.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_textureMapping">
<item row="7" column="0">
<widget class="QSpinBox" name="spinBox_mesh_minClusterSize">
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_meshDecimation">
<property name="text">
<string/>
<string>Mesh quadric decimation factor (0=no decimation). Used to reduce the number of polygons. Higher the factor, lower the output resolution (less polygons). Can be used when dense reconstruction flavor is selected.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" 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="7" column="1">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Minimum polygon cluster size.</string>
</property>
</widget>
</item>
@@ -1180,7 +1219,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</item>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_transferColorRadius">
<property name="suffix">
<string> m</string>
@@ -1202,52 +1241,13 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDoubleSpinBox" name="doubleSpinBox_meshDecimationFactor">
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>0.990000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_meshDecimation_3">
<property name="text">
<string>Clean mesh from polygons without color or texture.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QCheckBox" name="checkBox_cleanMesh">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_meshingTextureFormat">
<property name="text">
<string>Texture format.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QComboBox" name="comboBox_meshingTextureFormat">
<item>
@@ -1262,6 +1262,65 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_meshingTextureSize">
<property name="text">
<string>Output texture size. If not set or when clouds are not assembled, all textures are saved separately. Warning: values higher than 2048 may not be compatible with all GPUs.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="comboBox_meshingTextureSize">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>256x256</string>
</property>
</item>
<item>
<property name="text">
<string>512x512</string>
</property>
</item>
<item>
<property name="text">
<string>1024x1024</string>
</property>
</item>
<item>
<property name="text">
<string>2048x2048</string>
</property>
</item>
<item>
<property name="text">
<string>4096x4096</string>
</property>
</item>
<item>
<property name="text">
<string>8192x8192</string>
</property>
</item>
<item>
<property name="text">
<string>16384x16384</string>
</property>
</item>
<item>
<property name="text">
<string>32768x32768</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
@@ -1487,7 +1546,7 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
<item row="1" column="1">
<widget class="QLabel" name="label_outputPolygons">
<property name="text">
<string>Output polygons flag. Enabling this flag tells the reconstructor to output a polygon mesh (rather than triangulating of the results of Marching Cubes). Disabled when exporting to file or if mesh decimation is set.</string>
<string>Output polygons flag. Enabling this flag tells the reconstructor to output a polygon mesh (rather than triangulating of the results of Marching Cubes). Disabled when exporting to file, if mesh decimation is set or when texturing.</string>
</property>
<property name="wordWrap">
<bool>true</bool>