☰
Python桌面截图文字识别:mss+OpenCV+Tesseract自适应分辨率实战
2026/10/17 5:53:36 网站建设 项目流程

1. 项目缘起与整体设计思路

桌面截图加文字识别这个需求,最早来自我帮一个做数据录入的朋友处理报表的场景。他每天要从各种系统界面里把数字抄到表格里,眼睛都快看瞎了。我当时就想,能不能写个脚本,自动截屏、自动识别、自动填表。后来这个需求越滚越大,从固定区域截图变成全屏截图,从固定分辨率变成自适应分辨率,从单纯识别变成带坐标定位的结构化输出。这套东西我前后迭代了四五个版本,踩了不少坑,今天把完整思路和实现细节摊开来讲。

先说清楚这个项目到底解决什么问题。核心就三件事:第一,把当前屏幕的画面抓下来;第二,从画面里把文字提取出来;第三,把文字和它在屏幕上的位置对应起来。听起来简单,但实际做的时候,分辨率适配、多显示器处理、识别精度、坐标映射这几个环节都有坑。适合谁来参考?我觉得两类人最合适:一类是刚学Python想做点实用工具的新手,另一类是已经会写脚本但想把这套东西集成到自动化流程里的开发者。不管你是哪种,下面的内容都能直接抄作业。

整体方案选型上,我最终定的是mss + OpenCV + Tesseract这条链路。为什么不用PIL的ImageGrab?因为PIL在Windows上抓全屏还行,但多显示器和高DPI场景下经常出问题,而且速度慢。mss这个库专门为截图设计,跨平台,速度快,支持多显示器,返回的是原始像素数据,直接就能喂给OpenCV。文字识别这块,Tesseract虽然老,但胜在免费、离线、可训练,配合预处理能把准确率拉到可用水平。如果你追求更高精度,可以换PaddleOCR,但部署成本会高一些,后面我会对比。

自适应分辨率这个点单独拎出来说。很多人写截图脚本,直接写死1920x1080,换台电脑就废了。自适应要解决两个层面:一是获取当前屏幕的真实分辨率,二是根据分辨率动态调整截图区域和识别参数。比如在4K屏上,文字像素多,Tesseract的页面分割模式就要调;在1366x768的老笔记本上,文字小,预处理时的放大倍数就要加大。这些细节后面会展开。

提示:整个项目的依赖不多,但Tesseract需要单独安装引擎,不是pip装个包就完事。Windows下要下安装包,Mac用brew,Linux用apt,具体命令后面给。

2. 核心细节解析与实操要点

2.1 屏幕分辨率获取与自适应逻辑

获取屏幕分辨率这件事,不同系统有不同的坑。Windows下可以用ctypes调user32.GetSystemMetrics,Mac下要用Quartz,Linux下得看X11还是Wayland。我试过几种方案,最稳的还是用mss自带的monitors属性。mss初始化之后,monitors[0]是所有显示器的联合区域,monitors[1]开始是各个独立显示器。每个monitor是个字典,包含left、top、width、height四个键。这样你不需要关心底层系统,直接拿到的就是逻辑分辨率。

但这里有个大坑:高DPI缩放。Windows默认可能把150%或200%缩放,这时候mss拿到的分辨率是缩放后的逻辑分辨率,但实际像素是物理分辨率。比如一个4K屏,系统缩放200%,mss告诉你宽度是1920,但实际像素是3840。如果你直接按1920去截图,截出来的图是模糊的,因为系统帮你缩放了。解决办法是在程序启动时调用SetProcessDpiAwareness,把进程标记为DPI感知,这样mss拿到的就是物理分辨率。代码就一行:

import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(2)

这个调用必须在任何截图操作之前执行,而且只对Windows有效。Mac和Linux一般不需要,但Mac的Retina屏也有类似问题,mss在Mac上默认返回的是逻辑分辨率,实际像素要乘以2。我一般会在代码里做个判断,如果是Darwin系统,就把宽高乘以2。

自适应逻辑的核心是:先拿到物理分辨率,然后根据分辨率决定截图策略。我写了个简单的分级:

分辨率范围截图策略预处理放大倍数Tesseract PSM模式
宽度<1600全屏截图2.06
1600≤宽度<2560全屏截图1.56
2560≤宽度<3840全屏截图1.23
宽度≥3840分块截图1.03

分块截图是针对4K以上屏幕的,因为一次性识别整张4K图,Tesseract会非常慢,而且小字容易漏。分块逻辑是按2x2网格切,每块单独识别,最后合并结果。这个策略不是拍脑袋定的,是我在几台不同机器上实测出来的。1600以下的小屏,文字像素少,放大2倍能让Tesseract更准;4K屏文字本身够大,再放大反而增加计算量,不如分块。

2.2 截图库选型与性能对比

截图库我前后用过四个:PIL.ImageGrab、pyautogui、mss、dxcam。挨个说。

PIL.ImageGrab最简单,一行代码ImageGrab.grab()就完事。但问题多:Windows下多显示器只能抓主屏,高DPI下抓出来是缩放后的图,速度大概每张100ms左右。pyautogui底层也是调PIL,多了个跨平台封装,速度更慢,大概150ms。这两个适合做demo,不适合做自动化。

mss是我最推荐的。安装pip install mss,用法:

import mss with mss.mss() as sct: monitor = sct.monitors[1] img = sct.grab(monitor) # img是BGRA格式的原始数据

速度实测每张30-50ms,比PIL快一倍多。支持多显示器,支持指定区域,返回的数据可以直接转成numpy数组喂给OpenCV。唯一要注意的是它返回的是BGRA,OpenCV默认是BGR,所以转换的时候要丢掉alpha通道。

dxcam是专门为Windows游戏截图设计的,速度极快,能到每秒240帧。但它只支持Windows,而且依赖DirectX,在虚拟机或远程桌面里可能用不了。如果你只是做文字识别,不需要那么高的帧率,mss足够了。

注意:mss在Mac上需要授予屏幕录制权限,否则grab会返回黑屏。这个权限在系统设置-隐私与安全性-屏幕录制里开。很多人第一次跑代码发现全是黑图,就是没给权限。

2.3 图像预处理的关键步骤

截图拿到手,不能直接扔给Tesseract,得先预处理。预处理的目标是让文字更清晰、背景更干净。我一般走这几步:

第一步,灰度化。cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY),把三通道变单通道,减少计算量。

第二步,放大。根据前面表格里的倍数,用cv2.resize做插值放大。插值方法选cv2.INTER_CUBIC,比默认的线性插值效果好,文字边缘更锐利。

第三步,二值化。用cv2.threshold做全局阈值,或者cv2.adaptiveThreshold做自适应阈值。全局阈值适合背景均匀的截图,比如白底黑字的网页;自适应阈值适合背景复杂的,比如深色主题的IDE。我一般先用Otsu算法自动找阈值:

_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

第四步,去噪。用cv2.medianBlur做中值滤波,核大小3或5。这一步能去掉截图里的压缩噪点,对识别率提升明显。

第五步,边缘增强。可选,用cv2.Canny或者形态学操作。我一般不做,因为Tesseract对边缘敏感,过度增强反而引入噪声。

这五步下来,一张1080p的截图预处理耗时大概20-30ms,加上截图和识别,整个流程控制在200ms以内,做实时识别完全够用。

2.4 Tesseract配置与语言包选择

Tesseract的安装是个门槛。Windows下要去GitHub下安装包,安装时勾选中文语言包。Mac下brew install tesseract,然后brew install tesseract-lang装所有语言。Linux下apt install tesseract-ocr tesseract-ocr-chi-sim。

Python调用用pytesseract:

import pytesseract text = pytesseract.image_to_string(binary, lang='chi_sim+eng', config='--psm 6')

lang参数指定语言,chi_sim是简体中文,eng是英文,用加号连接可以同时识别。config里的psm是页面分割模式,6表示假设是一块统一的文本块,3表示全自动分割。我实测下来,截图识别用6比3准,因为截图通常是一整块内容,不需要Tesseract去猜布局。

还有一个参数是oem,OCR引擎模式。默认是3,表示用LSTM加传统引擎。如果只想要LSTM,设成1,速度快一点。我一般用默认。

提示:Tesseract对中文的识别率,在预处理到位的情况下,印刷体大概能到90%以上,手写体就别指望了。如果对中文精度要求极高,建议换PaddleOCR,它的中文模型比Tesseract强不少,但安装包大,依赖多。

3. 实操过程与核心环节实现

3.1 环境搭建与依赖安装

先把环境搭起来。我假设你用的是Python 3.8以上,太老的版本有些库不支持。

pip install mss opencv-python pytesseract numpy pillow

Tesseract引擎单独装:

Windows:去UB-Mannheim的页面下exe安装包,安装时勾选Additional language data里的Chinese Simplified。装完把安装路径加到系统PATH,比如C:\Program Files\Tesseract-OCR。

Mac:

brew install tesseract brew install tesseract-lang

Linux:

sudo apt update sudo apt install tesseract-ocr tesseract-ocr-chi-sim

装完验证一下:

tesseract --version tesseract --list-langs

第二个命令应该能看到chi_sim和eng。如果看不到chi_sim,说明中文包没装好,回去检查。

3.2 完整代码实现与逐段解析

下面是我最终版的代码,我把它拆成几个函数,方便你按需修改。

import ctypes import sys import cv2 import numpy as np import mss import pytesseract from pytesseract import Output # Windows DPI感知,必须在最前面 if sys.platform == 'win32': try: ctypes.windll.shcore.SetProcessDpiAwareness(2) except Exception: ctypes.windll.user32.SetProcessDPIAware() def get_screen_size(): """获取物理分辨率""" with mss.mss() as sct: monitor = sct.monitors[1] width = monitor['width'] height = monitor['height'] # Mac Retina屏修正 if sys.platform == 'darwin': width *= 2 height *= 2 return width, height def capture_screen(region=None): """截取屏幕,返回BGR格式的numpy数组""" with mss.mss() as sct: if region is None: monitor = sct.monitors[1] else: monitor = region img = np.array(sct.grab(monitor)) # BGRA转BGR return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) def preprocess(img, scale=1.5): """预处理:灰度、放大、二值化、去噪""" gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if scale != 1.0: gray = cv2.resize(gray, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] denoised = cv2.medianBlur(binary, 3) return denoised def recognize_text(img, lang='chi_sim+eng', psm=6): """识别文字,返回带坐标的结果""" config = f'--psm {psm}' data = pytesseract.image_to_data(img, lang=lang, config=config, output_type=Output.DICT) results = [] n = len(data['text']) for i in range(n): text = data['text'][i].strip() if text: results.append({ 'text': text, 'left': data['left'][i], 'top': data['top'][i], 'width': data['width'][i], 'height': data['height'][i], 'conf': data['conf'][i] }) return results def adaptive_capture_and_recognize(): """自适应分辨率的主流程""" width, height = get_screen_size() # 根据分辨率决定策略 if width < 1600: scale, psm = 2.0, 6 elif width < 2560: scale, psm = 1.5, 6 elif width < 3840: scale, psm = 1.2, 3 else: scale, psm = 1.0, 3 img = capture_screen() processed = preprocess(img, scale) results = recognize_text(processed, psm=psm) # 坐标还原:因为预处理放大了,坐标要除回去 for r in results: r['left'] = int(r['left'] / scale) r['top'] = int(r['top'] / scale) r['width'] = int(r['width'] / scale) r['height'] = int(r['height'] / scale) return results if __name__ == '__main__': results = adaptive_capture_and_recognize() for r in results: print(f"文字: {r['text']} 位置: ({r['left']}, {r['top']}) 置信度: {r['conf']}")

逐段说下关键点。get_screen_size里Mac的乘以2,是因为Retina屏的物理像素是逻辑像素的两倍,mss返回的是逻辑值。capture_screen里BGRA转BGR,是因为mss返回四通道,OpenCV处理三通道更顺手。preprocess里的Otsu阈值,对双峰直方图的图效果最好,截图通常都是双峰,所以很合适。recognize_text用image_to_data而不是image_to_string,因为前者返回每个词的坐标和置信度,后者只返回纯文本。坐标还原那步很容易忘,忘了的话所有坐标都是放大后的,定位就偏了。

3.3 多显示器与区域截取的实现

多显示器场景下,sct.monitors是个列表,索引0是所有显示器的联合区域,索引1到N是各个显示器。如果你想截第二个显示器:

with mss.mss() as sct: monitor = sct.monitors[2] # 第二个显示器 img = np.array(sct.grab(monitor))

区域截取就是自己构造一个字典:

region = {'left': 100, 'top': 200, 'width': 800, 'height': 600} img = capture_screen(region)

这个在自动化里很有用,比如你只想识别某个窗口的内容,就可以先拿到窗口的位置,然后按那个区域截。

注意:多显示器下,left和top可能是负数。比如主屏在右边,副屏在左边,副屏的left就是负的。mss能正确处理,但你自己算坐标的时候要注意。

3.4 识别结果的结构化输出与坐标映射

识别结果我一般存成JSON,方便后续处理:

import json with open('result.json', 'w', encoding='utf-8') as f: json.dump(results, f, ensure_ascii=False, indent=2)

坐标映射这块,如果你要把识别到的文字和屏幕上的实际位置对应起来,直接用left和top就行。但要注意,Tesseract返回的坐标是相对于你传给它的图片的。如果你传的是预处理后的图,坐标就是放大后的,要除回去。如果你传的是原图,坐标就是原图的。我建议传预处理后的图,因为识别率高,然后除回去。

还有个细节:Tesseract的坐标是文本框的左上角,不是文字基线的位置。如果你要做点击操作,可能需要加上height的一半,点到文字中间。

4. 常见问题与排查技巧实录

4.1 识别率低的排查思路

识别率低是最常见的问题。我一般按这个顺序排查:

先看截图本身清不清楚。把预处理后的图存下来,cv2.imwrite('debug.png', processed),用眼睛看。如果人眼都看不清,Tesseract肯定也认不出。这时候要调预处理参数,比如放大倍数加大,或者换自适应阈值。

如果图看着清楚但识别还是错,看是不是语言包没装对。tesseract --list-langs确认chi_sim在列表里。如果不在,重装语言包。

再看psm模式。不同的psm对结果影响很大。我整理了个速查表:

场景推荐PSM说明
单行文字7当成一行处理
单块文字6当成统一文本块
多列布局4假设可变大小的文本列
稀疏文字11找不到特定模式时用
全自动3默认,但截图场景不如6

如果还是不行,试试给Tesseract加白名单,限制它只识别某些字符:

config = '--psm 6 -c tessedit_char_whitelist=0123456789.'

这个在识别纯数字报表时特别有用,能排除字母干扰。

4.2 截图黑屏与权限问题

Mac下截图黑屏,99%是权限问题。去系统设置-隐私与安全性-屏幕录制,把你的终端或IDE加进去。加完要重启终端才生效。Windows下一般不会黑屏,但如果用了远程桌面,mss可能抓不到,这时候换dxcam或者用Windows自带的截图API。

Linux下如果是Wayland会话,mss可能不工作,因为Wayland不允许程序直接访问屏幕。解决办法是切到X11会话,或者用gnome-screenshot这类工具绕一下。

4.3 高DPI缩放导致的坐标偏移

这个坑我踩过好几次。现象是:识别出来的文字位置和实际位置对不上,偏了大概1.5倍或2倍。原因就是DPI缩放。系统缩放150%,你按逻辑分辨率算坐标,但实际像素是1.5倍。解决办法就是前面说的SetProcessDpiAwareness。如果这个调用没生效,检查是不是在import其他库之前调的。有些库会重置DPI感知,所以要在最前面调。

还有个隐蔽的情况:多显示器不同缩放。比如主屏100%,副屏150%。这种情况下,SetProcessDpiAwareness只能设一个全局值,没法按显示器区分。我的做法是,如果检测到多显示器且缩放不同,就统一按100%处理,然后手动乘缩放系数。这个逻辑比较复杂,一般场景遇不到,遇到了再具体分析。

4.4 性能优化与批量处理技巧

如果你要连续识别很多帧,比如做实时监控,性能就很关键。我总结了几条:

第一,复用mss实例。不要每次截图都with mss.mss(),在外面建一个实例,循环里反复用。这样能省掉初始化的开销。

第二,缩小识别区域。全屏识别慢,如果只关心某个区域,就只截那个区域。区域越小,预处理和识别越快。

第三,降低放大倍数。放大倍数和耗时是平方关系,2倍放大是4倍像素。如果识别率够用,就别放大太多。

第四,用多线程。截图和识别可以并行,一个线程截图,一个线程识别。但要注意Tesseract不是线程安全的,多个线程同时调会崩。解决办法是每个线程用独立的Tesseract实例,或者加锁串行识别。

第五,缓存不变的区域。如果屏幕大部分内容不变,只识别变化的部分。可以用帧差法检测变化区域,只对变化区域做识别。

我实测下来,优化前全屏1080p识别一次大概500ms,优化后能到150ms左右。4K屏优化前要2秒,分块加多线程后能到600ms。

4.5 常见问题速查表

问题现象可能原因解决办法
截图全黑权限未授予Mac开屏幕录制权限,Linux切X11
识别全是乱码语言包未装装chi_sim语言包
坐标偏移DPI缩放调SetProcessDpiAwareness
识别率低预处理不足加大放大倍数,调psm
速度慢全屏识别缩小区域,分块,多线程
中文识别差Tesseract局限换PaddleOCR
多显示器抓错屏monitors索引错确认monitors[1]是主屏
内存泄漏mss实例未释放用with语句或手动close

提示:Tesseract有个隐藏坑,如果图片太大(超过32767像素),会直接报错。4K屏全屏截图宽度3840,没超,但如果你放大3倍就超了。解决办法是分块,或者先缩小再放大。

5. 进阶扩展与个人经验体会

这套东西跑通之后,能扩展的方向很多。比如加个定时器,每隔几秒截一次,做屏幕内容监控;比如加个关键词匹配,识别到特定文字就触发动作;比如把坐标结果喂给pyautogui,实现自动点击。我有个朋友用这套逻辑做了个自动答题工具,当然那是另一个故事了。

如果你对中文识别精度要求高,我建议试试PaddleOCR。安装pip install paddlepaddle paddleocr,用法也简单:

from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang='ch') result = ocr.ocr(img, cls=True)

PaddleOCR的中文模型比Tesseract强不少,尤其是对倾斜文字和复杂背景。但它的包大,首次运行要下载模型,而且GPU版配置麻烦。我的建议是:如果Tesseract够用就别换,如果中文识别率低于80%再考虑。

最后分享几个我踩过的坑。第一个是Tesseract的版本问题,不同版本的识别结果可能不一样,建议锁定一个版本,别随便升级。第二个是OpenCV的imread和mss的grab返回的通道顺序不一样,imread是BGR,grab是BGRA,混用的时候容易搞错。第三个是Windows下如果开了HDR,截图颜色会发灰,识别率下降,建议关掉HDR再跑。

这套代码我放在几个不同的项目里用过,从简单的报表识别到复杂的界面自动化,稳定性都还不错。核心就是那几步:拿分辨率、截图、预处理、识别、还原坐标。每一步的参数都可以根据实际场景微调,没有一劳永逸的配置。多试几次,找到适合你屏幕和内容的参数组合,就能跑得很顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询