Python opencv库 tkinter 设计屏幕录制工具

有时, 我们在电脑上需要录屏, 或制作gif动画, 用于演示电脑操作等。如何使用Python来实现?

目录

    • 1.使用cv2库生成视频
    • 2.使用tkinter选择录制区域
    • 3.再次实现
    • 4.最终的程序
    • 5.拓展: 创建gif动画

1.使用cv2库生成视频

首先, opencv和PIL库可通过命令pip install opencv-python pillow同时安装。

程序的步骤是:
先调用PIL的ImageGrab模块截取屏幕指定区域的图片, 再转换为cv2的视频帧, 然后写入VideoWriter对象。接着, 程序延时一段时间(1 ÷ fps)。
最后调用VideoWriterrelease()方法, 将视频帧写入文件。
初版程序:

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()

2.使用tkinter选择录制区域

程序使用tkinterCanvas画布, 绘制文字和图形。
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]

3.再次实现

上述程序有不少bug, 如time.sleep()固定延时0.1秒, 而写入帧的步骤消耗了一定时间, 导致了视频回放的速度比实际操作的速度要, 尤其在配置较低, 或录制区域较大时较明显。
Python opencv库 tkinter 设计屏幕录制工具_第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()

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('',_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()

运行效果:
Python opencv库 tkinter 设计屏幕录制工具_第2张图片
本工具还有改进的空间,如录制视频时加入声音、添加裁剪视频功能,等等。
源代码: 录屏.py · GitCode

5.拓展: 创建gif动画

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

你可能感兴趣的:(Python,tkinter,python,图像处理,opencv,视频编解码)