从 OpenCV 中的 VideoCapture 中读取每第 n 帧
Posted
技术标签:
【中文标题】从 OpenCV 中的 VideoCapture 中读取每第 n 帧【英文标题】:Reading every nth frame from VideoCapture in OpenCV 【发布时间】:2014-05-07 10:46:21 【问题描述】:是否可以分步从视频中读取帧(例如,我想读取视频流的每五帧)。目前我正在这样做作为一种解决方法,但它不是很有效。
bool bSuccess
int FramesSkipped = 5;
for (int a = 0; < FramesSkipped; a++)
bSuccess = cap.read(NextFrameBGR);
有什么建议让我不必遍历五个帧来获得所需的帧吗?
【问题讨论】:
【参考方案1】:恐怕您无能为力,这不仅仅是 OpenCV 的缺点。你看,现代视频编解码器通常是复杂的野兽。为了获得更高的压缩率,帧的编码通常取决于之前的帧,有时甚至是连续的帧。
因此,大多数情况下,即使您不需要它们,您也必须在所需帧之前解码它们。
有相当重要的技巧可以专门对视频文件进行编码,因此每 N 帧获取一次会很便宜,但在一般情况下这是不可行的。
也就是说,您可以尝试 OpenCV 提供的搜索功能(请参阅OpenCV Seek Function/Rewind)。根据具体情况,它可能(也可能不会)工作得更快。但是,就我个人而言,我不会打赌。
【讨论】:
我不是专家,所以可能是非常错误的,但是是否可以在需要的帧之前跳过中间 P 和 B 帧直到最后一个 I 帧? 可能,它对上述搜索功能做了类似的事情。但我不认为 OpenCV 中有一个 API 可以在编解码器级别进行这种细粒度的操作。 谢谢。我最终选择使用 ffmpeg 提取关键帧。发现从opencv中提取出来的帧没有一个像ffmpeg那么好。【参考方案2】:我在 Python 3 中成功使用了一个简单的计数器并将捕获设置为该计数器的帧,如下所示:
import cv2
cap = cv2.VideoCapture('XYZ.avi')
# For streams:
# cap = cv2.VideoCapture('rtsp://url.to.stream/media.amqp')
# Or e.g. most common ID for webcams:
# cap = cv2.VideoCapture(0)
count = 0
while cap.isOpened():
ret, frame = cap.read()
if ret:
cv2.imwrite('frame:d.jpg'.format(count), frame)
count += 30 # i.e. at 30 fps, this advances one second
cap.set(cv2.CAP_PROP_POS_FRAMES, count)
else:
cap.release()
break
我试图找到一种方法,使用 with
语句使它更 Pythonic,但我不相信 CV2 库已为此更新。
【讨论】:
使用cap.set(cv2.CAP_PROP_POS_FRAMES, count)
而不是cap.set(1, count)
会更清楚
请随时贡献编辑,@chanban。诚然,我是作为一个初学者来到这里的,为了我自己的目的试图弄清楚这一点。
非常感谢@benJephunneh,您的回答对我帮助很大!我刚刚添加了推荐的属性值和一些示例值,因为我认为 OP 可能涉及帧同步问题,因此知道这不仅适用于媒体文件,而且适用于真实的相机设置,这可能是有价值的,其中如果图像处理需要太多时间来处理所有帧,则需要同步。【参考方案3】:
我让它在 Python 中工作...请参阅下面的两个示例用例和一些注意事项。
# First, import some packages
import cv2
import math
import numpy as np
# Make sure that the print function works on Python 2 and 3
from future import print_function
# Capture every n seconds (here, n = 5)
#################### Setting up the file ################
videoFile = "Jumanji.mp4"
vidcap = cv2.VideoCapture(videoFile)
success, image = vidcap.read()
#################### Setting up parameters ################
seconds = 5
fps = vidcap.get(cv2.CAP_PROP_FPS) # Gets the frames per second
multiplier = fps * seconds
#################### Initiate Process ################
while success:
frameId = int(round(vidcap.get(1))) #current frame number, rounded b/c sometimes you get frame intervals which aren't integers...this adds a little imprecision but is likely good enough
success, image = vidcap.read()
if frameId % multiplier == 0:
cv2.imwrite("FolderSeconds/frame%d.jpg" % frameId, image)
vidcap.release()
print("Complete")
# Alternatively, capture every n frames (here, n = 10)
#################### Setting up the file ################
videoFile = "Jumanji.mp4"
vidcap = cv2.VideoCapture(videoFile)
success, image = vidcap.read()
#################### Setting up parameters ################
#OpenCV is notorious for not being able to good to
# predict how many frames are in a video. The point here is just to
# populate the "desired_frames" list for all the individual frames
# you'd like to capture.
fps = vidcap.get(cv2.CAP_PROP_FPS)
est_video_length_minutes = 3 # Round up if not sure.
est_tot_frames = est_video_length_minutes * 60 * fps # Sets an upper bound # of frames in video clip
n = 5 # Desired interval of frames to include
desired_frames = n * np.arange(est_tot_frames)
#################### Initiate Process ################
for i in desired_frames:
vidcap.set(1, i-1)
success, image = vidcap.read(1) # image is an array of array of [R,G,B] values
frameId = vidcap.get(1) # The 0th frame is often a throw-away
cv2.imwrite("FolderFrames/frame%d.jpg" % frameId, image)
vidcap.release()
print("Complete")
差不多了。
一些不幸的警告...根据您的 `opencv` 版本(这是为 `opencv` V3 构建的),您可能需要以不同的方式设置 fps 变量。有关详细信息,请参阅 [此处][1]。要找出您的版本,您可以执行以下操作:
major_ver, minor_ver, subminor_ver = cv2.__version__.split('.')
print(major_ver)
【讨论】:
在您的第二个示例中,您可以使用cv2.CAP_PROP_FRAME_COUNT
获得准确的帧数并避免计算它。另外,你能解释一下n * np.arange(est_tot_frames)
的目的吗?它似乎没有做它应该做的事情。
虽然有助于找到解决方案,但这个答案并不能解决关于第 n 帧的问题。
这会很慢!坏主意【参考方案4】:
我遇到了同样的问题。 我所做的只是:
import cv2
vs = cv2.VideoCapture("<path of your video>.mp4")
print("Showing frames...")
c=1
while True:
grabbed, frame = vs.read()
if c%5==0:
cv2.imshow('Frame',frame)
cv2.waitKey(1)
c+=1
vs.release()
【讨论】:
【参考方案5】:这是我的建议:
CvCapture* capture = cvCaptureFromFile("input_video_path");
int loop = 0;
IplImage* frame = NULL;
Mat matframe;
char fname[20];
do
frame = cvQueryFrame(capture);
matframe = cv::cvarrToMat(frame);
cvNamedWindow("video_frame", CV_WINDOW_AUTOSIZE);
cvShowImage("video_frame", frame);
sprintf(fname, "frame%d.jpg", loop);
cv::imwrite(fname, matframe);//save each frame locally
loop++;
cvWaitKey(100);
while( frame != NULL );
现在您已经在本地保存了所有帧,您可以快速读取所需的第 n 帧。注意:我拥有的 12 秒的示例视频由 >200 个图像组成。这会占用很多空间。
一个简单而有效的优化是使用您正在使用的方法或@sergie 建议的方法读取第 n 帧。在此之后,您可以使用其索引保存图像,以便稍后在同一索引处查询将返回保存的图像,而不必像您一样跳过帧。这样,您将节省在保存您不会查询的帧时浪费的空间,以及读取和保存那些不需要的帧所花费的时间。
【讨论】:
我正在播放 130 个 1 分钟的 4K 视频样本,我向你保证这是一个非常糟糕的主意!【参考方案6】:当我对 OpenCV 有相同的目标时,我只是围绕我想要的每秒视频的“关键帧”数量进行调整,而不考虑帧速率或总帧数。因此,在给定目标 KPS 的情况下,这让我获得了第 N 个键。
# python3.6 code using OpenCV 3.4.2
import cv2
KPS = 5 # Target Keyframes Per Second
VIDEO_PATH = "path/to/video/folder" # Change this
IMAGE_PATH = "path/to/image/folder" # ...and this
EXTENSION = ".png"
cap = cv2.VideoCapture(VIDEO_PATH)
# Set frames-per-second for capture
fps = round(cap.get(cv2.CAP_PROP_FPS))
hop = round(fps / KPS)
curr_frame = 0
while(True):
ret, frame = cap.read()
if not ret: break
if curr_frame % hop == 0:
print('Creating... 0'.format(name,))
name = IMAGE_PATH + "_" + str(curr_frame) + EXTENSION
cv2.imwrite(name, frame)
curr_frame += 1
cap.release()
请注意,我正在遍历所有帧,但仅使用 hop
作为 N 写入第 N 帧。
【讨论】:
【参考方案7】:您应该使用grab
函数移动到下一帧。并且仅使用retrieve
来解码您需要的帧。
bool bSuccess
int FramesSkipped = 5;
for (int a = 0; < FramesSkipped; a++)
bSuccess = cap.grab();
bSuccess = cap.retrieve(NextFrameBGR);
【讨论】:
【参考方案8】:如果有人需要每 5 帧捕获一次并将其保存为 jpg,基于 Ishan Shah 的代码:
import cv2
vid = cv2.VideoCapture('C:/python_works/creatives_gardenscapes/0c52b83ed1dec617092aaf83278f12ad.mp4')
if not os.path.exists('images'):
os.makedirs('images')
index = 0
while(True):
ret, frame = vid.read()
if not ret:
break
name = 'C:/python_works/creatives_gardenscapes/frames/0c52b83ed1dec617092aaf83278f12ad' + str(index) + '.jpg'
if index%50==0:
cv2.imwrite(name, frame)
index += 1
【讨论】:
【参考方案9】:由于编码方案通常非常复杂,因此无法提取随机帧。例如在 MPEG-4 中,仅存储包含两帧之间差异的信息,因此显然需要先前的帧。
【讨论】:
【参考方案10】:我使用this repo!
主要思想是:
main.py
from camera import VideoCam
SKIPFRAME = 8
url = 0
v1 = VideoCam(url)
v1.check_camera(v1.cap)
ct = 0
while True:
ct += 1
try:
ret = v1.cap.grab()
if ct % SKIPFRAME == 0: # skip some frames
ret, frame = v1.get_frame()
if not ret:
v1.restart_capture(v1.cap)
v1.check_camera(v1.cap)
continue
# frame HERE
v1.show_frame(frame, 'frame')
except KeyboardInterrupt:
v1.close_cam()
exit(0)
camera.py
import cv2
import logging
class VideoCam():
def __init__(self, url=0):
self.url = url
self.cap = cv2.VideoCapture(self.url)
self.get_frame()
self.get_frame_read()
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
def check_camera(self, cap):
logging.info('Camera status: '.format(self.url, cap.isOpened()))
def show_frame(self, frame, name_fr='NAME'):
cv2.imshow(name_fr, frame)
# cv2.imshow(name_fr, cv2.resize(frame, (0, 0), fx=0.4, fy=0.4))
cv2.waitKey(1)
def get_frame(self):
return self.cap.retrieve()
def get_frame_read(self):
return self.cap.read()
def close_cam(self):
self.cap.release()
cv2.destroyAllWindows()
def restart_capture(self, cap):
cap.release()
self.cap = cv2.VideoCapture(self.url)
【讨论】:
【参考方案11】:代码:
import cv2
vidcap = cv2.VideoCapture('bali.mp4')
success,image = vidcap.read()
fps = vidcap.get(cv2.CAP_PROP_FPS)
print('video fps :', fps)
length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
print( 'total frames :', length )
count = 0
skip = 10
success = True
while success:
success,image = vidcap.read()
if count%skip==0:
print(count)
cv2.imwrite("frames/frame%d.jpg" % count, image) # save frame as JPEG file
if cv2.waitKey(10) == 27: # exit if Escape is hit
break
count += 1
输出:
【讨论】:
【参考方案12】:您可以通过阅读然后丢弃它来有效地“跳过”一帧。诚然,这完全不是您所要求的,但仍可能足以作为一种解决方法。显然,这会对性能产生影响,但可能足以满足您的目的。
vs = cv2.videoCapture('/my/clip.mp4')
for i in (thing):
# Read a frame
_, frame = vs.read()
# Skip the frame you just read by reading another
_, frame = vs.read()
# Run a process on said frame
cv2.imshow("Window", frame)
vs.release()
【讨论】:
以上是关于从 OpenCV 中的 VideoCapture 中读取每第 n 帧的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 OpenCV 从辅助网络摄像头读取 VideoCapture 中的帧
从网络摄像头捕获帧,它在 opencv cv2.VideoCapture() 中仅返回 1 帧
使用 VideoCapture (OpenCV) 从图像创建视频