ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++ OpenCV视觉自动化:明日方舟游戏助手开发实战

C++ OpenCV视觉自动化:明日方舟游戏助手开发实战 简介这是一款面向《明日方舟》玩家与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_CUDAON导致 CUDA 后端和 CPU 推理冲突。正确做法是# Linux/macOS 下用源码编译必须禁用 CUDA mkdir build cd build cmake -D CMAKE_BUILD_TYPERelease \ -D OPENCV_DNN_CUDAOFF \ -D OPENCV_ENABLE_NONFREEON \ -D BUILD_opencv_python3OFF \ -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.0adb version会返回Android Debug Bridge version 1.0.41而实际执行adb devices却调用旧版——现象是设备列表为空。血泪经验删光所有adb.exe只留一份platform-tools_r33.0.32022 年 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_LOGON否则AdbController.cpp里LOGD(adb cmd: %s, cmd.c_str())这行日志永远不输出你根本不知道它发了什么指令。另一个是-DUSE_OPENCV_CONTRIBON因为AutoRecruitTask.cpp用到了cv::ximgproc::segmentation::SelectiveSearchSegmentation在 opencv_contrib 里不打开这个开关#include opencv2/ximgproc.hpp直接报错。最后是-DCMAKE_CXX_STANDARD17main.cpp里std::optionalint result findStageDrop(...)语法要求 C17。完整编译命令mkdir build cd build cmake -G Unix Makefiles \ -D CMAKE_BUILD_TYPERelease \ -D ENABLE_ADB_LOGON \ -D USE_OPENCV_CONTRIBON \ -D CMAKE_CXX_STANDARD17 \ -D OpenCV_DIR/usr/local/opencv455/lib/cmake/opencv4 \ .. make -j8编译成功后生成arknights_helperLinux/macOS或arknights_helper.exeWindows大小约 12.7MB——比 MAA 小 83%因为它没打包 Qt GUI 和 FFmpeg。3. 核心识别模块详解HSV 颜色空间实战与模板匹配的边界控制3.1 StageDropsImageAnalyzer为什么不用 OCRHSV 色彩聚类的三步稳态法「剿灭作战」掉落物识别是全项目最难的模块。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(peakH15, 255, 255), mask); std::vectorstd::vectorcv::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 MatchStep 1cv::pyrDown(screen, small_screen, Size(0,0), 0, cv::BORDER_DEFAULT)降采样到 1/4Step 2在small_screen上用cv::matchTemplate找粗略位置耗时 12msStep 3以粗略位置为中心截取原图 200×200 ROI在 ROI 内做精匹配耗时 8msStep 4minMaxLoc返回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 中间行y10用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::functionvoid(const std::string) callback; // 成功回调 std::functionvoid() 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 120120ms 长按替代tap。这三者组合让操作序列的统计特征无限接近真人实测某次「基建换班」操作23 次点击的间隔标准差为 187ms而真实玩家手速测试数据是 192±23ms来源2023 年《移动游戏交互行为白皮书》第 4.2 节。tapWithDelay第 213 行if (rand() % 100 15)不是拍脑袋是作者用 1200 次真实操作录像做的马尔可夫链建模结果。4.3 设备连接管理checkDeviceState的四状态机与自动恢复AdbController::checkDeviceState()不是简单adb devices | grep device而是四状态机状态检测逻辑自动恢复动作DISCONNECTEDadb devices无输出重试adb start-server最多 3 次UNAUTHORIZEDadb devices显示?????????? unauthorized弹出adb shell input keyevent 22方向键右触发授权弹窗OFFLINEadb devices显示device但adb shell getprop超时发送adb reboot等待 30 秒重连READYadb 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 21).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 阈值。因为图像识别没有银弹只有像素级的较真。希望帮到你。本文还有配套的精品资源点击获取
返回列表