美文网首页
手把手教你使用opencv-python库制作屏幕录制工具(附源

手把手教你使用opencv-python库制作屏幕录制工具(附源

作者: 程序员小西 | 来源:发表于2022-01-08 09:45 被阅读0次

    目录

    目录

    应用平台

    屏幕录制部分

    计算视频最优fps及使用numpy计算中间帧数组

    使用pynput监听键盘按键

    如何保存MP4格式视频

    源码

    总结

    最近有在使用屏幕录制软件录制桌面,在用的过程中突发奇想,使用python能不能做屏幕录制工具,也锻炼下自己的动手能力。接下准备写使用python如何做屏幕录制工具的系列文章:

    录制屏幕制作视频

    录制音频

    合成视频,音频

    基于pyqt5制作可视化窗口

    大概上述四个部分,希望自己能够尽快完善,接下来开始使用python制作屏幕录制部分。

    应用平台

    windows 10

    python 3.7

    屏幕录制部分

    屏幕录制可以简单地理解为将屏幕快照以动图的形式播放,这里我选用PIL下的ImageGrab来截取屏幕画面,首先

    pip install Pillow

    之后需要将截取到的快照数组合成为视频,使用cv2模块

    pip install opencv-python

    ImageGrab类不能直接存储为视频,使用numpy模块进行数组化,再通过cv2.COLOR_BGR2RGB转换为cv2色彩通道。

    pip install numpy

    屏幕录制主要代码:

    import numpy as np

    from PIL import ImageGrab

    import cv2

    im = ImageGrab.grab()

    width, high = im.size  # 获取屏幕的宽和高

    fourcc = cv2.VideoWriter_fourcc(*'I420')  # 设置视频编码格式

    fps = 15  # 设置帧率

    video = cv2.VideoWriter('test.avi', fourcc, fps, (width, high))

    while True:  # 开始录制

        im = ImageGrab.grab()

        im_cv = cv2.cvtColor(np.array(im), cv2.COLOR_BGR2RGB)

        # 图像写入

        video.write(im_cv)

        if xx:  # 当某某条件满足中断循环

            break

    video.release()  # 释放缓存,持久化视频

    测试运行可以保存屏幕快照为视频,但操作起来不优雅,也不利于后续的操作。

    封装成类,继承线程父类,方便使用键盘来控制视频录制的结束。

    from threading import Thread

    class ScreenshotVideo(Thread):

        def __init__(self):

         """初始化参数"""

            super().__init__()

    详细代码将在文末给出。

    计算视频最优fps及使用numpy计算中间帧数组

    实际操作中视频录制在不同电脑中会出现不一样的帧率,导致视频播放或快或慢,需要根据不同的电脑计算出相应的最优fps值。

    def video_best_fps(self, path):

        """获取电脑录制视频的最优帧率"""

        video = cv2.VideoCapture(path)  # 读取视频

        fps = video.get(cv2.CAP_PROP_FPS)  # 获取当前视频的帧率

        count = video.get(cv2.CAP_PROP_FRAME_COUNT)  # 获取视频帧数,即该视频有多少幅画面

        self.best_fps = int(fps * ((int(count) / fps) / self.spend_time))   # 计算播放时间与录制时间对比得到最优帧率

        video.release()

    再调整帧率参数进行录制视频就减弱了视频播放太快或者太慢。也可以给视频增加帧数从而延长播放时间,这里我采用一种很简单的方法增加视频帧,仅供参考。

    from numba import jit

    # 使用numpy计算相邻两帧图像且更接近于后一帧的图像

    # 调用jit方法加速数组计算

    @jit(nopython=True)

    def average_n(x, y):

        """Numpy计算趋近值"""

        return ((x + y + y) // 3).astype(x.dtype)

    该方法仅针对于设置的fps比最优fps要高时,处理后的视频观感,视频还是较为急促,但是细节帧增多,所以播放时长会比未处理前的要长,略有残影。

    使用pynput监听键盘按键

    在视频录制中,并不知道视频何时结束,所以用while循环包裹录制代码,但也不可能让代码无休止的运行下去,在此使用监听键盘模块来中断录制代码的运行。

    from pynput import keyboard  # pip install pynput

    def hotkey(self):

        """热键监听"""

        with keyboard.Listener(on_press=self.on_press) as listener:

            listener.join()

    def on_press(self, key):

        try:

            if key.char == 't':  # 录屏结束,保存视频

                self.flag = True

            elif key.char == 'k':  # 录屏中止,删除文件

                self.flag = True

                self.kill = True

        except Exception as e:

            print(e)

    按下键盘“T”键时,结束录制,保存视频。“K”键则是停止录制,删除缓存文件。

    如何保存MP4格式视频

    视频编码格式应该为('a', 'v', 'c', '1'),文件后缀为'.mp4',在录制前先去下下载对应平台的dll.bz2文件,将压缩包解压放在项目文件夹下。

    源码

    本文实现的源码如下:

    import time

    from PIL import ImageGrab

    import cv2

    from pathlib import Path

    import numpy as np

    from numba import jit

    from pynput import keyboard

    from threading import Thread

    @jit(nopython=True)

    def average_n(x, y):

        """Numpy计算趋近值"""

        return ((x + y + y) // 3).astype(x.dtype)

    class ScreenshotVideo(Thread):

        def __init__(self, width, high, path='', fps=15):

            """初始化参数"""

            super().__init__()

            self.save_file = path

            self.best_fps = fps

            self.fps = fps

            self.width = width

            self.high = high

            self.spend_time = 1

            self.flag = False

            self.kill = False

            self.video = None

        def __call__(self, path):

            """重载视频路径,便于类的二次调用"""

            self.save_file = Path(path)

            self.video = self.init_videowriter(self.save_file)

        @staticmethod

        def screenshot():

            """静态方法,屏幕截图,并转换为np.array数组"""

            return np.array(ImageGrab.grab())

        @staticmethod

        def get_fourcc(name):

            """视频编码字典"""

            fourcc_maps = {'.avi': 'I420',

                           '.m4v': 'mp4v',

                           '.mp4': 'avc1',

                           '.ogv': 'THEO',

                           '.flv': 'FLV1',

                           }

            return fourcc_maps.get(name)

        def init_videowriter(self, path):

            """获取视频编码并新建视频文件"""

            if not path:

                raise Exception('视频路径未设置,请设置\nvideo = ScreenshotVideo(fps,width,high)\nvideo = video(video_path)')

            path = Path(path) if isinstance(path, str) else path

            fourcc = cv2.VideoWriter_fourcc(*self.get_fourcc(path.suffix))

            return cv2.VideoWriter(path.as_posix(), fourcc, self.fps, (self.width, self.high))

        def video_record_doing(self, img):

            """将BGR数组转换为RGB数组"""

            im_cv = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

            self.video.write(im_cv)

        def video_record_end(self):

            """录制结束,根据条件判断文件是否保存"""

            self.video.release()

            cv2.destroyAllWindows()

            if self.save_file and self.kill:

                Path(self.save_file).unlink()

        def video_best_fps(self, path):

            """获取电脑录制视频的最优帧率"""

            video = cv2.VideoCapture(path)

            fps = video.get(cv2.CAP_PROP_FPS)

            count = video.get(cv2.CAP_PROP_FRAME_COUNT)

            self.best_fps = int(fps * ((int(count) / fps) / self.spend_time))

            video.release()

        def pre_video_record(self):

            """预录制,以获取最佳fps值"""

            self.video = self.init_videowriter('test.mp4')

            start_time = time.time()

            for _ in range(10):

                im = self.screenshot()

                self.video_record_doing(im)

            self.spend_time = round(time.time() - start_time, 4)

            self.video_record_end()

            time.sleep(2)

            self.video_best_fps('test.mp4')

            Path('test.mp4').unlink()

        def insert_frame_array(self, frame_list):

            """Numpy增强截图信息"""

            fps_n = round(self.fps / self.best_fps)

            if fps_n <= 0:

                return frame_list

            times = int(np.log2(fps_n))  # 倍率

            for _ in range(times):

                frame_list2 = map(average_n, [frame_list[0]] + frame_list[:-1], frame_list)

                frame_list = [[x, y] for x, y in zip(frame_list2, frame_list)]

                frame_list = [j for i in frame_list for j in i]

            return frame_list

        def frame2video_run(self):

            """使用opencv将连续型截图转换为视频"""

            self.video = self.init_videowriter(self.save_file)

            start_time = time.time()

            frame_list = []

            while True:

                frame_list.append(self.screenshot())

                if self.flag:

                    break

            self.spend_time = round(time.time() - start_time, 4)

            if not self.kill:  # 视频录制不被终止将逐帧处理图像

                frame_list = self.insert_frame_array(frame_list)

                for im in frame_list:

                    self.video_record_doing(im)

            self.video_record_end()

        def hotkey(self):

            """热键监听"""

            with keyboard.Listener(on_press=self.on_press) as listener:

                listener.join()

        def on_press(self, key):

            try:

                if key.char == 't':  # 录屏结束,保存视频

                    self.flag = True

                elif key.char == 'k':  # 录屏中止,删除文件

                    self.flag = True

                    self.kill = True

            except Exception as e:

                print(e)

        def run(self):

            # 运行函数

            # 设置守护线程

            Thread(target=self.hotkey, daemon=True).start()

            # 运行截图函数

            self.frame2video_run()

    screen = ImageGrab.grab()

    width, high = screen.size

    video = ScreenshotVideo(width, high, fps=60)

    video.pre_video_record()  # 预录制获取最优fps

    video('test1.mp4')

    video.run()

    总结

    本文目前使用了opencv和相关模块对屏幕进行录制并转换为视频保存,学习将多个函数封装为类,方便后续功能开发。学习的道路是无止境的,大胆的迈步走吧!

    小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎关注我,我拉你进Python学习交流群共同探讨学习。

    相关文章

      网友评论

          本文标题:手把手教你使用opencv-python库制作屏幕录制工具(附源

          本文链接:https://www.haomeiwen.com/subject/qdlecrtx.html