vosonandCursor f32d24b8f8 Initial commit: 上分帝(Climperor)
从 dota2-draft-vision 迁出并定名,作为天梯选将识别项目起点。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 11:47:39 +08:00

上分帝(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_*.pngdraft_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.jsongsi 段:

字段 含义 默认
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
S
Description
Dota 2 天梯选将识别:GSI 触发 + 顶栏模板匹配 + 关系预览
Readme
16 MiB
Languages
Python 65.2%
JavaScript 25.8%
CSS 8%
HTML 0.9%