| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "mvs/meshing.h" |
| |
|
| | #include <fstream> |
| | #include <unordered_map> |
| | #include <vector> |
| |
|
| | #ifdef CGAL_ENABLED |
| | #include <CGAL/Delaunay_triangulation_3.h> |
| | #include <CGAL/Exact_predicates_inexact_constructions_kernel.h> |
| | #endif |
| |
|
| | #include "PoissonRecon/PoissonRecon.h" |
| | #include "PoissonRecon/SurfaceTrimmer.h" |
| | #include "base/graph_cut.h" |
| | #include "base/reconstruction.h" |
| | #include "util/endian.h" |
| | #include "util/logging.h" |
| | #include "util/misc.h" |
| | #include "util/option_manager.h" |
| | #include "util/ply.h" |
| | #include "util/random.h" |
| | #include "util/threading.h" |
| | #include "util/timer.h" |
| |
|
| | #ifdef CGAL_ENABLED |
| |
|
| | typedef CGAL::Exact_predicates_inexact_constructions_kernel K; |
| | typedef CGAL::Delaunay_triangulation_3<K, CGAL::Fast_location> Delaunay; |
| |
|
| | namespace std { |
| |
|
| | template <> |
| | struct hash<Delaunay::Vertex_handle> { |
| | std::size_t operator()(const Delaunay::Vertex_handle& handle) const { |
| | return reinterpret_cast<std::size_t>(&*handle); |
| | } |
| | }; |
| |
|
| | template <> |
| | struct hash<const Delaunay::Vertex_handle> { |
| | std::size_t operator()(const Delaunay::Vertex_handle& handle) const { |
| | return reinterpret_cast<std::size_t>(&*handle); |
| | } |
| | }; |
| |
|
| | template <> |
| | struct hash<Delaunay::Cell_handle> { |
| | std::size_t operator()(const Delaunay::Cell_handle& handle) const { |
| | return reinterpret_cast<std::size_t>(&*handle); |
| | } |
| | }; |
| |
|
| | template <> |
| | struct hash<const Delaunay::Cell_handle> { |
| | std::size_t operator()(const Delaunay::Cell_handle& handle) const { |
| | return reinterpret_cast<std::size_t>(&*handle); |
| | } |
| | }; |
| |
|
| | } |
| |
|
| | #endif |
| |
|
| | namespace colmap { |
| | namespace mvs { |
| |
|
| | bool PoissonMeshingOptions::Check() const { |
| | CHECK_OPTION_GE(point_weight, 0); |
| | CHECK_OPTION_GT(depth, 0); |
| | CHECK_OPTION_GE(color, 0); |
| | CHECK_OPTION_GE(trim, 0); |
| | CHECK_OPTION_GE(num_threads, -1); |
| | CHECK_OPTION_NE(num_threads, 0); |
| | return true; |
| | } |
| |
|
| | bool DelaunayMeshingOptions::Check() const { |
| | CHECK_OPTION_GE(max_proj_dist, 0); |
| | CHECK_OPTION_GE(max_depth_dist, 0); |
| | CHECK_OPTION_LE(max_depth_dist, 1); |
| | CHECK_OPTION_GT(visibility_sigma, 0); |
| | CHECK_OPTION_GT(distance_sigma_factor, 0); |
| | CHECK_OPTION_GE(quality_regularization, 0); |
| | CHECK_OPTION_GE(max_side_length_factor, 0); |
| | CHECK_OPTION_GE(max_side_length_percentile, 0); |
| | CHECK_OPTION_LE(max_side_length_percentile, 100); |
| | CHECK_OPTION_GE(num_threads, -1); |
| | CHECK_OPTION_NE(num_threads, 0); |
| | return true; |
| | } |
| |
|
| | bool PoissonMeshing(const PoissonMeshingOptions& options, |
| | const std::string& input_path, |
| | const std::string& output_path) { |
| | CHECK(options.Check()); |
| |
|
| | std::vector<std::string> args; |
| |
|
| | args.push_back("./binary"); |
| |
|
| | args.push_back("--in"); |
| | args.push_back(input_path); |
| |
|
| | args.push_back("--out"); |
| | args.push_back(output_path); |
| |
|
| | args.push_back("--pointWeight"); |
| | args.push_back(std::to_string(options.point_weight)); |
| |
|
| | args.push_back("--depth"); |
| | args.push_back(std::to_string(options.depth)); |
| |
|
| | if (options.color > 0) { |
| | args.push_back("--color"); |
| | args.push_back(std::to_string(options.color)); |
| | } |
| |
|
| | #ifdef OPENMP_ENABLED |
| | if (options.num_threads > 0) { |
| | args.push_back("--threads"); |
| | args.push_back(std::to_string(options.num_threads)); |
| | } |
| | #endif |
| |
|
| | if (options.trim > 0) { |
| | args.push_back("--density"); |
| | } |
| |
|
| | std::vector<const char*> args_cstr; |
| | args_cstr.reserve(args.size()); |
| | for (const auto& arg : args) { |
| | args_cstr.push_back(arg.c_str()); |
| | } |
| |
|
| | if (PoissonRecon(args_cstr.size(), const_cast<char**>(args_cstr.data())) != |
| | EXIT_SUCCESS) { |
| | return false; |
| | } |
| |
|
| | if (options.trim == 0) { |
| | return true; |
| | } |
| |
|
| | args.clear(); |
| | args_cstr.clear(); |
| |
|
| | args.push_back("./binary"); |
| |
|
| | args.push_back("--in"); |
| | args.push_back(output_path); |
| |
|
| | args.push_back("--out"); |
| | args.push_back(output_path); |
| |
|
| | args.push_back("--trim"); |
| | args.push_back(std::to_string(options.trim)); |
| |
|
| | args_cstr.reserve(args.size()); |
| | for (const auto& arg : args) { |
| | args_cstr.push_back(arg.c_str()); |
| | } |
| |
|
| | return SurfaceTrimmer(args_cstr.size(), |
| | const_cast<char**>(args_cstr.data())) == EXIT_SUCCESS; |
| | } |
| |
|
| | #ifdef CGAL_ENABLED |
| |
|
| | K::Point_3 EigenToCGAL(const Eigen::Vector3f& point) { |
| | return K::Point_3(point.x(), point.y(), point.z()); |
| | } |
| |
|
| | Eigen::Vector3f CGALToEigen(const K::Point_3& point) { |
| | return Eigen::Vector3f(point.x(), point.y(), point.z()); |
| | } |
| |
|
| | class DelaunayMeshingInput { |
| | public: |
| | struct Image { |
| | camera_t camera_id = kInvalidCameraId; |
| | Eigen::Matrix3x4f proj_matrix = Eigen::Matrix3x4f::Identity(); |
| | Eigen::Vector3f proj_center = Eigen::Vector3f::Zero(); |
| | std::vector<size_t> point_idxs; |
| | }; |
| |
|
| | struct Point { |
| | Eigen::Vector3f position = Eigen::Vector3f::Zero(); |
| | uint32_t num_visible_images = 0; |
| | }; |
| |
|
| | EIGEN_STL_UMAP(camera_t, Camera) cameras; |
| | std::vector<Image> images; |
| | std::vector<Point> points; |
| |
|
| | void ReadSparseReconstruction(const std::string& path) { |
| | Reconstruction reconstruction; |
| | reconstruction.Read(path); |
| | CopyFromSparseReconstruction(reconstruction); |
| | } |
| |
|
| | void CopyFromSparseReconstruction(const Reconstruction& reconstruction) { |
| | images.reserve(reconstruction.NumRegImages()); |
| | points.reserve(reconstruction.NumPoints3D()); |
| |
|
| | cameras = reconstruction.Cameras(); |
| |
|
| | std::unordered_map<point3D_t, size_t> point_id_to_idx; |
| | point_id_to_idx.reserve(reconstruction.NumPoints3D()); |
| | for (const auto& point3D : reconstruction.Points3D()) { |
| | point_id_to_idx.emplace(point3D.first, points.size()); |
| | DelaunayMeshingInput::Point input_point; |
| | input_point.position = point3D.second.XYZ().cast<float>(); |
| | input_point.num_visible_images = point3D.second.Track().Length(); |
| | points.push_back(input_point); |
| | } |
| |
|
| | for (const auto image_id : reconstruction.RegImageIds()) { |
| | const auto& image = reconstruction.Image(image_id); |
| | DelaunayMeshingInput::Image input_image; |
| | input_image.camera_id = image.CameraId(); |
| | input_image.proj_matrix = image.ProjectionMatrix().cast<float>(); |
| | input_image.proj_center = image.ProjectionCenter().cast<float>(); |
| | input_image.point_idxs.reserve(image.NumPoints3D()); |
| | for (const auto& point2D : image.Points2D()) { |
| | if (point2D.HasPoint3D()) { |
| | input_image.point_idxs.push_back( |
| | point_id_to_idx.at(point2D.Point3DId())); |
| | } |
| | } |
| | images.push_back(input_image); |
| | } |
| | } |
| |
|
| | void ReadDenseReconstruction(const std::string& path) { |
| | { |
| | Reconstruction reconstruction; |
| | reconstruction.Read(JoinPaths(path, "sparse")); |
| |
|
| | cameras = reconstruction.Cameras(); |
| |
|
| | images.reserve(reconstruction.NumRegImages()); |
| | for (const auto& image_id : reconstruction.RegImageIds()) { |
| | const auto& image = reconstruction.Image(image_id); |
| | DelaunayMeshingInput::Image input_image; |
| | input_image.camera_id = image.CameraId(); |
| | input_image.proj_matrix = image.ProjectionMatrix().cast<float>(); |
| | input_image.proj_center = image.ProjectionCenter().cast<float>(); |
| | images.push_back(input_image); |
| | } |
| | } |
| |
|
| | const auto& ply_points = ReadPly(JoinPaths(path, "fused.ply")); |
| |
|
| | const std::string vis_path = JoinPaths(path, "fused.ply.vis"); |
| | std::fstream vis_file(vis_path, std::ios::in | std::ios::binary); |
| | CHECK(vis_file.is_open()) << vis_path; |
| |
|
| | const size_t vis_num_points = ReadBinaryLittleEndian<uint64_t>(&vis_file); |
| | CHECK_EQ(vis_num_points, ply_points.size()); |
| |
|
| | points.reserve(ply_points.size()); |
| | for (const auto& ply_point : ply_points) { |
| | const int point_idx = points.size(); |
| | DelaunayMeshingInput::Point input_point; |
| | input_point.position = |
| | Eigen::Vector3f(ply_point.x, ply_point.y, ply_point.z); |
| | input_point.num_visible_images = |
| | ReadBinaryLittleEndian<uint32_t>(&vis_file); |
| | for (uint32_t i = 0; i < input_point.num_visible_images; ++i) { |
| | const int image_idx = ReadBinaryLittleEndian<uint32_t>(&vis_file); |
| | images.at(image_idx).point_idxs.push_back(point_idx); |
| | } |
| | points.push_back(input_point); |
| | } |
| | } |
| |
|
| | Delaunay CreateDelaunayTriangulation() const { |
| | std::vector<Delaunay::Point> delaunay_points(points.size()); |
| | for (size_t i = 0; i < points.size(); ++i) { |
| | delaunay_points[i] = |
| | Delaunay::Point(points[i].position.x(), points[i].position.y(), |
| | points[i].position.z()); |
| | } |
| | return Delaunay(delaunay_points.begin(), delaunay_points.end()); |
| | } |
| |
|
| | Delaunay CreateSubSampledDelaunayTriangulation( |
| | const float max_proj_dist, const float max_depth_dist) const { |
| | CHECK_GE(max_proj_dist, 0); |
| |
|
| | if (max_proj_dist == 0) { |
| | return CreateDelaunayTriangulation(); |
| | } |
| |
|
| | std::vector<std::vector<uint32_t>> points_visible_image_idxs(points.size()); |
| | for (size_t image_idx = 0; image_idx < images.size(); ++image_idx) { |
| | for (const auto& point_idx : images[image_idx].point_idxs) { |
| | points_visible_image_idxs[point_idx].push_back(image_idx); |
| | } |
| | } |
| |
|
| | std::vector<size_t> point_idxs(points.size()); |
| | std::iota(point_idxs.begin(), point_idxs.end(), 0); |
| | Shuffle(point_idxs.size(), &point_idxs); |
| |
|
| | Delaunay triangulation; |
| |
|
| | const float max_squared_proj_dist = max_proj_dist * max_proj_dist; |
| | const float min_depth_ratio = 1.0f - max_depth_dist; |
| | const float max_depth_ratio = 1.0f + max_depth_dist; |
| |
|
| | for (const auto point_idx : point_idxs) { |
| | const auto& point = points[point_idx]; |
| | const auto& visible_image_idxs = points_visible_image_idxs[point_idx]; |
| |
|
| | const K::Point_3 point_position = EigenToCGAL(point.position); |
| |
|
| | |
| | if (triangulation.number_of_vertices() < 4) { |
| | triangulation.insert(point_position); |
| | continue; |
| | } |
| |
|
| | const Delaunay::Cell_handle cell = triangulation.locate(point_position); |
| |
|
| | |
| | if (triangulation.is_infinite(cell)) { |
| | triangulation.insert(point_position); |
| | continue; |
| | } |
| |
|
| | |
| | |
| |
|
| | bool insert_point = false; |
| |
|
| | for (const auto& image_idx : visible_image_idxs) { |
| | const auto& image = images[image_idx]; |
| | const auto& camera = cameras.at(image.camera_id); |
| |
|
| | for (int i = 0; i < 4; ++i) { |
| | const Eigen::Vector3f cell_point = |
| | CGALToEigen(cell->vertex(i)->point()); |
| |
|
| | const Eigen::Vector3f point_local = |
| | image.proj_matrix * point.position.homogeneous(); |
| | const Eigen::Vector3f cell_point_local = |
| | image.proj_matrix * cell_point.homogeneous(); |
| |
|
| | |
| | if (point_local.z() <= 0 || cell_point_local.z() <= 0) { |
| | insert_point = true; |
| | break; |
| | } |
| |
|
| | |
| | const float depth_ratio = point_local.z() / cell_point_local.z(); |
| | if (depth_ratio < min_depth_ratio || depth_ratio > max_depth_ratio) { |
| | insert_point = true; |
| | break; |
| | } |
| |
|
| | |
| | const Eigen::Vector2f point_proj = |
| | camera.WorldToImage(point_local.hnormalized().cast<double>()) |
| | .cast<float>(); |
| | const Eigen::Vector2f cell_point_proj = |
| | camera.WorldToImage(cell_point_local.hnormalized().cast<double>()) |
| | .cast<float>(); |
| | const float squared_proj_dist = |
| | (point_proj - cell_point_proj).squaredNorm(); |
| | if (squared_proj_dist > max_squared_proj_dist) { |
| | insert_point = true; |
| | break; |
| | } |
| | } |
| |
|
| | if (insert_point) { |
| | break; |
| | } |
| | } |
| |
|
| | if (insert_point) { |
| | triangulation.insert(point_position); |
| | } |
| | } |
| |
|
| | std::cout << StringPrintf("Triangulation has %d using %d points.", |
| | triangulation.number_of_vertices(), points.size()) |
| | << std::endl; |
| |
|
| | return triangulation; |
| | } |
| | }; |
| |
|
| | struct DelaunayMeshingEdgeWeightComputer { |
| | DelaunayMeshingEdgeWeightComputer(const Delaunay& triangulation, |
| | const double visibility_sigma, |
| | const double distance_sigma_factor) |
| | : visibility_threshold_(5 * visibility_sigma), |
| | visibility_normalization_(-0.5 / |
| | (visibility_sigma * visibility_sigma)) { |
| | std::vector<float> edge_lengths; |
| | edge_lengths.reserve(triangulation.number_of_finite_edges()); |
| |
|
| | for (auto it = triangulation.finite_edges_begin(); |
| | it != triangulation.finite_edges_end(); ++it) { |
| | edge_lengths.push_back((it->first->vertex(it->second)->point() - |
| | it->first->vertex(it->third)->point()) |
| | .squared_length()); |
| | } |
| |
|
| | distance_sigma_ = distance_sigma_factor * |
| | std::max(std::sqrt(Percentile(edge_lengths, 25)), 1e-7f); |
| | distance_threshold_ = 5 * distance_sigma_; |
| | distance_normalization_ = -0.5 / (distance_sigma_ * distance_sigma_); |
| | } |
| |
|
| | double DistanceSigma() const { return distance_sigma_; } |
| |
|
| | double ComputeVisibilityProb(const double visibility_squared) const { |
| | if (visibility_squared < visibility_threshold_) { |
| | return std::max( |
| | 0.0, 1.0 - std::exp(visibility_squared * visibility_normalization_)); |
| | } else { |
| | return 1.0; |
| | } |
| | } |
| |
|
| | double ComputeDistanceProb(const double distance_squared) const { |
| | if (distance_squared < distance_threshold_) { |
| | return std::max( |
| | 0.0, 1.0 - std::exp(distance_squared * distance_normalization_)); |
| | } else { |
| | return 1.0; |
| | } |
| | } |
| |
|
| | private: |
| | double visibility_threshold_; |
| | double visibility_normalization_; |
| | double distance_sigma_; |
| | double distance_threshold_; |
| | double distance_normalization_; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | struct DelaunayTriangulationRayCaster { |
| | struct Intersection { |
| | Delaunay::Facet facet; |
| | double target_distance_squared = 0.0; |
| | }; |
| |
|
| | DelaunayTriangulationRayCaster(const Delaunay& triangulation) |
| | : triangulation_(triangulation) { |
| | FindHullFacets(); |
| | } |
| |
|
| | void CastRaySegment(const K::Segment_3& ray_segment, |
| | std::vector<Intersection>* intersections) const { |
| | intersections->clear(); |
| |
|
| | Delaunay::Cell_handle next_cell = |
| | triangulation_.locate(ray_segment.start()); |
| |
|
| | bool next_cell_found = true; |
| | while (next_cell_found) { |
| | next_cell_found = false; |
| |
|
| | if (triangulation_.is_infinite(next_cell)) { |
| | |
| |
|
| | for (const auto& hull_facet : hull_facets_) { |
| | |
| | const K::Triangle_3 triangle = triangulation_.triangle(hull_facet); |
| | if (CGAL::orientation(triangle[0], triangle[1], triangle[2], |
| | ray_segment.start()) == |
| | K::Orientation::NEGATIVE) { |
| | continue; |
| | } |
| |
|
| | |
| | K::Point_3 intersection_point; |
| | if (!CGAL::assign(intersection_point, |
| | CGAL::intersection(ray_segment, triangle))) { |
| | continue; |
| | } |
| |
|
| | |
| | const double target_distance_squared = |
| | (intersection_point - ray_segment.end()).squared_length(); |
| | if (!intersections->empty() && |
| | intersections->back().target_distance_squared < |
| | target_distance_squared) { |
| | continue; |
| | } |
| |
|
| | Intersection intersection; |
| | intersection.facet = |
| | Delaunay::Facet(hull_facet.first, hull_facet.second); |
| | intersection.target_distance_squared = target_distance_squared; |
| | intersections->push_back(intersection); |
| |
|
| | next_cell = hull_facet.first->neighbor(hull_facet.second); |
| | next_cell_found = true; |
| |
|
| | break; |
| | } |
| | } else { |
| | |
| |
|
| | for (int i = 0; i < 4; ++i) { |
| | |
| | const K::Triangle_3 triangle = triangulation_.triangle(next_cell, i); |
| | if (CGAL::orientation(triangle[0], triangle[1], triangle[2], |
| | ray_segment.start()) == |
| | K::Orientation::NEGATIVE) { |
| | continue; |
| | } |
| |
|
| | |
| | K::Point_3 intersection_point; |
| | if (!CGAL::assign(intersection_point, |
| | CGAL::intersection(ray_segment, triangle))) { |
| | continue; |
| | } |
| |
|
| | |
| | const double target_distance_squared = |
| | (intersection_point - ray_segment.end()).squared_length(); |
| | if (!intersections->empty() && |
| | intersections->back().target_distance_squared < |
| | target_distance_squared) { |
| | continue; |
| | } |
| |
|
| | Intersection intersection; |
| | intersection.facet = Delaunay::Facet(next_cell, i); |
| | intersection.target_distance_squared = target_distance_squared; |
| | intersections->push_back(intersection); |
| |
|
| | next_cell = next_cell->neighbor(i); |
| | next_cell_found = true; |
| |
|
| | break; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | private: |
| | |
| | void FindHullFacets() { |
| | for (auto it = triangulation_.all_cells_begin(); |
| | it != triangulation_.all_cells_end(); ++it) { |
| | if (triangulation_.is_infinite(it)) { |
| | for (int i = 0; i < 4; ++i) { |
| | if (!triangulation_.is_infinite(it, i)) { |
| | hull_facets_.emplace_back(it, i); |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | const Delaunay& triangulation_; |
| | std::vector<Delaunay::Facet> hull_facets_; |
| | }; |
| |
|
| | |
| | |
| | |
| | double ComputeCosFacetCellAngle(const Delaunay& triangulation, |
| | const Delaunay::Facet& facet) { |
| | if (triangulation.is_infinite(facet.first)) { |
| | return 1.0; |
| | } |
| |
|
| | const K::Triangle_3 triangle = triangulation.triangle(facet); |
| |
|
| | const K::Vector_3 facet_normal = |
| | CGAL::cross_product(triangle[1] - triangle[0], triangle[2] - triangle[0]); |
| | const double facet_normal_length_squared = facet_normal.squared_length(); |
| | if (facet_normal_length_squared == 0.0) { |
| | return 0.5; |
| | } |
| |
|
| | const K::Vector_3 co_tangent = facet.first->circumcenter() - triangle[0]; |
| | const float co_tangent_length_squared = co_tangent.squared_length(); |
| | if (co_tangent_length_squared == 0.0) { |
| | return 0.5; |
| | } |
| |
|
| | return (facet_normal * co_tangent) / |
| | std::sqrt(facet_normal_length_squared * co_tangent_length_squared); |
| | } |
| |
|
| | void WriteDelaunayTriangulationPly(const std::string& path, |
| | const Delaunay& triangulation) { |
| | std::fstream file(path, std::ios::out); |
| | CHECK(file.is_open()); |
| |
|
| | file << "ply" << std::endl; |
| | file << "format ascii 1.0" << std::endl; |
| | file << "element vertex " << triangulation.number_of_vertices() << std::endl; |
| | file << "property float x" << std::endl; |
| | file << "property float y" << std::endl; |
| | file << "property float z" << std::endl; |
| | file << "element edge " << triangulation.number_of_finite_edges() |
| | << std::endl; |
| | file << "property int vertex1" << std::endl; |
| | file << "property int vertex2" << std::endl; |
| | file << "element face " << triangulation.number_of_finite_facets() |
| | << std::endl; |
| | file << "property list uchar int vertex_index" << std::endl; |
| | file << "end_header" << std::endl; |
| |
|
| | std::unordered_map<const Delaunay::Vertex_handle, size_t> vertex_indices; |
| | vertex_indices.reserve(triangulation.number_of_vertices()); |
| | for (auto it = triangulation.finite_vertices_begin(); |
| | it != triangulation.finite_vertices_end(); ++it) { |
| | vertex_indices.emplace(it, vertex_indices.size()); |
| | file << it->point().x() << " " << it->point().y() << " " << it->point().z() |
| | << std::endl; |
| | } |
| |
|
| | for (auto it = triangulation.finite_edges_begin(); |
| | it != triangulation.finite_edges_end(); ++it) { |
| | file << vertex_indices.at(it->first->vertex(it->second)) << " " |
| | << vertex_indices.at(it->first->vertex(it->third)) << std::endl; |
| | } |
| |
|
| | for (auto it = triangulation.finite_facets_begin(); |
| | it != triangulation.finite_facets_end(); ++it) { |
| | file << "3 " |
| | << vertex_indices.at(it->first->vertex( |
| | triangulation.vertex_triple_index(it->second, 0))) |
| | << " " |
| | << vertex_indices.at(it->first->vertex( |
| | triangulation.vertex_triple_index(it->second, 1))) |
| | << " " |
| | << vertex_indices.at(it->first->vertex( |
| | triangulation.vertex_triple_index(it->second, 2))) |
| | << std::endl; |
| | } |
| | } |
| |
|
| | struct DelaunayCellData { |
| | DelaunayCellData() : DelaunayCellData(-1) {} |
| | DelaunayCellData(const int index) |
| | : index(index), |
| | source_weight(0), |
| | sink_weight(0), |
| | edge_weights({{0, 0, 0, 0}}) {} |
| | int index; |
| | float source_weight; |
| | float sink_weight; |
| | std::array<float, 4> edge_weights; |
| | }; |
| |
|
| | PlyMesh DelaunayMeshing(const DelaunayMeshingOptions& options, |
| | const DelaunayMeshingInput& input_data) { |
| | CHECK(options.Check()); |
| |
|
| | |
| | std::cout << "Triangulating points..." << std::endl; |
| | const auto triangulation = input_data.CreateSubSampledDelaunayTriangulation( |
| | options.max_proj_dist, options.max_depth_dist); |
| |
|
| | |
| | std::cout << "Initializing ray tracer..." << std::endl; |
| | const DelaunayTriangulationRayCaster ray_caster(triangulation); |
| |
|
| | |
| | const DelaunayMeshingEdgeWeightComputer edge_weight_computer( |
| | triangulation, options.visibility_sigma, options.distance_sigma_factor); |
| |
|
| | |
| |
|
| | std::cout << "Initializing graph optimization..." << std::endl; |
| |
|
| | typedef std::unordered_map<const Delaunay::Cell_handle, DelaunayCellData> |
| | CellGraphData; |
| |
|
| | CellGraphData cell_graph_data; |
| | cell_graph_data.reserve(triangulation.number_of_cells()); |
| | for (auto it = triangulation.all_cells_begin(); |
| | it != triangulation.all_cells_end(); ++it) { |
| | cell_graph_data.emplace(it, DelaunayCellData(cell_graph_data.size())); |
| | } |
| |
|
| | |
| | const int num_threads = GetEffectiveNumThreads(options.num_threads); |
| | ThreadPool thread_pool(num_threads); |
| | JobQueue<CellGraphData> result_queue(num_threads); |
| |
|
| | |
| | auto IntegreateImage = [&](const size_t image_idx) { |
| | |
| | CellGraphData image_cell_graph_data; |
| |
|
| | |
| | const auto& image = input_data.images[image_idx]; |
| | const K::Point_3 image_position = EigenToCGAL(image.proj_center); |
| |
|
| | |
| | std::vector<DelaunayTriangulationRayCaster::Intersection> intersections; |
| |
|
| | |
| | for (const auto& point_idx : image.point_idxs) { |
| | const auto& point = input_data.points[point_idx]; |
| |
|
| | |
| | const double alpha = edge_weight_computer.ComputeVisibilityProb( |
| | point.num_visible_images * point.num_visible_images); |
| |
|
| | const K::Point_3 point_position = EigenToCGAL(point.position); |
| | const K::Ray_3 viewing_ray = K::Ray_3(image_position, point_position); |
| | const K::Vector_3 viewing_direction = point_position - image_position; |
| | const K::Vector_3 viewing_direction_normalized = |
| | viewing_direction / std::sqrt(viewing_direction.squared_length()); |
| | const K::Vector_3 viewing_direction_epsilon = |
| | 0.001 * edge_weight_computer.DistanceSigma() * |
| | viewing_direction_normalized; |
| |
|
| | |
| | ray_caster.CastRaySegment( |
| | K::Segment_3(image_position, |
| | point_position - viewing_direction_epsilon), |
| | &intersections); |
| |
|
| | |
| | if (!intersections.empty()) { |
| | image_cell_graph_data[intersections.front().facet.first] |
| | .source_weight += alpha; |
| | } |
| |
|
| | |
| | for (const auto& intersection : intersections) { |
| | image_cell_graph_data[intersection.facet.first] |
| | .edge_weights[intersection.facet.second] += |
| | alpha * edge_weight_computer.ComputeDistanceProb( |
| | intersection.target_distance_squared); |
| | } |
| |
|
| | |
| | |
| |
|
| | { |
| | |
| | |
| | |
| |
|
| | const Delaunay::Cell_handle behind_point_cell = |
| | triangulation.locate(point_position + viewing_direction_epsilon); |
| |
|
| | int behind_neighbor_idx = -1; |
| | double behind_distance_squared = 0.0; |
| | for (int neighbor_idx = 0; neighbor_idx < 4; ++neighbor_idx) { |
| | const K::Triangle_3 triangle = |
| | triangulation.triangle(behind_point_cell, neighbor_idx); |
| |
|
| | K::Point_3 inter_point; |
| | if (CGAL::assign(inter_point, |
| | CGAL::intersection(viewing_ray, triangle))) { |
| | const double distance_squared = |
| | (inter_point - point_position).squared_length(); |
| | if (distance_squared > behind_distance_squared) { |
| | behind_distance_squared = distance_squared; |
| | behind_neighbor_idx = neighbor_idx; |
| | } |
| | } |
| | } |
| |
|
| | if (behind_neighbor_idx >= 0) { |
| | image_cell_graph_data[behind_point_cell] |
| | .edge_weights[behind_neighbor_idx] += |
| | alpha * |
| | edge_weight_computer.ComputeDistanceProb(behind_distance_squared); |
| |
|
| | const auto& inside_cell = |
| | behind_point_cell->neighbor(behind_neighbor_idx); |
| | image_cell_graph_data[inside_cell].sink_weight += alpha; |
| | } |
| | } |
| | } |
| |
|
| | CHECK(result_queue.Push(std::move(image_cell_graph_data))); |
| | }; |
| |
|
| | |
| | size_t image_idx = 0; |
| | const size_t init_num_tasks = |
| | std::min(input_data.images.size(), 2 * thread_pool.NumThreads()); |
| | for (; image_idx < init_num_tasks; ++image_idx) { |
| | thread_pool.AddTask(IntegreateImage, image_idx); |
| | } |
| |
|
| | |
| | |
| | for (size_t i = 0; i < input_data.images.size(); ++i) { |
| | Timer timer; |
| | timer.Start(); |
| |
|
| | std::cout << StringPrintf("Integrating image [%d/%d]", i + 1, |
| | input_data.images.size()) |
| | << std::flush; |
| |
|
| | |
| | if (image_idx < input_data.images.size()) { |
| | thread_pool.AddTask(IntegreateImage, image_idx); |
| | image_idx += 1; |
| | } |
| |
|
| | |
| | auto result = result_queue.Pop(); |
| | CHECK(result.IsValid()); |
| |
|
| | |
| | const auto& image_cell_graph_data = result.Data(); |
| | for (const auto& image_cell_data : image_cell_graph_data) { |
| | auto& cell_data = cell_graph_data.at(image_cell_data.first); |
| | cell_data.sink_weight += image_cell_data.second.sink_weight; |
| | cell_data.source_weight += image_cell_data.second.source_weight; |
| | for (size_t j = 0; j < cell_data.edge_weights.size(); ++j) { |
| | cell_data.edge_weights[j] += image_cell_data.second.edge_weights[j]; |
| | } |
| | } |
| |
|
| | std::cout << StringPrintf(" in %.3fs", timer.ElapsedSeconds()) << std::endl; |
| | } |
| |
|
| | |
| |
|
| | std::cout << "Setting up optimization..." << std::endl; |
| |
|
| | |
| | |
| | MinSTGraphCut<size_t, float> graph_cut(cell_graph_data.size()); |
| |
|
| | |
| | for (auto& cell_data : cell_graph_data) { |
| | graph_cut.AddNode(cell_data.second.index, cell_data.second.source_weight, |
| | cell_data.second.sink_weight); |
| |
|
| | |
| | for (int i = 0; i < 4; ++i) { |
| | |
| | const Delaunay::Facet facet = std::make_pair(cell_data.first, i); |
| |
|
| | |
| | const Delaunay::Facet mirror_facet = triangulation.mirror_facet(facet); |
| | const auto& mirror_cell_data = cell_graph_data.at(mirror_facet.first); |
| |
|
| | |
| | if (cell_data.second.index < mirror_cell_data.index) { |
| | continue; |
| | } |
| |
|
| | |
| | |
| | |
| | const double edge_shape_weight = |
| | options.quality_regularization * |
| | (1.0 - |
| | std::min(ComputeCosFacetCellAngle(triangulation, facet), |
| | ComputeCosFacetCellAngle(triangulation, mirror_facet))); |
| |
|
| | const float forward_edge_weight = |
| | cell_data.second.edge_weights[facet.second] + edge_shape_weight; |
| | const float backward_edge_weight = |
| | mirror_cell_data.edge_weights[mirror_facet.second] + |
| | edge_shape_weight; |
| |
|
| | graph_cut.AddEdge(cell_data.second.index, mirror_cell_data.index, |
| | forward_edge_weight, backward_edge_weight); |
| | } |
| | } |
| |
|
| | |
| |
|
| | std::cout << "Running graph-cut optimization..." << std::endl; |
| | graph_cut.Compute(); |
| |
|
| | std::cout << "Extracting surface as min-cut..." << std::endl; |
| |
|
| | std::unordered_set<Delaunay::Vertex_handle> surface_vertices; |
| | std::vector<Delaunay::Facet> surface_facets; |
| | std::vector<float> surface_facet_side_lengths; |
| |
|
| | for (auto it = triangulation.finite_facets_begin(); |
| | it != triangulation.finite_facets_end(); ++it) { |
| | const auto& cell_data = cell_graph_data.at(it->first); |
| | const auto& mirror_cell_data = |
| | cell_graph_data.at(it->first->neighbor(it->second)); |
| |
|
| | |
| | const bool cell_is_source = graph_cut.IsConnectedToSource(cell_data.index); |
| | const bool mirror_cell_is_source = |
| | graph_cut.IsConnectedToSource(mirror_cell_data.index); |
| |
|
| | |
| | |
| | if (cell_is_source == mirror_cell_is_source) { |
| | continue; |
| | } |
| |
|
| | |
| | for (int i = 0; i < 3; ++i) { |
| | const auto& vertex = |
| | it->first->vertex(triangulation.vertex_triple_index(it->second, i)); |
| | surface_vertices.insert(vertex); |
| | } |
| |
|
| | |
| | const K::Triangle_3 triangle = triangulation.triangle(*it); |
| | const float max_squared_side_length = |
| | std::max({(triangle[0] - triangle[1]).squared_length(), |
| | (triangle[0] - triangle[2]).squared_length(), |
| | (triangle[1] - triangle[2]).squared_length()}); |
| | surface_facet_side_lengths.push_back(std::sqrt(max_squared_side_length)); |
| |
|
| | |
| | if (cell_is_source) { |
| | surface_facets.push_back(*it); |
| | } else { |
| | surface_facets.push_back(triangulation.mirror_facet(*it)); |
| | } |
| | } |
| |
|
| | std::cout << "Creating surface mesh model..." << std::endl; |
| |
|
| | PlyMesh mesh; |
| |
|
| | std::unordered_map<const Delaunay::Vertex_handle, size_t> |
| | surface_vertex_indices; |
| | surface_vertex_indices.reserve(surface_vertices.size()); |
| | mesh.vertices.reserve(surface_vertices.size()); |
| | for (const auto& vertex : surface_vertices) { |
| | mesh.vertices.emplace_back(vertex->point().x(), vertex->point().y(), |
| | vertex->point().z()); |
| | surface_vertex_indices.emplace(vertex, surface_vertex_indices.size()); |
| | } |
| |
|
| | const float max_facet_side_length = |
| | options.max_side_length_factor * |
| | Percentile(surface_facet_side_lengths, |
| | options.max_side_length_percentile); |
| |
|
| | mesh.faces.reserve(surface_facets.size()); |
| |
|
| | for (size_t i = 0; i < surface_facets.size(); ++i) { |
| | |
| | |
| | if (surface_facet_side_lengths[i] > max_facet_side_length) { |
| | continue; |
| | } |
| |
|
| | const auto& facet = surface_facets[i]; |
| | mesh.faces.emplace_back( |
| | surface_vertex_indices.at(facet.first->vertex( |
| | triangulation.vertex_triple_index(facet.second, 0))), |
| | surface_vertex_indices.at(facet.first->vertex( |
| | triangulation.vertex_triple_index(facet.second, 1))), |
| | surface_vertex_indices.at(facet.first->vertex( |
| | triangulation.vertex_triple_index(facet.second, 2)))); |
| | } |
| |
|
| | return mesh; |
| | } |
| |
|
| | void SparseDelaunayMeshing(const DelaunayMeshingOptions& options, |
| | const std::string& input_path, |
| | const std::string& output_path) { |
| | Timer timer; |
| | timer.Start(); |
| |
|
| | DelaunayMeshingInput input_data; |
| | input_data.ReadSparseReconstruction(input_path); |
| |
|
| | const auto mesh = DelaunayMeshing(options, input_data); |
| |
|
| | std::cout << "Writing surface mesh..." << std::endl; |
| | WriteBinaryPlyMesh(output_path, mesh); |
| |
|
| | timer.PrintSeconds(); |
| | } |
| |
|
| | void DenseDelaunayMeshing(const DelaunayMeshingOptions& options, |
| | const std::string& input_path, |
| | const std::string& output_path) { |
| | Timer timer; |
| | timer.Start(); |
| |
|
| | DelaunayMeshingInput input_data; |
| | input_data.ReadDenseReconstruction(input_path); |
| |
|
| | const auto mesh = DelaunayMeshing(options, input_data); |
| |
|
| | std::cout << "Writing surface mesh..." << std::endl; |
| | WriteBinaryPlyMesh(output_path, mesh); |
| |
|
| | timer.PrintSeconds(); |
| | } |
| |
|
| | #endif |
| |
|
| | } |
| | } |
| |
|