Python怎么截取指定窗口区域并进行OCR识别

许南星
许南星
发布时间:2026-11-09 | 7人浏览过
原创

详细讲解使用 Python 截取特定应用窗口中的指定局部区域并进行高精度 OCR 文字识别的完整方案,涵盖 Windows 句柄定位、DPI 缩放适配、相对坐标几何换算、图像清晰度增强与 RapidOCR 提取解析。

Python怎么截取指定窗口区域并进行OCR识别
图 0:Python 窗口局部截图与 OCR 识别全流程概览

在桌面自动化、报表数据同步以及辅助工具开发中,开发者经常需要从某个运行中的软件界面中获取特定信息——比如订单状态栏的单号、特定输入框中的动态数据,或者游戏与监控工具的数值面板。这类需求不需要截取整个屏幕,也不需要截取整个窗口,而是需要精确截取指定窗口内的某个局部区域,并将其转化为可供程序解析的纯文本。

要稳定完成这项任务,核心解决路径分为三个明确步骤:首先,通过窗口标题检索操作系统中的窗口实例,读取其在屏幕上的物理坐标,并配置 DPI 缩放感知以消除高分屏位移;其次,基于窗口原点加上预设的相对偏移量计算出目标截取区域,调用截图库完成局部像素提取;最后,对截取的微小局部图像进行对比度增强或放大预处理,并交由轻量级 OCR 引擎提取文字与置信度。下文将围绕这一链路逐层展开具体实现。

Python怎么截取指定窗口区域并进行OCR识别
图 1:窗口指定区域截取与 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
为什么推荐 RapidOCR 而非传统 Tesseract?

传统的 pytesseract 仅是 Python 包装器,用户必须在 Windows 系统中单独下载安装 tesseract-ocr.exe,配置 PATH 环境变量并手动下载中文字库 chi_sim.traineddata,极易因版本不匹配导致报错。而 rapidocr-onnxruntime 直接打包了轻量级 ONNX 模型权重,纯 pip 安装即可运行,对中英文混合排版、数字串和特殊符号的识别率显著优于传统 OCR 引擎。

二、准确定位目标窗口与规避 DPI 缩放陷阱

很多初学者在编写窗口截图脚本时,最常遇到的挫折是“截出来的图片位置整体偏移”、“截到的是黑边”或者“多截出了一大块桌面”。这通常并非代码逻辑错误,而是由 Windows 系统的 DPI 缩放机制 引起的。

Python怎么截取指定窗口区域并进行OCR识别
图 2:屏幕全局坐标系与窗口内部相对区域几何换算及 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

三、目标局部区域的坐标计算与截取实现

在实际应用中,用户可能会随时拖动窗口或调整窗口大小。因此,绝不能将截图区域写死为屏幕绝对坐标。正确的做法是:

  1. 以窗口左上角 (win.left, win.top) 作为局部原点 (0, 0);
  2. 测量目标区域相对于窗口原点的偏移量 (rel_x, rel_y) 以及区域尺寸 (rel_w, rel_h);
  3. 通过加法换算出目标在屏幕上的绝对四元组:(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)将文字边缘当作背景噪声忽略,从而造成漏字或空结果。

Python怎么截取指定窗口区域并进行OCR识别
图 3:图像局部裁剪、插值放大与 RapidOCR 检测与识别全流程解析

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 状态: 审核通过
==================================================

六、常见问题排查与技术决策路径

在实际工程部署与复杂多屏环境中,可能会遇到各种边界情况。下方的决策路径总结了四大类典型异常的排查与处理方式:

Python怎么截取指定窗口区域并进行OCR识别
图 4:窗口截取与 OCR 识别常见异常排查与解决方案决策树
  1. 窗口处于最小化或后台非活动状态:

    如果目标窗口被用户最小化到任务栏,GetWindowRect 会返回位于屏幕外的异常坐标(例如 -32000),导致截图全黑或报错。在调用截图前,务必通过 win.isMinimized 检查状态,并使用 win.restore() 恢复正常显示。

  2. 多显示器负坐标环境:

    在双屏或多屏拼接工作区中,如果副显示器被设置在主显示器的左侧或上方,该屏幕上的窗口坐标会出现负数(如 left = -1920)。Pillow.ImageGrab 支持包含负数的全局坐标包围盒,但需要确保传递的 bbox 格式严格为 (min_x, min_y, max_x, max_y),即满足 x2 > x1 和 y2 > y1。

  3. 硬件加速与透明窗口截取黑屏:

    对于某些开启了 GPU 硬件加速的 Electron 应用(如新版 Chrome、VS Code、Slack 等),使用 win32gui.PrintWindow 后台捕获时可能返回纯黑图像。针对此类程序,应改用方案 A(先通过 win32gui.SetForegroundWindow 将窗口置顶激活,再使用 ImageGrab 截取屏幕)。

  4. 纯数字或序列号中的字符混淆(如 0 与 O、1 与 I):

    通用 OCR 模型在无上下文的纯数字串中可能误判相似字符。对于特定业务格式的字段(如金额、纯数字订单号),可以在拿到 OCR 文本后结合正则表达式进行后处理校正,或在 RapidOCR 初始化时传入自定义字典与规则限制只输出数字字符集。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表