From 5e98c8bbc114caeb75f57fe4f3ec5ab0ee7449de Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Wed, 28 Apr 2021 14:04:55 -0400 Subject: [PATCH 1/2] Update OpenMVS --- SuperBuild/cmake/External-OpenMVS.cmake | 2 +- VERSION | 2 +- opendm/mesh.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SuperBuild/cmake/External-OpenMVS.cmake b/SuperBuild/cmake/External-OpenMVS.cmake index fec75ba9..11ef2b4f 100644 --- a/SuperBuild/cmake/External-OpenMVS.cmake +++ b/SuperBuild/cmake/External-OpenMVS.cmake @@ -20,7 +20,7 @@ ExternalProject_Add(${_proj_name} #--Download step-------------- DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} GIT_REPOSITORY https://github.com/OpenDroneMap/openMVS - GIT_TAG 2411 + GIT_TAG 2412 #--Update/Patch step---------- UPDATE_COMMAND "" #--Configure step------------- diff --git a/VERSION b/VERSION index 11e32126..cf95c018 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4.11 +2.4.12 diff --git a/opendm/mesh.py b/opendm/mesh.py index 05b5fabd..15a109c4 100644 --- a/opendm/mesh.py +++ b/opendm/mesh.py @@ -124,7 +124,7 @@ def dem_to_mesh_gridded(inGeotiff, outMesh, maxVertexCount, verbose=False, maxCo 'verbose': '-verbose' if verbose else '' } - system.run('{reconstructmesh} --mesh-file "{infile}" ' + system.run('{reconstructmesh} -i "{infile}" ' '-o "{outfile}" ' '--remove-spikes 0 --remove-spurious 0 --smooth 0 ' '--target-face-num {max_faces} ' @@ -178,7 +178,7 @@ def screened_poisson_reconstruction(inPointCloud, outMesh, depth = 8, samples = 'verbose': '-verbose' if verbose else '' } - system.run('{reconstructmesh} --mesh-file "{infile}" ' + system.run('{reconstructmesh} -i "{infile}" ' '-o "{outfile}" ' '--remove-spikes 0 --remove-spurious 0 --smooth 0 ' '--target-face-num {max_faces} ' From 22373321b41bf708d8fe6f795f76a09f611cdd3f Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Wed, 28 Apr 2021 14:58:19 -0400 Subject: [PATCH 2/2] Moved odm_orthophoto to separate repo, removed build, modules folders --- SuperBuild/CMakeLists.txt | 8 + configure.sh | 7 +- modules/CMakeLists.txt | 7 - modules/odm_orthophoto/CMakeLists.txt | 42 - modules/odm_orthophoto/src/Logger.cpp | 29 - modules/odm_orthophoto/src/Logger.hpp | 68 - modules/odm_orthophoto/src/OdmOrthoPhoto.cpp | 1411 ------------------ modules/odm_orthophoto/src/OdmOrthoPhoto.hpp | 207 --- modules/odm_orthophoto/src/main.cpp | 8 - opendm/context.py | 1 + snap/snapcraft.yaml | 11 +- stages/odm_orthophoto.py | 4 +- 12 files changed, 16 insertions(+), 1787 deletions(-) delete mode 100644 modules/CMakeLists.txt delete mode 100644 modules/odm_orthophoto/CMakeLists.txt delete mode 100644 modules/odm_orthophoto/src/Logger.cpp delete mode 100644 modules/odm_orthophoto/src/Logger.hpp delete mode 100644 modules/odm_orthophoto/src/OdmOrthoPhoto.cpp delete mode 100644 modules/odm_orthophoto/src/OdmOrthoPhoto.hpp delete mode 100644 modules/odm_orthophoto/src/main.cpp diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt index df5e604f..951cf6b9 100644 --- a/SuperBuild/CMakeLists.txt +++ b/SuperBuild/CMakeLists.txt @@ -147,6 +147,14 @@ externalproject_add(dem2points CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR} ) +externalproject_add(odm_orthophoto + GIT_REPOSITORY https://github.com/OpenDroneMap/odm_orthophoto.git + GIT_TAG main + PREFIX ${SB_BINARY_DIR}/odm_orthophoto + SOURCE_DIR ${SB_SOURCE_DIR}/odm_orthophoto + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR} +) + externalproject_add(lastools GIT_REPOSITORY https://github.com/LAStools/LAStools.git GIT_TAG 2ef44281645999ec7217facec84a5913bbbbe165 diff --git a/configure.sh b/configure.sh index 96cd7dee..b39b34de 100755 --- a/configure.sh +++ b/configure.sh @@ -143,11 +143,6 @@ install() { mkdir -p build && cd build cmake .. && make -j$processes - echo "Compiling build" - cd ${RUNPATH} - mkdir -p build && cd build - cmake .. && make -j$processes - echo "Configuration Finished" } @@ -191,6 +186,8 @@ clean() { ${RUNPATH}/SuperBuild/build/dem2points \ ${RUNPATH}/SuperBuild/src/openmvs \ ${RUNPATH}/SuperBuild/build/openmvs \ + ${RUNPATH}/SuperBuild/src/odm_orthophoto \ + ${RUNPATH}/SuperBuild/build/odm_orthophoto \ ${RUNPATH}/SuperBuild/src/vcg # find in /code and delete static libraries and intermediate object files diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt deleted file mode 100644 index 5c372a95..00000000 --- a/modules/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -if (NOT CMAKE_BUILD_TYPE) - message(STATUS "No build type selected, default to Release") - set(CMAKE_BUILD_TYPE "Release") -endif() - -# Add ODM sub-modules -add_subdirectory(odm_orthophoto) diff --git a/modules/odm_orthophoto/CMakeLists.txt b/modules/odm_orthophoto/CMakeLists.txt deleted file mode 100644 index bd7217c5..00000000 --- a/modules/odm_orthophoto/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -project(odm_orthophoto) -cmake_minimum_required(VERSION 2.8) - -# Set pcl dir to the input spedified with option -DPCL_DIR="path" -set(PCL_DIR "PCL_DIR-NOTFOUND" CACHE "PCL_DIR" "Path to the pcl installation directory") -set(OPENCV_DIR "OPENCV_DIR-NOTFOUND" CACHE "OPENCV_DIR" "Path to the OPENCV installation directory") - -# Add compiler options. -add_definitions(-Wall -Wextra) - -# Find pcl at the location specified by PCL_DIR -find_package(PCL 1.8 HINTS "${PCL_DIR}/share/pcl-1.8" REQUIRED) -find_package(GDAL REQUIRED) - -# PCL should already link to Boost, but for some reason it doesn't... -find_package(Boost COMPONENTS filesystem REQUIRED) - -include_directories(${GDAL_INCLUDE_DIR}) - -# Find OpenCV at the default location -find_package(OpenCV HINTS "${OPENCV_DIR}" REQUIRED) - -# Only link with required opencv modules. -set(OpenCV_LIBS opencv_core opencv_imgproc opencv_highgui) - -# Add the PCL, Eigen and OpenCV include dirs. -# Necessary since the PCL_INCLUDE_DIR variable set by find_package is broken.) -include_directories(${PCL_ROOT}/include/pcl-${PCL_VERSION_MAJOR}.${PCL_VERSION_MINOR}) -include_directories(${EIGEN_ROOT}) -include_directories(${OpenCV_INCLUDE_DIRS}) - -#library_directories(${OpenCV_LIBRARY_DIRS}) - -# Add source directory -aux_source_directory("./src" SRC_LIST) - -# Add exectuteable -add_executable(${PROJECT_NAME} ${SRC_LIST}) -set_target_properties(${PROJECT_NAME} PROPERTIES - CXX_STANDARD 11 -) -target_link_libraries(odm_orthophoto ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES} ${PCL_SURFACE_LIBRARIES} ${OpenCV_LIBS} ${GDAL_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}) diff --git a/modules/odm_orthophoto/src/Logger.cpp b/modules/odm_orthophoto/src/Logger.cpp deleted file mode 100644 index 29cb8048..00000000 --- a/modules/odm_orthophoto/src/Logger.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "Logger.hpp" - - -Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout) -{ - -} - -Logger::~Logger() -{ - -} - -void Logger::print(std::string filePath) -{ - std::ofstream file(filePath.c_str(), std::ios::binary); - file << logStream_.str(); - file.close(); -} - -bool Logger::isPrintingInCout() const -{ - return isPrintingInCout_; -} - -void Logger::setIsPrintingInCout(bool isPrintingInCout) -{ - isPrintingInCout_ = isPrintingInCout; -} diff --git a/modules/odm_orthophoto/src/Logger.hpp b/modules/odm_orthophoto/src/Logger.hpp deleted file mode 100644 index 61520146..00000000 --- a/modules/odm_orthophoto/src/Logger.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -// STL -#include -#include -#include -#include - -/*! - * \brief The Logger class is used to store program messages in a log file. - * \details By using the << operator while printInCout is set, the class writes both to - * cout and to file, if the flag is not set, output is written to file only. - */ -class Logger -{ -public: - /*! - * \brief Logger Contains functionality for printing and displaying log information. - * \param printInCout Flag toggling if operator << also writes to cout. - */ - Logger(bool isPrintingInCout = true); - - /*! - * \brief Destructor. - */ - ~Logger(); - - /*! - * \brief print Prints the contents of the log to file. - * \param filePath Path specifying where to write the log. - */ - void print(std::string filePath); - - /*! - * \brief isPrintingInCout Check if console printing flag is set. - * \return Console printing flag. - */ - bool isPrintingInCout() const; - - /*! - * \brief setIsPrintingInCout Set console printing flag. - * \param isPrintingInCout Value, if true, messages added to the log are also printed in cout. - */ - void setIsPrintingInCout(bool isPrintingInCout); - - /*! - * Operator for printing messages to log and in the standard output stream if desired. - */ - template - friend Logger& operator<< (Logger &log, T t) - { - // If console printing is enabled. - if (log.isPrintingInCout_) - { - std::cout << t; - std::cout.flush(); - } - // Write to log. - log.logStream_ << t; - - return log; - } - -private: - bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */ - - std::stringstream logStream_; /*!< Stream for storing the log. */ -}; diff --git a/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp b/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp deleted file mode 100644 index 1028d8d4..00000000 --- a/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp +++ /dev/null @@ -1,1411 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "OdmOrthoPhoto.hpp" - -OdmOrthoPhoto::OdmOrthoPhoto() - :log_(false){ - outputFile_ = "ortho.tif"; - logFile_ = "log.txt"; - outputCornerFile_ = ""; - bandsOrder = "red,green,blue"; - - resolution_ = 0.0f; - - alphaBand = nullptr; - currentBandIndex = 0; -} - -OdmOrthoPhoto::~OdmOrthoPhoto() -{ -} - -int OdmOrthoPhoto::run(int argc, char *argv[]) -{ - try - { - parseArguments(argc, argv); - createOrthoPhoto(); - } - catch (const OdmOrthoPhotoException& e) - { - log_.setIsPrintingInCout(true); - log_ << e.what() << "\n"; - log_.print(logFile_); - return EXIT_FAILURE; - } - catch (const std::exception& e) - { - log_.setIsPrintingInCout(true); - log_ << "Error in OdmOrthoPhoto:\n"; - log_ << e.what() << "\n"; - log_.print(logFile_); - return EXIT_FAILURE; - } - catch (...) - { - log_.setIsPrintingInCout(true); - log_ << "Unknown error, terminating:\n"; - log_.print(logFile_); - return EXIT_FAILURE; - } - - log_.print(logFile_); - - return EXIT_SUCCESS; -} - -void OdmOrthoPhoto::parseArguments(int argc, char *argv[]) -{ - logFile_ = std::string(argv[0]) + "_log.txt"; - log_ << logFile_ << "\n\n"; - - // If no arguments were passed, print help. - if (argc == 1) - { - printHelp(); - } - - log_ << "Arguments given\n"; - for(int argIndex = 1; argIndex < argc; ++argIndex) - { - log_ << argv[argIndex] << '\n'; - } - - log_ << '\n'; - for(int argIndex = 1; argIndex < argc; ++argIndex) - { - // The argument to be parsed. - std::string argument = std::string(argv[argIndex]); - - if(argument == "-help") - { - printHelp(); - } - else if(argument == "-resolution") - { - ++argIndex; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided."); - } - std::stringstream ss(argv[argIndex]); - ss >> resolution_; - log_ << "Resolution count was set to: " << resolution_ << "pixels/meter\n"; - } - else if(argument == "-verbose") - { - log_.setIsPrintingInCout(true); - } - else if (argument == "-logFile") - { - ++argIndex; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Missing argument for '" + argument + "'."); - } - logFile_ = std::string(argv[argIndex]); - std::ofstream testFile(logFile_.c_str()); - if (!testFile.is_open()) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' has a bad value."); - } - log_ << "Log file path was set to: " << logFile_ << "\n"; - } - else if(argument == "-inputFiles") - { - argIndex++; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided."); - } - std::string inputFilesArg = std::string(argv[argIndex]); - std::stringstream ss(inputFilesArg); - std::string item; - while(std::getline(ss, item, ',')){ - inputFiles.push_back(item); - } - } - else if(argument == "-bands") - { - argIndex++; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided."); - } - bandsOrder = std::string(argv[argIndex]); - } - else if(argument == "-outputFile") - { - argIndex++; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided."); - } - outputFile_ = std::string(argv[argIndex]); - log_ << "Writing output to: " << outputFile_ << "\n"; - } - else if(argument == "-outputCornerFile") - { - argIndex++; - if (argIndex >= argc) - { - throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided."); - } - outputCornerFile_ = std::string(argv[argIndex]); - log_ << "Writing corners to: " << outputCornerFile_ << "\n"; - } - else - { - printHelp(); - throw OdmOrthoPhotoException("Unrecognised argument '" + argument + "'"); - } - } - log_ << "\n"; - - std::stringstream ss(bandsOrder); - std::string item; - while(std::getline(ss, item, ',')){ - std::string itemL = item; - // To lower case - std::transform(itemL.begin(), itemL.end(), itemL.begin(), [](unsigned char c){ return std::tolower(c); }); - - if (itemL == "red" || itemL == "r"){ - colorInterps.push_back(GCI_RedBand); - }else if (itemL == "green" || itemL == "g"){ - colorInterps.push_back(GCI_GreenBand); - }else if (itemL == "blue" || itemL == "b"){ - colorInterps.push_back(GCI_BlueBand); - }else{ - colorInterps.push_back(GCI_GrayIndex); - } - bandDescriptions.push_back(item); - } -} - -void OdmOrthoPhoto::printHelp() -{ - log_.setIsPrintingInCout(true); - - log_ << "odm_orthophoto\n\n"; - - log_ << "Purpose\n"; - log_ << "Create an orthograpical photo from an oriented textured mesh.\n\n"; - - log_ << "Usage:\n"; - log_ << "The program requires a path to an input OBJ mesh file and a resolution, as pixels/m. All other input parameters are optional.\n\n"; - - log_ << "The following flags are available\n"; - log_ << "Call the program with flag \"-help\", or without parameters to print this message, or check any generated log file.\n"; - log_ << "Call the program with flag \"-verbose\", to print log messages in the standard output stream as well as in the log file.\n\n"; - - log_ << "Parameters are specified as: \"- \", (without <>), and the following parameters are configureable:\n"; - log_ << "\"-inputFiles [,,,...]\" (mandatory)\n"; - log_ << "\"Input obj files that must contain a textured mesh.\n\n"; - - log_ << "\"-outputFile \" (optional, default: ortho.jpg)\n"; - log_ << "\"Target file in which the orthophoto is saved.\n\n"; - - log_ << "\"-outputCornerFile \" (optional)\n"; - log_ << "\"Target text file for boundary corner points, written as \"xmin ymin xmax ymax\".\n\n"; - - log_ << "\"-resolution \" (mandatory)\n"; - log_ << "\"The number of pixels used per meter.\n\n"; - - log_ << "\"-bands red,green,blue,[...]\" (optional)\n"; - log_ << "\"Naming of bands to assign color interpolation values when creating output TIFF.\n\n"; - - log_.setIsPrintingInCout(false); -} - -void OdmOrthoPhoto::saveTIFF(const std::string &filename, GDALDataType dataType){ - GDALAllRegister(); - GDALDriverH hDriver = GDALGetDriverByName( "GTiff" ); - if (!hDriver){ - std::cerr << "Cannot initialize GeoTIFF driver. Check your GDAL installation." << std::endl; - exit(1); - } - char **papszOptions = NULL; - GDALDatasetH hDstDS = GDALCreate( hDriver, filename.c_str(), width, height, - static_cast(bands.size() + 1), dataType, papszOptions ); - GDALRasterBandH hBand; - - // Bands - size_t i = 0; - for (; i < bands.size(); i++){ - hBand = GDALGetRasterBand( hDstDS, static_cast(i) + 1 ); - - GDALColorInterp interp = GCI_GrayIndex; - if (i < colorInterps.size()){ - interp = colorInterps[i]; - } - GDALSetRasterColorInterpretation(hBand, interp ); - - if (i < bandDescriptions.size()){ - GDALSetDescription(hBand, bandDescriptions[i].c_str()); - } - - if (GDALRasterIO( hBand, GF_Write, 0, 0, width, height, - bands[i], width, height, dataType, 0, 0 ) != CE_None){ - std::cerr << "Cannot write TIFF to " << filename << std::endl; - exit(1); - } - } - - // Alpha - if (dataType == GDT_Float32){ - finalizeAlphaBand(); - }else if (dataType == GDT_UInt16){ - finalizeAlphaBand(); - }else if (dataType == GDT_Byte){ - finalizeAlphaBand(); - }else{ - throw OdmOrthoPhotoException("Invalid data type"); - } - - // Alpha - hBand = GDALGetRasterBand( hDstDS, static_cast(i) + 1 ); - - // Set alpha band - GDALSetRasterColorInterpretation(hBand, GCI_AlphaBand ); - - if (GDALRasterIO( hBand, GF_Write, 0, 0, width, height, - alphaBand, width, height, dataType, 0, 0 ) != CE_None){ - std::cerr << "Cannot write TIFF (alpha) to " << filename << std::endl; - exit(1); - } - - GDALClose( hDstDS ); -} - -template -inline T maxRange(){ - return static_cast(pow(2, sizeof(T) * 8) - 1); -} - -template -void OdmOrthoPhoto::initBands(int count){ - size_t pixelCount = static_cast(width) * static_cast(height); - - // Channels - for (int i = 0; i < count; i++){ - T *arr = new T[pixelCount]; - for (size_t j = 0; j < pixelCount; j++){ - arr[j] = maxRange(); - } - bands.push_back(static_cast(arr)); - } -} - -template -void OdmOrthoPhoto::initAlphaBand(){ - size_t pixelCount = static_cast(width) * static_cast(height); - // Alpha - if (alphaBand == nullptr){ - T *arr = new T[pixelCount]; - for (size_t j = 0; j < pixelCount; j++){ - arr[j] = 0.0; - } - alphaBand = static_cast(arr); - } -} - -template -void OdmOrthoPhoto::finalizeAlphaBand(){ - // Adjust alpha band values, only pixels that have - // values on all bands should be visible - - size_t pixelCount = static_cast(width) * static_cast(height); - int channels = bands.size(); - - T *arr = reinterpret_cast(alphaBand); - for (size_t j = 0; j < pixelCount; j++){ - arr[j] = arr[j] >= channels ? 255.0 : 0.0; - } -} - - -void OdmOrthoPhoto::createOrthoPhoto() -{ - if(inputFiles.size() == 0) - { - throw OdmOrthoPhotoException("Failed to create ortho photo, no texture meshes given."); - } - - int textureDepth = -1; - bool primary = true; - Bounds bounds; - - for (auto &inputFile : inputFiles){ - log_ << "Reading mesh file... " << inputFile << "\n"; - - std::vector companions; /**< Materials (used by loadOBJFile). **/ - pcl::TextureMesh mesh; - loadObjFile(inputFile, mesh, companions); - log_ << "Mesh file read.\n\n"; - - // Does the model have more than one material? - bool multiMaterial_ = 1 < mesh.tex_materials.size(); - bool splitModel = false; - - if(multiMaterial_) - { - // Need to check relationship between texture coordinates and faces. - if(!isModelOk(mesh)) - { - splitModel = true; - } - } - - Bounds b = computeBoundsForModel(mesh); - - log_ << "Model bounds x : " << b.xMin << " -> " << b.xMax << '\n'; - log_ << "Model bounds y : " << b.yMin << " -> " << b.yMax << '\n'; - - if (primary){ - bounds = b; - }else{ - // Quick check - if (b.xMin != bounds.xMin || - b.xMax != bounds.xMax || - b.yMin != bounds.yMin || - b.yMax != bounds.yMax){ - throw OdmOrthoPhotoException("Bounds between models must all match, but they don't."); - } - } - - // The size of the area. - float xDiff = bounds.xMax - bounds.xMin; - float yDiff = bounds.yMax - bounds.yMin; - log_ << "Model area : " << xDiff*yDiff << "m2\n"; - - // The resolution necessary to fit the area with the given resolution. - height = static_cast(std::ceil(resolution_*yDiff)); - width = static_cast(std::ceil(resolution_*xDiff)); - - depth_ = cv::Mat::zeros(height, width, CV_32F) - std::numeric_limits::infinity(); - log_ << "Model resolution, width x height : " << width << "x" << height << '\n'; - - // Check size of photo. - if(0 >= height*width) - { - if(0 >= height) - { - log_ << "Warning: ortho photo has zero area, height = " << height << ". Forcing height = 1.\n"; - height = 1; - } - if(0 >= width) - { - log_ << "Warning: ortho photo has zero area, width = " << width << ". Forcing width = 1.\n"; - width = 1; - } - log_ << "New ortho photo resolution, width x height : " << width << "x" << height << '\n'; - } - - // Contains the vertices of the mesh. - pcl::PointCloud::Ptr meshCloud (new pcl::PointCloud); - pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud); - - // Split model and make copies of vertices and texture coordinates for all faces - if (splitModel) - { - pcl::PointCloud::Ptr meshCloudSplit (new pcl::PointCloud); - std::vector > textureCoordinates = std::vector >(0); - - size_t vertexIndexCount = 0; - for(size_t t = 0; t < mesh.tex_polygons.size(); ++t) - { - vertexIndexCount += 3 * mesh.tex_polygons[t].size(); - } - textureCoordinates.reserve(vertexIndexCount); - - for(size_t t = 0; t < mesh.tex_polygons.size(); ++t) - { - - for(size_t faceIndex = 0; faceIndex < mesh.tex_polygons[t].size(); ++faceIndex) - { - pcl::Vertices polygon = mesh.tex_polygons[t][faceIndex]; - - // The index to the vertices of the polygon. - size_t v1i = polygon.vertices[0]; - size_t v2i = polygon.vertices[1]; - size_t v3i = polygon.vertices[2]; - - // The polygon's points. - pcl::PointXYZ v1 = meshCloud->points[v1i]; - pcl::PointXYZ v2 = meshCloud->points[v2i]; - pcl::PointXYZ v3 = meshCloud->points[v3i]; - - Eigen::Vector2f vt1 = mesh.tex_coordinates[0][3*faceIndex]; - Eigen::Vector2f vt2 = mesh.tex_coordinates[0][3*faceIndex + 1]; - Eigen::Vector2f vt3 = mesh.tex_coordinates[0][3*faceIndex + 2]; - - meshCloudSplit->points.push_back(v1); - textureCoordinates.push_back(vt1); - mesh.tex_polygons[t][faceIndex].vertices[0] = vertexIndexCount; - - meshCloudSplit->points.push_back(v2); - textureCoordinates.push_back(vt2); - mesh.tex_polygons[t][faceIndex].vertices[1] = vertexIndexCount; - - meshCloudSplit->points.push_back(v3); - textureCoordinates.push_back(vt3); - mesh.tex_polygons[t][faceIndex].vertices[2] = vertexIndexCount; - } - } - - mesh.tex_coordinates.clear(); - mesh.tex_coordinates.push_back(textureCoordinates); - - meshCloud = meshCloudSplit; - } - - // Creates a transformation which aligns the area for the ortho photo. - Eigen::Transform transform = getROITransform(bounds.xMin, -bounds.yMax); - log_ << "Translating and scaling mesh...\n"; - - // Move the mesh into position. - pcl::transformPointCloud(*meshCloud, *meshCloud, transform); - log_ << ".. mesh translated and scaled.\n\n"; - - // Flatten texture coordinates. - std::vector uvs; - uvs.reserve(mesh.tex_coordinates.size()); - for(size_t t = 0; t < mesh.tex_coordinates.size(); ++t) - { - uvs.insert(uvs.end(), mesh.tex_coordinates[t].begin(), mesh.tex_coordinates[t].end()); - } - - // The current material texture - cv::Mat texture; - - // Used to keep track of the global face index. - size_t faceOff = 0; - - log_ << "Rendering the ortho photo...\n"; - - // Iterate over each part of the mesh (one per material). - for(size_t t = 0; t < mesh.tex_materials.size(); ++t) - { - // The material of the current submesh. - pcl::TexMaterial material = mesh.tex_materials[t]; - texture = cv::imread(material.tex_file, cv::IMREAD_ANYDEPTH | cv::IMREAD_UNCHANGED); - - // BGR to RGB when necessary - if (texture.channels() == 3){ - cv::cvtColor(texture, texture, cv::COLOR_BGR2RGB); - } - - // The first material determines the bit depth - // Init ortho photo - if (t == 0){ - if (primary) textureDepth = texture.depth(); - else if (textureDepth != texture.depth()){ - // Try to convert - if (textureDepth == CV_8U){ - if (texture.depth() == CV_16U){ - // 16U to 8U - texture.convertTo(texture, CV_8U, 255.0f / 65535.0f); - }else{ - throw OdmOrthoPhotoException("Unknown conversion from CV_8U"); - } - }else if (textureDepth == CV_16U){ - if (texture.depth() == CV_8U){ - // 8U to 16U - texture.convertTo(texture, CV_16U, 65535.0f / 255.0f); - }else{ - throw OdmOrthoPhotoException("Unknown conversion from CV_16U"); - } - }else{ - throw OdmOrthoPhotoException("Texture depth is not the same for all models and could not be converted"); - } - } - - log_ << "Texture channels: " << texture.channels() << "\n"; - - try{ - if (textureDepth == CV_8U){ - log_ << "Texture depth: 8bit\n"; - initBands(texture.channels()); - if (primary) initAlphaBand(); - }else if (textureDepth == CV_16U){ - log_ << "Texture depth: 16bit\n"; - initBands(texture.channels()); - if (primary) initAlphaBand(); - }else if (textureDepth == CV_32F){ - log_ << "Texture depth: 32bit (float)\n"; - initBands(texture.channels()); - if (primary) initAlphaBand(); - }else{ - std::cerr << "Unsupported bit depth value: " << textureDepth; - exit(1); - } - }catch(const std::bad_alloc &){ - std::cerr << "Couldn't allocate enough memory to render the orthophoto (" << width << "x" << height << " cells = " << ((long long)width * (long long)height * 4) << " bytes). Try to increase the --orthophoto-resolution parameter to a larger integer or add more RAM.\n"; - exit(1); - } - } - - // Check for missing files. - if(texture.empty()) - { - log_ << "Material texture could not be read:\n"; - log_ << material.tex_file << '\n'; - log_ << "Could not be read as image, does the file exist?\n"; - continue; // Skip to next material. - } - - // The faces of the current submesh. - std::vector faces = mesh.tex_polygons[t]; - - // Iterate over each face... - for(size_t faceIndex = 0; faceIndex < faces.size(); ++faceIndex) - { - // The current polygon. - pcl::Vertices polygon = faces[faceIndex]; - - // ... and draw it into the ortho photo. - if (textureDepth == CV_8U){ - drawTexturedTriangle(texture, polygon, meshCloud, uvs, faceIndex+faceOff); - }else if (textureDepth == CV_16U){ - drawTexturedTriangle(texture, polygon, meshCloud, uvs, faceIndex+faceOff); - }else if (textureDepth == CV_32F){ - drawTexturedTriangle(texture, polygon, meshCloud, uvs, faceIndex+faceOff); - } - } - faceOff += faces.size(); - log_ << "Material " << t << " rendered.\n"; - } - log_ << "... model rendered\n"; - - currentBandIndex += texture.channels(); - primary = false; - } - - log_ << '\n'; - log_ << "Writing ortho photo to " << outputFile_ << "\n"; - - if (textureDepth == CV_8U){ - saveTIFF(outputFile_, GDT_Byte); - }else if (textureDepth == CV_16U){ - saveTIFF(outputFile_, GDT_UInt16); - }else if (textureDepth == CV_32F){ - saveTIFF(outputFile_, GDT_Float32); - }else{ - std::cerr << "Unsupported bit depth value: " << textureDepth; - exit(1); - } - - if (!outputCornerFile_.empty()) - { - log_ << "Writing corner coordinates to " << outputCornerFile_ << "\n"; - std::ofstream cornerStream(outputCornerFile_.c_str()); - if (!cornerStream.is_open()) - { - throw OdmOrthoPhotoException("Failed opening output corner file " + outputCornerFile_ + "."); - } - cornerStream.setf(std::ios::scientific, std::ios::floatfield); - cornerStream.precision(17); - cornerStream << bounds.xMin << " " << bounds.yMin << " " << bounds.xMax << " " << bounds.yMax; - cornerStream.close(); - } - - log_ << "Orthophoto generation done.\n"; -} - -Bounds OdmOrthoPhoto::computeBoundsForModel(const pcl::TextureMesh &mesh) -{ - log_ << "Set boundary to contain entire model.\n"; - - // The boundary of the model. - Bounds r; - - r.xMin = std::numeric_limits::infinity(); - r.xMax = -std::numeric_limits::infinity(); - r.yMin = std::numeric_limits::infinity(); - r.yMax = -std::numeric_limits::infinity(); - - // Contains the vertices of the mesh. - pcl::PointCloud::Ptr meshCloud (new pcl::PointCloud); - pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud); - - for (size_t i = 0; i < meshCloud->points.size(); i++){ - pcl::PointXYZ v = meshCloud->points[i]; - r.xMin = std::min(r.xMin, v.x); - r.xMax = std::max(r.xMax, v.x); - r.yMin = std::min(r.yMin, v.y); - r.yMax = std::max(r.yMax, v.y); - } - - log_ << "Boundary points:\n"; - log_ << "Point 1: " << r.xMin << " " << r.yMin << "\n"; - log_ << "Point 2: " << r.xMin << " " << r.yMax << "\n"; - log_ << "Point 3: " << r.xMax << " " << r.yMax << "\n"; - log_ << "Point 4: " << r.xMax << " " << r.yMin << "\n"; - log_ << "\n"; - - return r; -} - -Eigen::Transform OdmOrthoPhoto::getROITransform(float xMin, float yMin) const -{ - // The transform used to move the chosen area into the ortho photo. - Eigen::Transform transform; - - transform(0, 0) = resolution_; // x Scaling. - transform(1, 0) = 0.0f; - transform(2, 0) = 0.0f; - transform(3, 0) = 0.0f; - - transform(0, 1) = 0.0f; - transform(1, 1) = -resolution_; // y Scaling, mirrored for easier rendering. - transform(2, 1) = 0.0f; - transform(3, 1) = 0.0f; - - transform(0, 2) = 0.0f; - transform(1, 2) = 0.0f; - transform(2, 2) = 1.0f; - transform(3, 2) = 0.0f; - - transform(0, 3) = -xMin * resolution_; // x Translation - transform(1, 3) = -yMin * resolution_; // y Translation - transform(2, 3) = 0.0f; - transform(3, 3) = 1.0f; - - return transform; -} - -template -void OdmOrthoPhoto::drawTexturedTriangle(const cv::Mat &texture, const pcl::Vertices &polygon, const pcl::PointCloud::Ptr &meshCloud, const std::vector &uvs, size_t faceIndex) -{ - // The index to the vertices of the polygon. - size_t v1i = polygon.vertices[0]; - size_t v2i = polygon.vertices[1]; - size_t v3i = polygon.vertices[2]; - - // The polygon's points. - pcl::PointXYZ v1 = meshCloud->points[v1i]; - pcl::PointXYZ v2 = meshCloud->points[v2i]; - pcl::PointXYZ v3 = meshCloud->points[v3i]; - - if(isSliverPolygon(v1, v2, v3)) - { - log_ << "Warning: Sliver polygon found at face index " << faceIndex << '\n'; - return; - } - - // The face data. Position v*{x,y,z}. Texture coordinate v*{u,v}. * is the vertex number in the polygon. - float v1x, v1y, v1z, v1u, v1v; - float v2x, v2y, v2z, v2u, v2v; - float v3x, v3y, v3z, v3u, v3v; - - // Barycentric coordinates of the currently rendered point. - float l1, l2, l3; - - // The size of the photo, as float. - float fRows, fCols; - fRows = static_cast(texture.rows); - fCols = static_cast(texture.cols); - - // Get vertex position. - v1x = v1.x; v1y = v1.y; v1z = v1.z; - v2x = v2.x; v2y = v2.y; v2z = v2.z; - v3x = v3.x; v3y = v3.y; v3z = v3.z; - - // Get texture coordinates. - v1u = uvs[3*faceIndex][0]; v1v = uvs[3*faceIndex][1]; - v2u = uvs[3*faceIndex+1][0]; v2v = uvs[3*faceIndex+1][1]; - v3u = uvs[3*faceIndex+2][0]; v3v = uvs[3*faceIndex+2][1]; - - // Check bounding box overlap. - int xMin = static_cast(std::min(std::min(v1x, v2x), v3x)); - if(xMin > width) - { - return; // Completely outside to the right. - } - int xMax = static_cast(std::max(std::max(v1x, v2x), v3x)); - if(xMax < 0) - { - return; // Completely outside to the left. - } - int yMin = static_cast(std::min(std::min(v1y, v2y), v3y)); - if(yMin > height) - { - return; // Completely outside to the top. - } - int yMax = static_cast(std::max(std::max(v1y, v2y), v3y)); - if(yMax < 0) - { - return; // Completely outside to the bottom. - } - - // Top point row and column positions - float topR, topC; - // Middle point row and column positions - float midR, midC; - // Bottom point row and column positions - float botR, botC; - - // Find top, middle and bottom points. - if(v1y < v2y) - { - if(v1y < v3y) - { - if(v2y < v3y) - { - // 1 -> 2 -> 3 - topR = v1y; topC = v1x; - midR = v2y; midC = v2x; - botR = v3y; botC = v3x; - } - else - { - // 1 -> 3 -> 2 - topR = v1y; topC = v1x; - midR = v3y; midC = v3x; - botR = v2y; botC = v2x; - } - } - else - { - // 3 -> 1 -> 2 - topR = v3y; topC = v3x; - midR = v1y; midC = v1x; - botR = v2y; botC = v2x; - } - } - else // v2y <= v1y - { - if(v2y < v3y) - { - if(v1y < v3y) - { - // 2 -> 1 -> 3 - topR = v2y; topC = v2x; - midR = v1y; midC = v1x; - botR = v3y; botC = v3x; - } - else - { - // 2 -> 3 -> 1 - topR = v2y; topC = v2x; - midR = v3y; midC = v3x; - botR = v1y; botC = v1x; - } - } - else - { - // 3 -> 2 -> 1 - topR = v3y; topC = v3x; - midR = v2y; midC = v2x; - botR = v1y; botC = v1x; - } - } - - // General appreviations: - // --------------------- - // tm : Top(to)Middle. - // mb : Middle(to)Bottom. - // tb : Top(to)Bottom. - // c : column. - // r : row. - // dr : DeltaRow, step value per row. - - // The step along column for every step along r. Top to middle. - float ctmdr; - // The step along column for every step along r. Top to bottom. - float ctbdr; - // The step along column for every step along r. Middle to bottom. - float cmbdr; - - ctbdr = (botC-topC)/(botR-topR); - - // The current column position, from top to middle. - float ctm = topC; - // The current column position, from top to bottom. - float ctb = topC; - - // Check for vertical line between middle and top. - if(FLT_EPSILON < midR-topR) - { - ctmdr = (midC-topC)/(midR-topR); - - // The first pixel row for the bottom part of the triangle. - int rqStart = std::max(static_cast(std::floor(topR+0.5f)), 0); - // The last pixel row for the top part of the triangle. - int rqEnd = std::min(static_cast(std::floor(midR+0.5f)), height); - - // Traverse along row from top to middle. - for(int rq = rqStart; rq < rqEnd; ++rq) - { - // Set the current column positions. - ctm = topC + ctmdr*(static_cast(rq)+0.5f-topR); - ctb = topC + ctbdr*(static_cast(rq)+0.5f-topR); - - // The first pixel column for the current row. - int cqStart = std::max(static_cast(std::floor(0.5f+std::min(ctm, ctb))), 0); - // The last pixel column for the current row. - int cqEnd = std::min(static_cast(std::floor(0.5f+std::max(ctm, ctb))), width); - - for(int cq = cqStart; cq < cqEnd; ++cq) - { - // Get barycentric coordinates for the current point. - getBarycentricCoordinates(v1, v2, v3, static_cast(cq)+0.5f, static_cast(rq)+0.5f, l1, l2, l3); - - if(0.f > l1 || 0.f > l2 || 0.f > l3) - { - //continue; - } - - // The z value for the point. - float z = v1z*l1+v2z*l2+v3z*l3; - - // Check depth - float depthValue = depth_.at(rq, cq); - if(z < depthValue) - { - // Current is behind another, don't draw. - continue; - } - - // The uv values of the point. - float u, v; - u = v1u*l1+v2u*l2+v3u*l3; - v = v1v*l1+v2v*l2+v3v*l3; - - renderPixel(rq, cq, u*fCols, (1.0f-v)*fRows, texture); - - // Update depth buffer. - depth_.at(rq, cq) = z; - } - } - } - - if(FLT_EPSILON < botR-midR) - { - cmbdr = (botC-midC)/(botR-midR); - - // The current column position, from middle to bottom. - float cmb = midC; - - // The first pixel row for the bottom part of the triangle. - int rqStart = std::max(static_cast(std::floor(midR+0.5f)), 0); - // The last pixel row for the bottom part of the triangle. - int rqEnd = std::min(static_cast(std::floor(botR+0.5f)), height); - - // Traverse along row from middle to bottom. - for(int rq = rqStart; rq < rqEnd; ++rq) - { - // Set the current column positions. - ctb = topC + ctbdr*(static_cast(rq)+0.5f-topR); - cmb = midC + cmbdr*(static_cast(rq)+0.5f-midR); - - // The first pixel column for the current row. - int cqStart = std::max(static_cast(std::floor(0.5f+std::min(cmb, ctb))), 0); - // The last pixel column for the current row. - int cqEnd = std::min(static_cast(std::floor(0.5f+std::max(cmb, ctb))), width); - - for(int cq = cqStart; cq < cqEnd; ++cq) - { - // Get barycentric coordinates for the current point. - getBarycentricCoordinates(v1, v2, v3, static_cast(cq)+0.5f, static_cast(rq)+0.5f, l1, l2, l3); - - if(0.f > l1 || 0.f > l2 || 0.f > l3) - { - //continue; - } - - // The z value for the point. - float z = v1z*l1+v2z*l2+v3z*l3; - - // Check depth - float depthValue = depth_.at(rq, cq); - if(z < depthValue) - { - // Current is behind another, don't draw. - continue; - } - - // The uv values of the point. - float u, v; - u = v1u*l1+v2u*l2+v3u*l3; - v = v1v*l1+v2v*l2+v3v*l3; - - renderPixel(rq, cq, u*fCols, (1.0f-v)*fRows, texture); - - // Update depth buffer. - depth_.at(rq, cq) = z; - } - } - } -} - -template -void OdmOrthoPhoto::renderPixel(int row, int col, float s, float t, const cv::Mat &texture) -{ - // The offset of the texture coordinate from its pixel positions. - float leftF, topF; - // The position of the top left pixel. - int left, top; - // The distance to the left and right pixel from the texture coordinate. - float dl, dt; - // The distance to the top and bottom pixel from the texture coordinate. - float dr, db; - - dl = modff(s, &leftF); - dr = 1.0f - dl; - dt = modff(t, &topF); - db = 1.0f - dt; - - left = static_cast(leftF); - top = static_cast(topF); - - // The interpolated color values. - size_t idx = static_cast(row) * static_cast(width) + static_cast(col); - T *data = reinterpret_cast(texture.data); // Faster access - int numChannels = texture.channels(); - - for (int i = 0; i < numChannels; i++){ - float value = 0.0f; - - T tl = data[(top) * texture.cols * numChannels + (left) * numChannels + i]; - T tr = data[(top) * texture.cols * numChannels + (left + 1) * numChannels + i]; - T bl = data[(top + 1) * texture.cols * numChannels + (left) * numChannels + i]; - T br = data[(top + 1) * texture.cols * numChannels + (left + 1) * numChannels + i]; - - value += static_cast(tl) * dr * db; - value += static_cast(tr) * dl * db; - value += static_cast(bl) * dr * dt; - value += static_cast(br) * dl * dt; - - static_cast(bands[currentBandIndex + i])[idx] = static_cast(value); - } - - // Increment the alpha band if the pixel was visible for this band - // the final alpha band will be set to 255 if alpha == num bands - // (all bands have information at this pixel) - static_cast(alphaBand)[idx] += static_cast(numChannels); -} - -void OdmOrthoPhoto::getBarycentricCoordinates(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3, float x, float y, float &l1, float &l2, float &l3) const -{ - // Diff along y. - float y2y3 = v2.y-v3.y; - float y1y3 = v1.y-v3.y; - float y3y1 = v3.y-v1.y; - float yy3 = y -v3.y; - - // Diff along x. - float x3x2 = v3.x-v2.x; - float x1x3 = v1.x-v3.x; - float xx3 = x -v3.x; - - // Normalization factor. - float norm = (y2y3*x1x3 + x3x2*y1y3); - - l1 = (y2y3*(xx3) + x3x2*(yy3)) / norm; - l2 = (y3y1*(xx3) + x1x3*(yy3)) / norm; - l3 = 1 - l1 - l2; -} - -bool OdmOrthoPhoto::isSliverPolygon(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3) const -{ - // Calculations are made using doubles, to minize rounding errors. - Eigen::Vector3d a = Eigen::Vector3d(static_cast(v1.x), static_cast(v1.y), static_cast(v1.z)); - Eigen::Vector3d b = Eigen::Vector3d(static_cast(v2.x), static_cast(v2.y), static_cast(v2.z)); - Eigen::Vector3d c = Eigen::Vector3d(static_cast(v3.x), static_cast(v3.y), static_cast(v3.z)); - Eigen::Vector3d dummyVec = (a-b).cross(c-b); - - // Area smaller than, or equal to, floating-point epsilon. - return std::numeric_limits::epsilon() >= static_cast(std::sqrt(dummyVec.dot(dummyVec))/2.0); -} - -bool OdmOrthoPhoto::isModelOk(const pcl::TextureMesh &mesh) -{ - // The number of texture coordinates in the model. - size_t nTextureCoordinates = 0; - // The number of faces in the model. - size_t nFaces = 0; - - for(size_t t = 0; t < mesh.tex_coordinates.size(); ++t) - { - nTextureCoordinates += mesh.tex_coordinates[t].size(); - } - for(size_t t = 0; t < mesh.tex_polygons.size(); ++t) - { - nFaces += mesh.tex_polygons[t].size(); - } - - log_ << "Number of faces in the model " << nFaces << '\n'; - - return 3*nFaces == nTextureCoordinates; -} - - -bool OdmOrthoPhoto::loadObjFile(std::string inputFile, pcl::TextureMesh &mesh, std::vector &companions) -{ - int data_type; - unsigned int data_idx; - int file_version; - int offset = 0; - Eigen::Vector4f origin; - Eigen::Quaternionf orientation; - - if (!readHeader(inputFile, mesh.cloud, origin, orientation, file_version, data_type, data_idx, offset, companions)) - { - throw OdmOrthoPhotoException("Problem reading header in modelfile!\n"); - } - - std::ifstream fs; - - fs.open (inputFile.c_str (), std::ios::binary); - if (!fs.is_open () || fs.fail ()) - { - //PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n", file_name.c_str (), strerror(errno)); - fs.close (); - log_<<"Could not read mesh from file "; - log_ << inputFile.c_str(); - log_ <<"\n"; - - throw OdmOrthoPhotoException("Problem reading mesh from file!\n"); - } - - // Seek at the given offset - fs.seekg (data_idx, std::ios::beg); - - // Get normal_x field indices - int normal_x_field = -1; - for (std::size_t i = 0; i < mesh.cloud.fields.size (); ++i) - { - if (mesh.cloud.fields[i].name == "normal_x") - { - normal_x_field = i; - break; - } - } - - std::size_t v_idx = 0; - std::size_t vn_idx = 0; - std::size_t vt_idx = 0; - std::size_t f_idx = 0; - std::string line; - std::vector st; - std::vector > coordinates; - std::vector allTexCoords; - - std::map f2vt; - - try - { - while (!fs.eof ()) - { - getline (fs, line); - // Ignore empty lines - if (line == "") - continue; - - // Tokenize the line - std::stringstream sstream (line); - sstream.imbue (std::locale::classic ()); - line = sstream.str (); - boost::trim (line); - boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on); - - // Ignore comments - if (st[0] == "#") - continue; - // Vertex - if (st[0] == "v") - { - try - { - for (int i = 1, f = 0; i < 4; ++i, ++f) - { - float value = boost::lexical_cast (st[i]); - memcpy (&mesh.cloud.data[v_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset], &value, sizeof (float)); - } - - ++v_idx; - } - catch (const boost::bad_lexical_cast &e) - { - log_<<"Unable to convert %s to vertex coordinates!\n"; - throw OdmOrthoPhotoException("Unable to convert %s to vertex coordinates!"); - } - continue; - } - // Vertex normal - if (st[0] == "vn") - { - try - { - for (int i = 1, f = normal_x_field; i < 4; ++i, ++f) - { - float value = boost::lexical_cast (st[i]); - memcpy (&mesh.cloud.data[vn_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset], - &value, - sizeof (float)); - } - ++vn_idx; - } - catch (const boost::bad_lexical_cast &e) - { - log_<<"Unable to convert %s to vertex normal!\n"; - throw OdmOrthoPhotoException("Unable to convert %s to vertex normal!"); - } - continue; - } - // Texture coordinates - if (st[0] == "vt") - { - try - { - Eigen::Vector3f c (0, 0, 0); - for (std::size_t i = 1; i < st.size (); ++i) - c[i-1] = boost::lexical_cast (st[i]); - - if (c[2] == 0) - coordinates.push_back (Eigen::Vector2f (c[0], c[1])); - else - coordinates.push_back (Eigen::Vector2f (c[0]/c[2], c[1]/c[2])); - ++vt_idx; - - } - catch (const boost::bad_lexical_cast &e) - { - log_<<"Unable to convert %s to vertex texture coordinates!\n"; - throw OdmOrthoPhotoException("Unable to convert %s to vertex texture coordinates!"); - } - continue; - } - // Material - if (st[0] == "usemtl") - { - mesh.tex_polygons.push_back (std::vector ()); - mesh.tex_materials.push_back (pcl::TexMaterial ()); - for (std::size_t i = 0; i < companions.size (); ++i) - { - std::vector::const_iterator mat_it = companions[i].getMaterial (st[1]); - if (mat_it != companions[i].materials_.end ()) - { - mesh.tex_materials.back () = *mat_it; - break; - } - } - // We didn't find the appropriate material so we create it here with name only. - if (mesh.tex_materials.back ().tex_name == "") - mesh.tex_materials.back ().tex_name = st[1]; - mesh.tex_coordinates.push_back (coordinates); - coordinates.clear (); - continue; - } - // Face - if (st[0] == "f") - { - //We only care for vertices indices - pcl::Vertices face_v; face_v.vertices.resize (st.size () - 1); - for (std::size_t i = 1; i < st.size (); ++i) - { - int v; - sscanf (st[i].c_str (), "%d", &v); - v = (v < 0) ? v_idx + v : v - 1; - face_v.vertices[i-1] = v; - - int v2, vt, vn; - sscanf (st[i].c_str (), "%d/%d/%d", &v2, &vt, &vn); - f2vt[3*(f_idx) + i-1] = vt-1; - } - mesh.tex_polygons.back ().push_back (face_v); - ++f_idx; - continue; - } - } - } - catch (const char *exception) - { - fs.close (); - log_<<"Unable to read file!\n"; - throw OdmOrthoPhotoException("Unable to read file!"); - } - - if (vt_idx != v_idx) - { - std::vector > texcoordinates = std::vector >(0); - texcoordinates.reserve(3*f_idx); - - for (size_t faceIndex = 0; faceIndex < f_idx; ++faceIndex) - { - for(size_t i = 0; i < 3; ++i) - { - Eigen::Vector2f vt = mesh.tex_coordinates[0][f2vt[3*faceIndex+i]]; - texcoordinates.push_back(vt); - } - } - - mesh.tex_coordinates.clear(); - mesh.tex_coordinates.push_back(texcoordinates); - } - - fs.close(); - return (0); -} - -bool OdmOrthoPhoto::readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud, - Eigen::Vector4f &origin, Eigen::Quaternionf &orientation, - int &file_version, int &data_type, unsigned int &data_idx, - const int offset, - std::vector &companions) -{ - origin = Eigen::Vector4f::Zero (); - orientation = Eigen::Quaternionf::Identity (); - file_version = 0; - cloud.width = cloud.height = cloud.point_step = cloud.row_step = 0; - cloud.data.clear (); - data_type = 0; - data_idx = offset; - - std::ifstream fs; - std::string line; - - if (file_name == "" || !boost::filesystem::exists (file_name)) - { - return false; - } - - // Open file in binary mode to avoid problem of - // std::getline() corrupting the result of ifstream::tellg() - fs.open (file_name.c_str (), std::ios::binary); - if (!fs.is_open () || fs.fail ()) - { - fs.close (); - return false; - } - - // Seek at the given offset - fs.seekg (offset, std::ios::beg); - - // Read the header and fill it in with wonderful values - bool vertex_normal_found = false; - bool vertex_texture_found = false; - // Material library, skip for now! - // bool material_found = false; - std::vector material_files; - std::size_t nr_point = 0; - std::vector st; - - try - { - while (!fs.eof ()) - { - getline (fs, line); - // Ignore empty lines - if (line == "") - continue; - - // Tokenize the line - std::stringstream sstream (line); - sstream.imbue (std::locale::classic ()); - line = sstream.str (); - boost::trim (line); - boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on); - // Ignore comments - if (st.at (0) == "#") - continue; - - // Vertex - if (st.at (0) == "v") - { - ++nr_point; - continue; - } - - // Vertex texture - if ((st.at (0) == "vt") && !vertex_texture_found) - { - vertex_texture_found = true; - continue; - } - - // Vertex normal - if ((st.at (0) == "vn") && !vertex_normal_found) - { - vertex_normal_found = true; - continue; - } - - // Material library, skip for now! - if (st.at (0) == "mtllib") - { - material_files.push_back (st.at (1)); - continue; - } - } - } - catch (const char *exception) - { - fs.close (); - return false; - } - - if (!nr_point) - { - fs.close (); - return false; - } - - int field_offset = 0; - for (int i = 0; i < 3; ++i, field_offset += 4) - { - cloud.fields.push_back (pcl::PCLPointField ()); - cloud.fields[i].offset = field_offset; - cloud.fields[i].datatype = pcl::PCLPointField::FLOAT32; - cloud.fields[i].count = 1; - } - - cloud.fields[0].name = "x"; - cloud.fields[1].name = "y"; - cloud.fields[2].name = "z"; - - if (vertex_normal_found) - { - std::string normals_names[3] = { "normal_x", "normal_y", "normal_z" }; - for (int i = 0; i < 3; ++i, field_offset += 4) - { - cloud.fields.push_back (pcl::PCLPointField ()); - pcl::PCLPointField& last = cloud.fields.back (); - last.name = normals_names[i]; - last.offset = field_offset; - last.datatype = pcl::PCLPointField::FLOAT32; - last.count = 1; - } - } - - if (material_files.size () > 0) - { - for (std::size_t i = 0; i < material_files.size (); ++i) - { - pcl::MTLReader companion; - - if (companion.read (file_name, material_files[i])) - { - log_<<"Problem reading material file."; - } - - companions.push_back (companion); - } - } - - cloud.point_step = field_offset; - cloud.width = nr_point; - cloud.height = 1; - cloud.row_step = cloud.point_step * cloud.width; - cloud.is_dense = true; - cloud.data.resize (cloud.point_step * nr_point); - fs.close (); - return true; -} diff --git a/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp b/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp deleted file mode 100644 index 32ee1840..00000000 --- a/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -// C++ -#include -#include -#include - -// PCL -#include -#include - -// OpenCV -#include -#include - -// PCL -#include -#include - -// OpenCV -#include - -// GDAL -#include "gdal_priv.h" -#include "cpl_conv.h" // for CPLMalloc() - -// Logger -#include "Logger.hpp" - -struct Bounds{ - float xMin; - float xMax; - float yMin; - float yMax; - - Bounds() : xMin(0), xMax(0), yMin(0), yMax(0) {} - Bounds(float xMin, float xMax, float yMin, float yMax) : - xMin(xMin), xMax(xMax), yMin(yMin), yMax(yMax){} - Bounds(const Bounds &b) { - xMin = b.xMin; - xMax = b.xMax; - yMin = b.yMin; - yMax = b.yMax; - } -}; - -/*! - * \brief The OdmOrthoPhoto class is used to create an orthographic photo over a given area. - * The class reads an oriented textured mesh from an OBJ-file. - * The class uses file read from pcl. - * The class uses image read and write from opencv. - */ -class OdmOrthoPhoto -{ -public: - OdmOrthoPhoto(); - ~OdmOrthoPhoto(); - - /*! - * \brief run Runs the ortho photo functionality using the provided input arguments. - * For a list of accepted arguments, pleas see the main page documentation or - * call the program with parameter "-help". - * \param argc Application argument count. - * \param argv Argument values. - * \return 0 if successful. - */ - int run(int argc, char* argv[]); - -private: - int width, height; - void parseArguments(int argc, char* argv[]); - void printHelp(); - - void createOrthoPhoto(); - - /*! - * \brief Compute the boundary points so that the entire model fits inside the photo. - * - * \param mesh The model which decides the boundary. - */ - Bounds computeBoundsForModel(const pcl::TextureMesh &mesh); - - /*! - * \brief Creates a transformation which aligns the area for the orthophoto. - */ - Eigen::Transform getROITransform(float xMin, float yMin) const; - - template - void initBands(int count); - - template - void initAlphaBand(); - - template - void finalizeAlphaBand(); - - void saveTIFF(const std::string &filename, GDALDataType dataType); - - /*! - * \brief Renders a triangle into the ortho photo. - * - * Pixel center defined as middle of pixel for triangle rasterisation, and in lower left corner for texture look-up. - * - * \param texture The texture of the polygon. - * \param polygon The polygon as athree indices relative meshCloud. - * \param meshCloud Contains all vertices. - * \param uvs Contains the texture coordinates for the active material. - * \param faceIndex The index of the face. - */ - template - void drawTexturedTriangle(const cv::Mat &texture, const pcl::Vertices &polygon, const pcl::PointCloud::Ptr &meshCloud, const std::vector &uvs, size_t faceIndex); - - /*! - * \brief Sets the color of a pixel in the photo. - * - * \param row The row index of the pixel. - * \param col The column index of the pixel. - * \param s The u texture-coordinate, multiplied with the number of columns in the texture. - * \param t The v texture-coordinate, multiplied with the number of rows in the texture. - * \param texture The texture from which to get the color. - **/ - template - void renderPixel(int row, int col, float u, float v, const cv::Mat &texture); - - /*! - * \brief Calculates the barycentric coordinates of a point in a triangle. - * - * \param v1 The first triangle vertex. - * \param v2 The second triangle vertex. - * \param v3 The third triangle vertex. - * \param x The x coordinate of the point. - * \param y The y coordinate of the point. - * \param l1 The first vertex weight. - * \param l2 The second vertex weight. - * \param l3 The third vertex weight. - */ - void getBarycentricCoordinates(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3, float x, float y, float &l1, float &l2, float &l3) const; - - /*! - * \brief Check if a given polygon is a sliver polygon. - * - * \param v1 The first vertex of the polygon. - * \param v2 The second vertex of the polygon. - * \param v3 The third vertex of the polygon. - */ - bool isSliverPolygon(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3) const; - - /*! - * \brief Check if the model is suitable for ortho photo generation. - * - * \param mesh The model. - * \return True if the model is ok for generating ortho photo. - */ - bool isModelOk(const pcl::TextureMesh &mesh); - - /*! - * \brief Loads a model from an .obj file (replacement for the pcl obj loader). - * - * \param inputFile Path to the .obj file. - * \param mesh The model. - * \return True if model was loaded successfully. - */ - bool loadObjFile(std::string inputFile, pcl::TextureMesh &mesh, std::vector &companions); - - /*! - * \brief Function is compied straight from the function in the pcl::io module. - */ - bool readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud, - Eigen::Vector4f &origin, Eigen::Quaternionf &orientation, - int &file_version, int &data_type, unsigned int &data_idx, - const int offset, - std::vector &companions); - - Logger log_; /**< Logging object. */ - - std::vector inputFiles; - std::string outputFile_; /**< Path to the destination file. */ - std::string outputCornerFile_; /**< Path to the output corner file. */ - std::string logFile_; /**< Path to the log file. */ - std::string bandsOrder; - - float resolution_; /**< The number of pixels per meter in the ortho photo. */ - - std::vector bands; - std::vector colorInterps; - std::vector bandDescriptions; - void *alphaBand; // Keep alpha band separate - int currentBandIndex; - - cv::Mat depth_; /**< The depth of the ortho photo as an OpenCV matrix, CV_32F. */ -}; - -/*! - * \brief The OdmOrthoPhoto class - */ -class OdmOrthoPhotoException : public std::exception -{ - -public: - OdmOrthoPhotoException() : message("Error in OdmOrthoPhoto") {} - OdmOrthoPhotoException(std::string msgInit) : message("Error in OdmOrthoPhoto:\n" + msgInit) {} - ~OdmOrthoPhotoException() throw() {} - virtual const char* what() const throw() {return message.c_str(); } - -private: - std::string message; /**< The error message **/ -}; diff --git a/modules/odm_orthophoto/src/main.cpp b/modules/odm_orthophoto/src/main.cpp deleted file mode 100644 index 1490915b..00000000 --- a/modules/odm_orthophoto/src/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Ortho photo generator. -#include "OdmOrthoPhoto.hpp" - -int main(int argc, char* argv[]) -{ - OdmOrthoPhoto orthoPhotoGenerator; - return orthoPhotoGenerator.run(argc, argv); -} diff --git a/opendm/context.py b/opendm/context.py index 7bd2e228..3eb6de74 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -44,6 +44,7 @@ pdal_path = os.path.join(superbuild_path, 'build/pdal/bin') odm_modules_path = os.path.join(root_path, "build/bin") odm_modules_src_path = os.path.join(root_path, "modules") +odm_orthophoto_path = os.path.join(superbuild_bin_path, "odm_orthophoto") settings_path = os.path.join(root_path, 'settings.yaml') # Define supported image extensions diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 61d0883a..a1737698 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -184,20 +184,14 @@ parts: cmake -G Ninja .. cmake --build . --parallel 1 - # Build the main ODM project - cd $SNAPCRAFT_PART_BUILD - mkdir -p build - cd build - cmake -G Ninja .. - cmake --build . --parallel $SNAPCRAFT_PARALLEL_BUILD_COUNT - rsync -av --exclude .git \ $SNAPCRAFT_PART_BUILD/ $SNAPCRAFT_PART_INSTALL/odm/ chmod -R u=rwX,go=rX $PYTHONUSERBASE/lib/python* stage: # strip the temporary build files and sources - -odm/SuperBuild/build/opencv - - -odm/SuperBuild/build/openmvs + - -odm/SuperBuild/build/openmvs + - -odm/SuperBuild/build/odm_orthophoto - -odm/SuperBuild/download - -odm/SuperBuild/src/ceres - -odm/SuperBuild/src/untwine @@ -215,6 +209,7 @@ parts: - -odm/SuperBuild/src/vcg - -odm/SuperBuild/src/dem2mesh - -odm/SuperBuild/src/dem2points + - -odm/SuperBuild/src/odm_orthophoto prime: # remove any static-libraries - -**/*.a diff --git a/stages/odm_orthophoto.py b/stages/odm_orthophoto.py index 8dadde89..9cbad637 100644 --- a/stages/odm_orthophoto.py +++ b/stages/odm_orthophoto.py @@ -38,7 +38,7 @@ class ODMOrthoPhotoStage(types.ODM_Stage): # odm_orthophoto definitions kwargs = { - 'bin': context.odm_modules_path, + 'odm_ortho_bin': context.odm_orthophoto_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_render, 'corners': tree.odm_orthophoto_corners, @@ -70,7 +70,7 @@ class ODMOrthoPhotoStage(types.ODM_Stage): kwargs['models'] = ','.join(map(quote, models)) # run odm_orthophoto - system.run('{bin}/odm_orthophoto -inputFiles {models} ' + system.run('{odm_ortho_bin} -inputFiles {models} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners} {bands}'.format(**kwargs))