diff --git a/modules/features/include/opencv2/features.hpp b/modules/features/include/opencv2/features.hpp index 55519cca94..703b2f4c12 100644 --- a/modules/features/include/opencv2/features.hpp +++ b/modules/features/include/opencv2/features.hpp @@ -998,6 +998,19 @@ public: */ CV_WRAP virtual bool isMaskSupported() const = 0; + /** @brief Provides keypoint and image-size context for matchers that need it (e.g. LightGlueMatcher). + + Must be called before match()/knnMatch()/radiusMatch() for matchers that require this context. + Matchers that don't need it (e.g. BFMatcher, FlannBasedMatcher) ignore the call. + + @param queryKpts Query image keypoints. + @param trainKpts Train image keypoints. + @param queryImageSize Size of the query image (width, height). + @param trainImageSize Size of the train image (width, height). + */ + CV_WRAP virtual void setImagePairInfo(const std::vector& queryKpts, const std::vector& trainKpts, + Size queryImageSize = Size(), Size trainImageSize = Size()); + /** @brief Trains a descriptor matcher Trains a descriptor matcher (for example, the flann index). In all methods to match, the method @@ -1349,6 +1362,10 @@ public: /** @brief Clears stored pair context information. */ CV_WRAP virtual void clearPairInfo() = 0; + + /** @brief Convenience overload of setPairInfo() taking keypoints directly. */ + CV_WRAP void setImagePairInfo(const std::vector& queryKpts, const std::vector& trainKpts, + Size queryImageSize = Size(), Size trainImageSize = Size()) CV_OVERRIDE; }; //! @} features_match diff --git a/modules/features/src/matchers.cpp b/modules/features/src/matchers.cpp index 2223d59400..d11876849d 100644 --- a/modules/features/src/matchers.cpp +++ b/modules/features/src/matchers.cpp @@ -580,6 +580,10 @@ bool DescriptorMatcher::empty() const void DescriptorMatcher::train() {} +void DescriptorMatcher::setImagePairInfo( const std::vector&, const std::vector&, + Size, Size ) +{} + void DescriptorMatcher::match( InputArray queryDescriptors, InputArray trainDescriptors, std::vector& matches, InputArray mask ) const { diff --git a/modules/features/src/matchers_lightglue.cpp b/modules/features/src/matchers_lightglue.cpp index 798c673362..3278f7fb94 100644 --- a/modules/features/src/matchers_lightglue.cpp +++ b/modules/features/src/matchers_lightglue.cpp @@ -15,6 +15,26 @@ namespace cv LightGlueMatcher::LightGlueMatcher() {} LightGlueMatcher::~LightGlueMatcher() {} +void LightGlueMatcher::setImagePairInfo(const std::vector& queryKpts, const std::vector& trainKpts, + Size queryImageSize, Size trainImageSize) +{ + Mat qk((int)queryKpts.size(), 2, CV_32F); + for (size_t i = 0; i < queryKpts.size(); ++i) + { + qk.at((int)i, 0) = queryKpts[i].pt.x; + qk.at((int)i, 1) = queryKpts[i].pt.y; + } + + Mat tk((int)trainKpts.size(), 2, CV_32F); + for (size_t i = 0; i < trainKpts.size(); ++i) + { + tk.at((int)i, 0) = trainKpts[i].pt.x; + tk.at((int)i, 1) = trainKpts[i].pt.y; + } + + setPairInfo(qk, tk, queryImageSize, trainImageSize); +} + #ifdef HAVE_OPENCV_DNN struct LightGluePairContext diff --git a/modules/ptcloud/src/slam/odometry/visual_odometry.cpp b/modules/ptcloud/src/slam/odometry/visual_odometry.cpp index 18b8bb1f3c..5213ebf713 100644 --- a/modules/ptcloud/src/slam/odometry/visual_odometry.cpp +++ b/modules/ptcloud/src/slam/odometry/visual_odometry.cpp @@ -174,20 +174,7 @@ void VisualOdometryImpl::matchFrames( if (qDesc.empty() || tDesc.empty()) return; if (qKp.empty() || tKp.empty()) return; - LightGlueMatcher* lg = dynamic_cast(matcher.get()); - if (lg) - { - Mat qk((int)qKp.size(), 2, CV_32F); - for (size_t i = 0; i < qKp.size(); ++i) - { qk.at((int)i,0) = qKp[i].pt.x; qk.at((int)i,1) = qKp[i].pt.y; } - - Mat tk((int)tKp.size(), 2, CV_32F); - for (size_t i = 0; i < tKp.size(); ++i) - { tk.at((int)i,0) = tKp[i].pt.x; tk.at((int)i,1) = tKp[i].pt.y; } - - lg->setPairInfo(qk, tk, qSz, tSz); - } - + matcher->setImagePairInfo(qKp, tKp, qSz, tSz); matcher->match(qDesc, tDesc, matches); } diff --git a/samples/slam/visual_odometry.cpp b/samples/slam/visual_odometry.cpp index 2e1242fd32..450c2c434f 100644 --- a/samples/slam/visual_odometry.cpp +++ b/samples/slam/visual_odometry.cpp @@ -3,15 +3,27 @@ // of this distribution and at http://opencv.org/license.html. // Copyright (C) 2026, BigVision LLC, all rights reserved. -#include +#include #include #include #include #include -using namespace cv; +#include "../dnn/common.hpp" -static const char* keys = +using namespace cv; +using namespace std; + +const string about = + "Monocular visual odometry using ALIKED + LightGlue.\n\n" + "Runs feature detection (ALIKED) and matching (LightGlue) from ONNX models over a\n" + "directory of images, estimates the camera trajectory and writes it to --output.\n" + "To run:\n" + "\t ./example_slam_visual_odometry --aliked=aliked.onnx --lightglue=lightglue.onnx --images=./seq\n" + "Sample command (run on the GPU):\n" + "\t ./example_slam_visual_odometry --aliked=aliked.onnx --lightglue=lightglue.onnx --images=./seq --target=cuda\n"; + +const string param_keys = "{ help h | | Print help message }" "{ aliked | | Path to ALIKED ONNX model }" "{ lightglue | | Path to LightGlue ONNX model }" @@ -24,11 +36,31 @@ static const char* keys = "{ min-parallax | 1.5 | Minimum initialisation parallax in degrees }" "{ min-points | 50 | Minimum initialisation map points }"; +const string backend_keys = format( + "{ backend | default | Choose one of computation backends: " + "default: automatically (by default), " + "openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), " + "opencv: OpenCV implementation, " + "vkcom: VKCOM, " + "cuda: CUDA, " + "webnn: WebNN }"); + +const string target_keys = format( + "{ target | cpu | Choose one of target computation devices: " + "cpu: CPU target (by default), " + "opencl: OpenCL, " + "opencl_fp16: OpenCL fp16 (half-float precision), " + "vpu: VPU, " + "vulkan: Vulkan, " + "cuda: CUDA, " + "cuda_fp16: CUDA fp16 (half-float preprocess) }"); + +string keys = param_keys + backend_keys + target_keys; + int main(int argc, char** argv) { CommandLineParser parser(argc, argv, keys); - parser.about("Monocular visual odometry using ALIKED + LightGlue\n" - " Example: visual_odometry --aliked=aliked.onnx --lightglue=lg.onnx --images=./seq\n"); + parser.about(about); if (parser.has("help")) { @@ -36,9 +68,11 @@ int main(int argc, char** argv) return 0; } - const String alikedPath = parser.get("aliked"); + const String alikedPath = parser.get("aliked"); const String lightgluePath = parser.get("lightglue"); - const String imagesDir = parser.get("images"); + const String imagesDir = parser.get("images"); + const int backendId = getBackendID(parser.get("backend")); + const int targetId = getTargetID(parser.get("target")); if (!parser.check() || alikedPath == "" || lightgluePath == "" || imagesDir == "") { @@ -56,11 +90,12 @@ int main(int argc, char** argv) ALIKED::Params detParams; detParams.inputSize = Size(640, 640); detParams.engine = dnn::ENGINE_NEW; + detParams.backend = backendId; + detParams.target = targetId; auto detector = ALIKED::create(alikedPath, detParams); auto matcher = LightGlueMatcher::create(lightgluePath, 0.0f, - dnn::DNN_BACKEND_DEFAULT, - dnn::DNN_TARGET_CPU); + backendId, targetId); slam::OdometryParams voParams; voParams.minInitParallaxDeg = parser.get("min-parallax");