C++ 从 OpenCV 中的 VideoCapture 读取每第 n 帧
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22704936/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reading every nth frame from VideoCapture in OpenCV
提问by user3079474
Is it possible to read frames from a video in steps (eg I want to read every fifth frame of a video stream). Currently I'm doing this as a workaround but it's not very effecient.
是否可以分步从视频中读取帧(例如,我想读取视频流的每第五帧)。目前我正在这样做作为一种解决方法,但它不是很有效。
bool bSuccess
int FramesSkipped = 5;
for (int a = 0; < FramesSkipped; a++)
bSuccess = cap.read(NextFrameBGR);
Any suggestions so I do not have to loop through the five frames to get the desired frame?
有什么建议可以让我不必遍历五个帧来获得所需的帧?
回答by Sergei Nosov
I'm afraid there's not much you can do and it's not just a shortcoming of OpenCV. You see, modern video codecs, are, generally, complex beasts. To get a higher compression rate the encoding of a frame is often dependent on previous and sometimes even successive frames.
恐怕你无能为力,这不仅仅是OpenCV的一个缺点。你看,现代视频编解码器通常是复杂的野兽。为了获得更高的压缩率,帧的编码通常取决于之前的帧,有时甚至是连续的帧。
So, most of the time you have to decode frames before the desired one even if you don't need them.
因此,大多数情况下,即使您不需要它们,您也必须在所需的帧之前对其进行解码。
There are rather non-trivial tricks to specifically encode a video file, so that it would be cheap to get every Nth frame, but it's not feasible in general case.
有一些非常重要的技巧可以专门对视频文件进行编码,因此获取每个第 N 帧的成本会很便宜,但在一般情况下是不可行的。
That said, you can try the seeking functionality OpenCV provides (see OpenCV Seek Function/Rewind). It may (as well as may not) work faster depending on the circumstances. However, personally, I wouldn't bet on it.
也就是说,您可以尝试 OpenCV 提供的搜索功能(请参阅 OpenCV 搜索功能/回退)。它可能(也可能不)根据情况更快地工作。但是,就我个人而言,我不会赌它。
回答by benJephunneh
I've had success in Python 3 using a simple counter and setting the capture to that counter's frame, as follows:
我在 Python 3 中使用一个简单的计数器取得了成功,并将捕获设置为该计数器的帧,如下所示:
import cv2
cap = cv2.VideoCapture('XYZ.avi')
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(1, count)
else:
cap.release()
break
I've tried to find a way to make this a little more pythonic using a with
statement but I don't believe the CV2 library has been updated for it.
我试图找到一种方法来使用with
语句使它更像 pythonic,但我不相信 CV2 库已经更新了。
回答by Afflatus
I got it to work in Python... See below for two sample use cases and some caveats.
我让它在 Python 中工作......请参阅下面的两个示例用例和一些注意事项。
First, import some packages
首先导入一些包
import cv2
import math
import numpy as np
Capture every n seconds (here, n = 5)
每 n 秒捕获一次(这里,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)
或者,每 n 帧捕获一次(此处,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"
That's pretty much it.
差不多就是这样。
一些不幸的警告...取决于您的 opencv 版本(这是为 opencv V3 构建的),您可能需要以不同的方式设置 fps 变量。看here详情请点击此处。要找出您的版本,您可以执行以下操作:
(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')
major_ver
回答by Deepthought
Here is what I suggest:
这是我的建议:
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 );
Now that you have saved all the frames locally you can quickly read the nth frame that you want.
CATUION:A sample video of 12 secs I had was composed of >200 images. This will eat up lot of space.
现在您已将所有帧保存在本地,您可以快速读取所需的第 n 帧。
注意:我拥有的 12 秒示例视频由 > 200 个图像组成。这会占用很多空间。
A simple yet effective optimization will be to read the nth frame using the approach that you are using or the one suggested by @sergie. After this you can save the image with its index so that later query at same index will return the saved image rather than having to skip frames like you are. This way you will save the space that you would have wasted in saving frames that you wouldn't have queried and time taken to read & save those unwanted frames aswell.
一个简单而有效的优化是使用您正在使用的方法或@sergie 建议的方法读取第 n 帧。在此之后,您可以使用其索引保存图像,以便以后在同一索引处查询将返回保存的图像,而不必像您一样跳过帧。这样,您将节省在保存不会查询的帧时浪费的空间以及阅读和保存这些不需要的帧所花费的时间。
回答by azatar
When I had the same goal with OpenCV, I just tuned around the number of "keyframes" I wanted per second of video, regardless of the frame rate or total number of frames. So, this gets me the N-th key given my target KPS.
当我对 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()
Note that I'm rolling through all the frames, but only writing the N-th frame using hop
as N.
请注意,我正在遍历所有帧,但仅使用 N 编写第 N 帧hop
。
回答by Ishan Shah
I encountered the same problem. All i did was this:
我遇到了同样的问题。我所做的就是这样:
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()
Hope this helps.
希望这可以帮助。
回答by vuamitom
You should use the grab
function to move to next frame. And only use retrieve
to decode the frame you need.
您应该使用该grab
功能移动到下一帧。并且仅用于retrieve
解码您需要的帧。
bool bSuccess
int FramesSkipped = 5;
for (int a = 0; < FramesSkipped; a++)
bSuccess = cap.grab();
bSuccess = cap.retrieve(NextFrameBGR);
回答by andrew-zmeul
if anyone will need to capture every 5th frame and save it as jpg, based on Ishan Shah's code:
如果有人需要根据 Ishan Shah 的代码捕获每 5 帧并将其另存为 jpg:
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
回答by Andrew Miroshnichenko
I use thisrepo!
我用这个回购!
Main idea is:
主要思想是:
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
相机.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)
回答by Tessaracter
It is not possible to extract random frames as the encoding scheme is generally extremely complex. For example in MPEG-4, Only the information containing the difference between two frames is stored, Hence clearly the previous frames are required.
由于编码方案通常极其复杂,因此无法提取随机帧。例如在 MPEG-4 中,只存储包含两帧之间差异的信息,因此显然需要前一帧。