屏幕上那行状态文字死活找不到,换了三个版本的字库还是不行——这种经历,我给客户做界面自动化录入工具的时候没少碰到。后来把大漠插件的找字函数从基础用法到参数原理彻底吃了一遍,再回头看当时的报错和误识别,几乎都是对参数的理解出了问题,而不是插件本身不行。这次就把我一直在用的FindStrE系列的经验完整梳理出来,从环境准备到参数拆解再到实际项目里的排错,力求让看完的人能直接上手,少走几个月弯路。
FindStrE是易语言环境下调用大漠插件做屏幕文字定位的核心函数组,简单说就是"在屏幕指定区域内,按你训练好的字库去找指定的字符串,返回坐标"。它比传统找图多了一个维度:找图是像素级匹配,对缩放、变色、遮挡非常敏感;找字是基于字形特征和颜色特征匹配,只要字体和颜色在偏色范围内,它就能认出来。这个特点决定了它特别适合做三件事:软件界面上的状态文字识别、固定报表里的关键字段定位、以及各类自动化测试脚本里的"按文字找控件"。
我默认看这篇文章的人,要么是刚接触易语言和大漠插件的新手,要么是已经在用但总被参数坑到的进阶用户。下面我不会只贴一段能跑的代码就完事,而是把调用链路上每个环节都拆开讲清楚。
1. FindStrE是干什么的:为什么找字比找图更省心
1.1 找字和找图的本质区别
很多人刚开始做屏幕自动化,脑子里第一个想法就是找图:把目标截图存下来,然后在大屏上做模板匹配。找图在图标、按钮这类"形状固定"的元素上很好用,可一旦遇到文字就头疼。同样的一个"销"字,不同字号、不同分辨率、不同渲染模式,截出来的图差距很大,模板匹配很容易失灵。更别提文字旁边还有背景色干扰、阴影效果这类情况,找图基本就是撞运气。
找字的思路完全不一样。它的机制分两层:第一层是图像层面的颜色过滤,先把你指定的区域里符合"主色-偏色"范围的点全部保留下来,其它颜色全部屏蔽掉;第二层是字形层面的特征比对,在过滤后的二值图像里,去和字库里保存的字符特征做匹配。所以它不要求背景像素一模一样,只要文字本身的颜色和近似度落在范围内,就能稳定识别。这个原理决定了找字在动态背景、系统缩放、轻微变色这些场景里,比找图可靠得多。
1.2 E后缀到底多了什么能力
大漠插件里,找字函数有一整个家族:FindStr、FindStrE、FindStrEx、FindStrExE、FindStrFast、FindStrFastE……新手最容易被后缀搞晕。按我的理解,命名规则其实很规律:
- 不带后缀的 FindStr:只返回第一个匹配到的坐标。
- 带 Ex 的 FindStrEx:会返回所有匹配到的坐标,格式是一串"x1,y1|x2,y2|..."。
- 带 Fast 的 FindStrFast:走更精简的匹配算法,速度更快,但对字库制作质量要求更高。
- 带 E 后缀的 FindStrE:多了一个字库名(font_name)参数,可以在一次调用里显式指定用哪个字库去识别,而不是依赖 SetDict 设置的默认字库。
E 后缀的价值在于字库切换的灵活性。真实项目里,一台机器上可能要识别多个软件的界面,不同软件的字体风格差异很大,你不可能把所有字符塞进同一个字库去匹配。有了 E 系列,你就可以为每个场景准备独立字库,调用时指定,互不干扰。后面我演示的 Demo 就是用 FindStrE 来实现的。
2. 环境准备:把大漠插件稳定地用起来
2.1 注册与全局初始化
大漠插件是一个 COM 组件,常规使用前需要先把它注册进系统。一般步骤是:把插件文件放到一个固定目录(比如 C:\dm),然后以管理员身份打开命令行,执行注册命令。注册完以后,在易语言里就可以通过创建对象的方式来调用。
有个容易忽略的坑:64 位系统下注册 32 位 COM 组件,路径可能和你预想不一样。如果你用的是 32 位插件版本,在 64 位系统上注册时要确认组件位数和易语言编译出来的程序位数一致。最稳妥的做法是让整个项目统一在 32 位模式下开发和运行,兼容性最好。
注册完成后,我建议在程序启动时做一次版本检查,避免用户机器上一份旧版插件导致接口对不上。通用初始化代码思路如下:
.版本 2 .支持库 spec .程序集 窗口程序集_启动窗口 .变量 dm, 对象 .子程序 _启动窗口_创建完毕 dm.创建 (“dm.dmsoft”, ) 输出调试文本 (“插件版本: ” + 到文本 (dm.数值方法 (“Ver”, )))这里用到了易语言的对象类型调用 COM 方法。如果调用失败,返回值为空或者直接抛异常,优先检查插件是否注册成功、是否被杀毒软件拦截、以及程序位数是否匹配。
2.2 字库文件:找字的弹药库
找字的前提是有对应的字库文件。字库是大漠的字库工具生成的 txt 文件,里面保存的是你已经标记过的字符特征数据。制作字库的流程通常是:
- 打开大漠配套的字库工具,截图目标软件的界面。
- 框选一个字,设定偏色范围,然后在工具里把它标记成对应字符。
- 一个字一个字地积累,最后导出成字库文件。
这个过程看起来很朴素,但它直接决定找字成功率。我见过太多人字库做得很随意,框选时把文字旁边的干扰点也圈进去了,导出后发现字体稍微一变就识别失败。标准做法是:一个字多做几个样本,把正常态、鼠标悬停态、选中态的颜色变化都收进字库或者偏色范围里,这样才有冗余度。
字库文件做好之后,要集中放在程序能访问到的目录,并且在初始化的设路径(SetPath)里指向它。路径里尽量别带中文,少部分环境对中文路径的兼容性不好,你排查半天还以为是代码问题。
2.3 在易语言里封装一层
直接在窗口代码里到处写“dm.数值方法(…)”,代码很快会乱成一团。我的习惯是做一个专门的大漠封装模块,把初始化、找字、找色、回收释放都包一层,项目里其它代码只调用中文子程序,这样就算后期换了一个插件,也只需要改封装层。
封装层至少要包含这几个子程序:
- 初始化大漠:注册判断、设置路径、加载字库。
- 找字单次:封装 FindStrE,把区域、字符串、颜色、偏色、字库名、相似度收成参数。
- 找字全部:封装 FindStrExE,返回坐标集合,方便需要遍历多个结果时使用。
- 释放对象:程序退出时调用,避免 COM 对象残留。
关键点在于把默认参数集中管理。比如相似度、偏色值这类全局配置,在封装层定义成常量或者程序集变量,不同调用点保持一致,排查问题时只需要改一处。
3. 参数逐个拆解:理解参数才算真正会用
3.1 查找区域与待查字符串
FindStrE 的前四个参数是查找区域的左上角和右下角坐标。这个区域越小,扫描的像素点越少,速度越快。很多人图省事,直接全屏找,结果性能慢、误识别率还高。正确做法是先通过窗口句柄拿到窗口在屏幕上的位置和大小,再在这个基础上缩小查找范围。
第五个参数是待查找的字符串。这里有几个细节:
- 字符串可以是多个字,比如"已完成",函数内部按字库里的字符顺序去匹配。
- 字符串里不要带空格,尤其是全角空格。很多文本看起来是空格,其实是排版产生的空白,加入待查字符串后会造成匹配失败。
- 如果待查文字里包含特殊符号,尽量单独验证这个字符是否成功做进了字库,不要假设它一定在。
3.2 偏色和相似度的配合
颜色参数一般写法是"主色-偏色",比如"9F9F9F-202020",意思是在 RGB 三个通道上允许 0x20 的浮动范围。理解它的作用非常重要:屏幕上几乎没有纯色文字,系统字体渲染有抗锯齿,边缘像素的颜色是渐变的。如果偏色设得太小,边缘像素全被过滤掉,剩下的是"骷髅字",字库匹配必然失败;偏色设得太大,背景里的近似颜色也会混进来,形成一堆噪点。
相似度参数范围是 0 到 1,表示字形匹配的允差。我建议从 0.9 开始调:太高(接近 1.0)要求过于严格,稍微一点抗锯齿差异就识别不到;太低又会把形状相近的字符认错。等到调参阶段,可以把相似度先降到 0.8 验证字符集是否完整,确认没问题后再升回 0.9 以上,这个顺序可以快速区分是字库问题还是阈值问题。
3.3 字库名与返回坐标
FindStrE 比基础版多出来的就是字库名参数。它的作用是告诉插件:这一次查找请你用叫"default"的这个字库来匹配。所以初始化时不仅要 SetPath,还要把对应的字库文件用字库加载接口导进来,并且给每个字库一个唯一名称,调用时名称对不上会直接返回查找失败。
返回坐标是两个整数型变量参数,采用引用传递。函数返回值是整数型,返回 1 表示找到,0 表示没找到。找到时 x 和 y 会被写入匹配位置的左上角坐标,这个坐标是屏幕绝对坐标,不是相对于查找区域的偏移。我在早期项目里犯过错,拿返回坐标去和窗口客户区坐标做减法,结果算出来的位置偏移整条工具栏的高度,后来才发现接口返回的就是屏幕绝对坐标。
4. 做一个完整的找字识别Demo
4.1 场景与思路
我拿一个真实做过的场景举例:某内部工单系统,界面是网页套壳,工单列表里每行有个状态栏,取值是"待处理""处理中""已完成"。我需要写一个自动化脚本,每天定时截取列表区域,把每行的状态识别出来,回填到另一个统计表里。这个需求用 FindStrE 特别合适,因为状态文字颜色是系统固定的,字体也是固定的,字库只做这几个词就行。
思路分三步:先获取窗口位置和大小;再锁定状态栏所在的列表区域;最后在区域内查找对应字符串,记录坐标和信息。
4.2 代码实现
核心代码简化后大概长这样:
.版本 2 .支持库 spec .程序集 窗口程序集_启动窗口 .变量 dm, 对象 .子程序 初始化环境 dm.创建 (“dm.dmsoft”, ) dm.数值方法 (“SetPath”, , “C:\dm_dict”) dm.数值方法 (“SetDict”, , 0, “status_dict.txt”) dm.数值方法 (“LoadDict”, , 0) .子程序 查找状态文字 .参数 查找区域左, 整数型 .参数 查找区域顶, 整数型 .参数 查找区域右, 整数型 .参数 查找区域底, 整数型 .局部变量 x, 整数型 .局部变量 y, 整数型 .局部变量 结果, 整数型 结果 = dm.数值方法 (“FindStrE”, , 查找区域左, 查找区域顶, 查找区域右, 查找区域底, “处理中”, “9F9F9F-202020”, 0.9, 0, x, y) 如果真 (结果 = 1) 输出调试文本 (“找到处理中,坐标: ” + 到文本 (x) + “,” + 到文本 (y)) 否则 输出调试文本 (“未找到”) 如果结束这套代码在封装层之下属于最简版本,真实项目里至少还要加一个容错:如果返回 0,可以把相似度调低 0.05 再试一次,大多数时候是抗锯齿导致的边缘差异。如果调低相似度后找到了,说明字库样本做得还不够全,回头去补字典。
4.3 跑通后的验证
跑通一次只是起点,我建议做一组验证用例:把界面上几种状态都截图存好,脚本自动切换状态后调用找字,断言返回值和坐标是否符合预期。这一步很多人忽略,以为当时能识别就万事大吉。实际经验是,换个显示器、改个缩放比例、或者系统主题从浅色切到深色,之前的参数可能全部失效。验证用例能让你发现"原来这个颜色其实是主题控制的",而不是纯粹的字库问题。
我自己的做法是做一个简单的调试窗口,实时显示查找区域、字库加载状态、最近一次找字的返回值。这样调参数的时候不用反复改代码编译,直接在调试器里能看到结果,效率高非常多。
5. 实战中踩过的坑:排查链路与解决
5.1 字符识别偏移或返回坐标不准
最典型的症状是:返回值是 1,坐标也拿到了,但鼠标按过去却点到了别的地方。这通常不是找字函数的问题,而是查找区域坐标系和鼠标移动坐标系不一致。窗口缩放、DPI 缩放、窗口在屏幕上的位置变化,都会造成这个偏差。
排查链路参考:
- 先确认返回的坐标是不是屏幕绝对坐标。用一个调试工具在屏幕上显示当前鼠标位置,和返回坐标对比。
- 确认窗口有没有发生移动或缩放。如果窗口在屏幕上的位置变了,旧坐标自然失效。
- 确认是否开启系统 DPI 缩放。高 DPI 下,系统会对程序和屏幕做坐标换算,需要在程序里声明 DPI 感知,否则易语言拿到的窗口坐标和屏幕真实坐标不一致。
- 最后再怀疑字库本身。字库的锚点位置偏移会导致返回坐标偏了几个像素,但不会差出整个控件区域那么多。
5.2 后台窗口找字失败
前台找字没问题,一旦把窗口最小化或者遮挡到后面,就找到不到了。原因是正常的找字走的是屏幕绘制结果,窗口被遮挡后,目标区域的画面已经不是你要的内容。这时候需要走后台绑定模式:通过背景接口把目标窗口绑定到插件上,让它读取窗口内部图像,而不是读屏幕。
绑定窗口是有前置条件的:目标窗口的渲染方式必须支持后台取图。网页套壳、自绘界面等相对容易;有些走硬件加速渲染的窗口,后台取图拿到的是黑屏,那就没辙。遇到这种情况,我会先试普通绑定模式,再试开启 gdi 模式,确认哪种模式能稳定取图,再继续做找字。绑定成功以后,记得所有找字操作都要在绑定上下文中执行,否则返回的坐标还是屏幕坐标系,容易混。
必须说一句,后台绑定这套能力只应该在你自己有权控制的软件、自动化测试环境、或者明确允许自动化的工具里使用。任何未经授权去操作别人软件的行为都违反用户协议,尤其是游戏中,后果不是技术问题而是账号和合规问题。
5.3 常见报错与对策
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 调用即返回 0 | 字库没加载成功 | 检查 SetPath 和 SetDict 路径、是否有中文路径 |
| 返回 0,调试时用同类字能识别 | 待查字符串里的字符不在字库 | 用字库工具逐个确认字符是否录入 |
| 识别到但坐标全部偏右上角 | 相似度和偏色太宽松,匹配到边缘点 | 收紧偏色范围,提高相似度到 0.92 以上 |
| 偶尔把相似字认错 | 字库存量字符特征太接近 | 补充正负面样本,重新生成字库 |
| 绑定模式取图黑屏 | 窗口走硬件加速渲染 | 尝试不同绑定模式,或放弃后台方式 |
6. 进阶:性能优化和函数变体怎么选
6.1 区域裁剪是最大的性能杠杆
找字速度和扫描面积直接相关。一个 1920×1080 的屏幕,全屏扫描要比 300×200 区域慢好几倍。所以优化第一步不是换 Fast 函数,而是把查找范围压缩到合理最小。如果窗口位置不固定,可以先通过窗口句柄拿到客户区范围,再在客户区内指定一个相对偏移区域,这比写死坐标健壮得多。
另一个技巧是两级预筛选。先用大色块的找色函数探测目标区域是否出现主色,如果主色都没出现,那就不用调 FindStrE 了,直接跳过。等主色出现后再执行找字,可以把识别频率降下来,CPU 占用明显下降。这种"先色后字"是我做定时轮询任务的标准姿势。
6.2 FindStr、FindStrEx、FindStrFast怎么选
| 函数 | 特点 | 适用场景 |
|---|---|---|
| FindStr | 只返回第一个匹配坐标,开销低 | 界面里只有一个目标文本 |
| FindStrE | 指定字库名,多个字库切换方便 | 多界面、多字体项目 |
| FindStrEx | 返回所有匹配坐标,适合遍历 | 列表里有多行同状态文字 |
| FindStrFast | 匹配速度快,但误识别率略高 | 大区域内高频查询 |
| FindStrExE | Ex 和 E 的组合,指定字库且返回全部 | 复杂场景首选 |
我个人常用组合是:列表类需求用 FindStrExE,界面控件定位用 FindStrE,全屏扫描时改用 FindStrFastE 配合作区域预判。Fast 系并不是任何时候都更快,它适合在字符特征非常稳定的字库上使用,如果字库本身质量一般,Fast 反而容易产生大量误报,得不偿失。
6.3 什么时候可以换OCR
大漠找字再强,也是基于你手工制作的字库,遇到字体多变、字号变化、艺术字渲染等场景,字库维护成本会爆炸。这时候就应该考虑系统级 OCR 方案。现代系统自带 OCR 接口,对印刷体文字的识别能力远强于像素匹配,而且不需要预建字库。我通常的判断标准是:
- 目标软件字体固定、颜色固定、区域固定:继续用 FindStrE 系列,维护成本最低。
- 目标内容来自网页动态渲染、字体库经常变:直接上 OCR 接口,省去大量做字库的工时。
- 两种结合的方案:先用 FindStrE 定位控件框架,再用 OCR 识别动态文本内容,各取所长。
我在实际项目里最后落地的往往是第三种。稳定性要求高的核心路径用字库找字,不做 OCR 的灰度识别,避免把错误数据写进统计表。
最后再分享一个小技巧:调试找字的时候,不要只盯着返回值。把查找区域、字库名、偏色值、相似度、返回坐标这些信息全部输出到调试窗,然后人为改变字体颜色、背景色、窗口位置,观察结果变化,很快就能找出你的字库和参数哪一环是薄弱点。字库是积累出来的,不可能一次做好,保持"小步迭代,实时验证"的习惯,比任何技巧都管用。