From af8578ae5e4a2be804b9959ff678bd0157f5af82 Mon Sep 17 00:00:00 2001 From: cDc Date: Thu, 4 May 2023 14:23:15 +0300 Subject: [PATCH] transform: transform scene by the given matrix --- apps/TransformScene/TransformScene.cpp | 42 +++++++++++++++++++++++--- libs/MVS/PythonWrapper.cpp | 3 +- libs/MVS/Scene.cpp | 32 ++++++++++++++++++-- libs/MVS/Scene.h | 2 ++ 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/apps/TransformScene/TransformScene.cpp b/apps/TransformScene/TransformScene.cpp index 0fd93ae89..d6c864a28 100644 --- a/apps/TransformScene/TransformScene.cpp +++ b/apps/TransformScene/TransformScene.cpp @@ -50,6 +50,7 @@ namespace OPT { String strInputFileName; String strOutputFileName; String strAlignFileName; + String strTransformFileName; String strTransferTextureFileName; String strIndicesFileName; bool bComputeVolume; @@ -98,7 +99,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input scene filename") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the scene") ("align-file,a", boost::program_options::value(&OPT::strAlignFileName), "input scene filename to which the scene will be cameras aligned") - ("transfer-texture-file,t", boost::program_options::value(&OPT::strTransferTextureFileName), "input mesh filename to which the texture of the scene's mesh will be transfered to (the two meshes should be aligned and the new mesh to have UV-map)") + ("transform-file,t", boost::program_options::value(&OPT::strTransformFileName), "input transform filename by which the scene will transformed") + ("transfer-texture-file", boost::program_options::value(&OPT::strTransferTextureFileName), "input mesh filename to which the texture of the scene's mesh will be transfered to (the two meshes should be aligned and the new mesh to have UV-map)") ("indices-file", boost::program_options::value(&OPT::strIndicesFileName), "input indices filename to be used with ex. texture transfer to select a subset of the scene's mesh") ("compute-volume", boost::program_options::value(&OPT::bComputeVolume)->default_value(false), "compute the volume of the given watertight mesh, or else try to estimate the ground plane and assume the mesh is bounded by it") ("plane-threshold", boost::program_options::value(&OPT::fPlaneThreshold)->default_value(0.f), "threshold used to estimate the ground plane (<0 - disabled, 0 - auto, >0 - desired threshold)") @@ -142,10 +144,12 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); Util::ensureValidPath(OPT::strAlignFileName); + Util::ensureValidPath(OPT::strTransformFileName); Util::ensureValidPath(OPT::strTransferTextureFileName); Util::ensureValidPath(OPT::strIndicesFileName); const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - const bool bInvalidCommand(OPT::strInputFileName.empty() || (OPT::strAlignFileName.empty() && OPT::strTransferTextureFileName.empty() && !OPT::bComputeVolume)); + const bool bInvalidCommand(OPT::strInputFileName.empty() || + (OPT::strAlignFileName.empty() && OPT::strTransformFileName.empty() && OPT::strTransferTextureFileName.empty() && !OPT::bComputeVolume)); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); @@ -215,7 +219,7 @@ int main(int argc, LPCTSTR* argv) Scene scene(OPT::nMaxThreads); // load given scene - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), !OPT::strTransferTextureFileName.empty() || OPT::bComputeVolume)) + if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), !OPT::strTransformFileName.empty() || !OPT::strTransferTextureFileName.empty() || OPT::bComputeVolume)) return EXIT_FAILURE; if (!OPT::strAlignFileName.empty()) { @@ -228,6 +232,31 @@ int main(int argc, LPCTSTR* argv) VERBOSE("Scene aligned to the given reference scene (%s)", TD_TIMER_GET_FMT().c_str()); } + if (!OPT::strTransformFileName.empty()) { + // transform this scene by the given transform matrix + std::ifstream file(MAKE_PATH_SAFE(OPT::strTransformFileName)); + std::string strLine; + std::vector transformValues; + while (std::getline(file, strLine)) { + errno = 0; + char* strEnd{}; + const double v = std::strtod(strLine.c_str(), &strEnd); + if (errno == ERANGE || strEnd == strLine.c_str()) + continue; + transformValues.push_back(v); + } + if (transformValues.size() != 12 && + (transformValues.size() != 16 || transformValues[12] != 0 || transformValues[13] != 0 || transformValues[14] != 0 || transformValues[15] != 1)) { + VERBOSE("error: invalid transform"); + return EXIT_FAILURE; + } + Matrix3x4 transform; + for (unsigned i=0; i<12; ++i) + transform[i] = transformValues[i]; + scene.Transform(transform); + VERBOSE("Scene transformed by the given transformation matrix (%s)", TD_TIMER_GET_FMT().c_str()); + } + if (!OPT::strTransferTextureFileName.empty()) { // transfer the texture of the scene's mesh to the new mesh; // the two meshes should be aligned and the new mesh to have UV-coordinates @@ -263,7 +292,12 @@ int main(int argc, LPCTSTR* argv) } // write transformed scene - scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + if (scene.IsValid()) + scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + else if (!scene.pointcloud.IsEmpty()) + scene.pointcloud.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + ".ply"); + else if (!scene.mesh.IsEmpty()) + scene.mesh.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + ".ply"); Finalize(); return EXIT_SUCCESS; diff --git a/libs/MVS/PythonWrapper.cpp b/libs/MVS/PythonWrapper.cpp index fd5827db6..62a8a1d71 100644 --- a/libs/MVS/PythonWrapper.cpp +++ b/libs/MVS/PythonWrapper.cpp @@ -118,7 +118,8 @@ BOOST_PYTHON_MODULE(pyOpenMVS) { .def("load_mesh", &Scene::pyLoadMesh, (arg("file_path"))) .def("save_mesh", &Scene::pySaveMesh, (arg("file_path"))) .def("scale_images", &Scene::ScaleImages) - .def("transform", &Scene::Transform) + .def("transform", static_cast(&Scene::Transform)) + .def("transform34", static_cast(&Scene::Transform)) .def("align_to", &Scene::AlignTo) .def("dense_reconstruction", &Scene::pyDenseReconstruction, (arg("resolution_level")=0, arg("fusion_mode")=0, arg("crop_to_roi")=true, arg("roi_border")=0.f)) .def("reconstruct_mesh", &Scene::pyReconstructMesh, (arg("dist_insert")=2, arg("use_free_space_support")=false, arg("use_only_roi")=false)) diff --git a/libs/MVS/Scene.cpp b/libs/MVS/Scene.cpp index 3b8023207..7043b0106 100644 --- a/libs/MVS/Scene.cpp +++ b/libs/MVS/Scene.cpp @@ -59,6 +59,11 @@ void Scene::Release() mesh.Release(); } +bool Scene::IsValid() const +{ + return !platforms.IsEmpty() && !images.IsEmpty(); +} + bool Scene::IsEmpty() const { return pointcloud.IsEmpty() && mesh.IsEmpty(); @@ -122,6 +127,7 @@ bool Scene::LoadInterface(const String & fileName) for (const Interface::Image& image: obj.images) { const uint32_t ID(images.size()); Image& imageData = images.AddEmpty(); + imageData.ID = (image.ID == NO_ID ? ID : image.ID); imageData.name = image.name; Util::ensureUnifySlash(imageData.name); imageData.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, imageData.name); @@ -137,7 +143,6 @@ bool Scene::LoadInterface(const String & fileName) } imageData.platformID = image.platformID; imageData.cameraID = image.cameraID; - imageData.ID = (image.ID == NO_ID ? ID : image.ID); // init camera const Interface::Platform::Camera& camera = obj.platforms[image.platformID].cameras[image.cameraID]; if (camera.HasResolution()) { @@ -1440,7 +1445,8 @@ bool Scene::ScaleImages(unsigned nMaxResolution, REAL scale, const String& folde } // ScaleImages // apply similarity transform -void Scene::Transform(const Matrix3x3& rotation, const Point3& translation, REAL scale) { +void Scene::Transform(const Matrix3x3& rotation, const Point3& translation, REAL scale) +{ const Matrix3x3 rotationScale(rotation * scale); for (Platform& platform : platforms) { for (Platform::Pose& pose : platform.poses) { @@ -1449,7 +1455,8 @@ void Scene::Transform(const Matrix3x3& rotation, const Point3& translation, REAL } } for (Image& image : images) { - image.UpdateCamera(platforms); + if (image.IsValid()) + image.UpdateCamera(platforms); } FOREACH(i, pointcloud.points) { pointcloud.points[i] = rotationScale * Cast(pointcloud.points[i]) + translation; @@ -1469,6 +1476,25 @@ void Scene::Transform(const Matrix3x3& rotation, const Point3& translation, REAL obb.Translate(Cast(translation)); } } +void Scene::Transform(const Matrix3x4& transform) +{ + #if 1 + Matrix3x3 mscale, rotation; + RQDecomp3x3(cv::Mat(3,4,cv::DataType::type,const_cast(transform.val))(cv::Rect(0,0, 3,3)), mscale, rotation); + const Point3 translation = transform.col(3); + #else + Eigen::Matrix transform4x4 = Eigen::Matrix::Identity(); + transform4x4.topLeftCorner<3,4>() = static_cast(transform); + Eigen::Transform transformIsometry(transform4x4); + Eigen::Matrix mrotation; + Eigen::Matrix mscale; + transformIsometry.computeRotationScaling(&mrotation, &mscale); + const Point3 translation = transformIsometry.translation(); + const Matrix3x3 rotation = mrotation; + #endif + ASSERT(mscale(0,0) > 0 && ISEQUAL(mscale(0,0), mscale(1,1)) && ISEQUAL(mscale(0,0), mscale(2,2))); + Transform(rotation, translation, mscale(0,0)); +} // Transform // transform this scene such that it best aligns with the given scene based on the camera positions bool Scene::AlignTo(const Scene& scene) diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index 93d6cf5b1..e49f717ef 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -67,6 +67,7 @@ class MVS_API Scene : obb(true), nMaxThreads(Thread::getMaxThreads(_nMaxThreads)) {} void Release(); + bool IsValid() const; bool IsEmpty() const; bool ImagesHaveNeighbors() const; bool IsBounded() const { return obb.IsValid(); } @@ -105,6 +106,7 @@ class MVS_API Scene bool Scale(const REAL* pScale = NULL); bool ScaleImages(unsigned nMaxResolution = 0, REAL scale = 0, const String& folderName = String()); void Transform(const Matrix3x3& rotation, const Point3& translation, REAL scale); + void Transform(const Matrix3x4& transform); bool AlignTo(const Scene&); REAL ComputeLeveledVolume(float planeThreshold=0, float sampleMesh=-100000, unsigned upAxis=2, bool verbose=true);