Python开发屏幕录制程序出现低FPS、掉帧卡顿问题如何解决?
问题原因
- 屏幕捕获模块性能极差:
PIL.ImageGrab.grab()为纯Python实现的截屏接口,1920*1080分辨率下单次截屏耗时普遍超过100ms,叠加后续数组转换、编码耗时,实际循环帧率远达不到你设定的fps值。拖动窗口时系统桌面渲染负载升高,截屏耗时会进一步上涨,必然出现大量掉帧。 - 色彩空间转换逻辑错误且冗余:
ImageGrab输出的图像为RGB通道顺序,OpenCV的VideoWriter写入需要BGR通道顺序,你代码中调用cv2.cvtColor时传入参数cv2.COLOR_BGR2RGB,转换方向完全写反,既额外消耗CPU算力,输出的视频还会出现蓝红通道互换的偏色问题。 - 编码器与帧率设置不合理:你选用的XVID为软编码器,1080P分辨率下单帧编码耗时高,进一步拖慢整体处理速度;代码中设置
fps=8.0本身远低于显示器120Hz的刷新率,就算没有性能问题,输出视频也会有明显卡顿感。 - 缺少帧率控制与流程解耦:当前代码单线程串行执行截屏、转换、写入操作,循环运行速度完全受每一步耗时影响,实际采集帧率波动极大,写入视频的帧时间戳完全错乱,播放时自然会出现卡顿、速度不均的问题。
优化方案
按优先级执行以下优化:
- 替换高性能截屏库:Windows平台优先用
dxcam(基于DXGI桌面复制接口,1080P下单次截屏耗时不到1ms,支持百帧级采集),跨平台场景可以用mss,性能是ImageGrab的3-5倍,完全可以满足流畅录屏需求。 - 砍掉冗余计算:截屏时直接输出OpenCV需要的BGR格式,省掉色彩空间转换步骤。
- 更换编码器:用
mp4v编码器替代XVID,编码效率更高,输出的mp4文件兼容性也更好。 - 多线程解耦流程:把截屏和视频写入拆成两个独立线程,截屏线程只负责抓帧存入队列,写入线程从队列取帧编码,避免截屏等待时阻塞编码流程,减少掉帧。
- 增加帧率控制逻辑:通过计时稳定采集间隔,保证输出帧率和设定值一致,避免帧率波动导致的播放卡顿。
优化后参考代码
import cv2 import time import queue import threading import dxcam # 安装命令:pip install dxcam opencv-python # 非Windows平台替换为mss即可,性能同样远高于ImageGrab # 录屏配置 TARGET_FPS = 60 # 日常录屏60fps足够流畅,性能够也可以设为120 SCREEN_RES = (1920, 1080) SAVE_PATH = "output.mp4" frame_queue = queue.Queue(maxsize=20) is_running = True def capture_worker(): """截屏线程:仅负责高速抓帧""" # 直接输出BGR格式,无需额外色彩转换 cam = dxcam.create(output_idx=0, output_color="BGR") cam.start(target_fps=TARGET_FPS) interval = 1 / TARGET_FPS next_trigger = time.time() while is_running: now = time.time() if now >= next_trigger: frame = cam.get_latest_frame() # 队列满就丢最旧的帧,避免延迟累积 if frame is not None: if frame_queue.full(): try: frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame) next_trigger += interval # 修正时间累积误差 if next_trigger < now: next_trigger = now + interval cam.stop() def write_worker(): """写入线程:仅负责视频编码存盘""" fourcc = cv2.VideoWriter_fourcc(*"mp4v") writer = cv2.VideoWriter(SAVE_PATH, fourcc, TARGET_FPS, SCREEN_RES) while is_running or not frame_queue.empty(): if not frame_queue.empty(): frame = frame_queue.get() writer.write(frame) else: time.sleep(0.001) writer.release() def start_record(): global is_running # 启动工作线程 t_capture = threading.Thread(target=capture_worker, daemon=True) t_write = threading.Thread(target=write_worker, daemon=True) t_capture.start() t_write.start() # 按键盘q键停止录制 while True: if cv2.waitKey(1) & 0xFF == ord('q'): is_running = False break time.sleep(0.01) t_capture.join() t_write.join() cv2.destroyAllWindows() if __name__ == "__main__": start_record()
注:如果不想用dxcam,替换为mss时只需要修改capture_worker里的截屏逻辑即可,其余多线程、写入逻辑不用改。不要强行把目标帧率设到超过硬件实际能处理的上限,否则还是会出现帧时间戳错乱的问题。
内容的提问来源于stack exchange,提问作者Forty966699669
相关产品推荐
相关产品推荐

