python视频提取关键帧_从零开始写⼀个简单的关键帧提取(基于帧间差分,附源码+详细使⽤。。。
引⾔
最近需要⽤到这样的⼀个功能,但是搜索了⼀下⽹上的⽅法,对于不依赖神经⽹络⽽且简单易⾏的⽅法估计只有帧间差分了,⽹上的帧间差分代码好像在我的Pycharm上跑不起来,莫得办法只能⾃⼰⼿写⼀个了,之前没有接触过cv⽅⾯的代码,所以这算⼀次⼊门的机会吧,也希望源码能够帮助到⼤家。
代码的效率也许并不是最⾼的,尤其是关键的两个循环还是可以优化的,在这个博⽂⾥主要是想向⼤家分享⼀下思路,也希望能和⼤家讨论相关的内容吧。
如果急⽤,也可以直接跳到后⾯,会附上完整代码链接和使⽤⽅法。
思路
导⼊视频⽂件
逐帧处理
差分
差分值列表平滑
窗⼝内寻峰值
切取视频关键帧
操作
准备⼯作
构造函数
我们需要定义这样⼀个类KeyFrameGetter,然后将可能⽤到的参数写进构造函数:
def __init__(self, video_path, img_path, window=25):
'''
Define the param in model.
:param video_path: The path points to the movie data,str
:
param img_path: The path we save the image,str
:param window: The comparing domain which decide how wide the peek serve.
'''
self.window = window # 关键帧数量
self.video_path = video_path # 视频路径
self.img_path = img_path # 图像存放路径
self.diff = [] # 差分值的list
self.idx = [] # 选取帧的list
其中这个window是后⾯选择峰值的⼀个窗⼝值,在后⾯的时候会解释
导⼊视频⽂件
接下来我们需要导⼊视频⽂件,我们事先相当于已经把视频路径放在了__init__⾥了,所以可以直接调⽤:
def load_diff_between_frm(self, smooth=True, alpha=0.07):
'''
Calculate and get the model param
:param smooth: Decide if you want to smooth the difference.
:param alpha: Difference factor
:return:
'''
print("load_diff_between_frm")
cap = cv2.VideoCapture(self.video_path) # 打开视频⽂件
diff = []
frm = 0
pre_image = np.array([])
curr_image = np.array([])
while True:
frm = frm + 1
success, data = ad()
if not success:
break
# 这⾥写接下来处理的函数体
cap = cv2.VideoCapture(self.video_path)是调⽤cv2库直接打开视频⽂件,视频⽂件的路径就是self.video_path
diff是计算差分存储差分值的矩阵
frm是记录循环轮数的变量
为了计算差分,分别使⽤pre_image和curr_image记录前后两个图⽚
从while True开始就是正式对每⼀帧进⾏遍历,success, data = ad()是读取每⼀帧,⽽等到读到最后⼀帧之后success就会返回False,这个时候就会退出循环。
逐帧处理
关键点就在每⼀帧要怎么对它处理:
⾸先:
if frm == 1:
pre_image = data
curr_image = data
else:
pre_image = curr_image
curr_image = data
如果是第⼀帧,那么没法差分,所以前后都存的是⾃⼰的数据;如果是后⾯的帧,那么到下⼀轮的时候把curr_image存到pre_image。接下来,就要进⾏图像处理和差分了:
diff.append(abs_diff(pre_image, curr_image))
这⼀句中abs_diff就是计算差分值,这⼀个函数我们还没有写,我们现在就去定义这个函数:
def precess_image(image):
'''
Graying and GaussianBlur
:param image: The image matrix,np.array
:return: The processed image matrix,np.array
'''
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 灰度化
gray_image = cv2.GaussianBlur(gray_image, (3, 3), 0) # ⾼斯滤波
return gray_image
def abs_diff(pre_image, curr_image):python怎么读取py文件
'''
Calculate absolute difference between pre_image and curr_image
:param pre_image:The image in past frame,np.array
:param curr_image:The image in current frame,np.array
:return:
'''
gray_pre_image = precess_image(pre_image)
gray_curr_image = precess_image(curr_image)
diff = cv2.absdiff(gray_pre_image, gray_curr_image)
res, diff = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# fixme:这⾥先写成简单加和的形式
cnt_diff = np.sum(np.sum(diff))
return cnt_diff
思路是:
分别预处理前后两个图像
灰度化
⾼斯滤波
计算绝对值差分值
进⾏⼆值化
把矩阵内所有的数值加起来,作为差分值
讨论:
这样做法可能有点粗暴,因为相当于把整个矩阵压缩成了⼀维,但是我们只是为了衡量帧与帧之间的差距,所以这种信息损失并不是⼀个很⼤的问题。
差分值列表平滑
在主体循环结束后:
if smooth:
diff = exponential_smoothing(alpha, diff)
# 标准化数据
self.diff = np.array(diff)
mean = np.mean(self.diff)
dev = np.std(self.diff)
self.diff = (self.diff-mean)/dev
# 在内部完成
self.pick_idx()
其中smooth是⼀个布尔型参数,也就是传⼊是否进⾏平滑的⼀个参数,当然我们这⾥要把平滑的代码写好hhh,不能偷懒,因为平滑之后选择的峰值更有代表性(会去掉⼀些⼲扰性很⼤的⽑刺),然后我们要对数据进⾏标准化,最后调⽤self.pick_idx()这个函数,选取关键帧并保存。
指数平滑我参考了⼀下别⼈的代码,并进⾏了⼀定的改进:
def exponential_smoothing(alpha, s):
'''
Primary exponential smoothing
:param alpha: Smoothing factor,num
:param s: List of data,list
:return: List of data after smoothing,list
'''
s_temp = [s[0]]
print(s_temp)
for i in range(1, len(s), 1):
s_temp.append(alpha * s[i - 1] + (1 - alpha) * s_temp[i - 1])
return s_temp
它满⾜的是:
$y_{t+1}^{\prime}=y_{t}^{\prime}+\alpha\cdot \left(y_{t}-y_{t}^{\prime}\right)$
其中$\alpha$就是函数传⼊的参数alpha。
窗⼝内寻峰值
假设我要⼀些关键帧,我需要其中差分值较⼤的那部分,⼀种⽅法就是所有差分值之间的前最
⼤$k$个,其中$k$就是输⼊的要求值,但是这样的话⾼峰值旁边的⼀些值就会算到⾥⾯去,会导致相似的场景被重复取到。
解决⽅法:
把⼀阶差分变成⼆阶差分(这样恢复index⽐较⿇烦,⽽且DDL逼迫,所以放弃了这种⽅法。
使⽤窗⼝内去最⼤值的⽅法,这种⽅法的复杂度和⼆阶差分的差不多,所以我使⽤了这种⽅法:
def pick_idx(self):
'''
Get the index which accord to the frame we want(peek in the window)
:return:
'''
print("pick_idx")
for i, d in enumerate(self.diff):
ub = len(self.diff)-1
lb = 0
if not i-self.window//2 < lb:
lb = i-self.window//2
if not i+self.window//2 > ub:
ub = i+self.window//2
comp_window = self.diff[lb:ub]
if d >= max(comp_window):
self.idx.append(i)
tmp = np.array(self.idx)
tmp = tmp + 1 # to make up the gap when diff
self.idx = list()
print("Extract the Frame Index:"+str(self.idx))
提取关键帧
这⼀步就是⾮常机械的部分了,只要把收集好的self.idx对应取关键帧即可,我还没有在⽹上到直接⽤$O(1)$的⽅式提取关键帧的⽅法,现在只能遍历所有的帧,然后把index in self.idx的帧出来保存,如果有知道怎么直接按index取帧的朋友,希望能指点⼀下我。
def save_key_frame(self):
'''
Save the key frame image
:return:
'''
print("save_key_frame")
cap = cv2.VideoCapture(self.video_path) # 打开视频⽂件
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论