kaarelyngejensen
kaarelyngejensen

Reputation: 31

OpenCV VideoCapture: Howto get specific frame correctly?

I am trying to get at specific frame from a video file using OpenCV 2.4.11. I have tried to follow the documentation and online tutorials of how to do it correctly and have now tested two approaches:

1) The first method is brute force reading each frame using the video.grab() until I reach the specific frame (timestamp) I want. This method is slow if the specific frame is late in the video sequence!

string videoFile(videoFilename);
VideoCapture video(videoFile);
double videoTimestamp = video.get(CV_CAP_PROP_POS_MSEC);
int videoFrameNumber = static_cast<int>(video.get(CV_CAP_PROP_POS_FRAMES));
while (videoTimestamp < targetTimestamp)
{
    videoTimestamp = video.get(CV_CAP_PROP_POS_MSEC);
    videoFrameNumber = static_cast<int>(video.get(CV_CAP_PROP_POS_FRAMES));

    // Grabe frame (but don't decode the frame as we are only "Fast forwarding")
    video.grab();
}
// Get and save frame
if (video.retrieve(frame))
{
    char txtBuffer[100];
    sprintf(txtBuffer, "Video1Frame_Target_%f_TS_%f_FN_%d.png", targetTimestamp, videoTimestamp, videoFrameNumber);
    string imgName = txtBuffer;
    imwrite(imgName, frame);
}

2) The second method I uses the video.set(...). This method is faster and doesn't seem to be any slower if the specific frame is late in the video sequence.

string videoFile(videoFilename);
VideoCapture video2(videoFile);
videoTimestamp = video2.get(CV_CAP_PROP_POS_MSEC);
videoFrameNumber = static_cast<int>(video2.get(CV_CAP_PROP_POS_FRAMES));
video2.set(CV_CAP_PROP_POS_MSEC, targetTimestamp);
while (videoTimestamp < targetTimestamp)
{
    videoTimestamp = video2.get(CV_CAP_PROP_POS_MSEC);
    videoFrameNumber = (int)video2.get(CV_CAP_PROP_POS_FRAMES);

    // Grabe frame (but don't decode the frame as we are only "Fast forwarding")
    video2.grab();
}
// Get and save frame
if (video2.retrieve(frame))
{
    char txtBuffer[100];
    sprintf(txtBuffer, "Video2Frame_Target_%f_TS_%f_FN_%d.png", targetTimestamp, videoTimestamp, videoFrameNumber);
    string imgName = txtBuffer;
    imwrite(imgName, frame);
}

Problem) Now the issue is that using the two methods does end up with the same frame number of the content of the target image frame is not equal?!?

I am tempted to conclude that Method 1 is the correct one and there is something wrong with the OpenCV video.set(...) method. But if I use the VLC player finding the approximate target frame position it is actually Method 2 that is closest to a "correct" result?

As some extra info: I have tested the same video sequence but in two different video files being encoded with respectively 'avc1' MPG4 and 'wmv3' WMV codec.

Using the WMV file the two found frames are way off?

Using the MPG4 file the two found frames are only slightly off?

Is there anybody having some experience with this, can explain my findings and tell me the correct way to get a specific frame from a video file?

Upvotes: 3

Views: 4470

Answers (2)

User42
User42

Reputation: 83

Obviously there's still a bug in opencv/ ffmpeg. ffmpeg doesn't deliver the frames that are wanted and/or opencv doesn't handles this. See here and here.

[Edit: Until that bug is fixed (either in ffmpeg or (as a work-around in opencv)) the only way to get exact frame by number is to "fast forward" as you did. (Concerning VLC-player: I suspect that it uses that buggy set ()-interface. As for a player it is usually not too important to seek frame-exact. But for an editor it is).]

Upvotes: 1

nils
nils

Reputation: 2534

I think that OpenCV uses FFmpeg for video decoding.

We once had a similar problem but used FFmpeg directly. By default, random (but exact) frame access isn't guaranteed. The WMV decoder was particularly fuzzy. Newer versions of FFmpeg allow you access to lower-level routines which can be used to build a retrieval function for frames. This solution was a little involved and nothing I can remember off my head right now. I try to find some more details later.

As a quick work-around, I would suggest to decode your videos off-line and then work on sequences off images. Though, this increases the amount of storage needed, it guarantees exact random frame access. You can use FFmpeg to convert your video file in to a sequence of images like this:

ffmpeg -i "input.mov" -an -f image2 "output_%05d.png"

Upvotes: 0

Related Questions