前两天一直在跟文本和图片打交道,今天我们更进一步,做一个能够播放本地视频文件的播放器。
主要用到了opencv库,原理和实时的摄像头显示是一样,只是把每一帧图像经过转换后封装到tkinter上。但是这个图像的显示,要想没有延迟、且不占用过多内存,只能使用canvas画布来实现。只想把视频播放出来的话,也可以用label显示图片,然后调用.after()方法更新,但是这种方法至少要把更新间隔设为10ms(i7处理器),否则会无法正常显示,而且内存也会逐渐增长。
我们直接来看完整代码:
import pygame as py import _thread import time import tkinter as tk from tkinter import * import cv2 from PIL import Image, ImageTk import multiprocessing window_width=960 window_height=720 image_width=int(window_width*0.5) image_height=int(window_height*0.5) imagepos_x=0 imagepos_y=0 butpos_x=450 butpos_y=450 vc1 = cv2.VideoCapture('25.mp4') #读取视频 #图像转换,用于在画布中显示 def tkImage(vc): ref,frame = vc.read() cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pilImage = Image.fromarray(cvimage) pilImage = pilImage.resize((image_width, image_height),Image.ANTIALIAS) tkImage = ImageTk.PhotoImage(image=pilImage) return tkImage #图像的显示与更新 def video(): def video_loop(): try: while True: picture1=tkImage(vc1) canvas1.create_image(0,0,anchor='nw',image=picture1) canvas2.create_image(0,0,anchor='nw',image=picture1) canvas3.create_image(0,0,anchor='nw',image=picture1) canvas4.create_image(0,0,anchor='nw',image=picture1) win.update_idletasks() #最重要的更新是靠这两句来实现 win.update() except: pass video_loop() win.mainloop() vc1.release() cv2.destroyAllWindows() '''布局''' win = tk.Tk() win.geometry(str(window_width)+'x'+str(window_height)) canvas1 =Canvas(win,bg='white',width=image_width,height=image_height) canvas1.place(x=imagepos_x,y=imagepos_y) canvas2 =Canvas(win,bg='white',width=image_width,height=image_height) canvas2.place(x=480,y=0) canvas3 =Canvas(win,bg='white',width=image_width,height=image_height) canvas3.place(x=imagepos_x,y=360) canvas4 =Canvas(win,bg='white',width=image_width,height=image_height) canvas4.place(x=480,y=360) if __name__ == '__main__': p1 = multiprocessing.Process(target=video) p1.start()
你可能会很奇怪,为什么要用到多进程?这里我先卖一个关子,现在这个程序里其实并不需要多进程,但是一会我们就用到了。
如果我们实现了以上内容,我们发现了一个很严重的问题——没有声音!这是因为cv2.VideoCapture是无法获取声音的,可是看视频没声怎么行,总不能只看卓别林和叶逢春吧?
我琢磨了许久,看来要想播放声音,只能单独提取出音频文件,和视频一起播放了。提取mp4中的音频,并写入mp3文件,需要moviepy这个库,代码很简单:
from moviepy.editor import* video = VideoFileClip('25.mp4') audio = video.audio audio.write_audiofile('25.mp3')
800M的视频,提取出的音频文件只有50M左右,还算能接受吧。
接下来只要在播放视频的同时播放音频就可以了,最开始我尝试了用多线程,发现音频会影响tkinter的刷新,导致视频十分卡顿,所以我就改用了多进程,视频终于不卡了(如果画布太大还是会略有延迟,我现在设的大小基本没有延迟了)。
用pygame来播放mp3文件:
def voice(): py.mixer.init() # 文件加载 track=py.mixer.music.load('25.mp3') # 播放,第一个是播放值 -1代表循环播放, 第二个参数代表开始播放的时间 py.mixer.music.play(-1, 0) while 1: #一定要有whlie让程序暂停在这,否则会自动停止 pass
最后的主函数改为:(多进程的实现一定要放在主函数里)
if __name__ == '__main__': p1 = multiprocessing.Process(target=voice) p2 = multiprocessing.Process(target=video) p1.start() p2.start()
(好吧,其实不用多进程也行,在播放视频前先执行播放音频的语句就行,音频会在后台自动运行,但是会让视频变卡)
这样一个简单的本地视频播放器就实现了,但是每看一个视频都要提取出音频,未免太智障了吧?所以今天这个程序玩玩就行,用处不大……(除非你爱看相声,提取出的音频还能放到手机里随时听)
但是,你以为到这就结束了吗?
刚才我们同时创建了四个画布,一起播放视频。同样的方法,是不是可以用来做视频监控呢?就像电影里演的那样,屏幕上显示好几个摄像头的监控影像,其实用tkinter就能实现了!当然,如果同时显示太多图像,延迟肯定会增加。
那么作业来了——
小作业:制作一个多摄像头的实时监控软件,同时检测图像中是否有人物出现,一旦有人则立刻报警。(提示:摄像头图像的人脸识别上网一搜就能找到,需要调用opencv官方提供的人脸分类器文件;报警的方式则有很多,如果不嫌麻烦的话,可以用twilio给自己发短信)
你以为这又结束了?呵呵,你还是不了解我啊……
既然是视频软件,怎么少得了暂停与倍速的功能呢?
先说暂停,我们用单机左键暂停,再点一下继续。我们需要加一个lock变量作为视频是否播放的判断条件,初始值设为0,每次点击左键就加一;
至于倍速功能,则绑定右键事件,倍速值也是每点击一次则加一,并且设置倍速上限为4倍;倍速的实现在tkImage函数里。
增加和修改的代码如下:
lock=0 #暂停标志 n=1 #初始倍速 def tkImage(n): #倍速在这里实现 for i in range(n): ref,frame = vc1.read() cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #注意这句,后面再说明 pilImage = Image.fromarray(cvimage) pilImage = pilImage.resize((image_width, image_height),Image.ANTIALIAS) tkImage1 = ImageTk.PhotoImage(image=pilImage) return tkImage1 def video(): def video_loop(): try: while True: if lock % 2 == 0: picture1=tkImage(n) canvas1.create_image(0,0,anchor='nw',image=picture1) canvas2.create_image(0,0,anchor='nw',image=picture1) canvas3.create_image(0,0,anchor='nw',image=picture1) canvas4.create_image(0,0,anchor='nw',image=picture1) win.update_idletasks() #最重要的更新是靠这两句来实现 win.update() else: win.update_idletasks() #最重要的更新是靠这两句来实现 win.update() except: pass video_loop() win.mainloop() vc1.release() cv2.destroyAllWindows() def right(self): global n n+=1 if n>4: n=1 def left(self): global lock lock+=1 #放在创建canvas的后面 canvas1.bind('<Button-1>', left) canvas1.bind('<Button-3>', right)
当然,这两个功能仅限于视频,另一个进程的音频文件是无法暂停和倍速的。所以啊,还是得看默片。
注意事项:
tkImage函数中的cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY),和前文同样位置的cvimage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)不同,前者是灰度图,后者是彩色图。如果我们用灰度图的话,会有丢帧现象,播放速度变成正常速度的1.5倍左右。
抛开音频不谈,这个播放器还是差点意思——没有时间和进度条啊!时间好说,cv2.VideoCapture读取视频后,可以用.get()获取总帧数和帧率,做除法就是总时间(比如1000和40,那么时长就是40秒),然后在每次读帧的时候计数,每过一个帧率就是一秒,最后用label显示出来就行了。
至于进度条咋办呢?一样不难!用canvas.create_rectangle绘制整个进度条的矩形框,然后用canvas.coords来填充。你可以每过一个帧率就填充一次,也可以自定义填充频率,只要根据矩形框的宽度,计算好每次填充的大小就行。注意:这两个函数的参数都包括了矩形框的对角线坐标,但是这个坐标不是绝对坐标,而是相对于矩形框所在的canvas的坐标。
怎么样,能暂停、开始,能倍速,能显示时长和进度条的视频播放器就此完成了。如果你喜欢看默剧的话,快点玩起来吧!