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.
706 lines
23 KiB
706 lines
23 KiB
/* |
|
* InterfaceMVSNet.cpp |
|
* |
|
* Copyright (c) 2014-2021 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" |
|
#define JSON_NOEXCEPTION |
|
#include "../../libs/IO/json.hpp" |
|
#include <boost/program_options.hpp> |
|
|
|
using namespace MVS; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
// uncomment to enable multi-threading based on OpenMP |
|
#ifdef _USE_OPENMP |
|
#define MVSNET_USE_OPENMP |
|
#endif |
|
|
|
#define APPNAME _T("InterfaceMVSNet") |
|
#define MVS_FILE_EXTENSION _T(".mvs") |
|
#define MVSNET_IMAGES_FOLDER _T("images") |
|
#define MVSNET_CAMERAS_FOLDER _T("cams") |
|
#define MVSNET_IMAGES_EXT _T(".jpg") |
|
#define MVSNET_CAMERAS_NAME _T("_cam.txt") |
|
#define RTMV_CAMERAS_EXT _T(".json") |
|
#define NERFSTUDIO_TRANSFORMS _T("transforms.json") |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
namespace { |
|
|
|
namespace OPT { |
|
String strInputFileName; |
|
String strOutputFileName; |
|
unsigned nArchiveType; |
|
int nProcessPriority; |
|
unsigned nMaxThreads; |
|
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") |
|
("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 config("Main options"); |
|
config.add_options() |
|
("input-file,i", boost::program_options::value<std::string>(&OPT::strInputFileName), "input filename containing camera poses and image list") |
|
("output-file,o", boost::program_options::value<std::string>(&OPT::strOutputFileName), "output filename for storing the mesh") |
|
; |
|
|
|
boost::program_options::options_description cmdline_options; |
|
cmdline_options.add(generic).add(config); |
|
|
|
boost::program_options::options_description config_file_options; |
|
config_file_options.add(config); |
|
|
|
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); |
|
const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); |
|
const bool bInvalidCommand(OPT::strInputFileName.empty()); |
|
if (OPT::vm.count("help") || bInvalidCommand) { |
|
boost::program_options::options_description visible("Available options"); |
|
visible.add(generic).add(config); |
|
GET_LOG() << visible; |
|
} |
|
if (bInvalidCommand) |
|
return false; |
|
|
|
// initialize optional options |
|
Util::ensureValidPath(OPT::strOutputFileName); |
|
if (OPT::strOutputFileName.empty()) |
|
OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + "scene" MVS_FILE_EXTENSION; |
|
|
|
MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); |
|
return true; |
|
} |
|
|
|
// finalize application instance |
|
void Application::Finalize() |
|
{ |
|
MVS::Finalize(); |
|
|
|
CLOSE_LOGFILE(); |
|
CLOSE_LOGCONSOLE(); |
|
CLOSE_LOG(); |
|
} |
|
|
|
void ImageListParse(const LPSTR* argv, Point3& C) |
|
{ |
|
// read position vector |
|
C.x = String::FromString<REAL>(argv[0]); |
|
C.y = String::FromString<REAL>(argv[1]); |
|
C.z = String::FromString<REAL>(argv[2]); |
|
} |
|
|
|
void ImageListParse(const LPSTR* argv, Matrix3x3& R) |
|
{ |
|
// read rotation matrix |
|
R(0, 0) = String::FromString<REAL>(argv[0]); |
|
R(0, 1) = String::FromString<REAL>(argv[1]); |
|
R(0, 2) = String::FromString<REAL>(argv[2]); |
|
R(1, 0) = String::FromString<REAL>(argv[3]); |
|
R(1, 1) = String::FromString<REAL>(argv[4]); |
|
R(1, 2) = String::FromString<REAL>(argv[5]); |
|
R(2, 0) = String::FromString<REAL>(argv[6]); |
|
R(2, 1) = String::FromString<REAL>(argv[7]); |
|
R(2, 2) = String::FromString<REAL>(argv[8]); |
|
} |
|
|
|
void ImageListParse(const LPSTR* argv, Matrix3x4& P) |
|
{ |
|
// read projection matrix |
|
P(0, 0) = String::FromString<REAL>(argv[0]); |
|
P(0, 1) = String::FromString<REAL>(argv[1]); |
|
P(0, 2) = String::FromString<REAL>(argv[2]); |
|
P(0, 3) = String::FromString<REAL>(argv[3]); |
|
P(1, 0) = String::FromString<REAL>(argv[4]); |
|
P(1, 1) = String::FromString<REAL>(argv[5]); |
|
P(1, 2) = String::FromString<REAL>(argv[6]); |
|
P(1, 3) = String::FromString<REAL>(argv[7]); |
|
P(2, 0) = String::FromString<REAL>(argv[8]); |
|
P(2, 1) = String::FromString<REAL>(argv[9]); |
|
P(2, 2) = String::FromString<REAL>(argv[10]); |
|
P(2, 3) = String::FromString<REAL>(argv[11]); |
|
} |
|
|
|
// convert a range-map to depth-map |
|
void RangeToDepthMap(const Image32F& rangeMap, const Camera& camera, DepthMap& depthMap) |
|
{ |
|
depthMap.create(rangeMap.size()); |
|
for (int y = 0; y < depthMap.rows; ++y) { |
|
const float* const rangeRow = rangeMap.ptr<float>(y); |
|
float* const depthRow = depthMap.ptr<float>(y); |
|
for (int x = 0; x < depthMap.cols; ++x) { |
|
const float range = rangeRow[x]; |
|
depthRow[x] = (Depth)(range <= 0 ? 0 : normalized(camera.TransformPointI2C(Point2(x,y))).z*range); |
|
} |
|
} |
|
} |
|
|
|
// parse scene stored in MVSNet format composed of undistorted images and camera poses |
|
// for example see GigaVision benchmark (http://gigamvs.net): |
|
// |--sceneX |
|
// |--images |
|
// |--xxx.jpg |
|
// |--xxx.jpg |
|
// .... |
|
// |--cams |
|
// |--xxx_cam.txt |
|
// |--xxx_cam.txt |
|
// .... |
|
// |--render_cams |
|
// |--xxx_cam.txt |
|
// |--xxx_cam.txt |
|
// .... |
|
// |
|
// where the camera parameter of one image stored in a cam.txt file contains the camera |
|
// extrinsic E = [R|t], intrinsic K and the depth range: |
|
// extrinsic |
|
// E00 E01 E02 E03 |
|
// E10 E11 E12 E13 |
|
// E20 E21 E22 E23 |
|
// E30 E31 E32 E33 |
|
// |
|
// intrinsic |
|
// K00 K01 K02 |
|
// K10 K11 K12 |
|
// K20 K21 K22 |
|
// |
|
// DEPTH_MIN DEPTH_INTERVAL (DEPTH_NUM DEPTH_MAX) |
|
bool ParseSceneMVSNet(Scene& scene, const String& strPath) |
|
{ |
|
#if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) |
|
IIndex prevPlatformID = NO_ID; |
|
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator((strPath + MVSNET_IMAGES_FOLDER).c_str())) { |
|
if (entry.path().extension() != MVSNET_IMAGES_EXT) |
|
continue; |
|
// parse camera |
|
const std::string strCamFileName(strPath + MVSNET_CAMERAS_FOLDER PATH_SEPARATOR_STR + entry.path().stem().string().c_str() + MVSNET_CAMERAS_NAME); |
|
std::ifstream fcam(strCamFileName); |
|
if (!fcam) |
|
continue; |
|
String line; |
|
do { |
|
std::getline(fcam, line); |
|
} while (line != "extrinsic" && fcam.good()); |
|
String strP; |
|
for (int i = 0; i < 3; ++i) { |
|
std::getline(fcam, line); |
|
strP += ' ' + line; |
|
} |
|
if (!fcam.good()) |
|
continue; |
|
size_t argc; |
|
CAutoPtrArr<LPSTR> argv; |
|
argv = Util::CommandLineToArgvA(strP, argc); |
|
if (argc != 12) |
|
continue; |
|
Matrix3x4 P; |
|
ImageListParse(argv, P); |
|
do { |
|
std::getline(fcam, line); |
|
} while (line != "intrinsic" && fcam.good()); |
|
strP.clear(); |
|
for (int i = 0; i < 3; ++i) { |
|
std::getline(fcam, line); |
|
strP += ' ' + line; |
|
} |
|
if (!fcam.good()) |
|
continue; |
|
argv = Util::CommandLineToArgvA(strP, argc); |
|
if (argc != 9) |
|
continue; |
|
Matrix3x3 K; |
|
ImageListParse(argv, K); |
|
// setup camera |
|
IIndex platformID; |
|
if (prevPlatformID == NO_ID || !K.IsEqual(scene.platforms[prevPlatformID].cameras[0].K, 1e-3)) { |
|
prevPlatformID = platformID = scene.platforms.size(); |
|
Platform& platform = scene.platforms.emplace_back(); |
|
Platform::Camera& camera = platform.cameras.emplace_back(); |
|
camera.K = K; |
|
camera.R = RMatrix::IDENTITY; |
|
camera.C = CMatrix::ZERO; |
|
} else { |
|
platformID = prevPlatformID; |
|
} |
|
Platform& platform = scene.platforms[platformID]; |
|
// setup image |
|
const IIndex ID = scene.images.size(); |
|
Image& imageData = scene.images.emplace_back(); |
|
imageData.platformID = platformID; |
|
imageData.cameraID = 0; // only one camera per platform supported by this format |
|
imageData.poseID = NO_ID; |
|
imageData.ID = ID; |
|
imageData.name = entry.path().string(); |
|
Util::ensureUnifySlash(imageData.name); |
|
imageData.name = MAKE_PATH_FULL(strPath, imageData.name); |
|
// set image resolution |
|
IMAGEPTR pimage = Image::ReadImageHeader(imageData.name); |
|
imageData.width = pimage->GetWidth(); |
|
imageData.height = pimage->GetHeight(); |
|
imageData.scale = 1; |
|
// set camera pose |
|
imageData.poseID = platform.poses.size(); |
|
Platform::Pose& pose = platform.poses.emplace_back(); |
|
DecomposeProjectionMatrix(P, pose.R, pose.C); |
|
imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); |
|
} |
|
if (scene.images.size() < 2) |
|
return false; |
|
scene.nCalibratedImages = (unsigned)scene.images.size(); |
|
return true; |
|
#else |
|
VERBOSE("error: C++17 is required to parse MVSNet format"); |
|
return false; |
|
#endif // _SUPPORT_CPP17 |
|
} |
|
|
|
// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv |
|
// |--sceneX |
|
// |--images |
|
// |--xxx.jpg |
|
// |--xxx.jpg |
|
// .... |
|
// |--outputs (optional) |
|
// |--depthxxxx.exr |
|
// |--normalxxxx.exr |
|
// .... |
|
// |--transforms.json |
|
bool ParseSceneNerfstudio(Scene& scene, const String& strPath) |
|
{ |
|
const nlohmann::json data = nlohmann::json::parse(std::ifstream(strPath + NERFSTUDIO_TRANSFORMS)); |
|
if (data.empty()) |
|
return false; |
|
// parse camera |
|
const cv::Size resolution(data["w"].get<uint32_t>(), data["h"].get<uint32_t>()); |
|
const IIndex platformID = scene.platforms.size(); |
|
Platform& platform = scene.platforms.emplace_back(); |
|
Platform::Camera& camera = platform.cameras.emplace_back(); |
|
camera.K = KMatrix::IDENTITY; |
|
camera.R = RMatrix::IDENTITY; |
|
camera.C = CMatrix::ZERO; |
|
camera.K(0,0) = data["fl_x"].get<REAL>(); |
|
camera.K(1,1) = data["fl_y"].get<REAL>(); |
|
camera.K(0,2) = data["cx"].get<REAL>(); |
|
camera.K(1,2) = data["cy"].get<REAL>(); |
|
const String cameraModel = data["camera_model"].get<std::string>(); |
|
if (cameraModel == "SIMPLE_PINHOLE") { |
|
} else |
|
// check ZERO radial distortion for all "PERSPECTIVE" type cameras |
|
if (cameraModel == "PINHOLE" || cameraModel == "SIMPLE_RADIAL" || cameraModel == "RADIAL" || cameraModel == "OPENCV") { |
|
const REAL k1 = data["k1"].get<REAL>(); |
|
const REAL k2 = data["k2"].get<REAL>(); |
|
const REAL p1 = data["p1"].get<REAL>(); |
|
const REAL p2 = data["p2"].get<REAL>(); |
|
if (k1 != 0 || k2 != 0 || p1 != 0 || p2 != 0) { |
|
VERBOSE("error: radial distortion not supported"); |
|
return false; |
|
} |
|
} else { |
|
VERBOSE("error: camera model not supported"); |
|
return false; |
|
} |
|
// parse images |
|
const nlohmann::json& frames = data["frames"]; |
|
for (const nlohmann::json& frame: frames) { |
|
// set image |
|
// frames expected to be ordered in JSON |
|
const IIndex imageID = scene.images.size(); |
|
const String strFileName(strPath + frame["file_path"].get<std::string>().c_str()); |
|
Image& imageData = scene.images.emplace_back(); |
|
imageData.platformID = platformID; |
|
imageData.cameraID = 0; // only one camera per platform supported by this format |
|
imageData.poseID = NO_ID; |
|
imageData.ID = imageID; |
|
imageData.name = strFileName; |
|
ASSERT(Util::isFullPath(imageData.name)); |
|
// set image resolution |
|
imageData.width = resolution.width; |
|
imageData.height = resolution.height; |
|
imageData.scale = 1; |
|
// load camera pose |
|
imageData.poseID = platform.poses.size(); |
|
Platform::Pose& pose = platform.poses.emplace_back(); |
|
const auto Ps = frame["transform_matrix"].get<std::vector<std::vector<double>>>(); |
|
Eigen::Matrix4d P{ |
|
{Ps[0][0], Ps[0][1], Ps[0][2], Ps[0][3]}, |
|
{Ps[1][0], Ps[1][1], Ps[1][2], Ps[1][3]}, |
|
{Ps[2][0], Ps[2][1], Ps[2][2], Ps[2][3]}, |
|
{Ps[3][0], Ps[3][1], Ps[3][2], Ps[3][3]} |
|
}; |
|
// revert nerfstudio conversion: |
|
// convert from COLMAP's camera coordinate system (OpenCV) to ours (OpenGL) |
|
// c2w[0:3, 1:3] *= -1 |
|
// c2w = c2w[np.array([1, 0, 2, 3]), :] |
|
// c2w[2, :] *= -1 |
|
P.row(2) *= -1; |
|
P.row(0).swap(P.row(1)); |
|
P.col(2) *= -1; |
|
P.col(1) *= -1; |
|
// set camera pose |
|
pose.R = P.topLeftCorner<3, 3>().transpose().eval(); |
|
pose.R.EnforceOrthogonality(); |
|
pose.C = P.topRightCorner<3, 1>().eval(); |
|
imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); |
|
// try reading depth-map and normal-map |
|
DepthMap depthMap; { |
|
const String depthPath(strPath + String::FormatString("outputs/depth%04u.exr", imageID)); |
|
const Image32F rangeMap = cv::imread(depthPath, cv::IMREAD_UNCHANGED); |
|
if (rangeMap.empty()) { |
|
VERBOSE("Unable to load depthmap %s.", depthPath.c_str()); |
|
continue; |
|
} |
|
RangeToDepthMap(rangeMap, imageData.camera, depthMap); |
|
} |
|
NormalMap normalMap; { |
|
const String normalPath(strPath + String::FormatString("outputs/normal%04u.exr", imageID)); |
|
normalMap = cv::imread(normalPath, cv::IMREAD_UNCHANGED); |
|
if (normalMap.empty()) { |
|
VERBOSE("Unable to load normalMap %s.", normalPath.c_str()); |
|
continue; |
|
} |
|
} |
|
const ConfidenceMap confMap; |
|
const ViewsMap viewsMap; |
|
const IIndexArr IDs = {imageID}; |
|
double dMin, dMax; |
|
cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); |
|
const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); |
|
if (!ExportDepthDataRaw(dmapPath, |
|
imageData.name, IDs, resolution, |
|
camera.K, pose.R, pose.C, |
|
(float)dMin, (float)dMax, |
|
depthMap, normalMap, confMap, viewsMap)) |
|
{ |
|
VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); |
|
continue; |
|
} |
|
} |
|
if (scene.images.size() < 2) |
|
return false; |
|
scene.nCalibratedImages = (unsigned)scene.images.size(); |
|
return true; |
|
} |
|
|
|
// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv |
|
// |--sceneX |
|
// |--xxx.exr |
|
// |--xxx.seg.exr |
|
// |--xxx.depth.exr |
|
// |--xxx.json |
|
// .... |
|
bool ParseSceneRTMV(Scene& scene, const String& strPath) |
|
{ |
|
const String strImagePath(strPath + "images/"); |
|
Util::ensureFolder(strImagePath); |
|
std::vector<String> strImageNames; |
|
#if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) |
|
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(strPath.c_str())) { |
|
if (entry.path().extension() != RTMV_CAMERAS_EXT) |
|
continue; |
|
strImageNames.emplace_back(entry.path().stem().string()); |
|
} |
|
#else |
|
VERBOSE("error: C++17 is required to parse RTMV format"); |
|
return false; |
|
#endif // _SUPPORT_CPP17 |
|
IIndex prevPlatformID = NO_ID; |
|
scene.images.resize((IIndex)strImageNames.size()); |
|
scene.platforms.reserve((IIndex)strImageNames.size()); |
|
#ifdef MVSNET_USE_OPENMP |
|
#pragma omp parallel for schedule(dynamic) |
|
for (int_t i=0; i<(int_t)strImageNames.size(); ++i) { |
|
#else |
|
FOREACH(i, strImageNames) { |
|
#endif |
|
const IIndex imageID((IIndex)i); |
|
const String& strImageName(strImageNames[imageID]); |
|
// parse camera |
|
const String strFileName(strPath + strImageName); |
|
const nlohmann::json dataCamera = nlohmann::json::parse(std::ifstream(strFileName+RTMV_CAMERAS_EXT)); |
|
if (dataCamera.empty()) |
|
continue; |
|
const nlohmann::json& data = dataCamera["camera_data"]; |
|
const cv::Size resolution(data["width"].get<uint32_t>(), data["height"].get<uint32_t>()); |
|
// set platform |
|
Matrix3x3 K = Matrix3x3::IDENTITY; |
|
K(0,0) = data["intrinsics"]["fx"].get<REAL>(); |
|
K(1,1) = data["intrinsics"]["fy"].get<REAL>(); |
|
K(0,2) = data["intrinsics"]["cx"].get<REAL>(); |
|
K(1,2) = data["intrinsics"]["cy"].get<REAL>(); |
|
IIndex platformID; |
|
if (prevPlatformID == NO_ID || !K.IsEqual(scene.platforms[prevPlatformID].cameras[0].K, 1e-3)) { |
|
#ifdef MVSNET_USE_OPENMP |
|
#pragma omp critical |
|
#endif |
|
{ |
|
prevPlatformID = platformID = scene.platforms.size(); |
|
Platform& platform = scene.platforms.emplace_back(); |
|
Platform::Camera& camera = platform.cameras.emplace_back(); |
|
platform.poses.reserve((IIndex)strImageNames.size()); |
|
camera.K = K; |
|
camera.R = RMatrix::IDENTITY; |
|
camera.C = CMatrix::ZERO; |
|
} |
|
} else { |
|
platformID = prevPlatformID; |
|
} |
|
Platform& platform = scene.platforms[platformID]; |
|
// set image |
|
Image& imageData = scene.images[imageID]; |
|
imageData.platformID = platformID; |
|
imageData.cameraID = 0; // only one camera per platform supported by this format |
|
imageData.poseID = NO_ID; |
|
imageData.ID = imageID; |
|
imageData.name = strImagePath+strImageName+".jpg"; |
|
ASSERT(Util::isFullPath(imageData.name)); |
|
{ |
|
cv::Mat image = cv::imread(strFileName+".exr", cv::IMREAD_UNCHANGED); |
|
ASSERT(image.type() == CV_32FC4); |
|
std::vector<cv::Mat> channels; |
|
cv::split(image, channels); |
|
cv::merge(std::vector<cv::Mat>{channels[0], channels[1], channels[2]}, image); |
|
image.convertTo(imageData.image, CV_8UC3, 255); |
|
} |
|
ASSERT(resolution == imageData.image.size()); |
|
if (imageData.image.empty()) { |
|
VERBOSE("Unable to load image %s.", (strFileName+".exr").c_str()); |
|
continue; |
|
} |
|
cv::imwrite(imageData.name, imageData.image); |
|
imageData.ReleaseImage(); |
|
// set image resolution |
|
imageData.width = resolution.width; |
|
imageData.height = resolution.height; |
|
imageData.scale = 1; |
|
// load camera pose |
|
#ifdef MVSNET_USE_OPENMP |
|
#pragma omp critical |
|
#endif |
|
{ |
|
imageData.poseID = platform.poses.size(); |
|
platform.poses.emplace_back(); |
|
} |
|
Platform::Pose& pose = platform.poses[imageData.poseID]; |
|
const auto Ps = data["cam2world"].get<std::vector<std::vector<double>>>(); |
|
Eigen::Matrix4d P{ |
|
{Ps[0][0], Ps[1][0], Ps[2][0], Ps[3][0]}, |
|
{Ps[0][1], Ps[1][1], Ps[2][1], Ps[3][1]}, |
|
{Ps[0][2], Ps[1][2], Ps[2][2], Ps[3][2]}, |
|
{Ps[0][3], Ps[1][3], Ps[2][3], Ps[3][3]} |
|
}; |
|
// apply the same transforms as nerfstudio converter |
|
P.row(2) *= -1; |
|
P.row(0).swap(P.row(1)); |
|
P.col(2) *= -1; |
|
P.col(1) *= -1; |
|
// set camera pose |
|
pose.R = P.topLeftCorner<3, 3>().transpose().eval(); |
|
pose.R.EnforceOrthogonality(); |
|
pose.C = P.topRightCorner<3, 1>().eval(); |
|
imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); |
|
// try reading the segmentation mask |
|
{ |
|
cv::Mat imgMask = cv::imread(strFileName+".seg.exr", cv::IMREAD_UNCHANGED); |
|
if (imgMask.empty()) { |
|
VERBOSE("Unable to load segmentation mask %s.", (strFileName+".seg.exr").c_str()); |
|
continue; |
|
} |
|
ASSERT(imgMask.type() == CV_32FC4); |
|
ASSERT(resolution == imgMask.size()); |
|
std::vector<cv::Mat> channels; |
|
cv::split(imgMask, channels); |
|
channels[0].convertTo(imgMask, CV_16U); |
|
imageData.maskName = strImagePath+strImageName+".mask.png"; |
|
cv::imwrite(imageData.maskName, imgMask); |
|
} |
|
// try reading the depth-map |
|
DepthMap depthMap; { |
|
const cv::Mat imgDepthMap = cv::imread(strFileName+".depth.exr", cv::IMREAD_UNCHANGED); |
|
if (imgDepthMap.empty()) { |
|
VERBOSE("Unable to load depthmap %s.", (strFileName+".depth.exr").c_str()); |
|
continue; |
|
} |
|
ASSERT(imgDepthMap.type() == CV_32FC4); |
|
ASSERT(resolution == imgDepthMap.size()); |
|
std::vector<cv::Mat> channels; |
|
cv::split(imgDepthMap, channels); |
|
RangeToDepthMap(channels[0], imageData.camera, depthMap); |
|
} |
|
const NormalMap normalMap; |
|
const ConfidenceMap confMap; |
|
const ViewsMap viewsMap; |
|
const IIndexArr IDs = {imageID}; |
|
double dMin, dMax; |
|
cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); |
|
const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); |
|
if (!ExportDepthDataRaw(dmapPath, |
|
imageData.name, IDs, resolution, |
|
K, pose.R, pose.C, |
|
(float)dMin, (float)dMax, |
|
depthMap, normalMap, confMap, viewsMap)) |
|
{ |
|
VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); |
|
continue; |
|
} |
|
} |
|
if (scene.images.size() < 2) |
|
return false; |
|
scene.nCalibratedImages = (unsigned)scene.images.size(); |
|
return true; |
|
} |
|
|
|
bool ParseScene(Scene& scene) |
|
{ |
|
#if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) |
|
String strPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); |
|
Util::ensureValidFolderPath(strPath); |
|
const std::filesystem::path path(static_cast<const std::string&>(strPath)); |
|
enum Type { |
|
MVSNet = 0, |
|
NERFSTUDIO, |
|
RTMV, |
|
} sceneType = MVSNet; |
|
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(path)) { |
|
if (entry.path().extension() == RTMV_CAMERAS_EXT) { |
|
if (entry.path().filename() == NERFSTUDIO_TRANSFORMS) { |
|
sceneType = NERFSTUDIO; |
|
break; |
|
} |
|
sceneType = RTMV; |
|
} |
|
} |
|
switch (sceneType) { |
|
case NERFSTUDIO: return ParseSceneNerfstudio(scene, strPath); |
|
case RTMV: return ParseSceneRTMV(scene, strPath); |
|
default: return ParseSceneMVSNet(scene, strPath); |
|
} |
|
#else |
|
VERBOSE("error: C++17 is required to parse MVSNet format"); |
|
return false; |
|
#endif // _SUPPORT_CPP17 |
|
} |
|
|
|
} // unnamed namespace |
|
|
|
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; |
|
|
|
TD_TIMER_START(); |
|
|
|
Scene scene(OPT::nMaxThreads); |
|
|
|
// convert data from MVSNet format to OpenMVS |
|
if (!ParseScene(scene)) |
|
return EXIT_FAILURE; |
|
|
|
// write OpenMVS input data |
|
scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); |
|
|
|
VERBOSE("Imported data: %u platforms, %u images, %u vertices (%s)", |
|
scene.platforms.size(), scene.images.size(), scene.pointcloud.GetSize(), |
|
TD_TIMER_GET_FMT().c_str()); |
|
return EXIT_SUCCESS; |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|