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

news/2024/7/21 6:29:07 标签: python, 图像处理, opencv, 视频编解码

有时, 我们在电脑上需要录屏, 或制作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()方法, 将视频帧写入文件。
初版程序:

python">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)可删除已创建的图形。

另外, 用户可能会从不同方向拖曳选择, 而不一定是从左上角至右下角, 所以还需要先判断出左、右和上、下边再返回值。

python">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") # 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('<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) # 减少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"># --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.最终的程序

python">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 # 窗口已关闭

    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

5.拓展: 创建gif动画

Python中创建gif动画,使用了另一个图像处理库 – imageio
在原最终程序的基础上修改:

python">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


http://www.niftyadmin.cn/n/1735328.html

相关文章

权限管理-动态路由的三种思路

思路1&#xff1a; 登录之后&#xff0c;后端根据该用户的角色查询当该用户的权限信息&#xff0c;这些权限信息包含的标识是和本地完整的动态路由的name是有匹配关系的。我们登录之后拿到权限标识和本地的动态路由进行匹配筛选出属于当前用户的动态路由&#xff0c;然后通过ro…

Python tkinter 设计pickle文件编辑器

在Python中, pickle是用于储存Python对象的模块。但pickle生成的文件是二进制类型, 不容易打开。 为此, 自己制作了一个小型pickle文件编辑器。 目录1.pickle 基础2.程序代码实现1.pickle 基础 pickle.load(file) 从已打开的 file object 文件 中读取打包后的对象。 pickle.l…

echarts 的使用/按钮权限/全局混入

echarts 的使用: // 1. 导入 或者 script外链资源 // 2. 初始化一个echart对象 let echart Echarts.init(挂载DOM节点) // 3. 设置配置项 echart.setOption({配置对象}) 配置对象翻阅文档即可&#xff01; mounted里面做第一次的渲染 请求数据回来之后再次渲染 按钮权限…

Python pyc文件 bytecode的压缩, 加壳和脱壳解析

我们常常看到, 自己用PyInstaller等库打包的exe被别人反编译。而源代码在exe文件中是以字节码形式存储的。掌握了字节码的加密技巧, 就可以防止源代码的反编译。 目录1.字节码是什么2.包装字节码3.压缩字节码4.加壳字节码(方法一)&#xff1a;修改co_code5.加壳字节码(方法二)&…

小程序笔记

什么是小程序? 小程序是一种不需要下载、安装即可使用的应用&#xff0c;它实现了应用触手可及的梦想&#xff0c;用户扫一扫或者搜一下就能打开应用&#xff0c;也 实现了用完即走的理念&#xff0c;用户不用安装太多应用&#xff0c;应用随处可用&#xff0c;但又无须安装卸…

Python pyd文件的制作和编译,以及程序源代码的保护

在Python程序开发后, 有可能想要保护程序的源代码, 避免被uncompyle6等库反编译。 目录pyd文件是什么安装Visual Studio C编译器方法1: 从py文件生成pyd文件 (常用)方法2: 编写C/C代码, 编译成pyd文件pyd文件是什么 pyd文件类似于DLL, 一般用C/C语言编译而成, 可用作模块导入P…

永久关闭IE 浏览器停止支持提示的方法 (针对360安全卫士或Windows 10)

最近, IE11浏览器已被微软官方停止支持, 用户打开原有的IE浏览器会看见各种提示, 给用户的使用带来了不便。本文介绍在Windows系统中关闭IE 浏览器停止支持提示的方法。 目录针对 Windows 10针对360安全卫士作者探索过程结语针对 Windows 10 打开IE浏览器右上角的设置按钮, 找…

Linux项目自动化构建工具——make和makefile

make和makefile 一.基本使用二.make并不是每一次都会进行编译三.原理四.特殊符号 一.基本使用 首先创建一个mycode.c文件&#xff0c;接着使用vim写几行代码。 接着创建一个makefile文件&#xff08;这里的m大写小写均可但需要在当前目录下&#xff09;&#xff0c;并使用vim进…