diff --git a/3rdParty/mapmap b/3rdParty/mapmap new file mode 160000 index 0000000..70d3542 --- /dev/null +++ b/3rdParty/mapmap @@ -0,0 +1 @@ +Subproject commit 70d35426b269f4371bd68d9197c6a843cb0da2b0 diff --git a/3rdParty/rayint b/3rdParty/rayint new file mode 160000 index 0000000..d5a8126 --- /dev/null +++ b/3rdParty/rayint @@ -0,0 +1 @@ +Subproject commit d5a8126aebb98c68b2b98bea05fcc8cebb6a2fb9 diff --git a/cmake/FindEigen.cmake b/cmake/FindEigen.cmake new file mode 100644 index 0000000..07d7168 --- /dev/null +++ b/cmake/FindEigen.cmake @@ -0,0 +1,46 @@ +############################################################################### +# Find Eigen3 +# +# This sets the following variables: +# EIGEN_FOUND - True if Eigen was found. +# EIGEN_INCLUDE_DIRS - Directories containing the Eigen include files. +# EIGEN_DEFINITIONS - Compiler flags for Eigen. +# EIGEN_VERSION - Package version + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr /usr/local) +endif(CMAKE_SYSTEM_NAME STREQUAL Linux) +if(APPLE) + list(APPEND CMAKE_INCLUDE_PATH /opt/local) + set(CMAKE_FIND_FRAMEWORK NEVER) +endif() + +find_path(EIGEN_INCLUDE_DIR Eigen/Core + HINTS ${PC_EIGEN_INCLUDEDIR} ${PC_EIGEN_INCLUDE_DIRS} "${EIGEN_ROOT}" "$ENV{EIGEN_ROOT}" + PATHS "$ENV{PROGRAMFILES}/Eigen" "$ENV{PROGRAMW6432}/Eigen" + "$ENV{PROGRAMFILES}/Eigen 3.0.0" "$ENV{PROGRAMW6432}/Eigen 3.0.0" + PATH_SUFFIXES eigen3 include/eigen3 include) + +if(EIGEN_INCLUDE_DIR) + file(READ "${EIGEN_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen_world_version_match "${_eigen_version_header}") + set(EIGEN_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen_major_version_match "${_eigen_version_header}") + set(EIGEN_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen_minor_version_match "${_eigen_version_header}") + set(EIGEN_MINOR_VERSION "${CMAKE_MATCH_1}") + set(EIGEN_VERSION ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}) +endif(EIGEN_INCLUDE_DIR) + +set(EIGEN_INCLUDE_DIRS ${EIGEN_INCLUDE_DIR}) +set(CMAKE_FIND_FRAMEWORK) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Eigen DEFAULT_MSG EIGEN_INCLUDE_DIR) + +mark_as_advanced(EIGEN_INCLUDE_DIR) + +if(EIGEN_FOUND) + message(STATUS "Eigen found (include: ${EIGEN_INCLUDE_DIRS}, version: ${EIGEN_VERSION})") +endif(EIGEN_FOUND) diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake new file mode 100644 index 0000000..a1131aa --- /dev/null +++ b/cmake/FindTBB.cmake @@ -0,0 +1,295 @@ +# The MIT License (MIT) +# +# Copyright (c) 2015 Justus Calvin +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# +# FindTBB +# ------- +# +# Find TBB include directories and libraries. +# +# Usage: +# +# find_package(TBB [major[.minor]] [EXACT] +# [QUIET] [REQUIRED] +# [[COMPONENTS] [components...]] +# [OPTIONAL_COMPONENTS components...]) +# +# where the allowed components are tbbmalloc and tbb_preview. Users may modify +# the behavior of this module with the following variables: +# +# * TBB_ROOT_DIR - The base directory the of TBB installation. +# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. +# * TBB_LIBRARY - The directory that contains the TBB library files. +# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. +# These libraries, if specified, override the +# corresponding library search results, where +# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, +# tbb_preview, or tbb_preview_debug. +# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will +# be used instead of the release version. +# +# Users may modify the behavior of this module with the following environment +# variables: +# +# * TBB_INSTALL_DIR +# * TBBROOT +# * LIBRARY_PATH +# +# This module will set the following variables: +# +# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or +# don’t want to use TBB. +# * TBB__FOUND - If False, optional part of TBB sytem is +# not available. +# * TBB_VERSION - The full version string +# * TBB_VERSION_MAJOR - The major version +# * TBB_VERSION_MINOR - The minor version +# * TBB_INTERFACE_VERSION - The interface version number defined in +# tbb/tbb_stddef.h. +# * TBB__LIBRARY_RELEASE - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# * TBB__LIBRARY_DEGUG - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# +# The following varibles should be used to build and link with TBB: +# +# * TBB_INCLUDE_DIRS - The include directory for TBB. +# * TBB_LIBRARIES - The libraries to link against to use TBB. +# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. +# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. +# * TBB_DEFINITIONS - Definitions to use when compiling code that uses +# TBB. +# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that +# uses TBB. +# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that +# uses TBB. +# +# This module will also create the "tbb" target that may be used when building +# executables and libraries. + +include(FindPackageHandleStandardArgs) + +if(NOT TBB_FOUND) + + ################################## + # Check the build type + ################################## + + if(NOT DEFINED TBB_USE_DEBUG_BUILD) + if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug|RelWithDebInfo|RELWITHDEBINFO|relwithdebinfo)") + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + elseif(TBB_USE_DEBUG_BUILD) + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + + ################################## + # Set the TBB search directories + ################################## + + # Define search paths based on user input and environment variables + set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) + + # Define the search directories based on the current platform + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" + "C:/Program Files (x86)/Intel/TBB") + + # Set the target architecture + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(TBB_ARCHITECTURE "intel64") + else() + set(TBB_ARCHITECTURE "ia32") + endif() + + # Set the TBB search library path search suffix based on the version of VC + if(WINDOWS_STORE) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") + elseif(MSVC14) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") + elseif(MSVC12) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") + elseif(MSVC11) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") + elseif(MSVC10) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") + endif() + + # Add the library path search suffix for the VC independent version of TBB + list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # OS X + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check to see which C++ library is being used by the compiler. + if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) + # The default C++ library on OS X 10.9 and later is libc++ + set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") + else() + set(TBB_LIB_PATH_SUFFIX "lib") + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Linux + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check compiler version to see the suffix should be /gcc4.1 or + # /gcc4.1. For now, assume that the compiler is more recent than + # gcc 4.4.x or later. + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") + endif() + endif() + + ################################## + # Find the TBB include dir + ################################## + + find_path(TBB_INCLUDE_DIRS tbb/tbb.h + HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} + PATH_SUFFIXES include) + + ################################## + # Set version strings + ################################## + + if(TBB_INCLUDE_DIRS) + file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) + string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" + TBB_VERSION_MAJOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" + TBB_VERSION_MINOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" + TBB_INTERFACE_VERSION "${_tbb_version_file}") + set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") + endif() + + ################################## + # Find TBB components + ################################## + + if(TBB_VERSION VERSION_LESS 4.3) + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) + else() + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) + endif() + + # Find each component + foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) + if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") + + # Search for the libraries + find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp} + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}_debug + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + if(TBB_${_comp}_LIBRARY_DEBUG) + list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") + endif() + if(TBB_${_comp}_LIBRARY_RELEASE) + list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") + endif() + if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") + endif() + + if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") + set(TBB_${_comp}_FOUND TRUE) + else() + set(TBB_${_comp}_FOUND FALSE) + endif() + + # Mark internal variables as advanced + mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) + mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) + mark_as_advanced(TBB_${_comp}_LIBRARY) + + endif() + endforeach() + + set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") + + ################################## + # Set compile flags + ################################## + + set(TBB_DEFINITIONS_RELEASE "") + set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") + + find_package_handle_standard_args(TBB + REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES + HANDLE_COMPONENTS + VERSION_VAR TBB_VERSION) + + ################################## + # Create targets + ################################## + + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + add_library(tbb SHARED IMPORTED) + set_target_properties(tbb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} + IMPORTED_LOCATION ${TBB_LIBRARIES}) + if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" + IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} + IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} + ) + elseif(TBB_LIBRARIES_RELEASE) + set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) + else() + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" + IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} + ) + endif() + endif() + + mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) + + unset(TBB_ARCHITECTURE) + unset(TBB_BUILD_TYPE) + unset(TBB_LIB_PATH_SUFFIX) + unset(TBB_DEFAULT_SEARCH_DIR) + +endif() diff --git a/examples/data/sequence_scene/surface_mesh_clean.ply b/examples/data/sequence_scene/surface_mesh_clean.ply new file mode 100644 index 0000000..9142b6b Binary files /dev/null and b/examples/data/sequence_scene/surface_mesh_clean.ply differ diff --git a/examples/data/sequence_scene/textured.conf b/examples/data/sequence_scene/textured.conf new file mode 100644 index 0000000..488664c --- /dev/null +++ b/examples/data/sequence_scene/textured.conf @@ -0,0 +1,11 @@ +Input scene: ./examples/data/sequence_scene::undistorted +Input mesh: ./examples/data/sequence_scene/surface_mesh_clean.ply +Output prefix: ./examples/data/sequence_scene/textured +Datacost file: +Labeling file: +Data term: gmi +Smoothness term: potts +Outlier removal method: none +Tone mapping: none +Apply global seam leveling: True +Apply local seam leveling: True diff --git a/examples/data/sequence_scene/textured_data_costs.spt b/examples/data/sequence_scene/textured_data_costs.spt new file mode 100644 index 0000000..677c3f3 Binary files /dev/null and b/examples/data/sequence_scene/textured_data_costs.spt differ diff --git a/texturing/CMakeLists.txt b/texturing/CMakeLists.txt new file mode 100644 index 0000000..dbfa819 --- /dev/null +++ b/texturing/CMakeLists.txt @@ -0,0 +1,71 @@ +project(texturing) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "-fPIC") + +include_directories(..) +include_directories(../3rdParty/rayint/libs) +include_directories(../3rdParty/mapmap/mapmap) +include_directories(../3rdParty/mapmap) + +set(HEADERS + defines.h + debug.h + util.h + timer.h + texturing.h + histogram.h + progress_counter.h + material_lib.h + obj_model.h + poisson_blending.h + rect.h + rectangular_bin.h + seam_leveling.h + settings.h + sparse_table.h + texture_atlas.h + texture_patch.h + texture_view.h + tri.h + uni_graph.h + timer.h + ) + +set(SOURCE_FILES + build_adjacency_graph.cpp + build_obj_model.cpp + calculate_data_costs.cpp + generate_debug_embeddings.cpp + generate_texture_atlases.cpp + generate_texture_patches.cpp + generate_texture_views.cpp + global_seam_leveling.cpp + histogram.cpp + local_seam_leveling.cpp + material_lib.cpp + obj_model.cpp + poisson_blending.cpp + prepare_mesh.cpp + prepare_mesh.cpp + rectangular_bin.cpp + seam_leveling.cpp + texture_atlas.cpp + texture_patch.cpp + texture_view.cpp + tri.cpp + uni_graph.cpp + view_selection.cpp + timer.cpp + ) +add_library(texturing ${HEADERS} ${SOURCE_FILES}) +#target_link_libraries(sfm core util features) + +#file (GLOB HEADERS "*.h") +#file (GLOB SOURCES "[^_]*.cpp") +# +#set(LIBRARY tex) +#add_library(${LIBRARY} STATIC ${SOURCES}) +#set_property(TARGET ${LIBRARY} PROPERTY INTERPROCEDURAL_OPTIMIZATION True) +#add_dependencies(${LIBRARY} ext_mve ext_rayint ext_eigen ext_mapmap) +#target_link_libraries(${LIBRARY} ${TBB_LIBRARIES} -lmve -lmve_util ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${TIFF_LIBRARIES}) +#install(TARGETS ${LIBRARY} ARCHIVE DESTINATION lib) diff --git a/texturing/build_adjacency_graph.cpp b/texturing/build_adjacency_graph.cpp new file mode 100644 index 0000000..f00bc82 --- /dev/null +++ b/texturing/build_adjacency_graph.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include "texturing.h" + +#include "progress_counter.h" + +TEX_NAMESPACE_BEGIN + +void +build_adjacency_graph(core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, UniGraph * graph) { + + core::TriangleMesh::FaceList const & faces = mesh->get_faces(); + std::size_t const num_faces = faces.size() / 3; + + ProgressCounter face_counter("\tAdding edges", num_faces); + for (std::size_t i = 0; i < faces.size(); i += 3) { + face_counter.progress(); + + std::size_t v1 = faces[i]; + std::size_t v2 = faces[i + 1]; + std::size_t v3 = faces[i + 2]; + + std::vector adj_faces; + mesh_info.get_faces_for_edge(v1, v2, &adj_faces); + mesh_info.get_faces_for_edge(v2, v3, &adj_faces); + mesh_info.get_faces_for_edge(v3, v1, &adj_faces); + + for (std::size_t j = 0; j < adj_faces.size(); ++j) { + /* Face id vs. face position. */ + std::size_t face = i / 3; + std::size_t adj_face = adj_faces[j]; + + /* Avoid self referencing. */ + if (face != adj_face) { + /* Edge not already in graph? */ + if (!graph->has_edge(face, adj_face)){ + graph->add_edge(face, adj_face); + } + } + } + face_counter.inc(); + } + + std::cout << "\t" << graph->num_edges() << " total edges." << std::endl; +} + +TEX_NAMESPACE_END diff --git a/texturing/build_obj_model.cpp b/texturing/build_obj_model.cpp new file mode 100644 index 0000000..b961de1 --- /dev/null +++ b/texturing/build_obj_model.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "defines.h" +#include "texture_atlas.h" +#include "obj_model.h" + +TEX_NAMESPACE_BEGIN + +void +build_model(core::TriangleMesh::ConstPtr mesh, + std::vector const & texture_atlases, ObjModel * obj_model) { + + core::TriangleMesh::VertexList const & mesh_vertices = mesh->get_vertices(); + core::TriangleMesh::NormalList const & mesh_normals = mesh->get_vertex_normals(); + core::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); + + ObjModel::Vertices & vertices = obj_model->get_vertices(); + vertices.insert(vertices.begin(), mesh_vertices.begin(), mesh_vertices.end()); + ObjModel::Normals & normals = obj_model->get_normals(); + normals.insert(normals.begin(), mesh_normals.begin(), mesh_normals.end()); + ObjModel::TexCoords & texcoords = obj_model->get_texcoords(); + + ObjModel::Groups & groups = obj_model->get_groups(); + MaterialLib & material_lib = obj_model->get_material_lib(); + + for (TextureAtlas::Ptr texture_atlas : texture_atlases) { + + Material material; + const std::size_t n = material_lib.size(); + material.name = std::string("material") + util::string::get_filled(n, 4); + material.diffuse_map = texture_atlas->get_image(); + material_lib.push_back(material); + + groups.push_back(ObjModel::Group()); + ObjModel::Group & group = groups.back(); + group.material_name = material.name; + + TextureAtlas::Faces const & atlas_faces = texture_atlas->get_faces(); + TextureAtlas::Texcoords const & atlas_texcoords = texture_atlas->get_texcoords(); + TextureAtlas::TexcoordIds const & atlas_texcoord_ids = texture_atlas->get_texcoord_ids(); + + std::size_t texcoord_id_offset = texcoords.size(); + + texcoords.insert(texcoords.end(), atlas_texcoords.begin(), + atlas_texcoords.end()); + + for (std::size_t i = 0; i < atlas_faces.size(); ++i) { + std::size_t mesh_face_pos = atlas_faces[i] * 3; + + std::size_t vertex_ids[] = { + mesh_faces[mesh_face_pos], + mesh_faces[mesh_face_pos + 1], + mesh_faces[mesh_face_pos + 2] + }; + std::size_t * normal_ids = vertex_ids; + + std::size_t texcoord_ids[] = { + texcoord_id_offset + atlas_texcoord_ids[i * 3], + texcoord_id_offset + atlas_texcoord_ids[i * 3 + 1], + texcoord_id_offset + atlas_texcoord_ids[i * 3 + 2] + }; + + group.faces.push_back(ObjModel::Face()); + ObjModel::Face & face = group.faces.back(); + std::copy(vertex_ids, vertex_ids + 3, face.vertex_ids); + std::copy(texcoord_ids, texcoord_ids + 3, face.texcoord_ids); + std::copy(normal_ids, normal_ids + 3, face.normal_ids); + } + } + //TODO remove unreferenced vertices/normals. +} + +TEX_NAMESPACE_END diff --git a/texturing/calculate_data_costs.cpp b/texturing/calculate_data_costs.cpp new file mode 100644 index 0000000..0e04a83 --- /dev/null +++ b/texturing/calculate_data_costs.cpp @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2015, Nils Moehrle, Michael Waechter + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include +#include +#include +#include + +#include "util.h" +#include "histogram.h" +#include "texturing.h" +#include "sparse_table.h" +#include "progress_counter.h" + +typedef acc::BVHTree BVHTree; + +TEX_NAMESPACE_BEGIN + +/** + * Dampens the quality of all views in which the face's projection + * has a much different color than in the majority of views. + * Returns whether the outlier removal was successfull. + * + * @param infos contains information about one face seen from several views + * @param settings runtime configuration. + */ +bool +photometric_outlier_detection(std::vector * infos, Settings const & settings) { + if (infos->size() == 0) return true; + + /* Configuration variables. */ + + double const gauss_rejection_threshold = 6e-3; + + /* If all covariances drop below this we stop outlier detection. */ + double const minimal_covariance = 5e-4; + + int const outlier_detection_iterations = 10; + int const minimal_num_inliers = 4; + + float outlier_removal_factor = std::numeric_limits::signaling_NaN(); + switch (settings.outlier_removal) { + case OUTLIER_REMOVAL_NONE: return true; + case OUTLIER_REMOVAL_GAUSS_CLAMPING: + outlier_removal_factor = 1.0f; + break; + case OUTLIER_REMOVAL_GAUSS_DAMPING: + outlier_removal_factor = 0.2f; + break; + } + + Eigen::MatrixX3d inliers(infos->size(), 3); + std::vector is_inlier(infos->size(), 1); + for (std::size_t row = 0; row < infos->size(); ++row) { + inliers.row(row) = mve_to_eigen(infos->at(row).mean_color).cast(); + } + + Eigen::RowVector3d var_mean; + Eigen::Matrix3d covariance; + Eigen::Matrix3d covariance_inv; + + for (int i = 0; i < outlier_detection_iterations; ++i) { + + if (inliers.rows() < minimal_num_inliers) { + return false; + } + + /* Calculate the inliers' mean color and color covariance. */ + var_mean = inliers.colwise().mean(); + Eigen::MatrixX3d centered = inliers.rowwise() - var_mean; + covariance = (centered.adjoint() * centered) / double(inliers.rows() - 1); + + /* If all covariances are very small we stop outlier detection + * and only keep the inliers (set quality of outliers to zero). */ + if (covariance.array().abs().maxCoeff() < minimal_covariance) { + for (std::size_t row = 0; row < infos->size(); ++row) { + if (!is_inlier[row]) infos->at(row).quality = 0.0f; + } + return true; + } + + /* Invert the covariance. FullPivLU is not the fastest way but + * it gives feedback about numerical stability during inversion. */ + Eigen::FullPivLU lu(covariance); + if (!lu.isInvertible()) { + return false; + } + covariance_inv = lu.inverse(); + + /* Compute new number of inliers (all views with a gauss value above a threshold). */ + for (std::size_t row = 0; row < infos->size(); ++row) { + Eigen::RowVector3d color = mve_to_eigen(infos->at(row).mean_color).cast(); + double gauss_value = multi_gauss_unnormalized(color, var_mean, covariance_inv); + is_inlier[row] = (gauss_value >= gauss_rejection_threshold ? 1 : 0); + } + /* Resize Eigen matrix accordingly and fill with new inliers. */ + inliers.resize(std::accumulate(is_inlier.begin(), is_inlier.end(), 0), Eigen::NoChange); + for (std::size_t row = 0, inlier_row = 0; row < infos->size(); ++row) { + if (is_inlier[row]) { + inliers.row(inlier_row++) = mve_to_eigen(infos->at(row).mean_color).cast(); + } + } + } + + covariance_inv *= outlier_removal_factor; + for (FaceProjectionInfo & info : *infos) { + Eigen::RowVector3d color = mve_to_eigen(info.mean_color).cast(); + double gauss_value = multi_gauss_unnormalized(color, var_mean, covariance_inv); + assert(0.0 <= gauss_value && gauss_value <= 1.0); + switch(settings.outlier_removal) { + case OUTLIER_REMOVAL_NONE: return true; + case OUTLIER_REMOVAL_GAUSS_DAMPING: + info.quality *= gauss_value; + break; + case OUTLIER_REMOVAL_GAUSS_CLAMPING: + if (gauss_value < gauss_rejection_threshold) info.quality = 0.0f; + break; + } + } + return true; +} + +void +calculate_face_projection_infos(core::TriangleMesh::ConstPtr mesh, + std::vector * texture_views, Settings const & settings, + FaceProjectionInfos * face_projection_infos) { + + std::vector const & faces = mesh->get_faces(); + std::vector const & vertices = mesh->get_vertices(); + core::TriangleMesh::NormalList const & face_normals = mesh->get_face_normals(); + + std::size_t const num_views = texture_views->size(); + + util::WallTimer timer; + std::cout << "\tBuilding BVH from " << faces.size() / 3 << " faces... " << std::flush; + BVHTree bvh_tree(faces, vertices); + std::cout << "done. (Took: " << timer.get_elapsed() << " ms)" << std::endl; + + ProgressCounter view_counter("\tCalculating face qualities", num_views); + #pragma omp parallel + { + std::vector > projected_face_view_infos; + + // for each view + #pragma omp for schedule(dynamic) + for (std::uint16_t j = 0; j < static_cast(num_views); ++j) { + view_counter.progress(); + + TextureView * texture_view = &texture_views->at(j); + texture_view->load_image(); + texture_view->generate_validity_mask(); + + if (settings.data_term == DATA_TERM_GMI) { + texture_view->generate_gradient_magnitude(); + texture_view->erode_validity_mask(); + } + + math::Vec3f const & view_pos = texture_view->get_pos(); + math::Vec3f const & viewing_direction = texture_view->get_viewing_direction(); + + // for each face + for (std::size_t i = 0; i < faces.size(); i += 3) { + std::size_t face_id = i / 3; + + math::Vec3f const & v1 = vertices[faces[i]]; + math::Vec3f const & v2 = vertices[faces[i + 1]]; + math::Vec3f const & v3 = vertices[faces[i + 2]]; + math::Vec3f const & face_normal = face_normals[face_id]; + math::Vec3f const face_center = (v1 + v2 + v3) / 3.0f; + + /* Check visibility and compute quality */ + math::Vec3f view_to_face_vec = (face_center - view_pos).normalized(); + math::Vec3f face_to_view_vec = (view_pos - face_center).normalized(); + + /* Backface and basic frustum culling */ + float viewing_angle = face_to_view_vec.dot(face_normal); + if (viewing_angle < 0.0f || viewing_direction.dot(view_to_face_vec) < 0.0f) + continue; + + if (std::acos(viewing_angle) > MATH_DEG2RAD(75.0f)) + continue; + + /* Projects into the valid part of the TextureView? */ + if (!texture_view->inside(v1, v2, v3)) + continue; + + if (settings.geometric_visibility_test) { + /* Viewing rays do not collide? */ + bool visible = true; + math::Vec3f const * samples[] = {&v1, &v2, &v3}; + // TODO: random monte carlo samples... + // collision detection + for (std::size_t k = 0; k < sizeof(samples) / sizeof(samples[0]); ++k) { + BVHTree::Ray ray; + ray.origin = *samples[k]; + ray.dir = view_pos - ray.origin; + ray.tmax = ray.dir.norm(); + ray.tmin = ray.tmax * 0.0001f; + ray.dir.normalize(); + + BVHTree::Hit hit; + if (bvh_tree.intersect(ray, &hit)) { + visible = false; + break; + } + } + if (!visible) continue; + } + + FaceProjectionInfo info = {j, 0.0f, math::Vec3f(0.0f, 0.0f, 0.0f)}; + + /* Calculate quality. */ + texture_view->get_face_info(v1, v2, v3, &info, settings); + + if (info.quality == 0.0) continue; + + /* Change color space. */ + core::image::color_rgb_to_ycbcr(*(info.mean_color)); + + std::pair pair(face_id, info); + projected_face_view_infos.push_back(pair); + } + + texture_view->release_image(); + texture_view->release_validity_mask(); + if (settings.data_term == DATA_TERM_GMI) { + texture_view->release_gradient_magnitude(); + } + view_counter.inc(); + } + + //std::sort(projected_face_view_infos.begin(), projected_face_view_infos.end()); + #pragma omp critical + { + for (std::size_t i = projected_face_view_infos.size(); 0 < i; --i) { + std::size_t face_id = projected_face_view_infos[i - 1].first; + FaceProjectionInfo const & info = projected_face_view_infos[i - 1].second; + face_projection_infos->at(face_id).push_back(info); + } + projected_face_view_infos.clear(); + } + } +} + +void +postprocess_face_infos(Settings const & settings, + FaceProjectionInfos * face_projection_infos, + DataCosts * data_costs) { + + ProgressCounter face_counter("\tPostprocessing face infos", + face_projection_infos->size()); + + // for each facet + #pragma omp parallel for schedule(dynamic) + for (std::size_t i = 0; i < face_projection_infos->size(); ++i) { + face_counter.progress(); + + // facet projections in multiple view + std::vector & infos = face_projection_infos->at(i); + if (settings.outlier_removal != OUTLIER_REMOVAL_NONE) { + photometric_outlier_detection(&infos, settings); + + infos.erase(std::remove_if(infos.begin(), infos.end(), + [](FaceProjectionInfo const & info) -> bool {return info.quality == 0.0f;}), + infos.end()); + } + std::sort(infos.begin(), infos.end()); + + face_counter.inc(); + } + + /* Determine the function for the normlization. */ + float max_quality = 0.0f; + for (std::size_t i = 0; i < face_projection_infos->size(); ++i) + for (FaceProjectionInfo const & info : face_projection_infos->at(i)) + max_quality = std::max(max_quality, info.quality); + + Histogram hist_qualities(0.0f, max_quality, 10000); + for (std::size_t i = 0; i < face_projection_infos->size(); ++i) + for (FaceProjectionInfo const & info : face_projection_infos->at(i)) + hist_qualities.add_value(info.quality); + + float percentile = hist_qualities.get_approx_percentile(0.995f); + + /* Calculate the costs. */ + for (std::uint32_t i = 0; i < face_projection_infos->size(); ++i) { + for (FaceProjectionInfo const & info : face_projection_infos->at(i)) { + + /* Clamp to percentile and normalize. */ + float normalized_quality = std::min(1.0f, info.quality / percentile); + float data_cost = (1.0f - normalized_quality); + data_costs->set_value(i, info.view_id, data_cost); + } + + /* Ensure that all memory is freeed. */ + face_projection_infos->at(i) = std::vector(); + } + + std::cout << "\tMaximum quality of a face within an image: " << max_quality << std::endl; + std::cout << "\tClamping qualities to " << percentile << " within normalization." << std::endl; +} + +void +calculate_data_costs(core::TriangleMesh::ConstPtr mesh, std::vector * texture_views, + Settings const & settings, DataCosts * data_costs) { + + std::size_t const num_faces = mesh->get_faces().size() / 3; + std::size_t const num_views = texture_views->size(); + + if (num_faces > std::numeric_limits::max()) + throw std::runtime_error("Exeeded maximal number of faces"); + if (num_views > std::numeric_limits::max()) + throw std::runtime_error("Exeeded maximal number of views"); + + FaceProjectionInfos face_projection_infos(num_faces); + calculate_face_projection_infos(mesh, texture_views, settings, &face_projection_infos); + postprocess_face_infos(settings, &face_projection_infos, data_costs); +} + +TEX_NAMESPACE_END diff --git a/texturing/debug.h b/texturing/debug.h new file mode 100644 index 0000000..ac860a4 --- /dev/null +++ b/texturing/debug.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_DEBUG_HEADER +#define TEX_DEBUG_HEADER + +#include +#include "texturing.h" + +TEX_NAMESPACE_BEGIN + +/** Replaces the encapsuled image of the texture_views with images containing the view id on a distinctive color. */ +void +generate_debug_embeddings(std::vector * texture_views); + +TEX_NAMESPACE_END + +#endif /* TEX_DEBUG_HEADER */ diff --git a/texturing/defines.h b/texturing/defines.h new file mode 100644 index 0000000..1706fe9 --- /dev/null +++ b/texturing/defines.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_DEFINES_HEADER +#define TEX_DEFINES_HEADER + +#define TEX_NAMESPACE_BEGIN namespace tex { +#define TEX_NAMESPACE_END } + +#endif /* TEX_DEFINES_HEADER */ diff --git a/texturing/generate_debug_embeddings.cpp b/texturing/generate_debug_embeddings.cpp new file mode 100644 index 0000000..9ab8705 --- /dev/null +++ b/texturing/generate_debug_embeddings.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "debug.h" + +TEX_NAMESPACE_BEGIN + +const bool font[] = { + 0,1,0, 0,1,0, 1,1,0, 1,1,0, 1,0,0, 1,1,1, 0,1,0, 1,1,1, 0,1,0 ,0,1,0, + 1,0,1, 1,1,0, 0,0,1, 0,0,1, 1,0,1, 1,0,0, 1,0,0, 0,0,1, 1,0,1, 1,0,1, + 1,0,1, 0,1,0, 0,1,0, 0,1,0, 1,1,1, 1,1,0, 1,1,0, 0,0,1, 0,1,0, 0,1,1, + 1,0,1, 0,1,0, 1,0,0, 0,0,1, 0,0,1, 0,0,1, 1,0,1, 0,1,0, 1,0,1, 0,0,1, + 0,1,0, 1,1,1, 1,1,1, 1,1,0, 0,0,1, 1,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0 +}; + +void print_number(core::ByteImage::Ptr image, int x, int y, int digit, math::Vec3uc color) { + assert(0 <= x && x < image->width() - 3); + assert(0 <= y && y < image->height() - 5); + assert(0 <= digit && digit <= 9); + + for(int i = 0; i < 3; ++i) { + for(int j = 0; j < 5; ++j) { + if(font[30 * j + digit * 3 + i]) { + for(int c = 0; c < image->channels(); ++c) { + image->at(x+i, y+j, c) = color[c]; + } + } + } + } +} + +void +generate_debug_colors(std::vector & colors) { + for (float s = 1.0f; s > 0.0f; s -= 0.4) { + for (float v = 1.0f; v > 0.0f; v -= 0.3) { + for (float h = 0.0f; h < 360.0f; h += 30.0f) { + float c = v * s; + float x = c * (1.0f - fabs(fmod(h / 60.0f, 2.0f) - 1.0f)); + float m = v - c; + + math::Vec4f color; + if (0 <= h && h < 60) + color = math::Vec4f(c, x, 0.0f, 1.0f); + if (60 <= h && h < 120) + color = math::Vec4f(x, c, 0.0f, 1.0f); + if (120 <= h && h < 180) + color = math::Vec4f(0.0f, c, x, 1.0f); + if (180 <= h && h < 240) + color = math::Vec4f(0.0f, x, c, 1.0f); + if (240 <= h && h < 300) + color = math::Vec4f(x, 0.0f, c, 1.0f); + if (300 <= h && h < 360) + color = math::Vec4f(c, 0.0f, x, 1.0f); + + color = color + math::Vec4f(m, m, m, 0.0f); + colors.push_back(color); + } + } + } +} + +void +generate_debug_embeddings(std::vector * texture_views) { + std::vector colors; + generate_debug_colors(colors); + + #pragma omp parallel for + for (std::size_t i = 0; i < texture_views->size(); ++i) { + math::Vec4f float_color = colors[i % colors.size()]; + + TextureView * texture_view = &(texture_views->at(i)); + + /* Determine font color depending on luminance of background. */ + float luminance = math::interpolate(float_color[0], float_color[1], float_color[2], 0.30f, 0.59f, 0.11f); + math::Vec3uc font_color = luminance > 0.5f ? math::Vec3uc(0,0,0) : math::Vec3uc(255,255,255); + + math::Vec3uc color; + color[0] = float_color[0] * 255.0f; + color[1] = float_color[1] * 255.0f; + color[2] = float_color[2] * 255.0f; + + core::ByteImage::Ptr image = core::ByteImage::create(texture_view->get_width(), texture_view->get_height(), 3); + image->fill_color(*color); + + for(int ox=0; ox < image->width() - 13; ox += 13) { + for(int oy=0; oy < image->height() - 6; oy += 6) { + std::size_t id = texture_view->get_id(); + int d0 = id / 100; + int d1 = (id % 100) / 10; + int d2 = id % 10; + + print_number(image, ox, oy, d0, font_color); + print_number(image, ox + 4, oy, d1, font_color); + print_number(image, ox + 8, oy, d2, font_color); + } + } + + texture_view->bind_image(image); + } +} + +TEX_NAMESPACE_END diff --git a/texturing/generate_texture_atlases.cpp b/texturing/generate_texture_atlases.cpp new file mode 100644 index 0000000..7361df1 --- /dev/null +++ b/texturing/generate_texture_atlases.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include +#include +#include + +#include +#include + +#include "defines.h" +#include "settings.h" +#include "histogram.h" +#include "texture_patch.h" +#include "texture_atlas.h" + +#define MAX_TEXTURE_SIZE (8 * 1024) +#define PREF_TEXTURE_SIZE (4 * 1024) +#define MIN_TEXTURE_SIZE (256) + +TEX_NAMESPACE_BEGIN + +/** + * Heuristic to calculate an appropriate texture atlas size. + * @warning asserts that no texture patch exceeds the dimensions + * of the maximal possible texture atlas size. + */ +unsigned int +calculate_texture_size(std::list const & texture_patches) { + unsigned int size = MAX_TEXTURE_SIZE; + + while (true) { + unsigned int total_area = 0; + unsigned int max_width = 0; + unsigned int max_height = 0; + unsigned int padding = size >> 7; + + for (TexturePatch::ConstPtr texture_patch : texture_patches) { + unsigned int width = texture_patch->get_width() + 2 * padding; + unsigned int height = texture_patch->get_height() + 2 * padding; + + max_width = std::max(max_width, width); + max_height = std::max(max_height, height); + + unsigned int area = width * height; + unsigned int waste = area - texture_patch->get_size(); + + /* Only consider patches where the information dominates padding. */ + if (static_cast(waste) / texture_patch->get_size() > 1.0) { + /* Since the patches are sorted by size we can assume that only + * few further patches will contribute to the size and break. */ + break; + } + + total_area += area; + } + + assert(max_width < MAX_TEXTURE_SIZE); + assert(max_height < MAX_TEXTURE_SIZE); + if (size > PREF_TEXTURE_SIZE && + max_width < PREF_TEXTURE_SIZE && + max_height < PREF_TEXTURE_SIZE && + total_area / (PREF_TEXTURE_SIZE * PREF_TEXTURE_SIZE) < 8) { + size = PREF_TEXTURE_SIZE; + continue; + } + + if (size <= MIN_TEXTURE_SIZE) { + return MIN_TEXTURE_SIZE; + } + + if (max_height < size / 2 && max_width < size / 2 && + static_cast(total_area) / (size * size) < 0.2) { + size = size / 2; + continue; + } + + return size; + } +} + +bool comp(TexturePatch::ConstPtr first, TexturePatch::ConstPtr second) { + return first->get_size() > second->get_size(); +} + +void +generate_texture_atlases(std::vector * orig_texture_patches, + Settings const & settings, std::vector * texture_atlases) { + + std::list texture_patches; + while (!orig_texture_patches->empty()) { + TexturePatch::Ptr texture_patch = orig_texture_patches->back(); + orig_texture_patches->pop_back(); + + if (settings.tone_mapping != TONE_MAPPING_NONE) { + core::image::gamma_correct(texture_patch->get_image(), 1.0f / 2.2f); + } + + texture_patches.push_back(texture_patch); + } + + std::cout << "\tSorting texture patches... " << std::flush; + /* Improve the bin-packing algorithm efficiency by sorting texture patches + * in descending order of size. */ + texture_patches.sort(comp); + std::cout << "done." << std::endl; + + std::size_t const total_num_patches = texture_patches.size(); + std::size_t remaining_patches = texture_patches.size(); + std::ofstream tty("/dev/tty", std::ios_base::out); + + #pragma omp parallel + { + #pragma omp single + { + + while (!texture_patches.empty()) { + unsigned int texture_size = calculate_texture_size(texture_patches); + + texture_atlases->push_back(TextureAtlas::create(texture_size)); + TextureAtlas::Ptr texture_atlas = texture_atlases->back(); + + /* Try to insert each of the texture patches into the texture atlas. */ + std::list::iterator it = texture_patches.begin(); + for (; it != texture_patches.end();) { + std::size_t done_patches = total_num_patches - remaining_patches; + int precent = static_cast(done_patches) + / total_num_patches * 100.0f; + if (total_num_patches > 100 + && done_patches % (total_num_patches / 100) == 0) { + + tty << "\r\tWorking on atlas " << texture_atlases->size() << " " + << precent << "%... " << std::flush; + } + + if (texture_atlas->insert(*it)) { + it = texture_patches.erase(it); + remaining_patches -= 1; + } else { + ++it; + } + } + + #pragma omp task + texture_atlas->finalize(); + } + + std::cout << "\r\tWorking on atlas " << texture_atlases->size() + << " 100%... done." << std::endl; + util::WallTimer timer; + std::cout << "\tFinalizing texture atlases... " << std::flush; + #pragma omp taskwait + std::cout << "done. (Took: " << timer.get_elapsed_sec() << "s)" << std::endl; + + /* End of single region */ + } + /* End of parallel region. */ + } +} + +TEX_NAMESPACE_END diff --git a/texturing/generate_texture_patches.cpp b/texturing/generate_texture_patches.cpp new file mode 100644 index 0000000..e7f00e4 --- /dev/null +++ b/texturing/generate_texture_patches.cpp @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include + +#include +#include +#include +#include + +#include "texturing.h" + +TEX_NAMESPACE_BEGIN + +#define MAX_HOLE_NUM_FACES 100 +#define MAX_HOLE_PATCH_SIZE 100 + +template +T clamp_nan_low(T const & v, T const & lo, T const & hi) { + return (v > lo) ? ((v < hi) ? v : hi) : lo; +} + +template +T clamp_nan_hi(T const & v, T const & lo, T const & hi) { + return (v < hi) ? ((v > lo) ? v : lo) : hi; +} + +template +T clamp(T const & v, T const & lo, T const & hi) { + return (v < lo) ? lo : ((v > hi) ? hi : v); +} + +void merge_vertex_projection_infos( + std::vector > * vertex_projection_infos) { + + /* Merge vertex infos within the same texture patch. */ + #pragma omp parallel for + for (std::size_t i = 0; i < vertex_projection_infos->size(); ++i) { + std::vector & infos = vertex_projection_infos->at(i); + + std::map info_map; + std::map::iterator it; + + for (VertexProjectionInfo const & info : infos) { + std::size_t texture_patch_id = info.texture_patch_id; + if((it = info_map.find(texture_patch_id)) == info_map.end()) { + info_map[texture_patch_id] = info; + } else { + it->second.faces.insert(it->second.faces.end(), + info.faces.begin(), info.faces.end()); + } + } + + infos.clear(); + infos.reserve(info_map.size()); + for (it = info_map.begin(); it != info_map.end(); ++it) { + infos.push_back(it->second); + } + } +} + +/** Struct representing a TexturePatch candidate + * - final texture patches are obtained by merging candiates. */ +struct TexturePatchCandidate { + Rect bounding_box; + TexturePatch::Ptr texture_patch; +}; + +/** Create a TexturePatchCandidate by calculating the faces' bounding box + * projected into the view, + * relative texture coordinates and extacting the texture views relevant part + */ +TexturePatchCandidate +generate_candidate(int label, TextureView const & texture_view, + std::vector const & faces, core::TriangleMesh::ConstPtr mesh, + Settings const & settings) { + + // get view image + core::ByteImage::Ptr view_image = texture_view.get_image(); + int min_x = view_image->width(), min_y = view_image->height(); + int max_x = 0, max_y = 0; + + // face list and vertices list + core::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + + // calculate the texcoords + std::vector texcoords; + for (std::size_t i = 0; i < faces.size(); ++i) { + for (std::size_t j = 0; j < 3; ++j) { + + math::Vec3f vertex = vertices[mesh_faces[faces[i] * 3 + j]]; + math::Vec2f pixel = texture_view.get_pixel_coords(vertex); + + texcoords.push_back(pixel); + + min_x = std::min(static_cast(std::floor(pixel[0])), min_x); + min_y = std::min(static_cast(std::floor(pixel[1])), min_y); + max_x = std::max(static_cast(std::ceil(pixel[0])), max_x); + max_y = std::max(static_cast(std::ceil(pixel[1])), max_y); + } + } + + /* Check for valid projections/erroneous labeling files. */ + assert(min_x >= 0); + assert(min_y >= 0); + assert(max_x < view_image->width()); + assert(max_y < view_image->height()); + + int width = max_x - min_x + 1; + int height = max_y - min_y + 1; + + /* Add border and adjust min accordingly. */ + width += 2 * texture_patch_border; + height += 2 * texture_patch_border; + min_x -= texture_patch_border; + min_y -= texture_patch_border; + + /* Calculate the relative texcoords. */ + math::Vec2f min(min_x, min_y); + for (std::size_t i = 0; i < texcoords.size(); ++i) { + texcoords[i] = texcoords[i] - min; + } + + core::ByteImage::Ptr byte_image; + byte_image = core::image::crop(view_image, width, height, min_x, min_y, *math::Vec3uc(255, 0, 255)); + core::FloatImage::Ptr image = core::image::byte_to_float_image(byte_image); + + if (!settings.tone_mapping == TONE_MAPPING_NONE) { + core::image::gamma_correct(image, 2.2f); + } + + TexturePatchCandidate texture_patch_candidate = + {Rect(min_x, min_y, max_x, max_y), + TexturePatch::create(label, faces, texcoords, image)}; + return texture_patch_candidate; +} + +bool fill_hole(std::vector const & hole, UniGraph const & graph, + core::TriangleMesh::ConstPtr mesh, core::MeshInfo const & mesh_info, + std::vector > * vertex_projection_infos, + std::vector * texture_patches) { + + core::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + + std::map > tmp; + for (std::size_t const face_id : hole) { + std::size_t const v0 = mesh_faces[face_id * 3 + 0]; + std::size_t const v1 = mesh_faces[face_id * 3 + 1]; + std::size_t const v2 = mesh_faces[face_id * 3 + 2]; + + tmp[v0].insert(face_id); + tmp[v1].insert(face_id); + tmp[v2].insert(face_id); + } + + std::size_t const num_vertices = tmp.size(); + /* Only fill small holes. */ + if (num_vertices > MAX_HOLE_NUM_FACES) return false; + + /* Calculate 2D parameterization using the technique from libremesh/patch2d, + * which was published as source code accompanying the following paper: + * + * Isotropic Surface Remeshing + * Simon Fuhrmann, Jens Ackermann, Thomas Kalbe, Michael Goesele + */ + + std::size_t seed = -1; + std::vector is_border(num_vertices, false); + std::vector > adj_verts_via_border(num_vertices); + /* Index structures to map from local <-> global vertex id. */ + std::map g2l; + std::vector l2g(num_vertices); + /* Index structure to determine column in matrix/vector. */ + std::vector idx(num_vertices); + + std::size_t num_border_vertices = 0; + + bool disk_topology = true; + std::map >::iterator it = tmp.begin(); + for (std::size_t j = 0; j < num_vertices; ++j, ++it) { + std::size_t vertex_id = it->first; + g2l[vertex_id] = j; + l2g[j] = vertex_id; + + /* Check topology in original mesh. */ + if (mesh_info[vertex_id].vclass != core::MeshInfo::VERTEX_CLASS_SIMPLE) { + /* Complex/Border vertex in original mesh */ + disk_topology = false; + break; + } + + /* Check new topology and determine if vertex is now at the border. */ + std::vector const & adj_faces = mesh_info[vertex_id].faces; + std::set const & adj_hole_faces = it->second; + std::vector > fan; + for (std::size_t k = 0; k < adj_faces.size(); ++k) { + std::size_t adj_face = adj_faces[k]; + if (graph.get_label(adj_faces[k]) == 0 && + adj_hole_faces.find(adj_face) != adj_hole_faces.end()) { + std::size_t curr = adj_faces[k]; + std::size_t next = adj_faces[(k + 1) % adj_faces.size()]; + std::pair pair(curr, next); + fan.push_back(pair); + } + } + + std::size_t gaps = 0; + for (std::size_t k = 0; k < fan.size(); k++) { + std::size_t curr = fan[k].first; + std::size_t next = fan[(k + 1) % fan.size()].first; + if (fan[k].second != next) { + ++gaps; + + for (std::size_t l = 0; l < 3; ++l) { + if(mesh_faces[curr * 3 + l] == vertex_id) { + std::size_t second = mesh_faces[curr * 3 + (l + 2) % 3]; + adj_verts_via_border[j].push_back(second); + } + if(mesh_faces[next * 3 + l] == vertex_id) { + std::size_t first = mesh_faces[next * 3 + (l + 1) % 3]; + adj_verts_via_border[j].push_back(first); + } + } + } + } + + is_border[j] = gaps == 1; + + /* Check if vertex is now complex. */ + if (gaps > 1) { + /* Complex vertex in hole */ + disk_topology = false; + break; + } + + if (is_border[j]) { + idx[j] = num_border_vertices++; + seed = vertex_id; + } else { + idx[j] = j - num_border_vertices; + } + } + tmp.clear(); + + /* No disk or genus zero topology */ + if (!disk_topology || num_border_vertices == 0) return false; + + std::vector border; border.reserve(num_border_vertices); + std::size_t prev = seed; + std::size_t curr = seed; + while (prev == seed || curr != seed) { + std::size_t next = std::numeric_limits::max(); + std::vector const & adj_verts = adj_verts_via_border[g2l[curr]]; + for (std::size_t adj_vert : adj_verts) { + assert(is_border[g2l[adj_vert]]); + if (adj_vert != prev && adj_vert != curr) { + next = adj_vert; + break; + } + } + if (next != std::numeric_limits::max()) { + prev = curr; + curr = next; + border.push_back(next); + } else { + /* No new border vertex */ + border.clear(); + break; + } + + /* Loop within border */ + if (border.size() > num_border_vertices) break; + } + + if (border.size() != num_border_vertices) return false; + + float total_length = 0.0f; + float total_projection_length = 0.0f; + for (std::size_t j = 0; j < border.size(); ++j) { + std::size_t vi0 = border[j]; + std::size_t vi1 = border[(j + 1) % border.size()]; + std::vector vpi0, vpi1; + #pragma omp critical (vpis) + { + vpi0 = vertex_projection_infos->at(vi0); + vpi1 = vertex_projection_infos->at(vi1); + } + + /* According to the previous checks (vertex class within the origial + * mesh and boundary) there has to be at least one projection + * of each border vertex in a common texture patch. */ + math::Vec2f vp0(NAN), vp1(NAN); + for (VertexProjectionInfo const & info0 : vpi0) { + for (VertexProjectionInfo const & info1 : vpi1) { + if (info0.texture_patch_id == info1.texture_patch_id) { + vp0 = info0.projection; + vp1 = info1.projection; + break; + } + } + } + assert(!std::isnan(vp0[0]) && !std::isnan(vp0[1])); + assert(!std::isnan(vp1[0]) && !std::isnan(vp1[1])); + + total_projection_length += (vp0 - vp1).norm(); + math::Vec3f const & v0 = vertices[vi0]; + math::Vec3f const & v1 = vertices[vi1]; + total_length += (v0 - v1).norm(); + } + float radius = total_projection_length / (2.0f * MATH_PI); + + if (total_length < std::numeric_limits::epsilon()) return false; + + std::vector projections(num_vertices); + { + float length = 0.0f; + for (std::size_t j = 0; j < border.size(); ++j) { + float angle = 2.0f * MATH_PI * (length / total_length); + projections[g2l[border[j]]] = math::Vec2f(std::cos(angle), std::sin(angle)); + math::Vec3f const & v0 = vertices[border[j]]; + math::Vec3f const & v1 = vertices[border[(j + 1) % border.size()]]; + length += (v0 - v1).norm(); + } + } + + typedef Eigen::Triplet Triplet; + std::vector coeff; + std::size_t matrix_size = num_vertices - border.size(); + + Eigen::VectorXf xx(matrix_size), xy(matrix_size); + + if (matrix_size != 0) { + Eigen::VectorXf bx(matrix_size); + Eigen::VectorXf by(matrix_size); + for (std::size_t j = 0; j < num_vertices; ++j) { + if (is_border[j]) continue; + + std::size_t const vertex_id = l2g[j]; + + /* Calculate "Mean Value Coordinates" as proposed by Michael S. Floater */ + std::map weights; + + std::vector const & adj_faces = mesh_info[vertex_id].faces; + for (std::size_t adj_face : adj_faces) { + std::size_t v0 = mesh_faces[adj_face * 3 + 0]; + std::size_t v1 = mesh_faces[adj_face * 3 + 1]; + std::size_t v2 = mesh_faces[adj_face * 3 + 2]; + if (v1 == vertex_id) std::swap(v1, v0); + if (v2 == vertex_id) std::swap(v2, v0); + + math::Vec3f v01 = vertices[v1] - vertices[v0]; + float v01n = v01.norm(); + math::Vec3f v02 = vertices[v2] - vertices[v0]; + float v02n = v02.norm(); + + /* Ensure numerical stability */ + if (v01n * v02n < std::numeric_limits::epsilon()) return false; + + float calpha = v01.dot(v02) / (v01n * v02n); + float alpha = std::acos(clamp(calpha, -1.0f, 1.0f)); + weights[g2l[v1]] += std::tan(alpha / 2.0f) / (v01n / 2.0f); + weights[g2l[v2]] += std::tan(alpha / 2.0f) / (v02n / 2.0f); + } + + std::map::iterator it; + float sum = 0.0f; + for (it = weights.begin(); it != weights.end(); ++it) + sum += it->second; + if (sum < std::numeric_limits::epsilon()) return false; + for (it = weights.begin(); it != weights.end(); ++it) + it->second /= sum; + + bx[idx[j]] = 0.0f; + by[idx[j]] = 0.0f; + for (it = weights.begin(); it != weights.end(); ++it) { + if (is_border[it->first]) { + std::size_t border_vertex_id = border[idx[it->first]]; + math::Vec2f projection = projections[g2l[border_vertex_id]]; + bx[idx[j]] += projection[0] * it->second; + by[idx[j]] += projection[1] * it->second; + } else { + coeff.push_back(Triplet(idx[j], idx[it->first], -it->second)); + } + } + } + + for (std::size_t j = 0; j < matrix_size; ++j) { + coeff.push_back(Triplet(j, j, 1.0f)); + } + + typedef Eigen::SparseMatrix SpMat; + SpMat A(matrix_size, matrix_size); + A.setFromTriplets(coeff.begin(), coeff.end()); + + Eigen::SparseLU solver; + solver.analyzePattern(A); + solver.factorize(A); + xx = solver.solve(bx); + xy = solver.solve(by); + } + + float const max_hole_patch_size = MAX_HOLE_PATCH_SIZE; + int image_size = std::min(std::floor(radius * 1.1f) * 2.0f, max_hole_patch_size); + /* Ensure a minimum scale of one */ + image_size += 2 * (1 + texture_patch_border); + int scale = image_size / 2 - texture_patch_border; + for (std::size_t j = 0, k = 0; j < num_vertices; ++j) { + if (is_border[j]) { + projections[j] = projections[j] * scale + image_size / 2; + } else { + projections[j] = math::Vec2f(xx[k], xy[k]) * scale + image_size / 2; + ++k; + } + } + + std::vector texcoords; texcoords.reserve(hole.size()); + for (std::size_t const face_id : hole) { + for (std::size_t j = 0; j < 3; ++j) { + std::size_t const vertex_id = mesh_faces[face_id * 3 + j]; + math::Vec2f const & projection = projections[g2l[vertex_id]]; + texcoords.push_back(projection); + } + } + core::FloatImage::Ptr image = core::FloatImage::create(image_size, image_size, 3); + //DEBUG image->fill_color(*math::Vec4uc(0, 255, 0, 255)); + TexturePatch::Ptr texture_patch = TexturePatch::create(0, hole, texcoords, image); + std::size_t texture_patch_id; + #pragma omp critical + { + texture_patches->push_back(texture_patch); + texture_patch_id = texture_patches->size() - 1; + } + + for (std::size_t j = 0; j < num_vertices; ++j) { + std::size_t const vertex_id = l2g[j]; + std::vector const & adj_faces = mesh_info[vertex_id].faces; + std::vector faces; faces.reserve(adj_faces.size()); + for (std::size_t adj_face : adj_faces) { + if (graph.get_label(adj_face) == 0) { + faces.push_back(adj_face); + } + } + VertexProjectionInfo info = {texture_patch_id, projections[j], faces}; + #pragma omp critical (vpis) + vertex_projection_infos->at(vertex_id).push_back(info); + } + + return true; +} + +void +generate_texture_patches(UniGraph const & graph, + core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, + std::vector * texture_views, + Settings const & settings, + std::vector > * vertex_projection_infos, + std::vector * texture_patches) +{ + + util::WallTimer timer; + + // mesh faces and vertices + core::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + vertex_projection_infos->resize(vertices.size()); + + std::size_t num_patches = 0; + + std::cout << "\tRunning... " << std::flush; + //#pragma omp parallel for schedule(dynamic) + // for each view + for (std::size_t i = 0; i < texture_views->size(); ++i) { + + std::vector > subgraphs; + int const label = i + 1; + graph.get_subgraphs(label, &subgraphs); + + TextureView * texture_view = &texture_views->at(i); + texture_view->load_image(); + + std::list candidates; + for (std::size_t j = 0; j < subgraphs.size(); ++j) { + candidates.push_back(generate_candidate(label, *texture_view, subgraphs[j], mesh, settings)); + } + texture_view->release_image(); + + /* Merge candidates which contain the same image content. */ + std::list::iterator it, sit; + for (it = candidates.begin(); it != candidates.end(); ++it) { + for (sit = candidates.begin(); sit != candidates.end();) { + Rect bounding_box = sit->bounding_box; + if (it != sit && bounding_box.is_inside(&it->bounding_box)) { + TexturePatch::Faces & faces = it->texture_patch->get_faces(); + TexturePatch::Faces & ofaces = sit->texture_patch->get_faces(); + faces.insert(faces.end(), ofaces.begin(), ofaces.end()); + + TexturePatch::Texcoords & texcoords = it->texture_patch->get_texcoords(); + TexturePatch::Texcoords & otexcoords = sit->texture_patch->get_texcoords(); + math::Vec2f offset; + offset[0] = sit->bounding_box.min_x - it->bounding_box.min_x; + offset[1] = sit->bounding_box.min_y - it->bounding_box.min_y; + for (std::size_t i = 0; i < otexcoords.size(); ++i) { + texcoords.push_back(otexcoords[i] + offset); + } + + sit = candidates.erase(sit); + } else { + ++sit; + } + } + } + + it = candidates.begin(); + for (; it != candidates.end(); ++it) { + std::size_t texture_patch_id; + + // #pragma omp critical + { + texture_patches->push_back(it->texture_patch); + texture_patch_id = num_patches++; + } + + std::vector const & faces = it->texture_patch->get_faces(); + std::vector const & texcoords = it->texture_patch->get_texcoords(); + for (std::size_t i = 0; i < faces.size(); ++i) { + std::size_t const face_id = faces[i]; + std::size_t const face_pos = face_id * 3; + for (std::size_t j = 0; j < 3; ++j) { + std::size_t const vertex_id = mesh_faces[face_pos + j]; + math::Vec2f const projection = texcoords[i * 3 + j]; + + VertexProjectionInfo info = {texture_patch_id, projection, {face_id}}; + + //#pragma omp critical + vertex_projection_infos->at(vertex_id).push_back(info); + } + } + } + } + + merge_vertex_projection_infos(vertex_projection_infos); + + { + std::vector unseen_faces; + std::vector > subgraphs; + graph.get_subgraphs(0, &subgraphs); + + //#pragma omp parallel for schedule(dynamic) + for (std::size_t i = 0; i < subgraphs.size(); ++i) { + std::vector const & subgraph = subgraphs[i]; + + bool success = false; + if (settings.hole_filling) { + success = fill_hole(subgraph, graph, mesh, mesh_info, + vertex_projection_infos, texture_patches); + } + + if (success) { + num_patches += 1; + } else { + if (settings.keep_unseen_faces) { + //#pragma omp critical + unseen_faces.insert(unseen_faces.end(), + subgraph.begin(), subgraph.end()); + } + } + } + + if (!unseen_faces.empty()) { + core::FloatImage::Ptr image = core::FloatImage::create(3, 3, 3); + std::vector texcoords; + for (std::size_t i = 0; i < unseen_faces.size(); ++i) { + math::Vec2f projections[] = {{2.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 2.0f}}; + texcoords.insert(texcoords.end(), &projections[0], &projections[3]); + } + TexturePatch::Ptr texture_patch + = TexturePatch::create(0, unseen_faces, texcoords, image); + texture_patches->push_back(texture_patch); + std::size_t texture_patch_id = texture_patches->size() - 1; + + for (std::size_t i = 0; i < unseen_faces.size(); ++i) { + std::size_t const face_id = unseen_faces[i]; + std::size_t const face_pos = face_id * 3; + for (std::size_t j = 0; j < 3; ++j) { + std::size_t const vertex_id = mesh_faces[face_pos + j]; + math::Vec2f const projection = texcoords[i * 3 + j]; + + VertexProjectionInfo info = {texture_patch_id, projection, {face_id}}; + + vertex_projection_infos->at(vertex_id).push_back(info); + } + } + } + } + + merge_vertex_projection_infos(vertex_projection_infos); + + std::cout << "done. (Took " << timer.get_elapsed_sec() << "s)" << std::endl; + std::cout << "\t" << num_patches << " texture patches." << std::endl; +} + +TEX_NAMESPACE_END diff --git a/texturing/generate_texture_views.cpp b/texturing/generate_texture_views.cpp new file mode 100644 index 0000000..105b803 --- /dev/null +++ b/texturing/generate_texture_views.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include +#include +#include +#include +#include + +#include "progress_counter.h" +#include "texturing.h" + +TEX_NAMESPACE_BEGIN + +void +from_core_scene(std::string const & scene_dir, std::string const & image_name, + std::vector * texture_views) { + + core::Scene::Ptr scene; + try { + scene = core::Scene::create(scene_dir); + } catch (std::exception& e) { + std::cerr << "Could not open scene: " << e.what() << std::endl; + std::exit(EXIT_FAILURE); + } + std::size_t num_views = scene->get_views().size(); + texture_views->reserve(num_views); + + ProgressCounter view_counter("\tLoading", num_views); + for (std::size_t i = 0; i < num_views; ++i) { + view_counter.progress(); + + core::View::Ptr view = scene->get_view_by_id(i); + if (view == NULL) { + view_counter.inc(); + continue; + } + + if (!view->has_image(image_name, core::IMAGE_TYPE_UINT8)) { + std::cout << "Warning: View " << view->get_name() << " has no byte image " + << image_name << std::endl; + continue; + } + + core::View::ImageProxy const * image_proxy = view->get_image_proxy(image_name); + + if (image_proxy->channels < 3) { + std::cerr << "Image " << image_name << " of view " << + view->get_name() << " is not a color image!" << std::endl; + exit(EXIT_FAILURE); + } + + texture_views->push_back( + TextureView(view->get_id(), view->get_camera(), util::fs::abspath( + util::fs::join_path(view->get_directory(), image_proxy->filename)))); + view_counter.inc(); + } +} + +void +from_images_and_camera_files(std::string const & path, + std::vector * texture_views, std::string const & tmp_dir) +{ + util::fs::Directory dir(path); + std::sort(dir.begin(), dir.end()); + std::vector files; + for (std::size_t i = 0; i < dir.size(); ++i) { + util::fs::File const & cam_file = dir[i]; + if (cam_file.is_dir) continue; + + std::string cam_file_ext = util::string::uppercase(util::string::right(cam_file.name, 4)); + if (cam_file_ext != ".CAM") continue; + + std::string prefix = util::string::left(cam_file.name, cam_file.name.size() - 4); + if (prefix.empty()) continue; + + /* Find corresponding image file. */ + int step = 1; + for (std::size_t j = i + 1; j < dir.size(); j += step) { + util::fs::File const & img_file = dir[j]; + + /* Since the files are sorted we can break - no more files with the same prefix exist. */ + if (util::string::left(img_file.name, prefix.size()) != prefix) { + if (step == 1) { + j = i; + step = -1; + continue; + } else { + break; + } + } + + /* Image file (based on extension)? */ + std::string img_file_ext = util::string::uppercase(util::string::right(img_file.name, 4)); + if (img_file_ext != ".PNG" && img_file_ext != ".JPG" && + img_file_ext != "TIFF" && img_file_ext != "JPEG") continue; + + files.push_back(cam_file.get_absolute_name()); + files.push_back(img_file.get_absolute_name()); + break; + } + } + + ProgressCounter view_counter("\tLoading", files.size() / 2); + #pragma omp parallel for + for (std::size_t i = 0; i < files.size(); i += 2) { + view_counter.progress(); + const std::string cam_file = files[i]; + const std::string img_file = files[i + 1]; + + /* Read CAM file. */ + std::ifstream infile(cam_file.c_str(), std::ios::binary); + if (!infile.good()) { + throw util::FileException(util::fs::basename(cam_file), std::strerror(errno)); + } + std::string cam_int_str, cam_ext_str; + std::getline(infile, cam_ext_str); + std::getline(infile, cam_int_str); + util::Tokenizer tok_ext, tok_int; + tok_ext.split(cam_ext_str); + tok_int.split(cam_int_str); + #pragma omp critical + if (tok_ext.size() != 12 || tok_int.size() < 1) { + std::cerr << "Invalid CAM file: " << util::fs::basename(cam_file) << std::endl; + std::exit(EXIT_FAILURE); + } + + /* Create cam_info and eventually undistort image. */ + core::CameraInfo cam_info; + cam_info.set_translation_from_string(tok_ext.concat(0, 3)); + cam_info.set_rotation_from_string(tok_ext.concat(3, 0)); + + std::stringstream ss(cam_int_str); + ss >> cam_info.flen; + if (ss.peek() && !ss.eof()) + ss >> cam_info.dist[0]; + if (ss.peek() && !ss.eof()) + ss >> cam_info.dist[1]; + if (ss.peek() && !ss.eof()) + ss >> cam_info.paspect; + if (ss.peek() && !ss.eof()) + ss >> cam_info.ppoint[0]; + if (ss.peek() && !ss.eof()) + ss >> cam_info.ppoint[1]; + + std::string image_file = util::fs::abspath(img_file); + if (cam_info.dist[0] != 0.0f) { + core::ByteImage::Ptr image = core::image::load_file(img_file); + if (cam_info.dist[1] != 0.0f) { + image = core::image::image_undistort_k2k4(image, + cam_info.flen, cam_info.dist[0], cam_info.dist[1]); + } else { + image = core::image::image_undistort_vsfm(image, + cam_info.flen, cam_info.dist[0]); + } + + image_file = util::fs::join_path( + tmp_dir, + util::fs::replace_extension(util::fs::basename(img_file), "png") + ); + core::image::save_png_file(image, image_file); + } + + #pragma omp critical + texture_views->push_back(TextureView(i / 2, cam_info, image_file)); + + view_counter.inc(); + } +} + +void +from_nvm_scene(std::string const & nvm_file, + std::vector * texture_views, std::string const & tmp_dir) +{ + std::vector nvm_cams; + core::Bundle::Ptr bundle = core::load_nvm_bundle(nvm_file, &nvm_cams); + core::Bundle::Cameras& cameras = bundle->get_cameras(); + + ProgressCounter view_counter("\tLoading", cameras.size()); + #pragma omp parallel for + for (std::size_t i = 0; i < cameras.size(); ++i) { + view_counter.progress(); + core::CameraInfo& core_cam = cameras[i]; + core::NVMCameraInfo const& nvm_cam = nvm_cams[i]; + + core::ByteImage::Ptr image = core::image::load_file(nvm_cam.filename); + + int const maxdim = std::max(image->width(), image->height()); + core_cam.flen = core_cam.flen / static_cast(maxdim); + + image = core::image::image_undistort_vsfm + (image, core_cam.flen, nvm_cam.radial_distortion); + + + const std::string image_file = util::fs::join_path( + tmp_dir, + util::fs::replace_extension( + util::fs::basename(nvm_cam.filename), + "png" + ) + ); + core::image::save_png_file(image, image_file); + + #pragma omp critical + texture_views->push_back(TextureView(i, core_cam, image_file)); + + view_counter.inc(); + } +} + +void +generate_texture_views(std::string const & in_scene, + std::vector * texture_views, std::string const & tmp_dir) +{ + /* Determine input format. */ + + /* BUNDLEFILE */ + if (util::fs::file_exists(in_scene.c_str())) { + std::string const & file = in_scene; + std::string extension = util::string::uppercase(util::string::right(file, 3)); + if (extension == "NVM") { + from_nvm_scene(file, texture_views, tmp_dir); + } + } + + /* SCENE_FOLDER */ + if (util::fs::dir_exists(in_scene.c_str())) { + from_images_and_camera_files(in_scene, texture_views, tmp_dir); + } + + /* MVE_SCENE::EMBEDDING */ + size_t pos = in_scene.rfind("::"); + if (pos != std::string::npos) { + std::string scene_dir = in_scene.substr(0, pos); + std::string image_name = in_scene.substr(pos + 2, in_scene.size()); + from_core_scene(scene_dir, image_name, texture_views); + } + + std::sort(texture_views->begin(), texture_views->end(), + [] (TextureView const & l, TextureView const & r) -> bool { + return l.get_id() < r.get_id(); + } + ); + + std::size_t num_views = texture_views->size(); + if (num_views == 0) { + std::cerr + << "No proper input scene descriptor given.\n" + << "A input descriptor can be:\n" + << "BUNDLE_FILE - a bundle file (currently onle .nvm files are supported)\n" + << "SCENE_FOLDER - a folder containing images and .cam files\n" + << "MVE_SCENE::EMBEDDING - a core scene and embedding\n" + << std::endl; + exit(EXIT_FAILURE); + } +} + +TEX_NAMESPACE_END diff --git a/texturing/global_seam_leveling.cpp b/texturing/global_seam_leveling.cpp new file mode 100644 index 0000000..ac05fe2 --- /dev/null +++ b/texturing/global_seam_leveling.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include + +#include +#include +#include +#include + +#include "texturing.h" +#include "seam_leveling.h" +#include "progress_counter.h" + +TEX_NAMESPACE_BEGIN + +typedef Eigen::SparseMatrix SpMat; + +math::Vec3f +sample_edge(TexturePatch::ConstPtr texture_patch, math::Vec2f p1, math::Vec2f p2) { + math::Vec2f p12 = p2 - p1; + + std::size_t num_samples = std::max(p12.norm(), 1.0f) * 2.0f; + + math::Accum color_accum(math::Vec3f(0.0f)); + /* Sample the edge with linear weights. */ + for (std::size_t s = 0; s < num_samples; ++s) { + float fraction = static_cast(s) / (num_samples - 1); + math::Vec2f sample_point = p1 + p12 * fraction; + math::Vec3f color(texture_patch->get_pixel_value(sample_point)); + + color_accum.add(color, 1.0f - fraction); + } + + return color_accum.normalized(); +} + + +void +find_seam_edges_for_vertex_label_combination(UniGraph const & graph, core::TriangleMesh::ConstPtr & mesh, + core::MeshInfo const & mesh_info, std::size_t vertex, std::size_t label1, std::size_t label2, + std::vector * seam_edges) { + + assert(label1 != 0 && label2 != 0 && label1 < label2); + + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + + std::vector const & adj_verts = mesh_info[vertex].verts; + for (std::size_t i = 0; i < adj_verts.size(); ++i) { + std::size_t adj_vertex = adj_verts[i]; + if (vertex == adj_vertex) continue; + + std::vector edge_faces; + mesh_info.get_faces_for_edge(vertex, adj_vertex, &edge_faces); + + for (std::size_t j = 0; j < edge_faces.size(); ++j) { + for(std::size_t k = j + 1; k < edge_faces.size(); ++k) { + + std::size_t face_label1 = graph.get_label(edge_faces[j]); + std::size_t face_label2 = graph.get_label(edge_faces[k]); + if (!(face_label1 < face_label2)) std::swap(face_label1, face_label2); + + if (face_label1 != label1 || face_label2 != label2) continue; + + math::Vec3f v1 = vertices[vertex]; + math::Vec3f v2 = vertices[adj_vertex]; + float length = (v2 - v1).norm(); + + /* Ignore zero length edges. */ + if (length == 0.0f) continue; + + MeshEdge seam_edge = {vertex, adj_vertex}; + seam_edges->push_back(seam_edge); + } + } + } +} + +math::Vec3f +calculate_difference(VertexProjectionInfos const & vertex_projection_infos, + core::TriangleMesh::ConstPtr & mesh, std::vector const & texture_patches, + std::vector const & seam_edges, int label1, int label2) { + + assert(label1 != 0 && label2 != 0 && label1 < label2); + assert(!seam_edges.empty()); + + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + + math::Accum color1_accum(math::Vec3f(0.0f)); + math::Accum color2_accum(math::Vec3f(0.0f)); + + for (MeshEdge const & seam_edge : seam_edges) { + math::Vec3f v1 = vertices[seam_edge.v1]; + math::Vec3f v2 = vertices[seam_edge.v2]; + float length = (v2 - v1).norm(); + + assert(length != 0.0f); + + std::vector edge_projection_infos; + find_mesh_edge_projections(vertex_projection_infos, seam_edge, &edge_projection_infos); + + std::size_t num_samples = 0; + + for (EdgeProjectionInfo const & edge_projection_info : edge_projection_infos) { + TexturePatch::Ptr texture_patch = texture_patches[edge_projection_info.texture_patch_id]; + const int texture_patch_label = texture_patch->get_label(); + if (texture_patch_label == label1 || texture_patch_label == label2) { + if (texture_patch_label == label1) + color1_accum.add(sample_edge(texture_patch, edge_projection_info.p1, edge_projection_info.p2), length); + + if (texture_patch_label == label2) + color2_accum.add(sample_edge(texture_patch, edge_projection_info.p1, edge_projection_info.p2), length); + + num_samples++; + } + } + assert(num_samples == 2); + } + + math::Vec3f color1 = color1_accum.normalized(); + math::Vec3f color2 = color2_accum.normalized(); + + /* The order is essential. */ + math::Vec3f difference = color2 - color1; + + assert(!std::isnan(difference[0])); + assert(!std::isnan(difference[1])); + assert(!std::isnan(difference[2])); + + return difference; +} + +void +global_seam_leveling(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, + std::vector > const & vertex_projection_infos, + std::vector * texture_patches) { + + core::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); + std::size_t const num_vertices = vertices.size(); + + std::cout << "\tCreate matrices for optimization... " << std::flush; + std::vector > vertlabel2row; + vertlabel2row.resize(num_vertices); + + std::vector > labels; + labels.resize(num_vertices); + + /* Assign each vertex for each label a new index(row) within the solution vector x. */ + std::size_t x_row = 0; + for (std::size_t i = 0; i < num_vertices; ++i) { + std::set label_set; + + std::vector faces = mesh_info[i].faces; + std::set::iterator it = label_set.begin(); + for (std::size_t j = 0; j < faces.size(); ++j) { + std::size_t label = graph.get_label(faces[j]); + label_set.insert(it, label); + } + + for (it = label_set.begin(); it != label_set.end(); ++it) { + std::size_t label = *it; + if (label == 0) continue; + vertlabel2row[i][label] = x_row; + labels[i].push_back(label); + ++x_row; + } + } + std::size_t x_rows = x_row; + assert(x_rows < static_cast(std::numeric_limits::max())); + + float const lambda = 0.1f; + + /* Fill the Tikhonov matrix Gamma(regularization constraints). */ + std::size_t Gamma_row = 0; + std::vector > coefficients_Gamma; + coefficients_Gamma.reserve(2 * num_vertices); + for (std::size_t i = 0; i < num_vertices; ++i) { + for (std::size_t j = 0; j < labels[i].size(); ++j) { + std::vector const & adj_verts = mesh_info[i].verts; + for (std::size_t k = 0; k < adj_verts.size(); ++k) { + std::size_t adj_vertex = adj_verts[k]; + for (std::size_t l = 0; l < labels[adj_vertex].size(); ++l) { + std::size_t label = labels[i][j]; + std::size_t adj_vertex_label = labels[adj_vertex][l]; + if (i < adj_vertex && label == adj_vertex_label) { + Eigen::Triplet t1(Gamma_row, vertlabel2row[i][label], lambda); + Eigen::Triplet t2(Gamma_row, vertlabel2row[adj_vertex][adj_vertex_label], -lambda); + coefficients_Gamma.push_back(t1); + coefficients_Gamma.push_back(t2); + Gamma_row++; + } + } + } + } + } + std::size_t Gamma_rows = Gamma_row; + assert(Gamma_rows < static_cast(std::numeric_limits::max())); + + SpMat Gamma(Gamma_rows, x_rows); + Gamma.setFromTriplets(coefficients_Gamma.begin(), coefficients_Gamma.end()); + + /* Fill the matrix A and the coefficients for the Vector b of the linear equation system. */ + std::vector > coefficients_A; + std::vector coefficients_b; + std::size_t A_row = 0; + for (std::size_t i = 0; i < num_vertices; ++i) { + for (std::size_t j = 0; j < labels[i].size(); ++j) { + for (std::size_t k = 0; k < labels[i].size(); ++k) { + std::size_t label1 = labels[i][j]; + std::size_t label2 = labels[i][k]; + if (label1 < label2) { + + std::vector seam_edges; + find_seam_edges_for_vertex_label_combination(graph, mesh, mesh_info, i, label1, label2, &seam_edges); + + if (seam_edges.empty()) continue; + + Eigen::Triplet t1(A_row, vertlabel2row[i][label1], 1.0f); + Eigen::Triplet t2(A_row, vertlabel2row[i][label2], -1.0f); + coefficients_A.push_back(t1); + coefficients_A.push_back(t2); + + coefficients_b.push_back(calculate_difference(vertex_projection_infos, mesh, *texture_patches, seam_edges, label1, label2)); + + ++A_row; + } + } + } + } + + std::size_t A_rows = A_row; + assert(A_rows < static_cast(std::numeric_limits::max())); + + SpMat A(A_rows, x_rows); + A.setFromTriplets(coefficients_A.begin(), coefficients_A.end()); + + SpMat Lhs = A.transpose() * A + Gamma.transpose() * Gamma; + /* Only keep lower triangle (CG only uses the lower), prune the rest and compress matrix. */ + Lhs.prune([](const int& row, const int& col, const float& value) -> bool { + return col <= row && value != 0.0f; + }); // value != 0.0f is only to suppress a compiler warning + + std::vector > adjust_values(num_vertices); + std::cout << " done." << std::endl; + std::cout << "\tLhs dimensionality: " << Lhs.rows() << " x " << Lhs.cols() << std::endl; + + util::WallTimer timer; + std::cout << "\tCalculating adjustments:"<< std::endl; + #pragma omp parallel for + for (std::size_t channel = 0; channel < 3; ++channel) { + /* Prepare solver. */ + Eigen::ConjugateGradient cg; + cg.setMaxIterations(1000); + cg.setTolerance(0.0001); + cg.compute(Lhs); + + /* Prepare right hand side. */ + Eigen::VectorXf b(A_rows); + for (std::size_t i = 0; i < coefficients_b.size(); ++i) { + b[i] = coefficients_b[i][channel]; + } + Eigen::VectorXf Rhs = SpMat(A.transpose()) * b; + + /* Solve for x. */ + Eigen::VectorXf x(x_rows); + x = cg.solve(Rhs); + + /* Subtract mean because system is underconstrained and we seek the solution with minimal adjustments. */ + x = x.array() - x.mean(); + + #pragma omp critical + std::cout << "\t\tColor channel " << channel << ": CG took " + << cg.iterations() << " iterations. Residual is " << cg.error() << std::endl; + + #pragma omp critical + for (std::size_t i = 0; i < num_vertices; ++i) { + for (std::size_t j = 0; j < labels[i].size(); ++j) { + std::size_t label = labels[i][j]; + adjust_values[i][label][channel] = x[vertlabel2row[i][label]]; + } + } + } + std::cout << "\t\tTook " << timer.get_elapsed_sec() << " seconds" << std::endl; + + core::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); + + ProgressCounter texture_patch_counter("\tAdjusting texture patches", texture_patches->size()); + #pragma omp parallel for schedule(dynamic) + for (std::size_t i = 0; i < texture_patches->size(); ++i) { + texture_patch_counter.progress(); + + TexturePatch::Ptr texture_patch = texture_patches->at(i); + + int label = texture_patch->get_label(); + std::vector const & faces = texture_patch->get_faces(); + std::vector patch_adjust_values(faces.size() * 3, math::Vec3f(0.0f)); + + /* Only adjust texture_patches originating form input images. */ + if (label == 0) { + texture_patch->adjust_colors(patch_adjust_values); + texture_patch_counter.inc(); + continue; + }; + + for (std::size_t j = 0; j < faces.size(); ++j) { + for (std::size_t k = 0; k < 3; ++k) { + std::size_t face_pos = faces[j] * 3 + k; + std::size_t vertex = mesh_faces[face_pos]; + patch_adjust_values[j * 3 + k] = adjust_values[vertex].find(label)->second; + } + } + + texture_patch->adjust_colors(patch_adjust_values); + texture_patch_counter.inc(); + } +} + +TEX_NAMESPACE_END diff --git a/texturing/histogram.cpp b/texturing/histogram.cpp new file mode 100644 index 0000000..0bf1210 --- /dev/null +++ b/texturing/histogram.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "histogram.h" + +Histogram::Histogram(float _min, float _max, std::size_t num_bins) + :min(_min), max(_max), num_values(0) { + bins.resize(num_bins); +} + +void +Histogram::add_value(float value) { + float clamped_value = std::max(min, std::min(max, value)); + std::size_t index = floor(((clamped_value - min) / (max - min)) * (bins.size() - 1)); + assert(index < bins.size()); + bins[index]++; + ++num_values; +} + +void +Histogram::save_to_file(std::string const & filename) const { + std::ofstream out(filename.c_str()); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + out << "Bin, Values" << std::endl; + for (std::size_t i = 0; i < bins.size(); ++i) { + out << i << ", " << bins[i] << std::endl; + } + out.close(); +} + +float +Histogram::get_approx_percentile(float percentile) const { + assert(percentile >= 0.0f && percentile <= 1.0f); + + int num = 0; + float upper_bound = min; + for (std::size_t i = 0; i < bins.size(); ++i) { + if (static_cast(num) / num_values > percentile) + return upper_bound; + + num += bins[i]; + upper_bound = (static_cast(i) / (bins.size() - 1)) * (max - min) + min; + } + return max; +} diff --git a/texturing/histogram.h b/texturing/histogram.h new file mode 100644 index 0000000..5f82db9 --- /dev/null +++ b/texturing/histogram.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_HISTOGRAM_HEADER +#define TEX_HISTOGRAM_HEADER + +#include +#include + +/** + * Class representing a histogram with a fixed number of bins + * optimized to calculate approximate permilles. + */ +class Histogram { + private: + std::vector bins; + float min; + float max; + int num_values; + + public: + /** Constructs a histogram with num_bins bins which clamps values to [_min, _max]. */ + Histogram(float _min, float _max, std::size_t num_bins); + + /** Adds a value to the histogram. The value is clamped to [min, max]. */ + void add_value(float value); + + /** + * Saves the histogram to a .csv file. + * @throws util::FileException + */ + void save_to_file(std::string const & filename) const; + + /** + * Returns the approximate permille. + */ + float get_approx_percentile(float percentile) const; +}; + +#endif /* TEX_HISTOGRAM_HEADER */ diff --git a/texturing/local_seam_leveling.cpp b/texturing/local_seam_leveling.cpp new file mode 100644 index 0000000..824d0cc --- /dev/null +++ b/texturing/local_seam_leveling.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "progress_counter.h" +#include "texturing.h" +#include "seam_leveling.h" + +TEX_NAMESPACE_BEGIN + +#define STRIP_SIZE 20 + +math::Vec3f +mean_color_of_edge_point(std::vector const & edge_projection_infos, + std::vector const & texture_patches, float t) { + + assert(0.0f <= t && t <= 1.0f); + math::Accum color_accum(math::Vec3f(0.0f)); + + for (EdgeProjectionInfo const & edge_projection_info : edge_projection_infos) { + TexturePatch::Ptr texture_patch = texture_patches[edge_projection_info.texture_patch_id]; + if (texture_patch->get_label() == 0) continue; + math::Vec2f pixel = edge_projection_info.p1 * t + (1.0f - t) * edge_projection_info.p2; + math::Vec3f color = texture_patch->get_pixel_value(pixel); + color_accum.add(color, 1.0f); + } + + math::Vec3f mean_color = color_accum.normalized(); + return mean_color; +} + +void +draw_line(math::Vec2f p1, math::Vec2f p2, + std::vector const & edge_color, TexturePatch::Ptr texture_patch) { + /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */ + + int x0 = std::floor(p1[0] + 0.5f); + int y0 = std::floor(p1[1] + 0.5f); + int const x1 = std::floor(p2[0] + 0.5f); + int const y1 = std::floor(p2[1] + 0.5f); + + float tdx = static_cast(x1 - x0); + float tdy = static_cast(y1 - y0); + float length = std::sqrt(tdx * tdx + tdy * tdy); + + int const dx = std::abs(x1 - x0); + int const dy = std::abs(y1 - y0); + int const sx = x0 < x1 ? 1 : -1; + int const sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + + int x = x0; + int y = y0; + while (true) { + math::Vec2i pixel(x, y); + + tdx = static_cast(x1 - x); + tdy = static_cast(y1 - y); + + /* If the length is zero we sample the midpoint of the projected edge. */ + float t = (length != 0.0f) ? std::sqrt(tdx * tdx + tdy * tdy) / length : 0.5f; + + math::Vec3f color; + if (t < 1.0f && edge_color.size() > 1) { + std::size_t idx = std::floor(t * (edge_color.size() - 1)); + color = (1.0f - t) * edge_color[idx] + t * edge_color[idx + 1]; + } else { + color = edge_color.back(); + } + + texture_patch->set_pixel_value(pixel, color); + if (x == x1 && y == y1) + break; + + int const e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } + } +} + +struct Pixel { + math::Vec2i pos; + math::Vec3f const * color; +}; + +struct Line { + math::Vec2i from; + math::Vec2i to; + std::vector const * color; +}; + +void +local_seam_leveling(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + VertexProjectionInfos const & vertex_projection_infos, + std::vector * texture_patches) { + + std::size_t const num_vertices = vertex_projection_infos.size(); + std::vector vertex_colors(num_vertices); + std::vector > edge_colors; + + std::vector > edge_projection_infos; + { + std::vector seam_edges; + find_seam_edges(graph, mesh, &seam_edges); + edge_colors.resize(seam_edges.size()); + edge_projection_infos.resize(seam_edges.size()); + for (std::size_t i = 0; i < seam_edges.size(); ++i) { + MeshEdge const & seam_edge = seam_edges[i]; + find_mesh_edge_projections(vertex_projection_infos, seam_edge, + &edge_projection_infos[i]); + } + } + + std::vector > lines(texture_patches->size()); + std::vector > pixels(texture_patches->size()); + /* Sample edge colors. */ + for (std::size_t i = 0; i < edge_projection_infos.size(); ++i) { + /* Determine sampling (ensure at least two samples per edge). */ + float max_length = 1; + for (EdgeProjectionInfo const & edge_projection_info : edge_projection_infos[i]) { + float length = (edge_projection_info.p1 - edge_projection_info.p2).norm(); + max_length = std::max(max_length, length); + } + + std::vector & edge_color = edge_colors[i]; + edge_color.resize(std::ceil(max_length * 2.0f)); + for (std::size_t j = 0; j < edge_color.size(); ++j) { + float t = static_cast(j) / (edge_color.size() - 1); + edge_color[j] = mean_color_of_edge_point(edge_projection_infos[i], *texture_patches, t); + } + + for (EdgeProjectionInfo const & edge_projection_info : edge_projection_infos[i]) { + Line line; + line.from = edge_projection_info.p1 + math::Vec2f(0.5f, 0.5f); + line.to = edge_projection_info.p2 + math::Vec2f(0.5f, 0.5f); + line.color = &edge_colors[i]; + lines[edge_projection_info.texture_patch_id].push_back(line); + } + } + + /* Sample vertex colors. */ + for (std::size_t i = 0; i < vertex_colors.size(); ++i) { + std::vector const & projection_infos = vertex_projection_infos[i]; + if (projection_infos.size() <= 1) continue; + + math::Accum color_accum(math::Vec3f(0.0f)); + for (VertexProjectionInfo const & projection_info : projection_infos) { + TexturePatch::Ptr texture_patch = texture_patches->at(projection_info.texture_patch_id); + if (texture_patch->get_label() == 0) continue; + math::Vec3f color = texture_patch->get_pixel_value(projection_info.projection); + color_accum.add(color, 1.0f); + } + if (color_accum.w == 0.0f) continue; + + vertex_colors[i] = color_accum.normalized(); + + for (VertexProjectionInfo const & projection_info : projection_infos) { + Pixel pixel; + pixel.pos = math::Vec2i(projection_info.projection + math::Vec2f(0.5f, 0.5f)); + pixel.color = &vertex_colors[i]; + pixels[projection_info.texture_patch_id].push_back(pixel); + } + } + + ProgressCounter texture_patch_counter("\tBlending texture patches", texture_patches->size()); + #pragma omp parallel for schedule(dynamic) + for (std::size_t i = 0; i < texture_patches->size(); ++i) { + TexturePatch::Ptr texture_patch = texture_patches->at(i); + core::FloatImage::Ptr image = texture_patch->get_image()->duplicate(); + + /* Apply colors. */ + for (Pixel const & pixel : pixels[i]) { + texture_patch->set_pixel_value(pixel.pos, *pixel.color); + } + + for (Line const & line : lines[i]) { + draw_line(line.from, line.to, *line.color, texture_patch); + } + + texture_patch_counter.progress(); + + /* Only alter a small strip of texture patches originating from input images. */ + if (texture_patch->get_label() != 0) { + texture_patch->prepare_blending_mask(STRIP_SIZE); + } + + texture_patch->blend(image); + texture_patch->release_blending_mask(); + texture_patch_counter.inc(); + } +} + +TEX_NAMESPACE_END diff --git a/texturing/material_lib.cpp b/texturing/material_lib.cpp new file mode 100644 index 0000000..bb12e78 --- /dev/null +++ b/texturing/material_lib.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include +#include + +#include +#include +#include + +#include "material_lib.h" + +void +MaterialLib::save_to_files(std::string const & prefix) const { + std::string filename = prefix + ".mtl"; + std::ofstream out(filename.c_str()); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + std::string const name = util::fs::basename(prefix); + + for (Material const & material : *this) { + std::string diffuse_map_postfix = "_" + material.name + "_map_Kd.png"; + out << "newmtl " << material.name << '\n' + << "Ka 1.000000 1.000000 1.000000" << '\n' + << "Kd 1.000000 1.000000 1.000000" << '\n' + << "Ks 0.000000 0.000000 0.000000" << '\n' + << "Tr 0.000000" << '\n' // *Tr*ansparancy vs. *d*issolve: Tr = 1.0 - d + << "illum 1" << '\n' + << "Ns 1.000000" << '\n' + << "map_Kd " << name + diffuse_map_postfix << std::endl; + } + out.close(); + + for (Material const & material : *this) { + std::string filename = prefix + "_" + material.name + "_map_Kd.png"; + core::image::save_png_file(material.diffuse_map, filename); + } +} diff --git a/texturing/material_lib.h b/texturing/material_lib.h new file mode 100644 index 0000000..8cd8e93 --- /dev/null +++ b/texturing/material_lib.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_MATERIALLIB_HEADER +#define TEX_MATERIALLIB_HEADER + +#include +#include + +struct Material { + std::string name; + core::ByteImage::ConstPtr diffuse_map; +}; + +/** + * Class representing a material lib of and obj model. + */ +class MaterialLib : public std::vector{ + + public: + /** Saves the material lib to an .mtl file and all maps of its + * materials with the given prefix. + */ + void save_to_files(std::string const & prefix) const; +}; + +#endif /* TEX_MATERIALLIB_HEADER */ diff --git a/texturing/obj_model.cpp b/texturing/obj_model.cpp new file mode 100644 index 0000000..7f39c01 --- /dev/null +++ b/texturing/obj_model.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "obj_model.h" + +#define OBJ_INDEX_OFFSET 1 + +void +ObjModel::save(ObjModel const & model, std::string const & prefix) { + model.save_to_files(prefix); +} + +void +ObjModel::save_to_files(std::string const & prefix) const { + material_lib.save_to_files(prefix); + + std::string name = util::fs::basename(prefix); + std::ofstream out((prefix + ".obj").c_str()); + if (!out.good()) + throw util::FileException(prefix + ".obj", std::strerror(errno)); + + out << "mtllib " << name << ".mtl" << '\n'; + + out << std::fixed << std::setprecision(6); + for (std::size_t i = 0; i < vertices.size(); ++i) { + out << "v " << vertices[i][0] << " " + << vertices[i][1] << " " + << vertices[i][2] << '\n'; + } + + for (std::size_t i = 0; i < texcoords.size(); ++i) { + out << "vt " << texcoords[i][0] << " " + << 1.0f - texcoords[i][1] << '\n'; + } + + for (std::size_t i = 0; i < normals.size(); ++i) { + out << "vn " << normals[i][0] << " " + << normals[i][1] << " " + << normals[i][2] << '\n'; + } + + for (std::size_t i = 0; i < groups.size(); ++i) { + out << "usemtl " << groups[i].material_name << '\n'; + for (std::size_t j = 0; j < groups[i].faces.size(); ++j) { + Face const & face = groups[i].faces[j]; + out << "f"; + for (std::size_t k = 0; k < 3; ++k) { + out << " " << face.vertex_ids[k] + OBJ_INDEX_OFFSET + << "/" << face.texcoord_ids[k] + OBJ_INDEX_OFFSET + << "/" << face.normal_ids[k] + OBJ_INDEX_OFFSET; + } + out << '\n'; + } + } + out.close(); +} diff --git a/texturing/obj_model.h b/texturing/obj_model.h new file mode 100644 index 0000000..e3615f1 --- /dev/null +++ b/texturing/obj_model.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_OBJMODEL_HEADER +#define TEX_OBJMODEL_HEADER + +#include "material_lib.h" + +/** + * Class representing a obj model. + */ +class ObjModel { +public: + struct Face { + std::size_t vertex_ids[3]; + std::size_t texcoord_ids[3]; + std::size_t normal_ids[3]; + }; + + struct Group { + std::string material_name; + std::vector faces; + }; + + typedef std::vector Vertices; + typedef std::vector TexCoords; + typedef std::vector Normals; + typedef std::vector Groups; + +private: + Vertices vertices; + TexCoords texcoords; + Normals normals; + Groups groups; + MaterialLib material_lib; + +public: + /** Saves the obj model to an .obj file, its material lib and the materials with the given prefix. */ + void save_to_files(std::string const & prefix) const; + + MaterialLib & get_material_lib(void); + Vertices & get_vertices(void); + TexCoords & get_texcoords(void); + Normals & get_normals(void); + Groups & get_groups(void); + + static void save(ObjModel const & model, std::string const & prefix); +}; + +inline +MaterialLib & +ObjModel::get_material_lib(void) { + return material_lib; +} + +inline +ObjModel::Vertices & +ObjModel::get_vertices(void) { + return vertices; +} + +inline +ObjModel::TexCoords & +ObjModel::get_texcoords(void) { + return texcoords; +} + +inline +ObjModel::Normals & +ObjModel::get_normals(void) { + return normals; +} + +inline +ObjModel::Groups & +ObjModel::get_groups(void) { + return groups; +} + +#endif /* TEX_OBJMODEL_HEADER */ diff --git a/texturing/poisson_blending.cpp b/texturing/poisson_blending.cpp new file mode 100644 index 0000000..9e8f316 --- /dev/null +++ b/texturing/poisson_blending.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include + +#include +#include +#include + +#include "poisson_blending.h" + +typedef Eigen::SparseMatrix SpMat; + +math::Vec3f simple_laplacian(int i, core::FloatImage::ConstPtr img){ + const int width = img->width(); + assert(i > width + 1 && i < img->get_pixel_amount() - width -1); + + return -4.0f * math::Vec3f(&img->at(i, 0)) + + math::Vec3f(&img->at(i - width, 0)) + + math::Vec3f(&img->at(i - 1, 0)) + + math::Vec3f(&img->at(i + 1, 0)) + + math::Vec3f(&img->at(i + width, 0)); +} + +bool valid_mask(core::ByteImage::ConstPtr mask){ + const int width = mask->width(); + const int height = mask->height(); + + for (int x = 0; x < width; ++x) + if (mask->at(x, 0, 0) == 255 || mask->at(x, height - 1, 0) == 255) + return false; + + for (int y = 0; y < height; ++y) + if (mask->at(0, y, 0) == 255 || mask->at(width - 1, y, 0) == 255) + return false; + + //TODO check for sane boundary conditions... + + return true; +} + +void +poisson_blend(core::FloatImage::ConstPtr src, core::ByteImage::ConstPtr mask, + core::FloatImage::Ptr dest, float alpha) { + + assert(src->width() == mask->width() && mask->width() == dest->width()); + assert(src->height() == mask->height() && mask->height() == dest->height()); + assert(src->channels() == 3 && dest->channels() == 3); + assert(mask->channels() == 1); + assert(valid_mask(mask)); + + const int n = dest->get_pixel_amount(); + const int width = dest->width(); + const int height = dest->height(); + const int channels = dest->channels(); + + core::Image::Ptr indices = core::Image::create(width, height, 1); + indices->fill(-1); + int index = 0; + for (int i = 0; i < n; ++i) { + if (mask->at(i) != 0) { + indices->at(i) = index; + index += 1; + } + } + const int nnz = index; + + std::vector coefficients_b; + coefficients_b.resize(nnz); + + std::vector > coefficients_A; + coefficients_A.reserve(nnz); //TODO better estimate... + + for (int i = 0; i < n; ++i) { + const int row = indices->at(i); + if (mask->at(i) == 128 || mask->at(i) == 64) { + Eigen::Triplet t(row, row, 1.0f); + coefficients_A.push_back(t); + + coefficients_b[row] = math::Vec3f(&dest->at(i, 0)); + } + + if (mask->at(i) == 255) { + const int i01 = indices->at(i - width); + const int i10 = indices->at(i - 1); + const int i11 = indices->at(i); + const int i12 = indices->at(i + 1); + const int i21 = indices->at(i + width); + + /* All neighbours should be eighter border conditions or part of the optimization. */ + assert(i01 != -1 && i10 != -1 && i11 != -1 && i12 != -1 && i21 != -1); + + Eigen::Triplet t01(row, i01, 1.0f); + + Eigen::Triplet t10(row, i10, 1.0f); + Eigen::Triplet t11(row, i11, -4.0f); + Eigen::Triplet t12(row, i12, 1.0f); + + Eigen::Triplet t21(row, i21, 1.0f); + + Eigen::Triplet triplets[] = {t01, t10, t11, t12, t21}; + + coefficients_A.insert(coefficients_A.end(), triplets, triplets + 5); + + math::Vec3f l_d = simple_laplacian(i, dest); + math::Vec3f l_s = simple_laplacian(i, src); + + coefficients_b[row] = (alpha * l_s + (1.0f - alpha) * l_d); + } + } + + SpMat A(nnz, nnz); + A.setFromTriplets(coefficients_A.begin(), coefficients_A.end()); + + Eigen::SparseLU > solver; + solver.compute(A); + + for (int channel = 0; channel < channels; ++channel) { + Eigen::VectorXf b(nnz); + for (std::size_t i = 0; i < coefficients_b.size(); ++i) + b[i] = coefficients_b[i][channel]; + + Eigen::VectorXf x(n); + x = solver.solve(b); + + for (int i = 0; i < n; ++i) { + int index = indices->at(i); + if (index != -1) dest->at(i, channel) = x[index]; + } + } +} diff --git a/texturing/poisson_blending.h b/texturing/poisson_blending.h new file mode 100644 index 0000000..50a01db --- /dev/null +++ b/texturing/poisson_blending.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_POISSONBLENDING_HEADER +#define TEX_POISSONBLENDING_HEADER + +#include "core/image.h" + +void +poisson_blend(core::FloatImage::ConstPtr src, core::ByteImage::ConstPtr mask, + core::FloatImage::Ptr dest, float alpha); + + +#endif /* TEX_POISSONBLENDING_HEADER */ diff --git a/texturing/prepare_mesh.cpp b/texturing/prepare_mesh.cpp new file mode 100644 index 0000000..0db3927 --- /dev/null +++ b/texturing/prepare_mesh.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include "texturing.h" + +TEX_NAMESPACE_BEGIN + +std::size_t remove_redundant_faces(core::MeshInfo const & mesh_info, core::TriangleMesh::Ptr mesh) { + core::TriangleMesh::FaceList & faces = mesh->get_faces(); + core::TriangleMesh::FaceList new_faces; + new_faces.reserve(faces.size()); + + std::size_t num_redundant = 0; + for (std::size_t i = 0; i < faces.size(); i += 3) { + std::size_t face_id = i / 3; + bool redundant = false; + for (std::size_t j = 0; !redundant && j < 3; ++j) { + core::MeshInfo::AdjacentFaces const & adj_faces = mesh_info[faces[i + j]].faces; + for (std::size_t k = 0; !redundant && k < adj_faces.size(); ++k) { + std::size_t adj_face_id = adj_faces[k]; + + /* Remove only the redundant face with smaller id. */ + if (face_id < adj_face_id) { + bool identical = true; + /* Faces are considered identical if they consist of the same vertices. */ + for(std::size_t l = 0; l < 3; ++l) { + std::size_t vertex = faces[adj_face_id * 3 + l]; + if (std::find(&faces[i], &faces[i + 3], vertex) == &faces[i + 3]) { + identical = false; + break; + } + } + + redundant = identical; + } + } + } + + if (redundant) { + ++num_redundant; + } else { + new_faces.insert(new_faces.end(), faces.cbegin() + i, faces.cbegin() + i + 3); + } + } + + faces.swap(new_faces); + + return num_redundant; +} + +void +prepare_mesh(core::MeshInfo * mesh_info, core::TriangleMesh::Ptr mesh) { + std::size_t num_redundant = remove_redundant_faces(*mesh_info, mesh); + if (num_redundant > 0) { + std::cout << "\tRemoved " << num_redundant << " redundant faces." << std::endl; + } + + /* Ensure face and vertex normals. */ + mesh->ensure_normals(true, true); + + /* Update vertex infos. */ + mesh_info->clear(); + mesh_info->initialize(mesh); +} + +TEX_NAMESPACE_END diff --git a/texturing/progress_counter.h b/texturing/progress_counter.h new file mode 100644 index 0000000..6e12e7e --- /dev/null +++ b/texturing/progress_counter.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_PROGRESSCOUNTER_HEADER +#define TEX_PROGRESSCOUNTER_HEADER + +#include +#include +#include +#include +#include "util/timer.h" +#include + +enum ProgressCounterStyle { + ETA, + SIMPLE +}; + +static const std::string clear = "\r" + std::string(80,' ') + "\r"; + +class ProgressCounter { + private: + std::ofstream tty; + util::WallTimer timer; + std::string task; + std::size_t max; + std::atomic_size_t count; + + public: + ProgressCounter(std::string const & _task, std::size_t max); + template void progress(void); + void inc(void); + void reset(std::string const & _task); +}; + +inline +ProgressCounter::ProgressCounter(std::string const & _task, std::size_t _max) + : tty("/dev/tty", std::ios_base::out), timer(), + task(_task), max(_max), count(0) {} + +inline void +ProgressCounter::inc(void) { + std::size_t tmp; + tmp = ++count; + + if(tmp == max) { + std::stringstream ss; + ss << clear << task << " 100%... done. (Took " + << timer.get_elapsed_sec() << "s)"; + #pragma omp critical(progress_counter_inc) + std::cout << ss.rdbuf() << std::endl; + } +} + +inline void +ProgressCounter::reset(std::string const & _task) { + timer.reset(); + count = 0; + task = _task; +} + +template void +ProgressCounter::progress(void) { + if ((max > 100 && count % (max / 100) == 0) || max <= 100) { + float percent = static_cast(count) / max; + int ipercent = std::floor(percent * 100.0f + 0.5f); + + std::stringstream ss; + ss << clear << task << " " << ipercent << "%..."; + + if (T == ETA && ipercent > 3){ + std::size_t const elapsed = timer.get_elapsed(); + std::size_t eta = (elapsed / percent - elapsed) / 1000; + ss << " eta ~ " << eta << " s"; + } + + #pragma omp critical(progress_counter_progress) + tty << ss.rdbuf() << std::flush; + } +} + +#endif /* TEX_PROGRESSCOUNTER_HEADER */ diff --git a/texturing/rect.h b/texturing/rect.h new file mode 100644 index 0000000..e4cd97c --- /dev/null +++ b/texturing/rect.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_RECT_HEADER +#define TEX_RECT_HEADER + +#include + +/** + * Simple class representing a rectangle. + */ +template +class Rect { +public: + T min_x; + T min_y; + T max_x; + T max_y; + + Rect (void); + Rect (Rect * rect); + Rect (T min_x, T min_y, T max_x, T max_y); + + T width(void) const; + T height(void) const; + T size(void) const; + + /** Returns true if the rectangle is within or on the border of the given rectangle. */ + bool is_inside(Rect const * orect) const; + /** Returns true if the rectangle intersects with the given rectangle. */ + bool intersect(Rect const * orect) const; + + void update(T min_x, T min_y, T max_x, T max_y); + /** Moves the rectangle and updates its position. */ + void move(T x, T y); +}; + +template +Rect::Rect(void) { + update(0, 0, 1, 1); +} + +template +Rect::Rect(Rect * rect) { + update(rect->min_x, rect->min_y, rect->max_x, rect->max_y); +} + +template +Rect::Rect(T min_x, T min_y, T max_x, T max_y) { + update(min_x, min_y, max_x, max_y); +} + +template +inline T +Rect::width(void) const{ + return max_x - min_x; +} + +template +inline T +Rect::height(void) const { + return max_y - min_y; +} + +template +inline T +Rect::size(void) const { + return width() * height(); +} + +template +inline bool +Rect::is_inside(Rect const * rect) const { + return + min_x >= rect->min_x && + max_x <= rect->max_x && + min_y >= rect->min_y && + max_y <= rect->max_y; +} + +template +inline void +Rect::update(T min_x, T min_y, T max_x, T max_y) { + this->min_x = min_x; + this->min_y = min_y; + this->max_x = max_x; + this->max_y = max_y; + assert(min_x <= max_x); + assert(min_y <= max_y); +} + +template +inline bool +Rect::intersect(Rect const * rect) const { + return + min_x < rect->max_x && max_x > rect->min_x && + min_y < rect->max_y && max_y > rect->min_y; +} + +template +inline void +Rect::move(T x, T y) { + update(x, y, x + width(), y + height()); +} + +#endif /* TEX_RECT_HEADER */ diff --git a/texturing/rectangular_bin.cpp b/texturing/rectangular_bin.cpp new file mode 100644 index 0000000..c4b8015 --- /dev/null +++ b/texturing/rectangular_bin.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "rectangular_bin.h" + +RectangularBin::RectangularBin(unsigned int width, unsigned int height) + : width(width), height(height) { + rects.push_back(Rect(0, 0, width, height)); +} + +bool RectangularBin::insert(Rect * rect) { + /* The best score is 0 so we initialize with the worst. */ + unsigned int best_score = width * height; + std::list >::iterator best_rect_it = rects.end(); + std::list >::iterator it = rects.begin(); + for (; it != rects.end(); ++it) { + Rect free_rect = *it; + if (rect->width() <= free_rect.width() + && rect->height() <= free_rect.height() ) { + unsigned int score = free_rect.size() - rect->size(); + if (score < best_score){ + best_score = score; + best_rect_it = it; + } + } + } + + /* Fits? */ + if (best_rect_it != rects.end()) { + Rect best_rect(&(*best_rect_it)); + rects.erase(best_rect_it); + + /* Update the rect. */ + rect->move(best_rect.min_x, best_rect.min_y); + + /* Decide split axis. */ + Rect hsplit_top(best_rect.min_x, rect->max_y, best_rect.max_x, best_rect.max_y); + Rect hsplit_bottom(rect->max_x, best_rect.min_y, best_rect.max_x, rect->max_y); + Rect vsplit_left(best_rect.min_x, rect->max_y, rect->max_x, best_rect.max_y); + Rect vsplit_right(rect->max_x, best_rect.min_y, best_rect.max_x, best_rect.max_y); + + float hsplit_ratio = 1.0f; + float vsplit_ratio = 1.0f; + + if (hsplit_top.size() != 0 && hsplit_bottom.size() != 0) + hsplit_ratio = static_cast(hsplit_top.size()) / hsplit_bottom.size(); + if (vsplit_left.size() != 0 && vsplit_right.size() != 0) + vsplit_ratio = static_cast(vsplit_left.size()) / vsplit_right.size(); + + if (std::abs(1.0f - hsplit_ratio) < std::abs(1.0f - vsplit_ratio)){ + if (vsplit_left.size() != 0) rects.push_back(vsplit_left); + if (vsplit_right.size() != 0) rects.push_back(vsplit_right); + } else { + if (hsplit_top.size() != 0) rects.push_back(hsplit_top); + if (hsplit_bottom.size() != 0) rects.push_back(hsplit_bottom); + } + + return true; + } else { + return false; + } +} diff --git a/texturing/rectangular_bin.h b/texturing/rectangular_bin.h new file mode 100644 index 0000000..60e8df1 --- /dev/null +++ b/texturing/rectangular_bin.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_RECTANGULARBIN_HEADER +#define TEX_RECTANGULARBIN_HEADER + +#include +#include + +#include "rect.h" + +/** + * Implementation of the binpacking algorithm GUILLUTINE from + * + * A Thousand Ways to Pack the Bin - + * A Practical Approach to Two-Dimensional Rectangle Bin Packing + * + */ +class RectangularBin { + public: + typedef std::shared_ptr Ptr; + + private: + unsigned int width; + unsigned int height; + std::list > rects; + + public: + /** + * Initializes the rectangular binpacking algorithm to fill a rectangle of the given size. + */ + RectangularBin(unsigned int width, unsigned int height); + + static RectangularBin::Ptr create(unsigned int width, unsigned int height); + + /** Returns true and changes the position of the given rect if it fits into the bin. */ + bool insert(Rect * rect); +}; + +inline RectangularBin::Ptr +RectangularBin::create(unsigned int width, unsigned int height) +{ + return Ptr(new RectangularBin(width, height)); +} + +#endif /* TEX_RECTANGULARBIN_HEADER */ diff --git a/texturing/seam_leveling.cpp b/texturing/seam_leveling.cpp new file mode 100644 index 0000000..7bcc6e3 --- /dev/null +++ b/texturing/seam_leveling.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "seam_leveling.h" + +TEX_NAMESPACE_BEGIN + +void +find_seam_edges(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + std::vector * seam_edges) { + core::TriangleMesh::FaceList const & faces = mesh->get_faces(); + + seam_edges->clear(); + + // Is it possible that a single edge is part of more than three faces whichs' label is non zero??? + + for (std::size_t node = 0; node < graph.num_nodes(); ++node) { + std::vector const & adj_nodes = graph.get_adj_nodes(node); + for (std::size_t adj_node : adj_nodes) { + /* Add each edge only once. */ + if (node > adj_node) continue; + + int label1 = graph.get_label(node); + int label2 = graph.get_label(adj_node); + /* Add only seam edges. */ + if (label1 == label2) continue; + + /* Find shared edge of the faces. */ + std::vector shared_edge; + for (int i = 0; i < 3; ++i){ + std::size_t v1 = faces[3 * node + i]; + + for (int j = 0; j < 3; j++){ + std::size_t v2 = faces[3 * adj_node + j]; + + if (v1 == v2) shared_edge.push_back(v1); + } + } + + assert(shared_edge.size() == 2); + std::size_t v1 = shared_edge[0]; + std::size_t v2 = shared_edge[1]; + + assert(v1 != v2); + if (v1 > v2) std::swap(v1, v2); + + MeshEdge seam_edge = {v1, v2}; + seam_edges->push_back(seam_edge); + } + } +} + +void +find_mesh_edge_projections( + std::vector > const & vertex_projection_infos, + MeshEdge mesh_edge, std::vector * edge_projection_infos) { + std::vector const & v1_projection_infos = vertex_projection_infos[mesh_edge.v1]; + std::vector const & v2_projection_infos = vertex_projection_infos[mesh_edge.v2]; + + /* Use a set to eliminate duplicates which may occur if the mesh is degenerated. */ + std::set edge_projection_infos_set; + + for (VertexProjectionInfo v1_projection_info : v1_projection_infos) { + for (VertexProjectionInfo v2_projection_info : v2_projection_infos) { + if (v1_projection_info.texture_patch_id != v2_projection_info.texture_patch_id) continue; + + for (std::size_t face_id1 : v1_projection_info.faces) { + for (std::size_t face_id2 : v2_projection_info.faces) { + if(face_id1 != face_id2) continue; + + std::size_t texture_patch_id = v1_projection_info.texture_patch_id; + math::Vec2f p1 = v1_projection_info.projection; + math::Vec2f p2 = v2_projection_info.projection; + + EdgeProjectionInfo edge_projection_info = {texture_patch_id, p1, p2}; + edge_projection_infos_set.insert(edge_projection_info); + } + } + } + } + + edge_projection_infos->insert(edge_projection_infos->end(), edge_projection_infos_set.begin(), edge_projection_infos_set.end()); +} + +TEX_NAMESPACE_END diff --git a/texturing/seam_leveling.h b/texturing/seam_leveling.h new file mode 100644 index 0000000..ed9339c --- /dev/null +++ b/texturing/seam_leveling.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_SEAMLEVELING_HEADER +#define TEX_SEAMLEVELING_HEADER + +#include + +#include + +#include "defines.h" +#include "uni_graph.h" + +TEX_NAMESPACE_BEGIN + +struct VertexProjectionInfo { + std::size_t texture_patch_id; + math::Vec2f projection; + std::vector faces; + + bool operator<(VertexProjectionInfo const & other) const { + return texture_patch_id < other.texture_patch_id; + } +}; + +struct EdgeProjectionInfo { + std::size_t texture_patch_id; + math::Vec2f p1; + math::Vec2f p2; + + bool operator<(EdgeProjectionInfo const & other) const { + return texture_patch_id < other.texture_patch_id; + } +}; + +struct MeshEdge { + std::size_t v1; + std::size_t v2; +}; + +void +find_seam_edges(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + std::vector * seam_edges); + +void +find_mesh_edge_projections( + std::vector > const & vertex_projection_infos, + MeshEdge mesh_edge, std::vector * projected_edge_infos); + +TEX_NAMESPACE_END + +#endif /* TEX_SEAMLEVELING_HEADER */ diff --git a/texturing/settings.h b/texturing/settings.h new file mode 100644 index 0000000..f2cc563 --- /dev/null +++ b/texturing/settings.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_SETTINGS_HEADER +#define TEX_SETTINGS_HEADER + +#include +#include +#include + +#include "defines.h" + +template +const std::vector choice_strings(); + +template inline +const std::string choice_string(T i) { + return choice_strings()[static_cast(i)]; +} + +template inline +const std::string choices() { + const std::vector strings = choice_strings(); + const std::size_t n = strings.size(); + std::stringstream ss; + for (std::size_t i = 0; i < n; ++i) { + ss << strings[i]; + if(i != n - 1) ss << ", "; + } + return ss.str(); +} + +template inline +T parse_choice(std::string s) { + const std::vector strings = choice_strings(); + const std::size_t n = strings.size(); + for (std::size_t i = 0; i < n; ++i) { + if (s == strings[i]) { + return static_cast(i); + } + } + + std::stringstream ss; + ss << "Invalid choice: " << s << " (Available choices: "; + ss << choices() << ")"; + + throw std::invalid_argument(ss.str()); +} + +TEX_NAMESPACE_BEGIN + +/** Enum representing a data term. */ +enum DataTerm { + DATA_TERM_AREA = 0, + DATA_TERM_GMI = 1 +}; + +/** Enum representing a smoothness term. */ +enum SmoothnessTerm { + SMOOTHNESS_TERM_POTTS = 0 +}; + +/** Enum representing outlier removal choice. */ +enum OutlierRemoval { + OUTLIER_REMOVAL_NONE = 0, + OUTLIER_REMOVAL_GAUSS_DAMPING = 1, + OUTLIER_REMOVAL_GAUSS_CLAMPING = 2 +}; + +/** Enum representing tone mapping choice. */ +enum ToneMapping { + TONE_MAPPING_NONE = 0, + TONE_MAPPING_GAMMA = 1 +}; + +struct Settings { + bool verbose = false; + + DataTerm data_term = DATA_TERM_GMI; + SmoothnessTerm smoothness_term = SMOOTHNESS_TERM_POTTS; + OutlierRemoval outlier_removal = OUTLIER_REMOVAL_NONE; + ToneMapping tone_mapping = TONE_MAPPING_NONE; + + bool geometric_visibility_test = true; + bool global_seam_leveling = true; + bool local_seam_leveling = true; + bool hole_filling = true; + bool keep_unseen_faces = false; +}; + +TEX_NAMESPACE_END + +template <> inline +const std::vector choice_strings() { + return {"area", "gmi"}; +} + +template <> inline +const std::vector choice_strings() { + return {"potts"}; +} + +template <> inline +const std::vector choice_strings() { + return {"none", "gauss_damping", "gauss_clamping"}; +} + +template <> inline +const std::vector choice_strings() { + return {"none", "gamma"}; +} + +#endif /* TEX_SETTINGS_HEADER */ diff --git a/texturing/sparse_table.h b/texturing/sparse_table.h new file mode 100644 index 0000000..b773ae8 --- /dev/null +++ b/texturing/sparse_table.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_SPARSETABLE_HEADER +#define TEX_SPARSETABLE_HEADER + +#include + +#include +#include +#include +#include + +#include "util/file_system.h" +#include "util/exception.h" + +#define HEADER "SPT" +#define VERSION "0.2" + +/** + * Class representing a sparse table optimized for row and column wise access. + */ +template +class SparseTable { + public: + typedef std::vector > Column; + typedef std::vector > Row; + + private: + std::vector column_wise_data; + std::vector row_wise_data; + + std::size_t nnz; + public: + SparseTable(); + SparseTable(C cols, R rows); + + C cols() const; + R rows() const; + + Column const & col(C id) const; + Row const & row(R id) const; + + void set_value(C col, R row, T value); + + std::size_t get_nnz(void) const; + + /** + * Saves the SparseTable to the file given by filename. + * Version and size information is stored in ascii, values in binary. + * @throws util::FileException + */ + static void save_to_file(SparseTable const & sparse_table, std::string const & filename); + + /** + * Loads a SparseTable from the file given by filename. + * @throws util::FileException if the file does not exist or if the header does not matches. + */ + static void load_from_file(std::string const & filename, SparseTable * sparse_table); +}; + +template std::size_t +SparseTable::get_nnz(void) const { + return nnz; +} + +template C +SparseTable::cols() const { + return column_wise_data.size(); +} + +template R +SparseTable::rows() const { + return row_wise_data.size(); +} + +template typename SparseTable::Column const & +SparseTable::col(C id) const { + return column_wise_data[id]; +} + +template typename SparseTable::Row const & +SparseTable::row(R id) const { + return row_wise_data[id]; +} + +template +SparseTable::SparseTable() { + nnz = 0; +} + +template +SparseTable::SparseTable(C cols, R rows) { + column_wise_data.resize(cols); + row_wise_data.resize(rows); + nnz = 0; +} + +template void +SparseTable::set_value(C col, R row, T value) { + column_wise_data[col].push_back(std::pair(row, value)); + row_wise_data[row].push_back(std::pair(col, value)); + nnz++; +} + +template void +SparseTable::save_to_file(SparseTable const & sparse_table, const std::string &filename) { + std::ofstream out(filename.c_str(), std::ios::binary); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + C cols = sparse_table.cols(); + R rows = sparse_table.rows(); + std::size_t nnz = sparse_table.get_nnz(); + out << HEADER << " " << VERSION << " " << cols << " " << rows << " " << nnz << std::endl; + + for (C col = 0; col < cols; ++col) { + SparseTable::Column column = sparse_table.col(col); + for (std::size_t i = 0; i < column.size(); ++i) { + std::pair entry = column[i]; + R row = entry.first; + T value = entry.second; + + out.write((char const*)&col, sizeof(C)); + out.write((char const*)&row, sizeof(R)); + out.write((char const*)&value, sizeof(T)); + } + } + out.close(); +} + +template void +SparseTable::load_from_file(const std::string & filename, SparseTable * sparse_table) { + std::ifstream in(filename.c_str(), std::ios::binary); + if (!in.good()) + throw util::FileException(filename, std::strerror(errno)); + + std::string header; + + in >> header; + + if (header != HEADER) { + in.close(); + throw util::FileException(filename, "Not a SparseTable file!"); + } + + std::string version; + in >> version; + + if (version != VERSION) { + in.close(); + throw util::FileException(filename, "Incompatible version of SparseTable file!"); + } + + C cols; + R rows; + std::size_t nnz; + in >> cols >> rows >> nnz; + + if (cols != sparse_table->cols() || rows != sparse_table->rows()) { + in.close(); + throw util::FileException(filename, "SparseTable has different dimension!"); + } + + std::string buffer; + + /* Discard the rest of the line. */ + std::getline(in, buffer); + + C col; + R row; + T value; + for (std::size_t i = 0; i < nnz; ++i) { + in.read((char*)&col, sizeof(C)); + in.read((char*)&row, sizeof(R)); + in.read((char*)&value, sizeof(T)); + sparse_table->set_value(col, row, value); + } + + in.close(); +} + +#endif /* TEX_SPARSETABLE_HEADER */ diff --git a/texturing/texture_atlas.cpp b/texturing/texture_atlas.cpp new file mode 100644 index 0000000..420ec9f --- /dev/null +++ b/texturing/texture_atlas.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include + +#include +#include +#include + +#include "texture_atlas.h" + +TextureAtlas::TextureAtlas(unsigned int size) : + size(size), padding(size >> 7), finalized(false) { + + bin = RectangularBin::create(size, size); + image = core::ByteImage::create(size, size, 3); + validity_mask = core::ByteImage::create(size, size, 1); +} + +/** + * Copies the src image into the dest image at the given position, + * optionally adding a border. + * @warning asserts that the given src image fits into the given dest image. + */ +void copy_into(core::ByteImage::ConstPtr src, int x, int y, + core::ByteImage::Ptr dest, int border = 0) { + + assert(x >= 0 && x + src->width() + 2 * border <= dest->width()); + assert(y >= 0 && y + src->height() + 2 * border <= dest->height()); + + for (int i = 0; i < src->width() + 2 * border; ++i) { + for(int j = 0; j < src->height() + 2 * border; j++) { + int sx = i - border; + int sy = j - border; + + if (sx < 0 || sx >= src->width() || sy < 0 || sy >= src->height()) + continue; + + for (int c = 0; c < src->channels(); ++c) { + dest->at(x + i, y + j, c) = src->at(sx, sy, c); + } + } + } +} + +typedef std::vector > PixelVector; +typedef std::set > PixelSet; + +bool +TextureAtlas::insert(TexturePatch::ConstPtr texture_patch) { + if (finalized) { + throw util::Exception("No insertion possible, TextureAtlas already finalized"); + } + + assert(bin != NULL); + assert(validity_mask != NULL); + + int const width = texture_patch->get_width() + 2 * padding; + int const height = texture_patch->get_height() + 2 * padding; + Rect rect(0, 0, width, height); + if (!bin->insert(&rect)) return false; + + /* Update texture atlas and its validity mask. */ + core::ByteImage::Ptr patch_image = core::image::float_to_byte_image( + texture_patch->get_image(), 0.0f, 1.0f); + + copy_into(patch_image, rect.min_x, rect.min_y, image, padding); + core::ByteImage::ConstPtr patch_validity_mask = texture_patch->get_validity_mask(); + copy_into(patch_validity_mask, rect.min_x, rect.min_y, validity_mask, padding); + + TexturePatch::Faces const & patch_faces = texture_patch->get_faces(); + TexturePatch::Texcoords const & patch_texcoords = texture_patch->get_texcoords(); + + /* Calculate the offset of the texture patches' relative texture coordinates */ + math::Vec2f offset = math::Vec2f(rect.min_x + padding, rect.min_y + padding); + + faces.insert(faces.end(), patch_faces.begin(), patch_faces.end()); + + /* Calculate the final textcoords of the faces. */ + for (std::size_t i = 0; i < patch_faces.size(); ++i) { + for (int j = 0; j < 3; ++j) { + math::Vec2f rel_texcoord(patch_texcoords[i * 3 + j]); + math::Vec2f texcoord = rel_texcoord + offset; + + texcoord[0] = texcoord[0] / this->size; + texcoord[1] = texcoord[1] / this->size; + texcoords.push_back(texcoord); + } + } + return true; +} + + + +void +TextureAtlas::apply_edge_padding(void) { + assert(image != NULL); + assert(validity_mask != NULL); + + const int width = image->width(); + const int height = image->height(); + + math::Matrix gauss; + gauss[0] = 1.0f; gauss[1] = 2.0f; gauss[2] = 1.0f; + gauss[3] = 2.0f; gauss[4] = 4.0f; gauss[5] = 2.0f; + gauss[6] = 1.0f; gauss[7] = 2.0f; gauss[8] = 1.0f; + gauss /= 16.0f; + + /* Calculate the set of invalid pixels at the border of texture patches. */ + PixelSet invalid_border_pixels; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (validity_mask->at(x, y, 0) == 255) continue; + + /* Check the direct neighbourhood of all invalid pixels. */ + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + int nx = x + i; + int ny = y + j; + /* If the invalid pixel has a valid neighbour: */ + if (0 <= nx && nx < width && + 0 <= ny && ny < height && + validity_mask->at(nx, ny, 0) == 255) { + + /* Add the pixel to the set of invalid border pixels. */ + invalid_border_pixels.insert(std::pair(x, y)); + } + } + } + } + } + + core::ByteImage::Ptr new_validity_mask = validity_mask->duplicate(); + + /* Iteratively dilate border pixels until padding constants are reached. */ + for (unsigned int n = 0; n <= padding; ++n) { + PixelVector new_valid_pixels; + + PixelSet::iterator it = invalid_border_pixels.begin(); + for (;it != invalid_border_pixels.end(); it++) { + int x = it->first; + int y = it->second; + + bool now_valid = false; + /* Calculate new pixel value. */ + for (int c = 0; c < 3; ++c) { + float norm = 0.0f; + float value = 0.0f; + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + int nx = x + i; + int ny = y + j; + if (0 <= nx && nx < width && + 0 <= ny && ny < height && + new_validity_mask->at(nx, ny, 0) == 255) { + + float w = gauss[(j + 1) * 3 + (i + 1)]; + norm += w; + value += (image->at(nx, ny, c) / 255.0f) * w; + } + } + } + + if (norm == 0.0f) + continue; + + now_valid = true; + image->at(x, y, c) = (value / norm) * 255.0f; + } + + if (now_valid) { + new_valid_pixels.push_back(*it); + } + } + + invalid_border_pixels.clear(); + + /* Mark the new valid pixels valid in the validity mask. */ + for (std::size_t i = 0; i < new_valid_pixels.size(); ++i) { + int x = new_valid_pixels[i].first; + int y = new_valid_pixels[i].second; + + new_validity_mask->at(x, y, 0) = 255; + } + + /* Calculate the set of invalid pixels at the border of the valid area. */ + for (std::size_t i = 0; i < new_valid_pixels.size(); ++i) { + int x = new_valid_pixels[i].first; + int y = new_valid_pixels[i].second; + + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + int nx = x + i; + int ny = y + j; + if (0 <= nx && nx < width && + 0 <= ny && ny < height && + new_validity_mask->at(nx, ny, 0) == 0) { + + invalid_border_pixels.insert(std::pair(nx, ny)); + } + } + } + } + } +} + +struct VectorCompare { + bool operator()(math::Vec2f const & lhs, math::Vec2f const & rhs) const { + return lhs[0] < rhs[0] || (lhs[0] == rhs[0] && lhs[1] < rhs[1]); + } +}; + +typedef std::map TexcoordMap; + +void +TextureAtlas::merge_texcoords() { + Texcoords tmp; tmp.swap(this->texcoords); + + TexcoordMap texcoord_map; + for (math::Vec2f const & texcoord : tmp) { + TexcoordMap::iterator iter = texcoord_map.find(texcoord); + if (iter == texcoord_map.end()) { + std::size_t texcoord_id = this->texcoords.size(); + texcoord_map[texcoord] = texcoord_id; + this->texcoords.push_back(texcoord); + this->texcoord_ids.push_back(texcoord_id); + } else { + this->texcoord_ids.push_back(iter->second); + } + } + +} + +void +TextureAtlas::finalize() { + if (finalized) { + throw util::Exception("TextureAtlas already finalized"); + } + + this->bin.reset(); + this->apply_edge_padding(); + this->validity_mask.reset(); + this->merge_texcoords(); + + this->finalized = true; +} diff --git a/texturing/texture_atlas.h b/texturing/texture_atlas.h new file mode 100644 index 0000000..f1184f9 --- /dev/null +++ b/texturing/texture_atlas.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TEXTUREATLAS_HEADER +#define TEX_TEXTUREATLAS_HEADER + + +#include + +#include +#include +#include +#include + +#include "tri.h" +#include "texture_patch.h" +#include "rectangular_bin.h" + +/** + * Class representing a texture atlas. + */ +class TextureAtlas { + public: + typedef std::shared_ptr Ptr; + typedef std::vector Faces; + typedef std::vector TexcoordIds; + typedef std::vector Texcoords; + + private: + unsigned int const size; + unsigned int const padding; + bool finalized; + + Faces faces; + Texcoords texcoords; + TexcoordIds texcoord_ids; + + core::ByteImage::Ptr image; + core::ByteImage::Ptr validity_mask; + + RectangularBin::Ptr bin; + + void apply_edge_padding(void); + void merge_texcoords(void); + + public: + TextureAtlas(unsigned int size); + + static TextureAtlas::Ptr create(unsigned int size); + + Faces const & get_faces(void) const; + TexcoordIds const & get_texcoord_ids(void) const; + Texcoords const & get_texcoords(void) const; + core::ByteImage::ConstPtr get_image(void) const; + + bool insert(TexturePatch::ConstPtr texture_patch); + + void finalize(void); +}; + +inline TextureAtlas::Ptr +TextureAtlas::create(unsigned int size) { + return Ptr(new TextureAtlas(size)); +} + +inline TextureAtlas::Faces const & +TextureAtlas::get_faces(void) const { + return faces; +} + +inline TextureAtlas::TexcoordIds const & +TextureAtlas::get_texcoord_ids(void) const { + return texcoord_ids; +} + +inline TextureAtlas::Texcoords const & +TextureAtlas::get_texcoords(void) const { + return texcoords; +} + +inline core::ByteImage::ConstPtr +TextureAtlas::get_image(void) const { + if (!finalized) { + throw util::Exception("Texture atlas not finalized"); + } + return image; +} + +#endif /* TEX_TEXTUREATLAS_HEADER */ diff --git a/texturing/texture_patch.cpp b/texturing/texture_patch.cpp new file mode 100644 index 0000000..c39fa72 --- /dev/null +++ b/texturing/texture_patch.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include +#include +#include +#include + +#include "texture_patch.h" + +TexturePatch::TexturePatch(int label, + std::vector const & faces, + std::vector const & texcoords, + core::FloatImage::Ptr image) + : label(label), faces(faces), texcoords(texcoords), image(image) { + + validity_mask = core::ByteImage::create(get_width(), get_height(), 1); + validity_mask->fill(255); + blending_mask = core::ByteImage::create(get_width(), get_height(), 1); +} + +TexturePatch::TexturePatch(TexturePatch const & texture_patch) { + label = texture_patch.label; + faces = std::vector(texture_patch.faces); + texcoords = std::vector(texture_patch.texcoords); + image = texture_patch.image->duplicate(); + validity_mask = texture_patch.validity_mask->duplicate(); + if (texture_patch.blending_mask != NULL) { + blending_mask = texture_patch.blending_mask->duplicate(); + } +} + +const float sqrt_2 = sqrt(2); + +void +TexturePatch::adjust_colors(std::vector const & adjust_values) { + assert(blending_mask != NULL); + + validity_mask->fill(0); + + core::FloatImage::Ptr iadjust_values = core::FloatImage::create(get_width(), get_height(), 3); + for (std::size_t i = 0; i < texcoords.size(); i += 3) { + math::Vec2f v1 = texcoords[i]; + math::Vec2f v2 = texcoords[i + 1]; + math::Vec2f v3 = texcoords[i + 2]; + + Tri tri(v1, v2, v3); + + float area = tri.get_area(); + if (area < std::numeric_limits::epsilon()) continue; + + Rect aabb = tri.get_aabb(); + int const min_x = static_cast(std::floor(aabb.min_x)) - texture_patch_border; + int const min_y = static_cast(std::floor(aabb.min_y)) - texture_patch_border; + int const max_x = static_cast(std::ceil(aabb.max_x)) + texture_patch_border; + int const max_y = static_cast(std::ceil(aabb.max_y)) + texture_patch_border; + assert(0 <= min_x && max_x <= get_width()); + assert(0 <= min_y && max_y <= get_height()); + + for (int y = min_y; y < max_y; ++y) { + for (int x = min_x; x < max_x; ++x) { + + math::Vec3f bcoords = tri.get_barycentric_coords(x, y); + bool inside = bcoords.minimum() >= 0.0f; + if (inside) { + assert(x != 0 && y != 0); + for (int c = 0; c < 3; ++c) { + iadjust_values->at(x, y, c) = math::interpolate( + adjust_values[i][c], adjust_values[i + 1][c], adjust_values[i + 2][c], + bcoords[0], bcoords[1], bcoords[2]); + } + validity_mask->at(x, y, 0) = 255; + blending_mask->at(x, y, 0) = 255; + } else { + + if (validity_mask->at(x, y, 0) == 255) + continue; + + /* Check whether the pixels distance from the triangle is more than one pixel. */ + float ha = 2.0f * -bcoords[0] * area / (v2 - v3).norm(); + float hb = 2.0f * -bcoords[1] * area / (v1 - v3).norm(); + float hc = 2.0f * -bcoords[2] * area / (v1 - v2).norm(); + + if (ha > sqrt_2 || hb > sqrt_2 || hc > sqrt_2) + continue; + + for (int c = 0; c < 3; ++c) { + iadjust_values->at(x, y, c) = math::interpolate( + adjust_values[i][c], adjust_values[i + 1][c], adjust_values[i + 2][c], + bcoords[0], bcoords[1], bcoords[2]); + } + validity_mask->at(x, y, 0) = 255; + blending_mask->at(x, y, 0) = 64; + } + } + } + } + + for (int i = 0; i < image->get_pixel_amount(); ++i) { + if (validity_mask->at(i, 0) != 0){ + for (int c = 0; c < 3; ++c) { + image->at(i, c) += iadjust_values->at(i, c); + } + } else { + math::Vec3f color(0.0f, 0.0f, 0.0f); + //DEBUG math::Vec3f color(1.0f, 0.0f, 1.0f); + std::copy(color.begin(), color.end(), &image->at(i, 0)); + } + } +} + +bool TexturePatch::valid_pixel(math::Vec2f pixel) const { + float x = pixel[0]; + float y = pixel[1]; + + float const height = static_cast(get_height()); + float const width = static_cast(get_width()); + + bool valid = (0.0f <= x && x < width && 0.0f <= y && y < height); + if (valid && validity_mask != NULL){ + /* Only pixel which can be correctly interpolated are valid. */ + float cx = std::max(0.0f, std::min(width - 1.0f, x)); + float cy = std::max(0.0f, std::min(height - 1.0f, y)); + int const floor_x = static_cast(cx); + int const floor_y = static_cast(cy); + int const floor_xp1 = std::min(floor_x + 1, get_width() - 1); + int const floor_yp1 = std::min(floor_y + 1, get_height() - 1); + + float const w1 = cx - static_cast(floor_x); + float const w0 = 1.0f - w1; + float const w3 = cy - static_cast(floor_y); + float const w2 = 1.0f - w3; + + valid = (w0 * w2 == 0.0f || validity_mask->at(floor_x, floor_y, 0) == 255) && + (w1 * w2 == 0.0f || validity_mask->at(floor_xp1, floor_y, 0) == 255) && + (w0 * w3 == 0.0f || validity_mask->at(floor_x, floor_yp1, 0) == 255) && + (w1 * w3 == 0.0f || validity_mask->at(floor_xp1, floor_yp1, 0) == 255); + } + + return valid; +} + +bool +TexturePatch::valid_pixel(math::Vec2i pixel) const { + int const x = pixel[0]; + int const y = pixel[1]; + + bool valid = (0 <= x && x < get_width() && 0 <= y && y < get_height()); + if (valid && validity_mask != NULL) { + valid = validity_mask->at(x, y, 0) == 255; + } + + return valid; +} + +math::Vec3f +TexturePatch::get_pixel_value(math::Vec2f pixel) const { + assert(valid_pixel(pixel)); + + math::Vec3f color; + image->linear_at(pixel[0], pixel[1], *color); + return color; +} + +void +TexturePatch::set_pixel_value(math::Vec2i pixel, math::Vec3f color) { + assert(blending_mask != NULL); + assert(valid_pixel(pixel)); + + std::copy(color.begin(), color.end(), &image->at(pixel[0], pixel[1], 0)); + blending_mask->at(pixel[0], pixel[1], 0) = 128; +} + +void +TexturePatch::blend(core::FloatImage::ConstPtr orig) { + poisson_blend(orig, blending_mask, image, 1.0f); + + /* Invalidate all pixels outside the boundary. */ + for (int y = 0; y < blending_mask->height(); ++y) { + for (int x = 0; x < blending_mask->width(); ++x) { + if (blending_mask->at(x, y, 0) == 64) { + validity_mask->at(x, y, 0) = 0; + } + } + } +} + +typedef std::vector > PixelVector; +typedef std::set > PixelSet; + +void +TexturePatch::prepare_blending_mask(std::size_t strip_width){ + int const width = blending_mask->width(); + int const height = blending_mask->height(); + + /* Calculate the set of valid pixels at the border of texture patch. */ + PixelSet valid_border_pixels; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (validity_mask->at(x, y, 0) == 0) continue; + + /* Valid border pixels need no invalid neighbours. */ + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + valid_border_pixels.insert(std::pair(x, y)); + continue; + } + + /* Check the direct neighbourhood of all invalid pixels. */ + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + int nx = x + i; + int ny = y + j; + /* If the valid pixel has a invalid neighbour: */ + if (validity_mask->at(nx, ny, 0) == 0) { + /* Add the pixel to the set of valid border pixels. */ + valid_border_pixels.insert(std::pair(x, y)); + } + } + } + } + } + + core::ByteImage::Ptr inner_pixel = validity_mask->duplicate(); + + /* Iteratively erode all border pixels. */ + for (std::size_t i = 0; i < strip_width; ++i) { + PixelVector new_invalid_pixels(valid_border_pixels.begin(), valid_border_pixels.end()); + PixelVector::iterator it; + valid_border_pixels.clear(); + + /* Mark the new invalid pixels invalid in the validity mask. */ + for (it = new_invalid_pixels.begin(); it != new_invalid_pixels.end(); ++it) { + int x = it->first; + int y = it->second; + + inner_pixel->at(x, y, 0) = 0; + } + + /* Calculate the set of valid pixels at the border of the valid area. */ + for (it = new_invalid_pixels.begin(); it != new_invalid_pixels.end(); ++it) { + int x = it->first; + int y = it->second; + + for (int j = -1; j <= 1; j++){ + for (int i = -1; i <= 1; i++){ + int nx = x + i; + int ny = y + j; + if (0 <= nx && nx < width && + 0 <= ny && ny < height && + inner_pixel->at(nx, ny, 0) == 255) { + + valid_border_pixels.insert(std::pair(nx, ny)); + } + } + } + } + } + + /* Sanitize blending mask. */ + for (int y = 1; y < height - 1; ++y) { + for (int x = 1; x < width - 1; ++x) { + if (blending_mask->at(x, y, 0) == 128) { + uint8_t n[] = {blending_mask->at(x - 1, y, 0), + blending_mask->at(x + 1, y, 0), + blending_mask->at(x, y - 1, 0), + blending_mask->at(x, y + 1, 0) + }; + bool valid = true; + for (uint8_t v : n) { + if (v == 255) continue; + valid = false; + } + if (valid) blending_mask->at(x, y, 0) = 255; + } + } + } + + /* Mark all remaining pixels invalid in the blending_mask. */ + for (int i = 0; i < inner_pixel->get_pixel_amount(); ++i) { + if (inner_pixel->at(i) == 255) blending_mask->at(i) = 0; + } + + /* Mark all border pixels. */ + PixelSet::iterator it; + for (it = valid_border_pixels.begin(); it != valid_border_pixels.end(); ++it) { + int x = it->first; + int y = it->second; + + blending_mask->at(x, y, 0) = 128; + } +} diff --git a/texturing/texture_patch.h b/texturing/texture_patch.h new file mode 100644 index 0000000..2200813 --- /dev/null +++ b/texturing/texture_patch.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TEXTUREPATCH_HEADER +#define TEX_TEXTUREPATCH_HEADER + +#include + +#include +#include + +#include "tri.h" +#include "poisson_blending.h" + +int const texture_patch_border = 1; + +/** + * Class representing a texture patch. + * Contains additionaly to the rectangular part of the TextureView + * the faces which it textures and their relative texture coordinates. + */ +class TexturePatch { + public: + typedef std::shared_ptr Ptr; + typedef std::shared_ptr ConstPtr; + typedef std::vector Faces; + typedef std::vector Texcoords; + + private: + int label; + Faces faces; + Texcoords texcoords; + core::FloatImage::Ptr image; + core::ByteImage::Ptr validity_mask; + core::ByteImage::Ptr blending_mask; + + public: + /** Constructs a texture patch. */ + TexturePatch(int _label, std::vector const & _faces, + std::vector const & _texcoords, core::FloatImage::Ptr _image); + + TexturePatch(TexturePatch const & texture_patch); + + static TexturePatch::Ptr create(TexturePatch::ConstPtr texture_patch); + static TexturePatch::Ptr create(int label, std::vector const & faces, + std::vector const & texcoords, core::FloatImage::Ptr image); + + TexturePatch::Ptr duplicate(void); + + /** Adjust the image colors and update validity mask. */ + void adjust_colors(std::vector const & adjust_values); + + math::Vec3f get_pixel_value(math::Vec2f pixel) const; + void set_pixel_value(math::Vec2i pixel, math::Vec3f color); + + bool valid_pixel(math::Vec2i pixel) const; + bool valid_pixel(math::Vec2f pixel) const; + + std::vector & get_faces(void); + std::vector const & get_faces(void) const; + std::vector & get_texcoords(void); + std::vector const & get_texcoords(void) const; + + core::FloatImage::Ptr get_image(void); + + core::FloatImage::ConstPtr get_image(void) const; + core::ByteImage::ConstPtr get_validity_mask(void) const; + core::ByteImage::ConstPtr get_blending_mask(void) const; + + std::pair get_min_max(void) const; + + void release_blending_mask(void); + void prepare_blending_mask(std::size_t strip_width); + + void erode_validity_mask(void); + + void blend(core::FloatImage::ConstPtr orig); + + int get_label(void) const; + int get_width(void) const; + int get_height(void) const; + int get_size(void) const; +}; + +inline TexturePatch::Ptr +TexturePatch::create(TexturePatch::ConstPtr texture_patch) { + return std::make_shared(*texture_patch); +} + +inline TexturePatch::Ptr +TexturePatch::create(int label, std::vector const & faces, + std::vector const & texcoords, core::FloatImage::Ptr image) { + return std::make_shared(label, faces, texcoords, image); +} + +inline TexturePatch::Ptr +TexturePatch::duplicate(void) { + return Ptr(new TexturePatch(*this)); +} + +inline int +TexturePatch::get_label(void) const { + return label; +} + +inline int +TexturePatch::get_width(void) const { + return image->width(); +} + +inline int +TexturePatch::get_height(void) const { + return image->height(); +} + +inline core::FloatImage::Ptr +TexturePatch::get_image(void) { + return image; +} + +inline core::FloatImage::ConstPtr +TexturePatch::get_image(void) const { + return image; +} + +inline core::ByteImage::ConstPtr +TexturePatch::get_validity_mask(void) const { + return validity_mask; +} + +inline core::ByteImage::ConstPtr +TexturePatch::get_blending_mask(void) const { + assert(blending_mask != NULL); + return blending_mask; +} + +inline void +TexturePatch::release_blending_mask(void) { + assert(blending_mask != NULL); + blending_mask.reset(); +} + +inline std::vector & +TexturePatch::get_texcoords(void) { + return texcoords; +} + +inline std::vector & +TexturePatch::get_faces(void) { + return faces; +} + +inline std::vector const & +TexturePatch::get_texcoords(void) const { + return texcoords; +} + +inline std::vector const & +TexturePatch::get_faces(void) const { + return faces; +} + +inline int +TexturePatch::get_size(void) const { + return get_width() * get_height(); +} + +#endif /* TEX_TEXTUREPATCH_HEADER */ diff --git a/texturing/texture_view.cpp b/texturing/texture_view.cpp new file mode 100644 index 0000000..75705d3 --- /dev/null +++ b/texturing/texture_view.cpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include +#include +#include + +#include "texture_view.h" + +TEX_NAMESPACE_BEGIN + +TextureView::TextureView(std::size_t id, core::CameraInfo const & camera, + std::string const & image_file) + : id(id), image_file(image_file) { + + core::image::ImageHeaders header; + try { + header = core::image::load_file_headers(image_file); + } catch (util::Exception e) { + std::cerr << "Could not load image header of " << image_file << std::endl; + std::cerr << e.what() << std::endl; + std::exit(EXIT_FAILURE); + } + + // width and height of camera + width = header.width; + height = header.height; + + //projection matrix of the camera + camera.fill_calibration(*projection, width, height); + + // position of the camera + camera.fill_camera_pos(*pos); + + // viewing dir of the camera + camera.fill_viewing_direction(*viewdir); + + // extrinsic matrix + camera.fill_world_to_cam(*world_to_cam); +} + +void +TextureView::generate_validity_mask(void) { + assert(image != NULL); + validity_mask.resize(width * height, true); + core::ByteImage::Ptr checked = core::ByteImage::create(width, height, 1); + + std::list queue; + + /* Start from the corners. */ + queue.push_back(math::Vec2i(0,0)); + checked->at(0, 0, 0) = 255; + queue.push_back(math::Vec2i(0, height - 1)); + checked->at(0, height - 1, 0) = 255; + queue.push_back(math::Vec2i(width - 1, 0)); + checked->at(width - 1, 0, 0) = 255; + queue.push_back(math::Vec2i(width - 1, height - 1)); + checked->at(width - 1, height - 1, 0) = 255; + + while (!queue.empty()) { + math::Vec2i pixel = queue.front(); + queue.pop_front(); + + int const x = pixel[0]; + int const y = pixel[1]; + + int sum = 0; + for (int c = 0; c < image->channels(); ++c) { + sum += image->at(x, y, c); + } + + if (sum == 0) { + validity_mask[x + y * width] = false; + + std::vector neighbours; + neighbours.push_back(math::Vec2i(x + 1, y)); + neighbours.push_back(math::Vec2i(x, y + 1)); + neighbours.push_back(math::Vec2i(x - 1, y)); + neighbours.push_back(math::Vec2i(x, y - 1)); + + for (std::size_t i = 0; i < neighbours.size(); ++i) { + math::Vec2i npixel = neighbours[i]; + int const nx = npixel[0]; + int const ny = npixel[1]; + if (0 <= nx && nx < width && 0 <= ny && ny < height) { + if (checked->at(nx, ny, 0) == 0) { + queue.push_front(npixel); + checked->at(nx, ny, 0) = 255; + } + } + } + } + } +} + +void +TextureView::load_image(void) { + if(image != NULL) return; + image = core::image::load_file(image_file); +} + +void +TextureView::generate_gradient_magnitude(void) { + assert(image != NULL); + // convert color image to gray one + core::ByteImage::Ptr bw = core::image::desaturate(image, core::image::DESATURATE_LUMINANCE); + gradient_magnitude = core::image::sobel_edge(bw); +} + +void +TextureView::erode_validity_mask(void) { + std::vector eroded_validity_mask(validity_mask); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + validity_mask[x + y * width] = false; + continue; + } + + if (validity_mask[x + y * width]) continue; + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + int const nx = x + i; + int const ny = y + j; + eroded_validity_mask[nx + ny * width] = false; + } + } + } + } + + validity_mask.swap(eroded_validity_mask); +} + +void +TextureView::get_face_info(math::Vec3f const & v1, math::Vec3f const & v2, + math::Vec3f const & v3, FaceProjectionInfo * face_info, Settings const & settings) const { + + assert(image != NULL); + assert(settings.data_term != DATA_TERM_GMI || gradient_magnitude != NULL); + + math::Vec2f p1 = get_pixel_coords(v1); + math::Vec2f p2 = get_pixel_coords(v2); + math::Vec2f p3 = get_pixel_coords(v3); + + assert(valid_pixel(p1) && valid_pixel(p2) && valid_pixel(p3)); + + // calculate the area of the triangle + Tri tri(p1, p2, p3); + float area = tri.get_area(); + + if (area < std::numeric_limits::epsilon()) { + face_info->quality = 0.0f; + return; + } + + std::size_t num_samples = 0; + math::Vec3d colors(0.0); + double gmi = 0.0; + + bool sampling_necessary = settings.data_term != DATA_TERM_AREA || settings.outlier_removal != OUTLIER_REMOVAL_NONE; + + if (sampling_necessary && area > 0.5f) { + /* Sort pixels in ascending order of y */ + while (true) + if(p1[1] <= p2[1]) + if(p2[1] <= p3[1]) break; + else std::swap(p2, p3); + else std::swap(p1, p2); + + /* Calculate line equations. */ + float const m1 = (p1[1] - p3[1]) / (p1[0] - p3[0]); + float const b1 = p1[1] - m1 * p1[0]; + + /* area != 0.0f => m1 != 0.0f. */ + float const m2 = (p1[1] - p2[1]) / (p1[0] - p2[0]); + float const b2 = p1[1] - m2 * p1[0]; + + float const m3 = (p2[1] - p3[1]) / (p2[0] - p3[0]); + float const b3 = p2[1] - m3 * p2[0]; + + bool fast_sampling_possible = std::isfinite(m1) && m2 != 0.0f && std::isfinite(m2) && m3 != 0.0f && std::isfinite(m3); + + Rect aabb = tri.get_aabb(); + for (int y = std::floor(aabb.min_y); y < std::ceil(aabb.max_y); ++y) { + float min_x = aabb.min_x - 0.5f; + float max_x = aabb.max_x + 0.5f; + + if (fast_sampling_possible) { + float const cy = static_cast(y) + 0.5f; + + min_x = (cy - b1) / m1; + if (cy <= p2[1]) max_x = (cy - b2) / m2; + else max_x = (cy - b3) / m3; + + if (min_x >= max_x) std::swap(min_x, max_x); + + if (min_x < aabb.min_x || min_x > aabb.max_x) continue; + if (max_x < aabb.min_x || max_x > aabb.max_x) continue; + } + + for (int x = std::floor(min_x + 0.5f); x < std::ceil(max_x - 0.5f); ++x) { + math::Vec3d color; + + const float cx = static_cast(x) + 0.5f; + const float cy = static_cast(y) + 0.5f; + if (!fast_sampling_possible && !tri.inside(cx, cy)) continue; + + // get colors of each sample + if (settings.outlier_removal != OUTLIER_REMOVAL_NONE) { + for (std::size_t i = 0; i < 3; i++){ + color[i] = static_cast(image->at(x, y, i)) / 255.0; + } + colors += color; + } + + // gradient of each sample + if (settings.data_term == DATA_TERM_GMI) { + gmi += static_cast(gradient_magnitude->at(x, y, 0)) / 255.0; + } + ++num_samples; + } + } + } + + // mean gradient weighted by the area of triangle + if (settings.data_term == DATA_TERM_GMI) { + if (num_samples > 0) { + gmi = (gmi / num_samples) * area; + } else { + double gmv1 = static_cast(gradient_magnitude->linear_at(p1[0], p1[1], 0)) / 255.0; + double gmv2 = static_cast(gradient_magnitude->linear_at(p2[0], p2[1], 0)) / 255.0; + double gmv3 = static_cast(gradient_magnitude->linear_at(p3[0], p3[1], 0)) / 255.0; + gmi = ((gmv1 + gmv2 + gmv3) / 3.0) * area; + } + } + + // mean color of the triangle + if (settings.outlier_removal != OUTLIER_REMOVAL_NONE) { + if (num_samples > 0) { + face_info->mean_color = colors / num_samples; + } else { + math::Vec3d c1, c2, c3; + for (std::size_t i = 0; i < 3; ++i) { + c1[i] = static_cast(image->linear_at(p1[0], p1[1], i)) / 255.0; + c2[i] = static_cast(image->linear_at(p2[0], p2[1], i)) / 255.0; + c3[i] = static_cast(image->linear_at(p3[0], p3[1], i)) / 255.0; + } + face_info->mean_color = ((c1 + c2 + c3) / 3.0); + } + } + + switch (settings.data_term) { + case DATA_TERM_AREA: face_info->quality = area; break; + case DATA_TERM_GMI: face_info->quality = gmi; break; + } +} + +bool +TextureView::valid_pixel(math::Vec2f pixel) const { + float const x = pixel[0]; + float const y = pixel[1]; + + /* The center of a pixel is in the middle. */ + bool valid = (x >= 0.0f && x < static_cast(width - 1) + && y >= 0.0f && y < static_cast(height - 1)); + + if (valid && validity_mask.size() == static_cast(width * height)) { + /* Only pixel which can be correctly interpolated are valid. */ + float cx = std::max(0.0f, std::min(static_cast(width - 1), x)); + float cy = std::max(0.0f, std::min(static_cast(height - 1), y)); + int const floor_x = static_cast(cx); + int const floor_y = static_cast(cy); + int const floor_xp1 = std::min(floor_x + 1, width - 1); + int const floor_yp1 = std::min(floor_y + 1, height - 1); + + /* We screw up if weights would be zero + * e.g. we lose valid pixel in the border of images... */ + + valid = validity_mask[floor_x + floor_y * width] && + validity_mask[floor_x + floor_yp1 * width] && + validity_mask[floor_xp1 + floor_y * width] && + validity_mask[floor_xp1 + floor_yp1 * width]; + } + + return valid; +} + +void +TextureView::export_triangle(math::Vec3f v1, math::Vec3f v2, math::Vec3f v3, + std::string const & filename) const { + assert(image != NULL); + math::Vec2f p1 = get_pixel_coords(v1); + math::Vec2f p2 = get_pixel_coords(v2); + math::Vec2f p3 = get_pixel_coords(v3); + + assert(valid_pixel(p1) && valid_pixel(p2) && valid_pixel(p3)); + + Tri tri(p1, p2, p3); + + Rect aabb = tri.get_aabb(); + const int width = ceil(aabb.width()); + const int height = ceil(aabb.height()); + const int left = floor(aabb.min_x); + const int top = floor(aabb.max_y); + + assert(width > 0 && height > 0); + core::image::save_png_file(core::image::crop(image, width, height, left, top, + *math::Vec3uc(255, 0, 255)), filename); +} + +void +TextureView::export_validity_mask(std::string const & filename) const { + assert(validity_mask.size() == static_cast(width * height)); + core::ByteImage::Ptr img = core::ByteImage::create(width, height, 1); + for (std::size_t i = 0; i < validity_mask.size(); ++i) { + img->at(static_cast(i), 0) = validity_mask[i] ? 255 : 0; + } + core::image::save_png_file(img, filename); +} + +TEX_NAMESPACE_END diff --git a/texturing/texture_view.h b/texturing/texture_view.h new file mode 100644 index 0000000..1742fc6 --- /dev/null +++ b/texturing/texture_view.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TEXTUREVIEW_HEADER +#define TEX_TEXTUREVIEW_HEADER + +#include +#include + +#include +#include +#include + +#include "tri.h" +#include "settings.h" + +TEX_NAMESPACE_BEGIN + +/** Struct containing the quality and mean color of a face within a view. */ +struct FaceProjectionInfo { + std::uint16_t view_id; + float quality; + math::Vec3f mean_color; + + bool operator<(FaceProjectionInfo const & other) const { + return view_id < other.view_id; + } +}; + +/** + * Class representing a view with specialized functions for texturing. + */ +class TextureView { + private: + std::size_t id; + + // position of the camera + math::Vec3f pos; + + // the view dir of the camera + math::Vec3f viewdir; + + // projection matrix (intrinsic matrix) + math::Matrix3f projection; + + // transform from world to camera (extrinsic matrix) + math::Matrix4f world_to_cam; + + // image width + int width; + + // image height + int height; + + // image file name + std::string image_file; + + // image data + core::ByteImage::Ptr image; + + // gradient of the image + core::ByteImage::Ptr gradient_magnitude; + + // validity mask for pixel selection + std::vector validity_mask; + + + public: + /** Returns the id of the TexureView which is consistent for every run. */ + std::size_t get_id(void) const; + + /** Returns the 2D pixel coordinates of the given vertex projected into the view. */ + math::Vec2f get_pixel_coords(math::Vec3f const & vertex) const; + + /** Returns the RGB pixel values [0, 1] for the given vertex projected into the view, calculated by linear interpolation. */ + math::Vec3f get_pixel_values(math::Vec3f const & vertex) const; + + /** Returns whether the pixel location is valid in this view. + * The pixel location is valid if its inside the visible area and, + * if a validity mask has been generated, all surrounding (integer coordinate) pixels are valid in the validity mask. + */ + bool valid_pixel(math::Vec2f pixel) const; + + /** TODO */ + bool inside(math::Vec3f const & v1, math::Vec3f const & v2, math::Vec3f const & v3) const; + + /** Returns the RGB pixel values [0, 1] for the give pixel location. */ + math::Vec3f get_pixel_values(math::Vec2f const & pixel) const; + + /** Constructs a TextureView from the give core::CameraInfo containing the given image. */ + TextureView(std::size_t id, core::CameraInfo const & camera, std::string const & image_file); + + /** Returns the camera position. */ + math::Vec3f get_pos(void) const; + + /** Returns the viewing direction. */ + math::Vec3f get_viewing_direction(void) const; + + /** Returns the width of the corresponding image. */ + int get_width(void) const; + + /** Returns the height of the corresponding image. */ + int get_height(void) const; + + /** Returns a reference pointer to the corresponding image. */ + core::ByteImage::Ptr get_image(void) const; + + /** Exchange encapsulated image. */ + void bind_image(core::ByteImage::Ptr new_image); + + /** Loads the corresponding image. */ + void load_image(void); + + /** Generates the validity mask. */ + void generate_validity_mask(void); + + /** Generates the gradient magnitude image for the encapsulated image. */ + void generate_gradient_magnitude(void); + + /** Releases the validity mask. */ + void release_validity_mask(void); + + /** Releases the gradient magnitude image. */ + void release_gradient_magnitude(void); + + /** Releases the corresponding image. */ + void release_image(void); + + /** Erodes the validity mask by one pixel. */ + void erode_validity_mask(void); + + void + get_face_info(math::Vec3f const & v1, math::Vec3f const & v2, math::Vec3f const & v3, + FaceProjectionInfo * face_info, Settings const & settings) const; + + void + export_triangle(math::Vec3f v1, math::Vec3f v2, math::Vec3f v3, std::string const & filename) const; + + void + export_validity_mask(std::string const & filename) const; +}; + + +inline std::size_t +TextureView::get_id(void) const { + return id; +} + +inline math::Vec3f +TextureView::get_pos(void) const { + return pos; +} + +inline math::Vec3f +TextureView::get_viewing_direction(void) const { + return viewdir; +} + +inline int +TextureView::get_width(void) const { + return width; +} + +inline int +TextureView::get_height(void) const { + return height; +} + +inline core::ByteImage::Ptr +TextureView::get_image(void) const { + assert(image != NULL); + return image; +} + +inline bool +TextureView::inside(math::Vec3f const & v1, math::Vec3f const & v2, math::Vec3f const & v3) const { + math::Vec2f p1 = get_pixel_coords(v1); + math::Vec2f p2 = get_pixel_coords(v2); + math::Vec2f p3 = get_pixel_coords(v3); + return valid_pixel(p1) && valid_pixel(p2) && valid_pixel(p3); +} + +inline math::Vec2f +TextureView::get_pixel_coords(math::Vec3f const & vertex) const { + math::Vec3f pixel = projection * world_to_cam.mult(vertex, 1.0f); + pixel /= pixel[2]; + return math::Vec2f(pixel[0] - 0.5f, pixel[1] - 0.5f); +} + +inline math::Vec3f +TextureView::get_pixel_values(math::Vec3f const & vertex) const { + math::Vec2f pixel = get_pixel_coords(vertex); + return get_pixel_values(pixel); +} + +inline math::Vec3f +TextureView::get_pixel_values(math::Vec2f const & pixel) const { + assert(image != NULL); + math::Vec3uc values; + image->linear_at(pixel[0], pixel[1], *values); + return math::Vec3f(values) / 255.0f; +} + +inline void +TextureView::bind_image(core::ByteImage::Ptr new_image) { + image = new_image; +} + +inline void +TextureView::release_validity_mask(void) { + assert(validity_mask.size() == static_cast(width * height)); + validity_mask = std::vector(); +} + +inline void +TextureView::release_gradient_magnitude(void) { + assert(gradient_magnitude != NULL); + gradient_magnitude.reset(); +} + +inline void +TextureView::release_image(void) { + assert(image != NULL); + image.reset(); +} + +TEX_NAMESPACE_END + +#endif /* TEX_TEXTUREVIEW_HEADER */ diff --git a/texturing/texturing.h b/texturing/texturing.h new file mode 100644 index 0000000..040f797 --- /dev/null +++ b/texturing/texturing.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TEXTURING_HEADER +#define TEX_TEXTURING_HEADER + +#include + +#include "core/mesh.h" +#include "core/mesh_info.h" + +#include "defines.h" +#include "settings.h" +#include "obj_model.h" +#include "uni_graph.h" +#include "texture_view.h" +#include "texture_patch.h" +#include "texture_atlas.h" +#include "sparse_table.h" + +#include "seam_leveling.h" + +TEX_NAMESPACE_BEGIN + +typedef std::vector TextureViews; +typedef std::vector TexturePatches; +typedef std::vector TextureAtlases; +typedef ObjModel Model; +typedef UniGraph Graph; +typedef SparseTable DataCosts; +typedef std::vector > VertexProjectionInfos; +typedef std::vector > FaceProjectionInfos; + +/** + * prepares the mesh for texturing + * -removes duplicated faces + * -ensures normals (face and vertex) + */ +void +prepare_mesh(core::MeshInfo * mesh_info, core::TriangleMesh::Ptr mesh); + +/** + * Generates TextureViews from the in_scene. + */ +void +generate_texture_views(std::string const & in_scene, + TextureViews * texture_views, std::string const & tmp_dir); + +/** + * Builds up the meshes face adjacency graph using the vertex_infos + */ +void +build_adjacency_graph(core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, UniGraph * graph); + +/** + * Calculates the data costs for each face and texture view combination, + * if the face is visible within the texture view. + */ +void +calculate_data_costs(core::TriangleMesh::ConstPtr mesh, + TextureViews * texture_views, Settings const & settings, + DataCosts * data_costs); + +void +postprocess_face_infos(Settings const & settings, + FaceProjectionInfos * projected_face_infos, + DataCosts * data_costs); + +/** + * Runs the view selection procedure and saves the labeling in the graph + */ +void +view_selection(DataCosts const & data_costs, UniGraph * graph, Settings const & settings); + +/** + * Generates texture patches using the graph to determine adjacent faces with the same label. + */ +void generate_texture_patches(UniGraph const & graph, + core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, + TextureViews * texture_views, + Settings const & settings, + VertexProjectionInfos * vertex_projection_infos, + TexturePatches * texture_patches); + +/** + * Runs the seam leveling procedure proposed by Ivanov and Lempitsky + * [Seamless mosaicing of image-based texture maps] + */ +void +global_seam_leveling(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + core::MeshInfo const & mesh_info, + VertexProjectionInfos const & vertex_projection_infos, + TexturePatches * texture_patches); + +void +local_seam_leveling(UniGraph const & graph, core::TriangleMesh::ConstPtr mesh, + VertexProjectionInfos const & vertex_projection_infos, + TexturePatches * texture_patches); + +void +generate_texture_atlases(TexturePatches * texture_patches, + Settings const & settings, TextureAtlases * texture_atlases); + +/** + * Builds up an model for the mesh by constructing materials and + * texture atlases form the texture_patches + */ +void +build_model(core::TriangleMesh::ConstPtr mesh, + TextureAtlases const & texture_atlas, Model * model); + +TEX_NAMESPACE_END + +#endif /* TEX_TEXTURING_HEADER */ diff --git a/texturing/timer.cpp b/texturing/timer.cpp new file mode 100644 index 0000000..b16c481 --- /dev/null +++ b/texturing/timer.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include "timer.h" + +Timer::Timer(void) { + header = ""; + ctimer = util::ClockTimer(); + wtimer = util::WallTimer(); + measure("Init"); +} + +Timer::Timer(std::string const & _header) { + header = _header; + ctimer = util::ClockTimer(); + wtimer = util::WallTimer(); +} + +void Timer::measure(std::string const & eventname) { + std::size_t abs_clocks = ctimer.get_elapsed(); + std::size_t abs_milliseconds = wtimer.get_elapsed(); + + if(events.empty()) { + Event event = {eventname, abs_clocks, abs_milliseconds, abs_clocks, abs_milliseconds}; + events.push_back(event); + } else { + Event last_event = events.back(); + std::size_t rel_clocks = abs_clocks - last_event.abs_clocks; + std::size_t rel_milliseconds = abs_milliseconds - last_event.abs_milliseconds; + + Event event = {eventname, abs_clocks, abs_milliseconds, rel_clocks, rel_milliseconds}; + events.push_back(event); + } +} + +void Timer::write_to_file(std::string const & filename) const { + std::ofstream out(filename.c_str()); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + if(!header.empty()) + out << "#" << header << std::endl; + + out << "Event, Absolute clocks, Absolute milliseconds, Relative clocks, Relative milliseconds" + << std::endl; + for (std::size_t i = 0; i < events.size(); ++i) { + Event event = events[i]; + out << event.name << ", " + << event.abs_clocks << ", " + << event.abs_milliseconds << ", " + << event.rel_clocks << ", " + << event.rel_milliseconds << std::endl; + } + out.close(); +} diff --git a/texturing/timer.h b/texturing/timer.h new file mode 100644 index 0000000..710747e --- /dev/null +++ b/texturing/timer.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TIMER_HEADER +#define TEX_TIMER_HEADER + +#include +#include +#include +#include +#include +#include + +#include "util/timer.h" +#include "util/exception.h" + +class Timer { + private: + util::ClockTimer ctimer; + util::WallTimer wtimer; + + std::string header; + + struct Event{ + std::string name; + std::size_t abs_clocks; + std::size_t abs_milliseconds; + std::size_t rel_clocks; + std::size_t rel_milliseconds; + }; + + std::vector events; + + public: + Timer(void); + Timer(std::string const & _header); + + void measure(std::string const & eventname); + void write_to_file(std::string const & filename) const; +}; + +#endif /* TEX_TIMER_HEADER */ diff --git a/texturing/tri.cpp b/texturing/tri.cpp new file mode 100644 index 0000000..8bdf05a --- /dev/null +++ b/texturing/tri.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include "tri.h" + +Tri::Tri(math::Vec2f v1, math::Vec2f v2, math::Vec2f v3) : + v1(v1), v2(v2), v3(v3) { + math::Matrix2f T; + T[0] = v1[0] - v3[0]; T[1] = v2[0] - v3[0]; + T[2] = v1[1] - v3[1]; T[3] = v2[1] - v3[1]; + + detT = T[0] * T[3] - T[2] * T[1]; + + aabb.min_x = std::min(v1[0], std::min(v2[0], v3[0])); + aabb.min_y = std::min(v1[1], std::min(v2[1], v3[1])); + aabb.max_x = std::max(v1[0], std::max(v2[0], v3[0])); + aabb.max_y = std::max(v1[1], std::max(v2[1], v3[1])); +} diff --git a/texturing/tri.h b/texturing/tri.h new file mode 100644 index 0000000..903ac48 --- /dev/null +++ b/texturing/tri.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_TRI_HEADER +#define TEX_TRI_HEADER + +#include "math/vector.h" +#include "math/matrix.h" +#include "rect.h" + +/** + * Simple class representing a two dimensional triangle optimized for the calculation of barycentric coordinates. + */ +class Tri { + private: + math::Vec2f v1; + math::Vec2f v2; + math::Vec2f v3; + float detT; + + Rect aabb; + public: + /** Constructor which calculates the axis aligned bounding box and prepares the calculation of barycentric coordinates. */ + Tri(math::Vec2f v1, math::Vec2f v2, math::Vec2f v3); + + /** Determines whether the given point is inside via barycentric coordinates. */ + bool inside(float x, float y) const; + + /** Returns the barycentric coordinates for the given point. */ + math::Vec3f get_barycentric_coords(float x, float y) const; + + /** Returns the area of the triangle. */ + float get_area(void) const; + + /** Returns the axis aligned bounding box. */ + Rect get_aabb(void) const; +}; + +inline Rect +Tri::get_aabb(void) const { + return aabb; +} + +inline math::Vec3f +Tri::get_barycentric_coords(float x, float y) const { + float const alpha = ((v2[1] - v3[1]) * (x - v3[0]) + (v3[0] - v2[0]) * (y - v3[1])) / detT; + float const beta = ((v3[1] - v1[1]) * (x - v3[0]) + (v1[0] - v3[0]) * (y - v3[1])) / detT; + float const gamma = 1.0f - alpha - beta; + return math::Vec3f(alpha, beta, gamma); +} + +inline bool +Tri::inside(float x, float y) const { + float const dx = (x - v3[0]); + float const dy = (y - v3[1]); + + float const alpha = ((v2[1] - v3[1]) * dx + (v3[0] - v2[0]) * dy) / detT; + if (alpha < 0.0f || alpha > 1.0f) + return false; + + float const beta = ((v3[1] - v1[1]) * dx + (v1[0] - v3[0]) * dy) / detT; + if (beta < 0.0f || beta > 1.0f) + return false; + + if (alpha + beta > 1.0f) + return false; + + /* else */ + return true; +} + +inline float +Tri::get_area(void) const { + math::Vec2f u = v2 - v1; + math::Vec2f v = v3 - v1; + + return 0.5f * std::abs(u[0] * v[1] - u[1] * v[0]); +} + +#endif /* TEX_TRI_HEADER */ diff --git a/texturing/uni_graph.cpp b/texturing/uni_graph.cpp new file mode 100644 index 0000000..7247726 --- /dev/null +++ b/texturing/uni_graph.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include +#include + +#include "uni_graph.h" + +UniGraph::UniGraph(std::size_t nodes) { + adj_lists.resize(nodes); + labels.resize(nodes); + edges = 0; +} + +void +UniGraph::get_subgraphs(std::size_t label, + std::vector > * subgraphs) const { + + std::vector used(adj_lists.size(), false); + + for(std::size_t i = 0; i < adj_lists.size(); ++i) { + if (labels[i] == label && !used[i]) { + subgraphs->push_back(std::vector()); + + std::list queue; + + queue.push_back(i); + used[i] = true; + + while (!queue.empty()) { + std::size_t node = queue.front(); + queue.pop_front(); + + subgraphs->back().push_back(node); + + /* Add all unused neighbours with the same label to the queue. */ + std::vector const & adj_list = adj_lists[node]; + for(std::size_t j = 0; j < adj_list.size(); ++j) { + std::size_t adj_node = adj_list[j]; + assert(adj_node < labels.size() && adj_node < used.size()); + if (labels[adj_node] == label && !used[adj_node]){ + queue.push_back(adj_node); + used[adj_node] = true; + } + } + } + } + } +} diff --git a/texturing/uni_graph.h b/texturing/uni_graph.h new file mode 100644 index 0000000..5f9a8f0 --- /dev/null +++ b/texturing/uni_graph.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_UNIGRAPH_HEADER +#define TEX_UNIGRAPH_HEADER + +#include +#include +#include + +/** + * Implementation of a unidirectional graph with fixed amount of nodes using adjacency lists. + */ +class UniGraph { + private: + std::vector > adj_lists; + std::vector labels; + std::size_t edges; + + public: + /** + * Creates a unidirectional graph without edges. + * @param nodes number of nodes. + */ + UniGraph(std::size_t nodes); + + /** + * Adds an edge between the nodes with indices n1 and n2. + * If the edge exists nothing happens. + * @warning asserts that the indices are valid. + */ + void add_edge(std::size_t n1, std::size_t n2); + + /** + * Removes the edge between the nodes with indices n1 and n2. + * If the edge does not exist nothing happens. + * @warning asserts that the indices are valid. + */ + void remove_edge(std::size_t n1, std::size_t n2); + + /** + * Returns true if an edge between the nodes with indices n1 and n2 exists. + * @warning asserts that the indices are valid. + */ + bool has_edge(std::size_t n1, std::size_t n2) const; + + /** Returns the number of edges. */ + std::size_t num_edges() const; + + /** Returns the number of nodes. */ + std::size_t num_nodes() const; + + /** + * Sets the label of node with index n to label. + * @warning asserts that the index is valid. + */ + void set_label(std::size_t n, std::size_t label); + + /** + * Returns the label of node with index n. + * @warning asserts that the index is valid. + */ + std::size_t get_label(std::size_t n) const; + + /** + * Fills given vector with all subgraphs of the given label. + * A subgraph is a vector containing all indices of connected nodes with the same label. + */ + void get_subgraphs(std::size_t label, std::vector > * subgraphs) const; + + std::vector const & get_adj_nodes(std::size_t node) const; +}; + +inline void +UniGraph::add_edge(std::size_t n1, std::size_t n2) { + assert(n1 < num_nodes() && n2 < num_nodes()); + if (!has_edge(n1, n2)) { + adj_lists[n1].push_back(n2); + adj_lists[n2].push_back(n1); + ++edges; + } +} + +inline void +delete_element(std::vector * vec, std::size_t element) { + vec->erase(std::remove(vec->begin(), vec->end(), element), vec->end()); +} + +inline void +UniGraph::remove_edge(std::size_t n1, std::size_t n2) { + assert(n1 < num_nodes() && n2 < num_nodes()); + if (has_edge(n1, n2)){ + delete_element(&adj_lists[n1], n2); + delete_element(&adj_lists[n2], n1); + --edges; + } +} + +inline bool +UniGraph::has_edge(std::size_t n1, std::size_t n2) const { + assert(n1 < num_nodes() && n2 < num_nodes()); + std::vector const & adj_list = adj_lists[n1]; + return std::find(adj_list.begin(), adj_list.end(), n2) != adj_list.end(); +} + +inline std::size_t +UniGraph::num_edges() const { + return edges; +} + +inline std::size_t +UniGraph::num_nodes() const { + return adj_lists.size(); +} + +inline void +UniGraph::set_label(std::size_t n, std::size_t label) { + assert(n < num_nodes()); + labels[n] = label; +} + +inline std::size_t +UniGraph::get_label(std::size_t n) const { + assert(n < num_nodes()); + return labels[n]; +} + +inline std::vector const & +UniGraph::get_adj_nodes(std::size_t node) const { + assert(node < num_nodes()); + return adj_lists[node]; +} + +#endif /* TEX_UNIGRAPH_HEADER */ diff --git a/texturing/util.h b/texturing/util.h new file mode 100644 index 0000000..3f96264 --- /dev/null +++ b/texturing/util.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015, Nils Moehrle, Michael Waechter + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#ifndef TEX_UTIL_HEADER +#define TEX_UTIL_HEADER + +#include +#include +#include +#include +#include +#include + +#include + +#include "util/exception.h" +#include "util/file_system.h" + +#include "math/vector.h" +#include "math/matrix.h" +#include "math/functions.h" + +/** + * Converts an MVE matrix into an Eigen matrix. + */ +template Eigen::Matrix +mve_to_eigen(math::Matrix const & mat) { + Eigen::Matrix ret; + for (int m = 0; m < M; ++m) + for (int n = 0; n < N; ++n) + ret(m, n) = mat(m, n); + return ret; +} + +/** + * Converts an MVE vector into an Eigen row vector. + */ +template Eigen::Matrix +mve_to_eigen(math::Vector const & vec) { + Eigen::Matrix ret; + for (int n = 0; n < N; ++n) + ret(0, n) = vec(n); + return ret; +} + +/** + * A multi-variate normal distribution which is NOT normalized such that the integral is 1. + * + * @param X is the vector for which the function is to be evaluated. Must have size 1xN. + * @param mu is the mean around which the distribution is centered. Must have size 1xN. + * @param covariance_inv is the INVERSE of the covariance matrix. Must have size NxN. + * @return \f$\exp(-\frac{1}{2} (X-\mbox{mu})^T \cdot \mbox{covariance\_inv} \cdot (X-\mbox{mu}))\f$ + */ +template T const +multi_gauss_unnormalized(Eigen::Matrix const & X, Eigen::Matrix const & mu, + Eigen::Matrix const & covariance_inv) { + + Eigen::Matrix mean_removed = X - mu; + return std::exp(T(-0.5) * mean_removed * covariance_inv * mean_removed.adjoint()); +} + +/** Return the number suffix for n, e.g. 3 -> "rd". */ +inline +std::string number_suffix(int n) { + if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) + return "th"; + if (n % 10 == 1) + return "st"; + if (n % 10 == 2) + return "nd"; + if (n % 10 == 3) + return "rd"; + return "th"; +} + +/** + * Write vector to csv file with "Index, $header" as header. + * @throws util::FileException + */ +template void +write_vector_to_csv(std::string const & filename, std::vector const & vector, std::string const & header) { + std::ofstream out(filename.c_str()); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + out << "Index, "<< header << std::endl; + for (std::size_t i = 0; i < vector.size(); i++){ + out << i << ", " << vector[i] << std::endl; + } + out.close(); +} + + +/** + * Write vector to binary file. + * @throws util::FileException + */ +template void +vector_to_file(std::string const & filename, std::vector const & vector) { + std::ofstream out(filename.c_str(), std::ios::binary); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + out.write(reinterpret_cast(&vector[0]), vector.size()*sizeof(T)); + out.close(); +} + +/** + * Loads vector from binary file. + * @throws util::FileException + */ +template std::vector +vector_from_file(std::string const & filename) { + std::ifstream in(filename.c_str(), std::ios::binary); + if (!in.good()) + throw util::FileException(filename, std::strerror(errno)); + in.seekg (0, in.end); + const size_t filesize = in.tellg(); + in.seekg (0, in.beg); + const size_t num_elements = filesize / sizeof(T); + std::vector vector(num_elements); + in.read(reinterpret_cast(&vector[0]), num_elements*sizeof(T)); + in.close(); + return vector; +} + +/** + * Writes the given string to the file given by filename. + * @throws util::FileException + */ +inline void +write_string_to_file(std::string const & filename, std::string const & string) { + std::ofstream out(filename.c_str()); + if (!out.good()) + throw util::FileException(filename, std::strerror(errno)); + + out << string; + out.close(); +} + +/** + * returns the corresponding jet color encoding for the given value. + * @warning asserts values within the interval [0, 1] + */ +inline math::Vec4f +get_jet_color(float value) { + assert(0.0f <= value && value <= 1.0f); + float mvalue = 4 * value; + float red = math::clamp(std::min(mvalue - 1.5f, -mvalue + 4.5f)); + float green = math::clamp(std::min(mvalue - 0.5f, -mvalue + 3.5f)); + float blue = math::clamp(std::min(mvalue + 0.5f, -mvalue + 2.5f)); + return math::Vec4f(red, green, blue, 1.0f); +} + +#endif /* TEX_UTIL_HEADER */ diff --git a/texturing/view_selection.cpp b/texturing/view_selection.cpp new file mode 100644 index 0000000..b409e59 --- /dev/null +++ b/texturing/view_selection.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015, Nils Moehrle + * TU Darmstadt - Graphics, Capture and Massively Parallel Computing + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD 3-Clause license. See the LICENSE.txt file for details. + */ + +#include + +#include "util.h" +#include "texturing.h" +#include "mapmap/full.h" + +TEX_NAMESPACE_BEGIN + +void +view_selection(DataCosts const & data_costs, UniGraph * graph, Settings const &) { + using uint_t = unsigned int; + using cost_t = float; + constexpr uint_t simd_w = mapmap::sys_max_simd_width(); + using unary_t = mapmap::UnaryTable; + using pairwise_t = mapmap::PairwisePotts; + + /* Construct graph */ + mapmap::Graph mgraph(graph->num_nodes()); + + for (std::size_t i = 0; i < graph->num_nodes(); ++i) { + if (data_costs.col(i).empty()) continue; + + std::vector adj_faces = graph->get_adj_nodes(i); + for (std::size_t j = 0; j < adj_faces.size(); ++j) { + std::size_t adj_face = adj_faces[j]; + if (data_costs.col(adj_face).empty()) continue; + + /* Uni directional */ + if (i < adj_face) { + mgraph.add_edge(i, adj_face, 1.0f); + } + } + } + mgraph.update_components(); + + mapmap::LabelSet label_set(graph->num_nodes(), false); + for (std::size_t i = 0; i < data_costs.cols(); ++i) { + DataCosts::Column const & data_costs_for_node = data_costs.col(i); + + std::vector > labels; + if (data_costs_for_node.empty()) { + labels.push_back(0); + } else { + labels.resize(data_costs_for_node.size()); + for(std::size_t j = 0; j < data_costs_for_node.size(); ++j) { + labels[j] = data_costs_for_node[j].first + 1; + } + } + + label_set.set_label_set_for_node(i, labels); + } + + std::vector unaries; + unaries.reserve(data_costs.cols()); + pairwise_t pairwise(1.0f); + for (std::size_t i = 0; i < data_costs.cols(); ++i) { + DataCosts::Column const & data_costs_for_node = data_costs.col(i); + + std::vector > costs; + if (data_costs_for_node.empty()) { + costs.push_back(1.0f); + } else { + costs.resize(data_costs_for_node.size()); + for(std::size_t j = 0; j < data_costs_for_node.size(); ++j) { + float cost = data_costs_for_node[j].second; + costs[j] = cost; + } + + } + + unaries.emplace_back(i, &label_set); + unaries.back().set_costs(costs); + } + + mapmap::StopWhenReturnsDiminish terminate(5, 0.01); + std::vector > solution; + + auto display = [](const mapmap::luint_t time_ms, + const mapmap::_iv_st objective) { + std::cout << "\t\t" << time_ms / 1000 << "\t" << objective << std::endl; + }; + + /* Create mapMAP solver object. */ + mapmap::mapMAP solver; + solver.set_graph(&mgraph); + solver.set_label_set(&label_set); + for(std::size_t i = 0; i < graph->num_nodes(); ++i) + solver.set_unary(i, &unaries[i]); + solver.set_pairwise(&pairwise); + solver.set_logging_callback(display); + solver.set_termination_criterion(&terminate); + + /* Pass configuration arguments (optional) for solve. */ + mapmap::mapMAP_control ctr; + ctr.use_multilevel = true; + ctr.use_spanning_tree = true; + ctr.use_acyclic = true; + ctr.spanning_tree_multilevel_after_n_iterations = 5; + ctr.force_acyclic = true; + ctr.min_acyclic_iterations = 5; + ctr.relax_acyclic_maximal = true; + ctr.tree_algorithm = mapmap::LOCK_FREE_TREE_SAMPLER; + + /* Set true for deterministic (but slower) mapMAP execution. */ + ctr.sample_deterministic = false; + ctr.initial_seed = 548923723; + + std::cout << "\tOptimizing:\n\t\tTime[s]\tEnergy" << std::endl; + solver.optimize(solution, ctr); + + /* Label 0 is undefined. */ + std::size_t num_labels = data_costs.rows() + 1; + std::size_t undefined = 0; + /* Extract resulting labeling from solver. */ + for (std::size_t i = 0; i < graph->num_nodes(); ++i) { + int label = label_set.label_from_offset(i, solution[i]); + if (label < 0 || num_labels <= static_cast(label)) { + throw std::runtime_error("Incorrect labeling"); + } + if (label == 0) undefined += 1; + graph->set_label(i, static_cast(label)); + } + std::cout << '\t' << undefined << " faces have not been seen" << std::endl; +} + +TEX_NAMESPACE_END