Files
rtabmap/corelib/src/util3d_surface.cpp
T

4304 lines
138 KiB
C++

/*
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 "rtabmap/core/util3d_surface.h"
#include "rtabmap/core/util3d_filtering.h"
#include "rtabmap/core/util3d_transforms.h"
#include "rtabmap/core/util3d.h"
#include "rtabmap/core/util2d.h"
#include "rtabmap/core/Memory.h"
#include "rtabmap/core/DBDriver.h"
#include "rtabmap/core/Compression.h"
#include "rtabmap/utilite/ULogger.h"
#include "rtabmap/utilite/UDirectory.h"
#include "rtabmap/utilite/UFile.h"
#include "rtabmap/utilite/UConversion.h"
#include "rtabmap/utilite/UMath.h"
#include "rtabmap/utilite/UTimer.h"
#include <opencv2/core/core_c.h>
#include <opencv2/imgproc/types_c.h>
#include <pcl/search/kdtree.h>
#include <pcl/surface/gp3.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/surface/mls.h>
#include <pcl18/surface/texture_mapping.h>
#include <pcl/features/integral_image_normal.h>
#ifdef RTABMAP_ALICE_VISION
#include <aliceVision/sfmData/SfMData.hpp>
#include <aliceVision/sfmDataIO/sfmDataIO.hpp>
#include <aliceVision/mesh/Mesh.hpp>
#include <aliceVision/mesh/Texturing.hpp>
#include <aliceVision/camera/Pinhole.hpp>
#include <boost/algorithm/string.hpp>
using namespace aliceVision;
#endif
#ifndef DISABLE_VTK
#include <pcl/surface/vtk_smoothing/vtk_mesh_quadric_decimation.h>
#endif
#if PCL_VERSION_COMPARE(>, 1, 11, 1)
#include <pcl/types.h>
#endif
#if PCL_VERSION_COMPARE(<, 1, 8, 0)
#include "pcl18/surface/organized_fast_mesh.h"
#else
#include <pcl/surface/organized_fast_mesh.h>
#include <pcl/surface/impl/marching_cubes.hpp>
#include <pcl/surface/impl/organized_fast_mesh.hpp>
#include <pcl/impl/instantiate.hpp>
#include <pcl/point_types.h>
// Instantiations of specific point types
PCL_INSTANTIATE(OrganizedFastMesh, (pcl::PointXYZRGBNormal))
#include <pcl/features/impl/normal_3d_omp.hpp>
#if PCL_VERSION_COMPARE(<=, 1, 8, 0)
#ifdef PCL_ONLY_CORE_POINT_TYPES
PCL_INSTANTIATE_PRODUCT(NormalEstimationOMP, ((pcl::PointXYZRGB))((pcl::Normal)))
#endif
#endif
#endif
namespace rtabmap
{
namespace util3d
{
void createPolygonIndexes(
const std::vector<pcl::Vertices> & polygons,
int cloudSize,
std::vector<std::set<int> > & neighbors,
std::vector<std::set<int> > & vertexToPolygons)
{
vertexToPolygons = std::vector<std::set<int> >(cloudSize);
neighbors = std::vector<std::set<int> >(polygons.size());
for(unsigned int i=0; i<polygons.size(); ++i)
{
std::set<int> vertices(polygons[i].vertices.begin(), polygons[i].vertices.end());
for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
{
int v = polygons[i].vertices.at(j);
for(std::set<int>::iterator iter=vertexToPolygons[v].begin(); iter!=vertexToPolygons[v].end(); ++iter)
{
int numSharedVertices = 0;
for(unsigned int k=0; k<polygons.at(*iter).vertices.size() && numSharedVertices<2; ++k)
{
if(vertices.find(polygons.at(*iter).vertices.at(k)) != vertices.end())
{
++numSharedVertices;
}
}
if(numSharedVertices >= 2)
{
neighbors[*iter].insert(i);
neighbors[i].insert(*iter);
}
}
vertexToPolygons[v].insert(i);
}
}
}
std::list<std::list<int> > clusterPolygons(
const std::vector<std::set<int> > & neighborPolygons,
int minClusterSize)
{
std::set<int> polygonsChecked;
std::list<std::list<int> > clusters;
for(unsigned int i=0; i<neighborPolygons.size(); ++i)
{
if(polygonsChecked.find(i) == polygonsChecked.end())
{
std::list<int> currentCluster;
currentCluster.push_back(i);
polygonsChecked.insert(i);
for(std::list<int>::iterator iter=currentCluster.begin(); iter!=currentCluster.end(); ++iter)
{
// get neighbor polygons
std::set<int> neighbors = neighborPolygons[*iter];
for(std::set<int>::iterator jter=neighbors.begin(); jter!=neighbors.end(); ++jter)
{
if(polygonsChecked.insert(*jter).second)
{
currentCluster.push_back(*jter);
}
}
}
if((int)currentCluster.size() > minClusterSize)
{
clusters.push_back(currentCluster);
}
}
}
return clusters;
}
std::vector<pcl::Vertices> organizedFastMesh(
const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
double angleTolerance,
bool quad,
int trianglePixelSize,
const Eigen::Vector3f & viewpoint)
{
UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
UASSERT(cloud->is_dense == false);
UASSERT(cloud->width > 1 && cloud->height > 1);
pcl::OrganizedFastMesh<pcl::PointXYZ> ofm;
ofm.setTrianglePixelSize (trianglePixelSize);
ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZ>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZ>::TRIANGLE_RIGHT_CUT);
ofm.setInputCloud (cloud);
ofm.setAngleTolerance(angleTolerance);
ofm.setViewpoint(viewpoint);
std::vector<pcl::Vertices> vertices;
ofm.reconstruct (vertices);
if(quad)
{
//flip all polygons (right handed)
std::vector<pcl::Vertices> output(vertices.size());
for(unsigned int i=0; i<vertices.size(); ++i)
{
output[i].vertices.resize(4);
output[i].vertices[0] = vertices[i].vertices[0];
output[i].vertices[3] = vertices[i].vertices[1];
output[i].vertices[2] = vertices[i].vertices[2];
output[i].vertices[1] = vertices[i].vertices[3];
}
return output;
}
return vertices;
}
std::vector<pcl::Vertices> organizedFastMesh(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
double angleTolerance,
bool quad,
int trianglePixelSize,
const Eigen::Vector3f & viewpoint)
{
UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
UASSERT(cloud->is_dense == false);
UASSERT(cloud->width > 1 && cloud->height > 1);
pcl::OrganizedFastMesh<pcl::PointXYZRGB> ofm;
ofm.setTrianglePixelSize (trianglePixelSize);
ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZRGB>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZRGB>::TRIANGLE_RIGHT_CUT);
ofm.setInputCloud (cloud);
ofm.setAngleTolerance(angleTolerance);
ofm.setViewpoint(viewpoint);
std::vector<pcl::Vertices> vertices;
ofm.reconstruct (vertices);
if(quad)
{
//flip all polygons (right handed)
std::vector<pcl::Vertices> output(vertices.size());
for(unsigned int i=0; i<vertices.size(); ++i)
{
output[i].vertices.resize(4);
output[i].vertices[0] = vertices[i].vertices[0];
output[i].vertices[3] = vertices[i].vertices[1];
output[i].vertices[2] = vertices[i].vertices[2];
output[i].vertices[1] = vertices[i].vertices[3];
}
return output;
}
return vertices;
}
std::vector<pcl::Vertices> organizedFastMesh(
const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
double angleTolerance,
bool quad,
int trianglePixelSize,
const Eigen::Vector3f & viewpoint)
{
UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
UASSERT(cloud->is_dense == false);
UASSERT(cloud->width > 1 && cloud->height > 1);
pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal> ofm;
ofm.setTrianglePixelSize (trianglePixelSize);
ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal>::TRIANGLE_RIGHT_CUT);
ofm.setInputCloud (cloud);
ofm.setAngleTolerance(angleTolerance);
ofm.setViewpoint(viewpoint);
std::vector<pcl::Vertices> vertices;
ofm.reconstruct (vertices);
if(quad)
{
//flip all polygons (right handed)
std::vector<pcl::Vertices> output(vertices.size());
for(unsigned int i=0; i<vertices.size(); ++i)
{
output[i].vertices.resize(4);
output[i].vertices[0] = vertices[i].vertices[0];
output[i].vertices[3] = vertices[i].vertices[1];
output[i].vertices[2] = vertices[i].vertices[2];
output[i].vertices[1] = vertices[i].vertices[3];
}
return output;
}
return vertices;
}
void appendMesh(
pcl::PointCloud<pcl::PointXYZRGBNormal> & cloudA,
std::vector<pcl::Vertices> & polygonsA,
const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloudB,
const std::vector<pcl::Vertices> & polygonsB)
{
UDEBUG("cloudA=%d polygonsA=%d cloudB=%d polygonsB=%d", (int)cloudA.size(), (int)polygonsA.size(), (int)cloudB.size(), (int)polygonsB.size());
UASSERT(!cloudA.isOrganized() && !cloudB.isOrganized());
int sizeA = (int)cloudA.size();
cloudA += cloudB;
int sizePolygonsA = (int)polygonsA.size();
polygonsA.resize(sizePolygonsA+polygonsB.size());
for(unsigned int i=0; i<polygonsB.size(); ++i)
{
pcl::Vertices vertices = polygonsB[i];
for(unsigned int j=0; j<vertices.vertices.size(); ++j)
{
vertices.vertices[j] += sizeA;
}
polygonsA[i+sizePolygonsA] = vertices;
}
}
void appendMesh(
pcl::PointCloud<pcl::PointXYZRGB> & cloudA,
std::vector<pcl::Vertices> & polygonsA,
const pcl::PointCloud<pcl::PointXYZRGB> & cloudB,
const std::vector<pcl::Vertices> & polygonsB)
{
UDEBUG("cloudA=%d polygonsA=%d cloudB=%d polygonsB=%d", (int)cloudA.size(), (int)polygonsA.size(), (int)cloudB.size(), (int)polygonsB.size());
UASSERT(!cloudA.isOrganized() && !cloudB.isOrganized());
int sizeA = (int)cloudA.size();
cloudA += cloudB;
int sizePolygonsA = (int)polygonsA.size();
polygonsA.resize(sizePolygonsA+polygonsB.size());
for(unsigned int i=0; i<polygonsB.size(); ++i)
{
pcl::Vertices vertices = polygonsB[i];
for(unsigned int j=0; j<vertices.vertices.size(); ++j)
{
vertices.vertices[j] += sizeA;
}
polygonsA[i+sizePolygonsA] = vertices;
}
}
std::vector<int> filterNotUsedVerticesFromMesh(
const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud,
const std::vector<pcl::Vertices> & polygons,
pcl::PointCloud<pcl::PointXYZRGBNormal> & outputCloud,
std::vector<pcl::Vertices> & outputPolygons)
{
UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
std::map<int, int> addedVertices; //<oldIndex, newIndex>
std::vector<int> output; //<oldIndex>
output.resize(cloud.size());
outputCloud.resize(cloud.size());
outputCloud.is_dense = true;
outputPolygons.resize(polygons.size());
int oi = 0;
for(unsigned int i=0; i<polygons.size(); ++i)
{
pcl::Vertices & v = outputPolygons[i];
v.vertices.resize(polygons[i].vertices.size());
for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
{
std::map<int, int>::iterator iter = addedVertices.find(polygons[i].vertices[j]);
if(iter == addedVertices.end())
{
outputCloud[oi] = cloud.at(polygons[i].vertices[j]);
addedVertices.insert(std::make_pair(polygons[i].vertices[j], oi));
output[oi] = polygons[i].vertices[j];
v.vertices[j] = oi++;
}
else
{
v.vertices[j] = iter->second;
}
}
}
outputCloud.resize(oi);
output.resize(oi);
return output;
}
std::vector<int> filterNotUsedVerticesFromMesh(
const pcl::PointCloud<pcl::PointXYZRGB> & cloud,
const std::vector<pcl::Vertices> & polygons,
pcl::PointCloud<pcl::PointXYZRGB> & outputCloud,
std::vector<pcl::Vertices> & outputPolygons)
{
UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
std::map<int, int> addedVertices; //<oldIndex, newIndex>
std::vector<int> output; //<oldIndex>
output.resize(cloud.size());
outputCloud.resize(cloud.size());
outputCloud.is_dense = true;
outputPolygons.resize(polygons.size());
int oi = 0;
for(unsigned int i=0; i<polygons.size(); ++i)
{
pcl::Vertices & v = outputPolygons[i];
v.vertices.resize(polygons[i].vertices.size());
for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
{
std::map<int, int>::iterator iter = addedVertices.find(polygons[i].vertices[j]);
if(iter == addedVertices.end())
{
outputCloud[oi] = cloud.at(polygons[i].vertices[j]);
addedVertices.insert(std::make_pair(polygons[i].vertices[j], oi));
output[oi] = polygons[i].vertices[j];
v.vertices[j] = oi++;
}
else
{
v.vertices[j] = iter->second;
}
}
}
outputCloud.resize(oi);
output.resize(oi);
return output;
}
std::vector<int> filterNaNPointsFromMesh(
const pcl::PointCloud<pcl::PointXYZRGB> & cloud,
const std::vector<pcl::Vertices> & polygons,
pcl::PointCloud<pcl::PointXYZRGB> & outputCloud,
std::vector<pcl::Vertices> & outputPolygons)
{
UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
std::map<int, int> addedVertices; //<oldIndex, newIndex>
std::vector<int> output; //<oldIndex>
output.resize(cloud.size());
outputCloud.resize(cloud.size());
outputCloud.is_dense = true;
std::vector<int> organizedToDense(cloud.size(), -1);
int oi = 0;
for(unsigned int i=0; i<cloud.size(); ++i)
{
if(pcl::isFinite(cloud.at(i)))
{
outputCloud.at(oi) = cloud.at(i);
output[oi] = i;
organizedToDense[i] = oi;
++oi;
}
}
outputCloud.resize(oi);
output.resize(oi);
// remap polygons to dense cloud
outputPolygons = polygons;
for(unsigned int i=0; i<outputPolygons.size(); ++i)
{
pcl::Vertices & v = outputPolygons[i];
for(unsigned int j=0; j<v.vertices.size(); ++j)
{
UASSERT(organizedToDense[v.vertices[j]] >= 0);
v.vertices[j] = organizedToDense[v.vertices[j]];
}
}
return output;
}
std::vector<pcl::Vertices> filterCloseVerticesFromMesh(
const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud,
const std::vector<pcl::Vertices> & polygons,
float radius,
float angle, // FIXME angle not used
bool keepLatestInRadius)
{
UDEBUG("size=%d polygons=%d radius=%f angle=%f keepLatest=%d",
(int)cloud->size(), (int)polygons.size(), radius, angle, keepLatestInRadius?1:0);
std::vector<pcl::Vertices> outputPolygons;
pcl::KdTreeFLANN<pcl::PointXYZRGBNormal>::Ptr kdtree(new pcl::KdTreeFLANN<pcl::PointXYZRGBNormal>);
kdtree->setInputCloud(cloud);
std::map<int, int> verticesDone;
outputPolygons = polygons;
for(unsigned int i=0; i<outputPolygons.size(); ++i)
{
pcl::Vertices & polygon = outputPolygons[i];
for(unsigned int j=0; j<polygon.vertices.size(); ++j)
{
std::map<int, int>::iterator iter = verticesDone.find(polygon.vertices[j]);
if(iter != verticesDone.end())
{
polygon.vertices[j] = iter->second;
}
else
{
std::vector<int> kIndices;
std::vector<float> kDistances;
kdtree->radiusSearch(polygon.vertices[j], radius, kIndices, kDistances);
if(kIndices.size())
{
int reference = -1;
for(unsigned int z=0; z<kIndices.size(); ++z)
{
if(reference == -1)
{
reference = kIndices[z];
}
else if(keepLatestInRadius)
{
if(kIndices[z] < reference)
{
reference = kIndices[z];
}
}
else
{
if(kIndices[z] > reference)
{
reference = kIndices[z];
}
}
}
if(reference >= 0)
{
for(unsigned int z=0; z<kIndices.size(); ++z)
{
verticesDone.insert(std::make_pair(kIndices[j], reference));
}
polygon.vertices[j] = reference;
}
}
else
{
verticesDone.insert(std::make_pair(polygon.vertices[j], polygon.vertices[j]));
}
}
}
}
return outputPolygons;
}
std::vector<pcl::Vertices> filterInvalidPolygons(const std::vector<pcl::Vertices> & polygons)
{
std::vector<pcl::Vertices> output(polygons.size());
int oi=0;
for(unsigned int i=0; i<polygons.size(); ++i)
{
bool valid = true;
for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
{
if(polygons[i].vertices[j] == polygons[i].vertices[(j+1)%polygons[i].vertices.size()])
{
valid = false;
break;
}
}
if(valid)
{
output[oi++] = polygons[i];
}
}
output.resize(oi);
return output;
}
pcl::PolygonMesh::Ptr createMesh(
const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloudWithNormals,
float gp3SearchRadius,
float gp3Mu,
int gp3MaximumNearestNeighbors,
float gp3MaximumSurfaceAngle,
float gp3MinimumAngle,
float gp3MaximumAngle,
bool gp3NormalConsistency)
{
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormalsNoNaN = removeNaNNormalsFromPointCloud(cloudWithNormals);
// Create search tree*
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree2 (new pcl::search::KdTree<pcl::PointXYZRGBNormal>);
tree2->setInputCloud (cloudWithNormalsNoNaN);
// Initialize objects
pcl::GreedyProjectionTriangulation<pcl::PointXYZRGBNormal> gp3;
pcl::PolygonMesh::Ptr mesh(new pcl::PolygonMesh);
// Set the maximum distance between connected points (maximum edge length)
gp3.setSearchRadius (gp3SearchRadius);
// Set typical values for the parameters
gp3.setMu (gp3Mu);
gp3.setMaximumNearestNeighbors (gp3MaximumNearestNeighbors);
gp3.setMaximumSurfaceAngle(gp3MaximumSurfaceAngle); // 45 degrees
gp3.setMinimumAngle(gp3MinimumAngle); // 10 degrees
gp3.setMaximumAngle(gp3MaximumAngle); // 120 degrees
gp3.setNormalConsistency(gp3NormalConsistency);
gp3.setConsistentVertexOrdering(gp3NormalConsistency);
// Get result
gp3.setInputCloud (cloudWithNormalsNoNaN);
gp3.setSearchMethod (tree2);
gp3.reconstruct (*mesh);
//UASSERT(mesh->cloud.data.size()/mesh->cloud.point_step == cloudWithNormalsNoNaN->size());
//mesh->polygons = normalizePolygonsSide(*cloudWithNormalsNoNaN, mesh->polygons);
return mesh;
}
pcl::texture_mapping::CameraVector createTextureCameras(
const std::map<int, Transform> & poses,
const std::map<int, std::vector<CameraModel> > & cameraModels,
const std::map<int, cv::Mat> & cameraDepths,
const std::vector<float> & roiRatios)
{
UASSERT(roiRatios.empty() || roiRatios.size() == 4);
pcl::texture_mapping::CameraVector cameras;
for(std::map<int, Transform>::const_iterator poseIter=poses.begin(); poseIter!=poses.end(); ++poseIter)
{
std::map<int, std::vector<CameraModel> >::const_iterator modelIter=cameraModels.find(poseIter->first);
if(modelIter!=cameraModels.end())
{
std::map<int, cv::Mat>::const_iterator depthIter = cameraDepths.find(poseIter->first);
// for each sub camera
for(unsigned int i=0; i<modelIter->second.size(); ++i)
{
pcl::TextureMapping<pcl::PointXYZ>::Camera cam;
// should be in camera frame
UASSERT(!modelIter->second[i].localTransform().isNull() && !poseIter->second.isNull());
Transform t = poseIter->second*modelIter->second[i].localTransform();
cam.pose = t.toEigen3f();
if(modelIter->second[i].imageHeight() <=0 || modelIter->second[i].imageWidth() <=0)
{
UERROR("Should have camera models with width/height set to create texture cameras!");
return pcl::texture_mapping::CameraVector();
}
UASSERT(modelIter->second[i].fx()>0 && modelIter->second[i].imageHeight()>0 && modelIter->second[i].imageWidth()>0);
cam.focal_length_w=modelIter->second[i].fx();
cam.focal_length_h=modelIter->second[i].fy();
cam.center_w=modelIter->second[i].cx();
cam.center_h=modelIter->second[i].cy();
cam.height=modelIter->second[i].imageHeight();
cam.width=modelIter->second[i].imageWidth();
if(modelIter->second.size() == 1)
{
cam.texture_file = uFormat("%d", poseIter->first); // camera index
}
else
{
cam.texture_file = uFormat("%d_%d", poseIter->first, (int)i); // camera index, sub camera model index
}
if(!roiRatios.empty())
{
cam.roi.resize(4);
cam.roi[0] = cam.width * roiRatios[0]; // left -> x
cam.roi[1] = cam.height * roiRatios[2]; // top -> y
cam.roi[2] = cam.width * (1.0 - roiRatios[1]) - cam.roi[0]; // right -> width
cam.roi[3] = cam.height * (1.0 - roiRatios[3]) - cam.roi[1]; // bottom -> height
}
if(depthIter != cameraDepths.end() && !depthIter->second.empty())
{
UASSERT(depthIter->second.type() == CV_32FC1 || depthIter->second.type() == CV_16UC1);
UASSERT(depthIter->second.cols % modelIter->second.size() == 0);
int subWidth = depthIter->second.cols/(modelIter->second.size());
cam.depth = cv::Mat(depthIter->second, cv::Range(0, depthIter->second.rows), cv::Range(subWidth*i, subWidth*(i+1)));
}
UDEBUG("%f", cam.focal_length);
UDEBUG("%f", cam.height);
UDEBUG("%f", cam.width);
UDEBUG("cam.pose=%s", t.prettyPrint().c_str());
cameras.push_back(cam);
}
}
}
return cameras;
}
pcl::TextureMesh::Ptr createTextureMesh(
const pcl::PolygonMesh::Ptr & mesh,
const std::map<int, Transform> & poses,
const std::map<int, CameraModel> & cameraModels,
const std::map<int, cv::Mat> & cameraDepths,
float maxDistance,
float maxDepthError,
float maxAngle,
int minClusterSize,
const std::vector<float> & roiRatios,
const ProgressState * state,
std::vector<std::map<int, pcl::PointXY> > * vertexToPixels,
bool distanceToCamPolicy)
{
std::map<int, std::vector<CameraModel> > cameraSubModels;
for(std::map<int, CameraModel>::const_iterator iter=cameraModels.begin(); iter!=cameraModels.end(); ++iter)
{
std::vector<CameraModel> models;
models.push_back(iter->second);
cameraSubModels.insert(std::make_pair(iter->first, models));
}
return createTextureMesh(
mesh,
poses,
cameraSubModels,
cameraDepths,
maxDistance,
maxDepthError,
maxAngle,
minClusterSize,
roiRatios,
state,
vertexToPixels,
distanceToCamPolicy);
}
pcl::TextureMesh::Ptr createTextureMesh(
const pcl::PolygonMesh::Ptr & mesh,
const std::map<int, Transform> & poses,
const std::map<int, std::vector<CameraModel> > & cameraModels,
const std::map<int, cv::Mat> & cameraDepths,
float maxDistance,
float maxDepthError,
float maxAngle,
int minClusterSize,
const std::vector<float> & roiRatios,
const ProgressState * state,
std::vector<std::map<int, pcl::PointXY> > * vertexToPixels,
bool distanceToCamPolicy)
{
UASSERT(mesh->polygons.size());
pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);
textureMesh->cloud = mesh->cloud;
textureMesh->tex_polygons.push_back(mesh->polygons);
// Original from pcl/gpu/kinfu_large_scale/tools/standalone_texture_mapping.cpp:
// Author: Raphael Favier, Technical University Eindhoven, (r.mysurname <aT> tue.nl)
// Create the texturemesh object that will contain our UV-mapped mesh
// create cameras
pcl::texture_mapping::CameraVector cameras = createTextureCameras(
poses,
cameraModels,
cameraDepths,
roiRatios);
// Create materials for each texture (and one extra for occluded faces)
textureMesh->tex_materials.resize (cameras.size () + 1);
for(unsigned int i = 0 ; i <= cameras.size() ; ++i)
{
pcl::TexMaterial mesh_material;
mesh_material.tex_Ka.r = 0.2f;
mesh_material.tex_Ka.g = 0.2f;
mesh_material.tex_Ka.b = 0.2f;
mesh_material.tex_Kd.r = 0.8f;
mesh_material.tex_Kd.g = 0.8f;
mesh_material.tex_Kd.b = 0.8f;
mesh_material.tex_Ks.r = 1.0f;
mesh_material.tex_Ks.g = 1.0f;
mesh_material.tex_Ks.b = 1.0f;
mesh_material.tex_d = 1.0f;
mesh_material.tex_Ns = 75.0f;
mesh_material.tex_illum = 2;
std::stringstream tex_name;
tex_name << "material_" << i;
tex_name >> mesh_material.tex_name;
if(i < cameras.size ())
{
mesh_material.tex_file = cameras[i].texture_file;
}
else
{
mesh_material.tex_file = "occluded";
}
textureMesh->tex_materials[i] = mesh_material;
}
// Texture by projection
pcl::TextureMapping<pcl::PointXYZ> tm; // TextureMapping object that will perform the sort
tm.setMaxDistance(maxDistance);
tm.setMaxAngle(maxAngle);
tm.setMaxDepthError(maxDepthError);
tm.setMinClusterSize(minClusterSize);
if(tm.textureMeshwithMultipleCameras2(*textureMesh, cameras, state, vertexToPixels, distanceToCamPolicy))
{
// compute normals for the mesh if not already here
bool hasNormals = false;
bool hasColors = false;
for(unsigned int i=0; i<textureMesh->cloud.fields.size(); ++i)
{
if(textureMesh->cloud.fields[i].name.compare("normal_x") == 0)
{
hasNormals = true;
}
else if(textureMesh->cloud.fields[i].name.compare("rgb") == 0)
{
hasColors = true;
}
}
if(!hasNormals)
{
// use polygons
if(hasColors)
{
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGBNormal>);
pcl::fromPCLPointCloud2(mesh->cloud, *cloud);
for(unsigned int i=0; i<mesh->polygons.size(); ++i)
{
pcl::Vertices & v = mesh->polygons[i];
UASSERT(v.vertices.size()>2);
Eigen::Vector3f v0(
cloud->at(v.vertices[1]).x - cloud->at(v.vertices[0]).x,
cloud->at(v.vertices[1]).y - cloud->at(v.vertices[0]).y,
cloud->at(v.vertices[1]).z - cloud->at(v.vertices[0]).z);
int last = v.vertices.size()-1;
Eigen::Vector3f v1(
cloud->at(v.vertices[last]).x - cloud->at(v.vertices[0]).x,
cloud->at(v.vertices[last]).y - cloud->at(v.vertices[0]).y,
cloud->at(v.vertices[last]).z - cloud->at(v.vertices[0]).z);
Eigen::Vector3f normal = v0.cross(v1);
normal.normalize();
// flat normal (per face)
for(unsigned int j=0; j<v.vertices.size(); ++j)
{
cloud->at(v.vertices[j]).normal_x = normal[0];
cloud->at(v.vertices[j]).normal_y = normal[1];
cloud->at(v.vertices[j]).normal_z = normal[2];
}
}
pcl::toPCLPointCloud2 (*cloud, textureMesh->cloud);
}
else
{
pcl::PointCloud<pcl::PointNormal>::Ptr cloud (new pcl::PointCloud<pcl::PointNormal>);
pcl::fromPCLPointCloud2(mesh->cloud, *cloud);
for(unsigned int i=0; i<mesh->polygons.size(); ++i)
{
pcl::Vertices & v = mesh->polygons[i];
UASSERT(v.vertices.size()>2);
Eigen::Vector3f v0(
cloud->at(v.vertices[1]).x - cloud->at(v.vertices[0]).x,
cloud->at(v.vertices[1]).y - cloud->at(v.vertices[0]).y,
cloud->at(v.vertices[1]).z - cloud->at(v.vertices[0]).z);
int last = v.vertices.size()-1;
Eigen::Vector3f v1(
cloud->at(v.vertices[last]).x - cloud->at(v.vertices[0]).x,
cloud->at(v.vertices[last]).y - cloud->at(v.vertices[0]).y,
cloud->at(v.vertices[last]).z - cloud->at(v.vertices[0]).z);
Eigen::Vector3f normal = v0.cross(v1);
normal.normalize();
// flat normal (per face)
for(unsigned int j=0; j<v.vertices.size(); ++j)
{
cloud->at(v.vertices[j]).normal_x = normal[0];
cloud->at(v.vertices[j]).normal_y = normal[1];
cloud->at(v.vertices[j]).normal_z = normal[2];
}
}
pcl::toPCLPointCloud2 (*cloud, textureMesh->cloud);
}
}
}
return textureMesh;
}
void cleanTextureMesh(
pcl::TextureMesh & textureMesh,
int minClusterSize)
{
UDEBUG("minClusterSize=%d", minClusterSize);
// Remove occluded polygons (polygons with no texture)
if(textureMesh.tex_coordinates.size())
{
// assume last texture is the occluded texture
textureMesh.tex_coordinates.pop_back();
textureMesh.tex_polygons.pop_back();
textureMesh.tex_materials.pop_back();
if(minClusterSize!=0)
{
// concatenate all polygons
unsigned int totalSize = 0;
for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
{
totalSize+=textureMesh.tex_polygons[t].size();
}
std::vector<pcl::Vertices> allPolygons(totalSize);
int oi=0;
for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
{
for(unsigned int i=0; i<textureMesh.tex_polygons[t].size(); ++i)
{
allPolygons[oi++] = textureMesh.tex_polygons[t][i];
}
}
// filter polygons
std::vector<std::set<int> > neighbors;
std::vector<std::set<int> > vertexToPolygons;
util3d::createPolygonIndexes(allPolygons,
(int)textureMesh.cloud.data.size()/textureMesh.cloud.point_step,
neighbors,
vertexToPolygons);
std::list<std::list<int> > clusters = util3d::clusterPolygons(
neighbors,
minClusterSize<0?0:minClusterSize);
std::set<int> validPolygons;
if(minClusterSize < 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())
{
for(std::list<int>::iterator jter=biggestClusterIndex->begin(); jter!=biggestClusterIndex->end(); ++jter)
{
validPolygons.insert(*jter);
}
}
}
else
{
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)
{
validPolygons.insert(*jter);
}
}
}
if(validPolygons.size() == 0)
{
UWARN("All %d polygons filtered after polygon cluster filtering. Cluster minimum size is %d.",totalSize, minClusterSize);
}
// for each texture
unsigned int allPolygonsIndex = 0;
for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
{
std::vector<pcl::Vertices> filteredPolygons(textureMesh.tex_polygons[t].size());
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > filteredCoordinates(textureMesh.tex_coordinates[t].size());
#else
std::vector<Eigen::Vector2f> filteredCoordinates(textureMesh.tex_coordinates[t].size());
#endif
if(textureMesh.tex_polygons[t].size())
{
UASSERT_MSG(allPolygonsIndex < allPolygons.size(), uFormat("%d vs %d", (int)allPolygonsIndex, (int)allPolygons.size()).c_str());
// 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());
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<filteredPolygons[oi].vertices.size(); ++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(ci);
textureMesh.tex_polygons[t] = filteredPolygons;
textureMesh.tex_coordinates[t] = filteredCoordinates;
}
}
}
}
}
pcl::TextureMesh::Ptr concatenateTextureMeshes(const std::list<pcl::TextureMesh::Ptr> & meshes)
{
pcl::TextureMesh::Ptr output(new 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)
{
if((*iter)->cloud.point_step &&
(*iter)->cloud.data.size()/(*iter)->cloud.point_step &&
(*iter)->tex_polygons.size() &&
(*iter)->tex_coordinates.size())
{
// append point cloud
int polygonStep = output->cloud.height * output->cloud.width;
pcl::PCLPointCloud2 tmp;
#if PCL_VERSION_COMPARE(>=, 1, 10, 0)
pcl::concatenate(output->cloud, iter->get()->cloud, tmp);
#else
pcl::concatenatePointCloud(output->cloud, iter->get()->cloud, tmp);
#endif
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)
{
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);
}
// 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, int maxTextures, float & scale, std::vector<bool> * materialsKept)
{
UASSERT(textureSize>0 && imageSize.width>0 && imageSize.height>0);
if(maxTextures < 1)
{
maxTextures = 1;
}
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)*maxTextures < 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
}
int outputTextures = (materials / (colCount*rowCount)) + (materials % (colCount*rowCount) > 0?1:0);
UDEBUG("materials=%d col=%d row=%d output textures=%d factor=%f scale=%f", materials, colCount, rowCount, outputTextures, factor-epsilon, scale);
UASSERT(mesh.tex_coordinates.size() == mesh.tex_materials.size() && mesh.tex_polygons.size() == mesh.tex_materials.size());
// prepare size
std::vector<int> totalPolygons(outputTextures, 0);
std::vector<int> totalCoordinates(outputTextures, 0);
int count = 0;
for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
{
if(mesh.tex_polygons[i].size())
{
int indexMaterial = count / (colCount*rowCount);
UASSERT(indexMaterial < outputTextures);
totalPolygons[indexMaterial]+=mesh.tex_polygons[i].size();
totalCoordinates[indexMaterial]+=mesh.tex_coordinates[i].size();
++count;
}
}
pcl::TextureMesh outputMesh;
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(), false);
}
for(unsigned int t=0; t<mesh.tex_materials.size(); ++t)
{
if(mesh.tex_polygons[t].size())
{
int indexMaterial = ti / (colCount*rowCount);
UASSERT(indexMaterial < outputTextures);
if((int)outputMesh.tex_polygons.size() <= indexMaterial)
{
std::vector<pcl::Vertices> newPolygons(totalPolygons[indexMaterial]);
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > newCoordinates(totalCoordinates[indexMaterial]); // UV coordinates
#else
std::vector<Eigen::Vector2f> newCoordinates(totalCoordinates[indexMaterial]); // UV coordinates
#endif
outputMesh.tex_polygons.push_back(newPolygons);
outputMesh.tex_coordinates.push_back(newCoordinates);
pi=0;
ci=0;
}
int row = (ti/colCount) % rowCount;
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)
{
UASSERT(pi < (int)outputMesh.tex_polygons[indexMaterial].size());
outputMesh.tex_polygons[indexMaterial][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)
{
outputMesh.tex_coordinates[indexMaterial][ci][0] = v[0]*scaledWidth + offsetU;
outputMesh.tex_coordinates[indexMaterial][ci][1] = v[1]*scaledHeight + offsetV;
}
else
{
outputMesh.tex_coordinates[indexMaterial][ci] = v;
}
++ci;
}
++ti;
if(materialsKept)
{
materialsKept->at(t) = true;
}
}
}
pcl::TexMaterial m = mesh.tex_materials.front();
mesh.tex_materials.clear();
for(int i=0; i<outputTextures; ++i)
{
m.tex_file = "texture";
m.tex_name = "material";
if(outputTextures > 1)
{
m.tex_file += uNumber2Str(i);
m.tex_name += uNumber2Str(i);
}
mesh.tex_materials.push_back(m);
}
mesh.tex_coordinates = outputMesh.tex_coordinates;
mesh.tex_polygons = outputMesh.tex_polygons;
}
}
std::vector<std::vector<RTABMAP_PCL_INDEX> > convertPolygonsFromPCL(const std::vector<pcl::Vertices> & polygons)
{
std::vector<std::vector<RTABMAP_PCL_INDEX> > polygonsOut(polygons.size());
for(unsigned int p=0; p<polygons.size(); ++p)
{
polygonsOut[p] = polygons[p].vertices;
}
return polygonsOut;
}
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > convertPolygonsFromPCL(const std::vector<std::vector<pcl::Vertices> > & tex_polygons)
{
std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygonsOut(tex_polygons.size());
for(unsigned int t=0; t<tex_polygons.size(); ++t)
{
polygonsOut[t].resize(tex_polygons[t].size());
for(unsigned int p=0; p<tex_polygons[t].size(); ++p)
{
polygonsOut[t][p] = tex_polygons[t][p].vertices;
}
}
return polygonsOut;
}
std::vector<pcl::Vertices> convertPolygonsToPCL(const std::vector<std::vector<RTABMAP_PCL_INDEX> > & polygons)
{
std::vector<pcl::Vertices> polygonsOut(polygons.size());
for(unsigned int p=0; p<polygons.size(); ++p)
{
polygonsOut[p].vertices = polygons[p];
}
return polygonsOut;
}
std::vector<std::vector<pcl::Vertices> > convertPolygonsToPCL(const std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > & tex_polygons)
{
std::vector<std::vector<pcl::Vertices> > polygonsOut(tex_polygons.size());
for(unsigned int t=0; t<tex_polygons.size(); ++t)
{
polygonsOut[t].resize(tex_polygons[t].size());
for(unsigned int p=0; p<tex_polygons[t].size(); ++p)
{
polygonsOut[t][p].vertices = tex_polygons[t][p];
}
}
return polygonsOut;
}
pcl::TextureMesh::Ptr assembleTextureMesh(
const cv::Mat & cloudMat,
const std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > & polygons,
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
const std::vector<std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > > & texCoords,
#else
const std::vector<std::vector<Eigen::Vector2f> > & texCoords,
#endif
cv::Mat & textures,
bool mergeTextures)
{
pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);
if(cloudMat.channels() <= 3)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = rtabmap::util3d::laserScanToPointCloud(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
}
else if(cloudMat.channels() == 4)
{
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGB(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
}
else if(cloudMat.channels() == 6)
{
pcl::PointCloud<pcl::PointNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudNormal(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
}
else if(cloudMat.channels() == 7)
{
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGBNormal(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
}
if(textureMesh->cloud.data.size() && polygons.size())
{
textureMesh->tex_polygons.resize(polygons.size());
for(unsigned int t=0; t<polygons.size(); ++t)
{
textureMesh->tex_polygons[t].resize(polygons[t].size());
for(unsigned int p=0; p<polygons[t].size(); ++p)
{
textureMesh->tex_polygons[t][p].vertices = polygons[t][p];
}
}
if(!texCoords.empty() && !textures.empty())
{
textureMesh->tex_coordinates = texCoords;
textureMesh->tex_materials.resize (textureMesh->tex_coordinates.size());
for(unsigned int i = 0 ; i < textureMesh->tex_coordinates.size() ; ++i)
{
pcl::TexMaterial mesh_material;
mesh_material.tex_Ka.r = 0.2f;
mesh_material.tex_Ka.g = 0.2f;
mesh_material.tex_Ka.b = 0.2f;
mesh_material.tex_Kd.r = 0.8f;
mesh_material.tex_Kd.g = 0.8f;
mesh_material.tex_Kd.b = 0.8f;
mesh_material.tex_Ks.r = 1.0f;
mesh_material.tex_Ks.g = 1.0f;
mesh_material.tex_Ks.b = 1.0f;
mesh_material.tex_d = 1.0f;
mesh_material.tex_Ns = 75.0f;
mesh_material.tex_illum = 2;
std::stringstream tex_name;
tex_name << "material_" << i;
tex_name >> mesh_material.tex_name;
mesh_material.tex_file = uFormat("%d", i);
textureMesh->tex_materials[i] = mesh_material;
}
if(mergeTextures && textures.cols/textures.rows > 1)
{
UASSERT(textures.cols % textures.rows == 0 && textures.cols/textures.rows == (int)textureMesh->tex_coordinates.size());
std::vector<bool> materialsKept;
float scale = 0.0f;
cv::Size imageSize(textures.rows, textures.rows);
int imageType = textures.type();
rtabmap::util3d::concatenateTextureMaterials(*textureMesh, imageSize, textures.rows, 1, scale, &materialsKept);
if(scale && textureMesh->tex_materials.size() == 1)
{
int cols = float(textures.rows)/(scale*imageSize.width);
int rows = float(textures.rows)/(scale*imageSize.height);
cv::Mat mergedTextures = cv::Mat(textures.rows, textures.rows, imageType, cv::Scalar::all(255));
// make a blank texture
cv::Size resizedImageSize(int(imageSize.width*scale), int(imageSize.height*scale));
int oi=0;
for(int i=0; i<(int)materialsKept.size(); ++i)
{
if(materialsKept.at(i))
{
int u = oi%cols * resizedImageSize.width;
int v = ((oi/cols) % rows ) * resizedImageSize.height;
UASSERT(u < textures.rows-resizedImageSize.width);
UASSERT(v < textures.rows-resizedImageSize.height);
cv::Mat resizedImage;
cv::resize(textures(cv::Range::all(), cv::Range(i*textures.rows, (i+1)*textures.rows)), resizedImage, resizedImageSize, 0.0f, 0.0f, cv::INTER_AREA);
UASSERT(resizedImage.type() == mergedTextures.type());
resizedImage.copyTo(mergedTextures(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));
++oi;
}
}
textures = mergedTextures;
}
}
}
}
return textureMesh;
}
pcl::PolygonMesh::Ptr assemblePolygonMesh(
const cv::Mat & cloudMat,
const std::vector<std::vector<RTABMAP_PCL_INDEX> > & polygons)
{
pcl::PolygonMesh::Ptr polygonMesh(new pcl::PolygonMesh);
if(cloudMat.channels() <= 3)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = rtabmap::util3d::laserScanToPointCloud(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
}
else if(cloudMat.channels() == 4)
{
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGB(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
}
else if(cloudMat.channels() == 6)
{
pcl::PointCloud<pcl::PointNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudNormal(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
}
else if(cloudMat.channels() == 7)
{
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGBNormal(LaserScan::backwardCompatibility(cloudMat));
pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
}
if(polygonMesh->cloud.data.size() && polygons.size())
{
polygonMesh->polygons.resize(polygons.size());
for(unsigned int p=0; p<polygons.size(); ++p)
{
polygonMesh->polygons[p].vertices = polygons[p];
}
}
return polygonMesh;
}
double sqr(uchar v)
{
return double(v)*double(v);
}
cv::Mat mergeTextures(
pcl::TextureMesh & mesh,
const std::map<int, cv::Mat> & images,
const std::map<int, CameraModel> & calibrations,
const Memory * memory,
const DBDriver * dbDriver,
int textureSize,
int textureCount,
const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels,
bool gainCompensation,
float gainBeta,
bool gainRGB,
bool blending,
int blendingDecimation,
int brightnessContrastRatioLow,
int brightnessContrastRatioHigh,
bool exposureFusion,
const ProgressState * state,
unsigned char blankValue,
bool clearVertexColorUnderTexture,
std::map<int, std::map<int, cv::Vec4d> > * gains,
std::map<int, std::map<int, cv::Mat> > * blendingGains,
std::pair<float, float> * contrastValues)
{
std::map<int, std::vector<CameraModel> > calibVectors;
for(std::map<int, CameraModel>::const_iterator iter=calibrations.begin(); iter!=calibrations.end(); ++iter)
{
std::vector<CameraModel> m;
m.push_back(iter->second);
calibVectors.insert(std::make_pair(iter->first, m));
}
return mergeTextures(mesh,
images,
calibVectors,
memory,
dbDriver,
textureSize,
textureCount,
vertexToPixels,
gainCompensation,
gainBeta,
gainRGB,
blending,
blendingDecimation,
brightnessContrastRatioLow,
brightnessContrastRatioHigh,
exposureFusion,
state,
blankValue,
clearVertexColorUnderTexture,
gains,
blendingGains,
contrastValues);
}
cv::Mat mergeTextures(
pcl::TextureMesh & mesh,
const std::map<int, cv::Mat> & images,
const std::map<int, std::vector<CameraModel> > & calibrations,
const Memory * memory,
const DBDriver * dbDriver,
int textureSize,
int textureCount,
const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels,
bool gainCompensation,
float gainBeta,
bool gainRGB,
bool blending,
int blendingDecimation,
int brightnessContrastRatioLow,
int brightnessContrastRatioHigh,
bool exposureFusion,
const ProgressState * state,
unsigned char blankValue,
bool clearVertexColorUnderTexture,
std::map<int, std::map<int, cv::Vec4d> > * gainsOut,
std::map<int, std::map<int, cv::Mat> > * blendingGainsOut,
std::pair<float, float> * contrastValuesOut)
{
//get texture size, if disabled use default 1024
UASSERT(textureSize%256 == 0);
UDEBUG("textureSize = %d", textureSize);
cv::Mat globalTextures;
if(!mesh.tex_materials.empty())
{
std::vector<std::pair<int, int> > textures(mesh.tex_materials.size(), std::pair<int, int>(-1,0));
cv::Size imageSize;
const int imageType=CV_8UC3;
UDEBUG("");
for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
{
std::list<std::string> texFileSplit = uSplit(mesh.tex_materials[i].tex_file, '_');
if(!mesh.tex_materials[i].tex_file.empty() &&
mesh.tex_polygons[i].size() &&
uIsInteger(texFileSplit.front(), false))
{
textures[i].first = uStr2Int(texFileSplit.front());
if(texFileSplit.size() == 2 &&
uIsInteger(texFileSplit.back(), false) )
{
textures[i].second = uStr2Int(texFileSplit.back());
}
int textureId = textures[i].first;
if(imageSize.width == 0 || imageSize.height == 0)
{
if(images.find(textureId) != images.end() &&
!images.find(textureId)->second.empty() &&
calibrations.find(textureId) != calibrations.end())
{
const std::vector<CameraModel> & models = calibrations.find(textureId)->second;
UASSERT(models.size()>=1);
if( models[0].imageHeight()>0 &&
models[0].imageWidth()>0)
{
imageSize = models[0].imageSize();
}
else if(images.find(textureId)!=images.end())
{
// backward compatibility for image size not set in CameraModel
cv::Mat image = images.find(textureId)->second;
if(image.rows == 1 && image.type() == CV_8UC1)
{
image = uncompressImage(image);
}
UASSERT(!image.empty());
imageSize = image.size();
if(models.size()>1)
{
imageSize.width/=models.size();
}
}
}
else if(memory)
{
SensorData data = memory->getNodeData(textureId, true, false, false, false);
const std::vector<CameraModel> & models = data.cameraModels();
const std::vector<StereoCameraModel> & stereoModels = data.stereoCameraModels();
if(models.size()>=1 &&
models[0].imageHeight()>0 &&
models[0].imageWidth()>0)
{
imageSize = models[0].imageSize();
}
else if(stereoModels.size()>=1 &&
stereoModels[0].left().imageHeight() > 0 &&
stereoModels[0].left().imageWidth() > 0)
{
imageSize = stereoModels[0].left().imageSize();
}
else // backward compatibility for image size not set in CameraModel
{
cv::Mat image;
data.uncompressDataConst(&image, 0);
UASSERT(!image.empty());
imageSize = image.size();
if(data.cameraModels().size()>1)
{
imageSize.width/=data.cameraModels().size();
}
}
}
else if(dbDriver)
{
std::vector<CameraModel> models;
std::vector<StereoCameraModel> stereoModels;
dbDriver->getCalibration(textureId, models, stereoModels);
if(models.size()>=1 &&
models[0].imageHeight()>0 &&
models[0].imageWidth()>0)
{
imageSize = models[0].imageSize();
}
else if(stereoModels.size()>=1 &&
stereoModels[0].left().imageHeight() > 0 &&
stereoModels[0].left().imageWidth() > 0)
{
imageSize = stereoModels[0].left().imageSize();
}
else // backward compatibility for image size not set in CameraModel
{
SensorData data;
dbDriver->getNodeData(textureId, data, true, false, false, false);
cv::Mat image;
data.uncompressDataConst(&image, 0);
UASSERT(!image.empty());
imageSize = image.size();
if(data.cameraModels().size()>1)
{
imageSize.width/=data.cameraModels().size();
}
}
}
}
}
else if(mesh.tex_polygons[i].size() && mesh.tex_materials[i].tex_file.compare("occluded")!=0)
{
UWARN("Failed parsing texture file name: %s", mesh.tex_materials[i].tex_file.c_str());
}
}
UDEBUG("textures=%d imageSize=%dx%d", (int)textures.size(), imageSize.height, imageSize.width);
if(textures.size() && imageSize.height>0 && imageSize.width>0)
{
float scale = 0.0f;
UDEBUG("");
std::vector<bool> materialsKept;
util3d::concatenateTextureMaterials(mesh, imageSize, textureSize, textureCount, scale, &materialsKept);
if(scale && mesh.tex_materials.size())
{
int materials = (int)mesh.tex_materials.size();
int cols = float(textureSize)/(scale*imageSize.width);
int rows = float(textureSize)/(scale*imageSize.height);
globalTextures = cv::Mat(textureSize, materials*textureSize, imageType, cv::Scalar::all(blankValue));
cv::Mat globalTextureMasks = cv::Mat(textureSize, materials*textureSize, CV_8UC1, cv::Scalar::all(0));
// used for multi camera texturing, to avoid reloading same texture for sub cameras
cv::Mat previousImage;
int previousTextureId = 0;
std::vector<CameraModel> previousCameraModels;
// make a blank texture
cv::Mat emptyImage(int(imageSize.height*scale), int(imageSize.width*scale), imageType, cv::Scalar::all(blankValue));
cv::Mat emptyImageMask(int(imageSize.height*scale), int(imageSize.width*scale), CV_8UC1, cv::Scalar::all(255));
int oi=0;
std::vector<cv::Point2i> imageOrigin(textures.size());
std::vector<int> newCamIndex(textures.size(), -1);
for(int t=0; t<(int)textures.size(); ++t)
{
if(materialsKept.at(t))
{
int indexMaterial = oi / (cols*rows);
UASSERT(indexMaterial < materials);
newCamIndex[t] = oi;
int u = oi%cols * emptyImage.cols;
int v = ((oi/cols) % rows ) * emptyImage.rows;
UASSERT_MSG(u < textureSize-emptyImage.cols, uFormat("u=%d textureSize=%d emptyImage.cols=%d", u, textureSize, emptyImage.cols).c_str());
UASSERT_MSG(v < textureSize-emptyImage.rows, uFormat("v=%d textureSize=%d emptyImage.rows=%d", v, textureSize, emptyImage.rows).c_str());
imageOrigin[t].x = u;
imageOrigin[t].y = v;
if(textures[t].first>=0)
{
cv::Mat image;
std::vector<CameraModel> models;
if(textures[t].first == previousTextureId)
{
image = previousImage;
models = previousCameraModels;
}
else
{
if(images.find(textures[t].first) != images.end() &&
!images.find(textures[t].first)->second.empty() &&
calibrations.find(textures[t].first) != calibrations.end())
{
image = images.find(textures[t].first)->second;
if(image.rows == 1 && image.type() == CV_8UC1)
{
image = uncompressImage(image);
}
models = calibrations.find(textures[t].first)->second;
}
else if(memory)
{
SensorData data = memory->getNodeData(textures[t].first, true, false, false, false);
models = data.cameraModels();
if(models.empty() && !data.stereoCameraModels().empty())
{
for(size_t i=0; i<data.stereoCameraModels().size(); ++i)
{
models.push_back(data.stereoCameraModels()[i].left());
}
}
data.uncompressDataConst(&image, 0);
}
else if(dbDriver)
{
SensorData data;
dbDriver->getNodeData(textures[t].first, data, true, false, false, false);
data.uncompressDataConst(&image, 0);
std::vector<StereoCameraModel> stereoModels;
dbDriver->getCalibration(textures[t].first, models, stereoModels);
if(models.empty() && !stereoModels.empty())
{
for(size_t i=0; i<stereoModels.size(); ++i)
{
models.push_back(stereoModels[i].left());
}
}
}
previousImage = image;
previousCameraModels = models;
previousTextureId = textures[t].first;
}
UASSERT(!image.empty());
if(textures[t].second>=0)
{
UASSERT(textures[t].second < (int)models.size());
int width = image.cols/models.size();
image = image.colRange(width*textures[t].second, width*(textures[t].second+1));
}
cv::Mat resizedImage;
cv::resize(image, resizedImage, emptyImage.size(), 0.0f, 0.0f, cv::INTER_AREA);
UASSERT(resizedImage.type() == CV_8UC1 || resizedImage.type() == CV_8UC3);
if(resizedImage.type() == CV_8UC1)
{
cv::Mat resizedImageColor;
cv::cvtColor(resizedImage, resizedImageColor, CV_GRAY2BGR);
resizedImage = resizedImageColor;
}
UASSERT(resizedImage.type() == globalTextures.type());
resizedImage.copyTo(globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, resizedImage.cols, resizedImage.rows)));
emptyImageMask.copyTo(globalTextureMasks(cv::Rect(u+indexMaterial*globalTextureMasks.rows, v, resizedImage.cols, resizedImage.rows)));
}
else
{
emptyImage.copyTo(globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, emptyImage.cols, emptyImage.rows)));
}
++oi;
}
if(state)
{
if(state->isCanceled())
{
return cv::Mat();
}
state->callback(uFormat("Assembled texture %d/%d.", t+1, (int)textures.size()));
}
}
UTimer timer;
if(vertexToPixels.size())
{
//UWARN("Saving original.png", globalTexture);
//cv::imwrite("original.png", globalTexture);
if(gainCompensation)
{
/**
* Original code from OpenCV: GainCompensator
*/
const int num_images = static_cast<int>(oi);
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> 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);
// Adjust UV coordinates to globalTexture
for(unsigned int p=0; p<vertexToPixels.size(); ++p)
{
for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
{
if(materialsKept.at(iter->first))
{
N(newCamIndex[iter->first], newCamIndex[iter->first]) +=1;
std::map<int, pcl::PointXY>::const_iterator jter=iter;
++jter;
int k = 1;
for(; jter!=vertexToPixels[p].end(); ++jter, ++k)
{
if(materialsKept.at(jter->first))
{
int i = newCamIndex[iter->first];
int j = newCamIndex[jter->first];
N(i, j) += 1;
N(j, i) += 1;
int indexMaterial = i / (cols*rows);
// uv in globalTexture
int ui = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
int vi = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
int uj = jter->second.x*emptyImage.cols + imageOrigin[jter->first].x;
int vj = (1.0-jter->second.y)*emptyImage.rows + imageOrigin[jter->first].y;
cv::Vec3b * pt1 = globalTextures.ptr<cv::Vec3b>(vi,ui+indexMaterial*globalTextures.rows);
cv::Vec3b * pt2 = globalTextures.ptr<cv::Vec3b>(vj,uj+indexMaterial*globalTextures.rows);
I(i, j) += std::sqrt(static_cast<double>(sqr(pt1->val[0]) + sqr(pt1->val[1]) + sqr(pt1->val[2])));
I(j, i) += std::sqrt(static_cast<double>(sqr(pt2->val[0]) + sqr(pt2->val[1]) + sqr(pt2->val[2])));
IR(i, j) += static_cast<double>(pt1->val[2]);
IR(j, i) += static_cast<double>(pt2->val[2]);
IG(i, j) += static_cast<double>(pt1->val[1]);
IG(j, i) += static_cast<double>(pt2->val[1]);
IB(i, j) += static_cast<double>(pt1->val[0]);
IB(j, i) += static_cast<double>(pt2->val[0]);
}
}
}
}
}
for(int i=0; i<num_images; ++i)
{
for(int j=i; j<num_images; ++j)
{
if(i == j)
{
if(N(i,j) == 0)
{
N(i,j) = 1;
}
}
else if(N(i, j))
{
I(i, j) /= N(i, j);
I(j, i) /= N(j, i);
IR(i, j) /= N(i, j);
IR(j, i) /= N(j, i);
IG(i, j) /= N(i, j);
IG(j, i) /= N(j, i);
IB(i, j) /= N(i, j);
IB(j, i) /= N(j, i);
}
}
}
cv::Mat_<double> A(num_images, num_images); A.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);
double alpha = 0.01;
double beta = gainBeta;
for (int i = 0; i < num_images; ++i)
{
for (int j = 0; j < num_images; ++j)
{
b(i, 0) += 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;
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);
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);
}
}
cv::Mat_<double> gainsGray, gainsR, gainsG, gainsB;
cv::solve(A, b, gainsGray);
cv::solve(AR, b, gainsR);
cv::solve(AG, b, gainsG);
cv::solve(AB, b, gainsB);
cv::Mat_<double> gains(gainsGray.rows, 4);
gainsGray.copyTo(gains.col(0));
gainsR.copyTo(gains.col(1));
gainsG.copyTo(gains.col(2));
gainsB.copyTo(gains.col(3));
for(int t=0; t<(int)textures.size(); ++t)
{
//break;
if(materialsKept.at(t))
{
int u = imageOrigin[t].x;
int v = imageOrigin[t].y;
UDEBUG("Gain cam%d = %f", newCamIndex[t], gainsGray(newCamIndex[t], 0));
int indexMaterial = newCamIndex[t] / (cols*rows);
cv::Mat roi = globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, emptyImage.cols, emptyImage.rows));
std::vector<cv::Mat> channels;
cv::split(roi, channels);
// assuming BGR
cv::multiply(channels[0], gains(newCamIndex[t], gainRGB?3:0), channels[0]);
cv::multiply(channels[1], gains(newCamIndex[t], gainRGB?2:0), channels[1]);
cv::multiply(channels[2], gains(newCamIndex[t], gainRGB?1:0), channels[2]);
cv::merge(channels, roi);
if(gainsOut)
{
cv::Vec4d g(
gains(newCamIndex[t], 0),
gains(newCamIndex[t], 1),
gains(newCamIndex[t], 2),
gains(newCamIndex[t], 3));
if(gainsOut->find(textures[t].first) == gainsOut->end())
{
std::map<int,cv::Vec4d> value;
value.insert(std::make_pair(textures[t].second, g));
gainsOut->insert(std::make_pair(textures[t].first, value));
}
else
{
gainsOut->at(textures[t].first).insert(std::make_pair(textures[t].second, g));
}
}
}
}
//UWARN("Saving gain.png", globalTexture);
//cv::imwrite("gain.png", globalTexture);
if(state) state->callback(uFormat("Gain compensation %fs", timer.ticks()));
}
if(blending)
{
// blending BGR
int decimation = 1;
if(blendingDecimation <= 0)
{
// determinate decimation to apply
std::vector<float> edgeLengths;
if(mesh.tex_coordinates.size() && mesh.tex_coordinates[0].size())
{
UASSERT(mesh.tex_polygons.size() && mesh.tex_polygons[0].size() && mesh.tex_polygons[0][0].vertices.size());
int polygonSize = mesh.tex_polygons[0][0].vertices.size();
UDEBUG("polygon size=%d", polygonSize);
for(unsigned int k=0; k<mesh.tex_coordinates.size(); ++k)
{
for(unsigned int i=0; i<mesh.tex_coordinates[k].size(); i+=polygonSize)
{
for(int j=0; j<polygonSize; ++j)
{
const Eigen::Vector2f & uc1 = mesh.tex_coordinates[k][i + j];
const Eigen::Vector2f & uc2 = mesh.tex_coordinates[k][i + (j+1)%polygonSize];
Eigen::Vector2f edge = (uc1-uc2)*textureSize;
edgeLengths.push_back(fabs(edge[0]));
edgeLengths.push_back(fabs(edge[1]));
}
}
}
float edgeLength = 0.0f;
if(edgeLengths.size())
{
std::sort(edgeLengths.begin(), edgeLengths.end());
float m = uMean(edgeLengths.data(), edgeLengths.size());
float stddev = std::sqrt(uVariance(edgeLengths.data(), edgeLengths.size(), m));
edgeLength = m+stddev;
decimation = 1 << 6;
for(int i=1; i<=6; ++i)
{
if(float(1 << i) >= edgeLength)
{
decimation = 1 << i;
break;
}
}
}
UDEBUG("edge length=%f decimation=%d", edgeLength, decimation);
}
}
else
{
if(blendingDecimation > 1)
{
UASSERT(textureSize % blendingDecimation == 0);
}
decimation = blendingDecimation;
UDEBUG("decimation=%d", decimation);
}
std::vector<cv::Mat> blendGains(materials);
for(int i=0; i<materials;++i)
{
blendGains[i] = cv::Mat(globalTextures.rows/decimation, globalTextures.rows/decimation, CV_32FC3, cv::Scalar::all(1.0f));
}
for(unsigned int p=0; p<vertexToPixels.size(); ++p)
{
if(vertexToPixels[p].size() > 1)
{
std::vector<float> gainsB(vertexToPixels[p].size());
std::vector<float> gainsG(vertexToPixels[p].size());
std::vector<float> gainsR(vertexToPixels[p].size());
float sumWeight = 0.0f;
int k=0;
for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
{
if(materialsKept.at(iter->first))
{
int u = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
int v = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
float x = iter->second.x - 0.5f;
float y = iter->second.y - 0.5f;
float weight = 0.7f - sqrt(x*x+y*y);
if(weight<0.0f)
{
weight = 0.0f;
}
int indexMaterial = newCamIndex[iter->first] / (cols*rows);
cv::Vec3b * pt = globalTextures.ptr<cv::Vec3b>(v,u+indexMaterial*globalTextures.rows);
gainsB[k] = static_cast<double>(pt->val[0]) * weight;
gainsG[k] = static_cast<double>(pt->val[1]) * weight;
gainsR[k] = static_cast<double>(pt->val[2]) * weight;
sumWeight += weight;
++k;
}
}
gainsB.resize(k);
gainsG.resize(k);
gainsR.resize(k);
if(sumWeight > 0)
{
float targetColor[3];
targetColor[0] = uSum(gainsB.data(), gainsB.size()) / sumWeight;
targetColor[1] = uSum(gainsG.data(), gainsG.size()) / sumWeight;
targetColor[2] = uSum(gainsR.data(), gainsR.size()) / sumWeight;
for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
{
if(materialsKept.at(iter->first))
{
int u = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
int v = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
int indexMaterial = newCamIndex[iter->first] / (cols*rows);
cv::Vec3b * pt = globalTextures.ptr<cv::Vec3b>(v,u+indexMaterial*globalTextures.rows);
float gB = targetColor[0]/(pt->val[0]==0?1.0f:pt->val[0]);
float gG = targetColor[1]/(pt->val[1]==0?1.0f:pt->val[1]);
float gR = targetColor[2]/(pt->val[2]==0?1.0f:pt->val[2]);
cv::Vec3f * ptr = blendGains[indexMaterial].ptr<cv::Vec3f>(v/decimation, u/decimation);
ptr->val[0] = (gB>1.3f)?1.3f:(gB<0.7f)?0.7f:gB;
ptr->val[1] = (gG>1.3f)?1.3f:(gG<0.7f)?0.7f:gG;
ptr->val[2] = (gR>1.3f)?1.3f:(gR<0.7f)?0.7f:gR;
}
}
}
}
}
if(blendingGainsOut)
{
for(int t=0; t<(int)textures.size(); ++t)
{
//break;
if(materialsKept.at(t))
{
int u = imageOrigin[t].x/decimation;
int v = imageOrigin[t].y/decimation;
int indexMaterial = newCamIndex[t] / (cols*rows);
cv::Mat roi = blendGains[indexMaterial](cv::Rect(u, v, emptyImage.cols/decimation, emptyImage.rows/decimation));
if(blendingGainsOut->find(textures[t].first) == blendingGainsOut->end())
{
std::map<int,cv::Mat> value;
value.insert(std::make_pair(textures[t].second, roi.clone()));
blendingGainsOut->insert(std::make_pair(textures[t].first, value));
}
else
{
blendingGainsOut->at(textures[t].first).insert(std::make_pair(textures[t].second, roi.clone()));
}
}
}
}
for(int i=0; i<materials; ++i)
{
/*std::vector<cv::Mat> channels;
cv::split(blendGains, channels);
cv::Mat img;
channels[0].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendSmallB.png", img);
channels[1].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendSmallG.png", img);
channels[2].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendSmallR.png", img);*/
cv::Mat globalTexturesROI = globalTextures(cv::Range::all(), cv::Range(i*globalTextures.rows, (i+1)*globalTextures.rows));
cv::Mat dst;
cv::blur(blendGains[i], dst, cv::Size(3,3));
cv::resize(dst, blendGains[i], globalTexturesROI.size(), 0, 0, cv::INTER_LINEAR);
/*cv::split(blendGains, channels);
channels[0].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendFullB.png", img);
channels[1].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendFullG.png", img);
channels[2].convertTo(img,CV_8U,128.0,0);
cv::imwrite("blendFullR.png", img);*/
cv::multiply(globalTexturesROI, blendGains[i], globalTexturesROI, 1.0, CV_8UC3);
//UWARN("Saving blending.png", globalTexture);
//cv::imwrite("blending.png", globalTexture);
}
if(state) state->callback(uFormat("Blending (decimation=%d) %fs", decimation, timer.ticks()));
}
}
if(brightnessContrastRatioLow > 0 || brightnessContrastRatioHigh > 0)
{
if(exposureFusion)
{
std::vector<cv::Mat> images;
images.push_back(globalTextures);
if (brightnessContrastRatioLow > 0)
{
images.push_back(util2d::brightnessAndContrastAuto(
globalTextures,
globalTextureMasks,
(float)brightnessContrastRatioLow,
0.0f));
}
if (brightnessContrastRatioHigh > 0)
{
images.push_back(util2d::brightnessAndContrastAuto(
globalTextures,
globalTextureMasks,
0.0f,
(float)brightnessContrastRatioHigh));
}
globalTextures = util2d::exposureFusion(images);
}
else
{
float alpha, beta;
globalTextures = util2d::brightnessAndContrastAuto(
globalTextures,
globalTextureMasks,
(float)brightnessContrastRatioLow,
(float)brightnessContrastRatioHigh,
&alpha,
&beta);
if(contrastValuesOut)
{
contrastValuesOut->first = alpha;
contrastValuesOut->second = beta;
}
}
if(state) state->callback(uFormat("Brightness and contrast auto %fs", timer.ticks()));
}
}
// Cloud color
if(clearVertexColorUnderTexture)
{
int colorOffset = 0;
for(unsigned int i=0; i<mesh.cloud.fields.size(); ++i)
{
if(mesh.cloud.fields[i].name.compare("rgb") == 0)
{
colorOffset = mesh.cloud.fields[i].offset;
break;
}
}
if(colorOffset>0)
{
pcl::IndicesPtr notTexturedVertexIndices(new std::vector<int>);
UASSERT(mesh.tex_coordinates.size() == mesh.tex_polygons.size());
for(size_t t=0; t<mesh.tex_polygons.size(); ++t)
{
int pixelIndex = 0;
for(size_t p=0; p<mesh.tex_polygons[t].size(); ++p)
{
// only clear polygon color if all 3 vertices have valid tex coordinates.
bool valid = true;
for(size_t v=0; v<mesh.tex_polygons[t][p].vertices.size() && valid; ++v)
{
UASSERT(pixelIndex+v < mesh.tex_coordinates[t].size());
const Eigen::Vector2f & uv = mesh.tex_coordinates[t][pixelIndex+v];
if(uv[0] == -1 || uv[1] == -1)
{
valid = false;
}
}
if(!valid)
{
for(size_t v=0; v<mesh.tex_polygons[t][p].vertices.size(); ++v)
{
int vertex = mesh.tex_polygons[t][p].vertices[v];
notTexturedVertexIndices->push_back(vertex);
}
}
pixelIndex+=mesh.tex_polygons[t][p].vertices.size();
}
}
// Set up the full indices set
pcl::IndicesPtr full_indices(new std::vector<int>(mesh.cloud.width* mesh.cloud.height));
for (size_t fii = 0; fii < full_indices->size(); ++fii) // fii = full indices iterator
full_indices->at(fii) = fii;
// Set up the sorted input indices
std::sort (notTexturedVertexIndices->begin (), notTexturedVertexIndices->end ());
// Store the difference in indices
pcl::IndicesPtr texturedVertexIndices(new std::vector<int>());
std::set_difference (full_indices->begin (), full_indices->end (), notTexturedVertexIndices->begin (), notTexturedVertexIndices->end (), std::inserter (*texturedVertexIndices, texturedVertexIndices->begin ()));
for(size_t i=0; i<texturedVertexIndices->size(); ++i)
{
std::uint32_t white = 0xffffff;
UASSERT(texturedVertexIndices->at(i) * mesh.cloud.point_step + colorOffset < mesh.cloud.data.size());
memcpy(&mesh.cloud.data.data()[texturedVertexIndices->at(i) * mesh.cloud.point_step + colorOffset], reinterpret_cast<float*>(&white), sizeof(float));
}
}
}
}
}
UDEBUG("globalTextures=%d", globalTextures.cols?globalTextures.cols / globalTextures.rows:0);
return globalTextures;
}
void fixTextureMeshForVisualization(pcl::TextureMesh & textureMesh)
{
// 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 < textureMesh.tex_coordinates.size(); ++t)
{
if(textureMesh.tex_polygons[t].size())
{
pcl::PointCloud<pcl::PointXYZ>::Ptr originalCloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::fromPCLPointCloud2(textureMesh.cloud, *originalCloud);
// make a cloud with as many points than polygon vertices
unsigned int nPoints = textureMesh.tex_coordinates[t].size();
UASSERT(nPoints == textureMesh.tex_polygons[t].size()*textureMesh.tex_polygons[t][0].vertices.size()); // assuming polygon size is constant!
pcl::PointCloud<pcl::PointXYZ>::Ptr newCloud(new pcl::PointCloud<pcl::PointXYZ>);
newCloud->resize(nPoints);
unsigned int oi = 0;
for (unsigned int i = 0; i < textureMesh.tex_polygons[t].size(); ++i)
{
pcl::Vertices & vertices = textureMesh.tex_polygons[t][i];
for(unsigned int j=0; j<vertices.vertices.size(); ++j)
{
UASSERT(oi < newCloud->size());
UASSERT_MSG((size_t)vertices.vertices[j] < originalCloud->size(), uFormat("%d vs %d", vertices.vertices[j], (int)originalCloud->size()).c_str());
newCloud->at(oi) = originalCloud->at(vertices.vertices[j]);
vertices.vertices[j] = oi; // new vertex index
++oi;
}
}
pcl::toPCLPointCloud2(*newCloud, textureMesh.cloud);
}
}
}
bool multiBandTexturing(
const std::string & outputOBJPath,
const pcl::PCLPointCloud2 & cloud,
const std::vector<pcl::Vertices> & polygons,
const std::map<int, Transform> & cameraPoses,
const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels, // required output of util3d::createTextureMesh()
const std::map<int, cv::Mat> & images, // raw or compressed, can be empty if memory or dbDriver should be used
const std::map<int, std::vector<CameraModel> > & cameraModels, // Should match images
const Memory * memory, // Should be set if images are not set
const DBDriver * dbDriver, // Should be set if images and memory are not set
int textureSize,
const std::string & textureFormat,
const std::map<int, std::map<int, cv::Vec4d> > & gains, // optional output of util3d::mergeTextures()
const std::map<int, std::map<int, cv::Mat> > & blendingGains, // optional output of util3d::mergeTextures()
const std::pair<float, float> & contrastValues, // optional output of util3d::mergeTextures()
bool gainRGB)
{
return multiBandTexturing(
outputOBJPath,
cloud,
polygons,
cameraPoses,
vertexToPixels,
images,
cameraModels,
memory,
dbDriver,
textureSize,
2,
"1 5 10 0",
textureFormat,
gains,
blendingGains,
contrastValues,
gainRGB);
}
bool multiBandTexturing(
const std::string & outputOBJPath,
const pcl::PCLPointCloud2 & cloud,
const std::vector<pcl::Vertices> & polygons,
const std::map<int, Transform> & cameraPoses,
const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels,
const std::map<int, cv::Mat> & images,
const std::map<int, std::vector<CameraModel> > & cameraModels,
const Memory * memory,
const DBDriver * dbDriver,
unsigned int textureSize,
unsigned int textureDownScale,
const std::string & nbContrib,
const std::string & textureFormat,
const std::map<int, std::map<int, cv::Vec4d> > & gains,
const std::map<int, std::map<int, cv::Mat> > & blendingGains,
const std::pair<float, float> & contrastValues,
bool gainRGB,
unsigned int unwrapMethod,
bool fillHoles,
unsigned int padding,
double bestScoreThreshold,
double angleHardThreshold,
bool forceVisibleByAllVertices)
{
#ifdef RTABMAP_ALICE_VISION
if(ULogger::level() == ULogger::kDebug)
{
system::Logger::get()->setLogLevel(system::EVerboseLevel::Trace);
}
else if(ULogger::level() == ULogger::kInfo)
{
system::Logger::get()->setLogLevel(system::EVerboseLevel::Info);
}
else if(ULogger::level() == ULogger::kWarning)
{
system::Logger::get()->setLogLevel(system::EVerboseLevel::Warning);
}
else
{
system::Logger::get()->setLogLevel(system::EVerboseLevel::Error);
}
sfmData::SfMData sfmData;
pcl::PointCloud<pcl::PointXYZRGB> cloud2;
pcl::fromPCLPointCloud2(cloud, cloud2);
UASSERT(vertexToPixels.size() == cloud2.size());
UINFO("Input mesh: %d points %d polygons", (int)cloud2.size(), (int)polygons.size());
mesh::Texturing texturing;
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=3)
texturing.mesh = new mesh::Mesh();
texturing.mesh->pts.resize(cloud2.size());
texturing.mesh->pointsVisibilities.resize(cloud2.size());
#else
texturing.me = new mesh::Mesh();
texturing.me->pts = new StaticVector<Point3d>(cloud2.size());
texturing.pointsVisibilities = new mesh::PointsVisibility();
texturing.pointsVisibilities->reserve(cloud2.size());
#endif
texturing.texParams.textureSide = textureSize;
texturing.texParams.downscale = textureDownScale;
std::vector<int> multiBandNbContrib;
std::list<std::string> values = uSplit(nbContrib, ' ');
for(std::list<std::string>::iterator iter=values.begin(); iter!=values.end(); ++iter)
{
multiBandNbContrib.push_back(uStr2Int(*iter));
}
if(multiBandNbContrib.size() != 4)
{
UERROR("multiband: Wrong number of nb of contribution (vaue=\"%s\", should be 4), using default values instead.", nbContrib.c_str());
}
else
{
texturing.texParams.multiBandNbContrib = multiBandNbContrib;
}
texturing.texParams.padding = padding;
texturing.texParams.fillHoles = fillHoles;
texturing.texParams.bestScoreThreshold = bestScoreThreshold;
texturing.texParams.angleHardThreshold = angleHardThreshold;
texturing.texParams.forceVisibleByAllVertices = forceVisibleByAllVertices;
texturing.texParams.visibilityRemappingMethod = mesh::EVisibilityRemappingMethod::Pull;
for(size_t i=0;i<cloud2.size();++i)
{
pcl::PointXYZRGB pt = cloud2.at(i);
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=3)
texturing.mesh->pointsVisibilities[i].reserve(vertexToPixels[i].size());
for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[i].begin(); iter!=vertexToPixels[i].end();++iter)
{
texturing.mesh->pointsVisibilities[i].push_back(iter->first);
}
texturing.mesh->pts[i] = Point3d(pt.x, pt.y, pt.z);
#else
mesh::PointVisibility* pointVisibility = new mesh::PointVisibility();
pointVisibility->reserve(vertexToPixels[i].size());
for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[i].begin(); iter!=vertexToPixels[i].end();++iter)
{
pointVisibility->push_back(iter->first);
}
texturing.pointsVisibilities->push_back(pointVisibility);
(*texturing.me->pts)[i] = Point3d(pt.x, pt.y, pt.z);
#endif
}
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=3)
texturing.mesh->tris.resize(polygons.size());
texturing.mesh->trisMtlIds().resize(polygons.size());
#else
texturing.me->tris = new StaticVector<mesh::Mesh::triangle>(polygons.size());
#endif
for(size_t i=0;i<polygons.size();++i)
{
UASSERT(polygons[i].vertices.size() == 3);
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=3)
texturing.mesh->trisMtlIds()[i] = -1;
texturing.mesh->tris[i] = mesh::Mesh::triangle(
#else
(*texturing.me->tris)[i] = mesh::Mesh::triangle(
#endif
polygons[i].vertices[0],
polygons[i].vertices[1],
polygons[i].vertices[2]);
}
UTimer timer;
std::string outputDirectory = UDirectory::getDir(outputOBJPath);
std::string tmpImageDirectory = outputDirectory+"/rtabmap_tmp_textures";
UDirectory::removeDir(tmpImageDirectory);
UDirectory::makeDir(tmpImageDirectory);
UINFO("Temporary saving images from %ld nodes in directory \"%s\"...", cameraPoses.size(), tmpImageDirectory.c_str());
int viewId = 0;
for(std::map<int, Transform>::const_iterator iter = cameraPoses.lower_bound(1); iter!=cameraPoses.end(); ++iter)
{
int camId = iter->first;
cv::Mat image;
std::vector<CameraModel> models;
if( images.find(camId) != images.end() &&
!images.find(camId)->second.empty() &&
cameraModels.find(camId) != cameraModels.end())
{
image = images.find(camId)->second;
models = cameraModels.find(camId)->second;
}
else if(memory)
{
SensorData data = memory->getNodeData(camId, true, false, false, false);
models = data.cameraModels();
if(models.empty() && data.stereoCameraModels().size())
{
for(size_t i=0; i<data.stereoCameraModels().size(); ++i)
{
models.push_back(data.stereoCameraModels()[i].left());
}
}
if(data.imageRaw().empty())
{
image = data.imageCompressed();
}
else
{
image = data.imageRaw();
}
if(models.empty() || image.empty())
{
Transform odomPose;
int mapId;
int weight=0;
std::string label;
double stamp;
Transform gt;
std::vector<float> vel;
GPS gps;
EnvSensors envs;
memory->getNodeInfo(camId, odomPose, mapId, weight, label, stamp, gt, vel, gps, envs, true);
if(weight == -1) // just ignore intermediate nodes if their data is not set
continue;
}
}
else if(dbDriver)
{
std::vector<StereoCameraModel> stereoModels;
dbDriver->getCalibration(camId, models, stereoModels);
if(models.empty() && stereoModels.size())
{
for(size_t i=0; i<stereoModels.size(); ++i)
{
models.push_back(stereoModels[i].left());
}
}
SensorData data;
dbDriver->getNodeData(camId, data, true, false, false, false);
if(data.imageRaw().empty())
{
image = data.imageCompressed();
}
else
{
image = data.imageRaw();
}
if(models.empty() || image.empty())
{
int weight=0;
dbDriver->getWeight(camId, weight);
if(weight == -1) // just ignore intermediate nodes if their data is not set
continue;
}
}
if(models.empty())
{
UERROR("No camera models found for camera %d. Aborting multiband texturing...", iter->first);
return false;
}
if(image.empty())
{
UERROR("No image found for camera %d. Aborting multiband texturing...", iter->first);
return false;
}
if(image.rows == 1 && image.type() == CV_8UC1)
{
image = uncompressImage(image);
}
else
{
image = image.clone();
}
for(size_t i=0; i<models.size(); ++i)
{
const CameraModel & model = models.at(i);
cv::Size imageSize = model.imageSize();
if(imageSize.height == 0)
{
// backward compatibility
imageSize.height = image.rows;
imageSize.width = image.cols;
}
UASSERT(image.cols % imageSize.width == 0);
cv::Mat imageRoi = image.colRange(i*imageSize.width, (i+1)*imageSize.width);
if(gains.find(camId) != gains.end() &&
gains.at(camId).find(i) != gains.at(camId).end())
{
const cv::Vec4d & g = gains.at(camId).at(i);
if(imageRoi.channels() == 1)
{
cv::multiply(imageRoi, g.val[0], imageRoi);
}
else
{
std::vector<cv::Mat> channels;
cv::split(imageRoi, channels);
// assuming BGR
cv::multiply(channels[0], g.val[gainRGB?3:0], channels[0]);
cv::multiply(channels[1], g.val[gainRGB?2:0], channels[1]);
cv::multiply(channels[2], g.val[gainRGB?1:0], channels[2]);
cv::Mat output;
cv::merge(channels, output);
imageRoi = output;
}
}
if(blendingGains.find(camId) != blendingGains.end() &&
blendingGains.at(camId).find(i) != blendingGains.at(camId).end())
{
// Should be color for blending options
if(imageRoi.channels() == 1)
{
cv::Mat imageRoiColor;
cv::cvtColor(imageRoi, imageRoiColor, CV_GRAY2BGR);
imageRoi = imageRoiColor;
}
cv::Mat g = blendingGains.at(camId).at(i);
cv::Mat dst;
cv::blur(g, dst, cv::Size(3,3));
cv::Mat gResized;
cv::resize(dst, gResized, imageRoi.size(), 0, 0, cv::INTER_LINEAR);
cv::Mat output;
cv::multiply(imageRoi, gResized, output, 1.0, CV_8UC3);
imageRoi = output;
}
Transform t = (iter->second * model.localTransform()).inverse();
Eigen::Matrix<double, 3, 4> m = t.toEigen3d().matrix().block<3,4>(0, 0);
sfmData::CameraPose pose(geometry::Pose3(m), true);
sfmData.setAbsolutePose((IndexT)viewId, pose);
UDEBUG("%d %d %f %f %f %f", imageSize.width, imageSize.height, model.fx(), model.fy(), model.cx(), model.cy());
std::shared_ptr<camera::IntrinsicBase> camPtr = std::make_shared<camera::Pinhole>(
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=4)
//https://github.com/alicevision/AliceVision/commit/9fab5c79a1c65595fe5c5001267e1c5212bc93f0#diff-b0c0a3c30de50be8e4ed283dfe4c8ae4a9bc861aa9a83bd8bfda8182e9d67c08
// [all] the camera principal point is now defined as an offset relative to the image center
imageSize.width, imageSize.height, model.fx(), model.fy(), model.cx() - double(imageSize.width) * 0.5, model.cy() - double(imageSize.height) * 0.5);
#else
imageSize.width, imageSize.height, model.fx(), model.cx(), model.cy());
#endif
sfmData.intrinsics.insert(std::make_pair((IndexT)viewId, camPtr));
std::string imagePath = tmpImageDirectory+uFormat("/%d.jpg", viewId);
cv::imwrite(imagePath, imageRoi);
std::shared_ptr<sfmData::View> viewPtr = std::make_shared<sfmData::View>(
imagePath,
(IndexT)viewId,
(IndexT)viewId,
(IndexT)viewId,
imageSize.width,
imageSize.height);
sfmData.views.insert(std::make_pair((IndexT)viewId, viewPtr));
++viewId;
}
UDEBUG("camId=%d", camId);
}
UINFO("Temporary saving images in directory \"%s\"... done (%d images of %d nodes). %fs", tmpImageDirectory.c_str(), viewId, (int)cameraPoses.size(), timer.ticks());
mvsUtils::MultiViewParams mp(sfmData);
UINFO("Unwrapping (method=%d=%s)...", unwrapMethod, mesh::EUnwrapMethod_enumToString((mesh::EUnwrapMethod)unwrapMethod).c_str());
texturing.unwrap(mp, (mesh::EUnwrapMethod)unwrapMethod);
UINFO("Unwrapping done. %fs", timer.ticks());
// save final obj file
std::string baseName = uSplit(UFile::getName(outputOBJPath), '.').front();
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=4)
texturing.saveAs(outputDirectory, baseName, aliceVision::mesh::EFileType::OBJ, imageIO::EImageFileType::PNG);
#else
texturing.saveAsOBJ(outputDirectory, baseName);
#endif
UINFO("Saved %s. %fs", outputOBJPath.c_str(), timer.ticks());
// generate textures
UINFO("Generating textures...");
texturing.generateTextures(mp, outputDirectory);
UINFO("Generating textures done. %fs", timer.ticks());
UINFO("Cleanup temporary directory \"%s\"...", tmpImageDirectory.c_str());
UDirectory dir(tmpImageDirectory);
std::string fp = dir.getNextFilePath();
while(!fp.empty())
{
UFile::erase(fp);
fp = dir.getNextFilePath();
}
UDirectory::removeDir(tmpImageDirectory);
UINFO("Cleanup temporary directory \"%s\"... done.", tmpImageDirectory.c_str());
UINFO("Rename/convert textures...");
dir.setPath(outputDirectory, "png");
std::map<std::string, std::string> texNames; // <old, new>
std::string outputFormat = textureFormat;
if(outputFormat.front() == '.')
{
outputFormat = outputFormat.substr(1, std::string::npos);
}
for(std::list<std::string>::const_iterator iter=dir.getFileNames().begin(); iter!=dir.getFileNames().end(); ++iter)
{
// Textures are called "texture_1001.png", "texture_1002.png", ...
if(uStrContains(*iter, "texture_10"))
{
cv::Mat img = cv::imread(outputDirectory+"/"+*iter);
if(contrastValues.first != 0.0f || contrastValues.second != 0.0f)
{
UASSERT(img.channels() == 3);
// Re-use same contrast values with all images
UINFO("Apply contrast values %f %f", contrastValues.first, contrastValues.second);
img.convertTo(img, -1, contrastValues.first, contrastValues.second);
}
std::string newName = *iter;
boost::replace_all(newName, "png", outputFormat);
boost::replace_all(newName, "texture", baseName);
texNames.insert(std::make_pair(*iter, newName));
cv::imwrite(outputDirectory+"/"+newName, img);
UFile::erase(outputDirectory+"/"+*iter);
}
}
std::ifstream fi(outputDirectory+"/"+baseName+".mtl");
std::string mtlStr((std::istreambuf_iterator<char>(fi)),
std::istreambuf_iterator<char>());
fi.close();
UFile::erase(outputDirectory+"/"+baseName);
for(std::map<std::string, std::string>::iterator iter=texNames.begin(); iter!=texNames.end(); ++iter)
{
boost::replace_all(mtlStr, iter->first, iter->second);
}
std::ofstream fo(outputDirectory+"/"+baseName+".mtl");
fo.write(mtlStr.c_str(), mtlStr.size());
fo.close();
UINFO("Rename/convert textures... done. %fs", timer.ticks());
#if RTABMAP_ALICE_VISION_MAJOR > 2 || (RTABMAP_ALICE_VISION_MAJOR==2 && RTABMAP_ALICE_VISION_MINOR>=3)
UINFO("Cleanup sfmdata...");
sfmData.clear();
UINFO("Cleanup sfmdata... done. %fs", timer.ticks());
#endif
return true;
#else
UERROR("Cannot unwrap texture mesh. RTAB-Map is not built with Alice Vision support! Returning false.");
return false;
#endif
}
LaserScan computeNormals(
const LaserScan & laserScan,
int searchK,
float searchRadius)
{
if(laserScan.isEmpty())
{
return laserScan;
}
pcl::PointCloud<pcl::Normal>::Ptr normals;
// convert to compatible PCL format and filter it
if(laserScan.hasRGB())
{
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = laserScanToPointCloudRGB(laserScan);
if(cloud->size())
{
UASSERT(!laserScan.is2d());
pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.rangeMax(), laserScan.localTransform());
}
}
else if(laserScan.hasIntensity())
{
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud = laserScanToPointCloudI(laserScan);
if(cloud->size())
{
if(laserScan.is2d())
{
pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals2D(cloud, searchK, searchRadius);
if(laserScan.angleIncrement() > 0.0f)
{
return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.rangeMin(), laserScan.rangeMax(), laserScan.angleMin(), laserScan.angleMax(), laserScan.angleIncrement(), laserScan.localTransform());
}
else
{
return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.rangeMax(), laserScan.localTransform());
}
}
else
{
pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.rangeMax(), laserScan.localTransform());
}
}
}
else
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = laserScanToPointCloud(laserScan);
if(cloud->size())
{
if(laserScan.is2d())
{
pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals2D(cloud, searchK, searchRadius);
if(laserScan.angleIncrement() > 0.0f)
{
return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.rangeMin(), laserScan.rangeMax(), laserScan.angleMin(), laserScan.angleMax(), laserScan.angleIncrement(), laserScan.localTransform());
}
else
{
return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.rangeMax(), laserScan.localTransform());
}
}
else
{
pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.rangeMax(), laserScan.localTransform());
}
}
}
return LaserScan();
}
template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeNormalsImpl(
const typename pcl::PointCloud<PointT>::Ptr & cloud,
const pcl::IndicesPtr & indices,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
typename pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT>);
if(indices->size())
{
tree->setInputCloud(cloud, indices);
}
else
{
tree->setInputCloud (cloud);
}
// Normal estimation*
#ifdef PCL_OMP
pcl::NormalEstimationOMP<PointT, pcl::Normal> n;
#else
pcl::NormalEstimation<PointT, pcl::Normal> n;
#endif
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
n.setInputCloud (cloud);
// Commented: Keep the output normals size the same as the input cloud
//if(indices->size())
//{
// n.setIndices(indices);
//}
n.setSearchMethod (tree);
n.setKSearch (searchK);
n.setRadiusSearch (searchRadius);
n.setViewPoint(viewPoint[0], viewPoint[1], viewPoint[2]);
n.compute (*normals);
return normals;
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
pcl::IndicesPtr indices(new std::vector<int>);
return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
pcl::IndicesPtr indices(new std::vector<int>);
return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
pcl::IndicesPtr indices(new std::vector<int>);
return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
const pcl::IndicesPtr & indices,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeNormalsImpl<pcl::PointXYZ>(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
const pcl::IndicesPtr & indices,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeNormalsImpl<pcl::PointXYZRGB>(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
const pcl::IndicesPtr & indices,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeNormalsImpl<pcl::PointXYZI>(cloud, indices, searchK, searchRadius, viewPoint);
}
template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2DImpl(
const typename pcl::PointCloud<PointT>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
UASSERT(searchK>0 || searchRadius>0.0f);
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
typename pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT>);
tree->setInputCloud (cloud);
normals->resize(cloud->size());
float bad_point = std::numeric_limits<float>::quiet_NaN ();
// assuming that points are ordered
for(unsigned int i=0; i<cloud->size(); ++i)
{
const PointT & pt = cloud->at(i);
std::vector<Eigen::Vector3f> neighborNormals;
Eigen::Vector3f direction;
direction[0] = viewPoint[0] - pt.x;
direction[1] = viewPoint[1] - pt.y;
direction[2] = viewPoint[2] - pt.z;
std::vector<int> k_indices;
std::vector<float> k_sqr_distances;
if(searchRadius>0.0f)
{
tree->radiusSearch(cloud->at(i), searchRadius, k_indices, k_sqr_distances, searchK);
}
else
{
tree->nearestKSearch(cloud->at(i), searchK, k_indices, k_sqr_distances);
}
for(unsigned int j=0; j<k_indices.size(); ++j)
{
if(k_indices.at(j) != (int)i)
{
const PointT & pt2 = cloud->at(k_indices.at(j));
Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
Eigen::Vector3f up = v.cross(direction);
Eigen::Vector3f n = up.cross(v);
n.normalize();
neighborNormals.push_back(n);
}
}
if(neighborNormals.empty())
{
normals->at(i).normal_x = bad_point;
normals->at(i).normal_y = bad_point;
normals->at(i).normal_z = bad_point;
}
else
{
Eigen::Vector3f meanNormal(0,0,0);
for(unsigned int j=0; j<neighborNormals.size(); ++j)
{
meanNormal+=neighborNormals[j];
}
meanNormal /= (float)neighborNormals.size();
meanNormal.normalize();
normals->at(i).normal_x = meanNormal[0];
normals->at(i).normal_y = meanNormal[1];
normals->at(i).normal_z = meanNormal[2];
}
}
return normals;
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2D(
const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeNormals2DImpl<pcl::PointXYZ>(cloud, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2D(
const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeNormals2DImpl<pcl::PointXYZI>(cloud, searchK, searchRadius, viewPoint);
}
template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2DImpl(
const typename pcl::PointCloud<PointT>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
UASSERT(searchK>0);
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
normals->resize(cloud->size());
searchRadius *= searchRadius; // squared distance
float bad_point = std::numeric_limits<float>::quiet_NaN ();
// assuming that points are ordered
for(int i=0; i<(int)cloud->size(); ++i)
{
int li = i-searchK;
if(li<0)
{
li=0;
}
int hi = i+searchK;
if(hi>=(int)cloud->size())
{
hi=(int)cloud->size()-1;
}
// get points before not too far
const PointT & pt = cloud->at(i);
std::vector<Eigen::Vector3f> neighborNormals;
Eigen::Vector3f direction;
direction[0] = viewPoint[0] - cloud->at(i).x;
direction[1] = viewPoint[1] - cloud->at(i).y;
direction[2] = viewPoint[2] - cloud->at(i).z;
for(int j=i-1; j>=li; --j)
{
const PointT & pt2 = cloud->at(j);
Eigen::Vector3f vd(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
if(searchRadius<=0.0f || (vd[0]*vd[0] + vd[1]*vd[1] + vd[2]*vd[2]) < searchRadius)
{
Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
Eigen::Vector3f up = v.cross(direction);
Eigen::Vector3f n = up.cross(v);
n.normalize();
neighborNormals.push_back(n);
}
else
{
break;
}
}
for(int j=i+1; j<=hi; ++j)
{
const PointT & pt2 = cloud->at(j);
Eigen::Vector3f vd(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
if(searchRadius<=0.0f || (vd[0]*vd[0] + vd[1]*vd[1] + vd[2]*vd[2]) < searchRadius)
{
Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
Eigen::Vector3f up = v[2]==0.0f?Eigen::Vector3f(0,0,1):v.cross(direction);
Eigen::Vector3f n = up.cross(v);
n.normalize();
neighborNormals.push_back(n);
}
else
{
break;
}
}
if(neighborNormals.empty())
{
normals->at(i).normal_x = bad_point;
normals->at(i).normal_y = bad_point;
normals->at(i).normal_z = bad_point;
}
else
{
Eigen::Vector3f meanNormal(0,0,0);
for(unsigned int j=0; j<neighborNormals.size(); ++j)
{
meanNormal+=neighborNormals[j];
}
meanNormal /= (float)neighborNormals.size();
meanNormal.normalize();
normals->at(i).normal_x = meanNormal[0];
normals->at(i).normal_y = meanNormal[1];
normals->at(i).normal_z = meanNormal[2];
}
}
return normals;
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2D(
const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeFastOrganizedNormals2DImpl<pcl::PointXYZ>(cloud, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2D(
const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
int searchK,
float searchRadius,
const Eigen::Vector3f & viewPoint)
{
return computeFastOrganizedNormals2DImpl<pcl::PointXYZI>(cloud, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
float maxDepthChangeFactor,
float normalSmoothingSize,
const Eigen::Vector3f & viewPoint)
{
pcl::IndicesPtr indices(new std::vector<int>);
return computeFastOrganizedNormals(cloud, indices, maxDepthChangeFactor, normalSmoothingSize, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
const pcl::IndicesPtr & indices,
float maxDepthChangeFactor,
float normalSmoothingSize,
const Eigen::Vector3f & viewPoint)
{
UASSERT(cloud->isOrganized());
pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB>);
if(indices->size())
{
tree->setInputCloud(cloud, indices);
}
else
{
tree->setInputCloud (cloud);
}
// Normal estimation
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
pcl::IntegralImageNormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;
ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT);
ne.setMaxDepthChangeFactor(maxDepthChangeFactor);
ne.setNormalSmoothingSize(normalSmoothingSize);
ne.setBorderPolicy(ne.BORDER_POLICY_MIRROR);
ne.setInputCloud(cloud);
// Commented: Keep the output normals size the same as the input cloud
//if(indices->size())
//{
// ne.setIndices(indices);
//}
ne.setSearchMethod(tree);
ne.setViewPoint(viewPoint[0], viewPoint[1], viewPoint[2]);
ne.compute(*normals);
return normals;
}
float computeNormalsComplexity(
const LaserScan & scan,
const Transform & t,
cv::Mat * pcaEigenVectors,
cv::Mat * pcaEigenValues)
{
if(!scan.isEmpty() && (scan.hasNormals()))
{
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(scan.size()*2);
bool is2d = scan.is2d();
cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
int oi = 0;
int nOffset = scan.getNormalsOffset();
bool doTransform = false;
Transform tn;
if(!t.isIdentity() || !scan.localTransform().isIdentity())
{
tn = (t*scan.localTransform()).rotation();
doTransform = true;
}
for (int i = 0; i < scan.size(); ++i)
{
const float * ptrScan = scan.data().ptr<float>(0, i);
if(is2d)
{
if(uIsFinite(ptrScan[nOffset]) && uIsFinite(ptrScan[nOffset+1]))
{
cv::Point3f n(ptrScan[nOffset], ptrScan[nOffset+1], 0);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
}
}
else
{
if(uIsFinite(ptrScan[nOffset]) && uIsFinite(ptrScan[nOffset+1]) && uIsFinite(ptrScan[nOffset+2]))
{
cv::Point3f n(ptrScan[nOffset], ptrScan[nOffset+1], ptrScan[nOffset+2]);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
ptr[2] = n.z;
}
}
}
if(oi>1)
{
cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);
if(pcaEigenVectors)
{
*pcaEigenVectors = pca_analysis.eigenvectors;
}
if(pcaEigenValues)
{
*pcaEigenValues = pca_analysis.eigenvalues;
}
UASSERT((is2d && pca_analysis.eigenvalues.total()>=2) || (!is2d && pca_analysis.eigenvalues.total()>=3));
// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
}
}
else if(!scan.isEmpty())
{
UERROR("Scan doesn't have normals!");
}
return 0.0f;
}
float computeNormalsComplexity(
const pcl::PointCloud<pcl::PointNormal> & cloud,
const Transform & t,
bool is2d,
cv::Mat * pcaEigenVectors,
cv::Mat * pcaEigenValues)
{
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(cloud.size()*2);
cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
int oi = 0;
bool doTransform = false;
Transform tn;
if(!t.isIdentity() && !t.isNull())
{
tn = t.rotation();
doTransform = true;
}
for (unsigned int i = 0; i < cloud.size(); ++i)
{
const pcl::PointNormal & pt = cloud.at(i);
cv::Point3f n(pt.normal_x, pt.normal_y, pt.normal_z);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
{
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
if(!is2d)
{
ptr[2] = n.z;
}
}
}
if(oi>1)
{
cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);
if(pcaEigenVectors)
{
*pcaEigenVectors = pca_analysis.eigenvectors;
}
if(pcaEigenValues)
{
*pcaEigenValues = pca_analysis.eigenvalues;
}
// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
}
return 0.0f;
}
float computeNormalsComplexity(
const pcl::PointCloud<pcl::Normal> & normals,
const Transform & t,
bool is2d,
cv::Mat * pcaEigenVectors,
cv::Mat * pcaEigenValues)
{
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(normals.size()*2);
cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
int oi = 0;
bool doTransform = false;
Transform tn;
if(!t.isIdentity())
{
tn = t.rotation();
doTransform = true;
}
for (unsigned int i = 0; i < normals.size(); ++i)
{
const pcl::Normal & pt = normals.at(i);
cv::Point3f n(pt.normal_x, pt.normal_y, pt.normal_z);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
{
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
if(!is2d)
{
ptr[2] = n.z;
}
}
}
if(oi>1)
{
cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);
if(pcaEigenVectors)
{
*pcaEigenVectors = pca_analysis.eigenvectors;
}
if(pcaEigenValues)
{
*pcaEigenValues = pca_analysis.eigenvalues;
}
// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
}
return 0.0f;
}
float computeNormalsComplexity(
const pcl::PointCloud<pcl::PointXYZINormal> & cloud,
const Transform & t,
bool is2d,
cv::Mat * pcaEigenVectors,
cv::Mat * pcaEigenValues)
{
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(cloud.size()*2);
cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
int oi = 0;
bool doTransform = false;
Transform tn;
if(!t.isIdentity())
{
tn = t.rotation();
doTransform = true;
}
for (unsigned int i = 0; i < cloud.size(); ++i)
{
const pcl::PointXYZINormal & pt = cloud.at(i);
cv::Point3f n(pt.normal_x, pt.normal_y, pt.normal_z);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
{
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
if(!is2d)
{
ptr[2] = n.z;
}
}
}
if(oi>1)
{
cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);
if(pcaEigenVectors)
{
*pcaEigenVectors = pca_analysis.eigenvectors;
}
if(pcaEigenValues)
{
*pcaEigenValues = pca_analysis.eigenvalues;
}
// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
}
return 0.0f;
}
float computeNormalsComplexity(
const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud,
const Transform & t,
bool is2d,
cv::Mat * pcaEigenVectors,
cv::Mat * pcaEigenValues)
{
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(cloud.size()*2);
cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
int oi = 0;
bool doTransform = false;
Transform tn;
if(!t.isIdentity())
{
tn = t.rotation();
doTransform = true;
}
for (unsigned int i = 0; i < cloud.size(); ++i)
{
const pcl::PointXYZRGBNormal & pt = cloud.at(i);
cv::Point3f n(pt.normal_x, pt.normal_y, pt.normal_z);
if(doTransform)
{
n = util3d::transformPoint(n, tn);
}
if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
{
float * ptr = data_normals.ptr<float>(oi++, 0);
ptr[0] = n.x;
ptr[1] = n.y;
if(!is2d)
{
ptr[2] = n.z;
}
}
}
if(oi>1)
{
cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);
if(pcaEigenVectors)
{
*pcaEigenVectors = pca_analysis.eigenvectors;
}
if(pcaEigenValues)
{
*pcaEigenValues = pca_analysis.eigenvalues;
}
// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
}
return 0.0f;
}
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mls(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
float searchRadius,
int polygonialOrder,
int upsamplingMethod, // NONE, DISTINCT_CLOUD, SAMPLE_LOCAL_PLANE, RANDOM_UNIFORM_DENSITY, VOXEL_GRID_DILATION
float upsamplingRadius, // SAMPLE_LOCAL_PLANE
float upsamplingStep, // SAMPLE_LOCAL_PLANE
int pointDensity, // RANDOM_UNIFORM_DENSITY
float dilationVoxelSize, // VOXEL_GRID_DILATION
int dilationIterations) // VOXEL_GRID_DILATION
{
pcl::IndicesPtr indices(new std::vector<int>);
return mls(cloud,
indices,
searchRadius,
polygonialOrder,
upsamplingMethod,
upsamplingRadius,
upsamplingStep,
pointDensity,
dilationVoxelSize,
dilationIterations);
}
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mls(
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
const pcl::IndicesPtr & indices,
float searchRadius,
int polygonialOrder,
int upsamplingMethod, // NONE, DISTINCT_CLOUD, SAMPLE_LOCAL_PLANE, RANDOM_UNIFORM_DENSITY, VOXEL_GRID_DILATION
float upsamplingRadius, // SAMPLE_LOCAL_PLANE
float upsamplingStep, // SAMPLE_LOCAL_PLANE
int pointDensity, // RANDOM_UNIFORM_DENSITY
float dilationVoxelSize, // VOXEL_GRID_DILATION
int dilationIterations) // VOXEL_GRID_DILATION
{
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB>);
if(indices->size())
{
tree->setInputCloud (cloud, indices);
}
else
{
tree->setInputCloud (cloud);
}
// Init object (second point type is for the normals)
pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGBNormal> mls;
// Set parameters
mls.setComputeNormals (true);
if(polygonialOrder > 0)
{
#if PCL_VERSION_COMPARE(<, 1, 10, 0)
mls.setPolynomialFit (true);
#endif
mls.setPolynomialOrder(polygonialOrder);
}
else
{
#if PCL_VERSION_COMPARE(<, 1, 10, 0)
mls.setPolynomialFit (false);
#else
mls.setPolynomialOrder(1);
#endif
}
UASSERT(upsamplingMethod >= mls.NONE &&
upsamplingMethod <= mls.VOXEL_GRID_DILATION);
mls.setUpsamplingMethod((pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGBNormal>::UpsamplingMethod)upsamplingMethod);
mls.setSearchRadius(searchRadius);
mls.setUpsamplingRadius(upsamplingRadius);
mls.setUpsamplingStepSize(upsamplingStep);
mls.setPointDensity(pointDensity);
mls.setDilationVoxelSize(dilationVoxelSize);
mls.setDilationIterations(dilationIterations);
// Reconstruct
mls.setInputCloud (cloud);
if(indices->size())
{
mls.setIndices(indices);
}
mls.setSearchMethod (tree);
mls.process (*cloud_with_normals);
// It seems that returned normals are not normalized!? FIXME: Is it a bug only in PCL 1.7.1?
for(unsigned int i=0; i<cloud_with_normals->size(); ++i)
{
Eigen::Vector3f normal(cloud_with_normals->at(i).normal_x, cloud_with_normals->at(i).normal_y, cloud_with_normals->at(i).normal_z);
normal.normalize();
cloud_with_normals->at(i).normal_x = normal[0];
cloud_with_normals->at(i).normal_y = normal[1];
cloud_with_normals->at(i).normal_z = normal[2];
}
return cloud_with_normals;
}
LaserScan adjustNormalsToViewPoint(
const LaserScan & scan,
const Eigen::Vector3f & viewpoint,
bool forceGroundNormalsUp)
{
return adjustNormalsToViewPoint(scan, viewpoint, forceGroundNormalsUp?0.8f:0.0f);
}
LaserScan adjustNormalsToViewPoint(
const LaserScan & scan,
const Eigen::Vector3f & viewpoint,
float groundNormalsUp)
{
if(scan.size() && !scan.is2d() && scan.hasNormals())
{
int nx = scan.getNormalsOffset();
int ny = nx+1;
int nz = ny+1;
cv::Mat output = scan.data().clone();
#pragma omp parallel for
for(int j=0; j<scan.data().rows; ++j)
{
for(int i=0; i<scan.data().cols; ++i)
{
float * ptr = output.ptr<float>(j, i);
if(uIsFinite(ptr[nx]) && uIsFinite(ptr[ny]) && uIsFinite(ptr[nz]))
{
Eigen::Vector3f v = viewpoint - Eigen::Vector3f(ptr[0], ptr[1], ptr[2]);
Eigen::Vector3f n(ptr[nx], ptr[ny], ptr[nz]);
float result = v.dot(n);
if(result < 0
|| (groundNormalsUp>0.0f && ptr[nz] < -groundNormalsUp && ptr[2] < viewpoint[2])) // some far velodyne rays on road can have normals toward ground
{
//reverse normal
ptr[nx] *= -1.0f;
ptr[ny] *= -1.0f;
ptr[nz] *= -1.0f;
}
}
}
}
if(scan.angleIncrement() > 0.0f)
{
return LaserScan(output, scan.format(), scan.rangeMin(), scan.rangeMax(), scan.angleMin(), scan.angleMax(), scan.angleIncrement(), scan.localTransform());
}
else
{
return LaserScan(output, scan.maxPoints(), scan.rangeMax(), scan.format(), scan.localTransform());
}
}
return scan;
}
template<typename PointNormalT>
void adjustNormalsToViewPointImpl(
typename pcl::PointCloud<PointNormalT>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
float groundNormalsUp)
{
#pragma omp parallel for
for(int i=0; i<(int)cloud->size(); ++i)
{
pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
if(pcl::isFinite(normal))
{
Eigen::Vector3f v = viewpoint - cloud->points[i].getVector3fMap();
Eigen::Vector3f n(normal.x, normal.y, normal.z);
float result = v.dot(n);
if(result < 0
|| (groundNormalsUp>0.0f && normal.z < -groundNormalsUp && cloud->points[i].z < viewpoint[2])) // some far velodyne rays on road can have normals toward ground
{
//reverse normal
cloud->points[i].normal_x *= -1.0f;
cloud->points[i].normal_y *= -1.0f;
cloud->points[i].normal_z *= -1.0f;
}
}
}
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointNormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
bool forceGroundNormalsUp)
{
adjustNormalsToViewPoint(cloud, viewpoint, forceGroundNormalsUp?0.8f:0.0f);
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointNormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
float groundNormalsUp)
{
adjustNormalsToViewPointImpl<pcl::PointNormal>(cloud, viewpoint, groundNormalsUp);
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
bool forceGroundNormalsUp)
{
adjustNormalsToViewPoint(cloud, viewpoint, forceGroundNormalsUp?0.8f:0.0f);
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
float groundNormalsUp)
{
adjustNormalsToViewPointImpl<pcl::PointXYZRGBNormal>(cloud, viewpoint, groundNormalsUp);
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointXYZINormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
bool forceGroundNormalsUp)
{
adjustNormalsToViewPoint(cloud, viewpoint, forceGroundNormalsUp?0.8f:0.0f);
}
void adjustNormalsToViewPoint(
pcl::PointCloud<pcl::PointXYZINormal>::Ptr & cloud,
const Eigen::Vector3f & viewpoint,
float groundNormalsUp)
{
adjustNormalsToViewPointImpl<pcl::PointXYZINormal>(cloud, viewpoint, groundNormalsUp);
}
template<typename PointT>
void adjustNormalsToViewPointsImpl(
const std::map<int, Transform> & poses,
const std::vector<int> & cameraIndices,
typename pcl::PointCloud<PointT>::Ptr & cloud,
float groundNormalsUp)
{
if(poses.size() && cloud->size() == cameraIndices.size() && cloud->size())
{
#pragma omp parallel for
for(int i=0; i<(int)cloud->size(); ++i)
{
pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
if(pcl::isFinite(normal))
{
const Transform & p = poses.at(cameraIndices[i]);
pcl::PointXYZ viewpoint(p.x(), p.y(), p.z());
Eigen::Vector3f v = viewpoint.getVector3fMap() - cloud->points[i].getVector3fMap();
Eigen::Vector3f n(normal.x, normal.y, normal.z);
float result = v.dot(n);
if(result < 0 ||
(groundNormalsUp>0.0f && normal.z < -groundNormalsUp && cloud->points[i].z < viewpoint.z)) // some far velodyne rays on road can have normals toward ground)
{
//reverse normal
cloud->points[i].normal_x *= -1.0f;
cloud->points[i].normal_y *= -1.0f;
cloud->points[i].normal_z *= -1.0f;
}
}
}
}
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const std::vector<int> & cameraIndices,
pcl::PointCloud<pcl::PointNormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointNormal>(poses, cameraIndices, cloud, groundNormalsUp);
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const std::vector<int> & cameraIndices,
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointXYZRGBNormal>(poses, cameraIndices, cloud, groundNormalsUp);
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const std::vector<int> & cameraIndices,
pcl::PointCloud<pcl::PointXYZINormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointXYZINormal>(poses, cameraIndices, cloud, groundNormalsUp);
}
template<typename PointT>
void adjustNormalsToViewPointsImpl(
const std::map<int, Transform> & poses,
const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
const std::vector<int> & rawCameraIndices,
typename pcl::PointCloud<PointT>::Ptr & cloud,
float groundNormalsUp)
{
if(poses.size() && rawCloud->size() && rawCloud->size() == rawCameraIndices.size() && cloud->size())
{
pcl::search::KdTree<pcl::PointXYZ>::Ptr rawTree (new pcl::search::KdTree<pcl::PointXYZ>);
rawTree->setInputCloud (rawCloud);
#pragma omp parallel for
for(int i=0; i<(int)cloud->size(); ++i)
{
pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
if(pcl::isFinite(normal))
{
std::vector<int> indices;
std::vector<float> dist;
rawTree->nearestKSearch(pcl::PointXYZ(cloud->points[i].x, cloud->points[i].y, cloud->points[i].z), 1, indices, dist);
UASSERT(indices.size() == 1);
if(indices.size() && indices[0]>=0)
{
const Transform & p = poses.at(rawCameraIndices[indices[0]]);
pcl::PointXYZ viewpoint(p.x(), p.y(), p.z());
Eigen::Vector3f v = viewpoint.getVector3fMap() - cloud->points[i].getVector3fMap();
Eigen::Vector3f n(normal.x, normal.y, normal.z);
float result = v.dot(n);
if(result < 0 ||
(groundNormalsUp>0.0f && normal.z < -groundNormalsUp && cloud->points[i].z < viewpoint.z)) // some far velodyne rays on road can have normals toward ground)
{
//reverse normal
cloud->points[i].normal_x *= -1.0f;
cloud->points[i].normal_y *= -1.0f;
cloud->points[i].normal_z *= -1.0f;
}
}
else
{
UWARN("Not found camera viewpoint for point %d", i);
}
}
}
}
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
const std::vector<int> & rawCameraIndices,
pcl::PointCloud<pcl::PointNormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointNormal>(poses, rawCloud, rawCameraIndices, cloud, groundNormalsUp);
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
const std::vector<int> & rawCameraIndices,
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointXYZRGBNormal>(poses, rawCloud, rawCameraIndices, cloud, groundNormalsUp);
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & poses,
const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
const std::vector<int> & rawCameraIndices,
pcl::PointCloud<pcl::PointXYZINormal>::Ptr & cloud,
float groundNormalsUp)
{
adjustNormalsToViewPointsImpl<pcl::PointXYZINormal>(poses, rawCloud, rawCameraIndices, cloud, groundNormalsUp);
}
void adjustNormalsToViewPoints(
const std::map<int, Transform> & viewpoints,
const LaserScan & rawScan,
const std::vector<int> & viewpointIds,
LaserScan & scan,
float groundNormalsUp)
{
UDEBUG("poses=%d, rawCloud=%d, rawCameraIndices=%d, cloud=%d", (int)viewpoints.size(), (int)rawScan.size(), (int)viewpointIds.size(), (int)scan.size());
if(viewpoints.size() && rawScan.size() && rawScan.size() == (int)viewpointIds.size() && scan.size() && scan.hasNormals())
{
pcl::PointCloud<pcl::PointXYZ>::Ptr rawCloud = util3d::laserScanToPointCloud(rawScan);
pcl::search::KdTree<pcl::PointXYZ>::Ptr rawTree (new pcl::search::KdTree<pcl::PointXYZ>);
rawTree->setInputCloud (rawCloud);
#pragma omp parallel for
for(int i=0; i<scan.size(); ++i)
{
pcl::PointNormal point = util3d::laserScanToPointNormal(scan, i);
pcl::PointXYZ normal(point.normal_x, point.normal_y, point.normal_z);
if(pcl::isFinite(normal))
{
std::vector<int> indices;
std::vector<float> dist;
rawTree->nearestKSearch(pcl::PointXYZ(point.x, point.y, point.z), 1, indices, dist);
if(indices.size() && indices[0]>=0)
{
UASSERT_MSG(indices[0]<(int)viewpointIds.size(), uFormat("indices[0]=%d rawCameraIndices.size()=%d", indices[0], (int)viewpointIds.size()).c_str());
UASSERT(uContains(viewpoints, viewpointIds[indices[0]]));
Transform p = viewpoints.at(viewpointIds[indices[0]]);
pcl::PointXYZ viewpoint(p.x(), p.y(), p.z());
Eigen::Vector3f v = viewpoint.getVector3fMap() - point.getVector3fMap();
Eigen::Vector3f n(normal.x, normal.y, normal.z);
float result = v.dot(n);
if(result < 0 ||
(groundNormalsUp>0.0f && normal.z < -groundNormalsUp && point.z < viewpoint.z)) // some far velodyne rays on road can have normals toward ground))
{
//reverse normal
scan.field(i, scan.getNormalsOffset()) *= -1.0f;
scan.field(i, scan.getNormalsOffset()+1) *= -1.0f;
scan.field(i, scan.getNormalsOffset()+2) *= -1.0f;
}
}
else
{
UWARN("Not found camera viewpoint for point %d!?", i);
}
}
}
}
}
pcl::PolygonMesh::Ptr meshDecimation(const pcl::PolygonMesh::Ptr & mesh, float factor)
{
pcl::PolygonMesh::Ptr output(new pcl::PolygonMesh);
#ifndef DISABLE_VTK
pcl::MeshQuadricDecimationVTK mqd;
mqd.setTargetReductionFactor(factor);
mqd.setInputMesh(mesh);
mqd.process (*output);
#else
UWARN("RTAB-Map is not built with VTK module so mesh decimation cannot be used!");
*output = *mesh;
#endif
return output;
}
bool intersectRayTriangle(
const Eigen::Vector3f & p,
const Eigen::Vector3f & dir,
const Eigen::Vector3f & v0,
const Eigen::Vector3f & v1,
const Eigen::Vector3f & v2,
float & distance,
Eigen::Vector3f & normal)
{
// get triangle edge cv::Vec3fs and plane normal
const Eigen::Vector3f u = v1-v0;
const Eigen::Vector3f v = v2-v0;
normal = u.cross(v); // cross product
if (normal == Eigen::Vector3f(0,0,0)) // triangle is degenerate
return false; // do not deal with this case
const float denomimator = normal.dot(dir);
if (fabs(denomimator) < 10e-9) // ray is parallel to triangle plane
return false;
// get intersect of ray with triangle plane
distance = normal.dot(v0 - p) / denomimator;
if (distance < 0.0) // ray goes away from triangle
return false;
// is I inside T?
float uu, uv, vv, wu, wv, D;
uu = u.dot(u);
uv = u.dot(v);
vv = v.dot(v);
const Eigen::Vector3f w = p + dir * distance - v0;
wu = w.dot(u);
wv = w.dot(v);
D = uv * uv - uu * vv;
// get and test parametric coords
float s, t;
s = (uv * wv - vv * wu) / D;
if (s < 0.0 || s > 1.0) // I is outside T
return false;
t = (uv * wu - uu * wv) / D;
if (t < 0.0 || (s + t) > 1.0) // I is outside T
return false;
return true; // I is in T
}
/**
* This is a modified copy of https://github.com/PointCloudLibrary/pcl/blob/pcl-1.12.1/io/src/obj_io.cpp
* with added color on each vertex if provided
*/
int saveOBJFile(
const std::string &file_name,
const pcl::TextureMesh &tex_mesh,
unsigned precision)
{
if (tex_mesh.cloud.data.empty ())
{
UERROR ("Input point cloud has no data!\n");
return (-1);
}
// Open file
std::ofstream fs;
fs.precision (precision);
fs.open (file_name.c_str ());
// Define material file
std::string mtl_file_name = file_name.substr (0, file_name.find_last_of ('.')) + ".mtl";
// Strip path for "mtllib" command
std::string mtl_file_name_nopath = mtl_file_name;
mtl_file_name_nopath.erase (0, mtl_file_name.find_last_of ('/') + 1);
/* Write 3D information */
// number of points
unsigned nr_points = tex_mesh.cloud.width * tex_mesh.cloud.height;
unsigned point_size = static_cast<unsigned> (tex_mesh.cloud.data.size () / nr_points);
// mesh size
unsigned nr_meshes = static_cast<unsigned> (tex_mesh.tex_polygons.size ());
// number of faces for header
unsigned nr_faces = 0;
for (unsigned m = 0; m < nr_meshes; ++m)
nr_faces += static_cast<unsigned> (tex_mesh.tex_polygons[m].size ());
// Write the header information
fs << "####" << '\n';
fs << "# OBJ dataFile simple version. File name: " << file_name << '\n';
fs << "# Vertices: " << nr_points << '\n';
fs << "# Faces: " <<nr_faces << '\n';
fs << "# Material information:" << '\n';
fs << "mtllib " << mtl_file_name_nopath << '\n';
fs << "####" << '\n';
// Write vertex coordinates
fs << "# Vertices" << '\n';
for (unsigned i = 0; i < nr_points; ++i)
{
int xyz = 0;
// "v" just be written one
bool v_written = false;
for (std::size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
{
// adding vertex
if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
tex_mesh.cloud.fields[d].name == "x" ||
tex_mesh.cloud.fields[d].name == "y" ||
tex_mesh.cloud.fields[d].name == "z"))
{
if (!v_written)
{
// write vertices beginning with v
fs << "v ";
v_written = true;
}
float value;
memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset], sizeof (float));
fs << value;
if (++xyz == 3)
continue;
fs << " ";
}
else if(tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32 &&
tex_mesh.cloud.fields[d].name == "rgb")
{
std::uint32_t rgb = *reinterpret_cast<const int*>(&tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset]);
std::uint8_t r = (rgb >> 16) & 0x0000ff;
std::uint8_t g = (rgb >> 8) & 0x0000ff;
std::uint8_t b = (rgb) & 0x0000ff;
fs << " " << float(r)/255.0f << " " << float(g)/255.0f << " " << float(b)/255.0f;
break;
}
}
if (xyz != 3)
{
UERROR ("Input point cloud has no XYZ data!\n");
return (-2);
}
fs << '\n';
}
fs << "# "<< nr_points <<" vertices" << '\n';
// Write vertex normals
for (unsigned i = 0; i < nr_points; ++i)
{
int xyz = 0;
// "vn" just be written one
bool v_written = false;
for (std::size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
{
// adding vertex
if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
tex_mesh.cloud.fields[d].name == "normal_x" ||
tex_mesh.cloud.fields[d].name == "normal_y" ||
tex_mesh.cloud.fields[d].name == "normal_z"))
{
if (!v_written)
{
// write vertices beginning with vn
fs << "vn ";
v_written = true;
}
float value;
memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset], sizeof (float));
fs << value;
if (++xyz == 3)
break;
fs << " ";
}
}
if (xyz != 3)
{
UERROR ("Input point cloud has no normals!\n");
return (-2);
}
fs << '\n';
}
// Write vertex texture with "vt" (adding latter)
for (unsigned m = 0; m < nr_meshes; ++m)
{
fs << "# " << tex_mesh.tex_coordinates[m].size() << " vertex textures in submesh " << m << '\n';
for (const auto &coordinate : tex_mesh.tex_coordinates[m])
{
fs << "vt ";
fs << coordinate[0] << " " << coordinate[1] << '\n';
}
}
unsigned f_idx = 0;
// int idx_vt =0;
for (unsigned m = 0; m < nr_meshes; ++m)
{
if (m > 0) f_idx += static_cast<unsigned> (tex_mesh.tex_polygons[m-1].size ());
fs << "# The material will be used for mesh " << m << '\n';
fs << "usemtl " << tex_mesh.tex_materials[m].tex_name << '\n';
fs << "# Faces" << '\n';
for (std::size_t i = 0; i < tex_mesh.tex_polygons[m].size(); ++i)
{
// Write faces with "f"
fs << "f";
// There's one UV per vertex per face, i.e., the same vertex can have
// different UV depending on the face.
for (std::size_t j = 0; j < tex_mesh.tex_polygons[m][i].vertices.size (); ++j)
{
std::uint32_t idx = tex_mesh.tex_polygons[m][i].vertices[j] + 1;
fs << " " << idx
<< "/" << tex_mesh.tex_polygons[m][i].vertices.size () * (i+f_idx) +j+1
<< "/" << idx; // vertex index in obj file format starting with 1
}
fs << '\n';
}
fs << "# "<< tex_mesh.tex_polygons[m].size() << " faces in mesh " << m << '\n';
}
fs << "# End of File" << std::flush;
// Close obj file
fs.close ();
/* Write material definition for OBJ file*/
// Open file
std::ofstream m_fs;
m_fs.precision (precision);
m_fs.open (mtl_file_name.c_str ());
// default
m_fs << "#" << '\n';
m_fs << "# Wavefront material file" << '\n';
m_fs << "#" << '\n';
for(unsigned m = 0; m < nr_meshes; ++m)
{
m_fs << "newmtl " << tex_mesh.tex_materials[m].tex_name << '\n';
m_fs << "Ka "<< tex_mesh.tex_materials[m].tex_Ka.r << " " << tex_mesh.tex_materials[m].tex_Ka.g << " " << tex_mesh.tex_materials[m].tex_Ka.b << '\n'; // defines the ambient color of the material to be (r,g,b).
m_fs << "Kd "<< tex_mesh.tex_materials[m].tex_Kd.r << " " << tex_mesh.tex_materials[m].tex_Kd.g << " " << tex_mesh.tex_materials[m].tex_Kd.b << '\n'; // defines the diffuse color of the material to be (r,g,b).
m_fs << "Ks "<< tex_mesh.tex_materials[m].tex_Ks.r << " " << tex_mesh.tex_materials[m].tex_Ks.g << " " << tex_mesh.tex_materials[m].tex_Ks.b << '\n'; // defines the specular color of the material to be (r,g,b). This color shows up in highlights.
m_fs << "d " << tex_mesh.tex_materials[m].tex_d << '\n'; // defines the transparency of the material to be alpha.
m_fs << "Ns "<< tex_mesh.tex_materials[m].tex_Ns << '\n'; // defines the shininess of the material to be s.
m_fs << "illum "<< tex_mesh.tex_materials[m].tex_illum << '\n'; // denotes the illumination model used by the material.
// illum = 1 indicates a flat material with no specular highlights, so the value of Ks is not used.
// illum = 2 denotes the presence of specular highlights, and so a specification for Ks is required.
m_fs << "map_Kd " << tex_mesh.tex_materials[m].tex_file << '\n';
m_fs << "###" << '\n';
}
m_fs.close ();
return (0);
}
/**
* This is a modified copy of https://github.com/PointCloudLibrary/pcl/blob/pcl-1.12.1/io/src/obj_io.cpp
* with added color on each vertex if provided
*/
int saveOBJFile(
const std::string &file_name,
const pcl::PolygonMesh &mesh,
unsigned precision)
{
if (mesh.cloud.data.empty ())
{
UERROR ("Input point cloud has no data!\n");
return (-1);
}
// Open file
std::ofstream fs;
fs.precision (precision);
fs.open (file_name.c_str ());
/* Write 3D information */
// number of points
int nr_points = mesh.cloud.width * mesh.cloud.height;
// point size
unsigned point_size = static_cast<unsigned> (mesh.cloud.data.size () / nr_points);
// number of faces for header
unsigned nr_faces = static_cast<unsigned> (mesh.polygons.size ());
// Do we have vertices normals?
int normal_index = getFieldIndex (mesh.cloud, "normal_x");
// Write the header information
fs << "####" << '\n';
fs << "# OBJ dataFile simple version. File name: " << file_name << '\n';
fs << "# Vertices: " << nr_points << '\n';
if (normal_index != -1)
fs << "# Vertices normals : " << nr_points << '\n';
fs << "# Faces: " <<nr_faces << '\n';
fs << "####" << '\n';
// Write vertex coordinates
fs << "# List of Vertices, with (x,y,z) coordinates, w is optional." << '\n';
for (int i = 0; i < nr_points; ++i)
{
int xyz = 0;
for (std::size_t d = 0; d < mesh.cloud.fields.size (); ++d)
{
// adding vertex
if ((mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
mesh.cloud.fields[d].name == "x" ||
mesh.cloud.fields[d].name == "y" ||
mesh.cloud.fields[d].name == "z"))
{
if (mesh.cloud.fields[d].name == "x")
// write vertices beginning with v
fs << "v ";
float value;
memcpy (&value, &mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset], sizeof (float));
fs << value;
if (++xyz == 3)
continue;
fs << " ";
}
else if(mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32 &&
mesh.cloud.fields[d].name == "rgb")
{
std::uint32_t rgb = *reinterpret_cast<const int*>(&mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset]);
std::uint8_t r = (rgb >> 16) & 0x0000ff;
std::uint8_t g = (rgb >> 8) & 0x0000ff;
std::uint8_t b = (rgb) & 0x0000ff;
fs << " " << float(r)/255.0f << " " << float(g)/255.0f << " " << float(b)/255.0f;
break;
}
}
if (xyz != 3)
{
UERROR ("Input point cloud has no XYZ data!\n");
return (-2);
}
fs << '\n';
}
fs << "# "<< nr_points <<" vertices" << '\n';
if(normal_index != -1)
{
fs << "# Normals in (x,y,z) form; normals might not be unit." << '\n';
// Write vertex normals
for (int i = 0; i < nr_points; ++i)
{
int nxyz = 0;
for (std::size_t d = 0; d < mesh.cloud.fields.size (); ++d)
{
// adding vertex
if ((mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
mesh.cloud.fields[d].name == "normal_x" ||
mesh.cloud.fields[d].name == "normal_y" ||
mesh.cloud.fields[d].name == "normal_z"))
{
if (mesh.cloud.fields[d].name == "normal_x")
// write vertices beginning with vn
fs << "vn ";
float value;
memcpy (&value, &mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset], sizeof (float));
fs << value;
if (++nxyz == 3)
break;
fs << " ";
}
}
if (nxyz != 3)
{
UERROR ("Input point cloud has no normals!\n");
return (-2);
}
fs << '\n';
}
fs << "# "<< nr_points <<" vertices normals" << '\n';
}
fs << "# Face Definitions" << '\n';
// Write down faces
if(normal_index == -1)
{
for(unsigned i = 0; i < nr_faces; i++)
{
fs << "f ";
for (std::size_t j = 0; j < mesh.polygons[i].vertices.size () - 1; ++j)
fs << mesh.polygons[i].vertices[j] + 1 << " ";
fs << mesh.polygons[i].vertices.back() + 1 << '\n';
}
}
else
{
for(unsigned i = 0; i < nr_faces; i++)
{
fs << "f ";
for (std::size_t j = 0; j < mesh.polygons[i].vertices.size () - 1; ++j)
fs << mesh.polygons[i].vertices[j] + 1 << "//" << mesh.polygons[i].vertices[j] + 1 << " ";
fs << mesh.polygons[i].vertices.back() + 1 << "//" << mesh.polygons[i].vertices.back() + 1 << '\n';
}
}
fs << "# End of File" << std::endl;
// Close obj file
fs.close ();
return 0;
}
}
}