简介:这是一款面向明日方舟玩家与C++图像识别开发者的游戏自动化辅助工具,聚焦日常任务一键化执行,解决重复操作耗时、多线程识别稳定性差等实际痛点。资源包共2000个文件,主体为1487个JSON配置文件(定义任务流程与图像匹配规则)、162个CPP/190个H头文件(核心图像处理与ADB控制逻辑),辅以31个Python脚本(用于模型预处理与调试)、18个Shell与7个Dart文件(跨平台适配与移动端通信),整体124.87MB,结构层次分明,模块解耦清晰。已有345人学习下载,涵盖Roguelike战斗、基建生产、自动抽卡、作战记录识别等完整功能链路,代码中可见StageDropsImageAnalyzer、AutoRecruitTask、CombatRecordRecognitionTask等关键模块,提供可复用的OpenCV图像预处理流水线、动态阈值适配策略及反检测的人类行为模拟机制,适合进阶学习计算机视觉落地与游戏自动化工程实践。
1. 明日方舟游戏助手:不是外挂,是用图像识别把日常任务“自动化流水线”跑起来的真实工程
你每天花20分钟点开明日方舟,刷理智、清剿、收源石锭、领登录奖励、点基建换班——这些操作没有逻辑分支,全是固定路径、固定UI位置、固定响应延迟。它本质上是一套高度结构化的视觉交互协议,而非需要AI推理的开放世界。正因如此,基于图像识别技术实现一键完成明日方舟全部日常任务,不是玄学,而是可复现、可调试、可长期维护的桌面自动化工程。它不修改游戏进程、不注入DLL、不调用未公开API,只靠屏幕截图+模板匹配+坐标点击闭环,就能稳定跑满365天。适合两类人:一是被重复操作消磨耐心的中重度玩家,二是想练手真实CV+自动化落地的开发者——这里没有“全自动打图”这种黑匣子承诺,只有你能看懂每行代码在干什么、每个阈值为什么设成0.85而不是0.9、每次失败时该去哪张截图里找原因。我们拆解的,是Maa(MAA)同类工具背后最硬核的那一层:从原始像素到可靠动作的完整链路。
2. 用OpenCV+ADB构建最小可行识别流水线:本地截图→模板匹配→坐标计算→设备点击
明日方舟的UI极度规范:主界面底部固定4个功能入口图标,活动入口永远在右上角红点位置,基建宿舍按钮永远在左下角第3个。这种强约束,让模板匹配成为首选方案——它比YOLO轻量百倍,比OCR稳定十倍,且无需标注数据集。我们不用深度学习模型,用纯OpenCV的cv2.matchTemplate配合cv2.minMaxLoc,就能在100ms内完成一次全屏关键区域定位。整个流程分四步:截屏 → 裁剪ROI → 匹配模板 → 计算点击坐标。下面给出可直接运行的最小闭环脚本,所有路径和参数均按实际部署场景设定。
2.1 截屏与ROI裁剪:为什么必须先裁剪再匹配?
明日方舟在不同分辨率设备上UI缩放比例一致,但状态栏/导航栏高度不一。若直接全屏匹配,模板图与实际截图的顶部偏移会导致匹配失败。常见做法是:先用ADB截取整屏,再用OpenCV按固定比例裁掉顶部状态栏和底部导航栏,只保留游戏内容区。这样模板图只需适配一种分辨率(如1280×720),即可通用于所有安卓设备。
import cv2 import numpy as np import subprocess import time def adb_screenshot_and_crop(): # 步骤1:ADB截屏(输出到设备临时目录) subprocess.run(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png']) # 步骤2:拉取到本地 subprocess.run(['adb', 'pull', '/sdcard/screen.png', './screen_raw.png']) # 步骤3:OpenCV读取并裁剪(示例:裁掉顶部120px + 底部100px,保留中间区域) img = cv2.imread('./screen_raw.png') h, w = img.shape[:2] cropped = img[120:h-100, :] # 只保留中间内容区 cv2.imwrite('./screen_cropped.png', cropped) return cropped # 执行一次 cropped_img = adb_screenshot_and_crop()提示:
120和100不是 magic number,而是实测值。你需用adb shell wm size查出设备物理分辨率,再用截图工具量出状态栏+导航栏总高度。例如Pixel 6(1080×2340)实测为132px+112px=244px,此时应设为[244:h-0, :]。裁剪不对,后面所有匹配都白做。
2.2 模板匹配核心:matchTemplate的3种方法与阈值选择逻辑
OpenCV提供6种匹配方法,但对明日方舟这类高对比度UI,只有cv2.TM_CCOEFF_NORMED真正可靠——它返回-1到1之间的归一化相关系数,数值越接近1表示匹配度越高。其他方法(如TM_SQDIFF)在图标边缘有轻微抗锯齿时极易误判。
def find_template_in_roi(roi_img, template_path, threshold=0.85): template = cv2.imread(template_path, 0) # 灰度读取,减少干扰 roi_gray = cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY) # 关键:使用归一化相关系数法 res = cv2.matchTemplate(roi_gray, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) if max_val < threshold: return None # 未找到,返回空 # 计算模板中心坐标(相对于roi_img的左上角) h, w = template.shape center_x = max_loc[0] + w // 2 center_y = max_loc[1] + h // 2 return (center_x, center_y, max_val) # 示例:查找主界面“作战”按钮(模板图已存为template_zuozhan.png) pos = find_template_in_roi(cropped_img, './template_zuozhan.png', threshold=0.85) if pos: print(f"找到作战按钮,中心坐标({pos[0]}, {pos[1]}), 置信度{pos[2]:.3f}") else: print("未找到作战按钮")参数说明:
threshold=0.85是血泪经验阈值。低于0.8易误触(如把“好友”图标当成“作战”);高于0.9则漏检(UI轻微偏移或截图压缩失真)。cv2.cvtColor(..., cv2.COLOR_BGR2GRAY)强制转灰度,消除颜色通道噪声——明日方舟UI图标本身是单色矢量渲染,彩色信息反而引入冗余。max_loc是模板左上角坐标,+ w//2, + h//2才是人类点击的中心点,这是新手最常翻车的点:直接点左上角会点歪。
2.3 坐标映射与ADB点击:如何把屏幕坐标变成设备真实点击?
find_template_in_roi返回的是裁剪后ROI图像内的坐标(x,y),而ADB的input tap命令需要的是设备原始分辨率下的绝对坐标。因此必须做坐标还原:
def convert_roi_to_device_coord(roi_x, roi_y, crop_top=120, crop_bottom=100): # 假设原始分辨率为1280x720(你的设备需实测) orig_h = 720 orig_w = 1280 # ROI高度 = orig_h - crop_top - crop_bottom roi_h = orig_h - crop_top - crop_bottom # ROI内y坐标 → 原始y坐标:加上裁剪掉的顶部高度 device_y = roi_y + crop_top # x坐标不变(水平方向未裁剪) device_x = roi_x return (device_x, device_y) # 接上例 if pos: dev_x, dev_y = convert_roi_to_device_coord(pos[0], pos[1]) # 执行ADB点击 subprocess.run(['adb', 'shell', 'input', 'tap', str(dev_x), str(dev_y)]) time.sleep(1.2) # 等待动画完成,避免连点注意:
crop_top和crop_bottom必须与adb_screenshot_and_crop()中裁剪值严格一致。若设备实际分辨率为2160×1080,则orig_h=1080,roi_h=1080-132-112=836,此时dev_y = roi_y + 132。错1px,点击就偏出按钮范围。
3. 构建任务状态机:用有限状态机(FSM)驱动全流程,拒绝“死循环点击”
一键完成全部日常任务,本质是状态流转:从主界面→进入作战→选择关卡→开始行动→等待结束→返回主界面→进入基建……若用简单while循环硬等,极易因网络延迟、加载卡顿、弹窗遮挡导致流程断裂。正确做法是定义清晰的状态节点与转移条件,每个状态只做一件事,并用图像识别结果作为状态跳转的唯一判决依据。
3.1 状态定义与转移规则表
| 当前状态 | 触发条件(图像识别结果) | 下一状态 | 动作 |
|---|---|---|---|
MAIN_MENU | 检测到“作战”按钮存在 | ENTER_COMBAT | 点击“作战” |
ENTER_COMBAT | 检测到“关卡选择”标题存在 | SELECT_STAGE | 点击目标关卡(如“CE-5”) |
SELECT_STAGE | 检测到“开始行动”按钮存在 | START_MISSION | 点击“开始行动” |
START_MISSION | 检测到“行动结束”弹窗或“返回”按钮 | END_MISSION | 点击“返回”或“确认” |
END_MISSION | 检测到主界面“作战”按钮再次出现 | MAIN_MENU | 流程完成,进入下个任务 |
关键设计原则:每个状态只检测一个强特征,且该特征必须是当前界面独占性存在。例如
MAIN_MENU状态只检测“作战”,不同时检测“好友”“邮件”——因为“好友”可能有红点,“邮件”可能无红点,但“作战”永远可见。状态越单一,鲁棒性越强。
3.2 状态机核心循环:带超时保护与重试机制
class ArknightsFSM: def __init__(self): self.state = 'MAIN_MENU' self.max_retries = 3 self.timeout_sec = 15 # 每个状态最长等待时间 def run_one_cycle(self): start_time = time.time() retry_count = 0 while retry_count < self.max_retries: # 1. 截图裁剪 roi_img = adb_screenshot_and_crop() # 2. 根据当前状态执行识别 if self.state == 'MAIN_MENU': pos = find_template_in_roi(roi_img, './template_zuozhan.png', 0.85) if pos: self._click_device(pos[0], pos[1]) self.state = 'ENTER_COMBAT' return True elif self.state == 'ENTER_COMBAT': pos = find_template_in_roi(roi_img, './template_ce5.png', 0.82) # CE-5图标 if pos: self._click_device(pos[0], pos[1]) self.state = 'SELECT_STAGE' return True # ... 其他状态省略,结构相同 # 3. 超时判断 if time.time() - start_time > self.timeout_sec: print(f"状态 {self.state} 超时,重试第{retry_count+1}次") retry_count += 1 time.sleep(2) continue print(f"状态 {self.state} 连续{self.max_retries}次失败,流程中断") return False def _click_device(self, roi_x, roi_y): dev_x, dev_y = convert_roi_to_device_coord(roi_x, roi_y) subprocess.run(['adb', 'shell', 'input', 'tap', str(dev_x), str(dev_y)]) time.sleep(1.2) # 启动状态机 fsm = ArknightsFSM() for _ in range(10): # 最多执行10个状态跳转 if not fsm.run_one_cycle(): break逻辑说明:
- 每次循环只尝试一次状态跳转,成功即return,失败则重试或超时退出。
timeout_sec=15防止卡死:若15秒内没识别到目标,说明可能进错界面或加载异常,主动重试比死等更可靠。max_retries=3是平衡效率与鲁棒性的经验值:重试1次太激进,5次太拖沓。
4. 避坑:图像识别在明日方舟自动化中的5个致命陷阱与解法
图像识别看似简单,但在明日方舟这种动态UI游戏中,稍不注意就会陷入“明明截图一模一样却匹配不到”的玄学困境。以下是我在3台不同安卓设备、5个游戏版本迭代中踩过的5个真实坑,每个都附带现象、根因和可立即验证的解法。
4.1 现象:模板图在PS里和截图完全重合,但matchTemplate返回置信度0.3
原因:截图经过ADB传输被JPEG压缩,高频细节(如图标边缘锐度)丢失,而模板图是PNG无损图。两者直方图分布差异导致相关系数暴跌。
解法:统一用JPEG保存模板图,并开启相同压缩质量。在Python中用cv2.imwrite('./template.jpg', template, [cv2.IMWRITE_JPEG_QUALITY, 95])生成模板,再用cv2.imread('./template.jpg', 0)读取。实测将置信度从0.3提升至0.87。
4.2 现象:白天能识别,晚上开夜灯后同一模板匹配失败
原因:明日方舟夜间模式会整体降低UI亮度并添加蓝调滤镜,RGB通道偏移导致灰度转换后纹理失真。
解法:放弃RGB转灰度,改用HSV空间的V通道(明度)进行匹配。hsv = cv2.cvtColor(roi_img, cv2.COLOR_BGR2HSV); v_channel = hsv[:,:,2]。V通道对色相变化不敏感,只反映亮度结构,夜间匹配成功率从42%升至91%。
4.3 现象:同一台设备,重启游戏后首次识别慢3秒,后续变快
原因:游戏启动初期,部分UI资源未预加载,图标渲染存在1~2帧延迟,导致截图恰好拍到“半渲染”状态(如文字模糊、图标缺边)。
解法:在关键状态跳转前,强制等待2帧渲染完成。subprocess.run(['adb', 'shell', 'input', 'keyevent', 'KEYCODE_DPAD_CENTER']); time.sleep(0.1)—— 发送一次无效按键触发渲染刷新,比盲等更精准。
4.4 现象:高分辨率设备(2K屏)上坐标点击总是偏右下角
原因:ADB默认截图为设备逻辑分辨率(如1440×3200),但input tap命令按物理像素执行。当设备启用了“字体大小/显示大小”缩放时,逻辑坐标与物理坐标不一致。
解法:用adb shell wm density获取当前密度,再用adb shell wm size获取逻辑分辨率,计算缩放系数。例如density=320,size=1440x3200,则物理宽度=1440×(320/160)=2880px。点击坐标需乘以320/160=2.0。不查density,永远点不准。
4.5 现象:连续运行2小时后,识别准确率从99%跌到60%
原因:Android系统内存压力增大,截图服务(screencap)开始丢帧或返回缓存旧图,导致screen.png内容滞后于实际屏幕。
解法:每次截屏前先清空设备端缓存。subprocess.run(['adb', 'shell', 'rm', '/sdcard/screen.png']); time.sleep(0.05)。实测可将长时运行准确率稳定在98%以上。
5. 进阶技巧:用模板图集+置信度加权,解决“图标动态变化”难题
明日方舟的某些按钮会随状态改变外观:例如“基建”按钮在有新订单时显示红点,“好友”按钮在有未读消息时显示数字徽章。若只用一张静态模板图,红点出现时匹配就会失败。解决方案不是训练YOLO检测红点,而是构建模板图集(Template Set)—— 为同一UI元素准备多张不同状态的模板图,匹配时取最高置信度结果。
5.1 模板图集构建规范:命名即语义
不要把所有模板图堆在一个文件夹里。按“UI元素_状态_分辨率”命名,例如:
infra_normal_1280x720.png(基建无红点)infra_redpoint_1280x720.png(基建有红点)mail_unread_1280x720.png(邮件有数字1)mail_read_1280x720.png(邮件无数字)
为什么必须带分辨率后缀?因为不同设备DPI下,红点大小会按比例缩放。1280×720的红点在2560×1440设备上会小一半,单独匹配效果远好于用resize强行缩放。
5.2 加权匹配算法:用置信度投票替代单次判决
def find_best_match_from_set(roi_img, template_dir, base_name, threshold=0.75): """ base_name: 如 'infra',自动匹配 infra_*.png 返回: (best_template_path, best_confidence, best_coord) """ import glob templates = glob.glob(f"{template_dir}/{base_name}_*.png") best_conf = -1 best_result = None for tpath in templates: pos = find_template_in_roi(roi_img, tpath, threshold=0.7) if pos and pos[2] > best_conf: best_conf = pos[2] best_result = (tpath, pos[2], (pos[0], pos[1])) return best_result # 使用示例 result = find_best_match_from_set(cropped_img, './templates/', 'infra') if result: print(f"匹配到{result[0]},置信度{result[1]:.3f}") self._click_device(result[2][0], result[2][1])参数设计逻辑:
threshold=0.7比单图匹配更低,因为图集内各模板互斥,允许较低阈值触发投票。- 返回
best_result包含模板路径,可用于日志追踪:“本次点击依据 infra_redpoint_1280x720.png 匹配”。- 若所有模板置信度都<0.7,则返回None,进入异常处理流程(如截图存档供人工分析)。
5.3 动态模板更新:当游戏更新UI时,如何零停机维护?
每次明日方舟大版本更新,UI微调不可避免。手动重采所有模板图效率极低。我的做法是:在状态机中加入“模板缺失自检”模块。当某个状态连续3次匹配失败,自动触发:
- 截取当前ROI区域保存为
./debug/missing_infra_20240520_142301.png - 用
cv2.imshow()弹窗展示该图 - 提示用户:“未找到基建按钮,请在弹窗中框选按钮区域,按c键保存为新模板”
- 用户用鼠标拖拽选中区域 → 按c → 自动保存为
infra_auto_1280x720.png并加入图集
def auto_capture_template(roi_img, element_name): cv2.namedWindow('Select Template') roi_copy = roi_img.copy() cv2.imshow('Select Template', roi_copy) roi_copy = cv2.cvtColor(roi_copy, cv2.COLOR_BGR2GRAY) # 等待用户框选(此处简化为全图,实际用cv2.setMouseCallback) # ... 省略交互逻辑 ... # 假设用户已框选,提取区域 selected_roi = roi_copy[100:180, 200:280] # 示例坐标 timestamp = time.strftime("%Y%m%d_%H%M%S") path = f"./templates/{element_name}_auto_1280x720_{timestamp}.png" cv2.imwrite(path, selected_roi) print(f"已保存新模板:{path}") # 在状态机中调用 if self.state == 'MAIN_MENU' and not pos: auto_capture_template(cropped_img, 'infra')这套机制让我在最近3次游戏热更后,平均10分钟内完成全部模板更新,全程无需停机。
我坚持不用任何第三方封装库(如Maa、Airtest),因为只有亲手写过cv2.matchTemplate的每一行参数,才敢在凌晨三点服务器告警时,盯着日志里那行max_val=0.849判断是阈值该调高还是模板该重采。图像识别不是魔法,它是像素、阈值、坐标、状态的精密咬合。希望帮到你。
本文还有配套的精品资源,点击获取