From 46ee0f79540d4a88e383f4883e76bb7b36f0613b Mon Sep 17 00:00:00 2001 From: Alex <117711477+0lekW@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:18:50 +1200 Subject: [PATCH] Merge pull request #28844 from 0lekW:videoio-image-seq-start Add CAP_PROP_IMAGE_SEQ_START #28844 Resolves https://github.com/opencv/opencv/issues/28843 New CAP_PROP_IMAGE_SEQ_START as an open only property for setting the first frame index when a VideoCap is opening with pattern, squashes test_images TODO. `CAP_FFMPEG`: forwarded to the image2 demuxers "start_number" option. `CAP_IMAGES`: Now a WithParams variant so the value reaches open() before probe runs, replaces 0 or 1 auto probe for sequence start. Property is opt in so default behavior is not affected. Please note that G_STREAMER is not considered here as I think its out of scope, it already has a "start-index" property and its backend is driven by the pipeline strings rather then these printf patterns. If an alternative solution is preferred that will not involve a new property or promoting CAP_IMAGES to a "WithParams" variant I don't mind implementing. ### 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 - [x] The PR is proposed to the proper branch - [x] 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. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/videoio/include/opencv2/videoio.hpp | 1 + modules/videoio/src/cap_ffmpeg_impl.hpp | 10 +++++ modules/videoio/src/cap_images.cpp | 31 ++++++++----- modules/videoio/src/cap_interface.hpp | 2 +- modules/videoio/test/test_images.cpp | 48 ++++++++++++++++++++- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index b917028042..e74e4f420d 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -210,6 +210,7 @@ enum VideoCaptureProperties { CAP_PROP_N_THREADS = 70, //!< (**open-only**) Set the maximum number of threads to use. Use 0 to use as many threads as CPU cores (applicable for FFmpeg back-end only). CAP_PROP_PTS = 71, //!< (read-only) FFmpeg back-end only - presentation timestamp of the most recently read frame using the FPS time base. e.g. fps = 25, VideoCapture::get(\ref CAP_PROP_PTS) = 3, presentation time = 3/25 seconds. CAP_PROP_DTS_DELAY = 72, //!< (read-only) FFmpeg back-end only - maximum difference between presentation (pts) and decompression timestamps (dts) using FPS time base. e.g. delay is maximum when frame_num = 0, if true, VideoCapture::get(\ref CAP_PROP_PTS) = 0 and VideoCapture::get(\ref CAP_PROP_DTS_DELAY) = 2, dts = -2. Non zero values usually imply the stream is encoded using B-frames which are not decoded in presentation order. + CAP_PROP_IMAGE_SEQ_START = 73, //!< (**open-only**) Start number for image sequences opened with a printf-style pattern (e.g. `frame_%05d.dpx`). Sets the initial frame number and disables automatic first-frame detection. Applicable to \ref CAP_FFMPEG (passed as the image2 demuxer `start_number`) and \ref CAP_IMAGES backends. Default: not set (automatic detection). #ifndef CV_DOXYGEN CV__CAP_PROP_LATEST #endif diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index b892bd561b..83ebfa7ffa 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -624,6 +624,7 @@ struct CvCapture_FFMPEG int use_opencl; int extraDataIdx; int requestedThreads; + int image_seq_start; // image2 demuxer start_number; -1 means unset }; void CvCapture_FFMPEG::init() @@ -679,6 +680,7 @@ void CvCapture_FFMPEG::init() use_opencl = 0; extraDataIdx = 1; requestedThreads = cv::getNumberOfCPUs(); + image_seq_start = -1; } @@ -1160,6 +1162,10 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptr(CAP_PROP_N_THREADS); } + if (params.has(CAP_PROP_IMAGE_SEQ_START)) + { + image_seq_start = params.get(CAP_PROP_IMAGE_SEQ_START); + } if (params.warnUnusedParameters()) { CV_LOG_ERROR(NULL, "VIDEOIO/FFMPEG: unsupported parameters in .open(), see logger INFO channel for details. Bailout"); @@ -1194,6 +1200,10 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptr= 0) + { + av_dict_set_int(&dict, "start_number", image_seq_start, 0); + } CV_FFMPEG_FMT_CONST AVInputFormat* input_format = NULL; AVDictionaryEntry* entry = av_dict_get(dict, "input_format", NULL, 0); if (entry != 0) diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index 1d7190003e..99de13889e 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -77,11 +77,6 @@ public: { init(); } - CvCapture_Images(const String& _filename) - { - init(); - open(_filename); - } virtual ~CvCapture_Images() CV_OVERRIDE { @@ -94,7 +89,7 @@ public: virtual bool isOpened() const CV_OVERRIDE; virtual int getCaptureDomain() /*const*/ CV_OVERRIDE { return cv::CAP_IMAGES; } - bool open(const String&); + bool open(const String&, int image_seq_start = -1); void close(); protected: std::string filename_pattern; // actually a printf-pattern @@ -285,13 +280,16 @@ std::string icvExtractPattern(const std::string& filename, unsigned *offset) } -bool CvCapture_Images::open(const std::string& _filename) +bool CvCapture_Images::open(const std::string& _filename, int image_seq_start) { unsigned offset = 0; close(); CV_Assert(!_filename.empty()); filename_pattern = icvExtractPattern(_filename, &offset); + const bool explicit_start = (image_seq_start >= 0); + if (explicit_start) + offset = static_cast(image_seq_start); if (filename_pattern.empty()) { filename_pattern = _filename; @@ -317,7 +315,7 @@ bool CvCapture_Images::open(const std::string& _filename) cv::String filename = cv::format(filename_pattern.c_str(), (int)(offset + length)); if (!utils::fs::exists(filename)) { - if (length == 0 && offset == 0) // allow starting with 0 or 1 + if (length == 0 && offset == 0 && !explicit_start) // allow starting with 0 or 1 { offset++; continue; @@ -355,9 +353,22 @@ bool CvCapture_Images::isOpened() const return !filename_pattern.empty(); } -Ptr create_Images_capture(const std::string &filename) +Ptr create_Images_capture(const std::string& filename, const VideoCaptureParameters& params) { - return makePtr(filename); + int image_seq_start = -1; + if (params.has(CAP_PROP_IMAGE_SEQ_START)) + { + image_seq_start = params.get(CAP_PROP_IMAGE_SEQ_START); + } + if (params.warnUnusedParameters()) + { + CV_LOG_ERROR(NULL, "VIDEOIO/IMAGES: unsupported parameters in .open(), see logger INFO channel for details. Bailout"); + return Ptr(); + } + Ptr cap = makePtr(); + if (!cap->open(filename, image_seq_start)) + return Ptr(); + return cap; } // diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index ba50f6dcad..e10c908d07 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -367,7 +367,7 @@ Ptr create_V4L_capture_file(const std::string &filename); Ptr create_OpenNI2_capture_cam( int index ); Ptr create_OpenNI2_capture_file( const std::string &filename ); -Ptr create_Images_capture(const std::string &filename); +Ptr create_Images_capture(const std::string& filename, const VideoCaptureParameters& params); Ptr create_Images_writer(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); diff --git a/modules/videoio/test/test_images.cpp b/modules/videoio/test/test_images.cpp index c3782f2c5d..255917950e 100644 --- a/modules/videoio/test/test_images.cpp +++ b/modules/videoio/test/test_images.cpp @@ -300,10 +300,56 @@ TEST(videoio_images, bug_26457) EXPECT_MAT_N_DIFF(img, col.getFirstFrame(), 0); } +typedef testing::TestWithParam videoio_image_seq_start; + +TEST_P(videoio_image_seq_start, open) +{ + const VideoCaptureAPIs apiPref = GetParam(); + if (!videoio_registry::hasBackend(apiPref)) + throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); + + // Sequence starts at an index outside the auto-probe range + // Opening with only the pattern must fail without the property, and succeed when it is set + const int start = 100; + const size_t count = 5; + ImageCollection col; + col.generate(count, start); + const std::string pattern = col.getPatternFilename(); + + { + VideoCapture cap(pattern, apiPref); + EXPECT_FALSE(cap.isOpened()); + } + + { + VideoCapture cap; + ASSERT_TRUE(cap.open(pattern, apiPref, + { CAP_PROP_IMAGE_SEQ_START, start })); + ASSERT_TRUE(cap.isOpened()); + for (size_t idx = 0; idx < count; ++idx) + { + Mat img; + ASSERT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(start + idx), 0); + } + } +} + +// PR: https://github.com/opencv/opencv/pull/28844/ +// Requires FFmpeg wrapper re-build on Windows +static const VideoCaptureAPIs BackendsWithSeqStart[] = +{ + CAP_IMAGES +#ifndef _WIN32 + ,CAP_FFMPEG +#endif +}; + +INSTANTIATE_TEST_CASE_P(videoio_images, videoio_image_seq_start, testing::ValuesIn(BackendsWithSeqStart)); + // TODO: should writer overwrite files? // TODO: is clamping good for seeking? // TODO: missing files? E.g. 3, 4, 6, 7, 8 (should it finish OR jump over OR return empty frame?) // TODO: non-numbered files (https://github.com/opencv/opencv/pull/23815) -// TODO: when opening with pattern (e.g. test%01d.png), first frame can be only 0 (test0.png) }} // opencv_test::::