← 返回自动化办公目录
← 返回学习笔记首页
专题: Python 自动化办公系统学习
关键词: Python, 自动化办公, 图像识别, OpenCV, 屏幕监控, OCR, 模板匹配, 自动化, Python
一、图像识别概述
图像识别是计算机视觉领域的核心任务之一,在屏幕自动化中扮演着"眼睛"的角色。传统基于坐标的自动化脚本在面对窗口位置变化、分辨率差异、UI布局调整时很容易失效,而基于图像识别的自动化方案则能通过视觉信息精确锁定目标元素,显著提高自动化脚本的鲁棒性和通用性。在桌面自动化场景中,图像识别技术主要解决三大问题:第一是"目标在哪里"(定位问题),第二是"屏幕上有什么"(识别问题),第三是"屏幕状态是否发生变化"(监控问题)。
从技术路线来看,图像识别在自动化中的应用可以分为三个层次。最基础的层次是模板匹配(Template Matching),它通过计算模板图像与屏幕截图的相似度来确定目标位置,适用于图标、按钮等固定形状元素的定位。中间层次是特征点匹配(Feature Matching),它提取图像中的关键点(如角点、斑点)并计算特征描述子,能够应对目标被旋转、缩放或部分遮挡的情况。最高层次是基于深度学习的图像识别,利用卷积神经网络(CNN)进行目标检测(如YOLO、SSD)和图像分类(如ResNet、EfficientNet),适合复杂场景下的通用目标识别。
在屏幕自动化中,最常用的Python库主要有三个。一是OpenCV(Open Source Computer Vision Library),它是计算机视觉领域的瑞士军刀,提供了超过2500种优化的算法,覆盖图像处理、特征提取、目标检测等各个方面。二是Pillow(PIL),它是Python最基础的图像处理库,擅长图像的读取、保存、裁剪、缩放和基本像素操作。三是pytesseract,它是Google Tesseract OCR引擎的Python封装,用于从图像中提取文字信息。在实际项目中,这三个库往往配合使用:Pillow负责截图和预处理,OpenCV负责图像分析和匹配,pytesseract负责文字识别。
import cv2
import numpy as np
from PIL import ImageGrab
import pytesseract
# 基本图像读取与显示
img = cv2.imread('screenshot.png')
print(f"图像尺寸: {img.shape}") # (高度, 宽度, 通道数)
# 转换为灰度图(大多数图像处理的第一步)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 截取屏幕区域
bbox = (100, 100, 800, 600) # (left, top, right, bottom)
screen = ImageGrab.grab(bbox)
screen_np = np.array(screen)
screen_bgr = cv2.cvtColor(screen_np, cv2.COLOR_RGB2BGR)
cv2.imshow('Screen Region', screen_bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 图像缩放与预处理管道
def preprocess_image(image_path):
img = cv2.imread(image_path)
# 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯模糊去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 自适应阈值二值化
binary = cv2.adaptiveThreshold(
blurred, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
return binary
processed = preprocess_image('screen_capture.png')
cv2.imwrite('processed.png', processed)
核心要点: 图像识别的本质是将视觉信息转化为可计算的数值特征。在屏幕自动化中,选择合适的技术路线取决于具体场景——固定UI用模板匹配,可变目标用特征匹配,复杂场景考虑深度学习。掌握OpenCV的基本图像读写和预处理操作是后续所有进阶技术的基础。
二、OpenCV模板匹配
模板匹配(Template Matching)是图像识别中最直观、最基础的方法,也是屏幕自动化中最常用的定位技术。其核心思想非常简单:给定一个模板图像(例如一个按钮图标),在目标图像(屏幕截图)上滑动这个模板,逐个像素地计算模板与对应区域的相似度,最终找到与模板最匹配的位置。OpenCV通过 cv2.matchTemplate() 函数实现了这一功能,该函数接收目标图像、模板图像和匹配方法三个参数,返回一个灰度图形式的匹配结果矩阵,矩阵中每个像素的值代表该位置与模板的匹配程度。
OpenCV提供了六种匹配方法,分为两类。第一类是衡量差异的(值越小越匹配):TM_SQDIFF(平方差匹配)和 TM_SQDIFF_NORMED(归一化平方差匹配)。第二类是衡量相似度的(值越大越匹配):TM_CCORR(相关匹配)、TM_CCORR_NORMED(归一化相关匹配)、TM_CCOEFF(相关系数匹配)和 TM_CCOEFF_NORMED(归一化相关系数匹配)。在实际屏幕自动化中,TM_CCOEFF_NORMED 是最常用的方法,因为它对光照变化和对比度变化具有更好的鲁棒性,返回值范围为0到1,便于设置统一的匹配阈值。
多目标匹配是模板匹配的一个重要扩展。在实际屏幕中,同一个图标可能出现在多个位置(如桌面上的多个快捷方式、列表中的多个相同按钮)。此时,仅靠 cv2.minMaxLoc() 只能找到最佳匹配位置。要找到所有匹配位置,需要在结果矩阵上应用阈值筛选:将大于(或小于,取决于匹配方法)某个阈值的所有位置都视为匹配,然后使用 cv2.groupRectangles() 或自定义非极大值抑制(NMS)来合并重叠区域,最终得到所有目标的位置列表。
import cv2
import numpy as np
def template_match(screen_path, template_path, threshold=0.8):
"""在屏幕截图中查找模板图像的位置"""
screen = cv2.imread(screen_path)
template = cv2.imread(template_path)
h, w = template.shape[:2]
# 执行模板匹配
result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)
# 找到最佳匹配位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val >= threshold:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 在屏幕截图上面矩形标注
cv2.rectangle(screen, top_left, bottom_right, (0, 255, 0), 2)
cv2.putText(screen, f'Match: {max_val:.2f}',
(top_left[0], top_left[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
return top_left, max_val
return None, 0.0
# 使用示例
pos, confidence = template_match('desktop.png', 'chrome_icon.png')
if pos:
print(f"Chrome图标位于: {pos}, 置信度: {confidence:.2f}")
else:
print("未找到Chrome图标")
def multi_template_match(screen_path, template_path, threshold=0.8):
"""多目标模板匹配——查找所有匹配位置"""
screen = cv2.imread(screen_path)
template = cv2.imread(template_path)
h, w = template.shape[:2]
result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)
# 找到所有超过阈值的匹配位置
locations = np.where(result >= threshold)
points = []
for pt in zip(*locations[::-1]): # 转为(x, y)坐标
points.append(pt)
cv2.rectangle(screen, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 1)
# 用非极大值抑制合并重叠区域
if points:
rects = [(x, y, x + w, y + h) for x, y in points]
rects, _ = cv2.groupRectangles(np.array(rects), 1, 0.5)
for (x1, y1, x2, y2) in rects:
cv2.rectangle(screen, (x1, y1), (x2, y2), (0, 255, 0), 2)
print(f"找到 {len(rects)} 个匹配目标")
return rects
# 查找桌面所有文件夹图标
icons = multi_template_match('desktop.png', 'folder_icon.png', threshold=0.7)
# 实时屏幕模板匹配(持续监控)
import time
from PIL import ImageGrab
def monitor_for_template(template_path, region=None, threshold=0.8, interval=0.5):
"""持续监控屏幕,直到出现匹配目标"""
template = cv2.imread(template_path)
print(f"开始监控屏幕,等待目标出现... (间隔: {interval}s)")
while True:
# 截取屏幕
screen = ImageGrab.grab(region)
screen_np = np.array(screen)
screen_bgr = cv2.cvtColor(screen_np, cv2.COLOR_RGB2BGR)
# 执行匹配
result = cv2.matchTemplate(screen_bgr, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
if max_val >= threshold:
print(f"检测到目标!位置: {max_loc}, 置信度: {max_val:.2f}")
return max_loc
time.sleep(interval)
# 等待"确认"按钮出现
btn_pos = monitor_for_template('confirm_button.png', region=(0, 0, 1920, 1080))
核心要点: 模板匹配是屏幕自动化定位的基石,TM_CCOEFF_NORMED 是最推荐的匹配方法,阈值一般设置在0.7-0.9之间。对于多目标场景需要结合阈值筛选和NMS。实时监控模式下,控制采样频率(0.2-1秒)以平衡响应速度和CPU负载。
三、特征点匹配
当模板匹配无法满足需求时——例如目标图像发生了旋转、缩放、透视变形,或者只有部分可见——就需要引入特征点匹配技术。特征点匹配的核心思路是:不是比较整张图像的像素,而是先检测图像中具有独特性的"关键点"(如角点、边缘交点、纹理突变点),然后为这些关键点计算描述子(一个能够唯一标识该点局部特征的向量),最后通过比较描述子的相似度来建立两幅图像之间的对应关系。这种方法对图像的几何变换具有天然的鲁棒性。
OpenCV支持多种特征检测算法。SIFT(Scale-Invariant Feature Transform)是2004年提出的经典算法,它对尺度变化和旋转具有不变性,描述子维度为128维,精度很高但计算速度较慢。SURF(Speeded Up Robust Features)是SIFT的加速版本,利用积分图像和Haar小波来加速特征提取。ORB(Oriented FAST and Rotated BRIEF)是2011年提出的高效算法,结合了FAST角点检测和BRIEF描述子,并加入了旋转不变性,计算速度比SIFT快两个数量级,非常适合实时的屏幕自动化场景。由于SIFT和SURF在OpenCV 4.x之后需要安装 opencv-contrib-python 且部分算法有专利限制,ORB成为屏幕自动化中最实用的选择。
特征匹配阶段常用的策略有两种。一是暴力匹配(Brute-Force Matcher,BFMatcher),它计算每个查询描述子与所有目标描述子的距离,找到最近邻的匹配对,简单直观但计算量大。二是快速最近邻搜索(FLANN),它基于KD-Tree或LSH(局部敏感哈希)来加速高维空间的近邻搜索,适合大规模特征匹配场景。无论使用哪种匹配器,都需要用比率测试(Lowe's ratio test,通常取0.75)来过滤误匹配——对于每个查询特征点,取距离最近和次近的两个匹配,只有当最近距离显著小于次近距离时才认为匹配是可靠的。匹配完成后,可以使用 cv2.findHomography() 计算透视变换矩阵,从而精确计算出目标在图像中的位置边界。
import cv2
import numpy as np
def orb_feature_match(target_path, scene_path, min_match_count=10):
"""使用ORB特征点进行图像匹配与定位"""
target = cv2.imread(target_path, cv2.IMREAD_GRAYSCALE)
scene = cv2.imread(scene_path, cv2.IMREAD_GRAYSCALE)
# 初始化ORB检测器
orb = cv2.ORB_create(nfeatures=2000)
# 检测关键点并计算描述子
kp1, des1 = orb.detectAndCompute(target, None)
kp2, des2 = orb.detectAndCompute(scene, None)
# 暴力匹配器 + 汉明距离(ORB默认使用二进制描述子)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 按距离排序,取最佳匹配
matches = sorted(matches, key=lambda x: x.distance)
# 如果足够匹配,计算目标位置
if len(matches) >= min_match_count:
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
# 用RANSAC计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
h, w = target.shape
if M is not None:
pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]
).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, M)
return dst, len(matches)
return None, 0
# 使用示例:在场景中查找目标图标(支持旋转和缩放)
corners, n_matches = orb_feature_match('target_logo.png', 'screen_full.png')
if corners is not None:
print(f"目标位置已确定,匹配点数量: {n_matches}")
def flann_feature_match(img1_path, img2_path):
"""使用SIFT + FLANN进行特征匹配(更高精度)"""
# 需要 opencv-contrib-python
sift = cv2.SIFT_create()
img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN参数——用于高维向量
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# Lowe's 比率测试
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
print(f"原始匹配: {len(matches)}, 优质匹配: {len(good_matches)}")
return kp1, kp2, good_matches
# 查看匹配可视化结果
kp1, kp2, good = flann_feature_match('icon.png', 'screen_region.png')
result_img = cv2.drawMatches(
cv2.imread('icon.png'), kp1,
cv2.imread('screen_region.png'), kp2,
good[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
核心要点: 特征点匹配比模板匹配更灵活,能应对旋转、缩放和部分遮挡。ORB适合实时场景(速度快、无专利限制),SIFT适合高精度场景。比率测试(0.75)和RANSAC(5.0阈值)是消除误匹配的标准组合。对于屏幕自动化,ORB通常能在10-50ms内完成匹配,足以满足实时监控需求。
四、屏幕区域监控
屏幕区域监控是桌面自动化的核心能力之一,它让程序能够"持续注视"屏幕的特定区域,并在区域内容发生变化时做出响应。典型的应用场景包括:监控某个软件的状态变化(如"处理完成"字样出现)、监测系统通知弹窗、跟踪数据报表的实时更新、以及检测网页元素的动态加载等。实现屏幕区域监控需要解决三个基本问题:如何高效截图、如何检测变化、以及如何平衡监控频率与系统资源消耗。
在截图效率方面,Python有多个选择。Pillow的 ImageGrab.grab() 是最简易的方式,适合低频率截图(每秒1-5帧)。对于更高的帧率需求,mss(Multiple Screen Shots)库是更好的选择——它基于底层API(Windows上使用DXGI,macOS上使用CGDisplay),能够轻松达到30-60帧每秒的截图速度。如果需要更精确的窗口截图而非全屏截图,可以使用 pygetwindow 或 win32gui 获取窗口的屏幕坐标和尺寸,然后截取对应区域。在实际项目中,建议优先使用mss库进行屏幕捕获,因为其速度快、CPU占用低、且支持多显示器。
变化检测策略决定了监控的灵敏度和准确性。最简单的策略是逐像素比较(通过计算两帧图像的绝对差,然后统计变化像素的比例)。但这种方法对光照变化、窗口抖动等噪声非常敏感。更鲁棒的策略包括:计算图像哈希值(如感知哈希pHash),比较两个哈希值的汉明距离;或者计算结构相似性指数(SSIM),它能区分结构变化和光照变化。在屏幕自动化实践中,推荐采用"两级检测"策略——先用快速哈希比较做初筛,只有哈希差异超过阈值时才进行更精确的像素级分析,这样既保证了检测速度又保证了准确度。
import cv2
import numpy as np
from PIL import ImageGrab
import time
def monitor_screen_region(region, callback=None, interval=1.0):
"""监控屏幕指定区域的变化"""
prev_frame = None
frame_count = 0
print(f"开始监控区域: {region}, 间隔: {interval}s")
try:
while True:
# 截取区域
screen = ImageGrab.grab(region)
frame = np.array(screen)
frame_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
if prev_frame is not None:
# 计算帧差
diff = cv2.absdiff(prev_frame, frame_gray)
# 阈值化
_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
change_pixels = np.sum(thresh > 0)
total_pixels = thresh.size
change_ratio = change_pixels / total_pixels
if change_ratio > 0.01: # 超过1%像素变化视为"有变化"
print(f"[变化检测] 帧 {frame_count}: 变化比例 {change_ratio:.4f}")
if callback:
callback(frame, change_ratio)
prev_frame = frame_gray
frame_count += 1
time.sleep(interval)
except KeyboardInterrupt:
print("监控已停止")
# 回调:检测到变化时保存截图
def on_change(frame, ratio):
timestamp = time.strftime('%Y%m%d_%H%M%S')
cv2.imwrite(f'change_{timestamp}.png',
cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
print(f"已保存变化截图: change_{timestamp}.png")
# 监控屏幕右上角区域(系统通知区域)
monitor_screen_region((1500, 0, 1920, 200), callback=on_change, interval=0.5)
def smart_monitor(region, interval=0.5, hash_size=8, pixel_threshold=0.05):
"""两级检测:先用感知哈希过滤,再精确检测变化区域"""
from PIL import Image
def compute_phash(image):
"""计算感知哈希"""
img = cv2.resize(image, (hash_size, hash_size))
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
avg = img.mean()
diff = img > avg
return diff.flatten()
prev_hash = None
prev_frame = None
while True:
screen = ImageGrab.grab(region)
frame = np.array(screen)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 第一级:感知哈希检测
current_hash = compute_phash(frame_rgb)
if prev_hash is not None:
hamming_dist = np.sum(current_hash != prev_hash)
if hamming_dist > 2: # 哈希差异超过2位
print(f"[哈希变化] 汉明距离: {hamming_dist}")
# 第二级:精确变化区域定位
if prev_frame is not None:
diff = cv2.absdiff(prev_frame, frame_rgb)
gray_diff = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
_, thresh = cv2.threshold(gray_diff, 30, 255, cv2.THRESH_BINARY)
# 找出变化区域的轮廓
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 500: # 忽略太小的变化区域
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(frame_rgb, (x, y),
(x + w, y + h), (0, 0, 255), 2)
cv2.imshow('Monitor', frame_rgb)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
prev_hash = current_hash
prev_frame = frame_rgb.copy()
time.sleep(interval)
cv2.destroyAllWindows()
核心要点: 屏幕区域监控的核心是平衡效率与准确度。mss库是最快的截图方案,两级检测策略(哈希初筛 + 像素精检)能显著降低CPU使用率。监控间隔根据场景调整——UI变化监控0.5-1秒即可,实时性要求高的场景可缩短到0.1-0.2秒。变化阈值需要根据具体场景调优,避免频繁误报。
五、OCR文字识别
OCR(Optical Character Recognition,光学字符识别)是将图像中的文字信息提取为计算机可编辑文本的技术。在屏幕自动化中,OCR扮演着"阅读者"的角色——当自动化脚本需要读取屏幕上的文字内容(如错误信息、状态提示、数据数值)时,OCR是唯一的可行方案。Tesseract是目前最成熟的开源OCR引擎,最初由HP实验室开发,后由Google维护,支持超过100种语言的文字识别。Python通过 pytesseract 库与Tesseract进行交互,将图像处理(Pillow/OpenCV)和文字识别无缝衔接。
Tesseract的安装和配置需要注意:在Windows上需要从GitHub下载安装包并安装,然后设置环境变量或直接在Python代码中指定tesseract路径。对于中文识别,需要额外下载中文语言包(chi_sim),并在调用时将语言参数设为 lang='chi_sim+eng' 来同时识别中英文。需要注意的是,Tesseract对图像质量要求较高——原始屏幕截图直接送入Tesseract往往识别率不高,必须配合图像预处理来提升识别准确度。
图像预处理是OCR流程中最关键的一环。标准预处理管道包括五个步骤:第一步是灰度化,将彩色图像转为灰度图以减少数据量。第二步是二值化,通过阈值处理将图像转为纯粹的黑白两色,可以用全局阈值(OTSU算法自动确定最优阈值)或自适应阈值(对不同区域采用不同阈值,适合光照不均的图像)。第三步是降噪,使用高斯模糊、中值滤波或形态学操作(开运算、闭运算)去除细小噪点。第四步是倾斜校正,使用霍夫变换或最小面积矩形来检测文本行的倾斜角度,然后通过仿射变换进行纠正。第五步是缩放,将文字区域放大到合适的大小(通常300-600 DPI),因为Tesseract对特定尺寸范围内的文字识别效果最好。每一步预处理都可以显著影响最终的识别准确率,在复杂场景下往往需要反复调优。
import pytesseract
from PIL import Image
import cv2
import numpy as np
# 配置Tesseract路径(Windows环境)
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def basic_ocr(image_path):
"""基础OCR识别"""
img = cv2.imread(image_path)
# 直接识别
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
return text
def enhanced_ocr(image_path):
"""增强预处理后的OCR识别"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1. 预处理管道
# 降噪
denoised = cv2.medianBlur(gray, 3)
# 二值化(OTSU自动阈值)
_, binary = cv2.threshold(denoised, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 形态学操作:去除小噪点
kernel = np.ones((1, 1), np.uint8)
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 2. 缩放(放大到合适尺寸)
height, width = cleaned.shape
if height < 30:
scale = 60 / height
new_width = int(width * scale)
new_height = int(height * scale)
cleaned = cv2.resize(cleaned, (new_width, new_height),
interpolation=cv2.INTER_CUBIC)
# 3. 识别
custom_config = r'--oem 3 --psm 6'
text = pytesseract.image_to_string(
cleaned, lang='chi_sim+eng', config=custom_config
)
return text.strip()
# 识别屏幕上的错误提示
result = enhanced_ocr('error_dialog.png')
print(f"识别结果: {result}")
def ocr_screen_region(region):
"""识别屏幕指定区域的文字"""
from PIL import ImageGrab
# 截取屏幕区域
screen = ImageGrab.grab(region)
screen_np = np.array(screen)
screen_bgr = cv2.cvtColor(screen_np, cv2.COLOR_RGB2BGR)
# 预处理
gray = cv2.cvtColor(screen_bgr, cv2.COLOR_BGR2GRAY)
# CLAHE自适应直方图均衡化(改善对比度)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(gray)
# 二值化
_, binary = cv2.threshold(enhanced, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 识别
text = pytesseract.image_to_string(binary, lang='chi_sim+eng',
config='--oem 3 --psm 6')
return text.strip()
def extract_numbers_from_screen(region):
"""从屏幕区域提取数字"""
screen = ImageGrab.grab(region)
screen_np = np.array(screen)
gray = cv2.cvtColor(screen_np, cv2.COLOR_RGB2GRAY)
# 反色处理(如果是黑底白字)
mean_val = gray.mean()
if mean_val < 127:
gray = 255 - gray
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 只识别数字和常用符号
text = pytesseract.image_to_string(
binary, config='--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789.-%'
)
return text.strip()
# 实时OCR循环
def watch_screen_text(region, target_text, interval=1.0):
"""持续监控屏幕区域直到出现目标文字"""
import time
while True:
text = ocr_screen_region(region)
print(f"[OCR] {text[:50]}...")
if target_text in text:
print(f"检测到目标文本: {target_text}")
return True
time.sleep(interval)
def deskew_image(image):
"""倾斜校正——检测文本行的倾斜角度并纠正"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) \
if len(image.shape) == 3 else image
gray = cv2.bitwise_not(gray)
# 阈值化
_, binary = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 查找所有非零像素点的坐标
coords = np.column_stack(np.where(binary > 0))
# 计算最小外接矩形,获取倾斜角度
angle = cv2.minAreaRect(coords)[-1]
# 调整角度范围
if angle < -45:
angle = 90 + angle
angle = -angle
if abs(angle) < 0.5: # 角度太小,无需校正
return image
# 仿射变换纠正
h, w = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
corrected = cv2.warpAffine(image, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
return corrected
# 倾斜校正前后对比
raw_text = pytesseract.image_to_string('skewed_text.png', lang='chi_sim')
corrected = deskew_image(cv2.imread('skewed_text.png'))
corrected_text = pytesseract.image_to_string(corrected, lang='chi_sim')
print(f"校正前: {raw_text}")
print(f"校正后: {corrected_text}")
核心要点: OCR的识别率高度依赖图像预处理质量。标准管道:灰度化 → 降噪 → 二值化(OTSU) → 倾斜校正 → 缩放。PSM模式6(假设为统一的文本块)适合大部分屏幕文字场景。chi_sim+eng 混合语言模式能同时识别中英文。对于特定格式(如纯数字),使用 tessedit_char_whitelist 限制字符集可显著提升准确率。
六、UI元素定位
UI元素定位是图像识别在桌面自动化中最直接的应用。与Web自动化中的CSS选择器不同,桌面UI没有标准化的DOM结构可供查询,必须通过视觉特征来定位目标元素。一个完整的UI元素定位系统需要解决四个问题:找得到(元素检测)、找得准(精确定位)、点得对(坐标映射)、跑得通(多分辨率适配)。这四个问题环环相扣,任何一个环节出问题都可能导致自动化流程失败。
在元素检测层面,模板匹配是最常用的方法,将预期UI元素的截图作为模板,在当前屏幕截图中搜索。但实际场景中,UI元素可能呈现多种状态:按下、悬停、禁用、加载中,每种状态的视觉表现都不同。因此,需要为同一个UI元素准备多张模板图像,并采用"按置信度降序"的匹配策略——先尝试高置信度的常态模板,如果匹配不到再尝试低一档的其他状态模板。对于动态内容的UI元素(如带有文字标签的按钮),可以结合模板匹配和OCR:先用模板匹配定位按钮的大致区域,再用OCR识别区域内的文字来确认是否为预期元素。
坐标映射是多分辨率适配的关键技术。不同电脑的屏幕分辨率不同,同一应用程序在不同分辨率下的UI元素位置也不同。解决这个问题的方法不是为每种分辨率都准备一套模板,而是使用"基准分辨率"策略:在一个固定分辨率(如1920x1080)下标注所有UI元素的坐标,然后通过缩放系数(当前分辨率宽/基准宽,当前分辨率高/基准高)将坐标映射到实际屏幕上。更精确的做法是使用 win32gui 或 pygetwindow 获取目标窗口的位置和尺寸,将UI坐标相对于窗口而非屏幕来计算,这样即使窗口被移动或缩放,自动化脚本仍然能正确操作。
import cv2
import numpy as np
from PIL import ImageGrab
import pyautogui
class UIElementLocator:
"""UI元素定位器"""
def __init__(self, template_dir, base_resolution=(1920, 1080)):
self.templates = {}
self.base_w, self.base_h = base_resolution
self.current_w, self.current_h = pyautogui.size()
def load_template(self, name, image_path):
"""加载UI元素模板"""
template = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
self.templates[name] = template
print(f"已加载模板 [{name}]: {template.shape}")
def find_element(self, name, screen=None, threshold=0.8):
"""在屏幕中定位UI元素"""
if name not in self.templates:
raise ValueError(f"模板 '{name}' 未加载")
if screen is None:
screen = ImageGrab.grab()
screen = np.array(screen)
screen_gray = cv2.cvtColor(screen, cv2.COLOR_RGB2GRAY)
else:
screen_gray = cv2.cvtColor(screen, cv2.COLOR_RGB2GRAY) \
if len(screen.shape) == 3 else screen
template = self.templates[name]
h, w = template.shape
# 多尺度搜索(适配不同分辨率)
found = None
for scale in [1.0, 0.9, 0.8, 1.1, 1.2]:
if scale != 1.0:
new_w = int(w * scale)
new_h = int(h * scale)
if new_w < 20 or new_h < 20:
continue
resized = cv2.resize(template, (new_w, new_h))
else:
resized = template
result = cv2.matchTemplate(screen_gray, resized,
cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
if max_val > threshold and (found is None or max_val > found[0]):
found = (max_val, max_loc, resized.shape)
if found:
confidence, top_left, (th, tw) = found
center_x = top_left[0] + tw // 2
center_y = top_left[1] + th // 2
return {
'center': (center_x, center_y),
'bbox': (*top_left, top_left[0] + tw, top_left[1] + th),
'confidence': confidence
}
return None
def click_element(locator_result, button='left'):
"""安全地点击定位到的UI元素"""
if locator_result is None:
print("元素未找到,无法点击")
return False
cx, cy = locator_result['center']
confidence = locator_result['confidence']
print(f"点击元素: ({cx}, {cy}), 置信度: {confidence:.2f}")
pyautogui.click(cx, cy, button=button)
return True
def find_and_click(name, locator, threshold=0.8):
"""查找并点击UI元素的快捷方法"""
result = locator.find_element(name, threshold=threshold)
return click_element(result)
def locate_multi_state_button(name, locator):
"""多状态按钮定位——依次尝试不同状态的模板"""
states = [f'{name}_normal', f'{name}_hover',
f'{name}_pressed', f'{name}_disabled']
for state in states:
try:
result = locator.find_element(state, threshold=0.75)
if result:
print(f"找到按钮 [{name}] 状态: {state}")
return result
except ValueError:
continue
print(f"按钮 [{name}] 所有状态均未找到")
return None
# 使用示例
locator = UIElementLocator('templates/')
locator.load_template('save_btn_normal', 'templates/save_normal.png')
locator.load_template('save_btn_hover', 'templates/save_hover.png')
pos = locate_multi_state_button('save_btn', locator)
if pos:
click_element(pos)
class AdaptiveLocator(UIElementLocator):
"""自适应分辨率UI定位器"""
def __init__(self, template_dir, base_resolution=(1920, 1080)):
super().__init__(template_dir, base_resolution)
self.setup_resolution_adaptation()
def setup_resolution_adaptation(self):
"""设置分辨率适配参数"""
import win32gui
import win32api
# 获取实际的屏幕尺寸
self.current_w = win32api.GetSystemMetrics(0)
self.current_h = win32api.GetSystemMetrics(1)
# 计算缩放系数
self.scale_x = self.current_w / self.base_w
self.scale_y = self.current_h / self.base_h
print(f"基准分辨率: {self.base_w}x{self.base_h}")
print(f"当前分辨率: {self.current_w}x{self.current_h}")
print(f"缩放系数: x={self.scale_x:.2f}, y={self.scale_y:.2f}")
def get_window_relative_pos(self, window_title, element_pos):
"""获取窗口相对坐标"""
import win32gui
import win32con
try:
hwnd = win32gui.FindWindow(None, window_title)
if hwnd:
# 获取窗口在屏幕上的位置
rect = win32gui.GetWindowRect(hwnd)
win_x, win_y = rect[0], rect[1]
# 计算相对于窗口的坐标
rel_x = element_pos[0] - win_x
rel_y = element_pos[1] - win_y
return (rel_x, rel_y)
except Exception as e:
print(f"获取窗口位置失败: {e}")
return element_pos
# 自适应定位
adapter = AdaptiveLocator('templates/')
adapter.load_template('confirm_btn', 'templates/confirm_1920.png')
result = adapter.find_element('confirm_btn')
if result:
# 坐标自适应映射
adapted_x = int(result['center'][0] / adapter.scale_x)
adapted_y = int(result['center'][1] / adapter.scale_y)
print(f"自适应坐标: ({adapted_x}, {adapted_y})")
pyautogui.click(adapted_x, adapted_y)
核心要点: UI元素定位需要系统性设计。多状态模板、多尺度搜索、自适应分辨率映射是三大关键策略。对于"同元素不同外观"的问题(如不同语言版本),准备多套模板;对于"同元素不同位置"的问题(如窗口拖动),采用相对坐标(窗口相对坐标而非屏幕绝对坐标)来解决。
七、图像变化检测
图像变化检测是屏幕监控自动化中的核心算法模块。与前面提到的模板匹配和OCR不同,变化检测不需要知道目标长什么样,只需要判断"图像是否发生了变化"以及"变化发生在哪里"。这在许多场景下非常有用:监控下载进度条的变化、检测软件安装完成的状态切换、监测视频流中是否出现新物体、以及发现屏幕上突然弹出的异常提示等。变化检测算法的核心输入是两帧或多帧图像,输出是变化区域的掩码(Mask)和变化程度的量化指标。
最基础的变化检测方法是帧差法(Frame Differencing),即计算前后两帧图像的像素绝对差,然后通过阈值化将差值图转为二值掩码。帧差法的优点是计算极快(几毫秒即可完成),对硬件要求低。但缺点也很明显:对光照变化敏感(屏幕亮度微变会导致大量误检)、对相机抖动敏感(窗口移动或刷新会导致大范围伪变化)、且无法检测缓慢变化(因为相邻帧差异太小)。改进版是三帧差分法——使用三帧连续图像,计算前两帧的差和后两帧的差,然后取交集,这样可以有效消除运动拖尾和噪声。
更高级的变化检测方法是结构相似性(SSIM,Structural Similarity Index)。SSIM从三个维度衡量两幅图像的相似度:亮度(luminance)、对比度(contrast)和结构(structure)。与逐像素比较不同,SSIM考虑的是像素之间的相互关系,因此能更好地感知图像的结构变化而忽略细微的噪声和光照变化。SSIM的返回值在-1到1之间,1表示两幅图完全相同。在OpenCV中,可以通过 scikit-image 库的 compare_ssim 函数来计算。通过SSIM差异图(1 - SSIM),可以直观地看到变化区域。此外,对于监控视频流或高频屏幕捕获的场景,还可以使用高斯混合模型(GMM)或KNN背景减除法,它们能够建立动态背景模型,从而区分"前景变化"(值得关注的目标变化)和"背景变化"(光照变化、屏幕闪烁等噪声)。
import cv2
import numpy as np
from PIL import ImageGrab
import time
def frame_diff_method(prev_frame, curr_frame, threshold=30):
"""帧差法——检测两帧之间的变化"""
# 转灰度
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_RGB2GRAY) \
if len(prev_frame.shape) == 3 else prev_frame
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_RGB2GRAY) \
if len(curr_frame.shape) == 3 else curr_frame
# 计算绝对差
diff = cv2.absdiff(prev_gray, curr_gray)
# 阈值化
_, thresh = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)
# 形态学操作:去除孤立噪点
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
clean = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# 计算变化指标
change_pixels = np.sum(clean > 0)
total_pixels = clean.size
change_ratio = change_pixels / total_pixels
return clean, change_ratio
def three_frame_diff(frames):
"""三帧差分法——减少运动拖尾"""
# 计算两对帧差
diff1 = cv2.absdiff(frames[0], frames[1])
diff2 = cv2.absdiff(frames[1], frames[2])
# 阈值化
_, thresh1 = cv2.threshold(diff1, 30, 255, cv2.THRESH_BINARY)
_, thresh2 = cv2.threshold(diff2, 30, 255, cv2.THRESH_BINARY)
# 取交集(像素级与操作)
combined = cv2.bitwise_and(thresh1, thresh2)
return combined
# 实时帧差法监控
region = (0, 0, 800, 600)
prev = None
print("启动帧差法监控(按Ctrl+C停止)...")
try:
while True:
screen = ImageGrab.grab(region)
frame = np.array(screen)
if prev is not None:
mask, ratio = frame_diff_method(prev, frame)
if ratio > 0.02: # 超过2%区域变化
print(f"检测到显著变化: {ratio:.2%}")
# 绘制变化轮廓
contours, _ = cv2.findContours(
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
display = frame.copy()
for cnt in contours:
if cv2.contourArea(cnt) > 100:
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(display, (x, y),
(x + w, y + h), (0, 0, 255), 2)
cv2.imshow('Change Detection', display)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
prev = frame
time.sleep(0.1)
except KeyboardInterrupt:
print("监控停止")
cv2.destroyAllWindows()
from skimage.metrics import structural_similarity as ssim
import cv2
import numpy as np
def ssim_change_detection(prev_frame, curr_frame):
"""基于结构相似性(SSIM)的变化检测"""
# 转灰度
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_RGB2GRAY) \
if len(prev_frame.shape) == 3 else prev_frame
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_RGB2GRAY) \
if len(curr_frame.shape) == 3 else curr_frame
# 计算SSIM
score, diff = ssim(prev_gray, curr_gray, full=True, data_range=255)
diff = (1 - diff) * 255 # 差异图:差异越大值越大
diff = diff.astype(np.uint8)
# 阈值化提取变化区域
_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
# 形态学清理
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
clean = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
clean = cv2.morphologyEx(clean, cv2.MORPH_OPEN, kernel)
return score, clean
# 使用示例
frame_a = cv2.imread('screen_before.png')
frame_b = cv2.imread('screen_after.png')
similarity, change_mask = ssim_change_detection(frame_a, frame_b)
print(f"SSIM相似度: {similarity:.4f} (1=完全相同)")
if similarity < 0.95:
print("屏幕内容发生显著变化!")
# 显示变化区域
cv2.imshow('Changes', change_mask)
cv2.imwrite('change_mask.png', change_mask)
class BackgroundSubtractorDetector:
"""基于背景减除法的变化检测(GMM模型)"""
def __init__(self, history=500, var_threshold=16):
self.bg_subtractor = cv2.createBackgroundSubtractorMOG2(
history=history,
varThreshold=var_threshold,
detectShadows=True
)
def detect_motion(self, frame, min_area=500):
"""检测前景运动目标"""
# 应用背景减除法
fg_mask = self.bg_subtractor.apply(frame)
# 去除阴影
_, fg_mask = cv2.threshold(fg_mask, 200, 255, cv2.THRESH_BINARY)
# 形态学清理
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_OPEN, kernel)
fg_mask = cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel)
# 查找轮廓
contours, _ = cv2.findContours(
fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
changes = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area > min_area:
x, y, w, h = cv2.boundingRect(cnt)
changes.append({
'bbox': (x, y, x + w, y + h),
'area': area,
'center': (x + w // 2, y + h // 2)
})
return changes
# 使用示例
detector = BackgroundSubtractorDetector()
screen = ImageGrab.grab((0, 0, 800, 600))
frame = np.array(screen)
changes = detector.detect_motion(frame)
print(f"检测到 {len(changes)} 个变化区域")
核心要点: 变化检测有三层技术栈可供选择:速度最快的是帧差法(毫秒级,适合实时监控),精度最好的是SSIM(亚像素级,适合精确比对),适应最强的是背景减除法(GMM/KNN,适合动态场景)。实际项目中推荐采用混合策略:先用帧差法做快速过滤,当帧差结果可疑时再用SSIM做二次确认,兼顾速度和准确度。
八、实战案例
将前面所学的各项技术综合起来,可以构建出功能强大的屏幕自动化应用。本节通过四个典型实战案例,展示图像识别与屏幕监控自动化在实际工作流中的完整应用。每个案例都结合了多种技术手段,并且提供了可直接运行的代码框架,读者可以根据自己的具体场景进行调整和扩展。
案例一:简单验证码自动识别
验证码识别是OCR技术的一个典型挑战场景。虽然现代深度学习在验证码识别上已经取得了很高的准确率,但对于简单的数字字母组合验证码,结合图像预处理和Tesseract仍然是一种快速有效的方案。核心技巧包括:对验证码图像进行适当的预处理(去背景、二值化、字符分割)、使用字符白名单限制识别范围、以及结合图像形态学操作去除干扰线。
def simple_captcha_recognizer(image_path):
"""简单验证码识别"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1. 去噪
denoised = cv2.medianBlur(gray, 3)
# 2. 二值化
_, binary = cv2.threshold(denoised, 0, 255,
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 3. 去除干扰线(形态学操作)
kernel = np.ones((2, 2), np.uint8)
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 4. 膨胀连通字符区域
kernel = np.ones((2, 2), np.uint8)
dilated = cv2.dilate(cleaned, kernel, iterations=1)
# 5. OCR识别(限定字符集)
config = r'--oem 3 --psm 8 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
text = pytesseract.image_to_string(dilated, config=config)
return text.strip()
# 识别验证码
captcha_text = simple_captcha_recognizer('captcha_sample.png')
print(f"验证码识别结果: {captcha_text}")
案例二:UI自动化测试——自动填写表单
将UI元素定位与鼠标键盘模拟结合,可以构建完整的UI自动化测试流程。以下示例演示了如何定位表单中的输入框、下拉菜单和提交按钮,并自动完成填写和提交操作。系统会在每个操作后截图留证,并通过OCR验证操作是否成功。
class FormAutomationTester:
"""UI表单自动化测试"""
def __init__(self, template_dir):
self.locator = UIElementLocator(template_dir)
self.screenshots = []
def run_test_flow(self):
"""执行完整的表单测试流程"""
test_steps = [
('username_field', 'click_and_type', 'admin'),
('password_field', 'click_and_type', 'test1234'),
('login_button', 'click', None),
('menu_item', 'click', None),
('form_field_1', 'click_and_type', '测试数据'),
('submit_button', 'click', None),
]
for step_name, action, value in test_steps:
print(f"\n[步骤] {step_name} -> {action} {value or ''}")
# 定位UI元素
result = self.locator.find_element(step_name)
if not result:
print(f" FAILED: 未找到元素 {step_name}")
self.take_screenshot(f'failed_{step_name}')
continue
# 执行操作
cx, cy = result['center']
if action == 'click':
pyautogui.click(cx, cy)
elif 'type' in action:
pyautogui.click(cx, cy)
time.sleep(0.2)
pyautogui.typewrite(value, interval=0.05)
time.sleep(0.5)
self.take_screenshot(f'step_{step_name}')
print(f" PASSED: {step_name}")
print("\n测试流程完成")
return True
def take_screenshot(self, name):
timestamp = time.strftime('%H%M%S')
filename = f'{name}_{timestamp}.png'
pyautogui.screenshot(filename)
self.screenshots.append(filename)
# 运行测试
tester = FormAutomationTester('templates/')
tester.run_test_flow()
案例三:屏幕特定图像出现告警
在运维监控场景中,经常需要等待屏幕出现特定图像后再执行后续操作。例如,等待软件安装完成界面的"完成"按钮出现、等待数据导出完成的提示图标出现、或等待某个错误弹窗出现并自动报警。以下实现了一个通用的"图像出现告警"系统,支持多目标同时监控、告警级别设定和自定义回调。
import threading
import winsound
class ScreenAlertMonitor:
"""屏幕图像出现告警监控器"""
def __init__(self):
self.watchers = []
def add_watcher(self, name, template_path, region=None,
threshold=0.8, callback=None, alert_type='info'):
"""添加一个监控目标"""
template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
self.watchers.append({
'name': name,
'template': template,
'region': region,
'threshold': threshold,
'callback': callback,
'alert_type': alert_type,
'triggered': False
})
def start_monitoring(self, interval=0.5):
"""启动多目标同时监控"""
print(f"启动屏幕告警监控,监控 {len(self.watchers)} 个目标...")
try:
while True:
for watcher in self.watchers:
if watcher['triggered']:
continue
# 截取屏幕
screen = ImageGrab.grab(watcher['region'])
screen_np = np.array(screen)
screen_gray = cv2.cvtColor(screen_np, cv2.COLOR_RGB2GRAY)
# 模板匹配
result = cv2.matchTemplate(
screen_gray, watcher['template'],
cv2.TM_CCOEFF_NORMED
)
_, max_val, _, _ = cv2.minMaxLoc(result)
if max_val >= watcher['threshold']:
watcher['triggered'] = True
msg = f"[警报] 检测到 '{watcher['name']}'!置信度: {max_val:.2f}"
print(msg)
# 声音告警
if watcher['alert_type'] in ('warning', 'error'):
winsound.Beep(1000, 500)
# 执行回调
if watcher['callback']:
watcher['callback'](watcher['name'], max_val)
time.sleep(interval)
except KeyboardInterrupt:
print("监控已停止")
# 告警回调函数
def alert_callback(name, confidence):
pyautogui.screenshot(f'alert_{name}_{int(time.time())}.png')
print(f"告警截图已保存,目标: {name}")
# 使用示例
monitor = ScreenAlertMonitor()
monitor.add_watcher('安装完成', 'finish_btn.png',
alert_type='info', callback=alert_callback)
monitor.add_watcher('错误弹窗', 'error_dialog.png',
threshold=0.7, alert_type='error', callback=alert_callback)
monitor.start_monitoring(interval=0.3)
案例四:游戏画面数据监控
对于游戏自动化场景,图像识别技术可以用于监控游戏画面中的数值变化、状态图标和事件提示。以下示例展示了如何监控游戏屏幕中的血量数值、状态图标和系统公告,并将数据记录到日志文件中,便于后续分析。
class GameScreenMonitor:
"""游戏画面数据监控"""
def __init__(self, game_window_region):
self.region = game_window_region
self.data_log = []
def monitor_hp_mp(self, hp_region, mp_region, interval=1.0):
"""监控血量和魔法值"""
while True:
# 截取血量区域
hp_screen = ImageGrab.grab(self._adjust_region(hp_region))
hp_np = np.array(hp_screen)
hp_text = pytesseract.image_to_string(
hp_np, config='--psm 7 -c tessedit_char_whitelist=0123456789/'
).strip()
# 截取魔法区域
mp_screen = ImageGrab.grab(self._adjust_region(mp_region))
mp_np = np.array(mp_screen)
mp_text = pytesseract.image_to_string(
mp_np, config='--psm 7 -c tessedit_char_whitelist=0123456789/'
).strip()
timestamp = time.strftime('%H:%M:%S')
log_entry = f"[{timestamp}] HP: {hp_text} | MP: {mp_text}"
print(log_entry)
self.data_log.append(log_entry)
time.sleep(interval)
def detect_combat_state(self, combat_icon_template, threshold=0.7):
"""检测是否处于战斗状态"""
screen = ImageGrab.grab(self.region)
screen_np = np.array(screen)
screen_gray = cv2.cvtColor(screen_np, cv2.COLOR_RGB2GRAY)
result = cv2.matchTemplate(screen_gray, combat_icon_template,
cv2.TM_CCOEFF_NORMED)
_, max_val, _, _ = cv2.minMaxLoc(result)
return max_val >= threshold
def _adjust_region(self, rel_region):
"""根据游戏窗口位置调整区域坐标"""
return (
self.region[0] + rel_region[0],
self.region[1] + rel_region[1],
self.region[0] + rel_region[2],
self.region[1] + rel_region[3]
)
def export_log(self, filename='game_monitor_log.txt'):
"""导出监控日志"""
with open(filename, 'w', encoding='utf-8') as f:
for entry in self.data_log:
f.write(entry + '\n')
print(f"日志已导出到: {filename}")
# 使用示例(假设游戏窗口在左上角 0,0)
monitor = GameScreenMonitor((0, 0, 1920, 1080))
# monitor.monitor_hp_mp((50, 50, 200, 80), (50, 90, 200, 120))
核心要点: 实战中需要根据场景灵活组合各项技术。验证码识别注重预处理技巧;UI自动化测试需要建立完善的元素定位库和测试流程;屏幕告警系统需要权衡监控频率和误报率;游戏监控则更注重OCR数值提取的准确性。无论哪种场景,日志记录和异常处理都是不可或缺的重要环节——自动化脚本只有具备了完善的自我诊断能力,才能真正投入生产环境使用。