有时, 我们在电脑上需要录屏, 或制作gif动画, 用于演示电脑操作等。如何使用Python来实现?
首先, opencv和PIL库可通过命令pip install opencv-python pillow
同时安装。
程序的步骤是:
先调用PIL的ImageGrab
模块截取屏幕指定区域的图片, 再转换为cv2
的视频帧, 然后写入VideoWriter
对象。接着, 程序延时一段时间(1 ÷ fps
)。
最后调用VideoWriter
的release()
方法, 将视频帧写入文件。
初版程序:
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对象, 指定视频编码格式
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
# 创建VideoWriter对象, 处理视频
videoWriter = cv2.VideoWriter(filename, fourcc, fps,
(area[2]-area[0],area[3]-area[1]))
while not flag:
image=ImageGrab.grab(area) # 截取屏幕指定区域的图片
# 将PIL的image对象转换为cv2的视频帧
frame = cv2.cvtColor(np.asarray(image),cv2.COLOR_RGB2BGR)
videoWriter.write(frame)
root.update() # 避免Tk窗口卡住
time.sleep(1/fps)
videoWriter.release()
程序使用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('' ,_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") # cursor设置控件的鼠标光标
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('' ,_press)
window.bind('' ,_move_label)
window.bind('' ,_drag,)
window.bind('' ,_release)
while 1: # 循环, 等待窗口关闭
try:
window.update()
time.sleep(0.01) # 减少cpu占用
except tk.TclError:break # 窗口已关闭
x1, x2 = area[0],area[2] # 区分出左、右和上、下边,
if x1 > x2:x1,x2 = x2,x1 # 即允许用户从不同方向拖曳选择
y1, y2 = area[1],area[3]
if y1 > y2:y1,y2 = y2,y1
return [x1, y1, x2, y2]
上述程序有不少bug, 如time.sleep()
固定延时0.1秒, 而写入帧的步骤消耗了一定时间, 导致了视频回放的速度比实际操作的速度要快, 尤其在配置较低, 或录制区域较大时较明显。
所以需要改进程序, 程序如下:
# --snip-- 之前部分省略
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()
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('' ,_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('' ,_press)
window.bind('' ,_move_label)
window.bind('' ,_drag,)
window.bind('' ,_release)
while 1:
try:
window.update()
time.sleep(0.01)
except tk.TclError:break # 窗口已关闭
x1, x2 = area[0],area[2] # 区分出左、右和上、下边,
if x1 > x2:x1,x2 = x2,x1 # 即允许用户从不同方向拖曳选择
y1, y2 = area[1],area[3]
if y1 > y2:y1,y2 = y2,y1
return [x1, y1, x2, y2]
def main():
def _stop():
nonlocal flag
flag=False
btn_start['state']=tk.DISABLED
root.title('录制已结束')
def _start():
nonlocal flag
flag=True
btn_start['text']='停止'
btn_start['command']=_stop
def select():
nonlocal area
area = select_area()
btn_start['state']=tk.NORMAL
root=tk.Tk()
root.title('录屏工具')
btn_select=tk.Button(root,text='选择录制区域',command=select)
btn_select.pack()
btn_start=tk.Button(root,text='开始',command=_start,state=tk.DISABLED)
btn_start.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=None
flag=False
while not flag: # 等待用户点击开始
root.update()
root.title('录制中')
fps=10
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 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=False # 窗口被关闭时
videoWriter.release()
if __name__=="__main__":main()
运行效果:
本工具还有改进的空间,如录制视频时加入声音、添加裁剪视频功能,等等。
源代码: 录屏.py · GitCode
Python中创建gif动画,使用了另一个图像处理库 – imageio
。
在原最终程序的基础上修改:
import imageio
# --snip-- 省略
def main():
# --snip--
filename=dialog.asksaveasfilename(master=root,
filetypes=[("gif动画","*.gif"),("所有文件","*.*")],
defaultextension='.gif')
# --snip--
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))
源程序: 录屏_gif.py · GitCode