OpenCV/Python:使用VideoCapture读取特定帧。

71
< p >有没有一种方法可以使用VideoCapture()方法获取特定帧?

我当前的代码是:

import numpy as np
import cv2

cap = cv2.VideoCapture('video.avi')

这个是我参考教程。


1
在您向下滚动之前请注意 - 由于OpenCV中存在的一个长期存在的错误,任何涉及CAP_PROP_POS_FRAMES的解决方案都无法可靠地工作。请参阅此线程:https://github.com/opencv/opencv/issues/9053 - Peter
6个回答

60
以下代码可以实现这个目标:
import cv2
cap = cv2.VideoCapture(videopath)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number-1)
res, frame = cap.read()

frame_number 是一个整数,范围在 0 到视频帧数之间。
注意:为了读取第 frame_number 帧,应将 frame_number-1 设置为强制读取。这并没有很好地记录下来,但这是 VideoCapture 模块的行为方式。

可以通过以下方式获取帧数:

amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)

21
cv2.cv.CV_CAP_PROP_FRAME_COUNT对我来说不可用。我找到了cv2.CAP_PROP_FRAME_COUNT - user3731622
9
我猜您正在使用的是OpenCV 3.x版本。OpenCV 3.x在cv2模块中定义这些常量,且不带有“CV_”前缀。而OpenCV 2.x则在cv2.cv模块中定义它们,并加上了“CV_”前缀。 - Alexey Antonenko
1
对于cap.set,您需要使用[0;amount_of_frames-1]中的frame_number作为索引。根据您的建议,对于第一帧,我们应该设置0-1=-1,对吗?这没有意义,因为那个操作之后cap.set返回0。然而,在一些mp4视频上,我可以设置0帧和num_of_frames帧,并成功读取它们(因此,总共读取n+1帧),这显然是某种舍入误差。 - apatsekin
该属性现在被命名为 cv2.cv2.CAP_PROP_POS_FRAMES - Jakob Lindskog
1
或者只需使用 cv2.CAP_PROP_POS_FRAMES - Mercury
警告 - CAP_PROP_POS_FRAMES 不可靠 - 它经常会导致潜在的错误。请参考线程:https://github.com/opencv/opencv/issues/9053 - Peter

57

谢谢GPPK。

视频参数应该以整数形式给出。每个标志都有自己的值。请参见此处的代码。

正确的解决方案是:

import numpy as np
import cv2

#Get video name from user
#Ginen video name must be in quotes, e.g. "pirkagia.avi" or "plaque.avi"
video_name = input("Please give the video name including its extension. E.g. \"pirkagia.avi\":\n")

#Open the video file
cap = cv2.VideoCapture(video_name)

#Set frame_no in range 0.0-1.0
#In this example we have a video of 30 seconds having 25 frames per seconds, thus we have 750 frames.
#The examined frame must get a value from 0 to 749.
#For more info about the video flags see here: https://dev59.com/22gu5IYBdhLWcg3wTVY6
#Here we select the last frame as frame sequence=749. In case you want to select other frame change value 749.
#BE CAREFUL! Each video has different time length and frame rate. 
#So make sure that you have the right parameters for the right video!
time_length = 30.0
fps=25
frame_seq = 749
frame_no = (frame_seq /(time_length*fps))

#The first argument of cap.set(), number 2 defines that parameter for setting the frame selection.
#Number 2 defines flag CV_CAP_PROP_POS_FRAMES which is a 0-based index of the frame to be decoded/captured next.
#The second argument defines the frame number in range 0.0-1.0
cap.set(2,frame_no);

#Read the next frame from the video. If you set frame 749 above then the code will return the last frame.
ret, frame = cap.read()

#Set grayscale colorspace for the frame. 
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

#Cut the video extension to have the name of the video
my_video_name = video_name.split(".")[0]

#Display the resulting frame
cv2.imshow(my_video_name+' frame '+ str(frame_seq),gray)

#Set waitKey 
cv2.waitKey()

#Store this frame to an image
cv2.imwrite(my_video_name+'_frame_'+str(frame_seq)+'.jpg',gray)

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

5
在Python 3.5中,使用cap.set()函数时,第一个参数定义了帧编号,范围为0.0-1.0。因此,使用cap.set(1,frame_no)来设置帧编号。 - zinon
25
在cap.set()函数中,第一个参数应该是'cv2.cv.CV_CAP_PROP_POS_FRAMES',不需要任何'1'或'2'等数字。第二个参数是帧号,范围在0到cv2.cv.CV_CAP_PROP_FRAME_COUNT之间,它是整数而不是浮点数0.0-1.0! - Alexey Antonenko
1
@AlexeyAntonenko 需要注意的是,“索引”转换并不总是完美的。它不一定会给你完全匹配的“索引”帧,我猜开发人员只是包装了旧的[0-1]代码,存在舍入误差。我在OpenCV页面上发布了一个问题/错误报告,因为他们主页上的实际错误报告链接已经失效。http://answers.opencv.org/question/200232/why-does-videosetframe_posindex-index-not-align-with-frame-number/ - Thomas Lux
1
使用opencv-python-4.1.1.26版本时,cv2.cv.CV_CAP_PROP_POS_FRAMES未找到。可能是枚举值出了问题,但我不得不使用vid.set(1, myEndFrame)。 - GuillaumeL
1
@AlexeyAntonenko 魔术常量 2 实际上是 cv2.CAP_PROP_POS_AVI_RATIO,它确实需要一个浮点参数,范围在 0.0 到 1.0 之间。 - Mark Ransom

29

如果您想要一个确切的帧,您可以将VideoCapture会话设置为该帧。自动调用该帧显然更加直观。“正确”的解决方案需要您输入已知的数据:如fps、长度等等。使用下面的代码,您只需知道要调用的帧即可。

import numpy as np
import cv2
cap = cv2.VideoCapture(video_name)  # video_name is the video being called
cap.set(1,frame_no)  # Where frame_no is the frame you want
ret, frame = cap.read()  # Read the frame
cv2.imshow('window_name', frame)  # show frame on window

如果你想让窗口一直停留,直到你按下退出键:

while True:
    ch = 0xFF & cv2.waitKey(1) # Wait for a second
    if ch == 27:
        break

5
cap.set(1,frame_no)中的1是指视频流中的属性编号,用于设置视频的帧数。 - Moondra
请看下面我的回答。 - Alexey Antonenko
'1' 是属性标识符。属性编号 1 是 CAP_PROP_POS_FRAMES:'要解码/捕获的下一帧的基于0的索引。' 更多信息请参见此处 - varungupta

19

设置特定帧

从VideoCaptureProperties的文档(文档)可以看出,在VideoCapture中设置帧的方法是:

frame = 30
cap.set(cv2.CAP_PROP_POS_FRAMES, frame)

注意,您不需要向函数传递frame - 1,因为正如文档所述,标志CAP_PROP_POS_FRAMES代表要解码/捕获的下一帧的从0开始的索引。

下面是一个读取每秒一帧的完整示例:

import cv2

cap = cv2.VideoCapture('video.avi')

# Get the frames per second
fps = cap.get(cv2.CAP_PROP_FPS) 

# Get the total numer of frames in the video.
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)

frame_number = 0
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number) # optional
success, image = cap.read()

while success and frame_number <= frame_count:

    # do stuff

    frame_number += fps
    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
    success, image = cap.read()

设置特定时间

在上面链接的文档中可以看到,在VideoCapture中设置特定时间的方法如下:

milliseconds = 1000
cap.set(cv2.CAP_PROP_POS_MSEC, milliseconds)

就像之前一样,可以通过以下方式实现每秒读取一帧的完整示例:

import cv2

cap = cv2.VideoCapture('video.avi')

# Get the frames per second
fps = cap.get(cv2.CAP_PROP_FPS) 

# Get the total numer of frames in the video.
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)

# Calculate the duration of the video in seconds
duration = frame_count / fps

second = 0
cap.set(cv2.CAP_PROP_POS_MSEC, second * 1000) # optional
success, image = cap.read()

while success and second <= duration:

    # do stuff

    second += 1
    cap.set(cv2.CAP_PROP_POS_MSEC, second * 1000)
    success, image = cap.read()

1
这是更为现代的答案。 - Mercury

7
例如,要开始阅读视频的第15帧,您可以使用以下代码:

frame = 15
cap.set(cv2.CAP_PROP_POS_FRAMES, frame-1)

1
此外,我想说的是,使用 CAP_PROP_POS_FRAMES 属性并不总是会给你正确的结果。特别是当你处理像 mp4(H.264)这样的压缩文件时。

在我的情况下,当我为 .mp4 文件调用 cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number) 时,它返回 False,但当我为 .avi 文件调用它时,它返回 True。在决定使用这个“功能”时请考虑到这一点。

very-hit 建议使用 CV_CAP_PROP_POS_MSEC 属性。

阅读 this thread 获取更多信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接