有时, 需要通过录屏, 演示电脑端的操作。如何使用Python来实现?
1.使用cv2库和生成视频
首先需要使用opencv和PIL库。使用pip install opencv-python pillow 可同时安装opencv-python 和pillow 两个库。 程序的步骤是: 先调用PIL的ImageGrab 模块截取屏幕指定区域的图片, 再转换为cv2 的视频帧, 然后写入VideoWriter 对象。接着, 程序延时一段时间(1 ÷ fps )。 最后调用VideoWriter 的release() 方法, 释放videoWriter。
import tkinter as tk
import tkinter.filedialog as dialog
import time
from PIL import Image,ImageGrab
import numpy as np
import cv2
def _stop():
global flag;flag=True
btn_stop['state']=tk.DISABLED
root.title('录制已结束')
root=tk.Tk()
root.title('录屏工具')
tk.Button(root,text='停止',command=_stop).pack()
filename="test.avi"
area=[0,0,root.winfo_screenwidth(),root.winfo_screenheight()]
root.title('录制中')
fps=10;flag=False
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
videoWriter = cv2.VideoWriter(filename, fourcc, fps,
(area[2]-area[0],area[3]-area[1]))
while not flag:
image=ImageGrab.grab(area)
frame = cv2.cvtColor(np.asarray(image),cv2.COLOR_RGB2BGR)
videoWriter.write(frame)
root.update()
time.sleep(1/fps)
videoWriter.release()
2.使用tkinter实现选择录制区域
程序使用tkinter 的Canvas 画布, 绘制文字和图形。 create_text (x坐标, y坐标, text=文字) : 创建文本。 create_rectangle (左上角x坐标, 左上角y, 终止点x, 右下角y) : 创建矩形。
注意: 在Canvas 中, 每创建一个图形对象, 如create_xxx() , 方法都会返回一个id , 作为这个图形对象的唯一标识。 用cv.delete(id) 可删除已创建的图形。
def select_area():
area=[0,0,0,0]
rectangle_id=None
def _press(event):
area[0],area[1]=event.x,event.y
def _move_label(event,text=None):
nonlocal tip_id,rectangle_id
text=text or "拖曳选择录制区域(%d, %d)"%(event.x,event.y)
cv.delete(tip_id)
tip_id=cv.create_text((event.x+8,event.y),
text=text, anchor = tk.W,justify = tk.LEFT)
def _drag(event):
nonlocal rectangle_id
if rectangle_id is not None:cv.delete(rectangle_id)
rectangle_id=cv.create_rectangle(area[0],area[1],
event.x,event.y)
_move_label(event)
def _release(event):
area[2],area[3]=event.x,event.y
_move_label(event,"按Enter键接受, 拖曳可重新选择")
window.bind_all('<Key-Return>',_accept)
def _accept(event):
window.destroy()
window=tk.Tk()
window.title("选择录制区域")
window.protocol("WM_DELETE_WINDOW",lambda:None)
cv=tk.Canvas(window,bg='white',cursor="target")
cv.pack(expand=True,fill=tk.BOTH)
tip_id=cv.create_text((cv.winfo_screenwidth()//2,
cv.winfo_screenheight()//2),
text="拖曳选择录制区域",
anchor = tk.W,justify = tk.LEFT)
window.attributes("-alpha",0.6)
window.attributes("-topmost",True)
window.attributes("-fullscreen",True)
window.bind('<Button-1>',_press)
window.bind('<Motion>',_move_label)
window.bind('<B1-Motion>',_drag,)
window.bind('<B1-ButtonRelease>',_release)
while 1:
try:
window.update()
time.sleep(0.01)
except tk.TclError:break
return area
将原先的代码
area=[0,0,root.winfo_screenwidth(),root.winfo_screenheight()]
改成:
area=select_area()
3.再次实现
上述程序有不少bug, 如time.sleep() 固定延时0.1秒, 而写入帧的步骤消耗了一定时间, 导致了视频回放的速度比实际操作的速度要快, 尤其在配置较低, 或录制区域较大时较明显。 所以需要改进程序, 程序如下:
fps=10;flag=False
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
videoWriter = cv2.VideoWriter(filename, fourcc, fps,
(area[2]-area[0],area[3]-area[1]))
start=last=time.perf_counter()
count=0
while not flag:
image=ImageGrab.grab(area)
frame = cv2.cvtColor(np.asarray(image),cv2.COLOR_RGB2BGR)
videoWriter.write(frame)
count+=1
end=time.perf_counter()
time.sleep(max(count/fps-(end-start),0))
print('fps:',str(1/(end-last)))
last = end
root.update()
videoWriter.release()
4.最终的程序
import tkinter as tk
import tkinter.filedialog as dialog
import time
from PIL import Image,ImageGrab
import numpy as np
import cv2
def select_area():
area=[0,0,0,0]
rectangle_id=None
def _press(event):
area[0],area[1]=event.x,event.y
def _move_label(event,text=None):
nonlocal tip_id,rectangle_id
text=text or "拖曳选择录制区域(%d, %d)"%(event.x,event.y)
cv.delete(tip_id)
tip_id=cv.create_text((event.x+8,event.y),
text=text, anchor = tk.W,justify = tk.LEFT)
def _drag(event):
nonlocal rectangle_id
if rectangle_id is not None:cv.delete(rectangle_id)
rectangle_id=cv.create_rectangle(area[0],area[1],
event.x,event.y)
_move_label(event)
def _release(event):
area[2],area[3]=event.x,event.y
_move_label(event,"按Enter键接受, 拖曳可重新选择")
window.bind_all('<Key-Return>',_accept)
def _accept(event):
window.destroy()
window=tk.Tk()
window.title("选择录制区域")
window.protocol("WM_DELETE_WINDOW",lambda:None)
cv=tk.Canvas(window,bg='white',cursor="target")
cv.pack(expand=True,fill=tk.BOTH)
tip_id=cv.create_text((cv.winfo_screenwidth()//2,
cv.winfo_screenheight()//2),
text="拖曳选择录制区域",
anchor = tk.W,justify = tk.LEFT)
window.attributes("-alpha",0.6)
window.attributes("-topmost",True)
window.attributes("-fullscreen",True)
window.bind('<Button-1>',_press)
window.bind('<Motion>',_move_label)
window.bind('<B1-Motion>',_drag,)
window.bind('<B1-ButtonRelease>',_release)
while 1:
try:
window.update()
time.sleep(0.01)
except tk.TclError:break
return area
def main():
def _stop():
nonlocal flag
flag=True
btn_stop['state']=tk.DISABLED
root.title('录制已结束')
root=tk.Tk()
root.title('录屏工具')
btn_stop=tk.Button(root,text='停止',command=_stop)
btn_stop.pack()
lbl_fps=tk.Label(root,text='fps:0')
lbl_fps.pack(fill=tk.X)
filename=dialog.asksaveasfilename(master=root,
filetypes=[("avi视频","*.avi"),("所有文件","*.*")],
defaultextension='.avi')
if not filename.strip():return
area=select_area()
root.title('录制中')
fps=10;flag=False
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
videoWriter = cv2.VideoWriter(filename, fourcc, fps,
(area[2]-area[0],area[3]-area[1]))
start=last=time.perf_counter()
count=0
while not flag:
image=ImageGrab.grab(area)
frame = cv2.cvtColor(np.asarray(image),cv2.COLOR_RGB2BGR)
videoWriter.write(frame)
count+=1
end=time.perf_counter()
time.sleep(max(count/fps-(end-start),0))
try:
lbl_fps['text']='fps:'+str(1/(end-last))
last = end
root.update()
except tk.TclError:flag=True
videoWriter.release()
if __name__=="__main__":main()
5.拓展: 创建gif动画
Python中创建gif动画, 使用了另一个图像处理库 – imageio 。 在原最终程序的基础上修改:
import imageio
def main():
filename=dialog.asksaveasfilename(master=root,
filetypes=[("gif动画","*.gif"),("所有文件","*.*")],
defaultextension='.gif')
lst_image=[]
while not flag:
image=ImageGrab.grab(area)
lst_image.append(image)
end=time.perf_counter()
time.sleep(max(len(lst_image)/fps-(end-start),0))
try:
lbl_fps['text']='fps:'+str(1/(end-last))
last=end
root.update()
except tk.TclError:flag=True
imageio.mimsave(filename,lst_image,'GIF',
duration=(end-start)/len(lst_image))
|