diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index bfc5bfcee..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ -### Specifications like the version of the project, operating system, and hardware - -### Steps to reproduce the problem diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..7f9c8d273 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Use `Viewer` to verify/display the input/output. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..3ba13e0ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/CMakeLists.txt b/CMakeLists.txt index 7966ed55b..fff705bda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ PROJECT(OpenMVS) set(OpenMVS_MAJOR_VERSION 1) set(OpenMVS_MINOR_VERSION 1) -set(OpenMVS_PATCH_VERSION 0) +set(OpenMVS_PATCH_VERSION 1) set(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) # Find dependencies: diff --git a/MvgMvsPipeline.py b/MvgMvsPipeline.py index f4914bf86..106857f7e 100644 --- a/MvgMvsPipeline.py +++ b/MvgMvsPipeline.py @@ -59,12 +59,16 @@ DEBUG = False -# add current directory to PATH if sys.platform.startswith('win'): - path_delim = ';' + PATH_DELIM = ';' else: - path_delim = ':' -os.environ['PATH'] += path_delim + os.getcwd() + PATH_DELIM = ':' + +# add this script's directory to PATH +os.environ['PATH'] += PATH_DELIM + os.path.dirname(os.path.abspath(__file__)) + +# add current directory to PATH +os.environ['PATH'] += PATH_DELIM + os.getcwd() def whereis(afile): @@ -81,15 +85,17 @@ def whereis(afile): except subprocess.CalledProcessError: return None + def find(afile): """ As whereis look only for executable on linux, this find look for all file type """ - for d in os.environ['PATH'].split(path_delim): + for d in os.environ['PATH'].split(PATH_DELIM): if os.path.isfile(os.path.join(d, afile)): return d return None + # Try to find openMVG and openMVS binaries in PATH OPENMVG_BIN = whereis("openMVG_main_SfMInit_ImageListing") OPENMVS_BIN = whereis("ReconstructMesh") @@ -115,11 +121,11 @@ def find(afile): PRESET_DEFAULT = 'SEQUENTIAL' - # HELPERS for terminal colors BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) NO_EFFECT, BOLD, UNDERLINE, BLINK, INVERSE, HIDDEN = (0, 1, 4, 5, 7, 8) + # from Python cookbook, #475186 def has_colours(stream): ''' @@ -161,6 +167,7 @@ def __init__(self): class AStep: + """ Represents a process step to be run """ def __init__(self, info, cmd, opt): self.info = info self.cmd = cmd @@ -168,6 +175,7 @@ def __init__(self, info, cmd, opt): class StepsStore: + """ List of steps with facilities to configure them """ def __init__(self): self.steps_data = [ ["Intrinsics analysis", # 0 @@ -245,29 +253,30 @@ def apply_conf(self, conf): STEPS = StepsStore() # ARGS -parser = argparse.ArgumentParser( +PARSER = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description="Photogrammetry reconstruction with these steps: \r\n" + "\r\n".join(("\t%i. %s\t %s" % (t, STEPS[t].info, STEPS[t].cmd) for t in range(STEPS.length()))) ) -parser.add_argument('input_dir', +PARSER.add_argument('input_dir', help="the directory wich contains the pictures set.") -parser.add_argument('output_dir', +PARSER.add_argument('output_dir', help="the directory wich will contain the resulting files.") -parser.add_argument('--steps', +PARSER.add_argument('--steps', type=int, nargs="+", help="steps to process") -parser.add_argument('--preset', +PARSER.add_argument('--preset', help="steps list preset in \r\n" + " \r\n".join([k + " = " + str(PRESET[k]) for k in PRESET]) + " \r\ndefault : " + PRESET_DEFAULT) -group = parser.add_argument_group('Passthrough', description="Option to be passed to command lines (remove - in front of option names)\r\ne.g. --1 p ULTRA to use the ULTRA preset in openMVG_main_ComputeFeatures") +GROUP = PARSER.add_argument_group('Passthrough', description="Option to be passed to command lines (remove - in front of option names)\r\ne.g. --1 p ULTRA to use the ULTRA preset in openMVG_main_ComputeFeatures") for n in range(STEPS.length()): - group.add_argument('--'+str(n), nargs='+') + GROUP.add_argument('--'+str(n), nargs='+') + +PARSER.parse_args(namespace=CONF) # store args in the ConfContainer -parser.parse_args(namespace=CONF) # store args in the ConfContainer # FOLDERS @@ -276,6 +285,7 @@ def mkdir_ine(dirname): if not os.path.exists(dirname): os.mkdir(dirname) + # Absolute path for input and ouput dirs CONF.input_dir = os.path.abspath(CONF.input_dir) CONF.output_dir = os.path.abspath(CONF.output_dir) diff --git a/apps/DensifyPointCloud/DensifyPointCloud.cpp b/apps/DensifyPointCloud/DensifyPointCloud.cpp index 9d225289c..d82c07294 100644 --- a/apps/DensifyPointCloud/DensifyPointCloud.cpp +++ b/apps/DensifyPointCloud/DensifyPointCloud.cpp @@ -91,7 +91,6 @@ bool Initialize(size_t argc, LPCTSTR* argv) unsigned nMinResolution; unsigned nNumViews; unsigned nMinViewsFuse; - unsigned nOptimize; unsigned nEstimateColors; unsigned nEstimateNormals; boost::program_options::options_description config("Densify options"); @@ -103,9 +102,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("min-resolution", boost::program_options::value(&nMinResolution)->default_value(640), "do not scale images lower than this resolution") ("number-views", boost::program_options::value(&nNumViews)->default_value(5), "number of views used for depth-map estimation (0 - all neighbor views available)") ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier") - ("optimize", boost::program_options::value(&nOptimize)->default_value(7), "filter used after depth-map estimation (0 - disabled, 1 - remove speckles, 2 - fill gaps, 4 - cross-adjust)") - ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(2), "estimate the colors for the dense point-cloud") - ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(0), "estimate the normals for the dense point-cloud") + ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(2), "estimate the colors for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") + ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(2), "estimate the normals for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(0.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") ("filter-point-cloud", boost::program_options::value(&OPT::thFilterPointCloud)->default_value(0), "filter dense point-cloud based on visibility (0 - disabled)") ("fusion-mode", boost::program_options::value(&OPT::nFusionMode)->default_value(0), "depth map fusion mode (-2 - fuse disparity-maps, -1 - export disparity-maps only, 0 - depth-maps & fusion, 1 - export depth-maps only)") @@ -179,7 +177,6 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPTDENSE::nMinResolution = nMinResolution; OPTDENSE::nNumViews = nNumViews; OPTDENSE::nMinViewsFuse = nMinViewsFuse; - OPTDENSE::nOptimize = nOptimize; OPTDENSE::nEstimateColors = nEstimateColors; OPTDENSE::nEstimateNormals = nEstimateNormals; if (!bValidConfig && !OPT::strDenseConfigFileName.IsEmpty()) diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index d450932df..dbe796ca6 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -34,6 +34,7 @@ #define _USE_OPENCV #include "../../libs/MVS/Interface.h" #include +#include "endian.h" using namespace MVS; @@ -44,9 +45,12 @@ using namespace MVS; #define MVS_EXT _T(".mvs") #define COLMAP_IMAGES_FOLDER _T("images/") #define COLMAP_SPARSE_FOLDER _T("sparse/") -#define COLMAP_CAMERAS COLMAP_SPARSE_FOLDER _T("cameras.txt") -#define COLMAP_IMAGES COLMAP_SPARSE_FOLDER _T("images.txt") -#define COLMAP_POINTS COLMAP_SPARSE_FOLDER _T("points3D.txt") +#define COLMAP_CAMERAS_TXT COLMAP_SPARSE_FOLDER _T("cameras.txt") +#define COLMAP_IMAGES_TXT COLMAP_SPARSE_FOLDER _T("images.txt") +#define COLMAP_POINTS_TXT COLMAP_SPARSE_FOLDER _T("points3D.txt") +#define COLMAP_CAMERAS_BIN COLMAP_SPARSE_FOLDER _T("cameras.bin") +#define COLMAP_IMAGES_BIN COLMAP_SPARSE_FOLDER _T("images.bin") +#define COLMAP_POINTS_BIN COLMAP_SPARSE_FOLDER _T("points3D.bin") #define COLMAP_DENSE_POINTS _T("fused.ply") #define COLMAP_DENSE_POINTS_VISIBILITY _T("fused.ply.vis") #define COLMAP_STEREO_FOLDER _T("stereo/") @@ -150,10 +154,9 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << _T("\n" - "Import/export 3D reconstruction from/to COLMAP format as TXT (the only documented format).\n" + "Import/export 3D reconstruction from COLMAP (TXT/BIN format) and to COLMAP (TXT format). \n" "In order to import a scene, run COLMAP SfM and next undistort the images (only PINHOLE\n" - "camera model supported for the moment); and convert the BIN scene to TXT by importing in\n" - "COLMAP the sparse scene stored in 'dense' folder and exporting it as TXT.\n" + "camera model supported for the moment)." "\n") << visible; } @@ -204,6 +207,33 @@ void Finalize() } namespace COLMAP { + +using namespace colmap; + +// See colmap/src/util/types.h +typedef uint32_t camera_t; +typedef uint32_t image_t; +typedef uint64_t image_pair_t; +typedef uint32_t point2D_t; +typedef uint64_t point3D_t; + +typedef std::unordered_map CameraModelMap; +CameraModelMap mapCameraModel; + +void DefineCameraModels() { + COLMAP::mapCameraModel.emplace(0, "SIMPLE_PINHOLE"); + COLMAP::mapCameraModel.emplace(1, "PINHOLE"); + COLMAP::mapCameraModel.emplace(2, "SIMPLE_RADIAL"); + COLMAP::mapCameraModel.emplace(3, "RADIAL"); + COLMAP::mapCameraModel.emplace(4, "OPENCV"); + COLMAP::mapCameraModel.emplace(5, "OPENCV_FISHEYE"); + COLMAP::mapCameraModel.emplace(6, "FULL_OPENCV"); + COLMAP::mapCameraModel.emplace(7, "FOV"); + COLMAP::mapCameraModel.emplace(8, "SIMPLE_RADIAL_FISHEYE"); + COLMAP::mapCameraModel.emplace(9, "RADIAL_FISHEYE"); + COLMAP::mapCameraModel.emplace(10, "THIN_PRISM_FISHEYE"); +} + // tools bool NextLine(std::istream& stream, std::istringstream& in, bool bIgnoreEmpty=true) { String line; @@ -223,6 +253,7 @@ struct Camera { String model; // camera model name uint32_t width, height; // camera resolution std::vector params; // camera parameters + bool parsedNumCameras = false; Camera() {} Camera(uint32_t _ID) : ID(_ID) {} @@ -247,9 +278,17 @@ struct Camera { } }; + bool Read(std::istream& stream, bool binary) { + if (binary) { + return ReadBIN(stream); + } else { + return ReadTXT(stream); + } + } + // Camera list with one line of data per camera: // CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[] - bool Read(std::istream& stream) { + bool ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; @@ -262,6 +301,31 @@ struct Camera { in >> params[0] >> params[1] >> params[2] >> params[3]; return !in.fail(); } + + // See: colmap/src/base/reconstruction.cc + // void Reconstruction::ReadCamerasBinary(const std::string& path) + bool ReadBIN(std::istream& stream) { + + if (stream.peek() == EOF) + return false; + + if (!parsedNumCameras) { + // Read the first entry in the binary file + ReadBinaryLittleEndian(&stream); + parsedNumCameras = true; + } + + ID = ReadBinaryLittleEndian(&stream); + model = mapCameraModel.at(ReadBinaryLittleEndian(&stream)); + width = (uint32_t)ReadBinaryLittleEndian(&stream); + height = (uint32_t)ReadBinaryLittleEndian(&stream); + if (model != _T("PINHOLE")) + return false; + params.resize(4); + ReadBinaryLittleEndian(&stream, ¶ms); + return true; + } + bool Write(std::ostream& out) const { out << ID+1 << _T(" ") << model << _T(" ") << width << _T(" ") << height; if (out.fail()) @@ -288,15 +352,24 @@ struct Image { uint32_t idCamera; // ID of the associated camera String name; // image file name std::vector projs; // known image projections + bool parsedNumRegImages = false; Image() {} Image(uint32_t _ID) : ID(_ID) {} bool operator < (const Image& rhs) const { return ID < rhs.ID; } + bool Read(std::istream& stream, bool binary) { + if (binary) { + return ReadBIN(stream); + } else { + return ReadTXT(stream); + } + } + // Image list with two lines of data per image: // IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME // POINTS2D[] as (X, Y, POINT3D_ID) - bool Read(std::istream& stream) { + bool ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; @@ -319,6 +392,52 @@ struct Image { } return true; } + + // See: colmap/src/base/reconstruction.cc + // void Reconstruction::ReadImagesBinary(const std::string& path) + bool ReadBIN(std::istream& stream) { + + if (stream.peek() == EOF) + return false; + + if (!parsedNumRegImages) { + // Read the first entry in the binary file + ReadBinaryLittleEndian(&stream); + parsedNumRegImages = true; + } + + ID = ReadBinaryLittleEndian(&stream); + q.w() = ReadBinaryLittleEndian(&stream); + q.x() = ReadBinaryLittleEndian(&stream); + q.y() = ReadBinaryLittleEndian(&stream); + q.z() = ReadBinaryLittleEndian(&stream); + t(0) = ReadBinaryLittleEndian(&stream); + t(1) = ReadBinaryLittleEndian(&stream); + t(2) = ReadBinaryLittleEndian(&stream); + idCamera = ReadBinaryLittleEndian(&stream); + + name = ""; + char nameChar; + do { + stream.read(&nameChar, 1); + if (nameChar != '\0') { + name += nameChar; + } + } while (nameChar != '\0'); + Util::ensureValidPath(name); + + const size_t numPoints2D = ReadBinaryLittleEndian(&stream); + projs.clear(); + for (size_t j = 0; j < numPoints2D; ++j) { + Proj proj; + proj.p(0) = (float)ReadBinaryLittleEndian(&stream); + proj.p(1) = (float)ReadBinaryLittleEndian(&stream); + proj.idPoint = (uint32_t)ReadBinaryLittleEndian(&stream); + projs.push_back(proj); + } + return true; + } + bool Write(std::ostream& out) const { out << ID+1 << _T(" ") << q.w() << _T(" ") << q.x() << _T(" ") << q.y() << _T(" ") << q.z() << _T(" ") @@ -346,14 +465,23 @@ struct Point { Interface::Col3 c; // BGR color float e; // error std::vector tracks; // point track + bool parsedNumPoints3D = false; Point() {} Point(uint32_t _ID) : ID(_ID) {} bool operator < (const Image& rhs) const { return ID < rhs.ID; } + bool Read(std::istream& stream, bool binary) { + if (binary) { + return ReadBIN(stream); + } else { + return ReadTXT(stream); + } + } + // 3D point list with one line of data per point: // POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX) - bool Read(std::istream& stream) { + bool ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; @@ -377,6 +505,44 @@ struct Point { } return !tracks.empty(); } + + // See: colmap/src/base/reconstruction.cc + // void Reconstruction::ReadPoints3DBinary(const std::string& path) + bool ReadBIN(std::istream& stream) { + + if (stream.peek() == EOF) + return false; + + if (!parsedNumPoints3D) { + // Read the first entry in the binary file + ReadBinaryLittleEndian(&stream); + parsedNumPoints3D = true; + } + + int r,g,b; + ID = (uint32_t)ReadBinaryLittleEndian(&stream); + p.x = (float)ReadBinaryLittleEndian(&stream); + p.y = (float)ReadBinaryLittleEndian(&stream); + p.z = (float)ReadBinaryLittleEndian(&stream); + r = ReadBinaryLittleEndian(&stream); + g = ReadBinaryLittleEndian(&stream); + b = ReadBinaryLittleEndian(&stream); + e = (float)ReadBinaryLittleEndian(&stream); + c.x = CLAMP(b,0,255); + c.y = CLAMP(g,0,255); + c.z = CLAMP(r,0,255); + + const size_t trackLength = ReadBinaryLittleEndian(&stream); + tracks.clear(); + for (size_t j = 0; j < trackLength; ++j) { + Track track; + track.idImage = ReadBinaryLittleEndian(&stream); + track.idProj = ReadBinaryLittleEndian(&stream); + tracks.push_back(track); + } + return !tracks.empty(); + } + bool Write(std::ostream& out) const { ASSERT(!tracks.empty()); const int r(c.z),g(c.y),b(c.x); @@ -399,23 +565,48 @@ typedef std::vector Points; typedef Eigen::Matrix EMat33d; typedef Eigen::Matrix EVec3d; + +bool DetermineInputSource(const String& filenameTXT, const String& filenameBIN, std::ifstream& file, String& filenameCamera, bool& binary) +{ + file.open(filenameTXT); + if (file.good()) { + filenameCamera = filenameTXT; + binary = false; + return true; + } + file.open(filenameBIN, std::ios::binary); + if (file.good()) { + filenameCamera = filenameBIN; + binary = true; + return true; + } + VERBOSE("error: unable to open file '%s'", filenameTXT.c_str()); + VERBOSE("error: unable to open file '%s'", filenameBIN.c_str()); + return false; +} + + bool ImportScene(const String& strFolder, Interface& scene) { + COLMAP::DefineCameraModels(); // read camera list typedef std::unordered_map CamerasMap; CamerasMap mapCameras; { - const String filenameCameras(strFolder+COLMAP_CAMERAS); - LOG_OUT() << "Reading cameras: " << filenameCameras << std::endl; - std::ifstream file(filenameCameras); - if (!file.good()) { - VERBOSE("error: unable to open file '%s'", filenameCameras.c_str()); + const String filenameCamerasTXT(strFolder+COLMAP_CAMERAS_TXT); + const String filenameCamerasBIN(strFolder+COLMAP_CAMERAS_BIN); + std::ifstream file; + bool binary; + String filenameCamera; + if (!DetermineInputSource(filenameCamerasTXT, filenameCamerasBIN, file, filenameCamera, binary)) { return false; } + LOG_OUT() << "Reading cameras: " << filenameCamera << std::endl; + typedef std::unordered_map CamerasSet; CamerasSet setCameras; COLMAP::Camera colmapCamera; - while (file.good() && colmapCamera.Read(file)) { + while (file.good() && colmapCamera.Read(file, binary)) { const auto setIt(setCameras.emplace(colmapCamera, (uint32_t)scene.platforms.size())); mapCameras.emplace(colmapCamera.ID, setIt.first->second); if (!setIt.second) { @@ -458,15 +649,18 @@ bool ImportScene(const String& strFolder, Interface& scene) typedef std::map ImagesMap; ImagesMap mapImages; { - const String filenameImages(strFolder+COLMAP_IMAGES); - LOG_OUT() << "Reading images: " << filenameImages << std::endl; - std::ifstream file(filenameImages); - if (!file.good()) { - VERBOSE("error: unable to open file '%s'", filenameImages.c_str()); + const String filenameImagesTXT(strFolder+COLMAP_IMAGES_TXT); + const String filenameImagesBIN(strFolder+COLMAP_IMAGES_BIN); + std::ifstream file; + bool binary; + String filenameImages; + if (!DetermineInputSource(filenameImagesTXT, filenameImagesBIN, file, filenameImages, binary)) { return false; } + LOG_OUT() << "Reading images: " << filenameImages << std::endl; + COLMAP::Image imageColmap; - while (file.good() && imageColmap.Read(file)) { + while (file.good() && imageColmap.Read(file, binary)) { mapImages.emplace(imageColmap, (uint32_t)scene.images.size()); Interface::Platform::Pose pose; Eigen::Map(pose.R.val) = imageColmap.q.toRotationMatrix(); @@ -489,15 +683,18 @@ bool ImportScene(const String& strFolder, Interface& scene) const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); if (!File::access(filenameDensePoints) || !File::access(filenameDenseVisPoints)) { // parse sparse point-cloud - const String filenamePoints(strFolder+COLMAP_POINTS); - LOG_OUT() << "Reading points: " << filenamePoints << std::endl; - std::ifstream file(filenamePoints); - if (!file.good()) { - VERBOSE("error: unable to open file '%s'", filenamePoints.c_str()); + const String filenamePointsTXT(strFolder+COLMAP_POINTS_TXT); + const String filenamePointsBIN(strFolder+COLMAP_POINTS_BIN); + std::ifstream file; + bool binary; + String filenamePoints; + if (!DetermineInputSource(filenamePointsTXT, filenamePointsBIN, file, filenamePoints, binary)) { return false; } + LOG_OUT() << "Reading images: " << filenamePoints << std::endl; + COLMAP::Point point; - while (file.good() && point.Read(file)) { + while (file.good() && point.Read(file, binary)) { Interface::Vertex vertex; vertex.X = point.p; for (const COLMAP::Point::Track& track: point.tracks) { @@ -560,7 +757,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) CLISTDEF0IDX(KMatrix,uint32_t) Ks; CLISTDEF0IDX(COLMAP::Camera,uint32_t) cams; { - const String filenameCameras(strFolder+COLMAP_CAMERAS); + const String filenameCameras(strFolder+COLMAP_CAMERAS_TXT); LOG_OUT() << "Writing cameras: " << filenameCameras << std::endl; std::ofstream file(filenameCameras); if (!file.good()) { @@ -662,7 +859,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) if (bSparsePointCloud) { // write points list { - const String filenamePoints(strFolder+COLMAP_POINTS); + const String filenamePoints(strFolder+COLMAP_POINTS_TXT); LOG_OUT() << "Writing points: " << filenamePoints << std::endl; std::ofstream file(filenamePoints); if (!file.good()) { @@ -774,7 +971,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) // write images list { - const String filenameImages(strFolder+COLMAP_IMAGES); + const String filenameImages(strFolder+COLMAP_IMAGES_TXT); LOG_OUT() << "Writing images: " << filenameImages << std::endl; std::ofstream file(filenameImages); if (!file.good()) { diff --git a/apps/InterfaceCOLMAP/endian.h b/apps/InterfaceCOLMAP/endian.h new file mode 100644 index 000000000..12a22cab6 --- /dev/null +++ b/apps/InterfaceCOLMAP/endian.h @@ -0,0 +1,167 @@ +// Copyright (c) 2018, ETH Zurich and UNC Chapel Hill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de) + +#ifndef COLMAP_SRC_UTIL_ENDIAN_H_ +#define COLMAP_SRC_UTIL_ENDIAN_H_ + +#include +#include +#include + +namespace colmap { + +// Reverse the order of each byte. +template +T ReverseBytes(const T& data); + +// Check the order in which bytes are stored in computer memory. +bool IsLittleEndian(); +bool IsBigEndian(); + +// Convert data between endianness and the native format. Note that, for float +// and double types, these functions are only valid if the format is IEEE-754. +// This is the case for pretty much most processors. +template +T LittleEndianToNative(const T x); +template +T BigEndianToNative(const T x); +template +T NativeToLittleEndian(const T x); +template +T NativeToBigEndian(const T x); + +// Read data in little endian format for cross-platform support. +template +T ReadBinaryLittleEndian(std::istream* stream); +template +void ReadBinaryLittleEndian(std::istream* stream, std::vector* data); + +// Write data in little endian format for cross-platform support. +template +void WriteBinaryLittleEndian(std::ostream* stream, const T& data); +template +void WriteBinaryLittleEndian(std::ostream* stream, const std::vector& data); + +//////////////////////////////////////////////////////////////////////////////// +// Implementation +//////////////////////////////////////////////////////////////////////////////// + +template +T ReverseBytes(const T& data) { + T data_reversed = data; + std::reverse(reinterpret_cast(&data_reversed), + reinterpret_cast(&data_reversed) + sizeof(T)); + return data_reversed; +} + +inline bool IsLittleEndian() { +#ifdef BOOST_BIG_ENDIAN + return false; +#else + return true; +#endif +} + +inline bool IsBigEndian() { +#ifdef BOOST_BIG_ENDIAN + return true; +#else + return false; +#endif +} + +template +T LittleEndianToNative(const T x) { + if (IsLittleEndian()) { + return x; + } else { + return ReverseBytes(x); + } +} + +template +T BigEndianToNative(const T x) { + if (IsBigEndian()) { + return x; + } else { + return ReverseBytes(x); + } +} + +template +T NativeToLittleEndian(const T x) { + if (IsLittleEndian()) { + return x; + } else { + return ReverseBytes(x); + } +} + +template +T NativeToBigEndian(const T x) { + if (IsBigEndian()) { + return x; + } else { + return ReverseBytes(x); + } +} + +template +T ReadBinaryLittleEndian(std::istream* stream) { + T data_little_endian; + stream->read(reinterpret_cast(&data_little_endian), sizeof(T)); + return LittleEndianToNative(data_little_endian); +} + +template +void ReadBinaryLittleEndian(std::istream* stream, std::vector* data) { + for (size_t i = 0; i < data->size(); ++i) { + (*data)[i] = ReadBinaryLittleEndian(stream); + } +} + +template +void WriteBinaryLittleEndian(std::ostream* stream, const T& data) { + const T data_little_endian = NativeToLittleEndian(data); + stream->write(reinterpret_cast(&data_little_endian), sizeof(T)); +} + +template +void WriteBinaryLittleEndian(std::ostream* stream, const std::vector& data) { + for (const auto& elem : data) { + WriteBinaryLittleEndian(stream, elem); + } +} + +} // namespace colmap + +#endif // COLMAP_SRC_UTIL_ENDIAN_H_ + diff --git a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp index 6230be3ce..c686abf8b 100644 --- a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp +++ b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp @@ -42,6 +42,7 @@ #define APPNAME _T("InterfaceVisualSFM") #define MVS_EXT _T(".mvs") #define VSFM_EXT _T(".nvm") +#define BUNDLE_EXT _T(".out") #define CMPMVS_EXT _T(".lst") @@ -88,7 +89,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) // group of options allowed both on command line and in config file boost::program_options::options_description config("Main options"); config.add_options() - ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list (NVM, undistorted OUT + image_list.TXT, LST)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") ; @@ -508,7 +509,7 @@ int main(int argc, LPCTSTR* argv) return EXIT_FAILURE; const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - if (strInputFileNameExt == VSFM_EXT) { + if (strInputFileNameExt == VSFM_EXT || strInputFileNameExt == BUNDLE_EXT) { if (!ImportSceneVSFM()) return EXIT_FAILURE; } else diff --git a/apps/InterfaceVisualSFM/Util.h b/apps/InterfaceVisualSFM/Util.h index e5e8c43ff..3afd61f1c 100644 --- a/apps/InterfaceVisualSFM/Util.h +++ b/apps/InterfaceVisualSFM/Util.h @@ -198,7 +198,13 @@ bool LoadBundlerOut(const char* name, std::ifstream& in, std::vector& c std::ifstream listin(listpath); if(!listin.is_open()) { - listin.close(); listin.clear(); + listin.close(); listin.clear(); + strcpy(ext, ".txt\0"); + listin.open(listpath); + } + if(!listin.is_open()) + { + listin.close(); listin.clear(); char * slash = strrchr(listpath, '/'); if(slash == NULL) slash = strrchr(listpath, '\\'); slash = slash ? slash + 1 : listpath; @@ -231,9 +237,7 @@ bool LoadBundlerOut(const char* name, std::ifstream& in, std::vector& c if(listin >> filepath && f != 0) { - char* slash = strrchr(filepath , '/'); - if(slash == NULL) slash = strchr(filepath, '\\'); - names[i] = (slash? (slash + 1) : filepath); + names[i] = filepath; std::getline(listin, token); if(!det_checked) diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index 8e63dbf9a..b40cb1bf2 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -334,7 +334,7 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) // init scene AABB3d bounds(true); if (!scene.pointcloud.IsEmpty()) { - bounds = scene.pointcloud.GetAABB(3); + bounds = scene.pointcloud.GetAABB(MINF(3u,scene.nCalibratedImages)); if (bounds.IsEmpty()) bounds = scene.pointcloud.GetAABB(); } @@ -378,7 +378,7 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) window.SetCamera(CameraPtr(new Camera(bounds))); window.camera->maxCamID = images.size(); window.SetName(String::FormatString((name + _T(": %s")).c_str(), Util::getFileName(fileName).c_str())); - window.Reset(); + window.Reset(MINF(2u, images.size())); return true; } diff --git a/apps/Viewer/Window.cpp b/apps/Viewer/Window.cpp index 6a9ada4f1..4cb3eeef4 100644 --- a/apps/Viewer/Window.cpp +++ b/apps/Viewer/Window.cpp @@ -102,12 +102,12 @@ void Window::SetVisible(bool v) else glfwHideWindow(window); } -void Window::Reset() +void Window::Reset(uint32_t _minViews) { if (camera) camera->Reset(); sparseType = SPR_ALL; - minViews = 2; + minViews = _minViews; pointSize = 2.f; cameraBlend = 0.5f; bRenderCameras = true; @@ -266,7 +266,7 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) else if (mod & GLFW_MOD_SHIFT) camera->scaleF *= 1.11f; else - cameraBlend += MAXF(cameraBlend-0.1f, 0.f); + cameraBlend = MINF(cameraBlend+0.1f, 1.f); } break; } diff --git a/apps/Viewer/Window.h b/apps/Viewer/Window.h index 87f62eb76..0d9fe5034 100644 --- a/apps/Viewer/Window.h +++ b/apps/Viewer/Window.h @@ -100,7 +100,7 @@ class Window void SetCamera(CameraPtr); void SetName(LPCTSTR); void SetVisible(bool); - void Reset(); + void Reset(uint32_t minViews=2); inline GLFWwindow* GetWindow() { return window; } diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index a8b41558a..3b6adb25c 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -5,4 +5,4 @@ ADD_SUBDIRECTORY(IO) ADD_SUBDIRECTORY(MVS) # Install -INSTALL(FILES "MVS.h" DESTINATION "include/${PROJECT_NAME}") +INSTALL(FILES "MVS.h" DESTINATION "${INSTALL_INCLUDE_DIR}") diff --git a/libs/Common/Types.h b/libs/Common/Types.h index bcac65c1b..631f024ce 100644 --- a/libs/Common/Types.h +++ b/libs/Common/Types.h @@ -1896,13 +1896,13 @@ struct TPixel { : r(TYPE((col>>16)&0xFF)), g(TYPE((col>>8)&0xFF)), b(TYPE(col&0xFF)) {} #endif // set/get from default type - inline void set(TYPE _r, TYPE _g, TYPE _b) { r = _r; g = _g; b = _b; } - inline void set(const TYPE* clr) { c[0] = clr[0]; c[1] = clr[1]; c[2] = clr[2]; } + inline TPixel& set(TYPE _r, TYPE _g, TYPE _b) { r = _r; g = _g; b = _b; return *this; } + inline TPixel& set(const TYPE* clr) { c[0] = clr[0]; c[1] = clr[1]; c[2] = clr[2]; return *this; } inline void get(TYPE& _r, TYPE& _g, TYPE& _b) const { _r = r; _g = g; _b = b; } inline void get(TYPE* clr) const { clr[0] = c[0]; clr[1] = c[1]; clr[2] = c[2]; } // set/get from alternative type - inline void set(ALT _r, ALT _g, ALT _b) { r = TYPE(_r); g = TYPE(_g); b = TYPE(_b); } - inline void set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); } + inline TPixel& set(ALT _r, ALT _g, ALT _b) { r = TYPE(_r); g = TYPE(_g); b = TYPE(_b); return *this; } + inline TPixel& set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); return *this; } inline void get(ALT& _r, ALT& _g, ALT& _b) const { _r = ALT(r); _g = ALT(g); _b = ALT(b); } inline void get(ALT* clr) const { clr[0] = ALT(c[0]); clr[1] = ALT(c[1]); clr[2] = ALT(c[2]); } template inline TPixel::value || !std::is_same::value,T>::type> cast() const { return TPixel(T(r), T(g), T(b)); } @@ -1952,9 +1952,9 @@ struct TPixel { } #endif }; -template <> inline void TPixel::set(uint8_t _r, uint8_t _g, uint8_t _b) { r = float(_r)/255; g = float(_g)/255; b = float(_b)/255; } -template <> inline void TPixel::get(uint8_t& _r, uint8_t& _g, uint8_t& _b) const { _r = uint8_t(r*255); _g = uint8_t(g*255); _b = uint8_t(b*255); } -template <> inline void TPixel::set(float _r, float _g, float _b) { r = uint8_t(_r*255); g = uint8_t(_g*255); b = uint8_t(_b*255); } +template <> inline TPixel& TPixel::set(uint8_t _r, uint8_t _g, uint8_t _b) { r = float(_r)/255; g = float(_g)/255; b = float(_b)/255; return *this; } +template <> inline void TPixel::get(uint8_t& _r, uint8_t& _g, uint8_t& _b) const { _r = (uint8_t)CLAMP(ROUND2INT(r*255), 0, 255); _g = (uint8_t)CLAMP(ROUND2INT(g*255), 0, 255); _b = (uint8_t)CLAMP(ROUND2INT(b*255), 0, 255); } +template <> inline TPixel& TPixel::set(float _r, float _g, float _b) { r = (uint8_t)CLAMP(ROUND2INT(_r*255), 0, 255); g = (uint8_t)CLAMP(ROUND2INT(_g*255), 0, 255); b = (uint8_t)CLAMP(ROUND2INT(_b*255), 0, 255); return *this; } template <> inline void TPixel::get(float& _r, float& _g, float& _b) const { _r = float(r)/255; _g = float(g)/255; _b = float(b)/255; } /*----------------------------------------------------------------*/ typedef TPixel Pixel8U; @@ -2019,13 +2019,13 @@ struct TColor { explicit inline TColor(uint32_t col) : r(TYPE((col>>16)&0xFF)), g(TYPE((col>>8)&0xFF)), b(TYPE(col&0xFF)), a(TYPE((col>>24)&0xFF)) {} // set/get from default type - inline void set(TYPE _r, TYPE _g, TYPE _b, TYPE _a=ColorType::ONE) { r = _r; g = _g; b = _b; a = _a; } - inline void set(const TYPE* clr) { c[0] = clr[0]; c[1] = clr[1]; c[2] = clr[2]; c[3] = clr[3]; } + inline TColor& set(TYPE _r, TYPE _g, TYPE _b, TYPE _a=ColorType::ONE) { r = _r; g = _g; b = _b; a = _a; return *this; } + inline TColor& set(const TYPE* clr) { c[0] = clr[0]; c[1] = clr[1]; c[2] = clr[2]; c[3] = clr[3]; return *this; } inline void get(TYPE& _r, TYPE& _g, TYPE& _b, TYPE& _a) const { _r = r; _g = g; _b = b; _a = a; } inline void get(TYPE* clr) const { clr[0] = c[0]; clr[1] = c[1]; clr[2] = c[2]; clr[3] = c[3]; } // set/get from alternative type - inline void set(ALT _r, ALT _g, ALT _b, ALT _a=ColorType::ALTONE) { r = TYPE(_r); g = TYPE(_g); b = TYPE(_b); a = TYPE(_a); } - inline void set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); c[3] = TYPE(clr[3]); } + inline TColor& set(ALT _r, ALT _g, ALT _b, ALT _a=ColorType::ALTONE) { r = TYPE(_r); g = TYPE(_g); b = TYPE(_b); a = TYPE(_a); return *this; } + inline TColor& set(const ALT* clr) { c[0] = TYPE(clr[0]); c[1] = TYPE(clr[1]); c[2] = TYPE(clr[2]); c[3] = TYPE(clr[3]); return *this; } inline void get(ALT& _r, ALT& _g, ALT& _b, ALT& _a) const { _r = ALT(r); _g = ALT(g); _b = ALT(b); _a = ALT(a); } inline void get(ALT* clr) const { clr[0] = ALT(c[0]); clr[1] = ALT(c[1]); clr[2] = ALT(c[2]); clr[3] = ALT(c[3]); } template inline TColor::value || !std::is_same::value,T>::type> cast() const { return TColor(T(r), T(g), T(b), T(a)); } @@ -2073,9 +2073,9 @@ struct TColor { } #endif }; -template <> inline void TColor::set(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) { r = float(_r)/255; g = float(_g)/255; b = float(_b)/255; a = float(_a)/255; } +template <> inline TColor& TColor::set(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) { r = float(_r)/255; g = float(_g)/255; b = float(_b)/255; a = float(_a)/255; return *this; } template <> inline void TColor::get(uint8_t& _r, uint8_t& _g, uint8_t& _b, uint8_t& _a) const { _r = uint8_t(r*255); _g = uint8_t(g*255); _b = uint8_t(b*255); _a = uint8_t(a*255); } -template <> inline void TColor::set(float _r, float _g, float _b, float _a) { r = uint8_t(_r*255); g = uint8_t(_g*255); b = uint8_t(_b*255); a = uint8_t(_a*255); } +template <> inline TColor& TColor::set(float _r, float _g, float _b, float _a) { r = uint8_t(_r*255); g = uint8_t(_g*255); b = uint8_t(_b*255); a = uint8_t(_a*255); return *this; } template <> inline void TColor::get(float& _r, float& _g, float& _b, float& _a) const { _r = float(r)/255; _g = float(g)/255; _b = float(b)/255; _a = float(a)/255; } template <> inline bool TColor::operator==(const TColor& col) const { return (*((const uint32_t*)c) == *((const uint32_t*)col.c)); } template <> inline bool TColor::operator!=(const TColor& col) const { return (*((const uint32_t*)c) != *((const uint32_t*)col.c)); } diff --git a/libs/Common/Types.inl b/libs/Common/Types.inl index 090f896dc..cbdf89fe1 100644 --- a/libs/Common/Types.inl +++ b/libs/Common/Types.inl @@ -2216,11 +2216,11 @@ TPixel TPixel::gray2color(ALT gray) return ALT(0); } }; - return TPixel( + return TPixel().set( Base(gray + ALT(0.25)), Base(gray), Base(gray - ALT(0.25)) - ).template cast(); + ); } /*----------------------------------------------------------------*/ diff --git a/libs/MVS/Camera.h b/libs/MVS/Camera.h index ee22d8a80..fabae38d4 100644 --- a/libs/MVS/Camera.h +++ b/libs/MVS/Camera.h @@ -107,6 +107,25 @@ class MVS_API CameraIntern return float(MAXF(width, height)); } + // return scaled K (assuming standard K format) + template + static inline TMatrix ScaleK(const TMatrix& K, TYPE s) { + #if 0 + TMatrix S(TMatrix::IDENTITY); + S(0,0) = S(1,1) = s; + return S*K; + #else + return TMatrix( + K(0,0)*s, K(0,1)*s, K(0,2)*s, + TYPE(0), K(1,1)*s, K(1,2)*s, + TYPE(0), TYPE(0), TYPE(1) + ); + #endif + } + inline KMatrix GetScaledK(REAL s) const { + return ScaleK(K, s); + } + // return K.inv() (assuming standard K format) inline KMatrix GetInvK() const { #if 0 diff --git a/libs/MVS/DepthMap.cpp b/libs/MVS/DepthMap.cpp index 0b3e6008d..f4068a292 100644 --- a/libs/MVS/DepthMap.cpp +++ b/libs/MVS/DepthMap.cpp @@ -31,6 +31,8 @@ #include "Common.h" #include "DepthMap.h" +#define _USE_OPENCV +#include "Interface.h" #include "../Common/AutoEstimator.h" // CGAL: depth-map initialization #include @@ -449,7 +451,7 @@ float DepthEstimator::ScorePixelImage(const ViewData& image1, Depth depth, const const float num(texels0.dot(texels1)); #endif const float ncc(CLAMP(num/SQRT(nrmSq), -1.f, 1.f)); - float score = 1.f - ncc; + float score(1.f-ncc); #if DENSE_SMOOTHNESS != DENSE_SMOOTHNESS_NA // encourage smoothness for (const NeighborEstimate& neighbor: neighborsClose) { @@ -500,17 +502,17 @@ float DepthEstimator::ScorePixel(Depth depth, const Normal& normal) #if 0 return std::accumulate(scores.cbegin(), &scores.GetNth(idxScore), 0.f) / idxScore; #elif 1 - const float* pescore(&scores.PartialSort(idxScore)); + const float* pescore(&scores.GetNth(idxScore)); const float* pscore(scores.cbegin()); - int n(0); float score(0); + int n(1); float score(*pscore); do { - const float s(*pscore); - if (s < thRobust) { - score += s; - ++n; - } - } while (++pscore <= pescore); - return n ? score/n : thRobust; + const float s(*(++pscore)); + if (s >= thRobust) + break; + score += s; + ++n; + } while (pscore < pescore); + return score/n; #else const float thScore(MAXF(*std::min_element(scores.cbegin(), scores.cend()), 0.05f)*2); const float* pscore(scores.cbegin()); @@ -658,7 +660,7 @@ void DepthEstimator::ProcessPixel(IDX idx) float& conf = confMap0(x0); Depth& depth = depthMap0(x0); Normal& normal = normalMap0(x0); - const Normal viewDir(Cast(static_cast(X0))); + const Normal viewDir(Cast(reinterpret_cast(X0))); ASSERT(depth > 0 && normal.dot(viewDir) <= 0); #if DENSE_REFINE == DENSE_REFINE_ITER // check if any of the neighbor estimates are better then the current estimate @@ -1485,36 +1487,40 @@ bool MVS::LoadConfidenceMap(const String& fileName, ConfidenceMap& confMap) // export depth map as an image (dark - far depth, light - close depth) -bool MVS::ExportDepthMap(const String& fileName, const DepthMap& depthMap, Depth minDepth, Depth maxDepth) +Image8U3 MVS::DepthMap2Image(const DepthMap& depthMap, Depth minDepth, Depth maxDepth) { + ASSERT(!depthMap.empty()); // find min and max values if (minDepth == FLT_MAX && maxDepth == 0) { - cList depths(0, depthMap.area()); + cList depths(0, depthMap.area()); for (int i=depthMap.area(); --i >= 0; ) { const Depth depth = depthMap[i]; ASSERT(depth == 0 || depth > 0); if (depth > 0) depths.Insert(depth); } - if (!depths.IsEmpty()) { - const std::pair th(ComputeX84Threshold(depths.Begin(), depths.GetSize())); - maxDepth = th.first+th.second; - minDepth = th.first-th.second; + if (!depths.empty()) { + const std::pair th(ComputeX84Threshold(depths.data(), depths.size())); + const std::pair mm(depths.GetMinMax()); + maxDepth = MINF(th.first+th.second, mm.second); + minDepth = MAXF(th.first-th.second, mm.first); } - if (minDepth < 0.1f) - minDepth = 0.1f; - if (maxDepth < 0.1f) - maxDepth = 30.f; DEBUG_ULTIMATE("\tdepth range: [%g, %g]", minDepth, maxDepth); } - const Depth deltaDepth = maxDepth - minDepth; - // save image - Image8U img(depthMap.size()); + const Depth sclDepth(Depth(1)/(maxDepth - minDepth)); + // create color image + Image8U3 img(depthMap.size()); for (int i=depthMap.area(); --i >= 0; ) { const Depth depth = depthMap[i]; - img[i] = (depth > 0 ? (uint8_t)CLAMP((maxDepth-depth)*255.f/deltaDepth, 0.f, 255.f) : 0); + img[i] = (depth > 0 ? Pixel8U::gray2color(CLAMP((maxDepth-depth)*sclDepth, Depth(0), Depth(1))) : Pixel8U::BLACK); } - return img.Save(fileName); + return img; +} // DepthMap2Image +bool MVS::ExportDepthMap(const String& fileName, const DepthMap& depthMap, Depth minDepth, Depth maxDepth) +{ + if (depthMap.empty()) + return false; + return DepthMap2Image(depthMap, minDepth, maxDepth).Save(fileName); } // ExportDepthMap /*----------------------------------------------------------------*/ @@ -1692,27 +1698,6 @@ bool MVS::ExportPointCloud(const String& fileName, const Image& imageData, const /*----------------------------------------------------------------*/ -struct HeaderDepthDataRaw { - enum { - HAS_DEPTH = (1<<0), - HAS_NORMAL = (1<<1), - HAS_CONF = (1<<2), - }; - uint16_t name; // file type - uint8_t type; // content type - uint8_t padding; // reserve - uint32_t imageWidth, imageHeight; // image resolution - uint32_t depthWidth, depthHeight; // depth-map resolution - float dMin, dMax; // depth range for this view - // image file name length followed by the characters: uint16_t nFileNameSize; char* FileName - // number of view IDs followed by view ID and neighbor view IDs: uint32_t nIDs; uint32_t* IDs - // camera, rotation and translation matrices (row-major): double K[3][3], R[3][3], C[3] - // depth, normal, confidence maps - inline HeaderDepthDataRaw() : name(0), type(0), padding(0) {} - static uint16_t HeaderDepthDataRawName() { return *reinterpret_cast("DR"); } - int GetStep() const { return ROUND2INT((float)imageWidth/depthWidth); } -}; - bool MVS::ExportDepthDataRaw(const String& fileName, const String& imageFileName, const IIndexArr& IDs, const cv::Size& imageSize, const KMatrix& K, const RMatrix& R, const CMatrix& C, @@ -1829,8 +1814,12 @@ bool MVS::ImportDepthDataRaw(const String& fileName, String& imageFileName, dMax = header.dMax; imageSize.width = header.imageWidth; imageSize.height = header.imageHeight; - depthMap.create(header.depthHeight, header.depthWidth); - fread(depthMap.getData(), sizeof(float), depthMap.area(), f); + if ((flags & HeaderDepthDataRaw::HAS_DEPTH) != 0) { + depthMap.create(header.depthHeight, header.depthWidth); + fread(depthMap.getData(), sizeof(float), depthMap.area(), f); + } else { + fseek(f, sizeof(float)*header.depthWidth*header.depthHeight, SEEK_CUR); + } // read normal-map if ((header.type & HeaderDepthDataRaw::HAS_NORMAL) != 0) { diff --git a/libs/MVS/DepthMap.h b/libs/MVS/DepthMap.h index 2e71cc48e..6cdb72c30 100644 --- a/libs/MVS/DepthMap.h +++ b/libs/MVS/DepthMap.h @@ -464,6 +464,7 @@ MVS_API bool LoadNormalMap(const String& fileName, NormalMap& normalMap); MVS_API bool SaveConfidenceMap(const String& fileName, const ConfidenceMap& confMap); MVS_API bool LoadConfidenceMap(const String& fileName, ConfidenceMap& confMap); +MVS_API Image8U3 DepthMap2Image(const DepthMap& depthMap, Depth minDepth=FLT_MAX, Depth maxDepth=0); MVS_API bool ExportDepthMap(const String& fileName, const DepthMap& depthMap, Depth minDepth=FLT_MAX, Depth maxDepth=0); MVS_API bool ExportNormalMap(const String& fileName, const NormalMap& normalMap); MVS_API bool ExportConfidenceMap(const String& fileName, const ConfidenceMap& confMap); diff --git a/libs/MVS/Interface.h b/libs/MVS/Interface.h index ea47cfcf9..6592a32dd 100644 --- a/libs/MVS/Interface.h +++ b/libs/MVS/Interface.h @@ -272,12 +272,12 @@ bool SerializeLoad(_Tp& obj, const std::string& fileName, uint32_t* pVersion=NUL #define ARCHIVE_DEFINE_TYPE(TYPE) \ template<> \ -bool Save(ArchiveSave& a, const TYPE& v) { \ +inline bool Save(ArchiveSave& a, const TYPE& v) { \ a.stream.write((const char*)&v, sizeof(TYPE)); \ return true; \ } \ template<> \ -bool Load(ArchiveLoad& a, TYPE& v) { \ +inline bool Load(ArchiveLoad& a, TYPE& v) { \ a.stream.read((char*)&v, sizeof(TYPE)); \ return true; \ } @@ -290,31 +290,31 @@ ARCHIVE_DEFINE_TYPE(double) // Serialization support for cv::Matx template -bool Save(ArchiveSave& a, const cv::Matx<_Tp,m,n>& _m) { +inline bool Save(ArchiveSave& a, const cv::Matx<_Tp,m,n>& _m) { a.stream.write((const char*)_m.val, sizeof(_Tp)*m*n); return true; } template -bool Load(ArchiveLoad& a, cv::Matx<_Tp,m,n>& _m) { +inline bool Load(ArchiveLoad& a, cv::Matx<_Tp,m,n>& _m) { a.stream.read((char*)_m.val, sizeof(_Tp)*m*n); return true; } // Serialization support for cv::Point3_ template -bool Save(ArchiveSave& a, const cv::Point3_<_Tp>& pt) { +inline bool Save(ArchiveSave& a, const cv::Point3_<_Tp>& pt) { a.stream.write((const char*)&pt.x, sizeof(_Tp)*3); return true; } template -bool Load(ArchiveLoad& a, cv::Point3_<_Tp>& pt) { +inline bool Load(ArchiveLoad& a, cv::Point3_<_Tp>& pt) { a.stream.read((char*)&pt.x, sizeof(_Tp)*3); return true; } // Serialization support for std::string template<> -bool Save(ArchiveSave& a, const std::string& s) { +inline bool Save(ArchiveSave& a, const std::string& s) { const uint64_t size(s.size()); Save(a, size); if (size > 0) @@ -322,7 +322,7 @@ bool Save(ArchiveSave& a, const std::string& s) { return true; } template<> -bool Load(ArchiveLoad& a, std::string& s) { +inline bool Load(ArchiveLoad& a, std::string& s) { uint64_t size; Load(a, size); if (size > 0) { @@ -334,7 +334,7 @@ bool Load(ArchiveLoad& a, std::string& s) { // Serialization support for std::vector template -bool Save(ArchiveSave& a, const std::vector<_Tp>& v) { +inline bool Save(ArchiveSave& a, const std::vector<_Tp>& v) { const uint64_t size(v.size()); Save(a, size); for (uint64_t i=0; i& v) { return true; } template -bool Load(ArchiveLoad& a, std::vector<_Tp>& v) { +inline bool Load(ArchiveLoad& a, std::vector<_Tp>& v) { uint64_t size; Load(a, size); if (size > 0) { @@ -383,6 +383,8 @@ struct Interface Camera() : width(0), height(0) {} bool HasResolution() const { return width > 0 && height > 0; } bool IsNormalized() const { return !HasResolution(); } + static uint32_t GetNormalizationScale(uint32_t width, uint32_t height) { return std::max(width, height); } + uint32_t GetNormalizationScale() const { return GetNormalizationScale(width, height); } template void serialize(Archive& ar, const unsigned int version) { @@ -429,6 +431,19 @@ struct Interface const Mat33d& GetK(uint32_t cameraID) const { return cameras[cameraID].K; } + static Mat33d ScaleK(const Mat33d& _K, double scale) { + Mat33d K(_K); + K(0,0) *= scale; + K(0,1) *= scale; + K(0,2) *= scale; + K(1,1) *= scale; + K(1,2) *= scale; + return K; + } + Mat33d GetFullK(uint32_t cameraID, uint32_t width, uint32_t height) const { + return ScaleK(GetK(cameraID), (double)Camera::GetNormalizationScale(width, height)/ + (cameras[cameraID].IsNormalized()?1.0:(double)cameras[cameraID].GetNormalizationScale())); + } Pose GetPose(uint32_t cameraID, uint32_t poseID) const { const Camera& camera = cameras[cameraID]; @@ -601,6 +616,39 @@ struct Interface }; /*----------------------------------------------------------------*/ + +// interface used to export/import MVS depth-map data; +// see MVS::ExportDepthDataRaw() and MVS::ImportDepthDataRaw() for usage example: +// - image-resolution at which the depth-map was estimated +// - depth-map-resolution, for now only the same resolution as the image is supported +// - min/max-depth of the values in the depth-map +// - image-file-name is the path to the reference color image +// - image-IDs are the reference view ID and neighbor view IDs used to estimate the depth-map +// - camera/rotation/position matrices (row-major) is the absolute pose corresponding to the reference view +// - depth-map represents the pixel depth +// - normal-map (optional) represents the 3D point normal in camera space; same resolution as the depth-map +// - confidence-map (optional) represents the 3D point confidence (usually a value in [0,1]); same resolution as the depth-map +struct HeaderDepthDataRaw { + enum { + HAS_DEPTH = (1<<0), + HAS_NORMAL = (1<<1), + HAS_CONF = (1<<2), + }; + uint16_t name; // file type + uint8_t type; // content type + uint8_t padding; // reserve + uint32_t imageWidth, imageHeight; // image resolution + uint32_t depthWidth, depthHeight; // depth-map resolution + float dMin, dMax; // depth range for this view + // image file name length followed by the characters: uint16_t nFileNameSize; char* FileName + // number of view IDs followed by view ID and neighbor view IDs: uint32_t nIDs; uint32_t* IDs + // camera, rotation and position matrices (row-major): double K[3][3], R[3][3], C[3] + // depth, normal, confidence maps: float depthMap[height][width], normalMap[height][width][3], confMap[height][width] + inline HeaderDepthDataRaw() : name(0), type(0), padding(0) {} + static uint16_t HeaderDepthDataRawName() { return *reinterpret_cast("DR"); } +}; +/*----------------------------------------------------------------*/ + } // namespace _INTERFACE_NAMESPACE #endif // _INTERFACE_MVS_H_ diff --git a/libs/MVS/Scene.cpp b/libs/MVS/Scene.cpp index ff8cf696d..4039377f2 100644 --- a/libs/MVS/Scene.cpp +++ b/libs/MVS/Scene.cpp @@ -277,10 +277,94 @@ bool Scene::SaveInterface(const String & fileName, int version) const } // SaveInterface /*----------------------------------------------------------------*/ + +// load depth-map and generate a Multi-View Stereo scene +bool Scene::LoadDMAP(const String& fileName) +{ + TD_TIMER_STARTD(); + + // load depth-map data + String imageFileName; + IIndexArr IDs; + cv::Size imageSize; + Camera camera; + Depth dMin, dMax; + DepthMap depthMap; + NormalMap normalMap; + ConfidenceMap confMap; + if (!ImportDepthDataRaw(fileName, imageFileName, IDs, imageSize, camera.K, camera.R, camera.C, dMin, dMax, depthMap, normalMap, confMap)) + return false; + + // create image + Platform& platform = platforms.AddEmpty(); + platform.name = _T("platform0"); + platform.cameras.emplace_back(CameraIntern::ScaleK(camera.K, REAL(1)/CameraIntern::GetNormalizationScale((uint32_t)imageSize.width,(uint32_t)imageSize.height)), RMatrix::IDENTITY, CMatrix::ZERO); + platform.poses.emplace_back(Platform::Pose{camera.R, camera.C}); + Image& image = images.AddEmpty(); + image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, imageFileName); + image.platformID = 0; + image.cameraID = 0; + image.poseID = 0; + image.ID = IDs.front(); + image.scale = 1; + image.avgDepth = (dMin+dMax)/2; + image.width = (uint32_t)imageSize.width; + image.height = (uint32_t)imageSize.height; + image.UpdateCamera(platforms); + nCalibratedImages = 1; + + // load image pixels + const Image8U3 imageDepth(DepthMap2Image(depthMap)); + if (image.ReloadImage(MAXF(image.width,image.height))) + cv::resize(image.image, image.image, depthMap.size()); + else + image.image = imageDepth; + + // create point-cloud + pointcloud.points.reserve(depthMap.area()); + pointcloud.pointViews.reserve(depthMap.area()); + pointcloud.colors.reserve(depthMap.area()); + if (!normalMap.empty()) + pointcloud.normals.reserve(depthMap.area()); + if (!confMap.empty()) + pointcloud.pointWeights.reserve(depthMap.area()); + for (int r=0; r(camera.R.t()*Cast(normalMap(r,c)))); + if (!confMap.empty()) + pointcloud.pointWeights.emplace_back(PointCloud::WeightArr{confMap(r,c)}); + } + } + + // replace color-image with depth-image + image.image = imageDepth; + + DEBUG_EXTRA("Scene loaded from depth-map format - %dx%d size, %.2f%% coverage (%s):\n" + "\t1 images (1 calibrated) with a total of %.2f MPixels (%.2f MPixels/image)\n" + "\t%u points, 0 lines", + depthMap.width(), depthMap.height(), 100.0*pointcloud.GetSize()/depthMap.area(), TD_TIMER_GET_FMT().c_str(), + (double)image.image.area()/(1024.0*1024.0), (double)image.image.area()/(1024.0*1024.0*nCalibratedImages), + pointcloud.GetSize()); + return true; +} // LoadDMAP +/*----------------------------------------------------------------*/ + // try to load known point-cloud or mesh files bool Scene::Import(const String& fileName) { const String ext(Util::getFileExt(fileName).ToLower()); + if (ext == _T(".dmap")) { + // import point-cloud from dmap file + Release(); + return LoadDMAP(fileName); + } if (ext == _T(".obj")) { // import mesh from obj file Release(); diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index 055969ca7..71b94acab 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -70,6 +70,7 @@ class MVS_API Scene bool LoadInterface(const String& fileName); bool SaveInterface(const String& fileName, int version=-1) const; + bool LoadDMAP(const String& fileName); bool Import(const String& fileName); bool Load(const String& fileName, bool bImport=false);