从 dota2-draft-vision 迁出并定名,作为天梯选将识别项目起点。 Co-authored-by: Cursor <cursoragent@cursor.com>
9.9 KiB
上分帝(Climperor)
Dota 2 天梯选将识别:从「决策时间」截图中识别双方 10 个英雄(模板匹配,本地、离线、秒级)。
用于验证可行性,成熟后可移植到 dota2-hex 或独立发布。
方案背景、技术选型与推进计划见 DESIGN.md;本文是操作手册。
原理
决策时间截图(PNG)
→ 按相对坐标裁出 10 个头像格(分辨率无关)
→ 每格纠裁、去 UI 边饰、缩放到统一尺寸
→ 与模板库做归一化相关匹配(TM_CCOEFF_NORMED)
→ Top-1 分数 + Top1-Top2 分差 双阈值门控 → hero_key 或 null
模板库分两层:
templates/real/{hero}/*.png—— 真实截图裁出的默认脸格子(高精度;不收皮肤变体)templates/cdn/{hero}.png—— Steam CDN 官方头像(全覆盖兜底,匹配时降权)
安装
pip install -r requirements.txt
使用流程
1. 下载 CDN 兜底模板(一次性)
python fetch_cdn_templates.py
生成 heroes.json(英雄 id/key 对照)和 templates/cdn/(全英雄头像)。
2. 采集截图
程序可自行截屏,无需手动按 PrintScreen:
python capture.py # 单张,存入 samples/raw/
python capture.py --loop 300 3 # 每 3 秒一张,持续 300 秒
GSI 自动跟踪时,截图按对局写入 samples/raw/<matchid>/(draft_*.png、draft_best_*.png 等)。
手动 capture.py 仍落在 samples/raw/ 根目录。
Dota 2 需运行在无边框窗口或窗口模式(独占全屏可能截出黑帧)。
也可以交给 GSI 自动触发,见下方「自动运行」。
3. 标定 ROI(一次性)
拿一张原始分辨率、未裁剪的决策时间全屏截图,自动标定:
python autocalibrate.py samples/raw/draft_141704.png
靠顶栏那 10 条固定玩家颜色条定位,无需手动框选,任何分辨率都适用。
输出 preview/autocalibrate_check.png(整条顶栏带框)和
preview/autocalibrate_slots.png(10 格裁切拼图)供核对,坐标以相对值存入 config.json。
只核对不写配置:加 --check。
手动标定仍可用(自动失败时的退路):
python calibrate.py samples/full.png
在弹出窗口中依次框选 10 个英雄头像格(每框完一个按空格,全部完成按 ESC)。
4. 建真实模板库(可多次,逐步积累)
先预览裁切是否正确:
python build_library.py samples/shot1.png
# 查看 preview/slot_1.png ... slot_10.png
确认无误后带标注入库(10 个 hero_key 从左到右,跳过用 ?):
python build_library.py samples/shot1.png tinker,earthshaker,juggernaut,dazzle,vengefulspirit,axe,sniper,slark,lion,drow_ranger
hero_key 见 heroes.json(即 Steam 内部名去掉 npc_dota_hero_ 前缀)。
5. 识别 + 验证准确率
python recognize.py samples/shot2.png
python recognize.py samples/shot2.png --sheet # 输出带预测标签的对照图
python recognize.py samples/shot2.png --truth tinker,earthshaker,... # 对答案
--sheet 生成 preview/recognize_sheet.png:10 格裁切并排,每格标注预测英雄与
分数/分差,绿色表示过阈值、橙色表示存疑(前缀 ?)。核对时比读 JSON 快得多。
批量评测所有已标注截图(真值写在 samples/labels.json):
python evaluate.py # 完整模板库
python evaluate.py --cdn-only # 只用 CDN 层,衡量开箱即用的表现
带 --truth 时输出每格对错与总准确率;认错的格子自动存入 failures/
(文件名含正确 key)。仅当裁切是选人阶段默认脸时再挪进 templates/real/{key}/;
皮肤/至宝头像不要入库(靠会话多帧 + 决策阶段禁改判处理)。
自动运行(GSI 触发)
装好后全程零操作:进入决策时间自动截图、识别、输出 JSON。
一次性配置
python gsi_setup.py # 自动找到 Dota 2 并写入 GSI 配置
然后在 Steam 库 → Dota 2 → 属性 → 启动项中加上 -gamestateintegration,重启游戏。
其他用法:--check 只查看状态,--remove 卸载配置,
--path "D:\Steam\steamapps\common\dota 2 beta" 手动指定目录。
开着它打游戏
python gsi_watch.py
监听 127.0.0.1:3223,游戏一进入英雄选择就开始跟踪整局选将,每秒轮询一次,
每有新英雄揭晓就打印一行,结束后把完整时间线存入 results/draft_<时间戳>.json。
每局只跟踪一次;中途启动程序会从决策时间兜底接入。
天梯全英雄选择是分 3 轮成批揭晓的(每队 2 / 2 / 1,本轮结束前互相不可见), 所以输出长这样:
[draft] grid: 16 heroes unavailable (contrast margin 11.0) - 术士, 殁境神蚀者, ...
[draft] + 4.2s round1 radiant slot2 冥魂大帝 [优势路]
[draft] + 4.2s round1 dire slot7 幻影刺客
[draft] + 31.5s round2 radiant slot3 狙击手 [中路]
[draft] ~ 33.0s slot2 幽鬼 -> 主宰 (score 0.52 -> 0.86)
...
you : slot 5 radiant 沉默术士 - position 5 (纯辅助)
lanes : 1:冥魂大帝, 2:狙击手, 3:军团指挥官, 4:祈求者, 5:沉默术士
bans : 16 - 术士, 殁境神蚀者, 拉比克, 冥界亚龙, 沉默术士, ...
~ 开头的是改判。刚揭晓的那一帧最不适合下判断——立绘还在淡入、天梯段位条又盖住下半张脸。
已确认的槽位仍可被更高分的稳定读数覆盖(revise_gain,默认 0.15)。
顶栏在所有人选完之前用默认头像,皮肤要等全员锁定后才上。因此视觉会读完选人阶段,
并在决策阶段继续读一段时间(strategy_tail_polls),直到凑齐 10 人或超时——避免你已进
决策界面、别人还没选完时漏掉最后一人。凑齐后才停视觉,再等 GSI 公布你自己的英雄。
选人界面顶部计时器下方的模式字(如「全英雄选择」)也会识别,写入结果的 mode 字段。
定位匹配局里,我方 5 格头像下方的位置文字会被一并识别。「我」是哪一格直接取自
GSI 的 player.team_slot;GSI 还没开始推送时,靠姓名颜色兜底(自己的名字是亮白,
其余九人偏蓝)。非定位局没有位置文字,就只报槽位不报位置。
天梯 AP 固定禁用的 16 个英雄从英雄选择网格里读出,不依赖任何图像匹配:网格按 主属性分四块、块内按客户端英雄名排序行优先填充,位置可直接推算;被禁或已被选走的 卡片整张压暗,灰度对比度会塌到 21 以下(正常卡 33 以上),据此判定。
不想开游戏调试时:python gsi_watch.py --once(立即截一次并识别)。
想改触发时机:python gsi_watch.py --states HERO_SELECTION,STRATEGY_TIME。
只看某张截图的位置识别:python roles.py samples/raw/<matchid>/<帧>.png。
只看某张截图的禁用识别:python grid.py samples/raw/<matchid>/<帧>.png。
主菜单里收不到数据是正常的。 Dota 的 GSI 与 CS 不同,客户端第一次载入对局后 才开始发 HTTP 请求,挂在主菜单时不会有任何推送。看到
[gsi] connected才算链路通。 一直没有的话,先确认启动项里有-gamestateintegration。
人机对战(Play with bots)同样会推送,HUD 顶栏与天梯一致,适合反复测试。
尚未标定时会自动进入「只截图」模式——按 matchid 存到 samples/raw/<matchid>/,
拿其中一张跑 calibrate.py 即可完成标定。这是当前推荐的第一步。
相关参数在 config.json 的 gsi 段:
| 字段 | 含义 | 默认 |
|---|---|---|
port |
监听端口 | 3223 |
trigger_states |
启动跟踪的游戏状态 | HERO_SELECTION + STRATEGY_TIME |
poll_interval |
选将期间的轮询间隔(秒) | 1.0 |
confirm_polls |
连续几帧认出同一英雄才算数 | 2 |
session_timeout |
单局跟踪上限(秒) | 300 |
keep_event_frames |
是否为每次揭晓存一张原图 | true |
dump_selection_every |
选将网格开着时每几秒存一帧(0 关闭) | 4 |
target_slots |
认满几格就提前停 | 10 |
截图要求
- PNG 格式、原始分辨率(不要经聊天工具/微信转发,会被压缩)
- 画面为对局内「决策时间」阶段,顶栏 10 个英雄完整可见
- 无边框窗口或窗口模式截图均可;直接把文件放入
samples/
判定标准(demo 验收)
| 指标 | 目标 | 实测(6 局 / 60 格,含 1 局天梯) |
|---|---|---|
| real 模板命中的格子准确率 | ≥ 95% | 100%(分数恒为 1.000) |
| 仅 CDN 兜底的格子准确率 | ≥ 70% | 人机 100%;天梯 9/10(皮肤差异) |
| 单张图识别耗时 | < 1 秒 | 270~520ms |
天梯顶栏会叠段位条和勋章:程序检测到后自动屏蔽底部/右侧遮挡区再匹配, 人机局不受影响。带皮肤的英雄仍需一张 real 模板。
已知限制 / 后续方向
- 斜切头像目前按矩形内缩裁切,未做仿射纠正(够用则不加)
- 分辨率靠相对坐标适配;宽高比差异大(21:9)时需重标定或做锚点自动定位(玩家颜色条 + 中央倒计时)
- 英雄皮肤(至宝/身心)可能改变头像,需为常见皮肤补充 real 模板; 计划加入「识别有误时手动校正」的入口,校正结果直接沉淀为 real 模板
- 禁用识别依赖网格默认排序(子类=属性)。若在客户端里改了排序或筛选方式,
格数会对不上英雄表,此时
grid.py会直接报ok=False而不是给错名单 - 新英雄上线后需重跑
python fetch_cdn_templates.py刷新heroes.json - 位置文字模板取自简体中文客户端,换语言需重新执行
python roles.py <帧>.png --build off,safe,mid,soft_support,hard_support