FindText函数实战课程:找图神器亮剑

  • 2024年4月30日第1次修订——河许人
  • 2024年5月11日第2次修订——河许人
  • 2024年5月22日第3次修订——河许人
  • 2024年5月24日第4次修订——河许人

一、课程简介


图像查找是当今办公、学习、游戏的重要技能之一,它在各个领域都有着广泛的应用,包括计算机视觉、自动化测试、文档处理等。本课程将带领你从零开始,系统学习图像查找的基础知识到实战技巧,让你轻松掌握图像查找的核心概念和技能。

课程特色:

  • 系统性学习:本课程将分为五个部分,从简介到实际案例,循序渐进地带你掌握图像查找的各个方面。
  • 实战案例:通过丰富的实际案例,让你学会如何将图像查找技术应用到实际问题中,提升解决问题的能力。
  • 重点概念:课程将重点讲解图像查找的关键概念和算法原理,帮助你建立深入的理解。
  • 包装函数:学习如何将图像查找功能封装成易于重复使用的函数,提高工作效率。

课程大纲:

  1. 课程简介:介绍课程的安排,图像查找的基本概念和应用场景。
  2. 快速入门:学习如何使用 findtext 函数进行基本的图像查找。
  3. 关键概念:深入研究使用 findtext图像查找的关键函数和技巧。
  4. 包装函数:全面讲解findtext的包装函数。
  5. 实际案例:通过实际案例深化对图像查找技术的理解,并提升解决问题的能力(穿插在全部课程中)。

适合对象:

  • 对图像处理和计算机视觉感兴趣的爱好者。
  • 希望学习图像查找技术并将其应用到实际工作中的人士。
  • 想要提升自己解决问题能力的任何人。

二、快速入门

基础知识:

学习Findtext函数之前,你需要一定的基础知识:

  • 熟悉AutoHotkey的基础语法
  • 颜色的基本概念和原理
  • 计算机屏幕的基本原理
  • 找色找图的基本概念和实现原理

Findtext是啥?

FindText是一个AHK函数库,包括适用于AutoHotkey v1.1和AutoHotkey v2.0的两个版本,该函数库可用于替代内置的ImageSearch功能,并具备多项附加功能。其主要应用于屏幕图像的快速查找,其匹配精度高于ImageSearch。它通过将屏幕上抓取的小图像转换为黑白图像(类似于ASCII艺术),然后将其转换为单行文本来实现匹配。在使用FindText时,它会截取屏幕截图,并将每个像素转换为黑白,然后尝试将抓取的图像与屏幕截图进行匹配。由于像素被转换为黑白,需要比较的次数大大减少(红、绿、蓝需要比较三次,而黑白只需要比较一次),因此匹配速度比ImageSearch更快。

FindText的优点:

  1. 不需要图像文件,而是使用图像的文本表示。该功能将屏幕图像抽象为具有代表性的“0”(“0”=黑色像素=文本像素)和“_”(“_”=白色像素=背景像素),它们依次排列成一行数字和字母序列。由于这种抽象,而不是逐位比较,因此可以实现快速匹配和轻松调整容错。
  2. 比ImageSearch更快,在我的测试中大约提高了10%的速度(在某些用例中甚至可能提高2-3倍)。
  3. 提供了轻松存储和在脚本中获取图像的函数。
  4. 使用BindWindow功能,即使窗口被其他窗口遮挡,也能使用FindText。
  5. 允许创建简单的自定义OCR函数。

下载地址:

你下载的版本可能是txt版本的,这时候你需要把文件后缀名修改为ahk。

页面介绍

运行 FindText.ahk 脚本,运行后应该会显示出以下 GUI :

FindText函数实战课程:找图神器亮剑

入门核心步骤:

按 抓图 按钮 -> 选择要抓取的区域 -> 右键单击两次(第一次右击选择,将鼠标移动到其他位置后再次右击以确认)-> 按灰度阈值二值化按钮 -> 按 确定。如果一切顺利,那么现在按下 测试 按钮后,FindText 应该会成功找到之前抓取的图像(找到图像的位置会出现一个闪烁的红色框)。

FindText函数实战课程:找图神器亮剑

抓取图像后,在下方的文本框会显示一些自动生成的代码,如下所示:

Text:="|<>*151$101.000000000000000000000000U0000M0000000000100000k0000000400200011U0000000A0040002300000000M008000460000DVUnw7kHk7kzAA7X0vX1XUtkxktksMkvW036331UlVVUkUn11a02A6661X161V1g63A1wMAAA343A323kDy8DMkMMM286M247UM0NkFUkkkAEAkA8BUk0nVX1VVUMUNUMEPVU0b33331UV0lUUUnXU1zS7S63b21Xb1dX3a1bY7g71s431s1n33s300000000000000006000000000000000080000000000000000k00000000000000034"
if (ok:=FindText(X, Y, 312-150000, 79-150000, 312+150000, 79+150000, 0, 0, Text))
{  
; FindText().Click(X, Y, "L")
}...

• Text 变量包含文本/字符串形式的图像,以及一些其他必要信息。

• FindText(X, Y, 312-150000, 79-150000, 312+150000, 79+150000, 0, 0, Text) 表示变量 X 和 Y 将被赋值为找到的第一个图像的确切中心的坐标;数字 312 和 79 是最初抓取图像的 x 和 y 坐标,312-150000 和 79-150000 只是搜索区域左上角的一些非常负的 x 和 y 坐标(包括屏幕外的窗口或第二个显示器)

;312+150000 和 79+150000 是搜索区域右下角的一些非常大的值;0 和 0 是默认误差范围(表示只允许完全匹配)

;Text 变量是我们截取图像的文本形式,提供给 FindText 使用。

• 如果删除 FindText().Click 之前的注释符号(;),则表示在 FindText 成功后,使用左键单击找到的图像。

三、关键概念

(一)包含头文件——移植

在上面的链接中下载的版本是txt文件,请修改后缀名为.ahk,修改后为FindText.ahk ,建议将它放在 Lib 文件夹中,然后在自己的脚本中通过以下代码来引入它:

#include <FindText>
#include findtext.ahk ;如果在同名文件夹下可以这样引用
#include 路径\findtext.ahk ;非同名文件夹这样引用

做完这些后,你可以使用 FindText().ImageSearch 来替换掉ahk自带的 ImageSearch函数,从而提升速度,如下所示:

ImageSearch, OutputVarX, OutputVarY, X1, Y1, X2, Y2, ImageFile ; 变成这样FindText().ImageSearch(OutputVarX , OutputVarY, X1, Y1, X2, Y2, ImageFile)

接下来的部分将包含一些示例脚本,可以把它们复制粘贴到 FindText main Gui 的示例代码框顶部,然后按 测试 按钮运行这些脚本。将以下图像保存到计算机,并使用 Paint 或其他看图软件打开。在按下 测试 之前确保图片可见,屏幕 DPI 设置为 100%。

(二)FindText 主函数

下面的大部分内容摘自 FindText.ahk 顶部描述区(作了一些补充):

;--------------------------------
;  FindText - 屏幕找字函数
;  版本 : 9.6  (2024-05-25)
;--------------------------------
;  返回变量:=FindText(
;      OutputX --> 保存返回的X坐标的变量名称
;    , OutputY --> 保存返回的Y坐标的变量名称
;    , X1 --> 查找范围的左上角X坐标
;    , Y1 --> 查找范围的左上角Y坐标
;    , X2 --> 查找范围的右下角X坐标
;    , Y2 --> 查找范围的右下角Y坐标
;    , err1 --> 文字的黑点容错百分率(0.1=10%)
;    , err0 --> 背景的白点容错百分率(0.1=10%)
;    , Text --> 由工具生成的查找图像的数据,可以一次查找多个,用“|”分隔
;    , ScreenShot --> 是否截屏,为0则使用上一次的截屏数据
;    , FindAll --> 是否搜索所有位置,为0则找到一个位置就返回
;    , JoinText --> 如果想组合查找,可以为1,或者是要查找单词的数组
;    , offsetX --> 组合图像的每个字和前一个字的最大横向间隔
;    , offsetY --> 组合图像的每个字和前一个字的最大高低间隔
;    , dir --> 查找的方向,有上、下、左、右、中心9种
;    1 ==> ( 从左到右 ) 从上到下
;    2 ==> ( 从右到左 ) 从上到下
;    3 ==>(从左到右)从下到上
;    4 ==>(从右到左)从下到上
;    5 ==>(从上到下)从左到右
;    6 ==>(从下到上)从左到右
;    7 ==>(从上到下)从右到左
;    8 ==>(从下到上)从右到左
;    9 ==>从中心向外
;    , zoomW --> 图像宽度的缩放百分率(1.0=100%)
;    , zoomH --> 图像高度的缩放百分率(1.0=100%)
;  )
;
;  返回变量 --> 如果没找到结果会返回0。否则返回一个二级数组,
;      第一级是每个结果对象,第二级是结果对象的具体信息对象:
;      { 1:左上角X, 2:左上角Y, 3:图像宽度W, 4:图像高度H
;        , x:中心点X, y:中心点Y, id:图像识别文本 }
;  坐标都是相对于屏幕,颜色使用RGB格式
;
;  如果 OutputX 等于 'wait' 或 'wait1' 意味着等待图像出现,
;  如果 OutputX 等于 'wait0' 意味着等待图像消失
;  此时 OutputY 设置等待时间的秒数,如果小于0则无限等待
;  如果超时则返回0,意味着失败,如果等待图像出现成功,则返回位置数组
;  如果等待图像消失成功,则返回 1(注意这里的等待功能仅适用于静态图像)
;  例1: FindText(X:='wait', Y:=3, 0,0,0,0,0,0,Text)   ; 等待3秒等图像出现
;  例2: FindText(X:='wait0', Y:=-1, 0,0,0,0,0,0,Text) ; 无限等待等图像消失
;
;  <FindMultiColor> 或 <FindColor> : 找色 是仅有一个点的 多点找色
;  Text:='|<>##DRDGDB $ 0/0/RRGGBB1-DRDGDB1/RRGGBB2, xn/yn/-RRGGBB3/RRGGBB4, ...'
;  '##'之后的颜色 (0xDRDGDB) 是所有颜色的默认偏色(各个分量允许的变化值)
;  初始点 (0,0) 匹配 0xRRGGBB1(+/-0xDRDGDB1) 或者 0xRRGGBB2(+/-0xDRDGDB),
;  点 (xn,yn) 匹配 排除 0xRRGGBB3(+/-0xDRDGDB) 和排除 0xRRGGBB4(+/-0xDRDGDB)
;  点坐标后面以 '-' 开头表示要排除后面的所有颜色,其他颜色都匹配
;  每个点最多允许匹配10组颜色 (xn/yn/RRGGBB1/.../RRGGBB10)
;
;  <FindPic> : Text 参数需要手动输入
;  Text:='|<>##DRDGDB-RRGGBB1-RRGGBB2... $ d:\a.bmp'
;  0xRRGGBB1(+/-0xDRDGDB)... 都是透明色,不参与匹配
;
;--------------------------------

该函数返回一个包含所有查找结果的二阶数组(returnArray[1] 表示找到的第一个结果,returnArray[2] 表示第二个,…),每个结果都是一个关联数组:{1:X, 2:Y, 3:W, 4:H, x:X+W/2, y:Y+H/2, id:Comment}

如果没有找到图像,则 FindText 返回 0。

例如,返回变量的名称是 “ok”, 则 ok[1] 表示找到的第一个结果。

ok[1][1] 和 ok[1][2] 是找到的第一个图像左上角的 X 和 Y 坐标,
ok[1][3] 是找到图像的宽度,而 ok[1][4]是找到图像的高度,
ok[1].x <==> ok[1][1]+ok[1][3]/2 <==> OutputX(找到图像中心的 X 坐标),
ok[1].y <==> ok[1][2]+ok[1][4]/2 <==> OutputY(找到图像中心的 Y坐标 ),
ok[1].id 是包含在 Text 中 <> 部分的注释文本。

如果 OutputX 设置为“wait”、“wait1”(等待“出现”)或“wait0”(等待“消失”),出现超时则意味着搜索失败并返回 0,其他值表示成功。如果等待“出现”并且找到图像,则 FindText 返回找到的数组对象。如果等待“消失”且找不到图像,则 FindText 返回 1。

FindText(X:="wait", Y:=3, 0,0,0,0,0,0,Text)   ; 等待 3s 看是否出现
FindText(X:="wait0", Y:=-1, 0,0,0,0,0,0,Text) ; 一直等待到消失

更多注意事项:

• 所有返回的坐标都是相对于屏幕而言的绝对坐标(就像是设置了 CoordMode Screen 模式一样)。使用 ScreenToWindow 和 ScreenToClient,可以转换为相对坐标。

• 所有颜色均为十六进制 RGB 格式。

• FindText 对屏幕 DPI 敏感,这意味着使用一种 DPI 抓取的图像/文本无法在另一种 DPI 下使用。为确保用户使用正确的 DPI,请预先使用内置变量 A_ScreenDPI 进行检查。

• 使用多台显示器时,请注意将 X1,Y1,X2,Y2 设为 0,0,A_ScreenWidth,A_ScreenHeight 这一情形,因为此设置只会抓取主显示器。建议将它们留空(默认值 -150000,-150000,150000,150000 应该足够大,可以覆盖所有显示器),或者使用与默认值类似的大值。

对于以下示例,请使用 画图软件 打开第 1 节提供的 Examples.png。然后在新脚本中运行代码,或者将 #include 行以外的所有代码复制到 FindText Gui 代码框,然后按 测试 按钮进行测试。

#include <FindText>
Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" ; 图片中 "autohotkey.com" 的 "auto" 部分。图像 Id 为 "auto" (位于 < 和 > 之间的字符)。
ok := FindText(outX, outY,,,,,,,Text) ; 调用 FindText 搜索 "auto" 图像。outX 和 outY 将被设置为最先找到结果的 X 和 Y 坐标。搜索范围坐标、err1 和 err0 都留空以使用默认值(搜索整个屏幕,并使用完全匹配)。结果会保存在 "ok" 变量中。
if ok { ; 检查 "ok" 是否为 0
    MsgBox, 找到的第一个图像(文本)位置:X: %outX% Y: %outY% ; 显示坐标 outX 和 outY
    MsgBox, % "共找到" ok.MaxIndex() "个结果。" ; ok.MaxIndex() 和 ok.Length() 返回一共找到几个结果。
    MsgBox, % "找到的第一个图像位于 X" ok[1][1] " Y" ok[1][2] ". 图像宽度为 " ok[1][3] " 图像高度为 " ok[1][4] ",其 id 为" ok[1].id
    if ok[2] ; ok[1] 表示第一个结果, ok[2] 表示第二个结果,依此类推。检查是否存在第二个结果,如果存在,则显示一些内容。
        MsgBox, % "找到的第二个图像位于 X" ok[2][1] " Y" ok[2][2] " 图像宽度为 " ok[2][3] " 图像宽度为 " ok[2][4] ",其注释文本为" ok[2].id
} else {
    MsgBox, 图像/文本未找到。请确认所有设置无误,且图像在 Paint 中可见! ; "ok" 变量中结果为空,表示未找到任何结果。
}
#include <FindText>
if (ok := FindText(outX, outY, 0, 0, A_ScreenWidth, A_ScreenHeight, 0.05, 0.05, "|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8")) { ; 调用 FindText 搜索 "auto" 图像。outX 和 outY 将被设置为最先找到结果的 X 和 Y 坐标。搜索时会覆盖整个屏幕,从左上角 (0;0) 到右下角 (A_ScreenWidth; A_ScreenHeight),多个显示器时可能不生效。"1" 和 "0" 的误差范围都设为 5%。搜索结果保存在 "ok" 变量,如果 "ok" 包含了任何结果(即搜索成功),则 "if" 的条件为真。
    for k, v in ok { ; 遍历 "ok" 中的所有搜索结果. "k" 表示第几个结果,"v" 表示结果本身。
        MsgBox, % "第" k " 个结果:X坐标:" v[1] " Y坐标:" v[2] " 图像宽度:" v[3] " 图像高度:" v[4] "。另外,图像的注释文本为" v.id ; v[1] 等价于 ok[k][1], v.id 等价于 ok[k].id,依此类推。
    }
} else {
    MsgBox, 图像/文本未找到。请确认所有设置无误,且图像在 Paint 中可见! ; "ok" 变量中结果为空,表示未找到任何结果。
}
#include <FindText>
Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" ; 图片中 "autohotkey.com" 的 "auto" 部分。图像 Id 为 "auto" (位于 < 和 > 之间的字符)。
Text.="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s" ; 将图片"autohotkey.com"中的"hot"部分附加到 Text 变量(注意使用 ".=" 运算符进行附加,之前使用的是 ":=" 进行赋值)。图像 Id 为 "hot"。
; 上两行代码等同于 Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s"
WinGetPos, pX, pY, pW, pH, ahk_class MSPaintApp ; 获取画图软件窗口的位置和大小
if !(ok := FindText(X, Y, pX, pY, pX+pW, pY+pH, 0.000001,, Text)) { ; 调用 FindText 查找 "auto" 或 "hot" 图像。X 和 Y 变量将设置为第一个找到图像的 X 和 Y 坐标。搜索范围限定为画图应用,将至少一个误差范围的值设为一个很小的非零值,以避免使用 5% 作为误差范围进行第二次查找。结果会保存在 "ok" 变量中。如果  "ok" 为空("!" 为取反运算符),则退出,否则继续。
    MsgBox, 图像/文本未找到。请确认所有设置无误,且图像在 Paint 中可见! ; "ok" 变量中结果为空,表示未找到任何结果。
    ExitApp
}
; 只有在找到 "hot" 或 "auto" 图像时,才会执行下面这部分内容。
for key, value in ok { ; 遍历 "ok" 中的所有结果。"key" 表示第几个结果,"value" 表示结果本身。
    FindText().MouseTip(value.x, value.y) ; 在结果的中心位置显示一个闪烁的框。
    MsgBox, % "第" key "个结果位于 X" value[1] " Y" value[2] " 图像宽度为" value[3] "图像高度为" value[4] ",其注释文本为" value.id ; value[1] 等价于 ok[k][1], value.id 等价于 ok[k].id, 依此类推。
    if (value.id == "auto")
        MsgBox, 找到 "auto" 图像。
}

(三)什么是 Text 文本?

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" 
; 按从左到右顺序:"|" 是分隔符
; "<auto>" 表示 Text 的 id/comment 为 "auto"
; "*" 是捕获模式,本例中为 Gray
; 159 是灰度阈值
; 40 (between $ and .) 是原图像的宽度
; 0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8 是图像的 64-bit 表示

Text 文本字符串由 5 部分组成:

1.竖线字符(|)是图像的分隔符。这意味着可以将多个图像一个接一个地连接在一起,进行同时搜索(或用于组合搜索)。

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" ; 首先设置 Text 变量包含 "auto" 图像
Text.="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s" ; 使用 ".=" 运算符向 Text 变量添加 "hot" 图像
ok:=FindText(X,Y,,,,,,,Text)
for k, v in ok
    FindText().MouseTip(v.x, v.y)

等价于以下代码:

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s"
ok:=FindText(X,Y,,,,,,,Text)
for k, v in ok
    FindText().MouseTip(v.x, v.y)

将返回匹配“auto”和“hot”图像的所有结果。

2. <> 字符之间是图像的“id”或“comment”,用于为图像命名。如果 ok:=FindText(…) 成功,则 ok[1].id 将包含找到的第一个图像的 id。id 可以包含任何字母、数字和符号(如果使用 AHK 的 Unicode 版本,则可包括所有 Unicode 字符),”>” 和换行符除外。

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" ; "<auto>" 设置图像 id 为 "auto".
Text.="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s" ; "<hot>" 设置图像 id 为"hot".
ok:=FindText(X,Y,,,,,,,Text)
for k, v in ok { ; 遍历所有结果
    if v.id == "hot" { ; 只显示结果中 id 为"hot"的图像,忽略 id 为 "auto" 的结果
MsgBox, % "The ""hot"" image was found at X:" v.x " Y:" v.y
    }
}

3. > 和 $ 之间的数字和符号将定义用于搜索的模式。有 5 种不同的模式,我们稍后将在“抓取模式”部分中介绍。在“auto”图像的这些示例中,* 表示正在使用 Gray 灰度阈值模式,154 是阈值。

4. $ 和 . 之间的数字是图像的像素宽度。这个数值不应该手动更改,应使用 Cut 按钮进行修改。在第一个示例中,图像“auto”的宽度为 40 像素。

5. 句点(.)之后的所有内容是图像的 64 位文本表示。这里的内容也不应该手动更改,应使用 Cut 按钮进行修改。

6. 有一种在 Text 中包含 err1 和 err0 的非正式方式,它将覆盖传递给 FindText 函数的 err1 和 err0 值。这可以通过在 id 之后添加 [err1,err0] 来完成:

Text:="|<mycomment>[0.2,0.1]*197$15.1U8A11U8C11k8600s0700Q01U004" ; 覆盖 err1 的取值为 0.2 (20%) 、err0 的取值为 0.1 (10%)
ok:=FindText(X,Y,,,,,0.3,0.3,Text) ; 即使 err1 和 err0 均设为 0.3,在搜索时也会使用 Text 中的 0.2 和 0.1

(四) 抓取模式

1. Gray 灰度阈值模式。

FindText函数实战课程:找图神器亮剑

在灰度模式下,每个像素的灰度值(取值从 0 到 255,其中 0 为纯黑色,255 为纯白色)将与指定阈值进行比较。如果像素灰度值超过阈值,则将其变为白色(white=background=”_”=”0″),否则变为黑色(black=text=”o”=”1″)。因此,任何比阈值更暗的像素都变成黑色,而任何比阈值更亮的像素都变成白色。阈值 255 会使整个图像变黑,从而匹配搜索范围内的所有内容。

在 Text 文本的形式中,灰度阈值模式由 * 字符标识,后跟阈值。

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8" ; "<auto>" 之后的 "*" 表示使用的是灰度模式,阈值为 159 (位于 * 和 $ 之间), 图像宽度为 40 像素 (位于 $ 和 . 之间)

2. GrayDiff 灰度差值模式。

FindText函数实战课程:找图神器亮剑

每个像素都会被一一考虑到,如果所考虑的像素周围的任一像素的灰度值大于该像素灰度值+阈值(总共比较 8 次),则该像素将变为黑色,否则将变为白色。从本质上讲,它会生成形状的边缘,因为最大的灰度差值出现在边缘等对比度较大的区域,而纯色区域则变为白色。在 Examples.png 的 Gray vs GrayDiff 示例中,如果我们希望 FindText 只找到最右边的 2 个纯色正方形,使用 Gray 灰度阈值模式是无法成功的,但 GrayDiff 灰度差值模式却可以把它们成功分离出来(见下面的示例)。

在 Text 文本的形式中,灰度差值模式由 ** 字符标识,后跟阈值。

#include <FindText>
Text:="|<solidcolorbox>**50$54.zzzzzzzzzU00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001U00000001zzzzzzzzzU" 
; "**" 表示 GrayDiff 模式,阈值设为 50, Text中图像宽度为 54 像素
ok:=FindText(X,Y,,,,,,,Text)
for k, v in ok
    FindText().MouseTip(v.x, v.y)

3. Color 颜色相似模式。

FindText函数实战课程:找图神器亮剑

在 FindText Gui 中,单击图像上的某个像素会选择该像素的颜色,并将其定义为“黑色”。然后可以用相似度滑块来包含相似的颜色(相同颜色的阴影),单击颜色相似二值化按钮应用滑块所做的更改。

在 Text 文本的形式中,颜色相似模式由所选颜色的十六进制表示进行标识,后跟@ 和滑块值(相似度取值从 0.00 到 1.00,1.00=100% )。

Text:="|<>505050@1.00$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s" 
; 只有 505050 这个颜色被视为 Text 文本(黑色像素)
; 相似度被设为 1.00 (100%),表示只允许完全匹配

4. ColorPos 颜色位置模式。

FindText函数实战课程:找图神器亮剑

单击图像上的像素将定义该像素相对于图像左上角的位置,该位置的颜色将用于黑白色的转换。因此,此模式将找到单一颜色的图案,其中颜色取自指定位置的像素。和颜色相似模式一样,该模式也有一个相似度滑块,可以检测相似的颜色。

在 Text 文本的形式中,颜色位置模式由 # 标识,后跟一个数字,该数字是选定的像素位置(从左上角起算,从左到右,然后从上到下),然后是 @ 和滑块值。

Text:="|<>#49@0.90$47.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz07zzzzzy07zzzwDwQ7zzzsTsyDzzzkzlwQ1w007Xsk1k00D7V11127yC2D2DwDw08T47sTs0kyA0kzlzVwQ0VzXz3szV3z7z7VzW7yDy22667wTy0A0A1szy0s0w3zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
; # 表示 ColorPos 模式
; 49 表示第 49 个像素(从左至右、从上到下)被视为“黑色”。由于图像宽度是 47,49 对应于第二行第二个像素
; 0.90 为相似度 (90%)

5. ColorDiff 颜色分量模式。

FindText函数实战课程:找图神器亮剑

在 FindText Gui 中,单击图像上的像素会选择该像素的颜色,并将其定义为“黑色”。颜色分量二值化按钮将使用所选颜色将图像转换成黑白色。现在,红、绿 和蓝三个输入框可用于选择允许的颜色差异(加或减,但不会从 255 环绕到 0,反之亦然)。例如,如果选择的颜色是 0098D9(十六进制 R=00,G=98,B=D9)并且输入框设置为 1,那么 FindText 将匹配颜色 0098D9 和 0198D9(但不是 FF98D9-0198D9,因为它不环绕)。然后将绿设置为 5 将匹配 0093D9 和 019DD9 之间的颜色。

Text:="|<>3F627F-00050A$51.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzk1zzzzzzy07zzzwDzlUTzzzVzyD3zzzwDzlwM1s007yDW06000zlsE0EEVzyA0D27wDzk0VsETVzy0ADW0ADzlzVwM0VzyDwD3w4DzlzVsTsVzyDy02467zlzk0k0k7yDz0C0C0zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzw"
; 定义为 "黑色" 的颜色是 3F627F。
; 允许的颜色误差范围是 00050A:红色 00 (十进制的 0)、绿色 05 (十进制的 5)、蓝色 0A (十进制的 10)。

6. MultiColor 多色查找模式。

FindText函数实战课程:找图神器亮剑

单击多色查找复选框后,就可以选择多个像素,FindText 将仅在其相对坐标处查找选定的像素(第一个选定像素将位于 x=0、y=0,后面的像素将相对于该坐标),并且只查找选定像素的特定颜色。例如,如果在此模式下选择了两个像素,则 FindText 将仅搜索所选颜色且相对坐标相同的两个像素。RGB 滑块可用于更改误差范围。在Text 文本的形式中,多色查找模式由 ## 标识,后跟一个数字表示 RGB 误差范围 (0-255)。然后在 $ 符号之后,将跟随所选像素的相对坐标和颜色:例如“0/0/3A8000,1/0/008000”表示将搜索颜色为3A8000 和 008000 的两个像素,

多色查找模式也可用于查找文件中的图像。这可以通过指定文件位置而不是单个像素来完成。FindText().ImageSearch 函数在内部使用了此方法。

Text:="|<>##1$0/0/3F627F,-7/1/FFFFFF"
; ## 表示 MultiColor 模式
; RGB误差范围:1
; 匹配位置像素颜色为 3F627F (R、G、B 误差范围 ± 1:匹配 3E617E - 406380),第二个像素位置为向左7像素、向下1像素,像素颜色为 FFFFFF (R、G、B 误差范围 ± 1)
Text := "|<>##0$" A_ScriptDir "\Example.png" ; 在 FindText 中使用此 Text 将查找脚本目录的 Example.png,RGB 误差范围设为 0

四、封装函数

(一)ImageSearch

FindText().ImageSearch 是 FindText 的包装函数,它更易于使用。它还可以像 AHK 原生函数 ImageSearch 一样接受图像文件(但不支持 *IconN 等选项),并且它支持不同的 CoordMode(FindText 总是返回相对于屏幕的坐标,但 FindText().ImageSearch 可以使用任何一个由 CoordMode Pixel 命令设置的坐标模式,例如 Window 或 Client 坐标模式)。

FindText().ImageSearch(OutputVarX, OutputVarY, X1, Y1, X2, Y2, TextOrImageFile, Screenshot:=1, FindAll:=0)
returnValue := FindText().ImageSearch(
    OutputVarX, OutputVarY --> 存储找到图像中心的 X 和 Y 坐标的变量名。除非用 CoordMode 来更改坐标模式,否则使用相对坐标。
    ,X1, Y1 --> 搜索矩形左上角的 X 和 Y 坐标。除非用 CoordMode 来更改坐标,否则使用相对坐标。
    ,X2, Y2 --> 搜索矩形右下角的 X 和 Y 坐标。除非用 CoordMode 来更改坐标,否则使用相对坐标。
    , TextOrImageFile --> FindText 文本或图像文件的文件名
    , Screenshot --> 如果值为 0,则使用最后一次截图,默认为 1(每次重新截图)
    , FindAll --> 如果值为0,则在找到第一个结果后返回,默认为1(查找所有结果)
)

如果搜索成功,returnValue 将被设置为 1,否则为 0。

如果搜索成功,ErrorLevel 将被设置为 0,否则为 1。

例子:

#include <FindText>
if FindText().ImageSearch(X,Y,,,,, A_ScriptDir "\Examples.png") ; 在屏幕上查找 Examples.png 中的图像
    FindText().MouseTip(X, Y)
if FindText().ImageSearch(X,Y,,,,, "|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8") ; 查找 "auto" 图像,其内容包含在 Text 中
    FindText().MouseTip(X, Y)

(二)辅助函数 Click、MouseTip、RangeTip、ScreenToWindow、ScreenToClient、ClientToScreen、WindowToScreen

1. FindText().Click(x, y, options) 的参数和 AHK 中的 Click 函数相同,但是在单击期间它将 CoordMode Mouse 设置为 Screen(绝对坐标)

FindText().Click(100, 200) ; 在屏幕坐标为 x 100, y 200 处左键单击

2. FindText().MouseTip(x:="", y:="", w:=10, h:=10, d:=4)会在指定的 x 和 y 坐标处创建一个闪烁的红色方块,通过直观的显示帮助确认正在使用正确的位置。如有需要,可以更改方框的宽度、高度及其边框的厚度。

FindText().MouseTip(100, 200) ; 在屏幕坐标 x 100, y 200 处闪烁一个红框

3. FindText().RangeTip(x:="", y:="", w:="", h:="", color:="Red", d:=2) 是 MouseTip 更一般的变体形式,它将显示指定大小、颜色和边框粗细的红色框。请注意,它不会像 MouseTip 那样自动消失。以下示例将在找到的图像边缘周围创建一个闪烁的矩形。

if (ok:=FindText(X,Y,,,,,,,"|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s")) {
    Loop 4
    {
        FindText().RangeTip(ok[1].1, ok[1].2, ok[1].3, ok[1].4, (A_Index & 1 ? "Red":"Blue"), 4) ; 在找到图像的边缘显示一个矩形框,边框颜色在红色和蓝色间每半秒切换一次。
        Sleep, 500
    }
}

4、ScreenToClient、ScreenToWindow、WindowToScreen、ClientToScreen 在不同 CoordMode 之间转换坐标。

FindText().ScreenToClient(outX, outY, x1, y1, ahk_id:="") 会将 x1 和 y1 从屏幕坐标转换为相对于 id 为 ahk_id 的窗口的客户端坐标,并将 outX 和 outY 设置为获得的结果值。例如,这在使用 ControlClick 时很有用:FindText 将返回相对于屏幕的坐标,但 ControlClick 使用相对于窗口的坐标,因此可以使用 ScreenToWindow

if (ok:=FindText(X,Y,,,,,,,"|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s")) {
    FindText().ScreenToWindow(outX, outY, X, Y, WinExist("ahk_class MSPaintApp")) ; 将绝对坐标 X 和 Y 转换为相对于画图窗口的相对坐标,结果存储在 outX 和 outY 变量中。
    ControlClick, X%outX% Y%outY%, ahk_class MSPaintApp ; 在画图程序中单击之前存储的坐标位置
}
FindText().WindowToScreen(outX2, outY2, 200, 300) ; 如果未指定 ahk_id,则使用活动窗口
;将相对坐标 X200 Y300 转换为绝对坐标,结果保存在 outX2 和 outY2 中。

(三) 文本库函数PicLib、PicN、PicX、PicInfo

1. PicLib 可用于创建图像/文本库,以便使用Text ID(comment)轻松获取文本。

FindText().PicLib(commentOrText, addToLib:=0, libraryIndex:=1)

可以通过将 addToLib 设置为 1 来添加新文本,并且 libraryIndex 可以指定要添加文本的库编号。如果多个 Text 具有相同的 id,则仅添加最后一个。

FindText().PicLib("|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8", 1) ; 将 "auto" Text 添加至默认的 1 号库
FindText().PicLib("|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s",1,2) ; 将 "hot" Text 添加至 2 号库

可以通过 id 从库中获取文本,此时 addToLib 必须为 0(默认值)。可以同时获取多个id,中间用 | 分隔 (“comment 1|comment2|…”)。

MsgBox, % "从 1 号库中获取 auto 的文本:" FindText().PicLib("auto") ; 从 1 号库中获取 id 为 "auto" 的相应 Text
MsgBox, % "从 2 号库中获取 hot 的文本:" FindText().PicLib("hot",,2) ; 从 2 号库中获取 "hot" 的文本
MsgBox, % "从 3 号库中获取 hot 的文本:" FindText().PicLib("hot",,3) ; 返回空值,因为 "hot" 在 2 号库,不在 3 号库

2. PicN(Comment, libraryIndex:=1) 将字符串分解为字符,然后使用 PicLib 获取 Text。这在进行组合搜索时很有用。

PicN(“hot”) 等价于 PicLib(“h|o|t”),生成 id 为 “h”、“o” 和 “t” 的 Text。

3. PicX(Text) 将包含一个单词的 Text 分割成多个包含单独字符的 Text。因此,如果文本包含单词“this”的图像,那么它将返回一个包含 4 个单个字符(“t”、“h”、“i”、“s”)图像的文本,用 | 分隔。在字符之间的间距可能发生变化时(例如,网页每次呈现可能会略有不同),这很有用,因为我们可以将单词分割成单独的字符,最终再执行组合查找,这样字符之间的距离就可以是变化的。

MsgBox, % "将 ""hot"" 切分为多个字符: " FindText().PicX("|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s")

4. PicInfo(Text) 返回有关文本的低层信息。将返回以下数组:

[v, w, h, len1, len0, e1, e0 , mode, color, n, comment, seterr]
v --> 内容为 0 和 1 的 Text 值
w --> Text 宽度
h --> Text 高度
len1 --> Text 中 1 的总数
len0 --> Text 中 0 的总数
e1 --> 1 的允许误差范围:是一个绝对数量(返回 len1 的百分数)
e0 --> 0 的允许误差范围:是一个绝对数量(返回 len0 的百分数)
mode --> 0 = Color 或 未定义, 1 = Gray, 2 = GrayDiff, 3 = ColorPos, 4 = ColorDiff, 5 = MultiColor 或 使用外部文件
color --> Color 模式下的颜色
n --> MultiColor 模式下颜色的数量
comment --> Text 的 comment/id
seterr --> Text 中是否设置了误差范围

示例:

#include <FindText>
Text:="|<mycomment>[0.1,0.1]*197$15.1U8A11U8C11k8600s0700Q01U004"
info := FindText().PicInfo(Text)
MsgBox, % "v: " info[1] "`nw: " info[2] "`nh: " info[3] "`nlen1: " info[4] "`nlen0: " info[5] "`ne1: " info[6] "`ne0: " info[7] "`nmode: " info[8] "`ncolor: " info[9] "`nn: " info[10] "`ncomment: " info[11] "`nseterr: " info[12]

(四) 光学字符识别 (OCR)

可以使用 FindText().OCR 函数来实现一个基本的光学字符识别器 (OCR)。可以在找到字符集中的所有字符后使用它。

FindText().Ocr(
    Ok --> 获取 FindText() 返回的结果
    ,offsetX:=20, offsetY:=20 --> x 和 y 的最大偏移量。如果超过此	值,则将插入 * 号。默认值为 20 和 20。
    ,overlapW:=0 --> 图像的最大重叠,默认为 0
)

返回的是一个关联数组:{text:text, x:X, y:Y, w:W, h:H}

使用 Examples.bmp 的示例:

 ; 首先将字母 "a" 和 "s" 的图像添加到默认图库
FindText().PicLib("|<a>**50$9.TXz0MzTzXkT7TtvU", 1)
FindText().PicLib("|<s>**50$8.DbzXs7kz0wDzTc", 1)
; 使用图库找到屏幕上所有 "a" 和 "s" 字符
ok:=FindText(X, Y,,,,, 0.15, 0.15, FindText().PicN("sa"))
if (ocr := FindText().OCR(ok, 3, 3))
MsgBox, % "找到 " ocr.text ; 返回结果应该为 "aaa*sss*asa*sas

更高级的示例是实现记事本搜索功能。在本例中,打开记事本并将字体(在菜单栏 格式 -> 字体…)设置为 Consolas、Regular、11。在记事本中写入一些示例文本,然后运行以下示例并按 F1,这将打开一个搜索对话框。当然在记事本中 Ctrl+F 要好用得多,因为这个 FindText 函数只能查找在记事本窗口中可见的文本,但类似的东西可以用来在游戏中查找文本。

#NoEnv
#SingleInstance Force
#include <FindText>
; 首先是一些数字、大写字母、小写字母的字符集(从显示在记事本中的字体截取的:Consolas, Regular, 11)
numbers :="|<0>*139$10.7Uza6MD1wDnjQz3sD0q6DkS8|<1>*139$9.73sv4M30M30M30M30MzzzU|<2>*139$9.D7wlk60k61UQ71kQ70zzzU|<3>*139$8.TDu70kA6D3s30E43zjm|<4>*139$11.1k3UD0q1g6MMkVX37zzzUM0k1W|<5>*139$9.zryk60k60z7y0k70kCzbsU|<6>*139$10.3sTXUM1U5wzvVw3kBUq6DsS8|<7>*139$10.zzzk30M1UA0k60M30A1U60k8|<8>*139$10.7lza6MBVbCDkT7CMD0y7Tsz8|<9>*139$10.7Vz66kP0w3sRznv081UATVw8"
uppercase := "|<A>**50$12.000000003k3k3s7M6M6QCQCAACTyTyM7s7s30000000000U|<B>**50$10.0000003zDysvVy6svzDyszVy7sTzjw000000008|<C>**50$10.0000000TXzSDUC0s30A0s3UC0SAzly000000008|<D>**50$11.0000000DsTwkxUv0y1w3s7kDUP1q7jyTk000000000E|<E>**50$9.000000zzzk60k60zryk60k60zzz00000004|<F>**50$9.000000zzzk60k60zryk60k60k6000000004|<G>**50$11.00000000z3zC6s1k30CDwTs6sBkPknzVy000000000E|<H>**50$10.00000030w3kD0w3kDzzzkD0w3kD0w3000000008|<I>**50$9.000000zzz60k60k60k60k60kzzz00000004|<J>**50$8.00000Dzz1kQ71kQ71kQ7Xjvw0000002|<K>**50$10.0000003VyCsnbCsz3sDUz3iCQsvXi7000000008|<L>**50$9.000000s70s70s70s70s70s70zzz00000004|<M>**50$12.00000000QCQCKOSSSLPrPrPrtbs7s7s7s7s70000000000U|<N>**50$10.0000003Uz3wDsxXrDgyntjayTszXy7000000008|<O>**50$12.000000007sDwSSQ6M7s7s7s7s7M7QCSSDw7s0000000000U|<P>**50$10.0000003zDyszVy3sTXzyzXUC0s3UC0000000008|<Q>**50$12.000000007sDwSSQ6M7s7s7s7s7M7QCSSDw7s1U1n0z0S00U|<R>**50$10.0000003yDwlv3gClnyDknX7AQkv3g7000000008|<S>**50$10.0000000z7ywPUC0S0y0y0w1k7kTzbw000000008|<T>**50$10.0000003zzz30A0k30A0k30A0k30A0k000000008|<U>**50$10.00000030w3kD0w3kD0w3kD0y7sxzXw000000008|<V>**50$12.00000000s3s7M6Q6QCACCACQ6Q6M7s3s3k3k0000000000U|<W>**50$11.k7UD0y1w3tbnjjPSqxhDSSwxsu|<X>**50$11.sDktnVr3w3k7UD0z1y7CCCsRUS|<Y>**50$11.kDUTVnX7C7MDkD0C0M0k1U3062|<Z>**50$10.0000003zzz0M3UQ1UC0k60s30M3zzz000000008"
lowercase := "|<a>**50$10.000000003wTtVk7Dxzy7sTXrzDQ0000008|<b>**50$10.03UC0s3UCwzvty7sDUy3sTXzyTU0000008|<c>**50$10.000000001yDxsr0M3UC0M1knz7s0000008|<d>**50$10.001k70Q1lzTxly7sT1y7sTbrzDQ0000008|<e>**50$10.000000001wTtly3zzzy0s1k7zDw0000008|<f>**50$11.003wDss1k3UzxzsQ0s1k3U70C0Q0s00000002|<g>**50$11.0000000007zTytnVX77yDss1k3zXzi3wCzwzW|<h>**50$10.03UC0s3UCwzvtz7sTVy7sTVy7sQ0000008|<i>**50$9.71s70007sz0s70s70s70szzz000000U|<j>**50$9.1kD1k003yTk60k60k60k60k60qCzXsU|<k>**50$9.060k60k67lqQr7ky6sr6Qlq7000000U|<l>**50$9.07sz0s70s70s70s70s70szzz000000U|<m>**50$11.000000000TizxrTCyNwntbnDaTAyNk0000002|<n>**50$10.00000000Cwzvtz7sTVy7sTVy7sQ0000008|<o>**50$10.000000003wTvny7kD0w3sTnryDk0000008|<p>**50$10.00000000Cwzvty7sDUy3sTXzyzXUC0s3U8|<q>**50$10.000000001zTxly7sT1y7sTbrzDQ1k70Q1s|<r>**50$9.00000006yzzbsS0k60k60k60000000U|<s>**50$9.00000003wzr6s7UTUS0y6zrw000000U|<t>**50$11.0000A0s1kTzzyC0Q0s1k3U7070DsDk0000002|<u>**50$10.00000000C7sTVy7sTVy7sRrrzDQ0000008|<v>**50$10.00000000A3sTVq6QsnXADkO1s7U0000008|<w>**50$12.0000000000s3s7s7NbNqPqPqOKSSSSCS00000000U|<x>**50$10.00000000C7QRnXw7US1wDlnb7sQ0000008|<y>**50$12.0000000000M7QCQCAACQ6M7M7s3k3k1U3U70z0w0U|<z>**50$10.000000007zTs3UQ1UA1UC1k7zzw0000008"
characterset := lowercase . uppercase . numbers
; 将新建的字符集添加到 PicLib 库
FindText().PicLib(characterset, 1)
return
F1::
InputBox, searchPhrase, Search Notepad, % "在记事本中搜索文本。如果要区分大小写,请把搜索内容置于双引号内。"
caseSensitive := (SubStr(searchPhrase,1,1) == """") && (SubStr(searchPhrase,0) == """")
if caseSensitive
searchPhrase := SubStr(searchPhrase,2,-1) ; 区分大小写进行搜索时,去除双引号
else
StringLower, searchPhrase, searchPhrase ; 不区分大小写进行搜索时,将搜索短语转成小写形式
if (ok:=FindText(X, Y,,,,, 0, 0, caseSensitive ? FindText().PicN(searchPhrase) : RegexReplace(characterset, "(?<=<)\p{L}(?=>)", "$L0"),1,1, caseSensitive ? 1 : [searchPhrase])) ; 如果进行的是区分大小写搜索,使用 PicN 将搜索短语中各个字符的图片拼起来,然后使用 FindAll=1 来查找图像。如果进行的是不区分大小写搜索,将所有大写字符图像替换成小写的图像(这表示默认情况下 FindText 在搜索时会同时使用大写和小写图像),然后再搜索小写的搜索短语。
{
FindText().MouseTip(X, Y)
 ; FindText().Click(X, Y, "L")
}
return

(五)BindWindow – 搜索遮挡窗口文本

BindWindow 允许 FindText 在被其他窗口遮挡的窗口中搜索文本。

FindText().BindWindow(
    bind_id -> 获取目标窗口的 ahk_id(这可以通过 WinExist() 获得)。		如果设置为 0,则禁用 BindWindow,采用正常方式搜索屏幕。
    , bind_mode -> 指定绑定的方法。有些窗口支持某些方法,有些窗口需要	其他的方法。奇数 bind_mode 会修改一个透明窗口以在其中进行搜索。通	  常 bind_mode 为 0 或 4 时起作用,默认为 0。
    ,get_id -> 如果设置为 1,则返回当前绑定窗口的 id,默认为 0。
    ,get_mode ->  如果设置为 1,则返回当前使用的绑定模式,默认为 0。
)
SetTitleMatchMode, 2
FindText().BindWindow(WinExist("Paint ahk_class MSPaintApp")) ; 将 FindText 绑定至画图程序
Text:="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s"
FindText(X, Y, 72-150000, 228-150000, 72+150000, 228+150000, 0, 0, Text) ; 画图程序可能被其他窗口遮挡,但是 FindText 还是能正常找到文本
FindText().MouseTip(X, Y)
FindText().BindWindow(0) ; 把画图程序和 FindText 解绑

注意!BindWindow 将为运行脚本中的所有 FindText 函数进行全局设置。如果使用多个线程,则新线程可以覆盖旧线程的绑定窗口(例如,一个热键绑定一个窗口并循环 FindText,然后被另一个绑定另一个窗口的热键中断)。可以通过使用 FindTextClass 来避免这种情况,具体将在下面讨论。

(六)FindTextClass 查找文本类

FindText 的每个函数都包含在一个名为 FindTextClass 的类中。FindText() 返回 FindTextClass 的全局共享实例,因此 FindTextClass 的每个函数都在同一个数据集上运行。例如,BindWindow 为每个 FindText 调用更改其绑定窗口,Screenshot=0 将使用任何 FindText 调用最后创建的屏幕截图,等等。FindText(arguments) 与 FindText().FindText(arguments) 是相同的。

对于此示例,在 Paint 中打开“Examples.png”,然后通过在其上方移动另一个窗口来隐藏图像。按 F1,鼠标提示找到的图像的位置。按F2 后,找不到图像了。

#NoEnv
#SingleInstance Force
#include <FindText>
Esc::ExitApp
F1::
FindText().BindWindow(WinExist("Examples.png - 画图"))
Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8"
while (ok:=FindText(X, Y,,,,, 0, 0, Text)) {
FindText().MouseTip(X, Y)
Sleep, 1000
}
MsgBox, 找不到画图程序!
return
F2::
FindText().BindWindow(0) ; 绑定另一窗口
return

这可以通过使用 FindTextClass 的新实例来解决:

F1::
ft := new FindTextClass
ft.BindWindow(WinExist("Examples.png - 画图"))
Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8"
while (ok:=ft.FindText(X, Y,,,,, 0, 0, Text)) {
ft.MouseTip(X, Y)
Sleep, 1000
}
MsgBox, 找不到画图程序!
return

(七)截图功能:ScreenShot、GetTextFromScreen、SavePic、ShowPic、ShowScreenShot

1. FindText().ScreenShot(x1:=0, y1:=0, x2:=0, y2:=0)

在点 x1,y1 和 x2,y2 之间获取 FindText 内部屏幕截图,以便在以后的 FindText 调用中使用。

2. Text := FindText().GetTextFromScreen(x1:=0, y1:=0, x2:=0, y2:=0, Threshold="", ScreenShot:=1, outX, outY)

使用指定的阈值将 x1,y1 和 x2,y2 之间的截图转换为 Text。Threshold 可以是灰度阈值模式的“*”或灰度差值模式的“**”,后跟阈值。ScreenShot = 1 将拍摄一个新的屏幕截图,取值为 0 表示使用最后一个内部屏幕截图(例如由 ScreenShot 函数生成的截图)。文本将自动从顶部和底部裁剪。变量 outX 和 outY 将设置为包含屏幕上抓取文本的中点。

3. FindText().SavePic(FileName, x1:=0, y1:=0, x2:=0, y2:=0, ScreenShot:=1)

将 x1,y1 和 x2,y2 两点之间的截屏区域保存到一个文件中。

4. FindText().ShowPic(FileName:="", show:=1, outX, outY, outW, outH)

在屏幕左上角显示一个图像文件内容。outX 和 outY 将设置为显示图像的 X 和 Y 坐标(在多屏幕设置中,此坐标可能不是 0,0),outW 和 outH 设置为图像的宽度和高度。在不指定 FileName 的情况下调用 ShowPic() 将从屏幕中删除图像。

5. FindText().ShowScreenShot(x1:=0, y1:=0, x2:=0, y2:=0, ScreenShot:=1)

与 ShowPic 函数一样,显示点 x1,y1 和 x2,y2 之间的区域。如果 ScreenShot 为 1,则将拍摄新的屏幕截图,否则使用最后一次的内部屏幕截图。

示例:

#include <FindText>
FindText().Screenshot(100,100,500,500) ; 在 100,100 和 500,500 位置间截图
FindText().ShowScreenShot(100, 100,500, 500, 0) ; 将截图显示在屏幕上。由于我们最后一个参数指定为 0,函数调用时会使用  Screenshot 函数截取的最后一次截图
Sleep, 3000
FindText().ShowScreenShot() ; 3 秒后隐藏截图
FindText().SavePic(A_ScriptDir "\TestScreenShot.png", 100, 100, 400, 400, 0) ; 将截图的一部分保存在脚本所在目录的一个文件中。

(八) 杂项:Sort、Sort2、Sort3、WaitChange

1. FindText().Sort(ok, dy:=10)

接受一个 FindText 结果数组(ok),返回一个从左到右 -> 从上到下排序的数组。比较点位于图像的中间(不是左上角!),通过改变 dy 值可以忽略 y 位置的细微差异。

2. FindText().Sort2(ok, px, py)

接受一个 FindText 结果数组(ok)和一个坐标(px, py),并使用 (x-px)^2 + (y-py)^2 公式计算图像与坐标的距离,进而对数组进行排序。

3. FindText().Sort3(ok, dir:=1)

接受一个 FindText 结果数组(ok),按方向排序。Dir 与 FindText dir 参数取相同的值(9 除外)。

4. FindText().WaitChange(WaitTime:=-1, x1:=0, y1:=0, x2:=0, y2:=0)

等待 “WaitTime” 秒,看坐标 x1,y1 和 x2,y2 之间的屏幕区域是否变化。将 “WaitTime” 指定为 -1 将无限期等待。

五、 FindText 技巧

(一)FindText 组合查找

可以通过将 JoinText 参数设置为 1,或指定要查找的单词数组,来完成组合查找。其中 offsetX 和 offsetY 是图像间所允许的最大相对偏移量。如果查找成功,则返回的结果 id 将是用于搜索的 id 的组合(在以下示例中,ok[1].id == “autohot”)

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8"
Text.="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s"
MsgBox, 查找 "auto" 和 "hot"
ok:=FindText(X,Y,,,,,,,Text) ; FindText 会分别返回 "auto" 和 "hot" 的所有位置
for k, v in ok
    FindText().MouseTip(v.x, v.y)
MsgBox, Looking for "auto" followed by "hot"
ok:=FindText(X,Y,,,,,,,Text,,,1) ; FindText 会返回所有后面是 "hot" 图像的"auto"图像位置,其中 "hot" 图像在 x 轴方向至多可以偏移 20 像素,在 y 轴方向至多可以偏移 10 像素。
for k, v in ok
    FindText().MouseTip(v.x, v.y)

如果创建了字符库,则可以使用组合查找来搜索单词。

; 首先将字母 "a" 和 "s" 的图像添加到默认图库
FindText().PicLib("|<a>**50$9.TXz0MzTzXkT7TtvU", 1)
FindText().PicLib("|<s>**50$8.DbzXs7kz0wDzTc", 1)
FindText(X, Y,,,,, 0.15, 0.15, FindText().PicN("sas"),,,1,5,3) ; FindText().PicN("sas") 使用图库组合出单词 "sas" 的 Text 值。误差范围设为 15%,因为所有的 "a" 和 "s" 字符都略有差别,设置成 15% 就可以找到所有的实例了。本例中默认的偏离值太大了,所以设置如下:OffsetX=5、 OffsetY=3。
FindText().MouseTip(X, Y)
FindText(X, Y,,,,, 0.15, 0.15, FindText().PicN("asa"),,,1,5,3) ; 查找单词 "asa"
FindText().MouseTip(X, Y)

在处理不同字体、浏览器中进行不同渲染或抗锯齿等情况时,同一字符需要有多个图像。此时,我们可以在Text 变量中提供字符集,然后在 JoinText 参数中提供数组形式的搜索词。常规 JoinText 只能使用一种字体。

Text:="|<A>*162$7.472VF4WzkMA"
Text.="|<A>*165$8.61UI92NWTYD1U"
Text.="|<s>**50$5.Tb3klz"
Text.="|<s>**50$5.Tb3klx"
Text.="|<a>**50$6.SH3TnnTU"
Text.="|<a>*152$5.x4/slz"
; Text now contains two fonts of "A", "s" and "a"
ok:=FindText(X, Y, , , , , 0.1, 0.1, Text,, 1,["Asa"]) ; FindText 将使用提供的字符集找到单词 "Asa" 的所有实例
for k, v in ok
    FindText().MouseTip(v.x, v.y)

(二)速度提升技巧

1. 使用尽可能小的搜索范围。可以通过多种方式实现,最简单的方法是使用 WinGetPos 将搜索限制在单个窗口中,以获取窗口位置和大小。

WinGetPos, pX, pY, pW, pH, ahk_class MSPaintApp ; 获取画图窗口的位置和大小
ok := FindText(X, Y, pX, pY, pX+pW, pY+pH,,, Text) ; 将搜索范围限定为画图窗口

有时搜索到的图像只会出现在特定区域,因此可以进一步调整搜索范围。在 Paint 窗口中,如果我们对菜单栏和工具栏不感兴趣,那么我们可以从顶部裁剪一部分:

WinGetPos, pX, pY, pW, pH, ahk_class MSPaintApp ; 获取画图窗口的位置和大小
ok := FindText(X, Y, pX, pY+150, pX+pW, pY+pH,,, Text) ; 将搜索范围限定为画图窗口,并从顶部剪切掉 150 像素,将菜单栏和工具栏排除在搜索范围外

2. 使用尽可能小的图像。可以通过使用 L3/R3/L/R/U/B/自动裁剪按钮,来裁剪抓取的图像。

3. 使用尽可能小的误差范围。例如,将 err1 和 err0 从 0 提高到 0.3 可能会导致搜索速度明显变慢(可能慢 5-10 倍)。

4. 如果要查找完全匹配,请为 err1、err0 或两者指定一个非常小的非零值。FindText 的默认动作是:如果 err1 和 err0 都设置为 0,那么如果搜索失败,将自动将 err1 和 err0 都设置为 0.05 并重新尝试搜索。当执行许多 FindText 调用来查找不同的图像时,这种额外的搜索会随着时间的推移而增加。但这仅在 err1 和 err0 恰好为 0 时发生,这意味着如果您将诸如 0.000001(很接近 0 但不等于 0)用于 err1、err0 或两者同时设置(只需一个非零),那么只会执行一次搜索(有效误差范围为 0.000001≈0)。

FindText(X, Y,,,,,,, Text) ; 如果 Text 在屏幕上不可见,那么会搜索 2 次:第一次 err1 和 err0 设为 0,第二次搜索设为 0.05(5% 的误差范围)
FindText(X, Y,,,,,0.000001,, Text) ; 如果 Text 在屏幕上不可见,只搜索 1 次,进行精确匹配(0.000001≈0)
FindText(X, Y,,,,,0.05,0.05, Text) ; 如果 Text 在屏幕上不可见,只搜索 1 次,误差范围是 5%

5. 如果要搜索多张图片,要么将 Text 拼成一个然后只调用一次 FindText,要么将 Screenshot 参数设置为 0 以进行后续搜索。这避免了 FindText 多次进行屏幕截图,这需要耗费大量时间。

使用组合查找方法的示例:

Text:="|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8"
Text.="|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s"
if (ok := FindText(X, Y,,,,,,, Text)) {
    if (ok[1].id == "auto") {
        ; 找到 "auto" 图像后要进行的操作
    } else if (ok[1].id == "hot") {
        ; 找到 "hot" 图像后要进行的操作
    }
}

重用第一个屏幕截图的示例:

FindText().PicLib("|<auto>*159$40.0000k000003000000A00DVUnw7lr6371nUAMAA630FUkkkAT63330rgMAAA1kFUkkkD363330wAAAA62vkvkkQtt1v1kS8",1)
FindText().PicLib("|<hot>*152$29.U00010000200014000280004Hk7kzxktktVVUkX161V43A3286M24EAkA8UNUMF0lUUW1Xb1g31s1s",1)
if (ok := FindText(X, Y,,,,,,, FindText().PicLib("auto"))) {
    ; 找到 "auto" 图像后要进行的操作
}
if (ok := FindText(X, Y,,,,,,, FindText().PicLib("hot"), 0)) { ; 使用上一次搜索"hot"的截图来调用 FindText
    ; 找到 "hot" 图像后要进行的操作
}

6. 使用需要最少像素比较次数的捕捉模式。通常灰度阈值模式是最快的选择;灰度差值、颜色位置 和 相似度设置为 100% 的颜色相似模式也非常快。最慢的是颜色相似模式(颜色范围越大或相似度越低 -> 搜索越慢)和多色查找(如果使用的是图像文件)。


这就是现有的全部内容,感谢您的阅读!

来自“飞跃”的2个细节补充:
1、最新版右键抓图时,可以使用方向键调整选框大小。
2、在放大界面制作字库时,可以点击黄色的分割标记来把图分割成几个部分,对应输入框的几个备注文字,就可以一次性分割添加几个字库了。

六、参考资料

1.【函数】FindText中文版- 屏幕抓字生成字库工具与找字函数 – AutoAHK

2.FindText tutorial – AutoHotkey Community

3.FindText 深度教程 v1.2 – AutoAHK

给TA捐赠
共{{data.count}}人
人已捐赠
教程

《GDI+系列教程》第12章 —— 图像的复用

2022-1-4 17:59:26

其他教程案例

AutoHotkey实现Excel自动化(第一章:通过原生com方法,实现简单操作)

2022-1-8 23:56:54

6 条回复 A文章作者 M管理员
  1. 特罗

    强强强!!!?

  2. 特罗

    视频讲的越来越细致了!点赞??

  3. 五分钟

    ???厉害

  4. 微尘

    666

  5. 何清风

    视频我找了半天的链接,没找见在哪?

个人中心
购物车
优惠劵
有新私信 私信列表
搜索