简介:这是一份面向明日方舟玩家的自动化辅助工具源码,基于图像识别技术实现日常任务的一键执行,适合具备C++基础、对计算机视觉与游戏自动化感兴趣的中高级开发者研究学习。资源包共2000个文件,约124.87MB,以1487个json配置数据、190个h头文件、162个cpp源文件为主,辅以hpp、py脚本、go与dart代码及yaml、md文档,覆盖任务调度、战斗识别、基建生产、自动招募等模块。已有344人学习下载。读者可从中获取完整的图像识别落地思路,包括屏幕元素捕捉、灰度化与降噪预处理、特征提取及分类器训练,并借助OpenCV与多线程并行计算提升识别效率;同时能参考任务触发条件、执行步骤与结果验证的完整逻辑设计,理解光照、角度、分辨率变化下的算法优化与反作弊规避思路,适合作为C++图像识别项目实战的参考范例。
1. 明日方舟日常太肝?图像识别助手把重复操作压成一次点击
基建换班、刷理智、领任务奖励、公招识别,这些事单看每件只要几十秒,但一天点下来,手指是真的酸。更麻烦的是它们高度重复,没有任何策略乐趣,纯粹是消耗时间。我最初的想法很简单:能不能让程序看着屏幕,自己判断当前在哪个界面,然后点该点的按钮?这就是「明日方舟游戏助手」要解决的问题——基于图像识别技术,实现一键完成日常任务。它适合两类人:一是每天必清日常但不想手动点的老玩家,二是想拿一个真实项目练图像识别落地的新手。核心逻辑不复杂:截屏、模板匹配、模拟点击,循环执行。但真做起来,分辨率适配、界面误判、点击偏移这些坑一个都不会少。下面把我自己跑通的方案拆开讲,从环境搭到参数调,再到翻车记录,尽量让你少走弯路。
2. 图像识别驱动点击:方案选型与最小可跑框架
2.1 为什么选模板匹配而不是深度学习
标题里写的是「图像识别技术」,热搜词里还挂着「深度学习图像识别」,很多人第一反应是上 YOLO 或者 CNN 分类器。我一开始也这么想,后来发现方向错了。明日方舟的日常任务界面有几个特点:UI 元素固定、按钮样式统一、背景变化小。这种场景下,模板匹配的准确率和速度都碾压深度学习方案。
具体对比一下。深度学习方案需要标注数据、训练模型、调参,一套下来没有几天搞不定,而且换分辨率还得重新标注。模板匹配只需要截几张图当模板,OpenCV 的matchTemplate直接跑,单次匹配在 1080P 下通常 10 到 30 毫秒。对于「找到按钮就点」这种需求,模板匹配是性价比最高的选择。
那深度学习什么时候用?当你要识别的是动态内容,比如公招标签的 OCR 识别、关卡掉落的物品图标分类,这些用模板匹配就不行了。我的方案是混合的:界面导航和按钮点击用模板匹配,文字识别用 OCR,物品识别才考虑轻量分类模型。这样既保证了速度,又覆盖了全部日常任务。
提示:不要一上来就追求「全深度学习」,先把模板匹配跑通,你会发现 80% 的操作都能覆盖。
2.2 环境搭建与依赖安装
我用的技术栈是 Python + OpenCV + ADB。ADB 负责截屏和模拟点击,OpenCV 负责图像匹配。这套组合在 Windows 和 macOS 上都能跑,Linux 也行但 ADB 驱动偶尔需要额外配置。
先装依赖:
pip install opencv-python numpy pillowADB 需要单独下载 Android Platform Tools,解压后把路径加到系统环境变量里。验证 ADB 是否可用:
adb version adb devicesadb devices应该列出你的设备或模拟器。如果是模拟器,常见端口是 5555 或 62001,具体看模拟器设置。真机需要开启 USB 调试。
这里有个细节:截屏方式有两种。一种是adb exec-out screencap -p,直接输出 PNG 到标准输出,速度快但部分设备兼容性差。另一种是adb shell screencap存到设备再拉回来,稳但慢。我一般先用第一种,如果花屏或截出来是黑屏,再换第二种。
import subprocess import cv2 import numpy as np def screenshot(fast=True): if fast: result = subprocess.run( ["adb", "exec-out", "screencap", "-p"], capture_output=True ) img_array = np.frombuffer(result.stdout, dtype=np.uint8) else: subprocess.run(["adb", "shell", "screencap", "-p", "/sdcard/screen.png"]) subprocess.run(["adb", "pull", "/sdcard/screen.png", "./screen.png"]) img_array = np.fromfile("./screen.png", dtype=np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) return img这段代码做了两件事:通过 ADB 拿到屏幕截图,然后用 OpenCV 解码成 numpy 数组。fast=True走exec-out通道,适合大多数情况;如果返回的图是空的或者花屏,把fast改成False走文件传输通道。注意np.frombuffer和np.fromfile的区别,前者从内存缓冲区读,后者从磁盘文件读,别搞混。
2.3 模板匹配的核心参数与点击映射
模板匹配的原理很简单:拿一张小图(模板)在大图(截图)上滑动,计算每个位置的相似度,找到最相似的位置。OpenCV 提供了cv2.matchTemplate,返回一个相似度矩阵,再用cv2.minMaxLoc找到最大值的位置。
def find_template(screen, template_path, threshold=0.8): template = cv2.imread(template_path) result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) if max_val >= threshold: h, w = template.shape[:2] center_x = max_loc[0] + w // 2 center_y = max_loc[1] + h // 2 return (center_x, center_y, max_val) return NoneTM_CCOEFF_NORMED是归一化相关系数匹配,返回值在 -1 到 1 之间,越接近 1 越相似。threshold是阈值,低于这个值就认为没找到。我一般设 0.8 起步,如果误判多就往上调到 0.85 或 0.9,如果漏判多就降到 0.75。max_loc是匹配区域的左上角坐标,加上模板宽高的一半就是中心点,这个中心点就是我们要点击的位置。
点击用 ADB 的input tap:
def tap(x, y): subprocess.run(["adb", "shell", "input", "tap", str(x), str(y)])看起来很简单对吧?但这里有个血泪经验:不同设备的屏幕分辨率不一样,模板是在 1080P 下截的,换到 720P 设备上匹配就会失败。解决办法有两个:一是按分辨率准备多套模板,二是把截图和模板都缩放到统一尺寸再匹配。我选的是第二种,后面避坑章节会详细讲。
3. 日常任务流水线:从基建收菜到刷理智的完整实现
3.1 任务调度器:状态机驱动的界面导航
日常任务不是一条直线走到底的。你可能在主页,点进基建,收完菜回到主页,再点进作战,刷完理智又回到主页。这种流程用状态机来描述最自然:每个界面是一个状态,每个操作是状态之间的转移。
我定义了一个简单的状态机:
class TaskRunner: def __init__(self): self.state = "home" self.max_retry = 3 def run(self): while True: screen = screenshot() if self.state == "home": self.handle_home(screen) elif self.state == "base": self.handle_base(screen) elif self.state == "combat": self.handle_combat(screen) else: break def handle_home(self, screen): # 优先检查基建入口 pos = find_template(screen, "templates/base_icon.png") if pos: tap(pos[0], pos[1]) self.state = "base" return # 检查作战入口 pos = find_template(screen, "templates/combat_icon.png") if pos: tap(pos[0], pos[1]) self.state = "combat" return # 都没找到,可能弹了公告,点关闭 pos = find_template(screen, "templates/close_btn.png") if pos: tap(pos[0], pos[1])这个调度器的逻辑是:每次循环截一张图,根据当前状态决定下一步操作。handle_home里按优先级检查各个入口,找到就点进去并切换状态。如果都没找到,可能是弹了公告或者网络卡了,尝试点关闭按钮。
这里的关键是「优先级」和「兜底」。优先级决定了先检查哪个入口,兜底决定了找不到任何已知元素时怎么办。我一般会加一个「未知界面」的处理:截一张图存下来,方便事后分析是哪个界面没覆盖到。
3.2 基建换班:识别干员疲劳与自动替换
基建是日常任务里最繁琐的部分。你需要检查每个设施里干员的疲劳度,疲劳了就得换人。用图像识别做这件事,核心是识别两个东西:设施图标和干员头像。
设施图标用来定位当前在哪个设施,干员头像用来判断是谁、疲劳度如何。疲劳度的识别比较麻烦,因为那个进度条是动态的。我的做法是:不直接识别疲劳度数值,而是识别「疲劳」这个状态图标。当干员疲劳时,头像上会出现一个明显的标记,用模板匹配找到这个标记就知道该换人了。
def check_fatigue(screen, facility_region): # 在设施区域内查找疲劳标记 fatigue_icon = cv2.imread("templates/fatigue_mark.png") region = screen[facility_region[1]:facility_region[3], facility_region[0]:facility_region[2]] result = cv2.matchTemplate(region, fatigue_icon, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) if max_val >= 0.75: return True, (max_loc[0] + facility_region[0], max_loc[1] + facility_region[1]) return False, Nonefacility_region是设施在屏幕上的区域,用左上角和右下角坐标表示。先裁剪出这个区域再匹配,比全屏匹配快很多,也减少了误判。阈值设 0.75 是因为疲劳标记比较小,设太高容易漏。
换人的逻辑是:点击疲劳干员,进入干员选择界面,选一个未疲劳的干员,确认。这里有个坑:干员列表可能很长,需要滑动。我的做法是只选列表前几个,因为通常前排都是低星干员,用来换班正好。
3.3 刷理智:关卡选择与代理指挥的自动化
刷理智是日常任务里最耗时的部分,但自动化反而最简单,因为流程固定:选关卡、点开始、等结算、点继续。唯一需要注意的是「代理指挥」是否勾选,以及理智是否够。
def farm_stage(screen, stage_name): # 找到关卡入口 pos = find_template(screen, f"templates/stage_{stage_name}.png") if not pos: return False tap(pos[0], pos[1]) time.sleep(1) # 检查代理指挥是否勾选 screen = screenshot() proxy_pos = find_template(screen, "templates/proxy_check.png") if not proxy_pos: # 没勾选就点一下 proxy_btn = find_template(screen, "templates/proxy_btn.png") if proxy_btn: tap(proxy_btn[0], proxy_btn[1]) # 点开始 start_pos = find_template(screen, "templates/start_btn.png") if start_pos: tap(start_pos[0], start_pos[1]) return True return Falsestage_name是关卡代号,比如1-7或CE-5。模板文件名对应关卡按钮的截图。proxy_check是勾选状态的截图,proxy_btn是未勾选状态的截图。先检查是否已勾选,没勾选才点。
刷完一局后,会出现结算界面,点继续回到关卡选择,然后循环。这里要加一个理智检查:如果理智不够,点开始会弹提示,识别到这个提示就停止刷图。
注意:刷理智的循环一定要加超时和最大次数限制,否则理智用完后程序会卡在结算界面无限循环。
3.4 公招识别与自动领取奖励
公招识别是唯一需要 OCR 的地方。公招标签是文字,模板匹配搞不定。我用的是pytesseract,配合图像预处理提高识别率。
import pytesseract def recognize_recruit_tags(screen, region): # 裁剪标签区域 tags_img = screen[region[1]:region[3], region[0]:region[2]] # 转灰度 gray = cv2.cvtColor(tags_img, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # OCR text = pytesseract.image_to_string(binary, lang="chi_sim") tags = [t.strip() for t in text.split() if t.strip()] return tagsregion是标签区域的坐标。先转灰度去掉颜色干扰,再二值化让文字更清晰,最后用pytesseract识别。lang="chi_sim"指定简体中文,需要提前安装中文语言包。
识别出标签后,根据标签组合判断该选什么。比如「高级资深干员」必选,「资深干员」加「输出」也是好组合。这部分逻辑可以写死,也可以做成配置文件。
领取奖励就简单了,找到「领取」按钮点一下,如果有多个奖励就循环点,直到按钮消失。
4. 避坑指南:分辨率、误判与点击偏移的排查记录
4.1 现象:换设备后所有模板都匹配失败
原因:模板是在 1080P 下截的,新设备是 720P 或 2K,像素尺寸对不上,matchTemplate自然找不到。
解决:统一缩放。在匹配前把截图缩放到模板的基准分辨率,或者把模板缩放到截图的分辨率。我选的是前者,因为截图缩放一次就行,模板不用动。
def resize_to_base(screen, base_width=1920): h, w = screen.shape[:2] if w == base_width: return screen scale = base_width / w new_h = int(h * scale) return cv2.resize(screen, (base_width, new_h))base_width是模板截取时的屏幕宽度,我一般用 1920。缩放后所有模板都能正常匹配。注意点击坐标也要按比例换算回去,否则点偏。
4.2 现象:明明按钮在屏幕上,但匹配不到
原因:可能是按钮有动态效果,比如呼吸灯、高亮闪烁,导致截图和模板不完全一致。也可能是按钮被部分遮挡,比如弹了个小窗。
解决:准备多张模板。同一个按钮截 3 到 5 张不同状态的图,匹配时只要有一张命中就算找到。另外,匹配前先检查有没有弹窗,有弹窗先关弹窗。
def find_template_multi(screen, template_paths, threshold=0.8): for path in template_paths: pos = find_template(screen, path, threshold) if pos: return pos return Nonetemplate_paths是模板路径列表,按优先级排列。这个函数会依次尝试每个模板,返回第一个匹配成功的结果。
4.3 现象:点击位置总是偏一点,点不到按钮
原因:ADB 的input tap坐标是相对于屏幕的,但截图可能有状态栏或导航栏,导致截图坐标和屏幕坐标不一致。另外,模板匹配返回的是左上角坐标,如果直接点左上角,就会点到按钮边缘。
解决:一是确认截图是否包含状态栏,如果包含,点击时要减去状态栏高度。二是点击时用中心点,不要用左上角。三是加一个偏移量校准,手动测几次,把偏差值写死。
def tap_with_offset(x, y, offset_x=0, offset_y=0): subprocess.run(["adb", "shell", "input", "tap", str(x + offset_x), str(y + offset_y)])offset_x和offset_y是校准偏移量,不同设备可能不一样。我一般先在主页测一个按钮,点几次看偏差多少,然后把值填进去。
4.4 现象:程序跑着跑着就卡住了,不知道在哪个界面
原因:状态机没有覆盖所有可能的界面,遇到未知界面就卡住了。或者网络延迟导致界面加载慢,截图截到了加载中的画面。
解决:加超时和未知界面处理。每次操作后等待一段时间再截图,如果连续几次都识别不到已知元素,就截一张图存下来,然后尝试点返回或关闭按钮。
def handle_unknown(screen, retry_count): if retry_count > 3: cv2.imwrite(f"unknown_{time.time()}.png", screen) # 尝试点返回 subprocess.run(["adb", "shell", "input", "keyevent", "4"]) return True return Falseretry_count是连续未知次数,超过 3 次就存图并点返回键。存图是为了事后分析,看看是哪个界面没覆盖到。
4.5 现象:OCR 识别公招标签总是出错
原因:游戏字体比较特殊,pytesseract默认模型识别率不高。另外,标签背景有颜色,二值化阈值没调好会导致文字断裂或粘连。
解决:一是用--psm参数调整识别模式,公招标签是单行文字,用--psm 7比默认模式准。二是二值化阈值多试几个,找到文字最清晰的那个。三是可以自己训练一个简单的 OCR 模型,但成本较高,先用调参解决。
text = pytesseract.image_to_string( binary, lang="chi_sim", config="--psm 7 -c tessedit_char_whitelist=高级资深干员输出防护治疗辅助术师狙击重装医疗先锋近卫特种" )--psm 7表示把图像当作单行文本处理。tessedit_char_whitelist是白名单,限制只识别这些字符,能显著提高准确率。
5. 进阶技巧:用多尺度匹配和日志回放把成功率拉到 95% 以上
模板匹配最大的问题是尺度不变性差。同一个按钮,在不同分辨率下大小不一样,直接匹配就会失败。虽然前面说了统一缩放,但缩放本身会引入模糊,降低匹配精度。更好的做法是多尺度匹配:把模板缩放到多个尺寸,分别匹配,取最高分。
def multi_scale_match(screen, template_path, scales=None, threshold=0.8): if scales is None: scales = [0.8, 0.9, 1.0, 1.1, 1.2] template = cv2.imread(template_path) best_match = None best_score = 0 for scale in scales: h, w = template.shape[:2] new_size = (int(w * scale), int(h * scale)) resized = cv2.resize(template, new_size) if resized.shape[0] > screen.shape[0] or resized.shape[1] > screen.shape[1]: continue result = cv2.matchTemplate(screen, resized, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) if max_val > best_score: best_score = max_val best_match = (max_loc[0] + new_size[0] // 2, max_loc[1] + new_size[1] // 2, max_val) if best_score >= threshold: return best_match return Nonescales是缩放比例列表,我一般用 0.8 到 1.2,步长 0.1。每个比例都匹配一次,记录最高分。这样即使模板和实际按钮尺寸有偏差,也能找到。代价是匹配时间变成原来的 5 倍,但单次匹配也就几十毫秒,完全可以接受。
另一个技巧是日志回放。每次操作都记录截图、匹配结果、点击坐标,存成结构化日志。出问题的时候,把日志里的截图拿出来重新跑一遍匹配,看看是哪个环节出了问题。这个习惯帮我省了大量调试时间。
import json import time def log_action(action, screen, result): log_entry = { "time": time.time(), "action": action, "result": result, "screen_path": f"logs/screen_{int(time.time())}.png" } cv2.imwrite(log_entry["screen_path"], screen) with open("logs/actions.jsonl", "a") as f: f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")action是操作名称,result是匹配结果,screen_path是截图路径。用 JSONL 格式追加写入,方便后续分析。截图会占磁盘,记得定期清理。
最后说一个我踩过的坑:不要试图用一套模板适配所有服务器。不同服务器的 UI 可能有细微差别,比如按钮颜色、字体渲染。我的做法是按服务器分目录存模板,启动时根据当前界面自动判断是哪个服务器,然后加载对应的模板集。
这套方案跑下来,日常任务的成功率能稳定在 95% 以上。剩下的 5% 主要是网络波动和游戏更新导致的 UI 变化,前者靠重试解决,后者靠更新模板解决。我现在的习惯是每次游戏大版本更新后,先跑一遍日志模式,把新界面的截图都存下来,更新模板库,再跑自动模式。这个习惯让我很少翻车。希望帮到你。
本文还有配套的精品资源,点击获取