基于Python的OpenCV图像处理3

May 5,2017   6982 words   25 min


视频处理

在之前介绍了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中关于视频输入及输出的内容就介绍完了。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部