Removed odm_texturing, cleaned up 2.5d generation algorithm, added params in ODM pipeline

Former-commit-id: f1571ceb7e
pull/1161/head
Piero Toffanin 2017-04-06 17:33:36 -04:00
rodzic 693a038af8
commit 362f4fecf6
8 zmienionych plików z 107 dodań i 273 usunięć

Wyświetl plik

@ -64,18 +64,31 @@ void Odm25dMeshing::parseArguments(int argc, char **argv) {
} else if (argument == "-verbose") {
log.setIsPrintingInCout(true);
} else if (argument == "-maxVertexCount" && argIndex < argc) {
// ++argIndex;
// if (argIndex >= argc)
// {
// throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
// }
// std::stringstream ss(argv[argIndex]);
// ss >> maxVertexCount_;
// if (ss.bad())
// {
// throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
// }
// log << "Vertex count was manually set to: " << maxVertexCount_ << "\n";
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> maxVertexCount;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
maxVertexCount = std::max<unsigned int>(maxVertexCount, 0);
log << "Vertex count was manually set to: " << maxVertexCount << "\n";
} else if (argument == "-outliersRemovalPercentage" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> outliersRemovalPercentage;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
outliersRemovalPercentage = std::min<double>(99.99, std::max<double>(outliersRemovalPercentage, 0));
log << "Outliers removal was manually set to: " << outliersRemovalPercentage << "\n";
} else if (argument == "-wlopIterations" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> wlopIterations;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
wlopIterations = std::min<unsigned int>(1000, std::max<unsigned int>(wlopIterations, 1));
log << "WLOP iterations was manually set to: " << wlopIterations << "\n";
} else if (argument == "-inputFile" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) {
@ -141,55 +154,41 @@ void Odm25dMeshing::loadPointCloud(){
void Odm25dMeshing::buildMesh(){
size_t pointCountBeforeOutRemoval = points.size();
if (outliersRemovalPercentage > 0)
log << "Removing outliers\n";
const unsigned int NEIGHBORS = 24;
const double REMOVED_PERCENTAGE = 5;
points.erase(CGAL::remove_outliers(points.begin(), points.end(),
CGAL::Nth_of_tuple_property_map<0, PointNormalColor>(), NEIGHBORS, REMOVED_PERCENTAGE),
CGAL::First_of_pair_property_map<Pwn>(), NEIGHBORS, outliersRemovalPercentage),
points.end());
std::vector<PointNormalColor>(points).swap(points);
std::vector<Pwn>(points).swap(points);
size_t pointCount = points.size();
log << "Removed " << (pointCountBeforeOutRemoval - pointCount) << " points\n";
size_t pointCountBeforeSimplify = pointCount;
const double RETAIN_PERCENTAGE = std::min<double>(((100. * (double)maxVertexCount) / (double)pointCount), 10.); // percentage of points to retain.
std::vector<Point3> simplifiedPoints;
const double CELL_SIZE = 0.01;
points.erase(CGAL::grid_simplify_point_set(points.begin(), points.end(),
CGAL::Nth_of_tuple_property_map<0, PointNormalColor>(),
CELL_SIZE),
points.end());
std::vector<PointNormalColor>(points).swap(points);
log << "Performing weighted locally optimal projection simplification and regularization (retain: " << RETAIN_PERCENTAGE << "%, iterate: " << wlopIterations << ")" << "\n";
pointCount = points.size();
CGAL::wlop_simplify_and_regularize_point_set<Concurrency_tag>(
points.begin(),
points.end(),
std::back_inserter(simplifiedPoints),
CGAL::First_of_pair_property_map<Pwn>(),
RETAIN_PERCENTAGE,
-1.0, // auto radius = 8 times the average spacing of the point set.
wlopIterations,
false); // require_uniform_sampling
log << "Removed " << (pointCountBeforeSimplify - pointCount) << " points\n";
pointCount = simplifiedPoints.size();
if (pointCount < 3){
throw Odm25dMeshingException("Not enough points");
}
log << "Smoothing point set\n";
const double SHARPNESS_ANGLE = 89; // control sharpness of the result. The bigger the smoother the result will be
const int SMOOTH_PASSES = 3;
for (int i = 0; i < SMOOTH_PASSES; ++i)
{
log << "Pass " << (i + 1) << " of " << SMOOTH_PASSES << "\n";
CGAL::bilateral_smooth_point_set <Concurrency_tag>(
points.begin(),
points.end(),
CGAL::Nth_of_tuple_property_map<0, PointNormalColor>(),
CGAL::Nth_of_tuple_property_map<1, PointNormalColor>(),
NEIGHBORS,
SHARPNESS_ANGLE);
}
log << "Final vertex count is " << pointCount << "\n";
std::vector< std::pair<cgalPoint, size_t > > pts;
try{
@ -199,7 +198,7 @@ void Odm25dMeshing::buildMesh(){
}
for (size_t i = 0; i < pointCount; ++i){
pts.push_back(std::make_pair(cgalPoint(points[i].get<0>().x(), points[i].get<0>().y()), i));
pts.push_back(std::make_pair(cgalPoint(simplifiedPoints[i].x(), simplifiedPoints[i].y()), i));
}
log << "Computing delaunay triangulation\n";
@ -216,75 +215,26 @@ void Odm25dMeshing::buildMesh(){
// Convert to tinyply format
std::vector<float> vertices;
std::vector<uint8_t> colors;
std::vector<int> vertexIndicies;
try{
vertices.reserve(pointCount);
colors.reserve(pointCount);
vertexIndicies.reserve(triIndexes);
} catch (const std::bad_alloc&){
throw Odm25dMeshingException("Not enough memory");
}
for (size_t i = 0; i < pointCount; ++i){
vertices.push_back(points[i].get<0>().x());
vertices.push_back(points[i].get<0>().y());
vertices.push_back(points[i].get<0>().z());
colors.push_back(points[i].get<2>()[0]);
colors.push_back(points[i].get<2>()[1]);
colors.push_back(points[i].get<2>()[2]);
for (size_t i = 0; i < pointCount; ++i){
vertices.push_back(simplifiedPoints[i].x());
vertices.push_back(simplifiedPoints[i].y());
vertices.push_back(simplifiedPoints[i].z());
}
for (DT::Face_iterator face = dt.faces_begin(); face != dt.faces_end(); ++face) {
vertexIndicies.push_back(static_cast<int>(face->vertex(0)->info()));
vertexIndicies.push_back(static_cast<int>(face->vertex(1)->info()));
vertexIndicies.push_back(static_cast<int>(face->vertex(2)->info()));
}
log << "Removing spikes\n";
const float THRESHOLD = 0.2;
const int PASSES = 5;
std::vector<float> heights;
float current_threshold = THRESHOLD;
for (int i = 1; i <= PASSES; i++){
log << "Pass " << i << " of " << PASSES << "\n";
for (DT::Vertex_iterator vertex = dt.vertices_begin(); vertex != dt.vertices_end(); ++vertex){
// Check if the height between this vertex and its
// incident vertices is greater than THRESHOLD
Vertex_circulator vc = dt.incident_vertices(vertex), done(vc);
if (vc != 0){
float height = vertices[vertex->info() * 3 + 2];
bool spike = false;
do{
if (dt.is_infinite(vc)) continue;
float ivHeight = vertices[vc->info() * 3 + 2];
if (fabs(height - ivHeight) > current_threshold) spike = true;
heights.push_back(ivHeight);
}while(++vc != done);
if (spike){
// Replace the height of the vertex by the median height
// of its incident vertices
std::sort(heights.begin(), heights.end());
vertices[vertex->info() * 3 + 2] = heights[heights.size() / 2];
}
heights.clear();
}
}
current_threshold /= 2;
vertexIndicies.push_back(face->vertex(0)->info());
vertexIndicies.push_back(face->vertex(1)->info());
vertexIndicies.push_back(face->vertex(2)->info());
}
log << "Saving mesh to file.\n";
@ -295,7 +245,6 @@ void Odm25dMeshing::buildMesh(){
tinyply::PlyFile plyFile;
plyFile.add_properties_to_element("vertex", {"x", "y", "z"}, vertices);
plyFile.add_properties_to_element("vertex", { "diffuse_red", "diffuse_green", "diffuse_blue"}, colors);
plyFile.add_properties_to_element("face", { "vertex_index" }, vertexIndicies, 3, tinyply::PlyProperty::Type::INT8);
plyFile.write(outputStream, false);
@ -308,32 +257,19 @@ void Odm25dMeshing::printHelp() {
bool printInCoutPop = log.isPrintingInCout();
log.setIsPrintingInCout(true);
log << "odm_25dmeshing\n\n";
log << "Usage: odm_25dmeshing -inputFile [plyFile] [optional-parameters]\n";
log << "Create a 2.5D mesh from an oriented point cloud (points with normals) using a constrained delaunay triangulation. "
<< "The program requires a path to an input PLY point cloud file, all other input parameters are optional.\n\n";
log << "Purpose:" << "\n";
log
<< "Create a 2.5D mesh from an oriented point cloud (points with normals) using a constrained delaunay triangulation"
<< "\n";
log << " -inputFile <path> to PLY point cloud\n"
<< " -outputFile <path> where the output PLY 2.5D mesh should be saved (default: " << outputFile << ")\n"
<< " -logFile <path> log file path (default: " << logFilePath << ")\n"
<< " -verbose whether to print verbose output (default: " << (printInCoutPop ? "true" : "false") << ")\n"
<< " -outliersRemovalPercentage <0 - 99.99> percentage of outliers to remove. Set to 0 to disable. (default: " << outliersRemovalPercentage << ")\n"
<< " -maxVertexCount <0 - N> Maximum number of vertices in the output mesh. The mesh might have fewer vertices, but will not exceed this limit. (default: " << maxVertexCount << ")\n"
<< " -wlopIterations <1 - 1000> Iterations of the Weighted Locally Optimal Projection (WLOP) simplification algorithm. Higher values take longer but produce a smoother mesh. (default: " << wlopIterations << ")\n"
log << "Usage:" << "\n";
log << "The program requires a path to an input PLY point cloud file, all other input parameters are optional."
<< "\n\n";
log << "The following flags are available\n";
log << "Call the program with flag \"-help\", or without parameters to print this message, or check any generated log file.\n";
log
<< "Call the program with flag \"-verbose\", to print log messages in the standard output stream as well as in the log file.\n\n";
log << "Parameters are specified as: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: "
<< "\n";
log << "\"-inputFile <path>\" (mandatory)" << "\n";
log << "\"Input ply file that must contain a point cloud with normals.\n\n";
log << "\"-outputFile <path>\" (optional, default: odm_mesh.ply)" << "\n";
log << "\"Target file in which the mesh is saved.\n\n";
log << "\"-logFile <path>\" (optional, default: odm_25dmeshing_log.txt)"
<< "\n";
<< "\n";
log.setIsPrintingInCout(printInCoutPop);
}

Wyświetl plik

@ -10,8 +10,7 @@
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_2.h>
#include <CGAL/remove_outliers.h>
#include <CGAL/grid_simplify_point_set.h>
#include <CGAL/bilateral_smooth_point_set.h>
#include <CGAL/wlop_simplify_and_regularize_point_set.h>
#include "Logger.hpp"
#include "PlyInterpreter.hpp"
@ -60,10 +59,12 @@ private:
Logger log;
std::string inputFile = "";
std::string outputFile = "odm_mesh.ply";
std::string outputFile = "odm_25dmesh.ply";
std::string logFilePath = "odm_25dmeshing_log.txt";
std::vector<PointNormalColor> points;
unsigned int maxVertexCount = 100000;
double outliersRemovalPercentage = 2;
unsigned int wlopIterations = 35;
std::vector<Pwn> points;
};
class Odm25dMeshingException: public std::exception {

Wyświetl plik

@ -7,10 +7,7 @@ bool PlyInterpreter::is_applicable(CGAL::Ply_reader& reader) {
&& reader.does_tag_exist<FT> ("z")
&& reader.does_tag_exist<FT> ("nx")
&& reader.does_tag_exist<FT> ("ny")
&& reader.does_tag_exist<FT> ("nz")
&& reader.does_tag_exist<unsigned char> ("diffuse_red")
&& reader.does_tag_exist<unsigned char> ("diffuse_green")
&& reader.does_tag_exist<unsigned char> ("diffuse_blue");
&& reader.does_tag_exist<FT> ("nz");
}
// Describes how to process one line (= one point object)
@ -18,21 +15,15 @@ void PlyInterpreter::process_line(CGAL::Ply_reader& reader) {
FT x = (FT)0., y = (FT)0., z = (FT)0.,
nx = (FT)0., ny = (FT)0., nz = (FT)0.;
Color c = {{ 0, 0, 0 }};
reader.assign (x, "x");
reader.assign (y, "y");
reader.assign (z, "z");
reader.assign (nx, "nx");
reader.assign (ny, "ny");
reader.assign (nz, "nz");
reader.assign (c[0], "diffuse_red");
reader.assign (c[1], "diffuse_green");
reader.assign (c[2], "diffuse_blue");
Point3 p(x, y, z);
Vector3 n(nx, ny, nz);
PointNormalColor pnc(p, n, c);
points.push_back(pnc);
points.push_back(std::make_pair(p, n));
}

Wyświetl plik

@ -7,7 +7,6 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/property_map.h>
#include <CGAL/IO/read_ply_points.h>
#include <boost/tuple/tuple.hpp>
// types
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
@ -15,19 +14,14 @@ typedef Kernel::FT FT;
typedef Kernel::Point_3 Point3;
typedef Kernel::Vector_3 Vector3;
// Point with normal vector stored as a std::pair.
// Color is red/green/blue array
typedef CGAL::cpp11::array<unsigned char, 3> Color;
// points, normals and colors
typedef boost::tuple<Point3, Vector3, Color> PointNormalColor;
// points, normals
typedef std::pair<Point3, Vector3> Pwn;
class PlyInterpreter {
std::vector<PointNormalColor>& points;
std::vector<Pwn>& points;
public:
PlyInterpreter (std::vector<PointNormalColor>& points)
PlyInterpreter (std::vector<Pwn>& points)
: points (points)
{ }
bool is_applicable (CGAL::Ply_reader& reader);

Wyświetl plik

@ -158,7 +158,6 @@ def config():
default=False,
help='Use 2.5D mesh to compute the orthophoto')
parser.add_argument('--use-pmvs',
action='store_true',
default=False,
@ -253,6 +252,23 @@ def config():
'times slightly but helps reduce memory usage. '
'Default: %(default)s'))
parser.add_argument('--mesh-remove-outliers',
metavar='<percent>',
default=2,
type=float,
help=('Percentage of outliers to remove from the point set. Set to 0 to disable. '
'Applies to 2.5D mesh only. '
'Default: %(default)s'))
parser.add_argument('--mesh-wlop-iterations',
metavar='<positive integer>',
default=35,
type=int,
help=('Iterations of the Weighted Locally Optimal Projection (WLOP) simplification algorithm. '
'Higher values take longer but produce a smoother mesh. '
'Applies to 2.5D mesh only. '
'Default: %(default)s'))
parser.add_argument('--texturing-data-term',
metavar='<string>',
default='gmi',

Wyświetl plik

@ -59,6 +59,8 @@ class ODMApp(ecto.BlackBox):
oct_tree=p.args.mesh_octree_depth,
samples=p.args.mesh_samples,
solver=p.args.mesh_solver_divide,
remove_outliers=p.args.mesh_remove_outliers,
wlop_iterations=p.args.mesh_wlop_iterations,
verbose=p.args.verbose),
'texturing': ODMMvsTexCell(data_term=p.args.texturing_data_term,
outlier_rem_type=p.args.texturing_outlier_removal_type,

Wyświetl plik

@ -19,6 +19,14 @@ class ODMeshingCell(ecto.Cell):
'is solved in the surface reconstruction step. '
'Increasing this value increases computation '
'times slightly but helps reduce memory usage.', 9)
params.declare("remove_outliers", 'Percentage of outliers to remove from the point set. Set to 0 to disable. '
'Applies to 2.5D mesh only.', 2)
params.declare("wlop_iterations", 'Iterations of the Weighted Locally Optimal Projection (WLOP) simplification algorithm. '
'Higher values take longer but produce a smoother mesh. '
'Applies to 2.5D mesh only. ', 35)
params.declare("verbose", 'print additional messages to console', False)
def declare_io(self, params, inputs, outputs):
@ -87,12 +95,17 @@ class ODMeshingCell(ecto.Cell):
'outfile': tree.odm_25dmesh,
'infile': infile,
'log': tree.odm_25dmeshing_log,
'verbose': verbose
'verbose': verbose,
'max_vertex': self.params.max_vertex,
'remove_outliers': self.params.remove_outliers,
'wlop_iterations': self.params.wlop_iterations
}
# run 2.5D meshing binary
system.run('{bin}/odm_25dmeshing -inputFile {infile} '
'-outputFile {outfile} -logFile {log} {verbose} '.format(**kwargs))
'-outputFile {outfile} -logFile {log} '
'-maxVertexCount {max_vertex} -outliersRemovalPercentage {remove_outliers} '
'-wlopIterations {wlop_iterations} {verbose}'.format(**kwargs))
else:
log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' %
tree.odm_25dmesh)

Wyświetl plik

@ -1,119 +0,0 @@
import os
import ecto
from opendm import log
from opendm import io
from opendm import system
from opendm import context
class ODMTexturingCell(ecto.Cell):
def declare_params(self, params):
params.declare("resize", 'resizes images by the largest side', 2400)
params.declare("resolution", 'The resolution of the output textures. Must be '
'greater than textureWithSize.', 4096)
params.declare("size", 'The resolution to rescale the images performing '
'the texturing.', 3600)
params.declare("verbose", 'print additional messages to console', False)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
inputs.declare("args", "The application arguments.", {})
inputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
outputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running ODM Texturing Cell')
# get inputs
args = self.inputs.args
tree = self.inputs.tree
verbose = '-verbose' if self.params.verbose else ''
# define paths and create working directories
system.mkdir_p(tree.odm_texturing)
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'odm_texturing') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'odm_texturing' in args.rerun_from)
# Undistort radial distortion
if not os.path.isdir(tree.odm_texturing_undistorted_image_path) or rerun_cell:
system.run(' '.join([
'cd {} &&'.format(tree.opensfm),
'PYTHONPATH={}:{}'.format(context.pyopencv_path,
context.opensfm_path),
'python',
os.path.join(context.odm_modules_src_path,
'odm_slam/src/undistort_radial.py'),
'--output',
tree.odm_texturing_undistorted_image_path,
tree.opensfm,
]))
system.run(
'PYTHONPATH=%s %s/bin/export_bundler %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
else:
log.ODM_WARNING(
'Found a valid Bundler file in: %s' %
(tree.opensfm_reconstruction))
runs = [{
'out_dir': tree.odm_texturing,
'model': tree.odm_mesh
}]
if args.use_25dmesh:
runs += [{
'out_dir': tree.odm_25dtexturing,
'model': tree.odm_25dmesh
}]
for r in runs:
odm_textured_model_obj = os.path.join(r['out_dir'], tree.odm_textured_model_obj)
if not io.file_exists(odm_textured_model_obj) or rerun_cell:
log.ODM_DEBUG('Writing ODM Textured file in: %s'
% odm_textured_model_obj)
# odm_texturing definitions
kwargs = {
'bin': context.odm_modules_path,
'out_dir': r['out_dir'],
'bundle': tree.opensfm_bundle,
'imgs_path': tree.odm_texturing_undistorted_image_path,
'imgs_list': tree.opensfm_bundle_list,
'model': r['model'],
'log': os.path.join(r['out_dir'], tree.odm_texuring_log),
'resize': self.params.resize,
'resolution': self.params.resolution,
'size': self.params.size,
'verbose': verbose
}
# run texturing binary
system.run('{bin}/odm_texturing -bundleFile {bundle} '
'-imagesPath {imgs_path} -imagesListPath {imgs_list} '
'-inputModelPath {model} -outputFolder {out_dir}/ '
'-textureResolution {resolution} -bundleResizedTo {resize} {verbose} '
'-textureWithSize {size} -logFile {log}'.format(**kwargs))
else:
log.ODM_WARNING('Found a valid ODM Texture file in: %s'
% odm_textured_model_obj)
if args.time:
system.benchmark(start_time, tree.benchmarking, 'Texturing')
log.ODM_INFO('Running ODM Texturing Cell - Finished')
return ecto.OK if args.end_with != 'odm_texturing' else ecto.QUIT