MacOS下如何用Python(mss/pyautogui/PIL)提升截图FPS?
我正在为计算机视觉(CV)项目连续采集屏幕截图,但帧率(FPS)较低。当前使用的是2019款MacBook Pro,已尝试pyautogui、mss和PIL库,其中mss库的表现最佳,约为3FPS,截取局部屏幕时速度会快很多。这是普遍结果吗?如何进一步优化?参考相关方案后,spl的方案能达到约8FPS,但截取窗口变大时速度仍会下降。
以下是我的代码:
import PIL.ImageGrab import cv2 as cv import numpy as np import os from mss.darwin import MSS as mss from time import time os.chdir(os.path.dirname(os.path.abspath(__file__))) loop_time=time() with mss() as sct: # monitor = {"top": 500, "left": 500, "width": 500, "height": 500} while True: # screenshot = sct.grab(monitor) filename = sct.shot() cv_image = cv.imread(filename) # Read the screenshot file cv_image = cv.cvtColor(cv_image, cv.COLOR_BGRA2BGR) # Convert from BGRA to BGR cv.imshow("screen", cv_image) print('FPS{}'.format(1 / (time() - loop_time))) loop_time = time() if cv.waitKey(1) == ord("q"): break cv.destroyAllWindows()
我希望实现全屏截图达到约20FPS的效果。
1. 彻底消除磁盘IO开销(核心优化)
你当前代码的最大瓶颈是sct.shot()将截图写入磁盘,再用cv.imread()读取——磁盘IO的速度远慢于内存操作。直接用sct.grab()获取内存中的截图数据,跳过文件读写步骤,帧率会立刻提升。
优化后的代码:
import cv2 as cv import numpy as np from mss.darwin import MSS as mss from time import time with mss() as sct: # 取全屏区域(mss的monitors[0]对应整个屏幕) monitor = sct.monitors[0] loop_time = time() while True: # 直接在内存中捕获截图 screenshot = sct.grab(monitor) # 转换为OpenCV兼容的numpy数组,再转BGR格式 cv_image = np.array(screenshot) cv_image = cv.cvtColor(cv_image, cv.COLOR_BGRA2BGR) cv.imshow("screen", cv_image) print('FPS: {:.2f}'.format(1 / (time() - loop_time))) loop_time = time() if cv.waitKey(1) == ord("q"): break cv.destroyAllWindows()
2. 降低截图分辨率/色彩深度
2019款MacBook Pro的全屏分辨率为2880×1800,像素量巨大。可以通过缩小分辨率减少计算和传输开销:
- 捕获后直接缩放图像:在转BGR后添加
cv_image = cv.resize(cv_image, (1440, 900))(缩至原尺寸一半) - 或者仅捕获屏幕的低分辨率镜像(macOS系统设置中开启“缩放”模式,让系统输出低分辨率画面,再捕获全屏)
如果CV项目对分辨率要求不高,这一步能让帧率接近20FPS。
3. 优化OpenCV显示环节
cv.imshow()的渲染会占用部分资源:
- 如果不需要实时预览,直接注释掉
cv.imshow()和cv.waitKey(),帧率会大幅提升(甚至超过20FPS) - 保留预览的话,用
cv.setWindowProperty("screen", cv.WND_PROP_FULLSCREEN, cv.WINDOW_FULLSCREEN)设置全屏窗口,减少窗口装饰的渲染开销
4. 调用macOS原生API(高阶优化)
mss已经封装了原生框架,但直接调用Quartz(macOS核心图形框架)可以绕过Python库的额外开销,进一步提升帧率。
示例代码:
import cv2 as cv import numpy as np from Quartz import CGDisplayCreateImage, kCGDirectMainDisplay from time import time while True: loop_time = time() # 直接调用原生API获取主屏幕图像 display_image = CGDisplayCreateImage(kCGDirectMainDisplay) # 转换为numpy数组 width = display_image.width height = display_image.height cv_image = np.frombuffer(display_image.data, dtype=np.uint8).reshape((height, width, 4)) cv_image = cv.cvtColor(cv_image, cv.COLOR_BGRA2BGR) cv.imshow("screen", cv_image) print('FPS: {:.2f}'.format(1 / (time() - loop_time))) if cv.waitKey(1) == ord("q"): break cv.destroyAllWindows()
需要先安装pyobjc依赖:pip install pyobjc
5. 多线程分离任务
将截图捕获和图像处理/显示拆分为两个线程,避免捕获过程阻塞渲染线程。用队列传递截图数据,保证线程安全:
import cv2 as cv import numpy as np from mss.darwin import MSS as mss from time import time import threading from queue import Queue def capture_screen(sct, monitor, queue): while True: screenshot = sct.grab(monitor) queue.put(np.array(screenshot)) def process_and_show(queue): loop_time = time() while True: if not queue.empty(): cv_image = queue.get() cv_image = cv.cvtColor(cv_image, cv.COLOR_BGRA2BGR) cv.imshow("screen", cv_image) print('FPS: {:.2f}'.format(1 / (time() - loop_time))) loop_time = time() if cv.waitKey(1) == ord("q"): break cv.destroyAllWindows() if __name__ == "__main__": with mss() as sct: monitor = sct.monitors[0] queue = Queue(maxsize=2) # 限制队列大小避免内存溢出 capture_thread = threading.Thread(target=capture_screen, args=(sct, monitor, queue), daemon=True) capture_thread.start() process_and_show(queue)
关于“是否是普遍结果”
在2019款MacBook Pro上,用Python库实现全屏3-8FPS是正常情况——Python的GIL限制、macOS屏幕捕获的系统开销,都会影响帧率。但通过上述优化(尤其是消除磁盘IO+降低分辨率),达到15-20FPS完全可行。
内容的提问来源于stack exchange,提问作者Yixuan Wang

