/* * InterfaceCOLMAP.cpp * * Copyright (c) 2014-2018 SEACAVE * * Author(s): * * cDc * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * * Additional Terms: * * You are required to preserve legal notices and author attributions in * that material or in the Appropriate Legal Notices displayed by works * containing it. */ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" #include #include "endian.h" using namespace MVS; // D E F I N E S /////////////////////////////////////////////////// #define APPNAME _T("InterfaceCOLMAP") #define MVS_FILE_EXTENSION _T(".mvs") #define COLMAP_IMAGES_FOLDER _T("images/") #define COLMAP_SPARSE_FOLDER _T("sparse/") #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/") #define COLMAP_FUSION COLMAP_STEREO_FOLDER _T("fusion.cfg") #define COLMAP_PATCHMATCH COLMAP_STEREO_FOLDER _T("patch-match.cfg") #define COLMAP_STEREO_CONSISTENCYGRAPHS_FOLDER COLMAP_STEREO_FOLDER _T("consistency_graphs/") #define COLMAP_STEREO_DEPTHMAPS_FOLDER COLMAP_STEREO_FOLDER _T("depth_maps/") #define COLMAP_STEREO_NORMALMAPS_FOLDER COLMAP_STEREO_FOLDER _T("normal_maps/") // S T R U C T S /////////////////////////////////////////////////// namespace { namespace OPT { bool IsFromOpenMVS; // conversion direction bool bNormalizeIntrinsics; bool bForceSparsePointCloud; String strInputFileName; String strPointCloudFileName; String strOutputFileName; String strImageFolder; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT class Application { public: Application() {} ~Application() { CleanUp(); } bool Initialize(size_t argc, LPCTSTR* argv); void CleanUp(); }; // Application // Initializes the application and parses command-line parameters bool Application::Initialize(size_t argc, LPCTSTR* argv) { // Open the log file and console for output OPEN_LOG(); OPEN_LOGCONSOLE(); // group of options allowed only on command line boost::program_options::options_description generic("Generic options"); generic.add_options() ("help,h", "imports SfM or MVS scene stored in COLMAP undistoreted format OR exports MVS scene to COLMAP format") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else 2 #endif ), "verbosity level") #endif ; // group of options allowed both on command line and in config file boost::program_options::options_description configOptions("Main options"); configOptions.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input COLMAP folder containing cameras, images and points files OR input MVS project file") ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "point-cloud with views file name (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the MVS project") ("image-folder", boost::program_options::value(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(false), "normalize intrinsics while exporting to MVS format") ("force-points,e", boost::program_options::value(&OPT::bForceSparsePointCloud)->default_value(false), "force exporting point-cloud as sparse points also even if dense point-cloud detected") ; // Combine all command-line options boost::program_options::options_description cmdline_options; cmdline_options.add(generic).add(configOptions); // Only config file options boost::program_options::options_description config_file_options; config_file_options.add(configOptions); boost::program_options::positional_options_description positionalOptions; positionalOptions.add("input-file", -1); try { // parse command line options boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(positionalOptions).run(), OPT::vm); boost::program_options::notify(OPT::vm); // Initialize working folder INIT_WORKING_FOLDER; // Parse configuration file if exists std::ifstream configFile(MAKE_PATH_SAFE(OPT::strConfigFileName)); if (configFile) { boost::program_options::store(parse_config_file(configFile, config_file_options), OPT::vm); boost::program_options::notify(OPT::vm); } } catch (const std::exception& e) { LOG(e.what()); return false; } // Open and configure the log file OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-")+Util::getUniqueName(0)+_T(".log"))); // print application details: version and command line Util::LogBuild(); LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // Check and validate paths Util::ensureValidPath(OPT::strInputFileName); Util::ensureValidPath(OPT::strPointCloudFileName); // Handle help option or invalid command const bool bInvalidCommand(OPT::strInputFileName.empty()); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(configOptions); GET_LOG() << _T("\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)." "\n") << visible; } if (bInvalidCommand) return false; // initialize optional options Util::ensureValidFolderPath(OPT::strImageFolder); Util::ensureValidPath(OPT::strOutputFileName); OPT::strImageFolder = MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strImageFolder); // Determine if the source file is an OpenMVS project by checking the file extension const String inputFileExtension(Util::getFileExt(OPT::strInputFileName).ToLower()); OPT::IsFromOpenMVS = (inputFileExtension == MVS_FILE_EXTENSION); // Set default output filename based on input filename if not explicitly specified if (OPT::IsFromOpenMVS) { if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName); } else { // Ensure the input filename ends with a directory slash if needed Util::ensureFolderSlash(OPT::strInputFileName); if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = OPT::strInputFileName + _T("scene") MVS_FILE_EXTENSION; } MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance void Application::CleanUp() { MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } 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; const std::vector mapCameraModel = { "SIMPLE_PINHOLE", "PINHOLE", "SIMPLE_RADIAL", "RADIAL", "OPENCV", "OPENCV_FISHEYE", "FULL_OPENCV", "FOV", "SIMPLE_RADIAL_FISHEYE", "RADIAL_FISHEYE", "THIN_PRISM_FISHEYE" }; // tools bool NextLine(std::istream& stream, std::istringstream& in, bool bIgnoreEmpty=true) { String line; do { std::getline(stream, line); Util::strTrim(line, _T(" ")); if (stream.fail()) return false; } while (((bIgnoreEmpty && line.empty()) || line[0u] == '#') && stream.good()); in.clear(); in.str(line); return true; } // structure describing a camera struct Camera { uint32_t ID; // ID of the camera String model; // camera model name uint32_t width, height; // camera resolution std::vector params; // camera parameters uint64_t numCameras{0}; // only for binary format Camera() {} Camera(uint32_t _ID) : ID(_ID) {} bool operator < (const Camera& rhs) const { return ID < rhs.ID; } struct CameraHash { size_t operator()(const Camera& camera) const { const size_t h1(std::hash()(camera.model)); const size_t h2(std::hash()(camera.width)); const size_t h3(std::hash()(camera.height)); size_t h(h1 ^ ((h2 ^ (h3 << 1)) << 1)); for (REAL p: camera.params) h = std::hash()(p) ^ (h << 1); return h; } }; struct CameraEqualTo { bool operator()(const Camera& _Left, const Camera& _Right) const { return _Left.model == _Right.model && _Left.width == _Right.width && _Left.height == _Right.height && _Left.params == _Right.params; } }; bool Read(std::istream& stream, bool binary) { if (binary) return ReadBIN(stream); return ReadTXT(stream); } bool Write(std::ostream& stream, bool binary) { if (binary) return WriteBIN(stream); return WriteTXT(stream); } // Camera list with one line of data per camera: // CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[] bool ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; in >> ID >> model >> width >> height; if (in.fail()) return false; --ID; if (model != _T("PINHOLE")) return false; params.resize(4); 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 (numCameras == 0) { // Read the first entry in the binary file numCameras = ReadBinaryLittleEndian(&stream); } ID = ReadBinaryLittleEndian(&stream)-1; model = mapCameraModel[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 WriteTXT(std::ostream& out) const { out << ID+1 << _T(" ") << model << _T(" ") << width << _T(" ") << height; if (out.fail()) return false; for (REAL param: params) { out << _T(" ") << param; if (out.fail()) return false; } out << std::endl; return true; } bool WriteBIN(std::ostream& stream) { if (numCameras != 0) { // Write the first entry in the binary file WriteBinaryLittleEndian(&stream, numCameras); numCameras = 0; } WriteBinaryLittleEndian(&stream, ID+1); const int64 modelId(std::distance(mapCameraModel.begin(), std::find(mapCameraModel.begin(), mapCameraModel.end(), model))); WriteBinaryLittleEndian(&stream, (int)modelId); WriteBinaryLittleEndian(&stream, width); WriteBinaryLittleEndian(&stream, height); for (REAL param: params) WriteBinaryLittleEndian(&stream, param); return !stream.fail(); } }; typedef std::vector Cameras; // structure describing an image struct Image { struct Proj { Eigen::Vector2f p; uint32_t idPoint; }; uint32_t ID; // ID of the image Eigen::Quaterniond q; // rotation Eigen::Vector3d t; // translation uint32_t idCamera; // ID of the associated camera String name; // image file name std::vector projs; // known image projections uint64_t numRegImages{0}; // only for binary format 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); return ReadTXT(stream); } bool Write(std::ostream& stream, bool binary) { if (binary) return WriteBIN(stream); return WriteTXT(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 ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; in >> ID >> q.w() >> q.x() >> q.y() >> q.z() >> t(0) >> t(1) >> t(2) >> idCamera >> name; if (in.fail()) return false; --ID; --idCamera; Util::ensureValidPath(name); if (!NextLine(stream, in, false)) return false; projs.clear(); while (true) { Proj proj; in >> proj.p(0) >> proj.p(1) >> (int&)proj.idPoint; if (in.fail()) break; --proj.idPoint; projs.emplace_back(proj); } 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 (!numRegImages) { // Read the first entry in the binary file numRegImages = ReadBinaryLittleEndian(&stream); } ID = ReadBinaryLittleEndian(&stream)-1; 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)-1; name = ""; while (true) { char nameChar; stream.read(&nameChar, 1); if (nameChar == '\0') break; name += nameChar; } 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)-1; projs.emplace_back(proj); } return true; } bool WriteTXT(std::ostream& out) const { out << ID+1 << _T(" ") << q.w() << _T(" ") << q.x() << _T(" ") << q.y() << _T(" ") << q.z() << _T(" ") << t(0) << _T(" ") << t(1) << _T(" ") << t(2) << _T(" ") << idCamera+1 << _T(" ") << name << std::endl; for (const Proj& proj: projs) { out << proj.p(0) << _T(" ") << proj.p(1) << _T(" ") << (int)proj.idPoint+1 << _T(" "); if (out.fail()) return false; } out << std::endl; return !out.fail(); } bool WriteBIN(std::ostream& stream) { if (numRegImages != 0) { // Write the first entry in the binary file WriteBinaryLittleEndian(&stream, numRegImages); numRegImages = 0; } WriteBinaryLittleEndian(&stream, ID+1); WriteBinaryLittleEndian(&stream, q.w()); WriteBinaryLittleEndian(&stream, q.x()); WriteBinaryLittleEndian(&stream, q.y()); WriteBinaryLittleEndian(&stream, q.z()); WriteBinaryLittleEndian(&stream, t(0)); WriteBinaryLittleEndian(&stream, t(1)); WriteBinaryLittleEndian(&stream, t(2)); WriteBinaryLittleEndian(&stream, idCamera+1); stream.write(name.c_str(), name.size()+1); WriteBinaryLittleEndian(&stream, projs.size()); for (const Proj& proj: projs) { WriteBinaryLittleEndian(&stream, proj.p(0)); WriteBinaryLittleEndian(&stream, proj.p(1)); WriteBinaryLittleEndian(&stream, proj.idPoint+1); } return !stream.fail(); } }; typedef std::vector Images; // structure describing a 3D point struct Point { struct Track { uint32_t idImage; uint32_t idProj; }; uint32_t ID; // ID of the point Interface::Pos3f p; // position Interface::Col3 c; // BGR color float e; // error std::vector tracks; // point track uint64_t numPoints3D{0}; // only for binary format 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); return ReadTXT(stream); } bool Write(std::ostream& stream, bool binary) { if (binary) return WriteBIN(stream); return WriteTXT(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 ReadTXT(std::istream& stream) { std::istringstream in; if (!NextLine(stream, in)) return false; int r,g,b; in >> ID >> p.x >> p.y >> p.z >> r >> g >> b >> e; c.x = CLAMP(b,0,255); c.y = CLAMP(g,0,255); c.z = CLAMP(r,0,255); if (in.fail()) return false; --ID; tracks.clear(); while (true) { Track track; in >> track.idImage >> track.idProj; if (in.fail()) break; --track.idImage; --track.idProj; tracks.emplace_back(track); } 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 (!numPoints3D) { // Read the first entry in the binary file numPoints3D = ReadBinaryLittleEndian(&stream); } int r,g,b; ID = (uint32_t)ReadBinaryLittleEndian(&stream)-1; 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)-1; track.idProj = ReadBinaryLittleEndian(&stream)-1; tracks.emplace_back(track); } return !tracks.empty(); } bool WriteTXT(std::ostream& out) const { ASSERT(!tracks.empty()); const int r(c.z),g(c.y),b(c.x); out << ID+1 << _T(" ") << p.x << _T(" ") << p.y << _T(" ") << p.z << _T(" ") << r << _T(" ") << g << _T(" ") << b << _T(" ") << e << _T(" "); for (const Track& track: tracks) { out << track.idImage+1 << _T(" ") << track.idProj+1 << _T(" "); if (out.fail()) return false; } out << std::endl; return !out.fail(); } bool WriteBIN(std::ostream& stream) { ASSERT(!tracks.empty()); if (numPoints3D != 0) { // Write the first entry in the binary file WriteBinaryLittleEndian(&stream, numPoints3D); numPoints3D = 0; } WriteBinaryLittleEndian(&stream, ID+1); WriteBinaryLittleEndian(&stream, p.x); WriteBinaryLittleEndian(&stream, p.y); WriteBinaryLittleEndian(&stream, p.z); WriteBinaryLittleEndian(&stream, c.z); WriteBinaryLittleEndian(&stream, c.y); WriteBinaryLittleEndian(&stream, c.x); WriteBinaryLittleEndian(&stream, e); WriteBinaryLittleEndian(&stream, tracks.size()); for (const Track& track: tracks) { WriteBinaryLittleEndian(&stream, track.idImage+1); WriteBinaryLittleEndian(&stream, track.idProj+1); } return !stream.fail(); } }; typedef std::vector Points; // structure describing an 2D dynamic matrix template struct Mat { size_t width_ = 0; size_t height_ = 0; size_t depth_ = 0; std::vector data_; size_t GetNumBytes() const { return data_.size() * sizeof(T); } const T* GetChannelPtr(size_t c) const { return data_.data()+width_*height_*c; } // See: colmap/src/mvs/mat.h void Read(const std::string& path) { std::streampos pos; { std::fstream text_file(path, std::ios::in | std::ios::binary); char unused_char; text_file >> width_ >> unused_char >> height_ >> unused_char >> depth_ >> unused_char; pos = text_file.tellg(); } data_.resize(width_ * height_ * depth_); std::fstream binary_file(path, std::ios::in | std::ios::binary); binary_file.seekg(pos); ReadBinaryLittleEndian(&binary_file, &data_); } void Write(const std::string& path) const { { std::fstream text_file(path, std::ios::out); text_file << width_ << "&" << height_ << "&" << depth_ << "&"; } std::fstream binary_file(path, std::ios::out | std::ios::binary | std::ios::app); WriteBinaryLittleEndian(&binary_file, data_); } }; } // namespace COLMAP typedef Eigen::Matrix EMat33d; typedef Eigen::Matrix EVec3d; /** * Determines whether to use the TXT or BIN version of a file based on availability. * It tries to open the TXT file first; if unsuccessful, it attempts to open the BIN file. * * @param filenameTXT The path to the TXT file. * @param filenameBIN The path to the BIN file. * @param fileStream Reference to the ifstream object that will be used for file operations. * @param chosenFilename Reference to a string that will store the path of the chosen file. * @param isBinaryFormat Reference to a boolean that indicates whether the chosen file is binary. * @return true if either file was successfully opened, false otherwise. */ bool DetermineInputSource(const String& filenameTXT, const String& filenameBIN, std::ifstream& fileStream, String& filenameCamera, bool& isBinaryFormat) { // Try to open the TXT file first. fileStream.open(filenameTXT); if (fileStream.good()) { filenameCamera = filenameTXT; isBinaryFormat = false; return true; } fileStream.open(filenameBIN, std::ios::binary); if (fileStream.good()) { filenameCamera = filenameBIN; isBinaryFormat = 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& inputFolder, const String& outputFolder, Interface& scene, PointCloud& pointcloud) { // Define a map to store camera IDs and associated platform index. typedef std::unordered_map CamerasMap; CamerasMap mapCameras; { // Determine the camera file source between TXT and BIN formats. const String filenameCamerasTXT(inputFolder+COLMAP_CAMERAS_TXT); const String filenameCamerasBIN(inputFolder+COLMAP_CAMERAS_BIN); std::ifstream cameraFile; bool isBinaryFormat; String filenameCamera; if (!DetermineInputSource(filenameCamerasTXT, filenameCamerasBIN, cameraFile, filenameCamera, isBinaryFormat)) { return false; // Exit if file source determination fails. } LOG_OUT() << "Reading cameras: " << filenameCamera << std::endl; typedef std::unordered_map CamerasSet; CamerasSet setCameras; COLMAP::Camera colmapCamera; while (cameraFile.good() && colmapCamera.Read(cameraFile, isBinaryFormat)) { const auto setIt(setCameras.emplace(colmapCamera, (uint32_t)scene.platforms.size())); mapCameras.emplace(colmapCamera.ID, setIt.first->second); if (!setIt.second) { // reuse existing platform continue; } // create new platform Interface::Platform platform; platform.name = String::FormatString(_T("platform%03u"), colmapCamera.ID); // only one camera per platform supported Interface::Platform::Camera camera; camera.name = colmapCamera.model; camera.K = Interface::Mat33d::eye(); // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) camera.K(0,0) = colmapCamera.params[0]; camera.K(1,1) = colmapCamera.params[1]; camera.K(0,2) = colmapCamera.params[2]-REAL(0.5); camera.K(1,2) = colmapCamera.params[3]-REAL(0.5); camera.R = Interface::Mat33d::eye(); camera.C = Interface::Pos3d(0,0,0); if (OPT::bNormalizeIntrinsics) { // normalize camera intrinsics camera.K = Camera::ScaleK(camera.K, 1.0/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); } else { camera.width = colmapCamera.width; camera.height = colmapCamera.height; } platform.cameras.emplace_back(camera); scene.platforms.emplace_back(platform); } } if (mapCameras.empty()) { VERBOSE("error: no valid cameras (make sure they are in PINHOLE model)"); return false; } // Function to read and process image data from input files typedef std::map ImagesMap; ImagesMap mapImages; { // Define a map to associate COLMAP image structures with scene image indices const String filenameImagesTXT(inputFolder+COLMAP_IMAGES_TXT); const String filenameImagesBIN(inputFolder+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; // Read image data from the file and store in the scene structure COLMAP::Image imageColmap; 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(); EnsureRotationMatrix((Matrix3x3d&)pose.R); Eigen::Map(&pose.C.x) = -(imageColmap.q.inverse() * imageColmap.t); Interface::Image image; image.name = MAKE_PATH_REL(outputFolder,OPT::strImageFolder+imageColmap.name); image.platformID = mapCameras.at(imageColmap.idCamera); image.cameraID = 0; image.ID = imageColmap.ID; Interface::Platform& platform = scene.platforms[image.platformID]; image.poseID = (uint32_t)platform.poses.size(); platform.poses.emplace_back(pose); scene.images.emplace_back(image); } } // read points list const String filenameDensePoints(inputFolder+COLMAP_DENSE_POINTS); const String filenameDenseVisPoints(inputFolder+COLMAP_DENSE_POINTS_VISIBILITY); { // parse sparse point-cloud const String filenamePointsTXT(inputFolder+COLMAP_POINTS_TXT); const String filenamePointsBIN(inputFolder+COLMAP_POINTS_BIN); std::ifstream file; bool binary; String filenamePoints; if (!DetermineInputSource(filenamePointsTXT, filenamePointsBIN, file, filenamePoints, binary)) { return false; } LOG_OUT() << "Reading points: " << filenamePoints << std::endl; COLMAP::Point point; while (file.good() && point.Read(file, binary)) { Interface::Vertex vertex; vertex.X = point.p; for (const COLMAP::Point::Track& track: point.tracks) { Interface::Vertex::View view; view.imageID = mapImages.at(COLMAP::Image(track.idImage)); view.confidence = 0; vertex.views.emplace_back(view); } std::sort(vertex.views.begin(), vertex.views.end(), [](const Interface::Vertex::View& view0, const Interface::Vertex::View& view1) { return view0.imageID < view1.imageID; }); scene.vertices.emplace_back(std::move(vertex)); scene.verticesColor.emplace_back(Interface::Color{point.c}); } } pointcloud.Release(); if (File::access(filenameDensePoints) && File::access(filenameDenseVisPoints)) { // parse dense point-cloud LOG_OUT() << "Reading points: " << filenameDensePoints << " and " << filenameDenseVisPoints << std::endl; if (!pointcloud.Load(filenameDensePoints)) { VERBOSE("error: unable to open file '%s'", filenameDensePoints.c_str()); return false; } File file(filenameDenseVisPoints, File::READ, File::OPEN); if (!file.isOpen()) { VERBOSE("error: unable to open file '%s'", filenameDenseVisPoints.c_str()); return false; } uint64_t numPoints(0); file.read(&numPoints, sizeof(uint64_t)); if (pointcloud.GetSize() != numPoints) { VERBOSE("error: point-cloud and visibility have different size"); return false; } pointcloud.pointViews.resize(numPoints); for (size_t i=0; isecond]; CLISTDEF2(String) neighborNames; Util::strSplit(neighbors, _T(','), neighborNames); FOREACH(i, neighborNames) { String& neighborName = neighborNames[i]; Util::strTrim(neighborName, _T(" ")); const ImagesMap::const_iterator it_neighbor = std::find_if(mapImages.begin(), mapImages.end(), [&neighborName](const ImagesMap::value_type& image) { return image.first.name == neighborName; }); if (it_neighbor == mapImages.end()) { if (i == 0) break; continue; } imageNeighbors.emplace_back(scene.images[it_neighbor->second].ID); } } } LOG_OUT() << "Reading depth-maps/normal-maps: " << pathDepthMaps << " and " << pathNormalMaps << std::endl; Util::ensureFolder(outputFolder); const String strType[] = {".geometric.bin", ".photometric.bin"}; FOREACH(idx, scene.images) { const Interface::Image& image = scene.images[idx]; COLMAP::Mat colDepthMap, colNormalMap; const String filenameImage(Util::getFileNameExt(image.name)); for (int i=0; i<2; ++i) { const String filenameDepthMaps(pathDepthMaps+filenameImage+strType[i]); if (File::isFile(filenameDepthMaps)) { colDepthMap.Read(filenameDepthMaps); const String filenameNormalMaps(pathNormalMaps+filenameImage+strType[i]); if (File::isFile(filenameNormalMaps)) { colNormalMap.Read(filenameNormalMaps); } break; } } if (!colDepthMap.data_.empty()) { IIndexArr IDs = {image.ID}; IDs.Join(imagesNeighbors[(IIndex)idx]); const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose pose(platform.GetPose(image.cameraID, image.poseID)); const Interface::Mat33d K(platform.GetFullK(image.cameraID, (uint32_t)colDepthMap.width_, (uint32_t)colDepthMap.height_)); MVS::DepthMap depthMap((int)colDepthMap.height_, (int)colDepthMap.width_); memcpy(depthMap.getData(), colDepthMap.data_.data(), colDepthMap.GetNumBytes()); MVS::NormalMap normalMap; if (!colNormalMap.data_.empty()) { normalMap.create((int)colNormalMap.height_, (int)colNormalMap.width_); cv::merge(std::vector{ cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(0)), cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(1)), cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(2)) }, normalMap); } MVS::ConfidenceMap confMap; MVS::ViewsMap viewsMap; const auto depthMM(std::minmax_element(colDepthMap.data_.cbegin(), colDepthMap.data_.cend())); const MVS::Depth dMin(*depthMM.first), dMax(*depthMM.second); if (!ExportDepthDataRaw(outputFolder+String::FormatString("depth%04u.dmap", image.ID), MAKE_PATH_FULL(outputFolder, image.name), IDs, depthMap.size(), K, pose.R, pose.C, dMin, dMax, depthMap, normalMap, confMap, viewsMap)) return false; } } } return true; } bool ImportPointCloud(const String& strPointCloudFileName, Interface& scene) { PointCloud pointcloud; if (!pointcloud.Load(strPointCloudFileName)) { VERBOSE("error: cannot load point-cloud file"); return false; } if (!pointcloud.IsValid()) { VERBOSE("error: loaded point-cloud does not have visibility information"); return false; } // replace scene point-cloud with the loaded one scene.vertices.clear(); scene.verticesColor.clear(); scene.verticesNormal.clear(); scene.vertices.reserve(pointcloud.points.size()); if (!pointcloud.colors.empty()) scene.verticesColor.reserve(pointcloud.points.size()); if (!pointcloud.normals.empty()) scene.verticesNormal.reserve(pointcloud.points.size()); FOREACH(i, pointcloud.points) { Interface::Vertex vertex; vertex.X = pointcloud.points[i]; vertex.views.reserve(pointcloud.pointViews[i].size()); FOREACH(j, pointcloud.pointViews[i]) { Interface::Vertex::View& view = vertex.views.emplace_back(); view.imageID = pointcloud.pointViews[i][j]; view.confidence = (pointcloud.pointWeights.empty() ? 0.f : pointcloud.pointWeights[i][j]); } scene.vertices.emplace_back(std::move(vertex)); if (!pointcloud.colors.empty()) { const Pixel8U& c = pointcloud.colors[i]; scene.verticesColor.emplace_back(Interface::Color{Interface::Col3{c.b, c.g, c.r}}); } if (!pointcloud.normals.empty()) scene.verticesNormal.emplace_back(Interface::Normal{pointcloud.normals[i]}); } return true; } bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSparsePointCloud = false, bool binary = true) { Util::ensureFolder(strFolder+COLMAP_SPARSE_FOLDER); // write camera list CLISTDEF0IDX(KMatrix,uint32_t) Ks; CLISTDEF0IDX(COLMAP::Camera,uint32_t) cams; { // Construct the filename for camera data based on the format preference const String filenameCameras(strFolder+(binary?COLMAP_CAMERAS_BIN:COLMAP_CAMERAS_TXT)); LOG_OUT() << "Writing cameras: " << filenameCameras << std::endl; std::ofstream file(filenameCameras, binary ? std::ios::trunc|std::ios::binary : std::ios::trunc); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenameCameras.c_str()); return false; } COLMAP::Camera cam; if (binary) { cam.numCameras = 0; for (const Interface::Platform& platform: scene.platforms) cam.numCameras += (uint32_t)platform.cameras.size(); } else { file << _T("# Camera list with one line of data per camera:") << std::endl; file << _T("# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]") << std::endl; } cam.model = _T("PINHOLE"); cam.params.resize(4); for (uint32_t ID=0; ID<(uint32_t)scene.platforms.size(); ++ID) { const Interface::Platform& platform = scene.platforms[ID]; ASSERT(platform.cameras.size() == 1); // only one camera per platform supported const Interface::Platform::Camera& camera = platform.cameras[0]; cam.ID = ID; KMatrix K; if (camera.width == 0 || camera.height == 0) { // find one image using this camera const Interface::Image* pImage(NULL); for (uint32_t i=0; i<(uint32_t)scene.images.size(); ++i) { const Interface::Image& image = scene.images[i]; if (image.platformID == ID && image.cameraID == 0 && image.poseID != NO_ID) { pImage = ℑ break; } } if (pImage == NULL) { LOG("error: no image using camera %u of platform %u", 0, ID); continue; } IMAGEPTR ptrImage(Image::ReadImageHeader(MAKE_PATH_SAFE(pImage->name))); if (ptrImage == NULL) return false; cam.width = ptrImage->GetWidth(); cam.height = ptrImage->GetHeight(); // unnormalize camera intrinsics K = platform.GetFullK(0, cam.width, cam.height); } else { cam.width = camera.width; cam.height = camera.height; K = camera.K; } // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) cam.params[0] = K(0,0); cam.params[1] = K(1,1); cam.params[2] = K(0,2)+REAL(0.5); cam.params[3] = K(1,2)+REAL(0.5); if (!cam.Write(file, binary)) return false; Ks.emplace_back(K); cams.emplace_back(cam); } } // create images list COLMAP::Images images; CameraArr cameras; float maxNumPointsSparse(0); const float avgViewsPerPoint(3.f); const uint32_t avgResolutionSmallView(640*480), avgResolutionLargeView(6000*4000); const uint32_t avgPointsPerSmallView(3000), avgPointsPerLargeView(12000); { images.resize(scene.images.size()); cameras.resize((unsigned)scene.images.size()); for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { const Interface::Image& image = scene.images[ID]; if (image.poseID == NO_ID) continue; const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose& pose = platform.poses[image.poseID]; ASSERT(image.cameraID == 0); COLMAP::Image& img = images[ID]; img.ID = ID; img.q = Eigen::Quaterniond(Eigen::Map(pose.R.val)); img.t = -(img.q * Eigen::Map(&pose.C.x)); img.idCamera = image.platformID; img.name = MAKE_PATH_REL(OPT::strImageFolder, MAKE_PATH_FULL(WORKING_FOLDER_FULL, image.name)); Camera& camera = cameras[ID]; camera.K = Ks[image.platformID]; camera.R = pose.R; camera.C = pose.C; camera.ComposeP(); const COLMAP::Camera& cam = cams[image.platformID]; const uint32_t resolutionView(cam.width*cam.height); const float linearFactor(float(avgResolutionLargeView-resolutionView)/(avgResolutionLargeView-avgResolutionSmallView)); maxNumPointsSparse += (avgPointsPerSmallView+(avgPointsPerLargeView-avgPointsPerSmallView)*linearFactor)/avgViewsPerPoint; } } // auto-select dense or sparse mode based on number of points const bool bSparsePointCloud(scene.vertices.size() < (size_t)maxNumPointsSparse); if (bSparsePointCloud || bForceSparsePointCloud) { // write points list { const String filenamePoints(strFolder+(binary?COLMAP_POINTS_BIN:COLMAP_POINTS_TXT)); LOG_OUT() << "Writing points: " << filenamePoints << std::endl; std::ofstream file(filenamePoints, binary ? std::ios::trunc|std::ios::binary : std::ios::trunc); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenamePoints.c_str()); return false; } uint64_t numPoints3D = 0; if (binary) { numPoints3D = scene.vertices.size(); } else { file << _T("# 3D point list with one line of data per point:") << std::endl; file << _T("# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)") << std::endl; } for (uint32_t ID=0; ID<(uint32_t)scene.vertices.size(); ++ID) { const Interface::Vertex& vertex = scene.vertices[ID]; COLMAP::Point point; point.ID = ID; point.p = vertex.X; for (const Interface::Vertex::View& view: vertex.views) { COLMAP::Image& img = images[view.imageID]; point.tracks.emplace_back(COLMAP::Point::Track{view.imageID, (uint32_t)img.projs.size()}); COLMAP::Image::Proj proj; proj.idPoint = ID; const Point3 X(vertex.X); ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) proj.p[0] += REAL(0.5); proj.p[1] += REAL(0.5); img.projs.emplace_back(proj); } point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; point.e = 0; if (numPoints3D != 0) { point.numPoints3D = numPoints3D; numPoints3D = 0; } if (!point.Write(file, binary)) return false; } } Util::ensureFolder(strFolder+COLMAP_STEREO_FOLDER); // write fusion list { const String filenameFusion(strFolder+COLMAP_FUSION); LOG_OUT() << "Writing fusion configuration: " << filenameFusion << std::endl; std::ofstream file(filenameFusion); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); return false; } for (const COLMAP::Image& img: images) { if (img.projs.empty()) continue; file << img.name << std::endl; if (file.fail()) return false; } } // write patch-match list { const String filenameFusion(strFolder+COLMAP_PATCHMATCH); LOG_OUT() << "Writing patch-match configuration: " << filenameFusion << std::endl; std::ofstream file(filenameFusion); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); return false; } for (const COLMAP::Image& img: images) { if (img.projs.empty()) continue; file << img.name << std::endl; if (file.fail()) return false; file << _T("__auto__, 20") << std::endl; if (file.fail()) return false; } } Util::ensureFolder(strFolder+COLMAP_STEREO_CONSISTENCYGRAPHS_FOLDER); Util::ensureFolder(strFolder+COLMAP_STEREO_DEPTHMAPS_FOLDER); Util::ensureFolder(strFolder+COLMAP_STEREO_NORMALMAPS_FOLDER); } if (!bSparsePointCloud) { // export dense point-cloud const String filenameDensePoints(strFolder+COLMAP_DENSE_POINTS); const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); LOG_OUT() << "Writing points: " << filenameDensePoints << " and " << filenameDenseVisPoints << std::endl; File file(filenameDenseVisPoints, File::WRITE, File::CREATE | File::TRUNCATE); if (!file.isOpen()) { VERBOSE("error: unable to write file '%s'", filenameDenseVisPoints.c_str()); return false; } const uint64_t numPoints(scene.vertices.size()); file.write(&numPoints, sizeof(uint64_t)); PointCloud pointcloud; for (size_t i=0; iGetWidth(), pImage->GetHeight()); Eigen::Matrix4d K4(Eigen::Matrix4d::Identity()); K4.topLeftCorner<3,3>() = Eigen::Map(K.val); Util::ensureFolder(fileName); std::ofstream out(fileName); if (!out.good()) { VERBOSE("error: unable to open file '%s'", fileName.c_str()); return false; } out << std::setprecision(12); out << K4(0,0) << _T(" ") << K4(0,1) << _T(" ") << K4(0,2) << _T(" ") << K4(0,3) << _T("\n"); out << K4(1,0) << _T(" ") << K4(1,1) << _T(" ") << K4(1,2) << _T(" ") << K4(1,3) << _T("\n"); out << K4(2,0) << _T(" ") << K4(2,1) << _T(" ") << K4(2,2) << _T(" ") << K4(2,3) << _T("\n"); out << K4(3,0) << _T(" ") << K4(3,1) << _T(" ") << K4(3,2) << _T(" ") << K4(3,3) << _T("\n"); return !out.fail(); } // export image poses in log format // see: http://redwood-data.org/indoor/fileformat.html // to support: https://www.tanksandtemples.org/tutorial bool ExportImagesLog(const String& fileName, const Interface& scene) { LOG_OUT() << "Writing poses: " << fileName << std::endl; Util::ensureFolder(fileName); std::ofstream out(fileName); if (!out.good()) { VERBOSE("error: unable to open file '%s'", fileName.c_str()); return false; } out << std::setprecision(12); IIndexArr orderedImages((uint32_t)scene.images.size()); std::iota(orderedImages.begin(), orderedImages.end(), 0u); orderedImages.Sort([&scene](IIndex i, IIndex j) { return scene.images[i].ID < scene.images[j].ID; }); for (IIndex ID: orderedImages) { const Interface::Image& image = scene.images[ID]; Eigen::Matrix3d R(Eigen::Matrix3d::Identity()); Eigen::Vector3d t(Eigen::Vector3d::Zero()); if (image.poseID != NO_ID) { const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose& pose = platform.poses[image.poseID]; R = Eigen::Map(pose.R.val).transpose(); t = Eigen::Map(&pose.C.x); } Eigen::Matrix4d T(Eigen::Matrix4d::Identity()); T.topLeftCorner<3,3>() = R; T.topRightCorner<3,1>() = t; out << ID << _T(" ") << ID << _T(" ") << 0 << _T("\n"); out << T(0,0) << _T(" ") << T(0,1) << _T(" ") << T(0,2) << _T(" ") << T(0,3) << _T("\n"); out << T(1,0) << _T(" ") << T(1,1) << _T(" ") << T(1,2) << _T(" ") << T(1,3) << _T("\n"); out << T(2,0) << _T(" ") << T(2,1) << _T(" ") << T(2,2) << _T(" ") << T(2,3) << _T("\n"); out << T(3,0) << _T(" ") << T(3,1) << _T(" ") << T(3,2) << _T(" ") << T(3,3) << _T("\n"); } return !out.fail(); } // export poses in Strecha camera format: // Strecha model is P = K[R^T|-R^T t] // our model is P = K[R|t], t = -RC bool ExportImagesCamera(const String& pathName, const Interface& scene) { LOG_OUT() << "Writing poses: " << pathName << std::endl; Util::ensureFolder(pathName); for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { const Interface::Image& image = scene.images[ID]; String imageFileName(image.name); Util::ensureValidPath(imageFileName); const String fileName(pathName+Util::getFileNameExt(imageFileName)+".camera"); std::ofstream out(fileName); if (!out.good()) { VERBOSE("error: unable to open file '%s'", fileName.c_str()); return false; } out << std::setprecision(12); KMatrix K(KMatrix::IDENTITY); RMatrix R(RMatrix::IDENTITY); CMatrix t(CMatrix::ZERO); unsigned width(0), height(0); if (image.platformID != NO_ID && image.cameraID != NO_ID) { const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Camera& camera = platform.cameras[image.cameraID]; if (camera.HasResolution()) { width = camera.width; height = camera.height; K = camera.K; } else { IMAGEPTR pImage = Image::ReadImageHeader(image.name); width = pImage->GetWidth(); height = pImage->GetHeight(); K = platform.GetFullK(image.cameraID, width, height); } if (image.poseID != NO_ID) { const Interface::Platform::Pose& pose = platform.poses[image.poseID]; R = pose.R.t(); t = pose.C; } } out << K(0,0) << _T(" ") << K(0,1) << _T(" ") << K(0,2) << _T("\n"); out << K(1,0) << _T(" ") << K(1,1) << _T(" ") << K(1,2) << _T("\n"); out << K(2,0) << _T(" ") << K(2,1) << _T(" ") << K(2,2) << _T("\n"); out << _T("0 0 0") << _T("\n"); out << R(0,0) << _T(" ") << R(0,1) << _T(" ") << R(0,2) << _T("\n"); out << R(1,0) << _T(" ") << R(1,1) << _T(" ") << R(1,2) << _T("\n"); out << R(2,0) << _T(" ") << R(2,1) << _T(" ") << R(2,2) << _T("\n"); out << t.x << _T(" ") << t.y << _T(" ") << t.z << _T("\n"); out << width << _T(" ") << height << _T("\n"); if (out.fail()) { VERBOSE("error: unable to write file '%s'", fileName.c_str()); return false; } } return true; } } // unnamed namespace // Main function with command-line arguments int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO // set _crtBreakAlloc index to stop in at allocation _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif Application app; // Instance of the application if (!app.Initialize(argc, argv)) // Initialize app with command line arguments return EXIT_FAILURE; // Exit if initialization fails TD_TIMER_START(); if (OPT::IsFromOpenMVS) { // read MVS input data Interface scene; // Scene data structure for MVS input if (!ARCHIVE::SerializeLoad(scene, MAKE_PATH_SAFE(OPT::strInputFileName))) return EXIT_FAILURE; // Exit if loading fails // Check output file type and export accordingly if (Util::getFileExt(OPT::strOutputFileName) == _T(".log")) { // write poses in log format ExportIntrinsicsTxt(MAKE_PATH_FULL(WORKING_FOLDER_FULL, String("intrinsics.txt")), scene); ExportImagesLog(MAKE_PATH_SAFE(OPT::strOutputFileName), scene); } else if (Util::getFileExt(OPT::strOutputFileName) == _T(".camera")) { // write poses in Strecha camera format ExportImagesCamera((OPT::strOutputFileName=Util::getFileFullName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName)))+PATH_SEPARATOR, scene); } else { // write COLMAP input data if (!OPT::strPointCloudFileName.empty() && !ImportPointCloud(MAKE_PATH_SAFE(OPT::strPointCloudFileName), scene)) return EXIT_FAILURE; Util::ensureFolderSlash(OPT::strOutputFileName); ExportScene(MAKE_PATH_SAFE(OPT::strOutputFileName), scene, OPT::bForceSparsePointCloud); } VERBOSE("Input data exported: %u images & %u vertices (%s)", scene.images.size(), scene.vertices.size(), TD_TIMER_GET_FMT().c_str()); } else { // read COLMAP input data Interface scene; const String strOutFolder(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); PointCloud pointcloud; if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene, pointcloud)) return EXIT_FAILURE; // write MVS input data Util::ensureFolder(strOutFolder); if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName))) return EXIT_FAILURE; if (!pointcloud.IsEmpty() && !pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)) + _T(".ply"), true)) return EXIT_FAILURE; VERBOSE("Exported data: %u images, %u points%s (%s)", scene.images.size(), scene.vertices.size(), pointcloud.IsEmpty()?"":String::FormatString(", %d dense points", pointcloud.GetSize()).c_str(), TD_TIMER_GET_FMT().c_str()); } return EXIT_SUCCESS; } /*----------------------------------------------------------------*/