Texturing: Added min polygon cluster size parameter

This commit is contained in:
matlabbe
2017-03-04 18:20:26 -05:00
parent 4db957f437
commit e003fb8d35
7 changed files with 132 additions and 90 deletions

View File

@@ -1992,6 +1992,7 @@ bool RTABMapApp::exportMesh(
cameraPoses, cameraPoses,
cameraModels, cameraModels,
maxTextureDistance, maxTextureDistance,
50,
&progressionStatus_); &progressionStatus_);
LOGI("Texturing... done! %fs", timer.ticks()); LOGI("Texturing... done! %fs", timer.ticks());

View File

@@ -138,6 +138,7 @@ pcl::TextureMesh::Ptr RTABMAP_EXP createTextureMesh(
const std::map<int, Transform> & poses, const std::map<int, Transform> & poses,
const std::map<int, CameraModel> & cameraModels, const std::map<int, CameraModel> & cameraModels,
float maxDistance = 0.0f, // max camera distance to polygon to apply texture float maxDistance = 0.0f, // max camera distance to polygon to apply texture
int minClusterSize = 50, // minimum size of polygons clusters textured
const ProgressState * state = 0); const ProgressState * state = 0);
pcl::PointCloud<pcl::Normal>::Ptr RTABMAP_EXP computeNormals( pcl::PointCloud<pcl::Normal>::Ptr RTABMAP_EXP computeNormals(

View File

@@ -1138,51 +1138,46 @@ pcl::TextureMapping<PointInT>::textureMeshwithMultipleCameras2 (
// af first (idx_pcan < current_cam), check if some of the faces attached to previous cameras occlude the current faces // af first (idx_pcan < current_cam), check if some of the faces attached to previous cameras occlude the current faces
// then (idx_pcam == current_cam), check for self occlusions. At this stage, we skip faces that were already marked as occluded // then (idx_pcam == current_cam), check for self occlusions. At this stage, we skip faces that were already marked as occluded
// project all faces // project all faces
int occludedFaces = 0; std::set<int> occludedFaces;
for (std::map<float, int>::iterator jter=sortedVisibleFaces.begin(); jter!=sortedVisibleFaces.end(); ++jter) for (std::map<float, int>::iterator jter=sortedVisibleFaces.begin(); jter!=sortedVisibleFaces.end(); ++jter)
//for (unsigned int idx = 0; idx<visibilityIndices.size(); ++idx) //for (unsigned int idx = 0; idx<visibilityIndices.size(); ++idx)
{ {
int idx_face = jter->second; int idx_face = jter->second;
//int idx_face = visibilityIndices[idx]; //int idx_face = visibilityIndices[idx];
std::map<int, FaceInfo>::iterator iter= visibleFaces[current_cam].find(idx_face); std::map<int, FaceInfo>::iterator iter= visibleFaces[current_cam].find(idx_face);
if(iter != visibleFaces[current_cam].end()) UASSERT(iter != visibleFaces[current_cam].end());
FaceInfo & info = iter->second;
// face is in the camera's FOV
//get its circumsribed circle
double radius;
pcl::PointXY center;
// getTriangleCircumcenterAndSize (info.uv_coord1, info.uv_coord2, info.uv_coord3, center, radius);
getTriangleCircumcscribedCircleCentroid(info.uv_coord1, info.uv_coord2, info.uv_coord3, center, radius); // this function yields faster results than getTriangleCircumcenterAndSize
// get points inside circ.circle
if (kdtree.radiusSearch (center, radius, idxNeighbors, neighborsSquaredDistance) > 0 )
{ {
FaceInfo & info = iter->second; // for each neighbor
for (size_t i = 0; i < idxNeighbors.size (); ++i)
// face is in the camera's FOV
//get its circumsribed circle
double radius;
pcl::PointXY center;
// getTriangleCircumcenterAndSize (info.uv_coord1, info.uv_coord2, info.uv_coord3, center, radius);
getTriangleCircumcscribedCircleCentroid(info.uv_coord1, info.uv_coord2, info.uv_coord3, center, radius); // this function yields faster results than getTriangleCircumcenterAndSize
// get points inside circ.circle
if (kdtree.radiusSearch (center, radius, idxNeighbors, neighborsSquaredDistance) > 0 )
{ {
// for each neighbor int neighborFaceIndex = idxNeighbors[i]/3;
for (size_t i = 0; i < idxNeighbors.size (); ++i) //std::map<int, FaceInfo>::iterator jter= visibleFaces[current_cam].find(visibilityIndices[neighborFaceIndex]);
//if(jter != visibleFaces[current_cam].end())
{ {
int neighborFaceIndex = idxNeighbors[i]/3; if (std::max(camera_cloud->points[faces[idx_face].vertices[0]].z,
//std::map<int, FaceInfo>::iterator jter= visibleFaces[current_cam].find(visibilityIndices[neighborFaceIndex]); std::max (camera_cloud->points[faces[idx_face].vertices[1]].z,
//if(jter != visibleFaces[current_cam].end()) camera_cloud->points[faces[idx_face].vertices[2]].z))
< camera_cloud->points[faces[visibilityIndices[neighborFaceIndex]].vertices[idxNeighbors[i]%3]].z)
//if (info.distance < jter->second.distance)
{ {
if (std::max(camera_cloud->points[faces[idx_face].vertices[0]].z, // neighbor is farther than all the face's points. Check if it falls into the triangle
std::max (camera_cloud->points[faces[idx_face].vertices[1]].z, if (checkPointInsideTriangle(info.uv_coord1, info.uv_coord2, info.uv_coord3, projections->at(idxNeighbors[i])))
camera_cloud->points[faces[idx_face].vertices[2]].z))
< camera_cloud->points[faces[visibilityIndices[neighborFaceIndex]].vertices[idxNeighbors[i]%3]].z)
//if (info.distance < jter->second.distance)
{ {
// neighbor is farther than all the face's points. Check if it falls into the triangle // current neighbor is inside triangle and is closer => the corresponding face
if (checkPointInsideTriangle(info.uv_coord1, info.uv_coord2, info.uv_coord3, projections->at(idxNeighbors[i]))) occludedFaces.insert(visibilityIndices[neighborFaceIndex]);
{ //TODO we could remove the projections of this face from the kd-tree cloud, but I fond it slower, and I need the point to keep ordered to querry UV coordinates later
// current neighbor is inside triangle and is closer => the corresponding face
if(visibleFaces[current_cam].erase(visibilityIndices[neighborFaceIndex]))
{
++occludedFaces;
}
//TODO we could remove the projections of this face from the kd-tree cloud, but I fond it slower, and I need the point to keep ordered to querry UV coordinates later
}
} }
} }
} }
@@ -1190,54 +1185,63 @@ pcl::TextureMapping<PointInT>::textureMeshwithMultipleCameras2 (
} }
} }
// remove occluded faces
for(std::set<int>::iterator iter= occludedFaces.begin(); iter!=occludedFaces.end(); ++iter)
{
visibleFaces[current_cam].erase(*iter);
}
// filter clusters // filter clusters
int clusterFaces = 0; int clusterFaces = 0;
std::vector<pcl::Vertices> polygons(visibleFaces[current_cam].size()); if(min_cluster_size_>0)
std::vector<int> polygon_to_face_index(visibleFaces[current_cam].size());
oi =0;
for(std::map<int, FaceInfo>::iterator iter=visibleFaces[current_cam].begin(); iter!=visibleFaces[current_cam].end(); ++iter)
{ {
polygons[oi].vertices.resize(3); std::vector<pcl::Vertices> polygons(visibleFaces[current_cam].size());
polygons[oi].vertices[0] = faces[iter->first].vertices[0]; std::vector<int> polygon_to_face_index(visibleFaces[current_cam].size());
polygons[oi].vertices[1] = faces[iter->first].vertices[1]; oi =0;
polygons[oi].vertices[2] = faces[iter->first].vertices[2]; for(std::map<int, FaceInfo>::iterator iter=visibleFaces[current_cam].begin(); iter!=visibleFaces[current_cam].end(); ++iter)
polygon_to_face_index[oi] = iter->first;
++oi;
}
std::vector<std::set<int> > neighbors;
std::vector<std::set<int> > vertexToPolygons;
rtabmap::util3d::createPolygonIndexes(polygons,
(int)camera_cloud->size(),
neighbors,
vertexToPolygons);
std::list<std::list<int> > clusters = rtabmap::util3d::clusterPolygons(
neighbors,
10);
std::set<int> polygonsKept;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
for(std::list<int>::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
{ {
polygonsKept.insert(polygon_to_face_index[*jter]); polygons[oi].vertices.resize(3);
faceCameras[polygon_to_face_index[*jter]].push_back(current_cam); polygons[oi].vertices[0] = faces[iter->first].vertices[0];
polygons[oi].vertices[1] = faces[iter->first].vertices[1];
polygons[oi].vertices[2] = faces[iter->first].vertices[2];
polygon_to_face_index[oi] = iter->first;
++oi;
}
std::vector<std::set<int> > neighbors;
std::vector<std::set<int> > vertexToPolygons;
rtabmap::util3d::createPolygonIndexes(polygons,
(int)camera_cloud->size(),
neighbors,
vertexToPolygons);
std::list<std::list<int> > clusters = rtabmap::util3d::clusterPolygons(
neighbors,
min_cluster_size_);
std::set<int> polygonsKept;
for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
{
for(std::list<int>::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
{
polygonsKept.insert(polygon_to_face_index[*jter]);
faceCameras[polygon_to_face_index[*jter]].push_back(current_cam);
}
}
for(std::map<int, FaceInfo>::iterator iter=visibleFaces[current_cam].begin(); iter!=visibleFaces[current_cam].end();)
{
if(polygonsKept.find(iter->first) == polygonsKept.end())
{
visibleFaces[current_cam].erase(iter++);
++clusterFaces;
}
else
{
++iter;
}
} }
} }
for(std::map<int, FaceInfo>::iterator iter=visibleFaces[current_cam].begin(); iter!=visibleFaces[current_cam].end();) std::string msg = uFormat("Processed camera %d/%d: %d occluded and %d spurious polygons out of %d", (int)current_cam+1, (int)cameras.size(), (int)occludedFaces.size(), clusterFaces, (int)visibilityIndices.size());
{
if(polygonsKept.find(iter->first) == polygonsKept.end())
{
visibleFaces[current_cam].erase(iter++);
++clusterFaces;
}
else
{
++iter;
}
}
std::string msg = uFormat("Processed camera %d/%d: %d occluded and %d spurious polygons out of %d", (int)current_cam+1, (int)cameras.size(), occludedFaces, clusterFaces, (int)visibilityIndices.size());
UINFO(msg.c_str()); UINFO(msg.c_str());
if(state && !state->callback(msg)) if(state && !state->callback(msg))
{ {

View File

@@ -119,7 +119,7 @@ namespace pcl
/** \brief Constructor. */ /** \brief Constructor. */
TextureMapping () : TextureMapping () :
f_ (), vector_field_ (), tex_files_ (), tex_material_ (), max_distance_(0.0f) f_ (), vector_field_ (), tex_files_ (), tex_material_ (), max_distance_(0.0f), min_cluster_size_(50)
{ {
} }
@@ -174,6 +174,12 @@ namespace pcl
max_distance_ = maxDistance; max_distance_ = maxDistance;
} }
inline void
setMinClusterSize(int size)
{
min_cluster_size_ = size;
}
/** \brief Map texture to a mesh synthesis algorithm /** \brief Map texture to a mesh synthesis algorithm
* \param[in] tex_mesh texture mesh * \param[in] tex_mesh texture mesh
*/ */
@@ -363,6 +369,9 @@ namespace pcl
/** \brief maximum distance between camera and polygon to apply a texture */ /** \brief maximum distance between camera and polygon to apply a texture */
float max_distance_; float max_distance_;
/** \brief Remove texture from small polygon clusters */
int min_cluster_size_;
/** \brief Map texture to a face /** \brief Map texture to a face
* \param[in] p1 the first point * \param[in] p1 the first point
* \param[in] p2 the second point * \param[in] p2 the second point

View File

@@ -608,6 +608,7 @@ pcl::TextureMesh::Ptr createTextureMesh(
const std::map<int, Transform> & poses, const std::map<int, Transform> & poses,
const std::map<int, CameraModel> & cameraModels, const std::map<int, CameraModel> & cameraModels,
float maxDistance, float maxDistance,
int minClusterSize,
const ProgressState * state) const ProgressState * state)
{ {
UASSERT(mesh->polygons.size()); UASSERT(mesh->polygons.size());
@@ -665,6 +666,7 @@ pcl::TextureMesh::Ptr createTextureMesh(
// Texture by projection // Texture by projection
pcl::TextureMapping<pcl::PointXYZ> tm; // TextureMapping object that will perform the sort pcl::TextureMapping<pcl::PointXYZ> tm; // TextureMapping object that will perform the sort
tm.setMaxDistance(maxDistance); tm.setMaxDistance(maxDistance);
tm.setMinClusterSize(minClusterSize);
if(tm.textureMeshwithMultipleCameras2(*textureMesh, cameras, state)) if(tm.textureMeshwithMultipleCameras2(*textureMesh, cameras, state))
{ {
// compute normals for the mesh if not already here // compute normals for the mesh if not already here

View File

@@ -149,6 +149,7 @@ ExportCloudsDialog::ExportCloudsDialog(QWidget *parent) :
connect(_ui->comboBox_meshingTextureFormat, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged())); connect(_ui->comboBox_meshingTextureFormat, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->comboBox_meshingTextureSize, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged())); connect(_ui->comboBox_meshingTextureSize, SIGNAL(currentIndexChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->doubleSpinBox_meshingTextureMaxDistance, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged())); connect(_ui->doubleSpinBox_meshingTextureMaxDistance, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
connect(_ui->spinBox_mesh_minTextureClusterSize, SIGNAL(valueChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_cameraFilter, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged())); connect(_ui->checkBox_cameraFilter, SIGNAL(stateChanged(int)), this, SIGNAL(configChanged()));
connect(_ui->checkBox_cameraFilter, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor())); connect(_ui->checkBox_cameraFilter, SIGNAL(stateChanged(int)), this, SLOT(updateReconstructionFlavor()));
connect(_ui->doubleSpinBox_cameraFilterRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged())); connect(_ui->doubleSpinBox_cameraFilterRadius, SIGNAL(valueChanged(double)), this, SIGNAL(configChanged()));
@@ -266,6 +267,7 @@ void ExportCloudsDialog::saveSettings(QSettings & settings, const QString & grou
settings.setValue("mesh_textureFormat", _ui->comboBox_meshingTextureFormat->currentIndex()); settings.setValue("mesh_textureFormat", _ui->comboBox_meshingTextureFormat->currentIndex());
settings.setValue("mesh_textureSize", _ui->comboBox_meshingTextureSize->currentIndex()); settings.setValue("mesh_textureSize", _ui->comboBox_meshingTextureSize->currentIndex());
settings.setValue("mesh_textureMaxDistance", _ui->doubleSpinBox_meshingTextureMaxDistance->value()); settings.setValue("mesh_textureMaxDistance", _ui->doubleSpinBox_meshingTextureMaxDistance->value());
settings.setValue("mesh_textureMinCluster", _ui->spinBox_mesh_minTextureClusterSize->value());
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());
@@ -358,6 +360,7 @@ void ExportCloudsDialog::loadSettings(QSettings & settings, const QString & grou
_ui->comboBox_meshingTextureFormat->setCurrentIndex(settings.value("mesh_textureFormat", _ui->comboBox_meshingTextureFormat->currentIndex()).toInt()); _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->comboBox_meshingTextureSize->setCurrentIndex(settings.value("mesh_textureSize", _ui->comboBox_meshingTextureSize->currentIndex()).toInt());
_ui->doubleSpinBox_meshingTextureMaxDistance->setValue(settings.value("mesh_textureMaxDistance", _ui->doubleSpinBox_meshingTextureMaxDistance->value()).toDouble()); _ui->doubleSpinBox_meshingTextureMaxDistance->setValue(settings.value("mesh_textureMaxDistance", _ui->doubleSpinBox_meshingTextureMaxDistance->value()).toDouble());
_ui->spinBox_mesh_minTextureClusterSize->setValue(settings.value("mesh_textureMinCluster", _ui->spinBox_mesh_minTextureClusterSize->value()).toDouble());
_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());
@@ -447,6 +450,7 @@ void ExportCloudsDialog::restoreDefaults()
_ui->comboBox_meshingTextureFormat->setCurrentIndex(0); _ui->comboBox_meshingTextureFormat->setCurrentIndex(0);
_ui->comboBox_meshingTextureSize->setCurrentIndex(5); // 4096 _ui->comboBox_meshingTextureSize->setCurrentIndex(5); // 4096
_ui->doubleSpinBox_meshingTextureMaxDistance->setValue(3.0); _ui->doubleSpinBox_meshingTextureMaxDistance->setValue(3.0);
_ui->spinBox_mesh_minTextureClusterSize->setValue(50);
_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);
@@ -1882,6 +1886,7 @@ bool ExportCloudsDialog::getExportedClouds(
cameraPoses, cameraPoses,
cameraModels, cameraModels,
_ui->doubleSpinBox_meshingTextureMaxDistance->value(), _ui->doubleSpinBox_meshingTextureMaxDistance->value(),
_ui->spinBox_mesh_minTextureClusterSize->value(),
&texturingState); &texturingState);
if(_canceled) if(_canceled)

View File

@@ -23,9 +23,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-1649</y> <y>-1800</y>
<width>773</width> <width>773</width>
<height>3158</height> <height>3200</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_13"> <layout class="QVBoxLayout" name="verticalLayout_13">
@@ -1376,6 +1376,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="0" 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="0" column="0"> <item row="0" column="0">
<widget class="QComboBox" name="comboBox_meshingTextureFormat"> <widget class="QComboBox" name="comboBox_meshingTextureFormat">
<item> <item>
@@ -1390,16 +1400,6 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</item> </item>
</widget> </widget>
</item> </item>
<item row="0" 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="1" column="0"> <item row="1" column="0">
<widget class="QComboBox" name="comboBox_meshingTextureSize"> <widget class="QComboBox" name="comboBox_meshingTextureSize">
<item> <item>
@@ -1491,14 +1491,14 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="checkBox_cameraFilter"> <widget class="QCheckBox" name="checkBox_cameraFilter">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<widget class="QLabel" name="label_meshingTextureSize_3"> <widget class="QLabel" name="label_meshingTextureSize_3">
<property name="text"> <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> <string>Camera filtering. By comparing poses in the same area, only one camera in a fixed radius and angle is used for texturing.</string>
@@ -1508,6 +1508,26 @@ Guidelines: 4 times the voxel size, 0.025 for voxel=0.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="QLabel" name="label_meshingTextureSize_4">
<property name="text">
<string>Minimum polygon cluster size for texturing. This removes textures applied on sparse polygons. Only used with dense reconstruction flavor.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QSpinBox" name="spinBox_mesh_minTextureClusterSize">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>