视频处理
在之前介绍了OpenCV图像的输入输出以及相关操作,其实视频处理也是同样的方法。 把视频看成是一帧帧静态影像,那么之前所有的图像处理的操作便能移植到视频上来了。 首先介绍OpenCV中视频的输入及输出操作。
视频输入
视频输入有两种模式,一种是读取现有的视频文件,另一种是通过摄像头实时获取视频。
在OpenCV中,这两种模式用一个VideoCapture
类就可以全部搞定,唯一的区别是构造函数的参数不同。
下面分别讲解:
摄像头捕获视频
要获取视频,需要调用VideoCapture
类,其构造参数是设备的索引号,也即指定需要用哪个
摄像头捕获视频。这点类似于Android编程获取相机号来指定是前置相机还是后置相机。一般
笔记本都有内置摄像头,所以参数可以设置成0。当然如果有多个摄像头可以通过设置成1或其它
来选择别的摄像头。开启捕获之后,别忘了完成后要关闭摄像头。
# coding=utf-8
import cv2
# 新建一个VideoCapture对象,指定第0个相机进行视频捕获
cap = cv2.VideoCapture(0)
# 一直循环捕获,直到手动退出
while 1:
# 返回两个值,ret表示读取是否成功,frame为读取的帧内容
ret, frame = cap.read()
# 判断传入的帧是否为空,为空则退出
if frame is None:
break
else:
# 调用OpenCV图像显示函数显示每一帧
cv2.imshow("video", frame)
# 用于进行退出条件的判断,这里与0xFF进行了与运算,取输入的低八位
k = cv2.waitKey(1) & 0xFF
# 27是ESC键,表示如果按ESC键则退出
if k == 27:
break
# 释放VideoCapture对象
cap.release()
利用上述代码即可实现视频的捕获。核心代码只有三行。分别对应三个步骤:新建VideoCapture对象(cap = cv2.VideoCapture(0))、
读取每帧内容(ret, frame = cap.read())、显示每帧内容(cv2.imshow(“video”, frame))。
有时候可能无法成功初始化摄像头。这时可以使用cap.isOpened()
来判断是否成功初始化,如果没成功,
则使用函数cap.open()
打开。当然我们还可以用cap.get(propId)
获取视频的各种属性信息,范围为0-18。每一个数代表
视频的一个属性。其中某些值是可以被修改的,利用cap.set(propId,value)
即可。例如3和4分别对应视频的宽和高,默认情况
下得到的是640×480。但我们可以手动设置cap.set(3,320)
、cap.set(4,240)
,从而改变视频大小。完整的视频属性如下:
打开视频文件
正如前面所说,无论是摄像头捕获还是打开文件,都是使用VideoCapture
类进行,只是构造参数不同。打开文件时,
传递的是视频文件的路径。同时要注意的是,在播放每一帧时,使用cv2.waitKey()
设置适当的持续时间。如果设置太低
视频就会播放地非常快,如果太高会播放的很慢。当然这也是控制播放速度的一种方式。一般设置25ms即可。
# coding=utf-8
import cv2
# 新建一个VideoCapture对象,打开视频
cap = cv2.VideoCapture("E:\\02.mp4")
# 一直播放视频,直到手动退出
while 1:
# 返回两个值,ret表示读取是否成功,frame为读取的帧内容
ret, frame = cap.read()
# 判断读取的帧内容是否为空,否则退出
if frame is None:
break
else:
# 调用OpenCV图像显示函数显示每一帧
cv2.imshow("video", frame)
# 用于进行退出条件的判断
k = cv2.waitKey(25) & 0xFF
# 27是ESC键,表示如果按ESC键则退出
if k == 27:
break
# 释放VideoCapture对象
cap.release()
相比于摄像头捕获,这段代码只改动了两个地方。一是构造函数参数变成了文件路径,而是waitKey的时间变成了25ms。当然如果
不修改还是1的话,那视频就是加速播放了。另外在视频捕获模式下,这个参数是不起作用的。不管是1还是25,并不会影响捕获
时视频的速度。而且在打开视频文件的模式下,之前说的那19个视频参数是只读不能修改的。
注意:
如果你打开视频文件失败,请先检查你是否已经正确安装了ffmpeg!如果没有它,是无法正常打开、保存视频文件的。
我们可以直接在OpenCV的安装包里找到,如下所示:
找到之后将其拷贝到Python的安装根目录下:
这样再次运行代码应该就能正常打开了,当然前提是你的代码是正确的。
输出视频
捕获视频保存
在我们捕获视频,并对每一帧进行加工后,我们想要保存这个视频。对于图片来说很简单,只需要cv2.imwrite(img,save_path)
,
但对于视频来说,就稍微复杂点了。与视频捕获的VideoCapture对应,我们需要建立一个VideoWriter对象。其次需要确定输出
文件的名字、指定视频FourCC编码、播放频率、帧大小以及isColor标签。若为True则为彩色图,否则为灰度图。
FourCC是一个4字节码,用来确定视频的编码格式。可用的编码列表可以从这里查到。FourCC码以下面的格式
传递给程序,以MJPG为例,两种写法都可以:
cv2.cv.FOURCC('M', 'J', 'P', 'G')
cv2.cv.FOURCC(*'MJPG')
MJPG编码器编码的视频会非常大,X264编码器编码的视频很小,而XVID编码器是相对较好的,质量较高且体积较小。 以下是捕获视频并保存的代码:
# coding=utf-8
import cv2
cap = cv2.VideoCapture(0)
# 指定FourCC编码是XVID,注意cv2和cv里的函数名不太一样,现在用cv2了
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 指定文件输出路径、编码、帧率以及每一帧的大小,还有最后一个可选参数isColor。
out = cv2.VideoWriter("E:\\test.avi", fourcc, 20.0, (640, 480))
while 1:
ret, frame = cap.read()
if frame is None:
break
else:
# 在获取每一帧并进行处理后,进行输出
out.write(frame)
cv2.imshow("video", frame)
k = cv2.waitKey(1) & 0xFF
if k == 27:
break
cap.release()
# 最后别忘了释放掉VideoWriter
out.release()
在代码中,首先指定了FourCC编码为XVID,然后新建了一个VideoWriter对象out,并 指定了文件路径、编码、帧率以及大小等信息。然后对每一帧进行输出。注意输出视频的速度 是与waitKey时间是无关的,与我们设定的fps有关。如果我们设定的fps比摄像头捕获的fps 要大,那么视频就加速了,反之变慢。
文件视频保存
我们更多的需求是从一个已有的视频文件中读取内容,并对其中的每一帧进行处理,
然后将处理后的结果重新保存成视频文件。在保存的时候,我们希望只是改变每帧的图像内容,
并不改变原始视频的相关属性,如帧大小、帧率等等。因此我们需要在读取视频的时候获取到这些值,
并将其在VideoWriter中指定。还记得我们前面说过的cap.get()
函数,3和4分别对应宽高,
5对应帧率。因此我们可以利用这个函数实现原始视频相关属性的读取。
# coding=utf-8
import cv2
cap = cv2.VideoCapture("E:\\02.mp4")
# 获取原始视频帧大小及fps信息,注意宽高需要强制类型转换成int,否则VideoWriter会报错
width = int(cap.get(3))
height = int(cap.get(4))
fps = cap.get(5)
# 指定FourCC编码是XVID
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter("E:\\test.avi", fourcc, fps, (width, height))
while 1:
ret, frame = cap.read()
if frame is None:
break
else:
# 在获取每一帧并进行处理后,进行输出
out.write(frame)
cv2.imshow("video", frame)
k = cv2.waitKey(25) & 0xFF
if k == 27:
break
cap.release()
out.release()
这里,waitKey的时间并不会影响输出视频,只是在播放的时候改变了速度。保存时还是原始速度。
我们可以在cap.read()
与out.write()
之间对每一帧进行操作。
视频速度
上面说了这么多,这里简单总结一下视频读取、输出时的速度问题。
1.在用摄像头捕获视频时,我们无法控制视频显示的速度,
其显示速度是由硬件本身捕获的fps决定,且与waitKey的时间无关,用cap.get(5)
获取到的fps是0。
2.在读取视频显示时,其速度与waitKey的时间有关,一般为25ms。但其实如果深入理解,这个waitKey的时间其实代表每一帧
停留的时间。例如某视频的fps为24,也就是说一秒有24帧影像,1000/24=41.667ms。也就是说每一帧的暂留时间是41.667ms,
如果低于这个值便会加速,高于这个值便会减速。所以我们也可以通过读取文件的fps动态计算出每帧的等待时间,从而
实现“原速”播放。当然如果不想麻烦的话也可以设成25ms,25ms对应的fps是40。
3.保存摄像头捕获的视频。由第一点所说,显示的速度是不受我们控制的,但是保存视频的速度是受我们设定的fps控制的。
如果设定的fps大于摄像头实际捕获的fps,那么视频就会加速,反之减速。但是我们并不能获取到实际捕获fps,所以只能
尝试,并设置一个最接近的fps用于保存。例如在我的电脑上,fps设为20就会有明显的加速,设为10则接近真实速度。
4.视频文件保存。由上面所说,视频的展示速度是受waitKey控制的。保存速度是由我们人为设定的。如果我们可以通过动态获取
原始视频的fps来作为输出视频的fps,这样可以保证输出视频与原始视频有相同的fps。当然也可以设置成0.5fps或2fps,
从而对输出视频进行加速或减速。
统一的代码
视频获取
通过上面的介绍可以发现,摄像头捕获视频与读取视频文件代码几乎是完全相同的。因此我们完全可以写一段统一的代码。
# coding=utf-8
import cv2
cap = cv2.VideoCapture(0)
# 设定waitTime的默认值,任何非0数都可以。
# 如果是0的话表示一直等待,那么视频就会卡在某一帧一直不动了
waitTime = 1
if cap.get(5) != 0:
waitTime = int(1000.0 / cap.get(5))
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
else:
cv2.imshow("video", frame)
# 注意wait的时间必须是int
k = cv2.waitKey(waitTime) & 0xFF
if k == 27:
break
cap.release()
这段代码便实现了统一,将VideoCapture里的0换成文件地址便能打开文件,而且等待时间是动态计算的,保持了原速。 同时注意在这里增加了判断语句,当视频读取完后,会自动break。不然当视频播放完后会报错,提示frame为空。
视频保存
# coding=utf-8
import cv2
cap = cv2.VideoCapture(0)
# 注意VideoWriter的长宽是int型,需要强制转换一下
width = int(cap.get(3))
height = int(cap.get(4))
# 默认fps设置为10,waitTime为1
fps = 10
waitTime = 1
if cap.get(5) != 0:
waitTime = int(1000.0 / cap.get(5))
fps = cap.get(5)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter("E:\\output.avi", fourcc, fps, (width, height))
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
else:
out.write(frame)
cv2.imshow("video", frame)
k = cv2.waitKey(waitTime) & 0xFF
if k == 27:
break
cap.release()
out.release()
这段代码实现了不论是捕获还是文件,都可以保存成视频文件。出于代码的容错性考虑,这里也最好增加一个判断语句, 判断frame是否为空。
实战演练
我们设定这样一个任务:读取一个电脑上的视频文件,对视频的每一帧进行二值化操作,然后再保存成新的视频。分析任务其实 很简单,只需要在保存视频的代码里对获取到的frame进行灰度化和二值化操作即可。代码如下:
# coding=utf-8
import numpy as np
import cv2
cap = cv2.VideoCapture("E:\\02.mp4")
width = int(cap.get(3))
height = int(cap.get(4))
fps = 10
waitTime = 1
if cap.get(5) != 0:
waitTime = int(1000.0 / cap.get(5))
fps = cap.get(5)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter("E:\\output.avi", fourcc, fps, (width, height))
out_frame = np.zeros((height, width, 3), np.uint8)
frame2gray = np.zeros((height, width, 3), np.uint8)
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
else:
frame2gray[:, :, 0] = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame2gray[:, :, 1] = frame2gray[:, :, 0]
frame2gray[:, :, 2] = frame2gray[:, :, 0]
ret, out_frame = cv2.threshold(frame2gray, 128, 255, cv2.THRESH_BINARY)
out.write(out_frame)
cv2.imshow("video", frame)
cv2.imshow("binary", out_frame)
k = cv2.waitKey(waitTime) & 0xFF
if k == 27:
break
cap.release()
out.release()
这段代码实现的效果如上图所示,可以实现视频的二值化操作并将结果保存在output.avi中。但还是有一些值得注意的地方。
首先是调用OpenCV的灰度化函数,转换后就只有一个通道了。这样继续传递给二值化函数,还是只有一个通道。cv2.imshow()
可以正常显示,但是保存的时候就出现问题了,会发现保存不了。因此需要将灰度图像转换成三通道的。其次,如果直接对RGB
图像调用二值化函数,得到的是彩色后的二值化图像,并不是基于单通道灰度的二值化,对比可如图所示。
因为这是基于刚刚统一输出的代码,所以它同样适用于摄像头捕获的实时视频,
只需将文件路径改成0即可。同样可以实现二值化操作,并将结果输出到文件。
此外,如果还想继续完善,还可以在控制台中输出转换的进度。利用cap.get(7)
即可获取视频中帧的总数,再定义一个count变量
用于计数,便可以实现转换百分比的显示了。
另一方面,还可以不断优化代码的性能。但其实OpenCV已经优化地很好了。将上述代码在我的平板上测试。CPU是Intel N3450,
主频1.1Ghz,睿频2.2Ghz、内存4G、在SD卡上进行视频文件的读写操作,不论是摄像头实时捕获处理,还是读取文件处理
(视频文件大小1104 x 622,fps 23)可以发现是完全没有问题的。运行时的性能截图如下:
CPU的使用率一直比较稳定,在0.9Ghz左右,磁盘占用率也并不是很高。可见OpenCV的优化还是相当厉害的。
至此,OpenCV中关于视频输入及输出的内容就介绍完了。
本文作者原创,未经许可不得转载,谢谢配合