一个使用Python编写,基于OpenCV的画面内容识别算法项目
该项目还处于初级开发阶段,任何功能函数方法都有变更的可能
(但会尽可能的只进行内部变更,不改变函数调用方式)
为了延长该项目活在光明下的时间,请勿公开使用与降低使用门槛
已实现基本功能:
1. 基于cv2.matchTemplate的图像匹配(支持有明显特征的RGB透明模板)
2. 图像模板对象(类封装ROI区域、反馈区域、色彩空间、长宽通道信息)
3. 安卓adb触摸调试输入(tap)与触摸事件输入(sendevent)
4. Windows窗口句柄截图(目标窗口不可最小化)
5. 调试运行日志输出
已实现核心功能:
1. 块作用域 临时只让特定while/if内的代码执行
2. 导航系统 显示当前代码执行位置与保存作用域标识
2. 检测目标 是否存在
3. 目标出现 是否在超时前出现
4. 目标消失 是否在超时前消失
5. 点击目标 是否在超时前响应出现/消失动作
待实现功能:
1. 其他模拟器支持(当前测试通过为MEMU)
2. Windows缩放支持(已适配缩放的模拟器窗口尺寸会随之改变)
3. 图像匹配的ROI功能实装
当前测试通过于:
1. 系统 分辨率3840x2160 缩放200%
2. MEMU 安卓分辨率1920x1080 窗口分辨率1280x750
仅允许个人以学习为目的使用 禁止用于任何商用盈利违法等用途
因使用本项目产生的 一切问题后果任何法律后果 由使用者自行承担
后果包括但不限于:游戏封禁或回档、账户拉黑或锁定、违法相关法律法规被起诉
使用者对特定安卓软件编写的自动化操作脚本,建议的结构:
import ...
def func1() : ... # 自定义函数,例如完成某个大操作的运行逻辑
def func2() : ... # 更多自定义函数,完成其他大操作的运行逻辑
def funcX() : ... # 可以是嵌套组合了其他自定义函数的运行逻辑
def main() : # 主函数,执行各自定义函数的次序或开关
...
funcX()
...
... # 定义 安卓设备 (全局对象)
... # 初始化 块作用域选择器 (全局对象)
if __name__ == '__main__' :
while True :
...
main() # 主循环,执行main()或处理异常
...
编写前,要先准备用途图像匹配的外部模板文件,大概的步骤为:
1. 使用本项目的 句柄截图功能 截取模拟器画面
2. 使用图像软件 切割制作模板图像 并适当命名保存
3. 使用本项目的 模板类 在自定义脚本里定义模板对象
4. 使用本项目的 图像匹配函数 测试定义的模板对象(同时测试模板图像)
1.0 关于分辨率的问题
(!)本项目 CV处理图像分辨率 和 安卓显示分辨率 不一样
*public.py文件内:
dvWidth dvHeight -> 物理分辨率(往显示器输出的)
uiWidth uiHeight -> 逻辑分辨率(系统界面缩放后)
muTitle -> 模拟器窗口的标题高度 (默认30px/200%缩放)
muWidth muHeight -> 模拟器窗口大小(根据系统缩放计算)
cvWidth cvHeight -> cv处理分辨率(默认1280x720)
vmWidth vmHeight -> 安卓显示分辨率(默认1920x1080)
cvScale = vmHeight/cvHeight -> cv处理结果映射到安卓分辨率的倍率
获取安卓画面的流程:
会先把模拟器窗口设定为 宽(muWidth) 高(muHeight+muTitle)
定位到画面渲染子句柄(安卓虚拟机画面输出的地方)
使用win32api截图子句柄的图形并转换为cv格式返回
(兼容性问题:不同模拟器的渲染子句柄相对位置与标题可能不一样)
*Win32.py文件内已经预设好截图并保存文件代码
(!)制作模板图像的时候都以 CV处理图像分辨率(默认1280x720) 为主
定义模板对象时的 ROI区域、反馈区域 也以CV处理图像分辨率为主
匹配结果、反馈区域的坐标 会利用cvScale转换为安卓触屏的实际坐标
1.1 修改*Win32.py脚本
(1)if __name__ == '__main__' : 内的 VM = GetVMScreen(title='VM')
title='VM' 的 VM 改为目标模拟器的窗口标题,可在任务管理器里看到这个标题
(2)save = 'debug\\' + time.strftime('%Y%m%d.%H%M%S', time.localtime(time.time()))[2 :] + '.tif'
'debug\\' 的 debug 为保存截图的文件夹,可以修改,若不存在需要自行建立
(3)执行*Win32.py脚本后,若修改正确会在保存文件就里看到一个以时间格式为文件名的tif图像文件
1.2 裁剪被识别图像模板
(0)本教程以Photoshop为主,且不会过多强调在PS里如何操作
也可以使用其他支持 选区 图层 辅助线 快速导出图层 的二维图像编辑工具
(1)使用选区尽可能大的选定 被识别图像区域,
(不要包含其他无关内容,除非没办法了或使用透明图像匹配)
例如要选找 [圆角的确定按钮],就矩形选择 [圆角的确定按钮] 但不包含左右两边的圆角部分
(2)然后Ctrl+J拷贝图层,会得到一个新图层,双击它的文字重命名为方便后期寻找的名字
(若需要透明匹配,给指定模板图层加蒙版,然后使用铅笔黑色画掉希望透明的区域)
例如 01某界面01某按钮,可以让资源管理器按照前面的数字顺序排列显示文件
(3)然后选定背景图层,选下一个模板再Ctrl+J拷贝图层得到所有需要的图像模板
(不选背景图层就Ctrl+J空气了,PS会报错区域为空)
(4)重复123到所有希望使用的模板都选完
(5)最后按住Ctrl选择所有模板图层,右键快速导出为PNG,在保存的文件夹可以看到导出的图像
(6)保存tif文件,方便以后修改,默认设置的tif会记住图层
1.3 在脚本里定义模板
(0)定义模板的目的是在一个对象里保存模板的图像、感兴趣区域、模拟触摸区域、注释信息、文件读取模式等
(1)使用definetl(图像文件集根目录, 图像文件路径, 文本注释, 感兴趣区域, 反馈触摸区域, [读取模式])
definetl('.\\AAAA\\', 'Image.png', 'xxxxx', ...)
(2)读取模式支持 definetl.RGBA definetl.RGB definetl.Gray 必须套一层[]传参
definetl(... ,[definetl.RGBA])
(3)感兴趣区域为元组(X1,Y1,X2,Y2)仅绝对区域,预设支持*public.py内 roiFull(匹配0,0到1280,720全图)
definetl(... , (0, 0, 1280, 720), (0, 0, 1280, 720), ...)
definetl(... , roiFull, (0, 0, 1280, 720), ...)
(!)相对区域使用*public.py内cal(pos0, mv1, mv2, mv3, mv4)函数,其中pos0为*public.py内mo(Enum)枚举,
传入pos0=mo.SELF可配置为:匹配成功的坐标为原点,经过mv1,mv2移动后,大小为宽mv3高mv4的区域
传入mo.LT则是图像左上角(Left-Top)为原点,mo.RB则是图像右下角(Right-Bottom)为原点
更多pos0参数查找本项目的mo枚举类
(4)反馈触摸区域为元组(X1,Y1,X2,Y2)可以为相对区域,预设支持*public.py内 fbNone(不点任何区域) fbSelf(点击自己)
definetl(... , roiFull, cal(mo.SELF, -50, 90, 100, 90), ...)
definetl(... , roiFull, fbSelf, ...)
(!)可以在PS裁剪模板的时候,通过参考线或选区得到相应的像素坐标
1.4 巧用字典
(0)字典的键值为字符串,可以使用英文之外的字符命名模板类,让后面写的自动化逻辑更清晰,例如
root = ".\\root\\"
demo = {
'中文:名字1' : [
tl(root, "1.png", "xxxx1", roiFull, fbSelf, [tl.RGBA]),
tl(root, "2.png", "xxxx2", roiFull, fbSelf, [tl.RGBA])],
'中文:名字2' : tl(root, "3.png", "xxxx3", roiFull, fbSelf, [tl.RGBA]),
}
识别函数(... ,demo['中文:名字1'] , ...) # 传入模板类组,匹配第一个失败就匹配第二个,直到成功或全部失败
识别函数(... ,demo['中文:名字1'][1] , ...) # 传入模板类组的第二个模板类
识别函数(... ,demo['中文:名字2'] , ...) # 传入模板类
1.5 模板类对象
(0)成员列表
.array 图像内容
.ROI 感兴趣区域
.FB 触摸反馈区域
.alpha 透明蒙版,不透明图像为None
.space 颜色空间,参考[读取模式]
.width 图像宽度
.height 图像高度
(1)得到引入随机的反馈坐标
使用*public.py内GetFBXY(xy,tl)函数,xy为元组(x,y)
tl可以为模板类对象,或元组(x1,x2,y1,y2),或*public.py内cal()的返回值
例如 XY = GetFBXY(XY, tl=tl)
使用随机可降低被反作弊或机器人检测发现的概率
2.0 基本逻辑
识别(按钮) → 成功该干什么(点击按钮) → 干了有什么现象(按钮消失或某东西出现) → 完成
使用有反馈的逻辑,就算模拟器卡顿了,但在超时前响应完成,可以优化脚本的逻辑反应速度
2.1 图像识别
在*.MATCH.py里提供了三个函数封装和一个枚举
mt(Enum) 匹配函数功能开关,套[]传入param,详细开关查找本项目的mt枚举类
mask(screen, tlclass, threshold=0.8, param=None) 透明匹配函数
rect(screen, tlclass, threshold=0.8, param=None) 矩形匹配函数
matchtl(screen, tlclass, threshold=0.8, param=None) 自动匹配函数,自动选择mask()或rect()
screen 为被识别背景
tlclass 为模板类列表或模板类
threshold 为识别成功阈值
param 为功能开关(例如 mt.rgb为彩色识别(默认灰度),可以使用列表同时传入多个开关,如果函数不支持会直接舍弃)
例如 xy = matchtl(mu.GetScreen(), tl, threshold, [mt.quiet]) # 为静默不输出日志匹配
其中 matchtl() 支持 模板类列表 输入 和 直接模板类 输入,mask()与rect()只支持直接模板类输入无法遍历列表
2.2 虚拟触摸
在*android.py里提供一个类用于定义安卓模拟器设备,且应该在脚本主循环运行之前执行
mudevice(addr="IP:Port", title="xxxx")
参数 addr 是模拟器ADB网络IP地址端口,title 为模拟器实例窗口标题,inputdev 为触摸设备事件地址
使用 mudevice.inputTouch(xy, endSleep=退出等待float秒数) 方法进行虚拟触摸
3.0 块作用域
复杂匹配逻辑就需要路由系统指导死循环时执行哪一段代码
例如 while True :
while flag==1 : ...
while flag==2 : ...
while flag==3 : ...
其中1 2 3对应的是不同的阶段,该使用什么识别组合,关闭不必要的算法提高脚本的实时性
以及防止相似场景的错误匹配成功,例如两个场景都有一样的确认按钮,但功能完全不同甚至相反
3.1 根作用域检测
根作用域指的是作用域集合,
(例如 1-1 1-2 1-3 1-4 都是 1- 开头,只允许 1-X 通过)
该操作可以保证进入正确的死循环,
(例如 1-1 进入 2-X,但检测发现 1-1 不是 2- 开头,就会无法死循环马上执行后面代码)
本项目提供了作用域标志类对象
class FlagInit(flagNum, flagNote='L', base=0)
flagNum -> 作用域序号,所属根作用域内唯一,范围 0 到 255
flagNote -> 作用域注释,用于显示执行位置时的字符串
base -> 根作用域号,整个自定义脚本唯一,范围 0x00 到 0xFF
3.2 作用域管理
本项目提供了作用域管理类对象
class FlagCtrl(flag) # 创建对象时设定 flag 预设作用域
.get() # 获取当前标志
.note() # 获取当前标志的注释
.isBase(base) # 判断当前标志是否属于传入base分类
.set(flag, param) # 改变当前标志并更新导航信息
.IS(flag) # 判断当前标志是否等于传入flag
其中 param 可传入控制符:
需要重置导航 .root,结果 /根
在进入页面时 .push 压入一层导航,结果 /根/操作1
页面又进又出 .sw 切换同层导航,结果 /根/操作2
完成退出页面 .pop 弹出一层导航,结果 /根
基于 MEMU 测试开发的 [公主连结Re:Dive简体服] 小号每日内容自动辅助脚本
目前仅稍稍完善了 地下城AutoDungeon(MEMU) 下一关AutoNextLevel(MEMU),需要自行编辑脚本切换
警告 该自定义脚本违反 上海宽娱数码科技有限公司运营的游戏(公主连结Re:Dive)内发布的游戏公平运营声明 如下链接
![image](https://user-images.githubusercontent.com/15169084/165489327-bdeff184-9d93-433a-a63f-47055934f484.png)
使用者自行承担一切后果。本项目若收到该游戏运营方删除该自定义脚本的要求,会移除该自定义脚本及图像资源。
平台接口:pywin32 win32gui
图像处理:opencv-python Pillow numpy
日志输出:colorama colorlog
可能移除:keyboard
Pip自动安装 pip install -r requirements
用于定义模拟安卓设备的类
*android.py 内提供了如下类与方法
定义安卓模拟器 mudevice(addr=Adb控制IP地址端口, title=模拟器窗口标题, inputdev=触摸事件输入地址)
连接设备 mudevice.connect()
发送指令 mudevice.command(单行shell指令)
发送事件 mudevice.sendevent(事件列表)
调试触摸 mudevice.inputTouch(数组(x,y), 结束延迟)
事件触摸 mudeviceandroidTouch(数组(x,y), 按压时间, 重复次数, 每次结束延迟)
↑ 实验性,存在adb反应慢,某些应用内无法完成单击的问题
获取屏幕 mudevice.GetScreen()
窗口必须前台 (可以被遮挡 可以在其他桌面 不能最小化)
*Win32.py 内提供了如下函数
获取系统内所有窗口 GetWinHandler()
获取安卓模拟器句柄 GetMuHandler()
获取安卓模拟器画面 GetVMScreen(title=句柄标题,Mu='MEMU')
在自定义用户python脚本内的使用方式为 定义安卓设备前
... 预运行部分 ...
GetMuHandler() # 获取可选的模拟器实例列表 (正在完善,可以匹配到黑名单外的所有QT外壳窗口,但不影响后期使用)
... 安卓设备定义 ...
用于定义被查找图像的类
*public.py 内提供了如下类与方法
定义资源 definetl(图像文件集根目录, 图像文件路径, 文本注释, 感兴趣区域, 反馈触摸区域, [读取模式])
例如 自定义脚本的资源存放于 AAAA文件夹下:definetl('.\\AAAA\\', 'Image.png', 'xxxxx', ....)
感兴趣区域与反馈触摸区域 元组(X1,Y1,X2,Y2) 是基于CV算法分辨率的像素区域
例如1280x720的算法分辨率,只对左上角100,100到200,200区域进行图像匹配:(100,100,200,200)
只对150,150到250,250区域内进行安卓模拟点击 (150,150,250,250),实际使用需要缩放回模拟器的实际坐标