在cvSetCaptureProperty中使用CV_CAP_PROP_POS_FRAMES获取单个帧

我试图通过设置CV_CAP_PROP_POS_FRAMES属性然后像这样读取框架来跳转到特定的框架:

 cvSetCaptureProperty( input_video, CV_CAP_PROP_POS_FRAMES, current_frame ); frame = cvQueryFrame( input_video ); 

我面临的问题是,OpenCV 2.1为current_frame的12个连续值返回相同的帧,而我想读取每个单独的帧,而不仅仅是关键帧。 谁能告诉我有什么问题?


我做了一些研究,发现问题是由解压缩算法引起的。

类似MPEG的算法(包括HD等)不会分别压缩每个帧,而是不时地保存关键帧,然后仅保存最后一帧和后续帧之间的差异。

您报告的问题是由于,当您选择一个帧时,解码器(ffmpeg,可能)会自动前进到下一个关键帧。

那么,有没有办法解决这个问题呢? 我不只想要关键帧,而是每个帧。

我不知道这对于你的目的是否足够精确,但是我已经成功地通过获取帧速率,将帧数转换为时间,然后前进到MPEGvideo中的特定点。时间。 像这样:

 cv::VideoCapture sourceVideo("/some/file/name.mpg"); double frameRate = sourceVideo.get(CV_CAP_PROP_FPS); double frameTime = 1000.0 * frameNumber / frameRate; sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime); 

由于OpenCV中的这种限制,使用FFMPEG可能是明智之举。 Moviepy是一个不错的包装库。

 # Get nth frame from a video from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader cap = FFMPEG_VideoReader("movie.mov",True) cap.initialize() cap.get_frame(n/FPS) 

表现也很棒。 使用get_frame寻找第n帧是O(1),并且如果请求(几乎)连续帧则使用加速。 我已经获得了比同时加载三个720pvideo更好的实时结果。

CV_CAP_PROP_POS_FRAMES跳转到关键帧。 我有同样的问题,并使用此(python-)代码解决它。 这可能不是完全有效的,但完成工作:

 def seekTo(cap, position): positiontoset = position pos = -1 cap.set(cv.CV_CAP_PROP_POS_FRAMES, position) while pos < position: ret, image = cap.read() pos = cap.get(cv.CV_CAP_PROP_POS_FRAMES) if pos == position: return image elif pos > position: positiontoset -= 1 cap.set(cv.CV_CAP_PROP_POS_FRAMES, positiontoset) pos = -1 

我在OpenCV 3 / Python 3上成功使用了以下内容:

  # Skip to 150 frame then read the 151th frame cap.set(cv2.CAP_PROP_POS_FRAMES, 150)) ret, frame = cap.read() 

经过几年的假设,这是一个不可靠的bug,我想我已经找到了一种方法,可以在速度和正确性之间取得良好的平衡。

之前的解决方案建议在读取帧之前使用CV_CAP_PROP_POS_MSEC属性:

 cv::VideoCapture sourceVideo("/some/file/name.mpg"); const auto frameRate = sourceVideo.get(CV_CAP_PROP_FPS); void readFrame(int frameNumber, cv::Mat& image) { const double frameTime = 1000.0 * frameNumber / frameRate; sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime); sourceVideo.read(image); } 

它确实返回预期的帧,但问题是使用CV_CAP_PROP_POS_MSEC可能非常慢 ,例如对于video转换。

注意:为简单起见,使用全局变量。


另一方面,如果你只是想按顺序阅读video,那么在不寻求的情况下阅读帧就足够了。

 for (int frameNumber = 0; frameNumber < nFrames; ++frameNumber) { sourceVideo.read(image); } 

解决方案来自两者的结合:使用变量来记住最后查询的帧, lastFrameNumber ,并且仅在请求的帧不是下一帧时进行搜索。 以这种方式,可以在顺序读取中提高速度,同时在必要时允许随机搜索。

 cv::VideoCapture sourceVideo("/some/file/name.mpg"); const auto frameRate = sourceVideo.get(CV_CAP_PROP_FPS); const int lastFrameNumber = -2; // guarantee seeking the first time void readFrame(int frameNumber, cv::Mat& image) { if (lastFrameNumber + 1 != frameNumber) { // not the next frame? seek const double frameTime = 1000.0 * frameNumber / frameRate; sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime); } sourceVideo.read(image); lastFrameNumber = frameNumber; }