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.
755 lines
26 KiB
755 lines
26 KiB
/* |
|
* InterfaceOpenMVG.cpp |
|
* |
|
* Copyright (c) 2014-2015 SEACAVE |
|
* |
|
* Author(s): |
|
* |
|
* cDc <cdc.seacave@gmail.com> |
|
* Pierre MOULON <p.moulon@foxel.ch> |
|
* |
|
* |
|
* 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> |
|
#ifdef _USE_OPENMVG |
|
#undef D2R |
|
#undef R2D |
|
#include <openMVG/sfm/sfm_data.hpp> |
|
#include <openMVG/sfm/sfm_data_io.hpp> |
|
#include <openMVG/image/image.hpp> |
|
#endif |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
#define APPNAME _T("InterfaceOpenMVG") |
|
#define MVS_EXT _T(".mvs") |
|
#define MVG_EXT _T(".baf") |
|
#define MVG2_EXT _T(".json") |
|
#define MVG3_EXT _T(".bin") |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
namespace { |
|
|
|
namespace openMVS { |
|
namespace MVS_IO { |
|
|
|
typedef REAL RealT; |
|
typedef Eigen::Matrix<RealT,3,3,Eigen::RowMajor> Mat33; |
|
typedef Eigen::Matrix<RealT,3,1> Vec3; |
|
|
|
// Structure to model the pinhole camera projection model |
|
struct Camera |
|
{ |
|
Mat33 K; // camera's normalized intrinsics matrix |
|
}; |
|
typedef std::vector<Camera> vec_Camera; |
|
|
|
// structure describing a pose along the trajectory of a platform |
|
struct Pose { |
|
Mat33 R; // pose's rotation matrix |
|
Vec3 C; // pose's translation vector |
|
}; |
|
typedef std::vector<Pose> vec_Pose; |
|
|
|
// structure describing an image |
|
struct Image { |
|
uint32_t id_camera; // ID of the associated camera on the associated platform |
|
uint32_t id_pose; // ID of the pose of the associated platform |
|
std::string name; // image file name |
|
}; |
|
typedef std::vector<Image> vec_Image; |
|
|
|
// structure describing a 3D point |
|
struct Vertex { |
|
|
|
typedef std::vector<uint32_t> vec_View; |
|
|
|
Vec3 X; // 3D point position |
|
vec_View views; // view visibility for this 3D feature |
|
}; |
|
typedef std::vector<Vertex> vec_Vertex; |
|
|
|
struct SfM_Scene |
|
{ |
|
vec_Pose poses; // array of poses |
|
vec_Camera cameras; // array of cameras |
|
vec_Image images; // array of images |
|
vec_Vertex vertices; // array of reconstructed 3D points |
|
}; |
|
|
|
|
|
bool ImportScene(const std::string& sList_filename, const std::string& sBaf_filename, SfM_Scene& sceneBAF) |
|
{ |
|
LOG_OUT() << "Reading:\n" |
|
<< sList_filename << "\n" |
|
<< sBaf_filename << std::endl; |
|
|
|
// Read view list file (view filename, id_intrinsic, id_pose) |
|
// Must be read first, since it allow to establish the link between the ViewId and the camera/poses ids. |
|
std::map< std::pair<uint32_t, uint32_t>, uint32_t > map_cam_pose_toViewId; |
|
{ |
|
std::ifstream file(sList_filename.c_str()); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open file '%s'", sList_filename.c_str()); |
|
return false; |
|
} |
|
Image image; |
|
uint32_t count = 0; |
|
while (file >> image.name >> image.id_camera >> image.id_pose) { |
|
sceneBAF.images.push_back(image); |
|
map_cam_pose_toViewId[std::make_pair(image.id_camera, image.id_pose)] = count++; |
|
LOG_OUT() << image.name << ' ' << image.id_camera << ' ' << image.id_pose << std::endl; |
|
} |
|
} |
|
|
|
// Read BAF file |
|
{ |
|
std::ifstream file(sBaf_filename.c_str()); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open file '%s'", sBaf_filename.c_str()); |
|
return false; |
|
} |
|
|
|
uint32_t num_intrinsics, num_poses, num_points; |
|
|
|
// Read header |
|
file >> num_intrinsics; |
|
file >> num_poses; |
|
file >> num_points; |
|
|
|
LOG_OUT() << "Reading BAF file with:\n" |
|
<< " num_intrinsics: " << num_intrinsics << "\n" |
|
<< " num_poses: " << num_poses << "\n" |
|
<< " num_points: " << num_points << "\n"; |
|
|
|
// Read the intrinsics (only support reading Pinhole Radial 3). |
|
{ |
|
for (uint32_t i = 0; i < num_intrinsics; ++i) { |
|
double focal, ppx, ppy, k1, k2, k3; |
|
file >> focal >> ppx >> ppy >> k1 >> k2 >> k3; |
|
Camera cam; |
|
cam.K << |
|
focal, 0, ppx, |
|
0, focal, ppy, |
|
0, 0, 1; |
|
LOG_OUT() << "\n" << cam.K << std::endl; |
|
sceneBAF.cameras.push_back(cam); |
|
} |
|
} |
|
|
|
// Read poses |
|
{ |
|
for (uint32_t i = 0; i < num_poses; ++i) { |
|
Pose pose; |
|
for (int r = 0; r < 3; ++r) { |
|
for (int c = 0; c < 3; ++c) { |
|
file >> pose.R(r,c); |
|
} |
|
} |
|
file >> pose.C[0] >> pose.C[1] >> pose.C[2]; |
|
#ifndef _RELEASE |
|
LOG_OUT() << "\n" << pose.R << "\n\n" << pose.C.transpose() << std::endl; |
|
#endif |
|
sceneBAF.poses.push_back(pose); |
|
} |
|
} |
|
|
|
// Read structure and visibility |
|
{ |
|
#ifdef _RELEASE |
|
Util::Progress progress(_T("Processed points"), num_points); |
|
#endif |
|
for (uint32_t i = 0; i < num_points; ++i) { |
|
Vertex vertex; |
|
file >> vertex.X[0] >> vertex.X[1] >> vertex.X[2]; |
|
uint32_t num_observations_for_point = 0; |
|
file >> num_observations_for_point; |
|
for (uint32_t j = 0; j < num_observations_for_point; ++j) { |
|
uint32_t id_intrinsics, id_pose; |
|
double x, y; |
|
file >> id_intrinsics >> id_pose >> x >> y; |
|
#ifndef _RELEASE |
|
LOG_OUT() << "observation:" |
|
<< " " << id_intrinsics |
|
<< " " << id_pose |
|
<< " " << x << " " << y << std::endl; |
|
#endif |
|
const auto itIntrPose(map_cam_pose_toViewId.find(std::make_pair(id_intrinsics, id_pose))); |
|
if (itIntrPose == map_cam_pose_toViewId.end()) { |
|
LOG_OUT() << "error: intrinsics-pose pair not existing" << std::endl; |
|
continue; |
|
} |
|
const uint32_t id_view(itIntrPose->second); |
|
vertex.views.push_back(id_view); |
|
} |
|
sceneBAF.vertices.push_back(vertex); |
|
#ifdef _RELEASE |
|
progress.display(i); |
|
#endif |
|
} |
|
#ifdef _RELEASE |
|
progress.close(); |
|
#endif |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
bool ExportScene(const std::string& sList_filename, const std::string& sBaf_filename, const SfM_Scene& sceneBAF) |
|
{ |
|
LOG_OUT() << "Writing:\n" |
|
<< sList_filename << "\n" |
|
<< sBaf_filename << std::endl; |
|
|
|
// Write view list file (view filename, id_intrinsic, id_pose) |
|
{ |
|
std::ofstream file(sList_filename.c_str()); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open file '%s'", sList_filename.c_str()); |
|
return false; |
|
} |
|
for (uint32_t i=0; i<sceneBAF.images.size(); ++i) { |
|
const Image& image = sceneBAF.images[i]; |
|
file << image.name << ' ' << image.id_camera << ' ' << image.id_pose << std::endl; |
|
LOG_OUT() << image.name << ' ' << image.id_camera << ' ' << image.id_pose << std::endl; |
|
} |
|
} |
|
|
|
// Write BAF file |
|
{ |
|
std::ofstream file(sBaf_filename.c_str()); |
|
if (!file.good()) { |
|
VERBOSE("error: unable to open file '%s'", sBaf_filename.c_str()); |
|
return false; |
|
} |
|
|
|
const uint32_t num_intrinsics = (uint32_t)sceneBAF.cameras.size(); |
|
const uint32_t num_poses = (uint32_t)sceneBAF.poses.size(); |
|
const uint32_t num_points = (uint32_t)sceneBAF.vertices.size(); |
|
|
|
LOG_OUT() << "Writing BAF file with:\n" |
|
<< " num_intrinsics: " << num_intrinsics << "\n" |
|
<< " num_poses: " << num_poses << "\n" |
|
<< " num_points: " << num_points << "\n"; |
|
|
|
// Write header |
|
file << num_intrinsics << std::endl; |
|
file << num_poses << std::endl; |
|
file << num_points << std::endl; |
|
|
|
// Write the intrinsics (only support writing Pinhole Radial 3). |
|
{ |
|
for (uint32_t i = 0; i < num_intrinsics; ++i) { |
|
const Camera& cam = sceneBAF.cameras[i]; |
|
file << cam.K(0,0) << ' ' << cam.K(0,2) << ' ' << cam.K(1,2) << ' ' << 0 << ' ' << 0 << ' ' << 0 << std::endl; |
|
LOG_OUT() << "\n" << cam.K << std::endl; |
|
} |
|
} |
|
|
|
// Write poses |
|
{ |
|
for (uint32_t i = 0; i < num_poses; ++i) { |
|
const Pose& pose = sceneBAF.poses[i]; |
|
for (int r = 0; r < 3; ++r) { |
|
for (int c = 0; c < 3; ++c) { |
|
file << pose.R(r,c) << ' '; |
|
} |
|
} |
|
file << pose.C[0] << ' ' << pose.C[1] << ' ' << pose.C[2] << std::endl; |
|
#ifndef _RELEASE |
|
LOG_OUT() << "\n" << pose.R << "\n\n" << pose.C.transpose() << std::endl; |
|
#endif |
|
} |
|
} |
|
|
|
// Write structure and visibility |
|
{ |
|
#ifdef _RELEASE |
|
Util::Progress progress(_T("Processed points"), num_points); |
|
#endif |
|
for (uint32_t i = 0; i < num_points; ++i) { |
|
const Vertex& vertex = sceneBAF.vertices[i]; |
|
file << vertex.X[0] << ' ' << vertex.X[1] << ' ' << vertex.X[2] << std::endl; |
|
const uint32_t num_observations_for_point = (uint32_t)vertex.views.size(); |
|
file << num_observations_for_point << std::endl; |
|
for (uint32_t j = 0; j < num_observations_for_point; ++j) { |
|
const uint32_t id_view = vertex.views[j]; |
|
const Image& image = sceneBAF.images[id_view]; |
|
file << image.id_camera << ' ' << image.id_pose << ' ' << 0 << ' ' << 0 << std::endl; |
|
#ifndef _RELEASE |
|
LOG_OUT() << "observation:" |
|
<< " " << image.id_camera |
|
<< " " << image.id_pose << std::endl; |
|
#endif |
|
} |
|
#ifdef _RELEASE |
|
progress.display(i); |
|
#endif |
|
} |
|
#ifdef _RELEASE |
|
progress.close(); |
|
#endif |
|
} |
|
} |
|
return true; |
|
} |
|
} // MVS_IO |
|
} // openMVS |
|
|
|
|
|
namespace OPT { |
|
#ifdef _USE_OPENMVG |
|
bool bOpenMVGjson; // new import format |
|
#endif |
|
bool bOpenMVS2OpenMVG; // conversion direction |
|
bool bNormalizeIntrinsics; |
|
String strListFileName; |
|
String strInputFileName; |
|
String strOutputFileName; |
|
String strOutputImageFolder; |
|
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_DEFAULT), "project archive type: 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() |
|
("images-list-file,l", boost::program_options::value<std::string>(&OPT::strListFileName), "input filename containing image list") |
|
("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") |
|
("output-image-folder", boost::program_options::value<std::string>(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") |
|
("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to OpenMVS format") |
|
; |
|
|
|
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::strListFileName); |
|
Util::ensureUnifySlash(OPT::strListFileName); |
|
Util::ensureValidPath(OPT::strInputFileName); |
|
Util::ensureUnifySlash(OPT::strInputFileName); |
|
Util::ensureUnifySlash(OPT::strOutputImageFolder); |
|
Util::ensureFolderSlash(OPT::strOutputImageFolder); |
|
const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); |
|
OPT::bOpenMVS2OpenMVG = (strInputFileNameExt == MVS_FILE_EXTENSION); |
|
#ifdef _USE_OPENMVG |
|
OPT::bOpenMVGjson = (strInputFileNameExt == MVG2_EXT || strInputFileNameExt == MVG3_EXT); |
|
const bool bInvalidCommand(OPT::strInputFileName.IsEmpty() || (OPT::strListFileName.IsEmpty() && !OPT::bOpenMVGjson && !OPT::bOpenMVS2OpenMVG)); |
|
#else |
|
const bool bInvalidCommand(OPT::strInputFileName.IsEmpty() || (OPT::strListFileName.IsEmpty() && !OPT::bOpenMVS2OpenMVG)); |
|
#endif |
|
if (OPT::vm.count("help") || bInvalidCommand) { |
|
boost::program_options::options_description visible("Available options"); |
|
visible.add(generic).add(config); |
|
GET_LOG() << visible; |
|
#ifndef _USE_OPENMVG |
|
GET_LOG() << "\nWARNING: Only " MVG_EXT " files supported! In order to import " MVG2_EXT " or " MVG3_EXT " files use the converter included in OpenMVG, or link OpenMVS to the latest version of OpenMVG during compile time.\n"; |
|
#endif |
|
} |
|
if (bInvalidCommand) |
|
return false; |
|
|
|
// initialize optional options |
|
Util::ensureValidPath(OPT::strOutputFileName); |
|
Util::ensureUnifySlash(OPT::strOutputFileName); |
|
if (OPT::bOpenMVS2OpenMVG) { |
|
if (OPT::strOutputFileName.IsEmpty()) |
|
OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName); |
|
} else { |
|
if (OPT::strOutputFileName.IsEmpty()) |
|
OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + 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(); |
|
} |
|
|
|
} // 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(); |
|
|
|
if (OPT::bOpenMVS2OpenMVG) { |
|
// read OpenMVS input data |
|
MVS::Scene scene(OPT::nMaxThreads); |
|
if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) |
|
return EXIT_FAILURE; |
|
|
|
// convert data from OpenMVS to OpenMVG |
|
openMVS::MVS_IO::SfM_Scene sceneBAF; |
|
FOREACH(p, scene.platforms) { |
|
const MVS::Platform& platform = scene.platforms[p]; |
|
if (platform.cameras.GetSize() != 1) { |
|
LOG("error: unsupported scene structure"); |
|
return EXIT_FAILURE; |
|
} |
|
const MVS::Platform::Camera& camera = platform.cameras[0]; |
|
openMVS::MVS_IO::Camera cameraBAF; |
|
cameraBAF.K = camera.K; |
|
sceneBAF.cameras.push_back(cameraBAF); |
|
} |
|
FOREACH(i, scene.images) { |
|
const MVS::Image& image = scene.images[i]; |
|
const MVS::Platform& platform = scene.platforms[image.platformID]; |
|
const MVS::Platform::Pose& pose = platform.poses[image.poseID]; |
|
openMVS::MVS_IO::Image imageBAF; |
|
imageBAF.name = image.name; |
|
imageBAF.name = MAKE_PATH_REL(WORKING_FOLDER_FULL, imageBAF.name); |
|
imageBAF.id_camera = image.platformID; |
|
imageBAF.id_pose = (uint32_t)sceneBAF.poses.size(); |
|
sceneBAF.images.push_back(imageBAF); |
|
openMVS::MVS_IO::Pose poseBAF; |
|
poseBAF.R = pose.R; |
|
poseBAF.C = pose.C; |
|
sceneBAF.poses.push_back(poseBAF); |
|
} |
|
sceneBAF.vertices.reserve(scene.pointcloud.points.GetSize()); |
|
FOREACH(p, scene.pointcloud.points) { |
|
const MVS::PointCloud::Point& point = scene.pointcloud.points[p]; |
|
openMVS::MVS_IO::Vertex vertexBAF; |
|
vertexBAF.X = ((const MVS::PointCloud::Point::EVec)point).cast<REAL>(); |
|
const MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews[p]; |
|
FOREACH(v, views) { |
|
unsigned viewBAF = views[(uint32_t)v]; |
|
vertexBAF.views.push_back(viewBAF); |
|
} |
|
sceneBAF.vertices.push_back(vertexBAF); |
|
} |
|
|
|
// write OpenMVG input data |
|
const String strOutputFileNameMVG(OPT::strOutputFileName + MVG_EXT); |
|
openMVS::MVS_IO::ExportScene(MAKE_PATH_SAFE(OPT::strListFileName), MAKE_PATH_SAFE(strOutputFileNameMVG), sceneBAF); |
|
|
|
VERBOSE("Input data exported: %u cameras & %u poses & %u images & %u vertices (%s)", sceneBAF.cameras.size(), sceneBAF.poses.size(), sceneBAF.images.size(), sceneBAF.vertices.size(), TD_TIMER_GET_FMT().c_str()); |
|
} else { |
|
// convert data from OpenMVG to OpenMVS |
|
MVS::Scene scene(OPT::nMaxThreads); |
|
size_t nCameras(0), nPoses(0); |
|
|
|
#ifdef _USE_OPENMVG |
|
if (OPT::bOpenMVGjson) { |
|
// read OpenMVG input data from a JSON file |
|
using namespace openMVG::sfm; |
|
using namespace openMVG::cameras; |
|
SfM_Data sfm_data; |
|
const String strSfM_Data_Filename(MAKE_PATH_SAFE(OPT::strInputFileName)); |
|
if (!Load(sfm_data, strSfM_Data_Filename, ESfM_Data(ALL))) { |
|
VERBOSE("error: the input SfM_Data file '%s' cannot be read", strSfM_Data_Filename.c_str()); |
|
return EXIT_FAILURE; |
|
} |
|
VERBOSE("Imported data: %u cameras, %u poses, %u images, %u vertices", |
|
sfm_data.GetIntrinsics().size(), |
|
sfm_data.GetPoses().size(), |
|
sfm_data.GetViews().size(), |
|
sfm_data.GetLandmarks().size()); |
|
|
|
// OpenMVG can have not contiguous index, use a map to create the required OpenMVS contiguous ID index |
|
std::map<openMVG::IndexT, uint32_t> map_intrinsic, map_view; |
|
|
|
// define a platform with all the intrinsic group |
|
nCameras = sfm_data.GetIntrinsics().size(); |
|
for (const auto& intrinsic: sfm_data.GetIntrinsics()) { |
|
if (isPinhole(intrinsic.second.get()->getType())) { |
|
const Pinhole_Intrinsic * cam = dynamic_cast<const Pinhole_Intrinsic*>(intrinsic.second.get()); |
|
if (map_intrinsic.count(intrinsic.first) == 0) |
|
map_intrinsic.insert(std::make_pair(intrinsic.first, scene.platforms.GetSize())); |
|
MVS::Platform& platform = scene.platforms.AddEmpty(); |
|
// add the camera |
|
MVS::Platform::Camera& camera = platform.cameras.AddEmpty(); |
|
camera.K = cam->K(); |
|
// sub-pose |
|
camera.R = RMatrix::IDENTITY; |
|
camera.C = CMatrix::ZERO; |
|
} |
|
} |
|
|
|
// define images & poses |
|
const uint32_t nViews((uint32_t)sfm_data.GetViews().size()); |
|
scene.images.Reserve(nViews); |
|
scene.nCalibratedImages = 0; |
|
Util::Progress progress(_T("Processed images"), nViews); |
|
GET_LOGCONSOLE().Pause(); |
|
#ifdef _USE_OPENMP |
|
const std::vector<Views::value_type> views(sfm_data.GetViews().cbegin(), sfm_data.GetViews().cend()); |
|
#pragma omp parallel for schedule(dynamic) |
|
for (int i=0; i<(int)nViews; ++i) { |
|
const Views::value_type& view = views[i]; |
|
#pragma omp critical |
|
map_view[view.first] = scene.images.GetSize(); |
|
#else |
|
for (const auto& view : sfm_data.GetViews()) { |
|
map_view[view.first] = scene.images.GetSize(); |
|
#endif |
|
MVS::Image& image = scene.images.AddEmpty(); |
|
image.name = view.second->s_Img_path; |
|
Util::ensureUnifySlash(image.name); |
|
Util::strTrim(image.name, PATH_SEPARATOR_STR); |
|
String pathRoot(sfm_data.s_root_path); Util::ensureFolderSlash(pathRoot); |
|
const String srcImage(MAKE_PATH_FULL(WORKING_FOLDER_FULL, pathRoot+image.name)); |
|
image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputImageFolder+image.name); |
|
Util::ensureDirectory(image.name); |
|
image.ID = static_cast<MVS::IIndex>(view.first); |
|
image.platformID = map_intrinsic.at(view.second->id_intrinsic); |
|
MVS::Platform& platform = scene.platforms[image.platformID]; |
|
image.cameraID = 0; |
|
if (sfm_data.IsPoseAndIntrinsicDefined(view.second.get()) && |
|
File::access(srcImage)) { |
|
MVS::Platform::Pose* pPose; |
|
#ifdef _USE_OPENMP |
|
#pragma omp critical |
|
#endif |
|
{ |
|
image.poseID = platform.poses.GetSize(); |
|
pPose = &platform.poses.AddEmpty(); |
|
++scene.nCalibratedImages; |
|
} |
|
const openMVG::geometry::Pose3 poseMVG(sfm_data.GetPoseOrDie(view.second.get())); |
|
pPose->R = poseMVG.rotation(); |
|
pPose->C = poseMVG.center(); |
|
// export undistorted images |
|
const openMVG::cameras::IntrinsicBase * cam = sfm_data.GetIntrinsics().at(view.second->id_intrinsic).get(); |
|
if (cam->have_disto()) { |
|
// undistort and save the image |
|
openMVG::image::Image<openMVG::image::RGBColor> imageRGB, imageRGB_ud; |
|
openMVG::image::ReadImage(srcImage, &imageRGB); |
|
openMVG::cameras::UndistortImage(imageRGB, cam, imageRGB_ud, openMVG::image::BLACK); |
|
openMVG::image::WriteImage(image.name, imageRGB_ud); |
|
} else { |
|
// no distortion, copy the image |
|
File::copyFile(srcImage, image.name); |
|
} |
|
++nPoses; |
|
} else { |
|
// image have not valid pose, so set an undefined pose |
|
image.poseID = NO_ID; |
|
// just copy the image |
|
File::copyFile(srcImage, image.name); |
|
DEBUG_EXTRA("warning: uncalibrated image '%s'", view.second->s_Img_path.c_str()); |
|
} |
|
++progress; |
|
} |
|
GET_LOGCONSOLE().Play(); |
|
progress.close(); |
|
|
|
// define structure |
|
scene.pointcloud.points.Reserve(sfm_data.GetLandmarks().size()); |
|
scene.pointcloud.pointViews.Reserve(sfm_data.GetLandmarks().size()); |
|
for (const auto& vertex: sfm_data.GetLandmarks()) { |
|
const Landmark & landmark = vertex.second; |
|
MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews.AddEmpty(); |
|
for (const auto& observation: landmark.obs) { |
|
const auto it(map_view.find(observation.first)); |
|
if (it != map_view.end()) |
|
views.InsertSort(it->second); |
|
} |
|
if (views.GetSize() < 2) { |
|
scene.pointcloud.pointViews.RemoveLast(); |
|
continue; |
|
} |
|
MVS::PointCloud::Point& point = scene.pointcloud.points.AddEmpty(); |
|
point = landmark.X.cast<float>(); |
|
} |
|
} else |
|
#endif |
|
{ |
|
// read OpenMVG input data from BAF file |
|
openMVS::MVS_IO::SfM_Scene sceneBAF; |
|
if (!openMVS::MVS_IO::ImportScene(MAKE_PATH_SAFE(OPT::strListFileName), MAKE_PATH_SAFE(OPT::strInputFileName), sceneBAF)) |
|
return EXIT_FAILURE; |
|
|
|
// convert data from OpenMVG to OpenMVS |
|
nCameras = sceneBAF.cameras.size(); |
|
scene.platforms.Reserve((uint32_t)nCameras); |
|
for (const auto& cameraBAF: sceneBAF.cameras) { |
|
MVS::Platform& platform = scene.platforms.AddEmpty(); |
|
MVS::Platform::Camera& camera = platform.cameras.AddEmpty(); |
|
camera.K = cameraBAF.K; |
|
camera.R = RMatrix::IDENTITY; |
|
camera.C = CMatrix::ZERO; |
|
} |
|
nPoses = sceneBAF.images.size(); |
|
scene.images.Reserve((uint32_t)nPoses); |
|
for (const auto& imageBAF: sceneBAF.images) { |
|
openMVS::MVS_IO::Pose& poseBAF = sceneBAF.poses[imageBAF.id_pose]; |
|
MVS::Image& image = scene.images.AddEmpty(); |
|
image.name = imageBAF.name; |
|
Util::ensureUnifySlash(image.name); |
|
image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, image.name); |
|
image.ID = imageBAF.id_camera; |
|
image.platformID = imageBAF.id_camera; |
|
MVS::Platform& platform = scene.platforms[image.platformID]; |
|
image.cameraID = 0; |
|
image.poseID = platform.poses.GetSize(); |
|
MVS::Platform::Pose& pose = platform.poses.AddEmpty(); |
|
pose.R = poseBAF.R; |
|
pose.C = poseBAF.C; |
|
} |
|
scene.pointcloud.points.Reserve(sceneBAF.vertices.size()); |
|
scene.pointcloud.pointViews.Reserve(sceneBAF.vertices.size()); |
|
for (const auto& vertexBAF: sceneBAF.vertices) { |
|
MVS::PointCloud::Point& point = scene.pointcloud.points.AddEmpty(); |
|
point = vertexBAF.X.cast<float>(); |
|
MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews.AddEmpty(); |
|
for (const auto& viewBAF: vertexBAF.views) |
|
views.InsertSort(viewBAF); |
|
} |
|
} |
|
|
|
// read images meta-data |
|
FOREACHPTR(pImage, scene.images) { |
|
if (!pImage->ReloadImage(0, false)) |
|
LOG("error: can not read image %s", pImage->name.c_str()); |
|
} |
|
if (OPT::bNormalizeIntrinsics) { |
|
// normalize camera intrinsics |
|
FOREACH(p, scene.platforms) { |
|
MVS::Platform& platform = scene.platforms[p]; |
|
FOREACH(c, platform.cameras) { |
|
MVS::Platform::Camera& camera = platform.cameras[c]; |
|
// find one image using this camera |
|
MVS::Image* pImage(NULL); |
|
FOREACHPTR(pImg, scene.images) { |
|
if (pImg->platformID == p && pImg->cameraID == c && pImg->poseID != NO_ID) { |
|
pImage = pImg; |
|
break; |
|
} |
|
} |
|
if (pImage == NULL) { |
|
LOG("error: no image using camera %u of platform %u", c, p); |
|
continue; |
|
} |
|
const REAL fScale(REAL(1)/MVS::Camera::GetNormalizationScale(pImage->width, pImage->height)); |
|
camera.K(0,0) *= fScale; |
|
camera.K(1,1) *= fScale; |
|
camera.K(0,2) *= fScale; |
|
camera.K(1,2) *= fScale; |
|
} |
|
} |
|
} |
|
|
|
// write OpenMVS input data |
|
scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); |
|
|
|
VERBOSE("Exported data: %u platforms, %u cameras, %u poses, %u images, %u vertices (%s)", |
|
scene.platforms.GetSize(), nCameras, nPoses, scene.images.GetSize(), scene.pointcloud.GetSize(), |
|
TD_TIMER_GET_FMT().c_str()); |
|
} |
|
|
|
return EXIT_SUCCESS; |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|