1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

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
This commit is contained in:
Alex
2026-04-28 19:18:50 +12:00
committed by GitHub
parent 597c360b9d
commit 46ee0f7954
5 changed files with 80 additions and 12 deletions
@@ -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
+10
View File
@@ -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<IStreamR
{
nThreads = requestedThreads = params.get<int>(CAP_PROP_N_THREADS);
}
if (params.has(CAP_PROP_IMAGE_SEQ_START))
{
image_seq_start = params.get<int>(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<IStreamR
av_dict_set(&dict, "rtsp_transport", "tcp", 0);
#endif
}
if (image_seq_start >= 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)
+21 -10
View File
@@ -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<unsigned>(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<IVideoCapture> create_Images_capture(const std::string &filename)
Ptr<IVideoCapture> create_Images_capture(const std::string& filename, const VideoCaptureParameters& params)
{
return makePtr<CvCapture_Images>(filename);
int image_seq_start = -1;
if (params.has(CAP_PROP_IMAGE_SEQ_START))
{
image_seq_start = params.get<int>(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<IVideoCapture>();
}
Ptr<CvCapture_Images> cap = makePtr<CvCapture_Images>();
if (!cap->open(filename, image_seq_start))
return Ptr<IVideoCapture>();
return cap;
}
//
+1 -1
View File
@@ -367,7 +367,7 @@ Ptr<IVideoCapture> create_V4L_capture_file(const std::string &filename);
Ptr<IVideoCapture> create_OpenNI2_capture_cam( int index );
Ptr<IVideoCapture> create_OpenNI2_capture_file( const std::string &filename );
Ptr<IVideoCapture> create_Images_capture(const std::string &filename);
Ptr<IVideoCapture> create_Images_capture(const std::string& filename, const VideoCaptureParameters& params);
Ptr<IVideoWriter> create_Images_writer(const std::string& filename, int fourcc,
double fps, const Size& frameSize,
const VideoWriterParameters& params);
+47 -1
View File
@@ -300,10 +300,56 @@ TEST(videoio_images, bug_26457)
EXPECT_MAT_N_DIFF(img, col.getFirstFrame(), 0);
}
typedef testing::TestWithParam<VideoCaptureAPIs> 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::<anonymous>::