You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1169 lines
42 KiB
1169 lines
42 KiB
/* |
|
* TextureMesh.cpp |
|
* |
|
* Copyright (c) 2014-2015 SEACAVE |
|
* |
|
* Author(s): |
|
* |
|
* cDc <cdc.seacave@gmail.com> |
|
* |
|
* |
|
* 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 <http://www.gnu.org/licenses/>. |
|
* |
|
* |
|
* 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 <boost/program_options.hpp> |
|
|
|
#include "../InterfaceCOLMAP/endian.h" |
|
|
|
using namespace MVS; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
#define APPNAME _T("TextureMesh") |
|
|
|
|
|
#define COLMAP_IMAGES_FOLDER _T("/images/") |
|
// #define COLMAP_SPARSE_FOLDER _T("/sparse/0/") |
|
#define COLMAP_CAMERAS_TXT _T("/cameras.txt") |
|
#define COLMAP_IMAGES_TXT _T("/images.txt") |
|
#define COLMAP_POINTS_TXT _T("/points3D.txt") |
|
#define COLMAP_CAMERAS_BIN _T("/cameras.bin") |
|
#define COLMAP_IMAGES_BIN _T("/images.bin") |
|
#define COLMAP_POINTS_BIN _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_PATCHMATCH COLMAP_STEREO_FOLDER _T("patch-match.cfg") |
|
#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 { |
|
String strInputFileName; |
|
String strMeshFileName; |
|
String strOutputFileName; |
|
String strViewsFileName; |
|
String strImageFolder; |
|
float fDecimateMesh; |
|
unsigned nCloseHoles; |
|
unsigned nResolutionLevel; |
|
unsigned nMinResolution; |
|
unsigned minCommonCameras; |
|
float fOutlierThreshold; |
|
float fRatioDataSmoothness; |
|
bool bGlobalSeamLeveling; |
|
bool bLocalSeamLeveling; |
|
bool bNormalizeIntrinsics; |
|
bool bOriginFaceview; |
|
unsigned nID; |
|
unsigned nTextureSizeMultiple; |
|
unsigned nRectPackingHeuristic; |
|
uint32_t nColEmpty; |
|
float fSharpnessWeight; |
|
int nIgnoreMaskLabel; |
|
unsigned nOrthoMapResolution; |
|
unsigned nArchiveType; |
|
int nProcessPriority; |
|
unsigned nMaxThreads; |
|
int nMaxTextureSize; |
|
String strExportType; |
|
String strConfigFileName; |
|
boost::program_options::variables_map vm; |
|
} // namespace OPT |
|
|
|
class Application { |
|
public: |
|
Application() {} |
|
~Application() { Finalize(); } |
|
|
|
bool Initialize(size_t argc, LPCTSTR* argv); |
|
void Finalize(); |
|
}; // Application |
|
|
|
// initialize and parse the command line parameters |
|
bool Application::Initialize(size_t argc, LPCTSTR* argv) |
|
{ |
|
// initialize log and console |
|
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", "produce this help message") |
|
("working-folder,w", boost::program_options::value<std::string>(&WORKING_FOLDER), "working directory (default current directory)") |
|
("config-file,c", boost::program_options::value<std::string>(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") |
|
("export-type", boost::program_options::value<std::string>(&OPT::strExportType)->default_value(_T("obj")), "file type used to export the 3D scene (ply, obj, glb or gltf)") |
|
("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 |
|
#ifdef _USE_CUDA |
|
("cuda-device", boost::program_options::value(&CUDA::desiredDeviceID)->default_value(-1), "CUDA device number to be used to texture the mesh (-2 - CPU processing, -1 - best GPU, >=0 - device index)") |
|
#endif |
|
; |
|
|
|
// group of options allowed both on command line and in config file |
|
boost::program_options::options_description config("Texture options"); |
|
config.add_options() |
|
("input-file,i", boost::program_options::value<std::string>(&OPT::strInputFileName), "input filename containing camera poses") |
|
("mesh-file,m", boost::program_options::value<std::string>(&OPT::strMeshFileName), "mesh file name to texture (overwrite existing mesh)") |
|
("output-file,o", boost::program_options::value<std::string>(&OPT::strOutputFileName), "output filename for storing the mesh") |
|
("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") |
|
("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") |
|
("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") |
|
("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") |
|
("outlier-threshold", boost::program_options::value(&OPT::fOutlierThreshold)->default_value(6e-2f), "threshold used to find and remove outlier face textures (0 - disabled)") |
|
("cost-smoothness-ratio", boost::program_options::value(&OPT::fRatioDataSmoothness)->default_value(0.1f), "ratio used to adjust the preference for more compact patches (1 - best quality/worst compactness, ~0 - worst quality/best compactness)") |
|
("virtual-face-images", boost::program_options::value(&OPT::minCommonCameras)->default_value(0), "generate texture patches using virtual faces composed of coplanar triangles sharing at least this number of views (0 - disabled, 3 - good value)") |
|
("global-seam-leveling", boost::program_options::value(&OPT::bGlobalSeamLeveling)->default_value(true), "generate uniform texture patches using global seam leveling") |
|
("local-seam-leveling", boost::program_options::value(&OPT::bLocalSeamLeveling)->default_value(true), "generate uniform texture patch borders using local seam leveling") |
|
("texture-size-multiple", boost::program_options::value(&OPT::nTextureSizeMultiple)->default_value(0), "texture size should be a multiple of this value (0 - power of two)") |
|
("patch-packing-heuristic", boost::program_options::value(&OPT::nRectPackingHeuristic)->default_value(3), "specify the heuristic used when deciding where to place a new patch (0 - best fit, 3 - good speed, 100 - best speed)") |
|
("empty-color", boost::program_options::value(&OPT::nColEmpty)->default_value(0x00FF7F27), "color used for faces not covered by any image") |
|
("sharpness-weight", boost::program_options::value(&OPT::fSharpnessWeight)->default_value(0.9f), "amount of sharpness to be applied on the texture (0 - disabled)") |
|
("orthographic-image-resolution", boost::program_options::value(&OPT::nOrthoMapResolution)->default_value(0), "orthographic image resolution to be generated from the textured mesh - the mesh is expected to be already geo-referenced or at least properly oriented (0 - disabled)") |
|
("ignore-mask-label", boost::program_options::value(&OPT::nIgnoreMaskLabel)->default_value(-1), "label value to ignore in the image mask, stored in the MVS scene or next to each image with '.mask.png' extension (-1 - auto estimate mask for lens distortion, -2 - disabled)") |
|
("max-texture-size", boost::program_options::value(&OPT::nMaxTextureSize)->default_value(8192), "maximum texture size, split it in multiple textures of this size if needed (0 - unbounded)") |
|
("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(false), "normalize intrinsics while exporting to MVS format") |
|
("image-folder", boost::program_options::value<std::string>(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") |
|
("origin-faceview", boost::program_options::value(&OPT::bOriginFaceview)->default_value(false), "use origin faceview selection") |
|
("id", boost::program_options::value(&OPT::nID)->default_value(0), "id") |
|
; |
|
|
|
// hidden options, allowed both on command line and |
|
// in config file, but will not be shown to the user |
|
boost::program_options::options_description hidden("Hidden options"); |
|
hidden.add_options() |
|
("views-file", boost::program_options::value<std::string>(&OPT::strViewsFileName), "file name containing the list of views to be used for texturing (optional)") |
|
; |
|
|
|
boost::program_options::options_description cmdline_options; |
|
cmdline_options.add(generic).add(config).add(hidden); |
|
|
|
boost::program_options::options_description config_file_options; |
|
config_file_options.add(config).add(hidden); |
|
|
|
boost::program_options::positional_options_description p; |
|
p.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(p).run(), OPT::vm); |
|
boost::program_options::notify(OPT::vm); |
|
INIT_WORKING_FOLDER; |
|
// parse configuration file |
|
std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); |
|
if (ifs) { |
|
boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); |
|
boost::program_options::notify(OPT::vm); |
|
} |
|
} |
|
catch (const std::exception& e) { |
|
LOG(e.what()); |
|
return false; |
|
} |
|
|
|
// initialize the log file |
|
OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-")+Util::getUniqueName(0)+_T(".log")).c_str()); |
|
|
|
// print application details: version and command line |
|
Util::LogBuild(); |
|
LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); |
|
|
|
// validate input |
|
Util::ensureValidPath(OPT::strInputFileName); |
|
if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { |
|
boost::program_options::options_description visible("Available options"); |
|
visible.add(generic).add(config); |
|
GET_LOG() << visible; |
|
} |
|
if (OPT::strInputFileName.empty()) |
|
return false; |
|
OPT::strExportType = OPT::strExportType.ToLower(); |
|
if (OPT::strExportType == _T("obj")) |
|
OPT::strExportType = _T(".obj"); |
|
else |
|
if (OPT::strExportType == _T("glb")) |
|
OPT::strExportType = _T(".glb"); |
|
else |
|
if (OPT::strExportType == _T("gltf")) |
|
OPT::strExportType = _T(".gltf"); |
|
else |
|
OPT::strExportType = _T(".ply"); |
|
|
|
// initialize optional options |
|
Util::ensureValidPath(OPT::strMeshFileName); |
|
Util::ensureValidPath(OPT::strOutputFileName); |
|
Util::ensureValidPath(OPT::strViewsFileName); |
|
if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) |
|
OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); |
|
if (OPT::strOutputFileName.empty()) |
|
OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_texture.mvs"); |
|
|
|
MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); |
|
return true; |
|
} |
|
|
|
// finalize application instance |
|
void Application::Finalize() |
|
{ |
|
MVS::Finalize(); |
|
|
|
CLOSE_LOGFILE(); |
|
CLOSE_LOGCONSOLE(); |
|
CLOSE_LOG(); |
|
} |
|
|
|
} // unnamed namespace |
|
|
|
IIndexArr ParseViewsFile(const String& filename, const Scene& scene) { |
|
IIndexArr views; |
|
std::ifstream file(filename); |
|
printf("filename: %s\n", filename.c_str()); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open views file '%s'", filename.c_str()); |
|
return views; |
|
} |
|
while (true) { |
|
String imageName; |
|
std::getline(file, imageName); |
|
if (file.fail() || imageName.empty()) |
|
break; |
|
LPTSTR endIdx; |
|
const IDX idx(strtoul(imageName, &endIdx, 10)); |
|
const size_t szIndex(*endIdx == '\0' ? size_t(0) : imageName.size()); |
|
FOREACH(idxImage, scene.images) { |
|
const Image& image = scene.images[idxImage]; |
|
if (szIndex == 0) { |
|
// try to match by index |
|
if (image.ID != idx) |
|
continue; |
|
} else { |
|
// try to match by file name |
|
const String name(Util::getFileNameExt(image.name)); |
|
if (name.size() < szIndex || _tcsnicmp(name, imageName, szIndex) != 0) |
|
continue; |
|
} |
|
views.emplace_back(idxImage); |
|
break; |
|
} |
|
} |
|
return views; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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<String> 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<REAL> 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<String>()(camera.model)); |
|
const size_t h2(std::hash<uint32_t>()(camera.width)); |
|
const size_t h3(std::hash<uint32_t>()(camera.height)); |
|
size_t h(h1 ^ ((h2 ^ (h3 << 1)) << 1)); |
|
for (REAL p: camera.params) |
|
h = std::hash<REAL>()(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<uint64_t>(&stream); |
|
} |
|
|
|
ID = ReadBinaryLittleEndian<camera_t>(&stream)-1; |
|
model = mapCameraModel[ReadBinaryLittleEndian<int>(&stream)]; |
|
width = (uint32_t)ReadBinaryLittleEndian<uint64_t>(&stream); |
|
height = (uint32_t)ReadBinaryLittleEndian<uint64_t>(&stream); |
|
if (model != _T("PINHOLE")) |
|
return false; |
|
params.resize(4); |
|
ReadBinaryLittleEndian<double>(&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<uint64_t>(&stream, numCameras); |
|
numCameras = 0; |
|
} |
|
|
|
WriteBinaryLittleEndian<camera_t>(&stream, ID+1); |
|
const int64 modelId(std::distance(mapCameraModel.begin(), std::find(mapCameraModel.begin(), mapCameraModel.end(), model))); |
|
WriteBinaryLittleEndian<int>(&stream, (int)modelId); |
|
WriteBinaryLittleEndian<uint64_t>(&stream, width); |
|
WriteBinaryLittleEndian<uint64_t>(&stream, height); |
|
for (REAL param: params) |
|
WriteBinaryLittleEndian<double>(&stream, param); |
|
return !stream.fail(); |
|
} |
|
}; |
|
typedef std::vector<Camera> 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<Proj> 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<uint64_t>(&stream); |
|
} |
|
|
|
ID = ReadBinaryLittleEndian<image_t>(&stream)-1; |
|
q.w() = ReadBinaryLittleEndian<double>(&stream); |
|
q.x() = ReadBinaryLittleEndian<double>(&stream); |
|
q.y() = ReadBinaryLittleEndian<double>(&stream); |
|
q.z() = ReadBinaryLittleEndian<double>(&stream); |
|
t(0) = ReadBinaryLittleEndian<double>(&stream); |
|
t(1) = ReadBinaryLittleEndian<double>(&stream); |
|
t(2) = ReadBinaryLittleEndian<double>(&stream); |
|
idCamera = ReadBinaryLittleEndian<camera_t>(&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<uint64_t>(&stream); |
|
projs.clear(); |
|
for (size_t j = 0; j < numPoints2D; ++j) { |
|
Proj proj; |
|
proj.p(0) = (float)ReadBinaryLittleEndian<double>(&stream); |
|
proj.p(1) = (float)ReadBinaryLittleEndian<double>(&stream); |
|
proj.idPoint = (uint32_t)ReadBinaryLittleEndian<point3D_t>(&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<uint64_t>(&stream, numRegImages); |
|
numRegImages = 0; |
|
} |
|
|
|
WriteBinaryLittleEndian<image_t>(&stream, ID+1); |
|
|
|
WriteBinaryLittleEndian<double>(&stream, q.w()); |
|
WriteBinaryLittleEndian<double>(&stream, q.x()); |
|
WriteBinaryLittleEndian<double>(&stream, q.y()); |
|
WriteBinaryLittleEndian<double>(&stream, q.z()); |
|
|
|
WriteBinaryLittleEndian<double>(&stream, t(0)); |
|
WriteBinaryLittleEndian<double>(&stream, t(1)); |
|
WriteBinaryLittleEndian<double>(&stream, t(2)); |
|
|
|
WriteBinaryLittleEndian<camera_t>(&stream, idCamera+1); |
|
|
|
stream.write(name.c_str(), name.size()+1); |
|
|
|
WriteBinaryLittleEndian<uint64_t>(&stream, projs.size()); |
|
for (const Proj& proj: projs) { |
|
WriteBinaryLittleEndian<double>(&stream, proj.p(0)); |
|
WriteBinaryLittleEndian<double>(&stream, proj.p(1)); |
|
WriteBinaryLittleEndian<point3D_t>(&stream, proj.idPoint+1); |
|
} |
|
return !stream.fail(); |
|
} |
|
}; |
|
typedef std::vector<Image> 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<Track> 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<uint64_t>(&stream); |
|
} |
|
|
|
int r,g,b; |
|
ID = (uint32_t)ReadBinaryLittleEndian<point3D_t>(&stream)-1; |
|
p.x = (float)ReadBinaryLittleEndian<double>(&stream); |
|
p.y = (float)ReadBinaryLittleEndian<double>(&stream); |
|
p.z = (float)ReadBinaryLittleEndian<double>(&stream); |
|
r = ReadBinaryLittleEndian<uint8_t>(&stream); |
|
g = ReadBinaryLittleEndian<uint8_t>(&stream); |
|
b = ReadBinaryLittleEndian<uint8_t>(&stream); |
|
e = (float)ReadBinaryLittleEndian<double>(&stream); |
|
c.x = CLAMP(b,0,255); |
|
c.y = CLAMP(g,0,255); |
|
c.z = CLAMP(r,0,255); |
|
|
|
const size_t trackLength = ReadBinaryLittleEndian<uint64_t>(&stream); |
|
tracks.clear(); |
|
for (size_t j = 0; j < trackLength; ++j) { |
|
Track track; |
|
track.idImage = ReadBinaryLittleEndian<image_t>(&stream)-1; |
|
track.idProj = ReadBinaryLittleEndian<point2D_t>(&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<uint64_t>(&stream, numPoints3D); |
|
numPoints3D = 0; |
|
} |
|
|
|
WriteBinaryLittleEndian<point3D_t>(&stream, ID+1); |
|
WriteBinaryLittleEndian<double>(&stream, p.x); |
|
WriteBinaryLittleEndian<double>(&stream, p.y); |
|
WriteBinaryLittleEndian<double>(&stream, p.z); |
|
WriteBinaryLittleEndian<uint8_t>(&stream, c.z); |
|
WriteBinaryLittleEndian<uint8_t>(&stream, c.y); |
|
WriteBinaryLittleEndian<uint8_t>(&stream, c.x); |
|
WriteBinaryLittleEndian<double>(&stream, e); |
|
|
|
WriteBinaryLittleEndian<uint64_t>(&stream, tracks.size()); |
|
for (const Track& track: tracks) { |
|
WriteBinaryLittleEndian<image_t>(&stream, track.idImage+1); |
|
WriteBinaryLittleEndian<point2D_t>(&stream, track.idProj+1); |
|
} |
|
return !stream.fail(); |
|
} |
|
}; |
|
typedef std::vector<Point> Points; |
|
// structure describing an 2D dynamic matrix |
|
template <typename T> |
|
struct Mat { |
|
size_t width_ = 0; |
|
size_t height_ = 0; |
|
size_t depth_ = 0; |
|
std::vector<T> 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<T>(&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<T>(&binary_file, data_); |
|
} |
|
}; |
|
} // namespace COLMAP |
|
|
|
typedef Eigen::Matrix<double,3,3,Eigen::RowMajor> EMat33d; |
|
typedef Eigen::Matrix<double,3,1> EVec3d; |
|
|
|
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<uint32_t,uint32_t> 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<COLMAP::Camera,uint32_t,COLMAP::Camera::CameraHash,COLMAP::Camera::CameraEqualTo> 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<double>(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<COLMAP::Image, uint32_t> 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<EMat33d>(pose.R.val) = imageColmap.q.toRotationMatrix(); |
|
EnsureRotationMatrix((Matrix3x3d&)pose.R); |
|
Eigen::Map<EVec3d>(&pose.C.x) = -(imageColmap.q.inverse() * imageColmap.t); |
|
|
|
Interface::Image image; |
|
// Start Generation Here |
|
if (!OPT::strImageFolder.empty() && OPT::strImageFolder.back() != '/') |
|
OPT::strImageFolder += '/'; |
|
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); |
|
printf("Don't read any cloud points\n"); |
|
// { |
|
// // 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; |
|
|
|
// // Process each point in the file |
|
// COLMAP::Point point; |
|
// while (file.good() && point.Read(file, binary)) { |
|
// Interface::Vertex vertex; |
|
// vertex.X = point.p; // Assign 3D coordinates to vertex |
|
|
|
// // Process each track associated with the point |
|
// for (const COLMAP::Point::Track& track: point.tracks) { |
|
// Interface::Vertex::View view; |
|
// view.imageID = mapImages.at(COLMAP::Image(track.0)); // Map COLMAP image ID to internal image ID |
|
// view.confidence = 0; |
|
// vertex.views.emplace_back(view); |
|
// } |
|
|
|
// // Ensure views are sorted by image ID to maintain consistency |
|
// 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; i<numPoints; ++i) { |
|
PointCloud::ViewArr& views = pointcloud.pointViews[i]; |
|
uint32_t numViews(0); |
|
file.read(&numViews, sizeof(uint32_t)); |
|
for (uint32_t v=0; v<numViews; ++v) { |
|
uint32_t imageID; |
|
file.read(&imageID, sizeof(uint32_t)); |
|
views.emplace_back(imageID); |
|
} |
|
views.Sort(); |
|
} |
|
} |
|
|
|
// read depth-maps |
|
const String pathDepthMaps(inputFolder+COLMAP_STEREO_DEPTHMAPS_FOLDER); |
|
const String pathNormalMaps(inputFolder+COLMAP_STEREO_NORMALMAPS_FOLDER); |
|
if (File::isFolder(pathDepthMaps) && File::isFolder(pathNormalMaps)) { |
|
// read patch-match list |
|
CLISTDEF2IDX(IIndexArr,IIndex) imagesNeighbors((IIndex)scene.images.size()); |
|
{ |
|
const String filenameFusion(inputFolder+COLMAP_PATCHMATCH); |
|
LOG_OUT() << "Reading patch-match configuration: " << filenameFusion << std::endl; |
|
std::ifstream file(filenameFusion); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); |
|
return false; |
|
} |
|
while (true) { |
|
String imageName, neighbors; |
|
std::getline(file, imageName); |
|
std::getline(file, neighbors); |
|
if (file.fail() || imageName.empty() || neighbors.empty()) |
|
break; |
|
const ImagesMap::const_iterator it_image = std::find_if(mapImages.begin(), mapImages.end(), |
|
[&imageName](const ImagesMap::value_type& image) { |
|
return image.first.name == imageName; |
|
}); |
|
if (it_image == mapImages.end()) |
|
continue; |
|
IIndexArr& imageNeighbors = imagesNeighbors[it_image->second]; |
|
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<float> 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>{ |
|
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 checkFileExistence(const std::string& filename) { |
|
std::ifstream file(filename); |
|
return file.good(); |
|
} |
|
|
|
int main(int argc, LPCTSTR* argv) |
|
{ |
|
#ifdef _DEBUGINFO |
|
// set _crtBreakAlloc index to stop in <dbgheap.c> at allocation |
|
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); |
|
#endif |
|
|
|
Application application; |
|
if (!application.Initialize(argc, argv)) |
|
return EXIT_FAILURE; |
|
|
|
if (1) { |
|
if (checkFileExistence(MAKE_PATH_SAFE(OPT::strOutputFileName).c_str())) { |
|
std::cout << "The file exists." << std::endl; |
|
} else { |
|
std::cout << "The file does not exist." << std::endl; |
|
} |
|
|
|
// read COLMAP input data |
|
Interface scene; |
|
const String strOutFolder(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); |
|
printf("Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)): %s\n", Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)).c_str()); |
|
PointCloud pointcloud; |
|
if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene, pointcloud)) |
|
return EXIT_FAILURE; |
|
printf("###########\n"); |
|
// write MVS input data |
|
Util::ensureFolder(strOutFolder); |
|
if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName))) |
|
return EXIT_FAILURE; |
|
printf("MAKE_PATH_SAFE(OPT::strOutputFileName): %s\n", MAKE_PATH_SAFE(OPT::strOutputFileName).c_str()); |
|
|
|
|
|
if (checkFileExistence(MAKE_PATH_SAFE(OPT::strOutputFileName).c_str())) { |
|
std::cout << "The file exists." << std::endl; |
|
} else { |
|
std::cout << "The file does not exist." << std::endl; |
|
} |
|
} |
|
|
|
Scene scene(OPT::nMaxThreads); |
|
printf("OPT::strViewsFileName: %s\n", OPT::strViewsFileName.c_str()); |
|
printf("MAKE_PATH_SAFE(OPT::strViewsFileName): %s\n", MAKE_PATH_SAFE(OPT::strViewsFileName).c_str()); |
|
// load and texture the mesh |
|
printf("MAKE_PATH_SAFE(OPT::strOutputFileName): %s\n", MAKE_PATH_SAFE(OPT::strOutputFileName).c_str()); |
|
const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strOutputFileName))); |
|
if (sceneType == Scene::SCENE_NA) |
|
return EXIT_FAILURE; |
|
printf("Now\n"); |
|
VERBOSE(MAKE_PATH_SAFE(OPT::strMeshFileName)); |
|
if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { |
|
VERBOSE("error: cannot load mesh file"); |
|
return EXIT_FAILURE; |
|
} |
|
if (scene.mesh.IsEmpty()) { |
|
VERBOSE("error: empty initial mesh"); |
|
return EXIT_FAILURE; |
|
} |
|
const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); |
|
if (OPT::nOrthoMapResolution && !scene.mesh.HasTexture()) { |
|
// the input mesh is already textured and an orthographic projection was requested |
|
goto ProjectOrtho; |
|
} |
|
|
|
{ |
|
// decimate to the desired resolution |
|
if (OPT::fDecimateMesh < 1.f) { |
|
ASSERT(OPT::fDecimateMesh > 0.f); |
|
scene.mesh.Clean(OPT::fDecimateMesh, 0.f, false, OPT::nCloseHoles, 0u, 0.f, false); |
|
scene.mesh.Clean(1.f, 0.f, false, 0u, 0u, 0.f, true); // extra cleaning to remove non-manifold problems created by closing holes |
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
if (VERBOSITY_LEVEL > 3) |
|
scene.mesh.Save(baseFileName +_T("_decim")+OPT::strExportType); |
|
#endif |
|
} |
|
// fetch list of views to be used for texturing |
|
IIndexArr views; |
|
// VERBOSE(MAKE_PATH_SAFE(OPT::strViewsFileName)); |
|
printf("MAKE_PATH_SAFE(OPT::strViewsFileName): %s\n", MAKE_PATH_SAFE(OPT::strViewsFileName).c_str()); |
|
if (!OPT::strViewsFileName.empty()) |
|
views = ParseViewsFile(MAKE_PATH_SAFE(OPT::strViewsFileName), scene); |
|
printf("Second\n"); |
|
// compute mesh texture |
|
TD_TIMER_START(); |
|
if (!scene.TextureMesh(OPT::nResolutionLevel, OPT::nMinResolution, OPT::minCommonCameras, OPT::fOutlierThreshold, OPT::fRatioDataSmoothness, |
|
OPT::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty), |
|
OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, OPT::nMaxTextureSize, views, baseFileName, OPT::bOriginFaceview, |
|
OPT::strInputFileName, OPT::strMeshFileName)) |
|
return EXIT_FAILURE; |
|
VERBOSE("Mesh texturing completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
// save the final mesh |
|
scene.mesh.Save(baseFileName+OPT::strExportType); |
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
if (VERBOSITY_LEVEL > 2) |
|
scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); |
|
#endif |
|
if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) |
|
scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); |
|
} |
|
|
|
if (OPT::nOrthoMapResolution) { |
|
// project mesh as an orthographic image |
|
ProjectOrtho: |
|
Image8U3 imageRGB; |
|
Image8U imageRGBA[4]; |
|
Point3 center; |
|
scene.mesh.ProjectOrthoTopDown(OPT::nOrthoMapResolution, imageRGB, imageRGBA[3], center); |
|
Image8U4 image; |
|
cv::split(imageRGB, imageRGBA); |
|
cv::merge(imageRGBA, 4, image); |
|
image.Save(baseFileName+_T("_orthomap.png")); |
|
SML sml(_T("OrthoMap")); |
|
sml[_T("Center")].val = String::FormatString(_T("%g %g %g"), center.x, center.y, center.z); |
|
sml.Save(baseFileName+_T("_orthomap.txt")); |
|
} |
|
|
|
return EXIT_SUCCESS; |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|