Discrepancy Between Frame Count Using OpenCV's cv2.VideoCapture and Manual Counting in Python

98 views Asked by At

I am working on a video processing project using OpenCV in Python, and I've encountered a strange issue related to counting the number of frames in a video. I am using cv2.VideoCapture to read the frames from a video file, but I am getting inconsistent results when counting the total number of frames.

Here's the code snippet I'm using:

import cv2

video_path = 'MSP-IMPROV-S01A-F01-P-MF01.avi'
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
print(f"FPS: {fps}")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print("Total frames using cv2.VideoCapture:", total_frames)

cap = cv2.VideoCapture(video_path)
frame_count = 0
while cap.isOpened():
    ret, _ = cap.read()
    if not ret:
        break
    frame_count += 1
cap.release()
print("Manually counted frames:", frame_count)

The output I'm getting is as follows:

FPS: 59.94005994005994
Total frames using cv2.VideoCapture: 1187
Manually counted frames: 594

Initially, I was puzzled by the discrepancy in the frame counts. However, upon closer inspection of the output frames, I noticed that the frames are being halved, and it seems OpenCV is skipping frames uniformly with a stride of one (i.e., one every two frames).

Could this be an issue with how OpenCV is reading the video file? Here are some additional considerations:

The video file format is AVI. Could this format be influencing how OpenCV reads the frames? Is this a known behavior or limitation in OpenCV's frame reading mechanism? Are there specific settings or parameters in cv2.VideoCapture that I need to adjust to avoid skipping frames? I would greatly appreciate any insights or suggestions on how to ensure that OpenCV reads every frame of the video without skipping.

Thank you for your help!

1

There are 1 answers

0
Tino D On

I think this answer might be useful in any case. Though I would be surprised if your video would not be interlaced, since this clear twice amount of frames is a very big indicator.

I created a function to test if the video is interlaced, it is based on ffmpeg, so you would have to get it with sudo apt install ffmpeg.

After that, I wrote this function in python to return True, False, or an error message in case anything goes wrong:

def testInterlacing(inputVideo):
    import subprocess
    import re
    # construct the ffmpeg command as a list of arguments
    ffmpegCommand = [
        "ffmpeg", # command for ffmpeg, you need to install this with sudo apt instlal ffmpeg
        "-filter:v", "idet", # filter this specific information
        "-frames:v", "100", # test on first 100 frames
        "-an", # don't test audio
        "-f", "rawvideo", "-y", "/dev/null", # assign results to nothing
        "-i", inputVideo # on this video
    ]
    try:
        outputBytes = subprocess.check_output(ffmpegCommand, stderr=subprocess.STDOUT) # get the output
        outputStr = outputBytes.decode('utf-8') # decode the output
        tffLines = re.findall(r'TFF:\s+(\d+)', outputStr) # search for a pattern that is the count of top field first 
        bffLines = re.findall(r'BFF:\s+(\d+)', outputStr) # search for a pattern that is the count of  bottom field first
        totalInterlacedFrames = sum(map(int, tffLines)) + sum(map(int, bffLines)) # get total interlaced frames in the 100 checked
        if totalInterlacedFrames != 0: # check if interlaced
            return True # YES
        else: # if not
            return False # NO
    except subprocess.CalledProcessError as e:
        return e

After this, you can use this function in your main, to dictate how the counting should work:

import cv2
inputVideoInterlaced = "letterman.mp4" # sample of interlaced video
inputVideoNotInterlaced = "countdown.mp4" # sample of nt interlaced video
inputVideo = inputVideoInterlaced # choose which file

if testInterlacing(inputVideo):
    cap = cv2.VideoCapture(inputVideo)
    fps = cap.get(cv2.CAP_PROP_FPS)
    print(f"FPS: {fps}")
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print("Total frames using cv2.VideoCapture:", total_frames//2) # divide by 2!
    cap = cv2.VideoCapture(inputVideo)
    frame_count = 0
    while cap.isOpened():
        ret, _ = cap.read()
        if not ret:
            break
        frame_count += 1
    cap.release()
    print("Manually counted frames:", frame_count)
    
elif not(testInterlacing(inputVideo)):
    cap = cv2.VideoCapture(inputVideo)
    fps = cap.get(cv2.CAP_PROP_FPS)
    print(f"FPS: {fps}")
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print("Total frames using cv2.VideoCapture:", total_frames)
    cap = cv2.VideoCapture(inputVideo)
    frame_count = 0
    while cap.isOpened():
        ret, _ = cap.read()
        if not ret:
            break
        frame_count += 1
    cap.release()
    print("Manually counted frames:", frame_count)
    
else:
    e = testInterlacing(inputVideo)
    print("Error while checking if video is interlaced:\n"+e)

I copy pasted your code into the answer with only the small adjustment of dividing the number of cv2 frames by 2 when interlaced is detected.

Hope this helps you further.