从 dota2-draft-vision 迁出并定名,作为天梯选将识别项目起点。 Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
9.9 KiB
Markdown
229 lines
9.9 KiB
Markdown
# 上分帝(Climperor)
|
||
|
||
Dota 2 天梯选将识别:从「决策时间」截图中识别双方 10 个英雄(模板匹配,本地、离线、秒级)。
|
||
|
||
用于验证可行性,成熟后可移植到 [dota2-hex](../dota2-hex) 或独立发布。
|
||
|
||
方案背景、技术选型与推进计划见 [DESIGN.md](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 官方头像(全覆盖兜底,匹配时降权)
|
||
|
||
## 安装
|
||
|
||
```powershell
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
## 使用流程
|
||
|
||
### 1. 下载 CDN 兜底模板(一次性)
|
||
|
||
```powershell
|
||
python fetch_cdn_templates.py
|
||
```
|
||
|
||
生成 `heroes.json`(英雄 id/key 对照)和 `templates/cdn/`(全英雄头像)。
|
||
|
||
### 2. 采集截图
|
||
|
||
程序可自行截屏,无需手动按 PrintScreen:
|
||
|
||
```powershell
|
||
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(一次性)
|
||
|
||
拿一张**原始分辨率、未裁剪**的决策时间全屏截图,自动标定:
|
||
|
||
```powershell
|
||
python autocalibrate.py samples/raw/draft_141704.png
|
||
```
|
||
|
||
靠顶栏那 10 条固定玩家颜色条定位,无需手动框选,任何分辨率都适用。
|
||
输出 `preview/autocalibrate_check.png`(整条顶栏带框)和
|
||
`preview/autocalibrate_slots.png`(10 格裁切拼图)供核对,坐标以相对值存入 `config.json`。
|
||
|
||
只核对不写配置:加 `--check`。
|
||
|
||
手动标定仍可用(自动失败时的退路):
|
||
|
||
```powershell
|
||
python calibrate.py samples/full.png
|
||
```
|
||
|
||
在弹出窗口中依次框选 10 个英雄头像格(每框完一个按空格,全部完成按 ESC)。
|
||
|
||
### 4. 建真实模板库(可多次,逐步积累)
|
||
|
||
先预览裁切是否正确:
|
||
|
||
```powershell
|
||
python build_library.py samples/shot1.png
|
||
# 查看 preview/slot_1.png ... slot_10.png
|
||
```
|
||
|
||
确认无误后带标注入库(10 个 hero_key 从左到右,跳过用 `?`):
|
||
|
||
```powershell
|
||
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. 识别 + 验证准确率
|
||
|
||
```powershell
|
||
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`):
|
||
|
||
```powershell
|
||
python evaluate.py # 完整模板库
|
||
python evaluate.py --cdn-only # 只用 CDN 层,衡量开箱即用的表现
|
||
```
|
||
|
||
带 `--truth` 时输出每格对错与总准确率;认错的格子自动存入 `failures/`
|
||
(文件名含正确 key)。仅当裁切是**选人阶段默认脸**时再挪进 `templates/real/{key}/`;
|
||
皮肤/至宝头像不要入库(靠会话多帧 + 决策阶段禁改判处理)。
|
||
|
||
## 自动运行(GSI 触发)
|
||
|
||
装好后全程零操作:进入决策时间自动截图、识别、输出 JSON。
|
||
|
||
### 一次性配置
|
||
|
||
```powershell
|
||
python gsi_setup.py # 自动找到 Dota 2 并写入 GSI 配置
|
||
```
|
||
|
||
然后在 **Steam 库 → Dota 2 → 属性 → 启动项**中加上 `-gamestateintegration`,重启游戏。
|
||
|
||
其他用法:`--check` 只查看状态,`--remove` 卸载配置,
|
||
`--path "D:\Steam\steamapps\common\dota 2 beta"` 手动指定目录。
|
||
|
||
### 开着它打游戏
|
||
|
||
```powershell
|
||
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`
|