Python怎么截取指定窗口区域并进行OCR识别
详细讲解使用 Python 截取特定应用窗口中的指定局部区域并进行高精度 OCR 文字识别的完整方案,涵盖 Windows 句柄定位、DPI 缩放适配、相对坐标几何换算、图像清晰度增强与 RapidOCR 提取解析。
在桌面自动化、报表数据同步以及辅助工具开发中,开发者经常需要从某个运行中的软件界面中获取特定信息——比如订单状态栏的单号、特定输入框中的动态数据,或者游戏与监控工具的数值面板。这类需求不需要截取整个屏幕,也不需要截取整个窗口,而是需要精确截取指定窗口内的某个局部区域,并将其转化为可供程序解析的纯文本。
要稳定完成这项任务,核心解决路径分为三个明确步骤:首先,通过窗口标题检索操作系统中的窗口实例,读取其在屏幕上的物理坐标,并配置 DPI 缩放感知以消除高分屏位移;其次,基于窗口原点加上预设的相对偏移量计算出目标截取区域,调用截图库完成局部像素提取;最后,对截取的微小局部图像进行对比度增强或放大预处理,并交由轻量级 OCR 引擎提取文字与置信度。下文将围绕这一链路逐层展开具体实现。
一、准备工作与技术选型
在 Python 生态中,完成这一流程需要协同使用窗口检索、图像捕获与文字识别三个维度的库。为了确保安装简便与环境隔离,推荐使用当前维护活跃、无需复杂外部系统配置的依赖组合:
| 库名称 | 主要职责 | 选用理由与优势 |
|---|---|---|
pygetwindow |
窗口检索与几何边界获取 | 跨平台封装,简单通过窗口标题关键词即可快速获取窗口坐标 (left, top, width, height)。 |
pywin32 |
Windows 底层 API 与 DPI 管理 | 直接与 Windows User32/GDI 交互,负责设置进程级 DPI 感知,支持窗口后台内存位图捕获。 |
Pillow |
屏幕截取与图像处理 | 内置 ImageGrab 模块支持区域截取,提供丰富的图像缩放、灰度化及二值化滤镜。 |
rapidocr-onnxruntime |
核心 OCR 文字识别推理 | 基于 PaddleOCR 深度学习模型并使用 ONNX Runtime 加速,免装 Tesseract 引擎及外部 DLL,识别率高且开箱即用。 |
打开终端或命令行,执行以下 pip 命令完成所需依赖的安装:
pip install pygetwindow pillow pywin32 rapidocr-onnxruntime
传统的 pytesseract 仅是 Python 包装器,用户必须在 Windows 系统中单独下载安装 tesseract-ocr.exe,配置 PATH 环境变量并手动下载中文字库 chi_sim.traineddata,极易因版本不匹配导致报错。而 rapidocr-onnxruntime 直接打包了轻量级 ONNX 模型权重,纯 pip 安装即可运行,对中英文混合排版、数字串和特殊符号的识别率显著优于传统 OCR 引擎。
二、准确定位目标窗口与规避 DPI 缩放陷阱
很多初学者在编写窗口截图脚本时,最常遇到的挫折是“截出来的图片位置整体偏移”、“截到的是黑边”或者“多截出了一大块桌面”。这通常并非代码逻辑错误,而是由 Windows 系统的 DPI 缩放机制 引起的。
在 2K、4K 显示器或高分辨率笔记本屏幕上,Windows 默认会开启 125%、150% 甚至 200% 的显示缩放。如果 Python 脚本没有向操作系统声明自己是“DPI 感知”进程,Windows 会默认以虚拟逻辑坐标向 Python 汇报窗口位置。但 Pillow.ImageGrab 是按照屏幕的物理真实像素来抓取的,两者的倍率差异会导致截图框与真实窗口完全脱节。
解决该问题的标准做法是在脚本的最开始导入 ctypes,显式声明进程的 DPI 感知等级:
import ctypes
def set_dpi_awareness():
# 通知 Windows 系统本进程采用物理像素,避免坐标缩放偏差
try:
# Windows 8.1 及更高版本推荐:Per-Monitor DPI Aware
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
try:
# 兼容早期版本 Windows
ctypes.windll.user32.SetProcessDPIAware()
except Exception:
pass
在声明 DPI 感知后,即可通过 pygetwindow 准确检索窗口。如果窗口处于最小化状态,Windows 会将其屏幕坐标移到 (-32000, -32000) 等不可见区域,此时需要先调用 restore() 将其恢复:
import pygetwindow as gw
def find_target_window(title_keyword):
# 根据标题关键字查找窗口并确保其处于正常可视状态
windows = gw.getWindowsWithTitle(title_keyword)
if not windows:
raise RuntimeError(f"未找到标题包含 '{title_keyword}' 的窗口,请确认程序已打开。")
win = windows[0]
# 若窗口处于最小化,将其恢复至前台可见
if win.isMinimized:
win.restore()
return win
三、目标局部区域的坐标计算与截取实现
在实际应用中,用户可能会随时拖动窗口或调整窗口大小。因此,绝不能将截图区域写死为屏幕绝对坐标。正确的做法是:
- 以窗口左上角
(win.left, win.top)作为局部原点(0, 0); - 测量目标区域相对于窗口原点的偏移量
(rel_x, rel_y)以及区域尺寸(rel_w, rel_h); - 通过加法换算出目标在屏幕上的绝对四元组:
(left, top, right, bottom)。
根据窗口是否可能被其他应用遮挡,截图的实现方案有两种:
方案 A:Pillow 屏幕抓取法(推荐首选)
当目标窗口在屏幕上可见且未被完全遮挡时,使用 PIL.ImageGrab.grab(bbox=...) 是最简单、最高效的方案:
from PIL import ImageGrab
def capture_window_region(win, rel_x, rel_y, rel_w, rel_h):
# 通过屏幕绝对包围盒截取指定窗口内的局部区域
# 计算目标区域在整个屏幕中的绝对坐标
x1 = win.left + rel_x
y1 = win.top + rel_y
x2 = x1 + rel_w
y2 = y1 + rel_h
# 截取该屏幕矩形
cropped_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
return cropped_img
方案 B:后台内存位图抓取法(遮挡场景备选)
如果希望在窗口被其他软件部分遮挡时依然能正确截取,可以借助 win32gui.PrintWindow 将窗口界面直接渲染到内存位图,随后在内存中进行裁切:
import win32gui
import win32ui
import win32con
from PIL import Image
def capture_window_background(hwnd, rel_x, rel_y, rel_w, rel_h):
# 利用 Windows 消息机制在后台渲染窗口位图并裁切局部
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width = right - left
height = bottom - top
hwnd_dc = win32gui.GetWindowDC(hwnd)
mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
save_dc = mfc_dc.CreateCompatibleDC()
save_bitmap = win32ui.CreateBitmap()
save_bitmap.CreateCompatibleBitmap(mfc_dc, width, height)
save_dc.SelectObject(save_bitmap)
# 2 = PW_RENDERFULLCONTENT (完整渲染包括子控件)
win32gui.PrintWindow(hwnd, save_dc.GetSafeHdc(), 2)
bmpinfo = save_bitmap.GetInfo()
bmpstr = save_bitmap.GetBitmapBits(True)
full_img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
# 释放 GDI 句柄
win32gui.DeleteObject(save_bitmap.GetHandle())
save_dc.DeleteDC()
mfc_dc.DeleteDC()
win32gui.ReleaseDC(hwnd, hwnd_dc)
# 在完整窗口截图中按相对坐标裁切
return full_img.crop((rel_x, rel_y, rel_x + rel_w, rel_y + rel_h))
四、图像预处理与 RapidOCR 文本识别
局部截取出来的图片通常非常小(例如高度仅有 20~30 像素的状态码或单据号)。直接将尺寸过小的低对比度图片送给深度学习模型,往往会导致检测网络(DBNet)将文字边缘当作背景噪声忽略,从而造成漏字或空结果。
1. 针对小字符的图像前处理
在送检前,建议对图像进行两步标准预处理:
- 高质量插值放大:使用
Image.Resampling.LANCZOS滤镜将图像放大 2 倍,让单字符笔画像素丰满; - 灰度化与二值化:滤除背景渐变色,增强前景色文字与背景的黑白反差。
from PIL import Image
def preprocess_image(img, scale_factor=2):
# 对局部小图进行双三次放大与对比度增强
w, h = img.size
# 尺寸放大,确保小字体笔画特征可被有效提取
resized = img.resize((w * scale_factor, h * scale_factor), Image.Resampling.LANCZOS)
# 转换为灰度图
gray = resized.convert('L')
# 简易二值化处理(可根据实际软件底色调整阈值)
threshold = 160
table = [0 if i < threshold else 255 for i in range(256)]
binary = gray.point(table, '1')
return binary.convert('RGB')
2. 调用 RapidOCR 并解析结构化数据
RapidOCR 的实例化只需一行代码。其推理方法 ocr(image) 接收 PIL Image 实例、NumPy 数组或图片路径,并返回两个对象:result 与 elapse。
result 的结构为包含多个文本行信息的列表,每一个元素包含三个字段:[dt_boxes, text, score]。在业务中,我们可以根据置信度分数(例如 score > 0.85)过滤不可信的识别结果:
import numpy as np
from rapidocr_onnxruntime import RapidOCR
# 全局初始化 OCR 引擎实例,避免重复加载模型权重
ocr_engine = RapidOCR()
def recognize_text_from_image(image):
# 调用 RapidOCR 识别图像并提取置信度合格的纯文本
# 将 PIL Image 转换为 numpy 数组
img_np = np.array(image)
# 执行识别
result, elapse = ocr_engine(img_np)
if not result:
return ""
extracted_lines = []
for item in result:
box, text, score = item
# 过滤置信度低于 0.8 的噪声识别
if score >= 0.80:
extracted_lines.append(text.strip())
return " ".join(extracted_lines)
五、端到端完整代码示例
下面是一个完整的、可直接在 Windows 系统上运行的独立示例。脚本将自动寻找目标窗口(示例中使用 Windows 自带的“记事本”或自定义程序),对指定局部区域进行高精度截取与文字提取,并打印识别出的文本与置信度。
# -*- coding: utf-8 -*-
"""
Python 截取指定窗口区域并进行 OCR 识别端到端示例
环境依赖: pip install pygetwindow pillow pywin32 rapidocr-onnxruntime
"""
import ctypes
import sys
import numpy as np
from PIL import ImageGrab, Image
import pygetwindow as gw
from rapidocr_onnxruntime import RapidOCR
# 1. 消除 Windows DPI 缩放影响
try:
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
try:
ctypes.windll.user32.SetProcessDPIAware()
except Exception:
pass
# 2. 初始化 OCR 引擎
ocr_engine = RapidOCR()
def capture_and_recognize(window_title, rel_x, rel_y, rel_w, rel_h):
print(f"[1/4] 检索目标窗口: {window_title}...")
windows = gw.getWindowsWithTitle(window_title)
if not windows:
print(f"错误: 未找到标题包含 '{window_title}' 的活动窗口。")
return None
target_win = windows[0]
if target_win.isMinimized:
print("提示: 窗口处于最小化,正在恢复窗口...")
target_win.restore()
# 将窗口激活到前台
try:
target_win.activate()
except Exception:
pass
# 计算全局物理像素坐标
abs_x1 = target_win.left + rel_x
abs_y1 = target_win.top + rel_y
abs_x2 = abs_x1 + rel_w
abs_y2 = abs_y1 + rel_h
print(f"[2/4] 计算截取包围盒: ({abs_x1}, {abs_y1}, {abs_x2}, {abs_y2}) [宽高: {rel_w}x{rel_h}]")
# 截取局部区域
cropped_image = ImageGrab.grab(bbox=(abs_x1, abs_y1, abs_x2, abs_y2))
# 3. 图像预处理: 针对小字号进行放大
scale = 2
processed_image = cropped_image.resize(
(rel_w * scale, rel_h * scale),
Image.Resampling.LANCZOS
)
# 4. 执行 OCR 推理
print("[3/4] 正在调用 RapidOCR 进行文本提取...")
img_array = np.array(processed_image)
ocr_result, elapse = ocr_engine(img_array)
print(f"[4/4] 识别完成,模型耗时: {elapse} 秒")
if not ocr_result:
print("未在指定区域检测到有效文字。")
return ""
parsed_items = []
for box, text, score in ocr_result:
print(f" - 检出文字: '{text}' (置信度: {score:.3f})")
if score >= 0.75:
parsed_items.append(text)
final_text = " ".join(parsed_items)
return final_text
if __name__ == "__main__":
# 示例配置:指定窗口标题关键字及目标区域在窗口内部的偏移
# 请在运行前将 '记事本' 替换为您实际需要捕获的软件标题
TARGET_TITLE = "记事本"
# 假设目标区域位于窗口左上角偏右 40 像素、偏下 60 像素处,尺寸为 300x80
REGION_OFFSET_X = 40
REGION_OFFSET_Y = 60
REGION_WIDTH = 300
REGION_HEIGHT = 80
result = capture_and_recognize(
TARGET_TITLE,
REGION_OFFSET_X,
REGION_OFFSET_Y,
REGION_WIDTH,
REGION_HEIGHT
)
print("\n================== 最终识别结果 ==================")
print(f"识别文本: {result}")
print("==================================================")
[1/4] 检索目标窗口: 记事本...
[2/4] 计算截取包围盒: (380, 240, 680, 320) [宽高: 300x80]
[3/4] 正在调用 RapidOCR 进行文本提取...
[4/4] 识别完成,模型耗时: 0.038 秒
- 检出文字: '单号: ORD-2026-X8849' (置信度: 0.992)
- 检出文字: '状态: 审核通过' (置信度: 0.978)
================== 最终识别结果 ==================
识别文本: 单号: ORD-2026-X8849 状态: 审核通过
==================================================
六、常见问题排查与技术决策路径
在实际工程部署与复杂多屏环境中,可能会遇到各种边界情况。下方的决策路径总结了四大类典型异常的排查与处理方式:
-
窗口处于最小化或后台非活动状态:
如果目标窗口被用户最小化到任务栏,
GetWindowRect会返回位于屏幕外的异常坐标(例如-32000),导致截图全黑或报错。在调用截图前,务必通过win.isMinimized检查状态,并使用win.restore()恢复正常显示。 -
多显示器负坐标环境:
在双屏或多屏拼接工作区中,如果副显示器被设置在主显示器的左侧或上方,该屏幕上的窗口坐标会出现负数(如
left = -1920)。Pillow.ImageGrab支持包含负数的全局坐标包围盒,但需要确保传递的bbox格式严格为(min_x, min_y, max_x, max_y),即满足x2 > x1和y2 > y1。 -
硬件加速与透明窗口截取黑屏:
对于某些开启了 GPU 硬件加速的 Electron 应用(如新版 Chrome、VS Code、Slack 等),使用
win32gui.PrintWindow后台捕获时可能返回纯黑图像。针对此类程序,应改用方案 A(先通过win32gui.SetForegroundWindow将窗口置顶激活,再使用ImageGrab截取屏幕)。 -
纯数字或序列号中的字符混淆(如 0 与 O、1 与 I):
通用 OCR 模型在无上下文的纯数字串中可能误判相似字符。对于特定业务格式的字段(如金额、纯数字订单号),可以在拿到 OCR 文本后结合正则表达式进行后处理校正,或在 RapidOCR 初始化时传入自定义字典与规则限制只输出数字字符集。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













