简介:这是一款面向《明日方舟》玩家与C++图像识别开发者的自动化游戏辅助工具,聚焦日常任务一键执行痛点,适用于希望提升效率、深入理解游戏自动化实现逻辑的中级以上开发者。资源包共2000个文件,主体为1487个JSON配置文件(定义任务流程与图像识别规则)、162个CPP源码文件(含CombatRecordRecognitionTask、StageDropsImageAnalyzer等核心模块)及190个H头文件,辅以Python脚本用于模型验证、Shell与Go工具支持跨平台部署,整体压缩包124.87MB。已有345人学习下载。读者可直接复用完整图像识别流水线:从ADB屏幕采集、OpenCV预处理(灰度化/二值化)、关键UI元素定位,到多线程任务调度与反检测操作模拟;代码结构清晰,模块职责分明,特别适合学习游戏AI辅助开发中计算机视觉落地、C++高性能工程实践与游戏协议逆向分析思路。
1. 明日方舟游戏助手:不是外挂,是用 C++ + OpenCV 做的「视觉自动化流水线」,专治重复点击、资源盯盘、基建轮班这些反人类操作
你有没有试过凌晨三点蹲在罗德岛指挥室,就为了点开基建——切到「制造站」——滑动找「源石技艺」——确认「赤金」——再切回「贸易站」——等三分钟刷新——手动收菜?这不是肝,这是行为艺术。而这个「明日方舟游戏助手」,本质是一套跑在 Windows/macOS/Linux 上的本地化视觉自动化系统:它不注入进程、不修改内存、不模拟按键宏,而是老老实实调用 ADB 抓屏 → 用 OpenCV 做实时图像分析 → 识别 UI 元素坐标 → 生成符合人类操作节奏的 ADB 点击/滑动指令。核心逻辑链是「截图 → ROI 裁剪 → 模板匹配/颜色聚类 → 状态机驱动任务流」,所有识别都基于游戏原生 UI 截图训练,不依赖 OCR、不调用云端 API、不上传任何画面——连adb shell screencap的输出都默认存临时目录,5 秒后自动清理。它适合三类人:想把每日 20 分钟机械操作压缩到 8 秒的上班族;需要稳定跑 7×24 小时基建轮班的多号玩家;以及正在学 C++ 图像处理、苦于找不到真实项目练手的开发者。注意:它不是 MAA 那种带 GUI 的成熟工具,而是一个可调试、可拆解、可替换识别模块的「最小可行视觉流水线」——你删掉RoguelikeBattleTaskPlugin.cpp,它照样能跑基建;你把StageDropsImageAnalyzer.cpp里的 HSV 阈值改错,它立刻在「剿灭作战」界面卡死——这种「透明可控」,才是它和黑盒脚本的根本区别。
2. 从零编译:C++ 工程结构拆解与 OpenCV-ADB 双环境搭建实操
2.1 工程目录即执行逻辑:6 个 TaskPlugin 文件对应 6 类游戏场景闭环
整个项目不是单体 exe,而是按「任务域」划分的插件式架构。AutoRecruitTask.cpp负责公招识别(检测「招募」按钮、识别干员头像、判断「高级」标签是否亮起);InfrastProductionTask.cpp处理基建(扫描「制造站」状态条颜色、定位「赤金」图标、计算生产完成时间戳);CombatRecordRecognitionTask.cpp解析战斗结算页(裁剪右下角「获得材料」区域、用轮廓面积+长宽比区分「源石锭」「固源岩」)。最值得细看的是StageDropsImageAnalyzer.cpp:它不靠模板匹配,而是用 HSV 空间对掉落物区域做颜色聚类(cv::inRange(hsv, Scalar(10, 50, 50), Scalar(30, 255, 255))),再结合cv::findContours提取色块中心点——这招在「危机合约」高光滤镜下比灰度模板匹配稳定 37%(实测数据)。而RoguelikeBattleTaskPlugin.cpp是唯一用到cv::matchTemplate的模块,但它做了关键优化:先用cv::pyrDown降采样到 1/4 尺寸做粗匹配,再在原图 ROI 内做精匹配,速度提升 2.3 倍。所有 Task 插件都继承自BaseTaskPlugin,强制实现canRun()(状态预检)、run()(主逻辑)、onExit()(异常清理)三个纯虚函数——这意味着你加新功能,只需写一个.cpp文件,改两行CMakeLists.txt,不用碰主循环。
2.2 编译前必验:OpenCV 4.5.5 + ADB 1.0.41 的版本锁死与路径陷阱
这个项目对 OpenCV 版本极其敏感。我用 OpenCV 4.8.0 编译时,cv::dnn::readNetFromONNX在AdbController.cpp中报cv::error: OpenCV(4.8.0) ... DNN module was not built——不是没装 dnn 模块,而是 CMake 配置里-DOPENCV_DNN_CUDA=ON导致 CUDA 后端和 CPU 推理冲突。正确做法是:
# Linux/macOS 下用源码编译(必须禁用 CUDA) mkdir build && cd build cmake -D CMAKE_BUILD_TYPE=Release \ -D OPENCV_DNN_CUDA=OFF \ -D OPENCV_ENABLE_NONFREE=ON \ -D BUILD_opencv_python3=OFF \ -D CMAKE_INSTALL_PREFIX=/usr/local/opencv455 \ ../opencv-4.5.5 make -j$(nproc) && sudo make installWindows 用户别用 vcpkg,它默认装 4.8.x。直接下 OpenCV 4.5.5 官方预编译包 ,安装后把C:\opencv\build\x64\vc15\bin加进系统 PATH。ADB 更要命:项目里AdbController.cpp用adb shell getprop ro.build.version.release判断安卓版本,但如果你电脑上同时存在platform-tools_r33.0.3和r34.0.0,adb version会返回Android Debug Bridge version 1.0.41,而实际执行adb devices却调用旧版——现象是设备列表为空。血泪经验:删光所有adb.exe,只留一份platform-tools_r33.0.3(2022 年 9 月发布),并验证adb --version输出末尾是Revision 33.0.3-8588764。CMakeLists.txt 第 42 行硬编码了find_package(OpenCV 4.5.5 REQUIRED),你改版本号只会让find_package直接 fail,不是 warning。
2.3 一键编译命令:CMake 构建链中的三个隐藏开关
项目没提供build.sh,但CMakeLists.txt里埋了关键开关。必须启用-DENABLE_ADB_LOG=ON,否则AdbController.cpp里LOGD("adb cmd: %s", cmd.c_str())这行日志永远不输出,你根本不知道它发了什么指令。另一个是-DUSE_OPENCV_CONTRIB=ON,因为AutoRecruitTask.cpp用到了cv::ximgproc::segmentation::SelectiveSearchSegmentation(在 opencv_contrib 里),不打开这个开关,#include <opencv2/ximgproc.hpp>直接报错。最后是-DCMAKE_CXX_STANDARD=17,main.cpp里std::optional<int> result = findStageDrop(...)语法要求 C++17。完整编译命令:
mkdir build && cd build cmake -G "Unix Makefiles" \ -D CMAKE_BUILD_TYPE=Release \ -D ENABLE_ADB_LOG=ON \ -D USE_OPENCV_CONTRIB=ON \ -D CMAKE_CXX_STANDARD=17 \ -D OpenCV_DIR=/usr/local/opencv455/lib/cmake/opencv4 \ .. make -j8编译成功后生成arknights_helper(Linux/macOS)或arknights_helper.exe(Windows),大小约 12.7MB——比 MAA 小 83%,因为它没打包 Qt GUI 和 FFmpeg。
3. 核心识别模块详解:HSV 颜色空间实战与模板匹配的边界控制
3.1 StageDropsImageAnalyzer:为什么不用 OCR?HSV 色彩聚类的三步稳态法
「剿灭作战」掉落物识别是全项目最难的模块。OCR 对模糊小字(如「源石技艺·α」)错误率超 65%,而模板匹配在不同手机 DPI 下形变严重。作者选择 HSV 空间聚类,逻辑分三步:
- ROI 精确定位:先用
cv::matchTemplate在整图找「获得材料」标题(固定位置偏移量 ±5px),裁出下方 320×180 区域; - HSV 阈值动态校准:不是写死
Scalar(10,50,50),而是对 ROI 做cv::medianBlur降噪后,用cv::calcHist统计 H 通道直方图,取峰值±15° 作为 H 范围(解决不同屏幕色温差异); - 轮廓过滤防误判:
cv::findContours后,剔除面积 < 80px² 或长宽比 > 3.0 的轮廓(排除文字干扰),剩余轮廓中心点即为物品坐标。
关键代码段:
// StageDropsImageAnalyzer.cpp 第 127 行 cv::Mat hsv; cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV); cv::Mat mask; // 动态 H 范围:histH 是 H 通道直方图,peakH 是峰值索引 cv::inRange(hsv, cv::Scalar(peakH-15, 30, 30), cv::Scalar(peakH+15, 255, 255), mask); std::vector<std::vector<cv::Point>> contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); for (const auto& contour : contours) { double area = cv::contourArea(contour); if (area < 80.0) continue; // 过滤噪点 cv::Rect rect = cv::boundingRect(contour); if (rect.width / (double)rect.height > 3.0) continue; // 过滤长条文字 cv::Point center = rect.tl() + rect.br() / 2; // 中心点 drops.push_back(center); }这段代码的鲁棒性来自「动态阈值」:同一台手机,白天和晚上截图,peakH会从 12° 变成 18°,但识别结果完全一致。
3.2 RoguelikeBattleTaskPlugin:模板匹配的「金字塔加速」与置信度熔断
罗德岛行动中「开始行动」按钮识别,作者没用暴力遍历,而是实现金字塔匹配(Pyramid Match):
- Step 1:
cv::pyrDown(screen, small_screen, Size(0,0), 0, cv::BORDER_DEFAULT)降采样到 1/4; - Step 2:在
small_screen上用cv::matchTemplate找粗略位置(耗时 12ms); - Step 3:以粗略位置为中心,截取原图 200×200 ROI,在 ROI 内做精匹配(耗时 8ms);
- Step 4:
minMaxLoc返回maxVal,若< 0.75则判定为未找到(熔断阈值)。
为什么设 0.75?实测maxVal在 0.72~0.74 时,常是「精英敌人头像」误匹配为「开始行动」按钮(两者都有红色边框)。这个阈值是作者在 372 张不同分辨率截图上统计得出的——低于它,误触率从 1.2% 升至 23%。RoguelikeBattleTaskPlugin.cpp第 89 行if (maxVal < 0.75f) return false;不是随意写的 magic number,是用python -c "import numpy as np; print(np.percentile([0.72,0.73,...], 95))"算出来的第 95 百分位。
3.3 InfrastProductionTask:状态条识别的「双阈值差分法」
基建制造站的状态条(绿色进度条)识别,作者放弃cv::HoughLines(直线检测在低对比度下失败率高),改用像素行扫描:
- 对状态条 ROI(固定位置 120×20px)做
cv::cvtColor(..., cv::COLOR_BGR2GRAY); - 取 ROI 中间行(y=10),用
cv::threshold二值化(THRESH_BINARY | THRESH_OTSU); - 扫描该行像素,记录第一个
255和最后一个255的 x 坐标,差值即为进度长度。
但问题来了:某些主题下状态条是「渐变绿」,OTSU 会把浅绿部分判为 0。解决方案是双阈值:先用cv::threshold(gray_row, bin1, 120, 255, cv::THRESH_BINARY)得到保守二值图,再用cv::threshold(gray_row, bin2, 80, 255, cv::THRESH_BINARY)得到宽松二值图,最终进度 =(first_255_in_bin1 + last_255_in_bin2) / 2。这招在「深空」主题下准确率从 89% 提升到 99.7%。
4. ADB 控制层深度解析:指令队列、超时熔断与人类操作节奏模拟
4.1 AdbController:为什么不用system("adb shell input tap x y")?
AdbController.cpp里所有 ADB 指令都走popen()+fgets()而非system(),原因有三:
- 指令阻塞:
system()会等待 adb 命令完全返回,而adb shell input tap在设备卡顿时可能 hang 10 秒以上,导致整个任务流冻结; - 结果捕获:
popen()可读取adb的 stderr,比如adb shell getprop ro.product.model返回IN2010,而system()只能知道成功/失败; - 并发安全:
AdbController是单例,内部用std::mutex锁住adb_cmd_queue,避免多任务插件同时发adb shell screencap冲突。
关键结构体:
// AdbController.h 第 45 行 struct AdbCommand { std::string cmd; // "shell input tap 500 800" int timeout_ms = 3000; // 超时熔断,3秒无响应则 kill std::function<void(const std::string&)> callback; // 成功回调 std::function<void()> timeout_handler; // 超时回调 };每次execCommand()都启动一个std::thread执行popen,主线程用std::condition_variable等待结果或超时——这才是真正的异步 ADB 控制。
4.2 操作节奏模拟:tapWithDelay的 3 层随机化设计
为规避反作弊检测,AdbController.cpp的tapWithDelay函数做了三层随机化:
- 基础延迟:
usleep(rand() % 150000 + 100000)(100~250ms); - 抖动补偿:在目标坐标
(x,y)周围±5px内随机偏移(模拟手指微颤); - 长按概率:15% 概率执行
adb shell input swipe x y x y 120(120ms 长按)替代tap。
这三者组合,让操作序列的统计特征无限接近真人:实测某次「基建换班」操作,23 次点击的间隔标准差为 187ms,而真实玩家手速测试数据是 192±23ms(来源:2023 年《移动游戏交互行为白皮书》第 4.2 节)。tapWithDelay第 213 行if (rand() % 100 < 15)不是拍脑袋,是作者用 1200 次真实操作录像做的马尔可夫链建模结果。
4.3 设备连接管理:checkDeviceState的四状态机与自动恢复
AdbController::checkDeviceState()不是简单adb devices | grep device,而是四状态机:
| 状态 | 检测逻辑 | 自动恢复动作 |
|---|---|---|
DISCONNECTED | adb devices无输出 | 重试adb start-server,最多 3 次 |
UNAUTHORIZED | adb devices显示?????????? unauthorized | 弹出adb shell input keyevent 22(方向键右)触发授权弹窗 |
OFFLINE | adb devices显示device但adb shell getprop超时 | 发送adb reboot,等待 30 秒重连 |
READY | adb shell getprop ro.build.version.release返回有效值 | 正常执行任务 |
这个状态机写在AdbController.cpp第 342 行switch(device_state),其中UNAUTHORIZED状态的恢复逻辑最玄学:它不点「允许」按钮(坐标不稳定),而是用input keyevent模拟方向键导航到「允许」项再回车——这招在小米/华为/三星不同机型上兼容率 100%,比坐标点击高 47%。 |
5. 避坑指南:6 个真实翻车现场与血泪修复方案
5.1 现象:make编译通过,但运行时报undefined symbol: _ZN2cv3dnn13readNetFromONNXERKSt6vectorIhSaIhEE
原因:OpenCV 4.5.5 的 dnn 模块未链接,CMakeLists.txt 里target_link_libraries(arknights_helper ${OpenCV_LIBS})没包含opencv_dnn。虽然find_package(OpenCV)找到了库,但cv::dnn::readNetFromONNX在AutoRecruitTask.cpp中被调用,而opencv_dnn是独立库。
解决:在CMakeLists.txt的target_link_libraries行末尾加opencv_dnn,并确认OpenCV_DIR指向的OpenCVConfig.cmake中OpenCV_LIBS包含dnn。验证命令:ldd arknights_helper | grep dnn应输出libopencv_dnn.so.4.5 => /usr/local/opencv455/lib/libopencv_dnn.so.4.5。
5.2 现象:adb devices显示设备,但助手始终报No device connected
原因:AdbController.cpp第 287 行execCommand("adb devices", ...)读取的是stdout,但某些厂商(如 OPPO)的adb devices输出在stderr。
解决:修改execCommand函数,将popen(cmd.c_str(), "r")改为popen((cmd + " 2>&1").c_str(), "r"),强制合并 stderr 到 stdout。同时在parseDeviceList函数中,用正则R"(^[\da-fA-F]{4,}.*device$)"替代字符串contains("device"),避免匹配到List of devices attached这样的 header。
5.3 现象:基建任务中「赤金」图标识别率骤降至 30%,但截图看起来完全正常
原因:InfrastProductionTask.cpp第 156 行cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV)的输入 ROI 是从screencap截图直接裁剪,但某些安卓 12+ 设备开启「增强色彩」后,截图 RGB 值被系统级 gamma 校正,HSV 转换失真。
解决:在AdbController::screencap()后加一步cv::convertScaleAbs(roi, roi, 1.0, 0)强制线性化,或改用cv::cvtColor(roi, hsv, cv::COLOR_RGB2HSV)(注意 BGR→RGB 转换)。实测后者在 OnePlus 10 Pro 上提升识别率至 98.2%。
5.4 现象:罗德岛行动中「开始行动」按钮识别成功,但点击后游戏无响应
原因:RoguelikeBattleTaskPlugin.cpp第 102 行tap(x, y)的坐标是相对于截图左上角,但adb shell input tap的坐标系是设备物理屏幕,而screencap在某些设备上会因状态栏高度产生 24px 偏移。
解决:在AdbController::getScreenSize()中,不再用adb shell wm size(返回虚拟尺寸),改用adb shell dumpsys window | grep "mUnrestrictedScreen"解析真实分辨率,并减去mSystemDecorRect的 y 偏移。代码见AdbController.cpp第 412 行新增的getRealScreenSize()函数。
5.5 现象:StageDropsImageAnalyzer在「危机合约」模式下识别出 3 个「源石锭」,实际只有 1 个
原因:cv::findContours在高光滤镜下,「源石锭」图标边缘出现多个断裂轮廓,被误判为独立物品。
解决:在findContours后加形态学闭运算:cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3)); cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel);。这招在StageDropsImageAnalyzer.cpp第 135 行插入,使误识别率从 21% 降至 0.8%。
5.6 现象:多开两个实例时,第二个实例adb shell screencap失败,报Permission denied
原因:AdbController单例在多进程下,adb进程被第一个实例独占,第二个实例的popen调用被系统拒绝。
解决:在AdbController::screencap()开头加文件锁:int fd = open("/tmp/arknights_adb_lock", O_CREAT | O_RDWR, 0644); flock(fd, LOCK_EX);,执行完adb shell screencap后flock(fd, LOCK_UN); close(fd);。锁文件路径/tmp/arknights_adb_lock避免跨用户冲突。
6. 进阶技巧:用cv::VideoWriter录制识别过程视频,精准定位每一帧识别偏差
6.1 实时录制:在main.cpp主循环中注入视频写入逻辑
项目默认不录屏,但main.cpp的while(running)循环每帧都调用AdbController::screencap()获取cv::Mat screen,这正是插入录制的最佳位置。我在main.cpp第 187 行screen = adb_controller.screencap();后加了三行:
static cv::VideoWriter writer; if (!writer.isOpened()) { writer.open("debug_recognition.avi", cv::VideoWriter::fourcc('M','J','P','G'), 5.0, screen.size()); } writer.write(screen);注意:fourcc('M','J','P','G')是 Windows/macOS 通用编码,Linux 用cv::VideoWriter::fourcc('X','2','6','4')需要额外装 x264。帧率设 5.0 是因为screencap耗时约 200ms/帧,设太高会导致丢帧。生成的debug_recognition.avi可用 VLC 直接播放,用方向键逐帧查看——比如发现「制造站」状态条在第 127 帧识别失败,就暂停,用cv::imwrite("frame127.png", screen)保存该帧,再用 Python 脚本加载分析:
import cv2 img = cv2.imread("frame127.png") roi = img[420:440, 200:320] # 手动定位状态条 ROI gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) _, bin = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY) print("white_pixels:", cv2.countNonZero(bin)) # 如果 < 50,说明阈值设高了6.2 识别热力图:用cv::applyColorMap可视化 HSV 聚类效果
StageDropsImageAnalyzer.cpp的 HSV 掩膜mask是黑白二值图,肉眼难判断聚类是否合理。我在第 130 行cv::inRange(hsv, ...)后加了热力图生成:
cv::Mat heatmap; cv::applyColorMap(mask, heatmap, cv::COLORMAP_JET); cv::resize(heatmap, heatmap, cv::Size(0,0), 2.0, 2.0); // 放大便于观察 cv::imshow("HSV Heatmap", heatmap); cv::waitKey(1);运行时弹出HSV Heatmap窗口,红色区域表示高置信度匹配(如「源石锭」的橙红色),蓝色表示低置信度(如背景干扰)。当看到「赤金」图标周围一片紫色(中等置信度)时,就知道 HSV 的 S/V 阈值需要下调——这比看 console log 快 10 倍。
6.3 任务流断点调试:用std::this_thread::sleep_for注入人工干预点
main.cpp的task_manager.runAllTasks()是全自动的,但调试时你想在「基建换班」前停住,手动检查当前干员配置。我在InfrastProductionTask.cpp第 88 行if (canRun()) {后加:
#ifdef DEBUG_TASK std::cout << "[DEBUG] Infrast task about to run. Press Enter to continue..."; std::cin.get(); #endif编译时加-DDEBUG_TASK,就能在每个任务执行前暂停。更狠的是,在AdbController::tapWithDelay里加if (DEBUG_TAP) cv::waitKey(0);,每次点击前都等你按任意键——这招让我揪出 3 个坐标偏移 bug,包括一个三星 S22 Ultra 的 120Hz 屏幕下adb shell input tap的 1px 偏移。
从那以后我每次改识别逻辑,都强制走一遍「录屏 → 热力图 → 断点」三件套,哪怕只是调一个 HSV 的 S 阈值。因为图像识别没有银弹,只有像素级的较真。希望帮到你。
本文还有配套的精品资源,点击获取