news 2026/10/21 9:14:16

UI自动化进阶:OCR与模板匹配实现视觉定位,让脚本真正“看见”屏幕

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UI自动化进阶:OCR与模板匹配实现视觉定位,让脚本真正“看见”屏幕

做UI自动化的人,一定经历过这种崩溃:昨晚case跑得还好好的,早上通知你“登录按钮从(500,1200)挪到了(520,1250),脚本挂了”。坐标点击就是死记硬背,界面一改,全部白搭。这个系列写到第四章,前几章把框架和基础定位聊了不少,这一章集中解决一个问题:让脚本“看见”屏幕。用OCR识别文字、用CV模板匹配找图标,两种视觉算法结合,最终把识别结果换算成点击坐标,交给下层执行。这部分属于“视觉处理”章节,下一次再聊怎么控制设备,把点击真正落下去。

1. 为什么UI自动化需要从“记坐标”升级到“看屏幕”

1.1 传统坐标点击的致命缺陷

先讲一个真实场景。某App的登录页,开发在发版时给按钮加了20像素的内边距,或者一个广告横幅把整个页面向下顶了几十像素,你脚本里的click(500, 1200)就点偏了。更麻烦的是,有些页面是动态布局,同一款手机不同launcher,甚至同一系统下字体大小调整,都可能让固定坐标失效。坐标点击的本质,是把“元素位置”写死成“屏幕位置”。而屏幕位置是结果,不是原因。原因是什么?是元素本身。

人操作界面,是靠眼睛找“登录”两个字,找那个蓝色圆角按钮,找到再点。机器如果也这么干,就不怕界面微调。所以要做的事,就是把定位思路从“记录坐标”切换成“视觉定位”:读取当前屏幕图像,在图像里找到目标,算出它此刻的坐标,再点击。这里的关键只有两点:带文字的目标用OCR找,纯图形的目标用模板匹配找。

1.2 OCR与模板匹配:给脚本装上一双眼睛

OCR(光学字符识别)解决的是“屏幕上有一行字,它在哪”的问题。给它一张截图,它返回识别出的文本内容,以及每个文本块在图像中的位置坐标。脚本只需要在返回结果里找目标关键词,拿到位置,就算定位成功。模板匹配是CV里的经典算法,解决的是“找一个图标”的问题:把目标区域先截下来作为模板,在整张截图里滑动计算相似度,相似度最高的位置就是目标所在。

这两个技术都不新鲜,但组合起来非常实用。打个比方:OCR像人眼识字,模板匹配像人眼认脸。界面上的控件无非两类,要么带文字,要么是纯图形。OCR吃掉文字类,模板匹配吃掉图形类,基本覆盖了日常点击的绝大多数目标。而且这套组合完全本地运行,不依赖网络,脚本执行速度快,也不容易被服务端抽风拖累。

2. 视觉方案选型:为什么偏偏是“OCR+模板匹配”组合

2.1 OCR引擎怎么选

OCR领域有很多实现。开源方案里最常用的是Tesseract,老牌、免费,Python下有pytesseract封装,安装中文语言包后能识别简体中文。针对中文优化的商业OCR SDK也有,识别率确实更高,但往往需要联网、按量计费,或者有客户端限制。做UI自动化,我的建议是先把开源方案跑通,真遇到识别率瓶颈再切商业方案。理由很简单:自动化框架第一要求是离线可用、可复现,网络一波动case就挂,这种代价不划算。

Tesseract的短板在于对图像质量敏感,字号小于20像素、艺术字体、渐变背景,识别率就往下掉。所以在方案里,OCR之前必须做图像预处理,后面实操部分我会详细展开。另外,语言包要提前验证,有的环境装了包但没装中文语言模型,跑出来全是乱码,这个非常容易踩。

2.2 模板匹配的实现基础

模板匹配直接用OpenCV的cv2.matchTemplate。原理是把模板图当一个小窗口,在待匹配的大图上按步长滑动,每个位置计算一次匹配度,输出一张匹配度图,最后用minMaxLoc找最强响应点。常用评分方法是TM_CCOEFF_NORMED,它对亮度漂移做了归一化,结果在[-1,1]之间,越接近1表示越像。

这个算法有点“单纯”:模板稍微旋转几度,匹配度就掉得厉害;模板带了一截背景,也可能误匹配。但它的优势是快、轻、不需要训练,非常适合界面这种“图标方向基本固定”的场景。考虑到设备分辨率不同,目标Button大小会变,我会用多尺度搜索来缓解缩放问题,这部分也放到后面实操。

2.3 为什么不是单用其中一种

有人会问,全部用OCR不就行了?不行。很多按钮上只有一个放大镜图标,没有任何文字,OCR是瞎的。全部用模板匹配也不行,一个是“登录”按钮在不同皮肤下长不一样,模板不好维护;另一个是文字按钮经常有状态变化,比如置灰、按下高亮,模板一换就得重截。

所以正确的分工是:文字按钮优先OCR,因为文字内容稳定,虽然样式会变,但“登录”两个字还在;图标按钮、无文字区域用模板匹配。更进一步,还能做交叉验证:OCR返回“登录”候选框之后,在这个候选框范围内再跑一次模板匹配,确认确实有个按钮长相符合,双保险能明显降低误点。我的项目里用这套组合,覆盖了大约八成以上的点击目标。

3. 环境准备与图像采集规范

3.1 截图是视觉自动化的地基

视觉定位的第一步是拿到屏幕截图。不同平台截图方式不一样,Android通常走adb命令,PC端用系统API截图,iOS走测试工具的接口。不管怎么截,有三条原则要守住。

必须用无损格式保存。PNG是标准选择,不要贪空间用JPG,JPG压缩会在文字边缘产生噪声,OCR识别率和模板匹配相似度都会受影响。必须记录截图时的图像分辨率,后面坐标换算全靠它。很多脚本“点偏”,就是因为截图是缩略图,而点击用的是全屏坐标。截图必须完整且时机准确。页面动画没结束就截图,要么截到半透明层,要么按钮还在滑入,识别还没开始就已经注定失败。

3.2 依赖安装与基准验证

环境上只需要两样东西:OpenCV和OCR引擎。Python侧安装opencv-python和pytesseract,再给OCR引擎装好中文语言包。装完别急着封装,先做一次基准验证:手工截一张带“登录”“退出”等常见文字的页面图,写三行代码跑一次识别。

import cv2 import pytesseract img = cv2.imread("screen.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) data = pytesseract.image_to_data(gray, lang="chi_sim", output_type=pytesseract.Output.DICT) print(data["text"])

如果能看到中文文本项,说明链路通了。这一步简单但重要,它确认了语言包路径、编码环境和图像读取都没问题,后面调试才会省心。很多人直接跳到封装,结果识别不出来,来回折腾才发现是语言包没装对。

4. OCR文字定位实操:让脚本读出按钮在哪

4.1 图像预处理:先让文字“更好认”

Tesseract对清晰锐利的黑字白底最友好。屏幕截图里常见的干扰有:深色背景上的浅色字、抗锯齿产生的边缘渐变、按钮阴影。预处理的目的,就是把文字从背景里“抠”出来。我最常用的一套组合是:转灰度、放大两倍、二值化。放大那一步很多人忽略,但对小字号特别管用,相当于把10像素的字变成20像素再识别。不过放大后得到的坐标要除以2还回去,否则点偏。

二值化可以用固定阈值,也可以自适应阈值,像页面里有渐变色背景时,自适应更稳。下面这段代码是标准的预处理流程:

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.resize(gray, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC) _, thresh = cv2.threshold(gray, 130, 255, cv2.THRESH_BINARY)

阈值130是一个经验值。页面整体偏亮就调高,整体偏暗就调低。预处理不是越狠越好,二值化过度会把文字笔画连成一片,识别反而更差。建议把预处理后的图片另存出来,肉眼检查一次,确认文字边界清晰再继续。

4.2 从OCR结果中提取点击坐标

OCR跑完之后,数据在image_to_data返回的字典里,每一行对应一个识别出来的字符或词,包含bounding box的left、top、width、height,以及conf置信度。要拿到“登录”按钮的中心点,就遍历结果,过滤出文本包含目标关键词的项,把框的中心算出来。

for i in range(len(data["text"])): text = data["text"][i].strip() if "登录" in text and int(data["conf"][i]) > 60: x = data["left"][i] // 2 y = data["top"][i] // 2 w = data["width"][i] // 2 h = data["height"][i] // 2 center = (x + w // 2, y + h // 2)

注意这里的除以2,就是把放大两倍的坐标还原回原始截图坐标。实际项目里,OCR会把“登录”识别成“登 录”带空格,或者拆成几个box,所以关键字匹配建议用“包含”而不是“等于”,并考虑把同一行的box合并。OCR返回坐标是相对于预处理后图像的,这个坐标换算第一层,千万别漏。

4.3 置信度阈值:别把所有字都当真

OCR输出里每个词都带conf,表示识别置信度,范围是0到100。实际页面里,背景装饰、花纹、半透明遮罩都可能被OCR当成“文字”,这些误识别结果的置信度通常不高。我一般设一个60分底线,低于60直接不采信。核心逻辑是:低置信度的结果,点错了比点不到更可怕,轻则case失败,重则误入业务流程造成脏数据。

光有全局阈值还不够,还有两个场景要处理。一个是同屏出现多个相同关键词,比如“下一步”出现两次,就需要按业务规则筛选,比如取最靠下的,或者取置信度最高的。另一个是目标文字在倒计时状态,比如按钮显示“重新获取(60s)”,你指望OCR识别“重新获取”碰运气,不如只匹配“重新获取”主关键词,括号里的数字变化不影响定位。

5. 模板匹配定位实操:让脚本认出按钮的长相

5.1 模板怎么截最稳

模板匹配的成败,八成取决于模板本身。截模板时,我踩过几次坑之后总结出几条经验。

模板要小,只包含目标控件的核心视觉区域,不要贪心截一大块背景进去。背景里的其他界面元素越少,误匹配概率越低。模板要“干净”,尽量截默认状态。按钮的按压态、选中态别当模板,否则实机上的普通状态匹配度反而低。同一个按钮如果有深色浅色两套主题,最好分开存两份模板,匹配时依次尝试,取分数高的。

模板文件建议按“页面_控件名_状态.png”的规则命名,放到测试工程resource目录下,方便复用和排查。我见过有人把模板截成600x300,里面全是背景,匹配分数低到没法用,其实就是没遵守第一条。

5.2 多尺度匹配:解决截图尺寸不一致

cv2.matchTemplate要求模板和原图尺度一致,但不同设备分辨率、不同系统字体大小,甚至页面缩放,都会让同一个按钮在截图里大小不一样。所以要做多尺度遍历:把模板按0.8到1.2的步长缩放,每个尺度跑一次matchTemplate,记录每个尺度下的最高分数和位置,最后取全局最高。

template = cv2.imread("tab_login.png") th, tw = template.shape[:2] best = (-1, None, 1.0) for scale in [0.8, 0.9, 1.0, 1.1, 1.2]: resized = cv2.resize(template, None, fx=scale, fy=scale) rh, rw = resized.shape[:2] result = cv2.matchTemplate(bg, resized, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) if max_val > best[0]: best = (max_val, max_loc, scale)

这里特别要注意:每个尺度的max_loc,都是当前缩放后模板在原始大图上的左上角位置,所以算中心点时,宽度高度要用resized的rw和rh,也就是tw*scale,不能直接用原始tw。尺度范围不要盲目扩大,0.8到1.2一般够用。真遇到字体特别大的设备,我再把上限临时调到1.4。

5.3 多目标匹配与防误点

很多App的控件是重复的,比如一个列表页有十几行,每行都有一个“置顶”图标。一次匹配只能找到最像的那个,不够用。要做到“找到所有候选”,就得把第一次匹配到的区域遮掉,再跑一次,循环直到分数低于阈值。

result = cv2.matchTemplate(bg, resized, cv2.TM_CCOEFF_NORMED) while True: _, max_val, _, max_loc = cv2.minMaxLoc(result) if max_val < 0.8: break cx = max_loc[0] + rw // 2 cy = max_loc[1] + rh // 2 candidates.append((cx, cy)) result[max_loc[1]:max_loc[1] + rh, max_loc[0]:max_loc[0] + rw] = 0

把匹配区域赋成0,就是告诉算法“这个地方已经找到,别再选了”。拿到候选列表之后,按业务确定点哪一个。比如列表操作一般点第一个,或者按y坐标从大到小排序。盲目点第一个候选很容易出问题,尤其是“收藏”“关注”这类按钮,前面几行可能是自己发的内容,点了没反应不报错,但case没真正成功。

6. 点击操作封装与坐标换算

6.1 两套坐标系的换算

视觉定位返回的坐标,是截图坐标系里的点;设备点击用的是设备物理坐标系。两者往往不一致,比如截图像素1280x720,设备实际分辨率1920x1080,或者截图为了省内存被压缩了。换算规则很简单,就是比例缩放:

def to_device_coord(image_point, image_size, device_size): ix, iy = image_point iw, ih = image_size dw, dh = device_size return int(ix * dw / iw), int(iy * dh / ih)

看起来简单,但它是“点偏”报障的重灾区。我做某跨平台项目时遇到过:截图拿到的是1280x720,点击坐标却按1920x1080去算,结果所有点都跑到右下角去了。拿到设备和截图分辨率后,先画个点对拍一遍,能省掉后面一上午的排查。

6.2 点击动作封装与重试机制

视觉定位模块和设备控制模块之间,我建议定义一个薄接口。视觉侧只负责返回“目标确定存在,坐标在哪”,不关心点击怎么传出去;控制侧负责把坐标转成真实触摸事件。这样视觉模块可以在Android、PC、iOS间复用,控制侧各自实现。

点击后的校验很重要。最好在点击后重新截一张图,再做一次视觉确认,判断页面是否发生了预期变化,比如OCR确认“登录”按钮消失,或者出现了“欢迎回来”的文本。这个闭环能解决很多“点了等于没点”的隐性失败,特别是网络慢导致页面跳转延迟的时候。视觉定位失败也要设计重试:发现定位不到目标时,重新截图再试,连续失败3次以上才判失败。页面动画、网络请求进度都有瞬时状态,一次截图的识别失败,不代表按钮不存在。

7. 常见问题与排查实录

7.1 OCR漏字、错字怎么处理

最典型的两个现象:目标文字识别不出来,或者“登录”被识别成“登乐”。先说原因,小字号、抗锯齿、彩色渐变背景都会干扰。解决办法按优先级来:先把图放大两倍并且转灰度之后识别;再不行做二值化;还不行就把局部区域裁剪出来单独识别,而不是整屏识别,因为整屏文本太多,小字很容易被忽略。错字问题只能靠同义词和多候选兜底,比如目标“确认支付”也接受“确定支付”,脚本里维护一个候选关键词列表。

7.2 模板匹配误匹配率偏高

误匹配大多出在模板带背景上。截模板时手一滑,把周围环境也截进去了,结果这个带环境的模板恰好和另一处界面对上了。排查方法很简单:把模板和匹配位置在原图上画框截图,肉眼看看是不是真的对上了。如果模板干净还误匹配,那就是阈值太低,把0.8提到0.85甚至0.9,误匹配率会下降得很明显。还不行就加区域约束,结合OCR结果,把匹配范围限制在特定区域里。

7.3 分辨率与系统缩放导致点偏

PC端最常见的是系统缩放问题,比如系统设置成125%、150%缩放之后,截图分辨率、程序窗口逻辑尺寸、屏幕物理尺寸三套数不一致。Android上则是dpi差异造成的逻辑分辨率和物理分辨率不一致。最稳的办法是不要自己猜,直接从设备控制层读取真实的物理分辨率,每次截图都带上分辨率信息一起传给视觉模块,两边用同一份元数据做换算。

换设备后匹配分数普遍降低,也常被忽视。某次换了一台手机,系统字体从默认改成特大,OCR文本位置偏移了30像素,最后发现是模板匹配尺度范围没覆盖到字体变大后的按钮,把scale上限调到1.4就解决了。所以多尺度参数的上下限,要根据实际设备池去调整,不要拍脑袋定死。

7.4 常见问题速查表

问题可能原因处理建议
OCR识别不出文字字号太小、对比度低放大2倍、提高对比度、局部识别
OCR识别成错字艺术字体、背景干扰维护同义词列表、换专用模型
模板匹配总匹配错位置模板带了背景、阈值过低重截干净模板、提高阈值
匹配分数普遍都很低目标尺度变了扩大scale范围、按新设备重新取样
点击坐标点偏截图分辨率与物理分辨率不一致统一用比例换算、读取真实分辨率
点击没反应但无报错动画未结束、目标被遮挡增加等待、加视觉校验重试

7.5 一套兜底策略

不管OCR还是模板匹配,视觉方案的精度都不可能到100%。真实项目里,我的做法是把视觉定位当作优化层:优先用框架自带的可访问性接口定位控件,定位不到再走视觉。这样既保留稳定,又覆盖那些无法用标准接口访问的图形按钮。覆盖率目标定在80%左右,而不是追求100%,这才是健康的工程预期。剩下的20%,要么是极端动态界面,要么是低概率场景,用人工或半自动处理更划算。

8. 经验小结与下一章预告

走到这里,视觉处理的整个技术闭环已经串起来了:截图、预处理、OCR识字、模板找图、坐标换算、点击接口封装、失败重试和校验,每一步都有坑,也都有对应的兜底手段。我自己最大的体会是:视觉自动化一开始能看到效果,但精度瓶颈往往不出在算法本身,而出现在“图像采集不干净”和“坐标换算不一致”这两个看起来很低级的地方。把截图规范和分辨率管理做好,识别率问题就解决了一半。

另外还想提醒一句,视觉定位不是越快越好。OCR和模板匹配都是CPU密集操作,如果case跑得又多又快,建议把识别结果缓存起来,页面没变化就直接复用之前的坐标,能省不少时间。下一章要进入设备控制层。视觉定位拿到了坐标,接下来就是怎么把点击真实地发送到设备上:Android的输入事件、PC端的鼠标模拟、滑动和长按的实现、多设备的统一调度。设备控制接好了,前面这套视觉识别才算真正落地。咱们下一回见。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 9:14:16

diagram-design:从草图到可维护图示资产的设计方法与实操

1. 从一张草图到一套系统&#xff1a;diagram-design 到底在解决什么问题第一次看到 diagram-design 这个标题&#xff0c;很多人会以为它只是某个画图工具的皮肤或者模板集合。但真正做过系统设计、技术方案评审、跨团队沟通的人会明白&#xff0c;图从来不是“画着好看”的装…

作者头像 李华
网站建设 2026/10/21 9:13:29

两级式光伏逆变器LVRT仿真模型与改进控制策略解析

搞光伏逆变器控制的同行&#xff0c;对LVRT&#xff08;低电压穿越&#xff09;这三个字母应该都不陌生。做过并网逆变器的人都有体会&#xff0c;电网电压跌落那几百毫秒&#xff0c;比满负荷运行一整天都揪心。最近整理了一套两级式光伏并网逆变器的LVRT仿真模型&#xff0c;…

作者头像 李华
网站建设 2026/10/21 9:13:23

性能测试面试必备:概念、高频题与调优实战

又是一年金三银四&#xff0c;团队里几个小年轻天天刷题准备跳槽&#xff0c;我发现他们最焦虑的不是算法&#xff0c;反而是性能测试这块。想想也正常&#xff0c;功能测试还能靠点业务积累糊弄&#xff0c;性能测试要是没真正压过几个项目、调过几个慢接口&#xff0c;面试官…

作者头像 李华
网站建设 2026/10/21 9:13:08

三菱QCPU与QD77MS16运动控制模块:大型设备多轴控制的经典组合

1. 为什么大型设备项目里&#xff0c;QCPU 和 QD77MS16 至今仍是经典组合做自动化这行十几年&#xff0c;接触过不少PLC平台。小设备用FX系列足够了&#xff0c;几百个点位、几个轴&#xff0c;跑起来也利索。但一旦进入几十个伺服轴、几百个模拟量信号、几十台从站设备协同动作…

作者头像 李华
网站建设 2026/10/21 9:13:00

养老机构智慧化改造实战:设备选型、成本测算与补贴申报

这几年做养老机构的人&#xff0c;应该都能感到一阵明显的风向变化。上个月我去一位做了多年养老的朋友那里走访&#xff0c;他一边泡茶一边跟我说了句话&#xff0c;我到现在还记得很清楚&#xff1a;“以前是我们求着老人来住&#xff0c;家属来了问的第一句是床位多少钱&…

作者头像 李华
网站建设 2026/10/21 9:12:46

石塑粘土立体摆件手作全流程:从揉泥调色到阴干上色

最近手头给自己定了一个小项目&#xff0c;代号叫 artcraft。说白了&#xff0c;就是尝试把"艺术"和"手工"这两件事真正揉在一起&#xff1a;不满足于仅仅做出一个"像样"的东西&#xff0c;而是从构图、色彩、质感、结构都按照作品的逻辑去推敲&…

作者头像 李华