一、概述
在计算机视觉数据预处理阶段,一个常用的工序是要将原始的视频数据抽帧,得到图像数据集。有时候视频很长,而我们感兴趣的场景可能只在某些较短的时段内出现。这种情况下如果对整个视频抽帧再进行人工筛选,不仅会占用不必要的空间,而且增大了筛选的工作量。
本文记录了一个抽帧脚本的编写过程。涉及的主要知识点:
- OpenCV中和视频处理相关的接口函数
-
cap.get(cv2.CAP_PROP_POS_MSEC)
获取当前帧信息
-
- 在循环中同时迭代两个元素
- zip和yield的用法
- 一个处理区间的库——pyinter
- 主要用到Interval和IntervalSet两个类
二、关键组件介绍
2.1 OpenCV相关接口
首先分享一个非常适合python-opencv快速上手的教程(也涉及一些简单的分类,用的keras API)。注意如果github上一些较大的ipynb文件打不开,可以直接去jupyter官网粘贴链接。
OpenCV这部分代码比较常规,注意几个地方就行:
-
cv2.waitkey可以控制视频播放速度,一般如果只是播放的话,可选cv2.waitkey(25)。不过通常用这个都是要处理数据或者做inference的,记得把数字改成0..要不然会浪费不必要的时间。
-
cap.get()
函数用于获取当前帧的信息。其中可以访问的信息很多,这里用到的是cv2.CAP_PROP_POS_MSEC,即当前帧在整个视频中处于第几毫秒的位置(注意,返回的精度为浮点型)。获取到这个时刻信息,只需判断该时刻是否落在感兴趣的时刻区间内,再对区间内的帧进行保存即可。
2.2 同时迭代一个列表中的两个元素
为了将未知数量的时刻对组合成区间,首先一个问题是,对于用户输入的一长串时刻,对其两两配对,每两个时刻组成一个区间。这里涉及到一个问题:即如何迭代同一个列表中的多个元素。
首先区分一下,从多个列表中迭代多个元素和该问题是不同的。多个列表迭代直接使用zip就可以实现。而上述问题需要用到生成器。涉及一些生成和迭代器的知识。下面提供两种实现方法。
方法一:使用iter函数
iter函数将列表(其实不只是列表,只要是包含多个元素的对象就行)转换成一个迭代器对象it,该迭代器对象通过调用next函数可以获取迭代器的下一个元素。直到迭代至最后一个元素结束(StopIteration)。
故代码可以这样写:
def pairwise(lst):
it = iter(lst)
while True:
yield next(it), next(it)
这里用到了一个强大的函数,yield:
- 关于yield的理解。先看菜鸟教程中的一段话:
# 例子:菲波那切数列
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b # 使用 yield
# print b
a, b = b, a + b
n = n + 1
for n in fab(5):
print n
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码, 执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。*
也就是说,使用了yield的函数会返回一个生成器,然后该函数的代码会在对生成器的每一次迭代中被执行(故yield函数通常伴随着两个循环,一个是yield语句通常定义在函数的循环中;另一个是调用包含yield的函数时会使用循环)。每次执行函数时,代码会在执行yield语句之后返回一个迭代值,然后中断。下次再执行该函数,代码直接从yield的下一条语句开始,需要注意的是,函数的局部变量值会一直保留到结束。
和yield相关的两个细节:next和return。next可以接收迭代器和生成器作为输入参数,然后每次返回一个迭代值。(python3中next不是成员函数,next是成员函数,这两种调用都可以。python2中next是成员函数)
在generation function中如果执行过程遇到return,则直接跑出StopIteration终止迭代。
方法二:zip的妙用
def pairwise(lst):
'''另一种方法:利用zip实现多元素迭代'''
for x,y in zip(lst[0::2], lst[1::2]):
yield x,y
将列表的奇数项和偶数项元素zip到一起,然后生成。该部分内容参考stackoverflow。
2.2 区间管理模块
根据上面的描述,我们解决了如何从给定的输入时刻列表中两两取出开始和结束时刻点。接下来解决如何将这些时刻点组成多个闭合区间,最后再整合为一个区间。
首先要强调一点,这里的区间是一个连续的区间,因为我们要判断当前帧是否在某时间区间内,而由2.1知,当前帧所对应的时刻(单位毫秒)不一定是一个整数!先来看一个简化版的问题,如何组合多个独立的列表(离散区间)。
我们知道,用Python判断一个元素是否在列表中可以用if x in list
这种简易表达。但是对于多个不连续的区间,怎么使用类似的方法呢?这里有几种不同的实现方法:
①最简单:几个列表直接相加。
②建一个空列表,然后每次循环append一个子区间。最后得到一个两层的嵌套列表,展开即得到整合区间。
小问题:如何展开嵌套列表?
这里我有一个不太常规的方法,但是蛮好用的:
可以展开任意层数的嵌套列表~不过注意下返回的是tuple类型。
那么如何根据给定端点产生连续区间?这里用到一个区间管理的库——pyinter。直接上图来看看效果:
然后只需要用
pyinter.IntervalSet
来组合多个区间即可。Fig. 3 组合多个连续区间
更多关于pyinterval模块的介绍1, 2。
三、总结
至此,抽帧脚本需要的所有组件都准备好了。只需要读取视频,然后对每一帧获取时刻并判断是否在用户指定的时间区间中,若在则进一步处理,比如imwrite或imshow等。如需要完整定时抽帧脚本,请到我的github上去拿~脚本使用方法:
python get_frame.py 1.mp4 3 0:32 0:58 1:10 1:26 2:01 2:40
其中第一个参数为视频路径,第二个为抽帧间隔,后面的参数为感兴趣的时间段,这里我取了三小段~Repo里面还有一些个人经常用到的小脚本,喜欢的话记得留个star!
四、参考
- https://github.com/jrobchin/Computer-Vision-Basics-with-Python-Keras-and-OpenCV/blob/master/README.md
- https://nbviewer.jupyter.org/
- http://www.runoob.com/w3cnote/python-yield-used-analysis.html
- https://stackoverflow.com/questions/5389507/iterating-over-every-two-elements-in-a-list
- https://github.com/cy810557/PocketKit/tree/master/ImageProcessing
- https://zhuanlan.zhihu.com/p/41510100
- https://www.cnblogs.com/cotyb/p/5256303.html
网友评论