From 4ba3d472ec1daad8bc9d23a63851dbf08eac52f5 Mon Sep 17 00:00:00 2001 From: inventshah <39803835+inventshah@users.noreply.github.com> Date: Mon, 21 Jul 2025 00:13:48 -0400 Subject: [PATCH 1/7] fix: mark distCoeffs/R/D as optional in calib3d functions --- .../typing_stubs_generation/api_refinement.py | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index cff54c602f..a7f0607dbf 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -2,7 +2,7 @@ __all__ = [ "apply_manual_api_refinement" ] -from typing import cast, Sequence, Callable, Iterable +from typing import cast, Sequence, Callable, Iterable, Optional from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode, ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode, @@ -93,19 +93,35 @@ def export_matrix_type_constants(root: NamespaceNode) -> None: ) -def make_optional_arg(arg_name: str) -> Callable[[NamespaceNode, SymbolName], None]: +def make_optional_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]: def _make_optional_arg(root_node: NamespaceNode, function_symbol_name: SymbolName) -> None: function = find_function_node(root_node, function_symbol_name) - for overload in function.overloads: - arg_idx = _find_argument_index(overload.arguments, arg_name) - # Avoid multiplying optional qualification - if isinstance(overload.arguments[arg_idx].type_node, OptionalTypeNode): - continue + for arg_name in arg_names: + found_overload_with_arg = False - overload.arguments[arg_idx].type_node = OptionalTypeNode( - cast(TypeNode, overload.arguments[arg_idx].type_node) - ) + for overload in function.overloads: + arg_idx = _find_argument_index(overload.arguments, arg_name) + + # skip overloads without this argument + if arg_idx is None: + continue + + # Avoid multiplying optional qualification + if isinstance(overload.arguments[arg_idx].type_node, OptionalTypeNode): + continue + + overload.arguments[arg_idx].type_node = OptionalTypeNode( + cast(TypeNode, overload.arguments[arg_idx].type_node) + ) + + found_overload_with_arg = True + + if not found_overload_with_arg: + raise RuntimeError( + f"Failed to find argument with name: '{arg_name}'" + f" in '{function_symbol_name.name}' overloads" + ) return _make_optional_arg @@ -327,13 +343,11 @@ def _trim_class_name_from_argument_types( def _find_argument_index(arguments: Sequence[FunctionNode.Arg], - name: str) -> int: + name: str) -> Optional[int]: for i, arg in enumerate(arguments): if arg.name == name: return i - raise RuntimeError( - f"Failed to find argument with name: '{name}' in {arguments}" - ) + return None NODES_TO_REFINE = { @@ -341,6 +355,23 @@ NODES_TO_REFINE = { SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), SymbolName(("cv", ), (), "floodFill"): make_optional_arg("mask"), SymbolName(("cv", ), ("Feature2D", ), "detectAndCompute"): make_optional_arg("mask"), + SymbolName(("cv", ), (), "findEssentialMat"): make_optional_arg( + "distCoeffs1", "distCoeffs2", "dist_coeff1", "dist_coeff2" + ), + SymbolName(("cv", ), (), "drawFrameAxes"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "getOptimalNewCameraMatrix"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "initInverseRectificationMap"): make_optional_arg("distCoeffs", "R"), + SymbolName(("cv", ), (), "initUndistortRectifyMap"): make_optional_arg("distCoeffs", "R"), + SymbolName(("cv", ), (), "projectPoints"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solveP3P"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solvePnP"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solvePnPGeneric"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solvePnPRansac"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solvePnPRefineLM"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "solvePnPRefineVVS"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "undistort"): make_optional_arg("distCoeffs"), + SymbolName(("cv", ), (), "undistortPoints"): make_optional_arg("distCoeffs"), + SymbolName(("cv", "fisheye"), (), "initUndistortRectifyMap"): make_optional_arg("D"), SymbolName(("cv", ), (), "imread"): make_optional_none_return, SymbolName(("cv", ), (), "imdecode"): make_optional_none_return, } From 8dc4ad3ff38ebe998dff29b322f4c3797061220a Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov <2536374+asmorkalov@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:05:37 +0300 Subject: [PATCH 2/7] Merge pull request #27607 from asmorkalov:as/apple_kleidicv KleidiCV support on Apple devices #27607 Scope: - Disabled bitcode generation in iPhone framework by default. - Enabled KleidiCV build for iPhone. - Added Github Actions log tags to group per-architecture builds and format logs. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- CMakeLists.txt | 2 +- platforms/ios/build_framework.py | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 150a018f8a..bb3e9bb0a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,7 +228,7 @@ OCV_OPTION(WITH_CAP_IOS "Enable iOS video capture" ON OCV_OPTION(WITH_CAROTENE "Use NVidia carotene acceleration library for ARM platform" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF (ARM OR AARCH64) AND NOT IOS AND NOT XROS) OCV_OPTION(WITH_KLEIDICV "Use KleidiCV library for ARM platforms" (ANDROID AND AARCH64 AND NOT CV_DISABLE_OPTIMIZATION) - VISIBLE_IF (AARCH64 AND (ANDROID OR UNIX AND NOT IOS AND NOT XROS))) + VISIBLE_IF (AARCH64 AND (ANDROID OR UNIX))) OCV_OPTION(WITH_NDSRVP "Use Andes RVP extension" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) OCV_OPTION(WITH_HAL_RVV "Use HAL RVV optimizations" (NOT CV_DISABLE_OPTIMIZATION) diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index 1904a67ae7..cc7be20384 100755 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -50,7 +50,7 @@ CURRENT_FILE_DIR = os.path.dirname(__file__) class Builder: - def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name, run_tests, build_docs, swiftdisabled): + def __init__(self, opencv, contrib, dynamic, embed_bitcode, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name, run_tests, build_docs, swiftdisabled): self.opencv = os.path.abspath(opencv) self.contrib = None if contrib: @@ -60,7 +60,7 @@ class Builder: else: print("Note: contrib repository is bad - modules subfolder not found", file=sys.stderr) self.dynamic = dynamic - self.bitcodedisabled = bitcodedisabled + self.embed_bitcode = embed_bitcode self.exclude = exclude self.build_objc_wrapper = not "objc" in self.exclude self.disable = disable @@ -112,7 +112,7 @@ class Builder: cmake_flags = [] if self.contrib: cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib) - if xcode_ver >= 7 and target[1] == 'iPhoneOS' and self.bitcodedisabled == False: + if xcode_ver >= 7 and target[1] == 'iPhoneOS' and self.embed_bitcode: cmake_flags.append("-DCMAKE_C_FLAGS=-fembed-bitcode") cmake_flags.append("-DCMAKE_CXX_FLAGS=-fembed-bitcode") if xcode_ver >= 7 and target[1] == 'Catalyst': @@ -123,7 +123,7 @@ class Builder: "-iframework %s/System/iOSSupport/System/Library/Frameworks" % sdk_path, "-isystem %s/System/iOSSupport/usr/include" % sdk_path, ] - if self.bitcodedisabled == False: + if self.embed_bitcode: c_flags.append("-fembed-bitcode") cmake_flags.append("-DCMAKE_C_FLAGS=" + " ".join(c_flags)) cmake_flags.append("-DCMAKE_CXX_FLAGS=" + " ".join(c_flags)) @@ -139,12 +139,20 @@ class Builder: cmake_flags.append("-DCMAKE_OSX_SYSROOT=%s" % sdk_path) cmake_flags.append("-DCMAKE_CXX_COMPILER_WORKS=TRUE") cmake_flags.append("-DCMAKE_C_COMPILER_WORKS=TRUE") + + print("::group::Building target", target[0], target[1], flush=True) self.buildOne(target[0], target[1], main_build_dir, cmake_flags) + print("::endgroup::", flush=True) if not self.dynamic: + print("::group::Merge libs", target[0], target[1], flush=True) self.mergeLibs(main_build_dir) + print("::endgroup::", flush=True) else: + print("::group::Make dynamic lib", target[0], target[1], flush=True) self.makeDynamicLib(main_build_dir) + print("::endgroup::", flush=True) + self.makeFramework(outdir, dirs) if self.build_objc_wrapper: if self.run_tests: @@ -232,7 +240,7 @@ class Builder: "xcodebuild", ] - if (self.dynamic or self.build_objc_wrapper) and not self.bitcodedisabled and target == "iPhoneOS": + if (self.dynamic or self.build_objc_wrapper) and self.embed_bitcode and target == "iPhoneOS": buildcmd.append("BITCODE_GENERATION_MODE=bitcode") buildcmd += [ @@ -364,7 +372,7 @@ class Builder: link_target = "%s-apple-ios14.0-macabi" % target[:target.find("-")] else: link_target = "%s-apple-darwin" % target[:target.find("-")] - bitcode_flags = ["-fembed-bitcode", "-Xlinker", "-bitcode_verify"] if is_device and not self.bitcodedisabled else [] + bitcode_flags = ["-fembed-bitcode", "-Xlinker", "-bitcode_verify"] if is_device and self.embed_bitcode else [] toolchain_dir = get_xcode_setting("TOOLCHAIN_DIR", builddir) sdk_dir = get_xcode_setting("SDK_DIR", builddir) framework_options = [] @@ -532,7 +540,7 @@ if __name__ == "__main__": parser.add_argument('--without', metavar='MODULE', default=[], action='append', help='OpenCV modules to exclude from the framework. To exclude multiple, specify this flag again, e.g. "--without video --without objc"') parser.add_argument('--disable', metavar='FEATURE', default=[], action='append', help='OpenCV features to disable (add WITH_*=OFF). To disable multiple, specify this flag again, e.g. "--disable tbb --disable openmp"') parser.add_argument('--dynamic', default=False, action='store_true', help='build dynamic framework (default is "False" - builds static framework)') - parser.add_argument('--disable-bitcode', default=False, dest='bitcodedisabled', action='store_true', help='disable bitcode (enabled by default)') + parser.add_argument('--embed_bitcode', default=False, dest='embed_bitcode', action='store_true', help='disable bitcode (enabled by default)') parser.add_argument('--iphoneos_deployment_target', default=os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', IPHONEOS_DEPLOYMENT_TARGET), help='specify IPHONEOS_DEPLOYMENT_TARGET') parser.add_argument('--build_only_specified_archs', default=False, action='store_true', help='if enabled, only directly specified archs are built and defaults are ignored') parser.add_argument('--iphoneos_archs', default=None, help='select iPhoneOS target ARCHS. Default is "armv7,armv7s,arm64"') @@ -598,6 +606,6 @@ if __name__ == "__main__": if iphonesimulator_archs: targets.append((iphonesimulator_archs, "iPhoneSimulator")) - b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs, args.swiftdisabled) + b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.embed_bitcode, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs, args.swiftdisabled) b.build(args.out) From 1dd06eb0da3fc96bc030c6bd0fb4584d220d71a2 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Fri, 1 Aug 2025 09:49:43 +0900 Subject: [PATCH 3/7] imgcodecs: png: adjust value range for IMWRITE_PNG_ZLIBBUFFER_SIZE --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 2 +- modules/imgcodecs/src/grfmt_png.cpp | 6 +++++- modules/imgcodecs/src/grfmt_spng.cpp | 6 ++++++ modules/imgcodecs/test/test_png.cpp | 20 +++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 039482017f..841d5b32cf 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -97,7 +97,7 @@ enum ImwriteFlags { IMWRITE_PNG_STRATEGY = 17, //!< For PNG, One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE. IMWRITE_PNG_BILEVEL = 18, //!< For PNG, Binary level PNG, 0 or 1, default is 0. IMWRITE_PNG_FILTER = 19, //!< For PNG, One of cv::ImwritePNGFilterFlags, default is IMWRITE_PNG_FILTER_SUB. - IMWRITE_PNG_ZLIBBUFFER_SIZE = 20, //!< For PNG, sets the size of the internal zlib compression buffer in bytes. + IMWRITE_PNG_ZLIBBUFFER_SIZE = 20, //!< For PNG with libpng, sets the size of the internal zlib compression buffer in bytes, from 6 to 1048576(1024 KiB). Default is 8192(8 KiB). For normal use, 131072(128 KiB) or 262144(256 KiB) may be sufficient. If WITH_SPNG=ON, it is not supported. IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1. IMWRITE_EXR_TYPE = (3 << 4) + 0 /* 48 */, //!< override EXR storage type (FLOAT (FP32) is default) IMWRITE_EXR_COMPRESSION = (3 << 4) + 1 /* 49 */, //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 55e5f7ff11..4d3d8f9d8d 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -1007,7 +1007,11 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) break; case IMWRITE_PNG_ZLIBBUFFER_SIZE: - png_set_compression_buffer_size(png_ptr, params[i+1]); + // The default value is 8 KiB. + // The minimum limit is 6, which is from from https://github.com/opencv/opencv/blob/4.12.0/3rdparty/libpng/pngset.c#L1600 . + // The maximum limit is 1 MiB, which has been provisionally set. libpng limitation is 2 GiB(INT32_MAX), but it is too large. + // For normal use, 128 or 256 KiB may be sufficient. See https://zlib.net/zlib_how.html . + png_set_compression_buffer_size(png_ptr, MIN(MAX(params[i+1],6), 1024*1024)); break; default: diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index 8057af1222..e2f6f02bb9 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -23,6 +23,7 @@ #include #include "grfmt_spng.hpp" +#include /* * libspng does not support RGB -> Gray conversion. In order to decode colorful images as grayscale @@ -554,6 +555,11 @@ bool SPngEncoder::write(const Mat &img, const std::vector ¶ms) filter = params[i+1]; set_filter = true; } + if( params[i] == IMWRITE_PNG_ZLIBBUFFER_SIZE ) + { + // See https://libspng.org/docs/migrate-libpng/#miscellaneous-functions + CV_LOG_WARNING(nullptr, "libspng does not support png_set_compression_buffer_size() which is required for IMWRITE_PNG_ZLIBBUFFER_SIZE"); + } } ihdr.bit_depth = depth == CV_8U ? isBilevel ? 1 : 8 : 16; diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index 0d2512fd20..967b852d76 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -569,6 +569,26 @@ INSTANTIATE_TEST_CASE_P(/**/, make_tuple("../perf/512x512.png", 8, 153708), make_tuple("../perf/512x512.png", 9, 152181))); +// See https://github.com/opencv/opencv/issues/27614 +typedef testing::TestWithParam Imgcodecs_Png_ZLIBBUFFER_SIZE; +TEST_P(Imgcodecs_Png_ZLIBBUFFER_SIZE, encode_regression_27614) +{ + Mat img(320,240,CV_8UC3,cv::Scalar(64,76,43)); + vector buff; + bool status = false; + ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, GetParam() })); + ASSERT_TRUE(status); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_ZLIBBUFFER_SIZE, + testing::Values(5, + 6, // Minimum limit + 8192, // Default value + 131072, // 128 KiB + 262144, // 256 KiB + 1048576, // Maximum limit + 1048577)); + #endif // HAVE_PNG }} // namespace From 83e352dc48557551d8f517ffda55e47a24e34e59 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 1 Aug 2025 09:50:10 +0300 Subject: [PATCH 4/7] Added OpenCV.AI copyright. --- COPYRIGHT | 1 + 1 file changed, 1 insertion(+) diff --git a/COPYRIGHT b/COPYRIGHT index 6b0b6882f6..23eb02ef08 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -7,5 +7,6 @@ Copyright (C) 2008-2016, Itseez Inc., all rights reserved. Copyright (C) 2019-2023, Xperience AI, all rights reserved. Copyright (C) 2019-2022, Shenzhen Institute of Artificial Intelligence and Robotics for Society, all rights reserved. Copyright (C) 2022-2023, Southern University of Science And Technology, all rights reserved. +Copyright (C) 2023-2025, OpenCV AI, all rights reserved. Third party copyrights are property of their respective owners. From 84967062162cce1eced994fd7f0560c7c2092af4 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 31 Jul 2025 12:46:58 +0300 Subject: [PATCH 5/7] Added timestamps support for Orbbec SDK backend in VideoIO. --- modules/videoio/include/opencv2/videoio.hpp | 2 ++ .../videoio/src/cap_obsensor_liborbbec.cpp | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index d618874c4a..b9a490bd0f 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -714,6 +714,8 @@ enum VideoCaptureOBSensorProperties{ CAP_PROP_OBSENSOR_INTRINSIC_FY=26002, CAP_PROP_OBSENSOR_INTRINSIC_CX=26003, CAP_PROP_OBSENSOR_INTRINSIC_CY=26004, + CAP_PROP_OBSENSOR_RGB_POS_MSEC=26005, + CAP_PROP_OBSENSOR_DEPTH_POS_MSEC=26006, }; //! @} OBSENSOR diff --git a/modules/videoio/src/cap_obsensor_liborbbec.cpp b/modules/videoio/src/cap_obsensor_liborbbec.cpp index ac581d81dc..5bb4a925d3 100644 --- a/modules/videoio/src/cap_obsensor_liborbbec.cpp +++ b/modules/videoio/src/cap_obsensor_liborbbec.cpp @@ -83,7 +83,31 @@ double VideoCapture_obsensor::getProperty(int propIdx) const case CAP_PROP_OBSENSOR_INTRINSIC_CY: rst = camParam.p1[3]; break; + case CAP_PROP_POS_MSEC: + case CAP_PROP_OBSENSOR_RGB_POS_MSEC: + if (grabbedColorFrame) + { + rst = grabbedColorFrame->globalTimeStampUs(); + if (rst == 0.0) + { + CV_LOG_ONCE_WARNING(NULL, "Camera reports zero global timestamp. System timestamp is used instead."); + rst = grabbedColorFrame->systemTimeStamp(); + } + } + break; + case CAP_PROP_OBSENSOR_DEPTH_POS_MSEC: + if (grabbedDepthFrame) + { + rst = grabbedDepthFrame->systemTimeStamp(); + if (rst == 0.0) + { + CV_LOG_ONCE_WARNING(NULL, "Camera reports zero global timestamp. System timestamp is used instead."); + rst = grabbedDepthFrame->systemTimeStamp(); + } + } + break; } + return rst; } From 243f74cc8cbac3fc86bd699499af3a218ca08fc2 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Sun, 3 Aug 2025 17:00:26 +0900 Subject: [PATCH 6/7] doc: Enable GIF support by default on documentation --- .../introduction/config_reference/config_reference.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index 67a2c60863..b8b82b19b0 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -315,7 +315,7 @@ Following formats can be read by OpenCV without help of any third-party library: | [Sun Raster](https://en.wikipedia.org/wiki/Sun_Raster) | `WITH_IMGCODEC_SUNRASTER` | _ON_ | | [PPM, PGM, PBM, PAM](https://en.wikipedia.org/wiki/Netpbm#File_formats) | `WITH_IMGCODEC_PXM` | _ON_ | | [PFM](https://en.wikipedia.org/wiki/Netpbm#File_formats) | `WITH_IMGCODEC_PFM` | _ON_ | -| [GIF](https://en.wikipedia.org/wiki/GIF) | `WITH_IMGCODEC_GIF` | _OFF_ | +| [GIF](https://en.wikipedia.org/wiki/GIF) | `WITH_IMGCODEC_GIF` | _ON_ | ### PNG, JPEG, TIFF, WEBP, JPEG 2000, EXR, JPEG XL support From 9f88e9ae315b5908c609eca8182bc9417a7f5f5f Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 5 Aug 2025 11:19:35 +0300 Subject: [PATCH 7/7] Added options to initialize Orbbec cameras with custom fps and resolution. --- modules/videoio/include/opencv2/videoio.hpp | 3 + modules/videoio/src/cap_interface.hpp | 2 +- modules/videoio/src/cap_obsensor_capture.cpp | 82 +++++++++++++------ modules/videoio/src/cap_obsensor_capture.hpp | 2 +- .../videoio/src/cap_obsensor_liborbbec.cpp | 59 +++++++++++-- .../videoio/src/cap_obsensor_liborbbec.hpp | 2 +- samples/cpp/videocapture_obsensor.cpp | 64 ++++++++++++++- 7 files changed, 177 insertions(+), 37 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index b9a490bd0f..a3ce5dc6e9 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -716,6 +716,9 @@ enum VideoCaptureOBSensorProperties{ CAP_PROP_OBSENSOR_INTRINSIC_CY=26004, CAP_PROP_OBSENSOR_RGB_POS_MSEC=26005, CAP_PROP_OBSENSOR_DEPTH_POS_MSEC=26006, + CAP_PROP_OBSENSOR_DEPTH_WIDTH=26007, + CAP_PROP_OBSENSOR_DEPTH_HEIGHT=26008, + CAP_PROP_OBSENSOR_DEPTH_FPS=26009 }; //! @} OBSENSOR diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index 58cada06d3..bb1e0b4ec9 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -400,7 +400,7 @@ Ptr createAndroidVideoWriter(const std::string& filename, int four double fps, const Size& frameSize, const VideoWriterParameters& params); -Ptr create_obsensor_capture(int index); +Ptr create_obsensor_capture(int index, const cv::VideoCaptureParameters& params); bool VideoCapture_V4L_waitAny( const std::vector& streams, diff --git a/modules/videoio/src/cap_obsensor_capture.cpp b/modules/videoio/src/cap_obsensor_capture.cpp index 10e457f7fd..4b59ff26a8 100644 --- a/modules/videoio/src/cap_obsensor_capture.cpp +++ b/modules/videoio/src/cap_obsensor_capture.cpp @@ -23,15 +23,20 @@ #include "cap_obsensor_capture.hpp" #include "cap_obsensor/obsensor_stream_channel_interface.hpp" +#include + +#define OB_WIDTH_ANY 0 +#define OB_HEIGHT_ANY 0 +#define OB_FPS_ANY 0 #if defined(HAVE_OBSENSOR) && !defined(HAVE_OBSENSOR_ORBBEC_SDK) namespace cv { -Ptr create_obsensor_capture(int index) +Ptr create_obsensor_capture(int index, const cv::VideoCaptureParameters& params) { - return makePtr(index); + return makePtr(index, params); } -VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) +VideoCapture_obsensor::VideoCapture_obsensor(int index, const cv::VideoCaptureParameters& params) : isOpened_(false) { static const obsensor::StreamProfile colorProfile = { 640, 480, 30, obsensor::FRAME_FORMAT_MJPG }; static const obsensor::StreamProfile depthProfile = { 640, 480, 30, obsensor::FRAME_FORMAT_Y16 }; @@ -55,15 +60,23 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) { case obsensor::OBSENSOR_STREAM_COLOR: { - auto profile = colorProfile; - if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){ - profile = megaColorProfile; - }else if(OBSENSOR_GEMINI2L_PID == channel->getPid()){ - profile = gemini2lColorProfile; - }else if(OBSENSOR_ASTRA2_PID == channel->getPid()){ - profile = astra2ColorProfile; - }else if(OBSENSOR_GEMINI2XL_PID == channel->getPid()){ - profile = gemini2XlColorProfile; + uint32_t color_width = params.get(CAP_PROP_FRAME_WIDTH, OB_WIDTH_ANY); + uint32_t color_height = params.get(CAP_PROP_FRAME_HEIGHT, OB_HEIGHT_ANY); + uint32_t color_fps = params.get(CAP_PROP_FPS, OB_FPS_ANY); + + obsensor::StreamProfile profile = colorProfile; + if (color_width != OB_WIDTH_ANY || color_height != OB_HEIGHT_ANY || color_fps != OB_FPS_ANY) { + profile = { color_width, color_height, color_fps, obsensor::FRAME_FORMAT_MJPG }; + } else { + if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){ + profile = megaColorProfile; + }else if(OBSENSOR_GEMINI2L_PID == channel->getPid()){ + profile = gemini2lColorProfile; + }else if(OBSENSOR_ASTRA2_PID == channel->getPid()){ + profile = astra2ColorProfile; + }else if(OBSENSOR_GEMINI2XL_PID == channel->getPid()){ + profile = gemini2XlColorProfile; + } } channel->start(profile, [&](obsensor::Frame* frame) { std::unique_lock lk(frameMutex_); @@ -77,17 +90,25 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) uint8_t data = 1; channel->setProperty(obsensor::DEPTH_TO_COLOR_ALIGN, &data, 1); + uint32_t depth_width = params.get(CAP_PROP_OBSENSOR_DEPTH_WIDTH, OB_WIDTH_ANY); + uint32_t depth_height = params.get(CAP_PROP_OBSENSOR_DEPTH_HEIGHT, OB_HEIGHT_ANY); + uint32_t depth_fps = params.get(CAP_PROP_OBSENSOR_DEPTH_FPS, OB_FPS_ANY); + obsensor::StreamProfile profile = depthProfile; - if(OBSENSOR_GEMINI2_PID == channel->getPid()){ - profile = gemini2DepthProfile; - }else if(OBSENSOR_ASTRA2_PID == channel->getPid()){ - profile = astra2DepthProfile; - }else if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){ - profile = megaDepthProfile; - }else if(OBSENSOR_GEMINI2L_PID == channel->getPid()){ - profile = gemini2lDepthProfile; - }else if(OBSENSOR_GEMINI2XL_PID == channel->getPid()){ - profile = gemini2XlDepthProfile; + if (depth_width != OB_WIDTH_ANY || depth_height != OB_HEIGHT_ANY || depth_fps != OB_FPS_ANY) { + profile = { depth_width, depth_height, depth_fps, obsensor::FRAME_FORMAT_Y16 }; + } else { + if(OBSENSOR_GEMINI2_PID == channel->getPid()){ + profile = gemini2DepthProfile; + }else if(OBSENSOR_ASTRA2_PID == channel->getPid()){ + profile = astra2DepthProfile; + }else if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){ + profile = megaDepthProfile; + }else if(OBSENSOR_GEMINI2L_PID == channel->getPid()){ + profile = gemini2lDepthProfile; + }else if(OBSENSOR_GEMINI2XL_PID == channel->getPid()){ + profile = gemini2XlDepthProfile; + } } channel->start(profile, [&](obsensor::Frame* frame) { std::unique_lock lk(frameMutex_); @@ -218,7 +239,22 @@ double VideoCapture_obsensor::getProperty(int propIdx) const { bool VideoCapture_obsensor::setProperty(int propIdx, double /*propVal*/) { - CV_LOG_WARNING(NULL, "Unsupported or read only property, id=" << propIdx); + switch(propIdx) + { + case CAP_PROP_OBSENSOR_DEPTH_WIDTH: + case CAP_PROP_OBSENSOR_DEPTH_HEIGHT: + case CAP_PROP_OBSENSOR_DEPTH_FPS: + CV_LOG_WARNING(NULL, "CAP_PROP_OBSENSOR_DEPTH_WIDTH, CAP_PROP_OBSENSOR_DEPTH_HEIGHT, CAP_PROP_OBSENSOR_DEPTH_FPS options are supported during camera initialization only"); + break; + case CAP_PROP_FRAME_WIDTH: + case CAP_PROP_FRAME_HEIGHT: + case CAP_PROP_FPS: + CV_LOG_WARNING(NULL, "CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FPS options are supported during camera initialization only"); + break; + default: + CV_LOG_WARNING(NULL, "Unsupported or read only property, id=" << propIdx); + } + return false; } diff --git a/modules/videoio/src/cap_obsensor_capture.hpp b/modules/videoio/src/cap_obsensor_capture.hpp index 89ab403bb2..bd01050626 100644 --- a/modules/videoio/src/cap_obsensor_capture.hpp +++ b/modules/videoio/src/cap_obsensor_capture.hpp @@ -34,7 +34,7 @@ namespace cv { class VideoCapture_obsensor : public IVideoCapture { public: - VideoCapture_obsensor(int index); + VideoCapture_obsensor(int index, const cv::VideoCaptureParameters& params); virtual ~VideoCapture_obsensor(); virtual double getProperty(int propIdx) const CV_OVERRIDE; diff --git a/modules/videoio/src/cap_obsensor_liborbbec.cpp b/modules/videoio/src/cap_obsensor_liborbbec.cpp index 5bb4a925d3..40d8ba283f 100644 --- a/modules/videoio/src/cap_obsensor_liborbbec.cpp +++ b/modules/videoio/src/cap_obsensor_liborbbec.cpp @@ -28,23 +28,52 @@ namespace cv { -Ptr create_obsensor_capture(int index) +Ptr create_obsensor_capture(int index, const cv::VideoCaptureParameters& params) { - return makePtr(index); + return makePtr(index, params); } -VideoCapture_obsensor::VideoCapture_obsensor(int) +VideoCapture_obsensor::VideoCapture_obsensor(int, const cv::VideoCaptureParameters& params) { ob::Context::setLoggerToFile(OB_LOG_SEVERITY_OFF, ""); config = std::make_shared(); pipe = std::make_shared(); + + int color_width = params.get(CAP_PROP_FRAME_WIDTH, OB_WIDTH_ANY); + int color_height = params.get(CAP_PROP_FRAME_HEIGHT, OB_HEIGHT_ANY); + int color_fps = params.get(CAP_PROP_FPS, OB_FPS_ANY); + auto colorProfiles = pipe->getStreamProfileList(OB_SENSOR_COLOR); - auto colorProfile = colorProfiles->getProfile(OB_PROFILE_DEFAULT); - config->enableStream(colorProfile->as()); + if (color_width == OB_WIDTH_ANY && color_height == OB_HEIGHT_ANY && color_fps == OB_FPS_ANY) + { + CV_LOG_INFO(NULL, "Use default color stream profile"); + auto colorProfile = colorProfiles->getProfile(OB_PROFILE_DEFAULT); + config->enableStream(colorProfile->as()); + } + else + { + CV_LOG_INFO(NULL, "Looking for custom color profile " << color_width << "x" << color_height << "@" << color_fps << " fps"); + auto colorProfile = colorProfiles->getVideoStreamProfile(color_width, color_height, OB_FORMAT_MJPG, color_fps); + config->enableStream(colorProfile->as()); + } + + int depth_width = params.get(CAP_PROP_OBSENSOR_DEPTH_WIDTH, OB_WIDTH_ANY); + int depth_height = params.get(CAP_PROP_OBSENSOR_DEPTH_HEIGHT, OB_HEIGHT_ANY); + int depth_fps = params.get(CAP_PROP_OBSENSOR_DEPTH_FPS, OB_FPS_ANY); auto depthProfiles = pipe->getStreamProfileList(OB_SENSOR_DEPTH); - auto depthProfile = depthProfiles->getProfile(OB_PROFILE_DEFAULT); - config->enableStream(depthProfile->as()); + if (depth_width == OB_WIDTH_ANY && depth_height == OB_HEIGHT_ANY && depth_fps == OB_FPS_ANY) + { + CV_LOG_INFO(NULL, "Use default depth stream profile"); + auto depthProfile = depthProfiles->getProfile(OB_PROFILE_DEFAULT); + config->enableStream(depthProfile->as()); + } + else + { + CV_LOG_INFO(NULL, "Looking for custom color profile " << depth_width << "x" << depth_height << "@" << depth_fps << " fps"); + auto depthProfile = depthProfiles->getVideoStreamProfile(depth_width, depth_height, OB_FORMAT_Y14, depth_fps); + config->enableStream(depthProfile->as()); + } config->setAlignMode(ALIGN_D2C_SW_MODE); @@ -111,8 +140,22 @@ double VideoCapture_obsensor::getProperty(int propIdx) const return rst; } -bool VideoCapture_obsensor::setProperty(int, double) +bool VideoCapture_obsensor::setProperty(int prop, double) { + switch(prop) + { + case CAP_PROP_OBSENSOR_DEPTH_WIDTH: + case CAP_PROP_OBSENSOR_DEPTH_HEIGHT: + case CAP_PROP_OBSENSOR_DEPTH_FPS: + CV_LOG_WARNING(NULL, "CAP_PROP_OBSENSOR_DEPTH_WIDTH, CAP_PROP_OBSENSOR_DEPTH_HEIGHT, CAP_PROP_OBSENSOR_DEPTH_FPS options are supported during camera initialization only"); + break; + case CAP_PROP_FRAME_WIDTH: + case CAP_PROP_FRAME_HEIGHT: + case CAP_PROP_FPS: + CV_LOG_WARNING(NULL, "CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FPS options are supported during camera initialization only"); + break; + } + return false; } diff --git a/modules/videoio/src/cap_obsensor_liborbbec.hpp b/modules/videoio/src/cap_obsensor_liborbbec.hpp index 13dbf413ca..44df040030 100644 --- a/modules/videoio/src/cap_obsensor_liborbbec.hpp +++ b/modules/videoio/src/cap_obsensor_liborbbec.hpp @@ -46,7 +46,7 @@ struct CameraParam class VideoCapture_obsensor : public IVideoCapture { public: - VideoCapture_obsensor(int index); + VideoCapture_obsensor(int index, const cv::VideoCaptureParameters& params); virtual ~VideoCapture_obsensor(); virtual double getProperty(int propIdx) const CV_OVERRIDE; diff --git a/samples/cpp/videocapture_obsensor.cpp b/samples/cpp/videocapture_obsensor.cpp index ce71a1808b..03f0c49381 100644 --- a/samples/cpp/videocapture_obsensor.cpp +++ b/samples/cpp/videocapture_obsensor.cpp @@ -8,10 +8,68 @@ #include using namespace cv; -int main() +int main(int argc, char** argv) { - VideoCapture obsensorCapture(0, CAP_OBSENSOR); - if(!obsensorCapture.isOpened()){ + cv::CommandLineParser parser(argc, argv, + "{help h ? | | help message}" + "{dw | | depth width }" + "{dh | | depth height }" + "{df | | depth fps }" + "{cw | | color width }" + "{ch | | color height }" + "{cf | | depth fps }" + + ); + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + + std::vector params; + if (parser.has("dw")) + { + params.push_back(CAP_PROP_OBSENSOR_DEPTH_WIDTH); + params.push_back(parser.get("dw")); + } + + if (parser.has("dh")) + { + params.push_back(CAP_PROP_OBSENSOR_DEPTH_HEIGHT); + params.push_back(parser.get("dh")); + } + + if (parser.has("df")) + { + params.push_back(CAP_PROP_OBSENSOR_DEPTH_FPS); + params.push_back(parser.get("df")); + } + + if (parser.has("cw")) + { + params.push_back(CAP_PROP_FRAME_WIDTH); + params.push_back(parser.get("cw")); + } + + if (parser.has("ch")) + { + params.push_back(CAP_PROP_FRAME_HEIGHT); + params.push_back(parser.get("ch")); + } + + if (parser.has("cf")) + { + params.push_back(CAP_PROP_FPS); + params.push_back(parser.get("cf")); + } + + VideoCapture obsensorCapture; + + if (params.empty()) + obsensorCapture.open(0, CAP_OBSENSOR); + else + obsensorCapture.open(0, CAP_OBSENSOR, params); + if(!obsensorCapture.isOpened()) { std::cerr << "Failed to open obsensor capture! Index out of range or no response from device"; return -1; }