diff --git a/cmake/FindDependencies.cmake b/cmake/FindDependencies.cmake index a656c221..64525887 100644 --- a/cmake/FindDependencies.cmake +++ b/cmake/FindDependencies.cmake @@ -20,6 +20,39 @@ if(TESTS_ENABLED) find_package(GTest REQUIRED) endif() +include(FetchContent) + +# PoseLib with EQUIRECTANGULAR support +FetchContent_Declare(PoseLib + GIT_REPOSITORY https://github.com/zalo/PoseLib.git + GIT_TAG feature/equirectangular + EXCLUDE_FROM_ALL + SYSTEM +) +message(STATUS "Configuring PoseLib (with EQUIRECTANGULAR support)...") +if (FETCH_POSELIB) + FetchContent_MakeAvailable(PoseLib) +else() + find_package(PoseLib REQUIRED) +endif() +message(STATUS "Configuring PoseLib... done") + +# COLMAP with EQUIRECTANGULAR support +FetchContent_Declare(COLMAP + GIT_REPOSITORY https://github.com/zalo/colmap.git + GIT_TAG feature/equirectangular + EXCLUDE_FROM_ALL +) +message(STATUS "Configuring COLMAP (with EQUIRECTANGULAR support)...") +set(UNINSTALL_ENABLED OFF CACHE INTERNAL "") +set(GUI_ENABLED OFF CACHE INTERNAL "") +if (FETCH_COLMAP) + FetchContent_MakeAvailable(COLMAP) +else() + find_package(COLMAP REQUIRED) +endif() +message(STATUS "Configuring COLMAP... done") + set(CUDA_MIN_VERSION "7.0") if(CUDA_ENABLED) if(CMAKE_VERSION VERSION_LESS 3.17) diff --git a/glomap/controllers/track_retriangulation.cc b/glomap/controllers/track_retriangulation.cc index 0b133e0d..cd2fa22c 100644 --- a/glomap/controllers/track_retriangulation.cc +++ b/glomap/controllers/track_retriangulation.cc @@ -18,12 +18,11 @@ bool RetriangulateTracks(const TriangulatorOptions& options, std::unordered_map& images, std::unordered_map& tracks) { // Following code adapted from COLMAP - auto database_cache = - colmap::DatabaseCache::Create(database, - options.min_num_matches, - false, // ignore_watermarks - {} // reconstruct all possible images - ); + colmap::DatabaseCache::Options cache_options; + cache_options.min_num_matches = options.min_num_matches; + cache_options.ignore_watermarks = false; + // image_names defaults to empty set (reconstruct all possible images) + auto database_cache = colmap::DatabaseCache::Create(database, cache_options); // Check whether the image is in the database cache. If not, set the image // as not registered to avoid memory error. diff --git a/glomap/estimators/view_graph_calibration.cc b/glomap/estimators/view_graph_calibration.cc index a66b5a2c..680573a1 100644 --- a/glomap/estimators/view_graph_calibration.cc +++ b/glomap/estimators/view_graph_calibration.cc @@ -54,6 +54,10 @@ void ViewGraphCalibrator::Reset( focals_.clear(); focals_.reserve(cameras.size()); for (const auto& [camera_id, camera] : cameras) { + // Skip spherical cameras - they don't have a focal length to estimate + if (camera.IsSpherical()) { + continue; + } focals_[camera_id] = camera.Focal(); } @@ -85,6 +89,12 @@ void ViewGraphCalibrator::AddImagePair( const camera_t camera_id1 = images.at(image_pair.image_id1).camera_id; const camera_t camera_id2 = images.at(image_pair.image_id2).camera_id; + // Skip image pairs involving spherical cameras + if (cameras.at(camera_id1).IsSpherical() || + cameras.at(camera_id2).IsSpherical()) { + return; + } + if (camera_id1 == camera_id2) { problem_->AddResidualBlock( FetzerFocalLengthSameCameraCost::Create( @@ -123,6 +133,11 @@ void ViewGraphCalibrator::CopyBackResults( std::unordered_map& cameras) { size_t counter = 0; for (auto& [camera_id, camera] : cameras) { + // Skip spherical cameras - they were not optimized + if (camera.IsSpherical()) { + continue; + } + if (!problem_->HasParameterBlock(&(focals_[camera_id]))) continue; // if the estimated parameter is too crazy, reject it diff --git a/glomap/io/colmap_converter.cc b/glomap/io/colmap_converter.cc index 4f33b940..dd8984ea 100644 --- a/glomap/io/colmap_converter.cc +++ b/glomap/io/colmap_converter.cc @@ -384,19 +384,27 @@ void ConvertDatabaseToGlomap(const colmap::Database& database, // Collect the fundemental matrices if (two_view.config == colmap::TwoViewGeometry::UNCALIBRATED) { - image_pair.F = two_view.F; + if (two_view.F.has_value()) { + image_pair.F = two_view.F.value(); + } } else if (two_view.config == colmap::TwoViewGeometry::CALIBRATED) { - FundamentalFromMotionAndCameras( - cameras.at(images.at(image_pair.image_id1).camera_id), - cameras.at(images.at(image_pair.image_id2).camera_id), - two_view.cam2_from_cam1, - &image_pair.F); + if (two_view.cam2_from_cam1.has_value()) { + FundamentalFromMotionAndCameras( + cameras.at(images.at(image_pair.image_id1).camera_id), + cameras.at(images.at(image_pair.image_id2).camera_id), + two_view.cam2_from_cam1.value(), + &image_pair.F); + } } else if (two_view.config == colmap::TwoViewGeometry::PLANAR || two_view.config == colmap::TwoViewGeometry::PANORAMIC || two_view.config == colmap::TwoViewGeometry::PLANAR_OR_PANORAMIC) { - image_pair.H = two_view.H; - image_pair.F = two_view.F; + if (two_view.H.has_value()) { + image_pair.H = two_view.H.value(); + } + if (two_view.F.has_value()) { + image_pair.F = two_view.F.value(); + } } image_pair.config = two_view.config; diff --git a/glomap/processors/view_graph_manipulation.cc b/glomap/processors/view_graph_manipulation.cc index 24c36726..474589ed 100644 --- a/glomap/processors/view_graph_manipulation.cc +++ b/glomap/processors/view_graph_manipulation.cc @@ -288,11 +288,13 @@ void ViewGraphManipulater::DecomposeRelPose( return; image_pair.config = two_view_geometry.config; - image_pair.cam2_from_cam1 = two_view_geometry.cam2_from_cam1; + if (two_view_geometry.cam2_from_cam1.has_value()) { + image_pair.cam2_from_cam1 = two_view_geometry.cam2_from_cam1.value(); - if (image_pair.cam2_from_cam1.translation.norm() > EPS) { - image_pair.cam2_from_cam1.translation = - image_pair.cam2_from_cam1.translation.normalized(); + if (image_pair.cam2_from_cam1.translation.norm() > EPS) { + image_pair.cam2_from_cam1.translation = + image_pair.cam2_from_cam1.translation.normalized(); + } } }); } diff --git a/glomap/scene/camera.h b/glomap/scene/camera.h index eeaf883f..518ee562 100644 --- a/glomap/scene/camera.h +++ b/glomap/scene/camera.h @@ -4,6 +4,7 @@ #include #include +#include #include @@ -20,18 +21,52 @@ struct Camera : public colmap::Camera { bool has_refined_focal_length = false; + // Returns true if this is a spherical camera model (e.g., EQUIRECTANGULAR) + // Spherical cameras do not have meaningful focal length or principal point + inline bool IsSpherical() const; + inline double Focal() const; inline Eigen::Vector2d PrincipalPoint() const; inline Eigen::Matrix3d GetK() const; }; -double Camera::Focal() const { return (FocalLengthX() + FocalLengthY()) / 2.0; } +bool Camera::IsSpherical() const { + // Check model_id for EQUIRECTANGULAR (most robust) + // Also check model name as fallback for compatibility + return model_id == colmap::CameraModelId::kEquirectangular || + ModelName() == "EQUIRECTANGULAR" || ModelName() == "SPHERICAL"; +} + +double Camera::Focal() const { + // Spherical cameras don't have a meaningful focal length + // Return a dummy value based on image dimensions for compatibility + if (IsSpherical()) { + constexpr double kPi = 3.141592653589793238462643383279502884L; + return static_cast(std::max(width, height)) / kPi; + } + return (FocalLengthX() + FocalLengthY()) / 2.0; +} Eigen::Vector2d Camera::PrincipalPoint() const { + // Spherical cameras don't have a principal point in the traditional sense + // Return the image center for compatibility + if (IsSpherical()) { + return Eigen::Vector2d(width / 2.0, height / 2.0); + } return Eigen::Vector2d(PrincipalPointX(), PrincipalPointY()); } Eigen::Matrix3d Camera::GetK() const { + // Spherical cameras don't have a 3x3 intrinsic matrix + // Return identity-like matrix for compatibility (should not be used) + if (IsSpherical()) { + LOG(WARNING) << "GetK() called on spherical camera - intrinsic matrix is " + "not meaningful for equirectangular cameras"; + Eigen::Matrix3d K = Eigen::Matrix3d::Identity(); + K(0, 2) = width / 2.0; + K(1, 2) = height / 2.0; + return K; + } Eigen::Matrix3d K; K << FocalLengthX(), 0, PrincipalPointX(), 0, FocalLengthY(), PrincipalPointY(), 0, 0, 1;