Drop real template library; keep CDN-only matching.
Remove build_library and runtime artifacts, ignore regenerable outputs, and add README/AGENTS/DESIGN/CHANGELOG for the simplified project. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -7,3 +7,9 @@ venv/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.log
|
||||
|
||||
# Runtime / debug artifacts (regenerable)
|
||||
preview/
|
||||
results/
|
||||
failures/
|
||||
samples/raw/
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 协作说明(面向 AI 与贡献者)
|
||||
|
||||
本文件描述 **上分帝(Climperor)** 的职责边界、代码布局与修改注意点。
|
||||
|
||||
## 项目是什么
|
||||
|
||||
Windows 上的 Dota 2 **天梯选将识别**工具:用 GSI 感知选将阶段,截屏后对顶栏 10 格做 OpenCV 模板匹配,输出双方阵容与选将时间线。
|
||||
|
||||
- 中文名:上分帝
|
||||
- 英文名 / 仓库目录:Climperor / `climperor`
|
||||
- 模板来源:**仅** Steam CDN 官方头像(`templates/cdn/`)
|
||||
- **已放弃** real 实拍模板库补充;不要再引入 `templates/real/` 或入库流水线
|
||||
|
||||
## 目录与模块
|
||||
|
||||
| 路径 | 职责 |
|
||||
|------|------|
|
||||
| `common.py` | 配置 IO、槽位几何、裁切、NCC 匹配、天梯遮罩、CDN 模板加载 |
|
||||
| `recognize.py` | 单帧识别;`recognize_image()` 供会话复用 |
|
||||
| `draft_session.py` | 整局选将跟踪、改判、皮肤规避策略 |
|
||||
| `gsi_watch.py` / `gsi_setup.py` | GSI 监听与 cfg 安装 |
|
||||
| `fetch_cdn_templates.py` | 拉取 CDN 头像 + 生成 `heroes.json` |
|
||||
| `autocalibrate.py` / `calibrate.py` | ROI 自动 / 手动标定 |
|
||||
| `capture.py` | 屏幕捕获 |
|
||||
| `evaluate.py` | 按 `samples/labels.json` 批量评测 |
|
||||
| `roles.py` / `grid.py` / `modes.py` | 位置字、禁用网格、模式字 |
|
||||
| `config.json` | 相对坐标、阈值、GSI 参数 |
|
||||
| `templates/cdn/` | 全英雄 CDN 模板(需提交或由脚本生成) |
|
||||
| `templates/roles/` / `templates/modes/` | 位置 / 模式辅助模板 |
|
||||
|
||||
运行时产物(**勿提交**,见 `.gitignore`):
|
||||
|
||||
- `samples/raw/` — 截图
|
||||
- `preview/` — 标定 / sheet 预览
|
||||
- `results/` — 每局 JSON
|
||||
- `failures/` — `--truth` 调试用错识裁切
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
Dota 2 GSI → gsi_watch.py (:3223)
|
||||
→ DraftSession 轮询截屏
|
||||
→ recognize_image (CDN 模板 + 可选天梯遮罩)
|
||||
→ roles / grid / modes(辅助)
|
||||
→ results/draft_*.json + 终端时间线
|
||||
```
|
||||
|
||||
## 技术约束(修改前必读)
|
||||
|
||||
- **合规**:仅 GSI + 屏幕截图;**禁止**读进程内存、注入、绕过 VAC。
|
||||
- **GSI 范围**:普通玩家视角拿不到双方 pick;GSI 只作阶段触发与 `team_slot` 等自身信息。
|
||||
- **模板策略**:只维护 CDN 层。皮肤问题用会话策略(选人可改判、决策 `allow_revise=False`、best 帧偏 HERO_SELECTION),不要为皮肤加模板库。
|
||||
- **坐标**:一律相对坐标(相对屏幕高 / 相对中心),勿写死像素分辨率。
|
||||
- **宁可不认,不可乱认**:`min_score` + `min_margin` 双门控;不确定就 `null`。
|
||||
- **平台**:面向 Windows;截屏依赖无边框/窗口模式。
|
||||
|
||||
## 开发命令
|
||||
|
||||
```powershell
|
||||
pip install -r requirements.txt
|
||||
python fetch_cdn_templates.py
|
||||
python gsi_setup.py --check
|
||||
python gsi_watch.py --once
|
||||
python recognize.py samples/raw/<帧>.png --sheet
|
||||
python evaluate.py
|
||||
```
|
||||
|
||||
## 修改原则
|
||||
|
||||
- 只改任务所需逻辑,避免无关重构与大范围格式化。
|
||||
- 代码标识符、日志、错误信息、注释用**英文**;用户可见摘要可用中文。
|
||||
- 改匹配阈值或裁切时,用 `evaluate.py` / 标注帧验证,并更新 `CHANGELOG.md` 与必要时的 `DESIGN.md`。
|
||||
- 改 GSI cfg 时同步核对 `gsi_setup.py` 与 Dota `gamestate_integration` 目录。
|
||||
- 改 `heroes.json` 结构时同步 `grid.py`(依赖 `attr` / `name_loc`)。
|
||||
- 不要重新引入 real 模板双层库、`cdn_penalty`、`build_library.py`。
|
||||
|
||||
## 文档分工
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 安装与操作手册 |
|
||||
| `DESIGN.md` | 背景、选型、关键实测与决策记录 |
|
||||
| `AGENTS.md` | 本文件:给协作者 / AI 的约束 |
|
||||
| `CHANGELOG.md` | 面向用户的版本变更 |
|
||||
|
||||
## 与 dota2-hex
|
||||
|
||||
本仓库独立演进。并入或旁路对接时,仍须遵守 [dota2-hex/AGENTS.md](../dota2-hex/AGENTS.md) 的合规边界。
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
本文件记录 **上分帝(Climperor)** 的用户可见变更。格式参考 [Keep a Changelog](https://keepachangelog.com/)。
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- 项目定名:**上分帝** / **Climperor**(目录 `climperor`),由 `dota2-draft-vision` 迁出独立仓库。
|
||||
- **放弃 real 实拍模板库**:删除 `build_library.py`、`templates/real/`;匹配仅使用 CDN 层。
|
||||
- 移除 `match.cdn_penalty` 与 `evaluate.py --cdn-only`(双层库对比已无意义)。
|
||||
- 模板加载 API 简化为 `(hero_key, image)`。
|
||||
- 运行产物纳入 `.gitignore`:`preview/`、`results/`、`failures/`、`samples/raw/`。
|
||||
- 文档拆分为 `README`(操作)、`DESIGN`(决策)、`AGENTS`(协作)、`CHANGELOG`(变更)。
|
||||
|
||||
### Removed
|
||||
|
||||
- real 库补充流水线与相关验收指标。
|
||||
- 仓库内历史 `preview/`、`results/` 调试产物。
|
||||
|
||||
### Fixed
|
||||
|
||||
- (继承自原型)CDN 头像按顶栏窗口裁切,而非中心正方形;CDN-only 即可高准确率。
|
||||
- (继承自原型)天梯段位条 / 勋章遮罩匹配;选人多帧 + 决策禁改判规避皮肤。
|
||||
|
||||
## [0.1.0] - 2026-07-25
|
||||
|
||||
### Added
|
||||
|
||||
- 决策时间顶栏 10 格模板匹配识别。
|
||||
- 相对坐标 ROI、自动标定(玩家颜色条)。
|
||||
- Steam CDN 全英雄模板拉取(`fetch_cdn_templates.py`)。
|
||||
- GSI 触发的选将会话跟踪(`gsi_watch.py` / `draft_session.py`)。
|
||||
- 位置文字、禁用网格、模式字辅助识别。
|
||||
- 天梯 UI 遮罩与会话层皮肤策略。
|
||||
@@ -1,7 +1,6 @@
|
||||
# 上分帝(Climperor)—— 方案与实施细节
|
||||
|
||||
本文档记录 **上分帝(Climperor)** 的背景、技术选型、实现细节与推进计划。
|
||||
`README.md` 是操作手册,本文是设计依据与决策记录。
|
||||
`README.md` 是操作手册;本文记录背景、选型与关键决策。协作约束见 `AGENTS.md`。
|
||||
|
||||
---
|
||||
|
||||
@@ -9,12 +8,10 @@
|
||||
|
||||
### 要解决的问题
|
||||
|
||||
在 Dota 2 选将阶段(以及进入游戏后),**几秒内自动获取双方 10 个英雄**,用于选将辅助或赛后分析。
|
||||
在 Dota 2 选将阶段(以及进入游戏后)**几秒内自动获取双方 10 个英雄**,服务天梯选将辅助或赛后分析。
|
||||
|
||||
### 为什么 GSI 做不到
|
||||
|
||||
官方 Game State Integration 在**普通玩家视角**下不提供双方 pick:
|
||||
|
||||
| 场景 | GSI 可获得的阵容数据 |
|
||||
|------|---------------------|
|
||||
| 排位 / 普通 All Pick | 仅自己的 `hero.id`;`draft` 通常为空 |
|
||||
@@ -22,38 +19,21 @@
|
||||
| 观战 / 裁判视角 | 阵容字段较全 |
|
||||
| 赛后 | 需依赖 OpenDota 等外部 API |
|
||||
|
||||
Valve 官方 issue 中已明确:All Pick 的实时 draft 数据因隐私考量被关闭
|
||||
Valve 已明确关闭普通玩家视角的实时 draft
|
||||
([#9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562)、
|
||||
[#7193](https://github.com/ValveSoftware/Dota2-Gameplay/issues/7193)),
|
||||
且被标记为 not planned。
|
||||
[#7193](https://github.com/ValveSoftware/Dota2-Gameplay/issues/7193),not planned)。
|
||||
|
||||
`dota2-hex` 中的 `lineup_probe` 埋点(`src/gsi/telemetry.rs`)正是为验证此事而写,
|
||||
其单元测试即假定 AP 模式下 `draft:{}` 不含阵容键。
|
||||
### 候选方案
|
||||
|
||||
### 候选方案对比
|
||||
| 方案 | 结论 |
|
||||
|------|------|
|
||||
| 官方 GSI | 拿不到双方 pick |
|
||||
| Overwolf GEP | 备选,偏重 |
|
||||
| 读游戏内存 | **排除**(合规) |
|
||||
| 截屏 + 模板匹配 | **选定** |
|
||||
| 整图问多模态大模型 | 排除为主路径(实测整图几乎全错;裁顶栏后仍不如白名单匹配) |
|
||||
|
||||
| 方案 | 准确率 | 延迟 | 合规性 | 门槛 | 结论 |
|
||||
|------|--------|------|--------|------|------|
|
||||
| 官方 GSI | — | — | 好 | 低 | 拿不到双方 pick |
|
||||
| Overwolf GEP | 很高 | 实时 | 好(与 Valve 有协议) | 玩家须装 Overwolf | 备选,偏重 |
|
||||
| 读游戏内存 | 高 | 实时 | **风险高** | 低 | **排除**,违反项目合规边界 |
|
||||
| 截屏 + 模板匹配 | 中高(可迭代) | < 1s | 好 | 低 | **选定** |
|
||||
| 截屏 + 云端大模型 | 低(实测不可靠) | 数秒 | 好 | 需联网/付费 | 排除为主路径 |
|
||||
|
||||
### 实测记录:为什么不用「整图问大模型」
|
||||
|
||||
用一张 1024×576 的对局截图直接让多模态模型识别顶栏阵容,
|
||||
**10 个英雄几乎全部识别错误**;换用裁剪后的顶栏特写(1024×71),
|
||||
准确率提升到 8/10。结论:
|
||||
|
||||
- 整图 → 单个英雄头像只有几十像素,信息量不足
|
||||
- 通用视觉模型不按英雄库分类,会「脑补」出看似合理实则错误的阵容
|
||||
- **必须先裁格子再识别**,且输出需约束在英雄白名单内
|
||||
|
||||
社区独立工具([dota-hero-picker](https://github.com/YaShock/dota-hero-picker)、
|
||||
[dota2-picker](https://github.com/mohsenheydari/dota2-picker)、
|
||||
[ability-draft-plus](https://github.com/Tiarin-Hino/ability-draft-plus))
|
||||
的共同做法也是:**裁固定 ROI + OpenCV 模板匹配 / 小型 CNN**。
|
||||
社区同类工具共同点:**裁固定 ROI + 模板匹配 / 小模型**,输出约束在英雄白名单内。
|
||||
|
||||
---
|
||||
|
||||
@@ -62,367 +42,130 @@ Valve 官方 issue 中已明确:All Pick 的实时 draft 数据因隐私考量
|
||||
### 处理流程
|
||||
|
||||
```
|
||||
决策时间截图(PNG,原生分辨率)
|
||||
↓ ① 按相对坐标裁出 10 个头像格
|
||||
↓ ② 去除 UI 边饰(顶部玩家颜色条、底部 ID 名牌),缩放到统一尺寸
|
||||
↓ ③ 与模板库逐一做归一化相关匹配 TM_CCOEFF_NORMED
|
||||
↓ ④ 双阈值门控:Top-1 分数 + (Top1 - Top2) 分差
|
||||
↓
|
||||
{"radiant": [...], "dire": [...]} 每格给出 hero_key 或 null
|
||||
截图 → 相对坐标裁 10 格 →(天梯则遮罩段位条)→ CDN NCC 匹配
|
||||
→ score + margin 双门控 → hero_key | null
|
||||
```
|
||||
|
||||
### 关键设计决策
|
||||
### 关键决策
|
||||
|
||||
**① 相对坐标而非像素坐标**
|
||||
**① 相对坐标**
|
||||
|
||||
Dota 2 的顶栏 UI 以屏幕顶部中央为锚点、随分辨率等比缩放。因此坐标存储为:
|
||||
横向 `(cx - W/2) / H`,纵向与宽高 `/ H`。同宽高比下分辨率无关;21:9 等需锚点或重标定。
|
||||
|
||||
- 横向:`(格子中心 x - 屏幕宽/2) / 屏幕高`
|
||||
- 纵向、宽高:`值 / 屏幕高`
|
||||
**② 单一 CDN 模板库**
|
||||
|
||||
在 1440p 标定一次,1080p / 4K / 大部分 16:10 可直接套用。
|
||||
这是**通用性的第一层保障**——目标是所有 Dota 玩家可用,不能写死单一分辨率。
|
||||
`templates/cdn/{hero}.png` 来自 Steam 官方头像,按顶栏实际窗口裁切后写入。
|
||||
|
||||
**② 两层模板库**
|
||||
|
||||
| 层 | 路径 | 来源 | 特点 |
|
||||
|----|------|------|------|
|
||||
| real | `templates/real/{hero}/*.png` | 选人阶段默认脸实拍 | 与目标同源;**不收皮肤/至宝**;逐步积累 |
|
||||
| cdn | `templates/cdn/{hero}.png` | Steam 官方 CDN 头像 | 全 127 英雄覆盖;按顶栏实际窗口裁切后实测已可 100% |
|
||||
|
||||
CDN 层保证「库里绝不会缺英雄」——缺模板时匹配器只能在已有英雄里硬选,
|
||||
必然乱配(前述实测错误正是此类)。real 层随使用逐步替换 CDN 层。
|
||||
曾尝试「CDN 兜底 + real 实拍」双层库。实测修正 CDN 裁切后 **CDN-only 即可全对**,
|
||||
继续攒 real 库性价比低,已放弃(见 §3 发现二)。皮肤靠会话策略,不堆变体模板。
|
||||
|
||||
**③ 宁可不认,不可乱认**
|
||||
|
||||
同时满足两个条件才输出结果,否则返回 `null`:
|
||||
|
||||
- `score >= min_score`(默认 0.45)
|
||||
- `margin = Top1 - Top2 >= min_margin`(默认 0.04)
|
||||
- `margin = Top1 − Top2 >= min_margin`(默认 0.04)
|
||||
|
||||
分差门控用于排除「两个英雄都像」的情况,比单一分数阈值更可靠。
|
||||
**④ 会话层处理皮肤与淡入**
|
||||
|
||||
**④ 失败即样本**
|
||||
|
||||
`recognize.py --truth` 会把认错的格子连同正确答案存入 `failures/`
|
||||
(文件名含正确 hero_key)。确认是默认脸后再移入 `templates/real/{key}/`。
|
||||
皮肤顶栏靠会话策略(决策阶段只补空槽、best 优先选人帧),不靠穷举皮肤模板。
|
||||
- 选人阶段:多帧确认、允许更高分改判(`revise_gain`)
|
||||
- 决策阶段:只补空槽、禁止改判(避免皮肤顶栏覆盖默认脸结论)
|
||||
- best 帧优先选人阶段默认脸
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码结构
|
||||
## 3. 关键实测(2026-07-25)
|
||||
|
||||
环境:2560×1440 无边框;人机 + 天梯。
|
||||
|
||||
### 发现一:CDN 裁切窗口决定上限
|
||||
|
||||
初版按正方形取中心,比例与顶栏 111×88 不符,均分约 0.65。
|
||||
网格搜索得到正确窗口 **x0=38, w=182, 全高**(源 256×144),均分约 0.94。
|
||||
写入 `fetch_cdn_templates.py` 的 `CROP_X0/CROP_X1` 后,**CDN-only 30/30 / 40/40**。
|
||||
|
||||
> 兜底层差时先查素材处理,再考虑堆数据。
|
||||
|
||||
### 发现二:不必维护 real 库
|
||||
|
||||
CDN-only 开箱即用后,「打几十局攒默认脸」不再是主路径。项目改为只维护 CDN。
|
||||
|
||||
### 发现三:自动标定靠玩家颜色条
|
||||
|
||||
10 条固定玩家色定位槽位;中心用中位数槽距拟合(剔除被身后头像污染的宽条);
|
||||
头像下沿用「格内列 vs 格间空隙」亮度差,勿用逐行差分(易误判到名字行)。
|
||||
|
||||
### 发现四:天梯段位条用遮罩
|
||||
|
||||
GSI 不含可靠 lobby 类型时,用右下金色勋章检测(`has_ranked_overlay`),
|
||||
匹配时屏蔽底部 32% + 右侧 22%。人机无勋章走全图匹配。
|
||||
|
||||
### 发现五:禁用名单不靠模板
|
||||
|
||||
网格按主属性分块 + 客户端本地化名行优先排布;禁用/已选卡片对比度塌陷
|
||||
(std 约 8–21 vs 正常 ≥33)。`heroes.json` 需含 `attr` / `name_loc`。
|
||||
格数对不上时 `ok=False`,不返回残缺名单。
|
||||
|
||||
### 发现六:位置与「我」
|
||||
|
||||
定位局位置字用二值 IoU(非 OCR)。「我」优先 GSI `team_slot`;
|
||||
否则姓名亮度相对差(最亮比次亮 ≥ 25)兜底。
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码结构
|
||||
|
||||
```
|
||||
Climperor/
|
||||
├── config.json # 相对坐标、裁切参数、匹配阈值
|
||||
├── heroes.json # 127 英雄 id / key / 英文名对照(自动生成)
|
||||
├── common.py # 配置读写、坐标换算、裁切预处理、模板库加载
|
||||
├── capture.py # 屏幕捕获(单张 / 定时连拍 / 供程序调用的 grab_frame)
|
||||
├── calibrate.py # ROI 手动标定 + 可视化校验(自动标定失败时的退路)
|
||||
├── autocalibrate.py # 靠玩家颜色条自动标定 10 格,免手工框选
|
||||
├── fetch_cdn_templates.py # 拉取全英雄 CDN 头像 + 生成 heroes.json
|
||||
├── build_library.py # 裁格子 → 人工标注 → 入 real 模板库
|
||||
├── recognize.py # 识别 + 准确率评估 + 失败样本归档
|
||||
├── roles.py # 定位匹配的位置文字识别 + 判断哪一格是「我」
|
||||
├── draft_session.py # 跟踪整局选将过程,逐轮记录 pick 时间线
|
||||
├── evaluate.py # 按 samples/labels.json 批量评测所有已标注截图
|
||||
├── samples/labels.json # 已标注截图的真值(10 个 hero_key,未知用 ?)
|
||||
├── gsi_setup.py # 定位 Dota 目录,安装 / 卸载 GSI 配置
|
||||
├── gsi_watch.py # 监听 GSI 状态 → 跟踪选将 → 识别 → 输出 JSON
|
||||
├── templates/roles/ # 5 个位置文字的二值模板
|
||||
├── samples/raw/ # 手动截图;GSI 会话写入 raw/<matchid>/
|
||||
├── results/ # gsi_watch.py 每局的识别结果 JSON
|
||||
├── templates/cdn/ # 127 张兜底模板(已下载)
|
||||
├── templates/real/ # 默认脸实拍模板(不含皮肤)
|
||||
├── preview/ # build_library.py 的裁切预览
|
||||
└── failures/ # 识别失败的格子,待标注入库
|
||||
climperor/
|
||||
├── config.json / heroes.json
|
||||
├── common.py / recognize.py / draft_session.py
|
||||
├── gsi_watch.py / gsi_setup.py
|
||||
├── fetch_cdn_templates.py
|
||||
├── autocalibrate.py / calibrate.py / capture.py
|
||||
├── evaluate.py / roles.py / grid.py / modes.py
|
||||
├── templates/cdn|roles|modes/
|
||||
└── samples/labels.json # 评测真值;raw/ 见 .gitignore
|
||||
```
|
||||
|
||||
### config.json 参数说明
|
||||
运行时目录 `preview/`、`results/`、`failures/`、`samples/raw/` 不入库。
|
||||
|
||||
### 主要配置
|
||||
|
||||
| 字段 | 含义 | 默认 |
|
||||
|------|------|------|
|
||||
| `slots[]` | 10 个格子的相对中心坐标 `cx_rel` / `cy_rel` | 待标定 |
|
||||
| `slot_w_rel` / `slot_h_rel` | 格子宽高 / 屏幕高 | 待标定 |
|
||||
| `crop_trim` | 裁掉的边饰比例(上 10% 颜色条、下 22% 名牌、左右各 8%) | 见文件 |
|
||||
| `canonical_size` | 统一缩放后的边长(px) | 96 |
|
||||
| `match.min_score` | Top-1 最低分 | 0.45 |
|
||||
| `match.min_margin` | Top1−Top2 最小分差 | 0.04 |
|
||||
| `match.cdn_penalty` | CDN 模板得分惩罚,优先采信 real 模板 | 0.05 |
|
||||
| `gsi.port` | GSI 监听端口 | 3223 |
|
||||
| `gsi.trigger_states` | 启动跟踪会话的游戏状态 | `[HERO_SELECTION, STRATEGY_TIME]` |
|
||||
| `gsi.poll_interval` | 选将期间的轮询间隔(秒) | 1.0 |
|
||||
| `gsi.confirm_polls` | 同一格连续几帧认出同一英雄才算确认 | 2 |
|
||||
| `gsi.session_timeout` | 单局跟踪的最长时间(秒) | 300 |
|
||||
| `text_rows.name` / `text_rows.role` | 头像下方姓名行 / 位置行的相对纵坐标 | 见文件 |
|
||||
| `roles.min_iou` | 位置文字模板匹配的最低 IoU | 0.55 |
|
||||
| `roles.self_min_gap` | 「我」那格姓名亮度需高出次亮格多少 | 25.0 |
|
||||
| `match.min_score` / `min_margin` | 识别门控 | 0.45 / 0.04 |
|
||||
| `match.ranked_mask` | 天梯遮罩比例 | bottom 0.32, right 0.22 |
|
||||
| `gsi.port` | 监听端口 | 3223 |
|
||||
| `gsi.confirm_polls` | 连续同结果帧数 | 2 |
|
||||
| `gsi.revise_gain` | 改判所需分数增益 | 0.15 |
|
||||
| `gsi.strategy_tail_polls` | 决策阶段继续视觉轮询 | 8 |
|
||||
|
||||
阈值需在积累一定样本后按实测重新调优,当前为经验初值。
|
||||
---
|
||||
|
||||
### 自动标定(`autocalibrate.py`)
|
||||
|
||||
2560×1440 实测:颜色条位于 y=1~7,头像区 y=8~96(高 88),格宽 111,
|
||||
槽位中心间距 165px,10 格全部命中。
|
||||
|
||||
三个关键判据:
|
||||
|
||||
1. **颜色条定位**——10 个玩家颜色是游戏固定值,逐列取最近颜色(容差 60)
|
||||
找连续段,既给出横向位置又天然给出槽位编号
|
||||
2. **中心线性拟合**——同队 5 格等距,对 index→center 做最小二乘,
|
||||
可修复被身后头像污染的个别颜色条(实测槽位 9/10 偏差 3~6px 被纠正)
|
||||
3. **头像下沿**——用「格内列 vs 格间空隙」的亮度差:有头像时差值 20~67,
|
||||
头像结束瞬间塌到 0。**不能用逐行差分**,因为下方玩家名字的跳变更大,
|
||||
会误判到名字行(初版就踩了这个坑,把底边定到 129 而非 96)
|
||||
|
||||
### 实测结论(2026-07-25,四局人机,2560×1440 无边框)
|
||||
|
||||
| 阶段 | CDN-only | 完整库 |
|
||||
|------|----------|--------|
|
||||
| 初版(正方形裁切 + 线性拟合) | 32/40 | — |
|
||||
| 修正裁切 + 鲁棒拟合后 | **40/40**(最低分 0.784) | **40/40**(最低分 1.000) |
|
||||
|
||||
其中 `draft_145136.png` 是**修复完成前**就抓好的帧,未参与任何调参,
|
||||
旧配置下 8/10、新配置下 10/10,属于干净的盲测样本。
|
||||
|
||||
单次识别 340~460ms,远低于 1 秒目标。用 `python evaluate.py` 复现。
|
||||
|
||||
#### 发现一:顶栏头像是静态图标,可像素级匹配
|
||||
|
||||
real 模板命中时分数恒为 **1.000**。顶栏图标每局渲染逐像素相同,
|
||||
所以一个英雄只要入库一次,之后永远满分命中——不需要"积累多个变体求鲁棒",
|
||||
**一张就够**(同皮肤前提下)。
|
||||
|
||||
#### 发现二:CDN 模板的裁切方式原本就是错的
|
||||
|
||||
初版按 1:1 正方形取中心裁切,但顶栏格子是 111×88(约 1.26:1),比例根本对不上,
|
||||
平均匹配分只有 0.649。以 15 张实拍模板为标准答案做网格搜索,
|
||||
反推出正确窗口是 **x0=38, w=182, 全高**(源图 256×144),平均分升到 **0.936**。
|
||||
|
||||
改用相对比例 `CROP_X0/CROP_X1` 写死在 `fetch_cdn_templates.py` 里,重新生成 127 张后,
|
||||
**仅靠 CDN 模板即可 30/30 全对**。这意味着不必靠打几十局去攒模板库——
|
||||
全英雄覆盖开箱即用。
|
||||
|
||||
> 教训:当兜底层表现明显低于预期时,先怀疑**素材处理方式**,
|
||||
> 而不是急着靠堆数据去补。这里省下了约 70 局的采集成本
|
||||
> (127 英雄按优惠券收集问题估算)。
|
||||
|
||||
#### 发现三:坏点会拖垮最小二乘拟合
|
||||
|
||||
夜魇方槽位 9/10 的颜色条被身后头像污染,检测宽度 141/133(正常 105~119),
|
||||
中心偏了几像素。最小二乘对离群点没有抵抗力,算出的槽距是 166.2 而非真实的 165,
|
||||
导致槽位 10 裁切偏 4px、同一英雄的匹配分从 1.000 掉到 0.567。
|
||||
|
||||
改为**先按条宽剔除不可靠的条**,再取**中位数槽距**、中位数截距,
|
||||
槽位中心与实测真值完全吻合。
|
||||
|
||||
**阈值的已知不足**:瘟疫法师曾出现分差 0.294(很笃定)但分数 0.412 未过 0.45 的情况。
|
||||
现行门控要求分数与分差同时达标,对"分差极高但分数中等"偏严。
|
||||
修正裁切后此问题不再出现(最低分 0.784),暂不调整。
|
||||
|
||||
#### 发现四:天梯段位徽章是稳定遮挡,可用遮罩匹配规避
|
||||
|
||||
天梯全英雄选择时,每个顶栏头像底部有「传奇 III」半透明条、右侧有金色段位勋章。
|
||||
GSI 的 `map` 不含 `game_mode` / `lobby_type`,但画面上金色勋章可稳定检测
|
||||
(`has_ranked_overlay`:≥60% 的格子右下角有金色像素 → 判定为天梯 UI)。
|
||||
|
||||
处理方式:检测到天梯 UI 后,匹配时屏蔽底部 32% + 右侧 22%(`ranked_match_mask`),
|
||||
只对剩余面部区域做归一化相关。人机 / 普通匹配无勋章,走原全图匹配,互不干扰。
|
||||
|
||||
实测:
|
||||
|
||||
| 集合 | 修复前 | 遮罩 + real 入库后 |
|
||||
|------|--------|-------------------|
|
||||
| 5 局人机 | 50/50 | 50/50(未误触发遮罩) |
|
||||
| 1 局天梯 | 9/10(LC 被段位条打崩) | **10/10**(分数 1.000) |
|
||||
|
||||
注意:遮罩解决的是**遮挡**。皮肤/至宝改头像时不要往 real 里堆变体——选人阶段
|
||||
用默认脸多帧确认,决策阶段禁止改判即可;CDN 对默认脸仍偏弱的英雄才补一张 real。
|
||||
|
||||
#### 发现五:位置文字与「我」那一格都能纯视觉读出
|
||||
|
||||
定位匹配(Ranked Roles)里,**只有我方 5 格**的头像下方会画出位置文字:
|
||||
优势路 / 中路 / 劣势路 / 辅助 / 纯辅助。2560×1440 下位于 y=143~159。
|
||||
这行文字是纯灰(饱和度≈0),用 `value>110 且 saturation<0.08` 就能干净抠出来。
|
||||
|
||||
不做 OCR,改成**二值掩膜 IoU 匹配**:候选只有 5 个,且宽度各不相同
|
||||
(图标+2字 ~ 图标+3字),同帧自匹配 IoU=1.0,跨类差距极大。掩膜先紧裁再
|
||||
归一化到固定高度 24px,因此换分辨率不用重建模板。
|
||||
|
||||
哪一格是「我」,最终由 GSI 直接回答:`player` 块里带 `team_slot`(队内 0~4),
|
||||
顶栏就是按队内序号排的,所以 `slot = team_slot + 1`(天辉)或 `+ 6`(夜魇)。
|
||||
实测 `team_slot=4 / radiant / drow_ranger` 对应顶栏第 5 格,与画面一致。
|
||||
|
||||
视觉判据仍保留为兜底(GSI 在载入对局前不发 `player` 块)——
|
||||
**自己的名字是亮白色,其余九人是偏蓝的灰**:
|
||||
|
||||
| 局 | 我那格亮度 | 其余最高 | 差值 |
|
||||
|----|-----------|---------|------|
|
||||
| 天梯定位局 | 229.9 | 170.2 | 59.7 |
|
||||
| 人机局 ×3 | 229.8~230.2 | 175.1 | ~55 |
|
||||
|
||||
用绝对阈值会在「整屏都亮」的界面上误判(实测游戏内 HUD 帧十格都是 248),
|
||||
所以改成**相对判据**:最亮格需比次亮格高出 25 以上。8 张选将截图全部命中,
|
||||
9 张非选将截图全部正确返回 None。
|
||||
|
||||
### 官方选将规则(决定采集节奏)
|
||||
|
||||
天梯全英雄选择的规则来自 Dota 2 Wiki / Liquipedia:
|
||||
|
||||
- **禁用**:全员 15 秒投票,每人 1 个不可重复;每票各有 50% 概率生效;
|
||||
系统再按该 MMR 段位的 ban 率补随机禁用,**最终固定 16 个**
|
||||
- **选人分 3 轮**:25 秒 / 每队 2 人 → 25 秒 / 每队 2 人 → 20 秒 / 每队 1 人
|
||||
- **本轮结束前双方互相不可见**;同轮撞英雄则该英雄被禁、本轮重来(最多 2 次)
|
||||
|
||||
关键推论:顶栏英雄是**按 2/2/1 成批揭晓**的。原来只在决策时间截一次,
|
||||
拿到的是最终阵容,选人顺序信息全丢——而顺序恰好是后续给建议最需要的。
|
||||
|
||||
### GSI 自动化链路
|
||||
|
||||
GSI 拿不到双方 pick,但能可靠告诉我们**现在处于哪个阶段**,正好用作触发器:
|
||||
## 5. GSI 自动化
|
||||
|
||||
```
|
||||
Dota 2(-gamestateintegration)
|
||||
↓ POST JSON,每 0.5s
|
||||
gsi_watch.py 本地 HTTP 服务(127.0.0.1:3223)
|
||||
↓ map.game_state 进入 HERO_SELECTION(错过则 STRATEGY_TIME 兜底)
|
||||
DraftSession:每秒轮询 → recognize_image() + detect_roles()
|
||||
↓ 某格连续 2 帧认出同一英雄才算确认,避免头像淡入时的抖动
|
||||
确认集合变化 → 追加一条时间线事件(轮次由每队已选人数推出)
|
||||
↓ 状态离开选将 / 10 格认满
|
||||
终端摘要 + results/draft_<时间戳>.json
|
||||
Dota 2 (-gamestateintegration)
|
||||
→ POST → gsi_watch.py :3223
|
||||
→ DraftSession 轮询 recognize + roles/grid/modes
|
||||
→ results/draft_*.json
|
||||
```
|
||||
|
||||
设计要点:
|
||||
要点:按 `matchid` 去重;识别在工作线程;未标定时降级为只截图。
|
||||
|
||||
- **按 `matchid` 去重**,一局一个跟踪会话,不论从哪个阶段接入
|
||||
- **轮询与识别在工作线程**,HTTP 回调立即返回,不阻塞游戏侧推送
|
||||
- **确认需连续 2 帧**,单帧可能拍到半透明的入场动画
|
||||
- **位置与「我」只解析一次**,这两项全局不变,认出后不再重复计算
|
||||
- **「我」优先用 GSI 的 `team_slot`**,视觉判据仅在 GSI 尚未提供时兜底
|
||||
- **未标定时降级为「只截图」**,仍在正确时机存帧,供 `calibrate.py` 使用
|
||||
天梯 AP 选人按 2/2/1 成批揭晓,本轮结束前互不可见——因此必须在
|
||||
**HERO_SELECTION** 跟踪,不能只在决策时间截一张终局图。
|
||||
|
||||
---
|
||||
|
||||
## 4. 环境与依赖
|
||||
## 6. 与 dota2-hex
|
||||
|
||||
- Python 3.14(本机已装)
|
||||
- `opencv-python >= 4.10`(实装 5.0.0)、`numpy >= 2.0`、`requests >= 2.32`、`mss`(屏幕捕获)
|
||||
|
||||
已完成的初始化:
|
||||
|
||||
```powershell
|
||||
pip install -r requirements.txt
|
||||
pip install mss
|
||||
python fetch_cdn_templates.py # 127/127 模板下载成功
|
||||
```
|
||||
|
||||
已验证 `capture.py` 可正常截取主显示器,输出为原生 **2560×1440** PNG。
|
||||
独立验证项目。可选:Rust 重写并入,或本地旁路 HTTP 回传。
|
||||
合规同 `dota2-hex`:**屏幕可见信息 only**。
|
||||
|
||||
---
|
||||
|
||||
## 5. 推进计划
|
||||
## 7. 参考
|
||||
|
||||
| 阶段 | 内容 | 状态 |
|
||||
|------|------|------|
|
||||
| 0 | 项目骨架、依赖、CDN 模板库 | **已完成** |
|
||||
| 1 | 采集真实决策时间截图 | **已完成** |
|
||||
| 2 | ROI 标定,生成 `config.json` 坐标 | **已完成**(自动标定) |
|
||||
| 3 | 首轮识别(纯 CDN 兜底),摸底准确率 | **已完成**(8/10) |
|
||||
| 4 | 积累 real 模板,迭代阈值,统计准确率 | **已完成**(30/30,CDN-only 亦 30/30) |
|
||||
| 5 | 锚点自动定位,去除标定依赖 | **已完成**(`autocalibrate.py`) |
|
||||
| 6 | 接入 GSI 自动触发,全流程免操作 | **已完成**(真机人机局已验证) |
|
||||
|
||||
### 验收标准(判定方案是否可行)
|
||||
|
||||
| 指标 | 目标 |
|
||||
|------|------|
|
||||
| real 模板命中格子的准确率 | ≥ 95% |
|
||||
| 仅 CDN 兜底格子的准确率 | ≥ 70% |
|
||||
| 单张图识别耗时 | < 1 秒 |
|
||||
| 错误类型 | 以「输出 null」为主,而非「输出错误英雄」 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 通用性设计(面向所有玩家)
|
||||
|
||||
**核心原则:成品阶段玩家零操作。** 当前 demo 的手动步骤均为验证期临时措施。
|
||||
|
||||
| Demo 期手动操作 | 成品自动方案 |
|
||||
|----------------|-------------|
|
||||
| 手动截图 | GSI 检测 `DOTA_GAMERULES_STATE_STRATEGY_TIME` 自动触发(`gsi_watch.py`,已实现) |
|
||||
| 手动框选 10 个格子标定 | 锚点自动定位(见下) |
|
||||
| 人工标注建模板库 | 模板库随程序内置,开箱即用 |
|
||||
| `--truth` 对答案 | 仅开发期使用 |
|
||||
| 手动设置无边框窗口 | 首次运行自动检测,或改用 Windows Graphics Capture |
|
||||
|
||||
### 分辨率与宽高比适配(三层递进)
|
||||
|
||||
1. **相对坐标**(已实现)——覆盖同宽高比下的任意分辨率
|
||||
2. **锚点自动定位**(阶段 5)——运行时在截图中自动找顶栏:
|
||||
- 中央倒计时区域作水平锚点
|
||||
- 10 条固定玩家颜色条(蓝、青、紫、黄、橙 / 粉、灰绿、浅蓝、墨绿、棕)
|
||||
既是定位标记,也天然给出槽位编号
|
||||
- 由锚点反推格子位置,任何分辨率、宽高比免配置
|
||||
3. **多尺度匹配**——0.9~1.1 倍尺度搜索,吸收残余缩放误差
|
||||
|
||||
### 已知风险
|
||||
|
||||
| 风险 | 应对 |
|
||||
|------|------|
|
||||
| 21:9 等特殊宽高比布局差异 | 阶段 5 锚点定位;短期可分档标定 |
|
||||
| 英雄皮肤(至宝 / 身心)改变头像 | 不入库皮肤模板;决策阶段只补空槽、禁止改判;best 帧优先选人阶段默认脸 |
|
||||
| 头像为平行四边形,矩形 ROI 会带入邻格边缘 | 当前靠内缩裁切规避;必要时加仿射纠正 |
|
||||
| 独占全屏截图可能为黑帧 | 建议无边框窗口;或改用 Windows Graphics Capture |
|
||||
| Dota 更新改动 HUD 布局 | 锚点方案对布局微调更鲁棒;必要时重标定 |
|
||||
|
||||
### 禁用英雄识别(grid.py)
|
||||
|
||||
天梯 AP 固定禁用 16 个,这个信息不在顶栏,只在英雄选择网格里。最后的做法
|
||||
**完全不用模板匹配**,因为网格的排布是可推算的。
|
||||
|
||||
一开始确实试了匹配。网格用的是竖版英雄卡,Steam CDN 上唯一对得上的素材是
|
||||
遗留路径 `images/heroes/{key}_vert.jpg`(235×272),拟合出的裁切窗口
|
||||
x[0.15,0.80] y[0.00,0.85] 宽高比 0.66 与实拍卡片的 52/79 完全吻合。但均分
|
||||
只有 0.50,且下半段的 top1/top2 几乎没有间距——这批图是 2015 年前后的旧原画,
|
||||
大量英雄重做过,根本对不上。
|
||||
|
||||
真正的突破口是排布本身。127 个英雄按主属性分成四块从左到右排列,块内按
|
||||
**客户端本地化名称**排序、**行优先**填充,多出来的空格永远在块尾。用实战帧
|
||||
交叉验证了三点:四块各 36 / 35 / 34 / 22 格,与英雄表的属性数量分毫不差;
|
||||
5 个空格全在最后一行的块尾;聊天栏里点名的 9 个禁用英雄,按此推算出的格子
|
||||
**全部**画着禁用斜杠。所以英雄表(`heroes.json` 现在带 `attr` 和 `name_loc`,
|
||||
都取自 Valve 自己的 `datafeed/herolist`)就足以定位每一格。
|
||||
|
||||
禁用态的判定绕了点弯路。斜杠本身不好测——试过方向梯度直方图和错切后找亮脊,
|
||||
两者都失败,后者甚至完全反向(禁用格反而排在最后)。原因是禁用卡整张被压暗
|
||||
去饱和,**低对比度**才是主特征。实测那一局:17 张不可选卡片的灰度 std 落在
|
||||
8–21,其余 110 张全部 ≥32.6,中间空出 11.6 的间隔,直接卡阈值即可。
|
||||
17 = 16 个禁用 + 1 个已被选走的莉娜,与官方规则严丝合缝。
|
||||
|
||||
`read_grid()` 在格数对不上英雄表时返回 `ok=False` 而不是给一份残缺名单——
|
||||
鼠标悬停会弹出大号英雄卡遮住网格,这类帧就是这样被挡掉的。禁用名单全程不变,
|
||||
`DraftSession` 只取第一帧读成功的结果,之后不再重复读。
|
||||
|
||||
---
|
||||
|
||||
## 7. 与 dota2-hex 的关系
|
||||
|
||||
本项目为**独立验证 demo**,不修改 `dota2-hex`。
|
||||
|
||||
- 选用 Python 是因为验证期迭代快,非最终技术栈
|
||||
- 准确率验证通过后,可选:用 Rust 重写并入 `dota2-hex`,或保留为本地旁路服务通过 HTTP 回传
|
||||
- 无论哪种,都需遵守 `dota2-hex` 的合规边界:**仅使用玩家屏幕上可见的信息,
|
||||
不读进程内存、不注入**(见 `AGENTS.md`「技术约束」)
|
||||
|
||||
`dota2-hex` 现有的阶段判定(`src/phase.rs`)可直接复用为截图触发信号。
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考资料
|
||||
|
||||
- [Valve #9562 GSI get draft data during draft](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562) — 官方关闭 AP 实时 draft
|
||||
- [Valve #14915 How can I get live pick data](https://github.com/ValveSoftware/Dota2-Gameplay/issues/14915) — 开发者自述 OCR 方案约 85% 准确率
|
||||
- [Overwolf Dota 2 GEP](https://dev.overwolf.com/ow-native/live-game-data-gep/supported-games/dota-2/) — `roster.draft` / `bans` 字段定义
|
||||
- [OpenCV Template Matching](https://pyimagesearch.com/2021/03/22/opencv-template-matching-cv2-matchtemplate/)
|
||||
- Steam CDN 头像:`https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/heroes/{key}.png`
|
||||
- [Valve #9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562)
|
||||
- [Valve #14915](https://github.com/ValveSoftware/Dota2-Gameplay/issues/14915)(开发者 OCR ≈85%)
|
||||
- [Overwolf Dota 2 GEP](https://dev.overwolf.com/ow-native/live-game-data-gep/supported-games/dota-2/)
|
||||
- Steam CDN:`https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/heroes/{key}.png`
|
||||
|
||||
@@ -1,228 +1,84 @@
|
||||
# 上分帝(Climperor)
|
||||
|
||||
Dota 2 天梯选将识别:从「决策时间」截图中识别双方 10 个英雄(模板匹配,本地、离线、秒级)。
|
||||
Dota 2 **天梯选将识别**:从选人 / 决策时间截图中识别双方 10 个英雄。
|
||||
|
||||
用于验证可行性,成熟后可移植到 [dota2-hex](../dota2-hex) 或独立发布。
|
||||
本地、离线、秒级;模板来自 Steam CDN 官方头像。GSI 只负责触发时机,阵容靠视觉读顶栏。
|
||||
|
||||
方案背景、技术选型与推进计划见 [DESIGN.md](DESIGN.md);本文是操作手册。
|
||||
设计背景见 [DESIGN.md](DESIGN.md);协作约定见 [AGENTS.md](AGENTS.md);变更记录见 [CHANGELOG.md](CHANGELOG.md)。
|
||||
|
||||
## 原理
|
||||
|
||||
```
|
||||
决策时间截图(PNG)
|
||||
→ 按相对坐标裁出 10 个头像格(分辨率无关)
|
||||
→ 每格纠裁、去 UI 边饰、缩放到统一尺寸
|
||||
→ 与模板库做归一化相关匹配(TM_CCOEFF_NORMED)
|
||||
→ Top-1 分数 + Top1-Top2 分差 双阈值门控 → hero_key 或 null
|
||||
→ 缩放到统一尺寸;天梯局自动屏蔽段位条/勋章遮挡区
|
||||
→ 与 CDN 模板做归一化相关匹配(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
|
||||
python fetch_cdn_templates.py # 一次性:heroes.json + templates/cdn/
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 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"` 手动指定目录。
|
||||
|
||||
### 开着它打游戏
|
||||
## 快速开始(GSI 自动跟踪)
|
||||
|
||||
```powershell
|
||||
python gsi_setup.py # 写入 Dota GSI 配置
|
||||
# Steam → Dota 2 → 属性 → 启动项加上 -gamestateintegration,重启游戏
|
||||
python gsi_watch.py
|
||||
```
|
||||
|
||||
监听 `127.0.0.1:3223`,游戏一进入英雄选择就开始跟踪整局选将,每秒轮询一次,
|
||||
每有新英雄揭晓就打印一行,结束后把完整时间线存入 `results/draft_<时间戳>.json`。
|
||||
每局只跟踪一次;中途启动程序会从决策时间兜底接入。
|
||||
进入英雄选择后自动跟踪;凑齐 10 人或超时后写出 `results/draft_<时间戳>.json`(该目录已 gitignore)。
|
||||
|
||||
天梯全英雄选择是**分 3 轮成批揭晓**的(每队 2 / 2 / 1,本轮结束前互相不可见),
|
||||
所以输出长这样:
|
||||
> 主菜单收不到 GSI 是正常的:客户端**第一次载入对局后**才开始推送。看到 `[gsi] connected` 才算链路通。
|
||||
|
||||
```
|
||||
[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 - 术士, 殁境神蚀者, 拉比克, 冥界亚龙, 沉默术士, ...
|
||||
## 手动流程
|
||||
|
||||
### 截图
|
||||
|
||||
```powershell
|
||||
python capture.py # 单张 → samples/raw/
|
||||
python capture.py --loop 300 3 # 每 3 秒一张,持续 300 秒
|
||||
```
|
||||
|
||||
`~` 开头的是**改判**。刚揭晓的那一帧最不适合下判断——立绘还在淡入、天梯段位条又盖住下半张脸。
|
||||
已确认的槽位仍可被更高分的稳定读数覆盖(`revise_gain`,默认 0.15)。
|
||||
需无边框窗口或窗口模式(独占全屏可能黑帧)。
|
||||
|
||||
顶栏在**所有人选完之前**用默认头像,皮肤要等全员锁定后才上。因此视觉会读完选人阶段,
|
||||
并在决策阶段继续读一段时间(`strategy_tail_polls`),直到凑齐 10 人或超时——避免你已进
|
||||
决策界面、别人还没选完时漏掉最后一人。凑齐后才停视觉,再等 GSI 公布你自己的英雄。
|
||||
### 标定 ROI(一次性)
|
||||
|
||||
选人界面顶部计时器下方的模式字(如「全英雄选择」)也会识别,写入结果的 `mode` 字段。
|
||||
```powershell
|
||||
python autocalibrate.py samples/raw/<某帧>.png
|
||||
# 失败时退路:python calibrate.py samples/raw/<某帧>.png
|
||||
```
|
||||
|
||||
定位匹配局里,我方 5 格头像下方的位置文字会被一并识别。「我」是哪一格直接取自
|
||||
GSI 的 `player.team_slot`;GSI 还没开始推送时,靠姓名颜色兜底(自己的名字是亮白,
|
||||
其余九人偏蓝)。非定位局没有位置文字,就只报槽位不报位置。
|
||||
### 识别 / 评测
|
||||
|
||||
天梯 AP 固定禁用的 16 个英雄从英雄选择网格里读出,不依赖任何图像匹配:网格按
|
||||
主属性分四块、块内按客户端英雄名排序行优先填充,位置可直接推算;被禁或已被选走的
|
||||
卡片整张压暗,灰度对比度会塌到 21 以下(正常卡 33 以上),据此判定。
|
||||
```powershell
|
||||
python recognize.py samples/raw/<帧>.png
|
||||
python recognize.py samples/raw/<帧>.png --sheet
|
||||
python recognize.py samples/raw/<帧>.png --truth hero1,...,hero10
|
||||
python evaluate.py # 按 samples/labels.json 批量评测
|
||||
```
|
||||
|
||||
不想开游戏调试时:`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 |
|
||||
辅助脚本:`roles.py`(位置字)、`grid.py`(禁用名单)、`modes.py`(模式字)。
|
||||
|
||||
## 截图要求
|
||||
|
||||
- **PNG 格式、原始分辨率**(不要经聊天工具/微信转发,会被压缩)
|
||||
- 画面为对局内「决策时间」阶段,顶栏 10 个英雄完整可见
|
||||
- 无边框窗口或窗口模式截图均可;直接把文件放入 `samples/`
|
||||
- PNG、原始分辨率(勿经聊天工具压缩)
|
||||
- 顶栏 10 个英雄完整可见
|
||||
- 优先选人阶段默认脸帧(皮肤尚未上顶栏)
|
||||
|
||||
## 判定标准(demo 验收)
|
||||
## 已知限制
|
||||
|
||||
| 指标 | 目标 | 实测(6 局 / 60 格,含 1 局天梯) |
|
||||
|------|------|-----------------------------------|
|
||||
| real 模板命中的格子准确率 | ≥ 95% | 100%(分数恒为 1.000) |
|
||||
| 仅 CDN 兜底的格子准确率 | ≥ 70% | 人机 100%;天梯 9/10(皮肤差异) |
|
||||
| 单张图识别耗时 | < 1 秒 | 270~520ms |
|
||||
- 宽高比差异大(如 21:9)时可能需重标定
|
||||
- 客户端改网格排序后 `grid.py` 会 `ok=False`,不会瞎报禁用名单
|
||||
- 新英雄上线后重跑 `fetch_cdn_templates.py`
|
||||
- 位置文字模板取自简体中文客户端
|
||||
|
||||
天梯顶栏会叠段位条和勋章:程序检测到后自动屏蔽底部/右侧遮挡区再匹配,
|
||||
人机局不受影响。带皮肤的英雄仍需一张 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`
|
||||
成熟后可移植到 [dota2-hex](../dota2-hex) 或独立发布。合规边界:只使用屏幕可见信息,不读内存、不注入。
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
"""Crop slots from a labeled screenshot and add them to the real template library.
|
||||
|
||||
Usage:
|
||||
# 1) preview: crop 10 slots to preview/ so you can see what each slot contains
|
||||
python build_library.py samples/shot1.png
|
||||
|
||||
# 2) import: provide 10 comma-separated hero keys (left to right), '?' to skip a slot
|
||||
python build_library.py samples/shot1.png tinker,earthshaker,juggernaut,dazzle,vengefulspirit,axe,sniper,slark,lion,drow_ranger
|
||||
|
||||
Hero keys must match Steam internal names without the npc_dota_hero_ prefix
|
||||
(see heroes.json after running fetch_cdn_templates.py).
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
from common import ROOT, TEMPLATES_REAL, crop_slot, load_config
|
||||
|
||||
PREVIEW_DIR = ROOT / "preview"
|
||||
|
||||
|
||||
def known_hero_keys() -> set[str]:
|
||||
path = ROOT / "heroes.json"
|
||||
if not path.exists():
|
||||
return set()
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return {h["key"] for h in json.load(f)}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit(__doc__)
|
||||
image_path = sys.argv[1]
|
||||
labels = sys.argv[2].split(",") if len(sys.argv) > 2 else None
|
||||
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
sys.exit(f"cannot read image: {image_path}")
|
||||
cfg = load_config()
|
||||
if not cfg["slots"]:
|
||||
sys.exit("config.json has no slots - run calibrate.py first")
|
||||
|
||||
crops = [(slot["index"], crop_slot(img, slot, cfg)) for slot in cfg["slots"]]
|
||||
|
||||
if labels is None:
|
||||
PREVIEW_DIR.mkdir(exist_ok=True)
|
||||
for idx, crop in crops:
|
||||
if crop is not None:
|
||||
cv2.imwrite(str(PREVIEW_DIR / f"slot_{idx}.png"), crop)
|
||||
print(f"wrote {len(crops)} crops to {PREVIEW_DIR}/ - inspect them, then rerun with labels")
|
||||
return
|
||||
|
||||
if len(labels) != 10:
|
||||
sys.exit(f"expected 10 labels, got {len(labels)}")
|
||||
known = known_hero_keys()
|
||||
stamp = time.strftime("%Y%m%d_%H%M%S")
|
||||
added = 0
|
||||
for (idx, crop), label in zip(crops, labels):
|
||||
label = label.strip()
|
||||
if label == "?" or crop is None:
|
||||
continue
|
||||
if known and label not in known:
|
||||
print(f" WARNING slot {idx}: '{label}' not in heroes.json - saved anyway, double-check spelling")
|
||||
hero_dir = TEMPLATES_REAL / label
|
||||
hero_dir.mkdir(parents=True, exist_ok=True)
|
||||
cv2.imwrite(str(hero_dir / f"{stamp}_s{idx}.png"), crop)
|
||||
added += 1
|
||||
total = sum(1 for _ in TEMPLATES_REAL.rglob("*.png"))
|
||||
heroes = sum(1 for d in TEMPLATES_REAL.iterdir() if d.is_dir())
|
||||
print(f"added {added} templates; library now {total} images / {heroes} heroes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,7 +8,6 @@ import numpy as np
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
CONFIG_PATH = ROOT / "config.json"
|
||||
TEMPLATES_REAL = ROOT / "templates" / "real"
|
||||
TEMPLATES_CDN = ROOT / "templates" / "cdn"
|
||||
|
||||
|
||||
@@ -123,20 +122,13 @@ def has_ranked_overlay(img: np.ndarray, cfg: dict) -> bool:
|
||||
return checked > 0 and hits >= max(6, checked * 0.6)
|
||||
|
||||
|
||||
def load_template_library() -> list[tuple[str, str, np.ndarray]]:
|
||||
"""Return list of (hero_key, source, image). source is 'real' or 'cdn'."""
|
||||
lib: list[tuple[str, str, np.ndarray]] = []
|
||||
if TEMPLATES_REAL.is_dir():
|
||||
for hero_dir in sorted(TEMPLATES_REAL.iterdir()):
|
||||
if not hero_dir.is_dir():
|
||||
continue
|
||||
for png in hero_dir.glob("*.png"):
|
||||
img = cv2.imread(str(png))
|
||||
if img is not None:
|
||||
lib.append((hero_dir.name, "real", img))
|
||||
if TEMPLATES_CDN.is_dir():
|
||||
for png in TEMPLATES_CDN.glob("*.png"):
|
||||
img = cv2.imread(str(png))
|
||||
if img is not None:
|
||||
lib.append((png.stem, "cdn", img))
|
||||
def load_template_library() -> list[tuple[str, np.ndarray]]:
|
||||
"""Return list of (hero_key, image) from Steam CDN portraits."""
|
||||
lib: list[tuple[str, np.ndarray]] = []
|
||||
if not TEMPLATES_CDN.is_dir():
|
||||
return lib
|
||||
for png in sorted(TEMPLATES_CDN.glob("*.png")):
|
||||
img = cv2.imread(str(png))
|
||||
if img is not None:
|
||||
lib.append((png.stem, img))
|
||||
return lib
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
"match": {
|
||||
"min_score": 0.45,
|
||||
"min_margin": 0.04,
|
||||
"cdn_penalty": 0.05,
|
||||
"ranked_mask": {
|
||||
"comment": "Ignore the bottom rank-title bar and right-side medal when ranked overlays are detected.",
|
||||
"bottom": 0.32,
|
||||
|
||||
@@ -4,13 +4,11 @@ Labels live in samples/labels.json as {frame filename: 10 hero keys}, '?' for
|
||||
slots nobody has identified yet. Those slots are skipped, not counted wrong.
|
||||
|
||||
Usage:
|
||||
python evaluate.py # use the full template library
|
||||
python evaluate.py --cdn-only # ignore templates/real, measure the fallback layer
|
||||
python evaluate.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
@@ -22,7 +20,6 @@ RAW_DIR = ROOT / "samples" / "raw"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cdn_only = "--cdn-only" in sys.argv
|
||||
if not LABELS_PATH.is_file():
|
||||
sys.exit(f"missing {LABELS_PATH}")
|
||||
frames = json.loads(LABELS_PATH.read_text(encoding="utf-8"))["frames"]
|
||||
@@ -31,9 +28,7 @@ def main() -> None:
|
||||
if not cfg["slots"]:
|
||||
sys.exit("config.json has no slots - run autocalibrate.py first")
|
||||
library = load_template_library()
|
||||
if cdn_only:
|
||||
library = [t for t in library if t[1] == "cdn"]
|
||||
print(f"library: {len(library)} templates{' (cdn only)' if cdn_only else ''}")
|
||||
print(f"library: {len(library)} CDN templates")
|
||||
|
||||
graded = correct = skipped = 0
|
||||
misses: list[str] = []
|
||||
|
||||
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 644 KiB |
|
Before Width: | Height: | Size: 386 KiB |
@@ -1,12 +1,13 @@
|
||||
"""Recognize the 10 drafted heroes from a strategy-time screenshot.
|
||||
|
||||
Usage:
|
||||
python recognize.py samples/shot2.png
|
||||
python recognize.py samples/shot2.png --truth tinker,earthshaker,...,drow_ranger
|
||||
python recognize.py samples/raw/draft.png
|
||||
python recognize.py samples/raw/draft.png --truth tinker,earthshaker,...,drow_ranger
|
||||
python recognize.py samples/raw/draft.png --sheet
|
||||
|
||||
Outputs per-slot JSON with top-1 hero, score and margin; slots failing the
|
||||
confidence gate are reported as null. With --truth, prints accuracy and saves
|
||||
misrecognized crops to failures/ for later labeling.
|
||||
misrecognized crops to failures/ (debug only, gitignored).
|
||||
|
||||
recognize_image() is the reusable entry point used by gsi_watch.py.
|
||||
"""
|
||||
@@ -58,13 +59,14 @@ def write_sheet(img: np.ndarray, results: list[dict], cfg: dict) -> str:
|
||||
|
||||
|
||||
def recognize_slot(crop, library, cfg, mask=None):
|
||||
"""Return (best_hero, best_score, margin, scored list)."""
|
||||
penalty = cfg["match"]["cdn_penalty"]
|
||||
"""Return (best_hero, best_score, margin, scored list).
|
||||
|
||||
cfg is accepted for call-site compatibility; score gates are applied by the caller.
|
||||
"""
|
||||
_ = cfg
|
||||
best_per_hero: dict[str, float] = {}
|
||||
for hero, source, tmpl in library:
|
||||
for hero, tmpl in library:
|
||||
s = match_score(crop, tmpl, mask)
|
||||
if source == "cdn":
|
||||
s -= penalty
|
||||
if s > best_per_hero.get(hero, -2.0):
|
||||
best_per_hero[hero] = s
|
||||
ranked = sorted(best_per_hero.items(), key=lambda kv: kv[1], reverse=True)
|
||||
@@ -142,7 +144,7 @@ def main() -> None:
|
||||
sys.exit("config.json has no slots - run calibrate.py first")
|
||||
library = load_template_library()
|
||||
if not library:
|
||||
sys.exit("template library is empty - run fetch_cdn_templates.py and/or build_library.py")
|
||||
sys.exit("template library is empty - run fetch_cdn_templates.py")
|
||||
|
||||
out = recognize_image(img, cfg, library)
|
||||
results = out.pop("slots")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
opencv-python>=4.10
|
||||
numpy>=2.0
|
||||
requests>=2.32
|
||||
mss>=9.0
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.598,
|
||||
"margin": 0.296,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.598
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.302
|
||||
],
|
||||
[
|
||||
"kez",
|
||||
0.237
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.676,
|
||||
"margin": 0.367,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.676
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.309
|
||||
],
|
||||
[
|
||||
"monkey_king",
|
||||
0.221
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "viper",
|
||||
"raw_best": "viper",
|
||||
"score": 0.651,
|
||||
"margin": 0.256,
|
||||
"top3": [
|
||||
[
|
||||
"viper",
|
||||
0.651
|
||||
],
|
||||
[
|
||||
"faceless_void",
|
||||
0.396
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.317
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": null,
|
||||
"raw_best": "earthshaker",
|
||||
"score": 0.434,
|
||||
"margin": 0.128,
|
||||
"top3": [
|
||||
[
|
||||
"earthshaker",
|
||||
0.434
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.306
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.297
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": null,
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 0.327,
|
||||
"margin": 0.052,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
0.327
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.275
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.266
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.603,
|
||||
"margin": 0.393,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.603
|
||||
],
|
||||
[
|
||||
"phantom_lancer",
|
||||
0.21
|
||||
],
|
||||
[
|
||||
"monkey_king",
|
||||
0.197
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "zuus",
|
||||
"raw_best": "zuus",
|
||||
"score": 0.461,
|
||||
"margin": 0.231,
|
||||
"top3": [
|
||||
[
|
||||
"zuus",
|
||||
0.461
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.23
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.206
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.691,
|
||||
"margin": 0.247,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.691
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.445
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.396
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "juggernaut",
|
||||
"raw_best": "juggernaut",
|
||||
"score": 0.67,
|
||||
"margin": 0.201,
|
||||
"top3": [
|
||||
[
|
||||
"juggernaut",
|
||||
0.67
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.469
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.379
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "lion",
|
||||
"raw_best": "lion",
|
||||
"score": 0.509,
|
||||
"margin": 0.194,
|
||||
"top3": [
|
||||
[
|
||||
"lion",
|
||||
0.509
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.315
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.275
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 8,
|
||||
"library_size": 127,
|
||||
"elapsed_ms": 358,
|
||||
"match_id": "0",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 14:37:42",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_143742.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.598,
|
||||
"margin": 0.296,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.598
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.302
|
||||
],
|
||||
[
|
||||
"kez",
|
||||
0.237
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.676,
|
||||
"margin": 0.367,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.676
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.309
|
||||
],
|
||||
[
|
||||
"monkey_king",
|
||||
0.221
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "viper",
|
||||
"raw_best": "viper",
|
||||
"score": 0.651,
|
||||
"margin": 0.256,
|
||||
"top3": [
|
||||
[
|
||||
"viper",
|
||||
0.651
|
||||
],
|
||||
[
|
||||
"faceless_void",
|
||||
0.396
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.317
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": null,
|
||||
"raw_best": "earthshaker",
|
||||
"score": 0.434,
|
||||
"margin": 0.128,
|
||||
"top3": [
|
||||
[
|
||||
"earthshaker",
|
||||
0.434
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.306
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.297
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": null,
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 0.327,
|
||||
"margin": 0.052,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
0.327
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.275
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.266
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.603,
|
||||
"margin": 0.393,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.603
|
||||
],
|
||||
[
|
||||
"phantom_lancer",
|
||||
0.21
|
||||
],
|
||||
[
|
||||
"monkey_king",
|
||||
0.197
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "zuus",
|
||||
"raw_best": "zuus",
|
||||
"score": 0.461,
|
||||
"margin": 0.231,
|
||||
"top3": [
|
||||
[
|
||||
"zuus",
|
||||
0.461
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.23
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.206
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.691,
|
||||
"margin": 0.247,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.691
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.445
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.396
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "juggernaut",
|
||||
"raw_best": "juggernaut",
|
||||
"score": 0.67,
|
||||
"margin": 0.201,
|
||||
"top3": [
|
||||
[
|
||||
"juggernaut",
|
||||
0.67
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.469
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.379
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "lion",
|
||||
"raw_best": "lion",
|
||||
"score": 0.509,
|
||||
"margin": 0.194,
|
||||
"top3": [
|
||||
[
|
||||
"lion",
|
||||
0.509
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.315
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.275
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "crystal_maiden",
|
||||
"raw_best": "crystal_maiden",
|
||||
"score": 0.454,
|
||||
"margin": 0.222,
|
||||
"top3": [
|
||||
[
|
||||
"crystal_maiden",
|
||||
0.454
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.232
|
||||
],
|
||||
[
|
||||
"visage",
|
||||
0.206
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.637,
|
||||
"margin": 0.277,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.637
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.361
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.298
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.688,
|
||||
"margin": 0.255,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.688
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.433
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.401
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 0.652,
|
||||
"margin": 0.523,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
0.652
|
||||
],
|
||||
[
|
||||
"primal_beast",
|
||||
0.129
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.127
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.693,
|
||||
"margin": 0.356,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.693
|
||||
],
|
||||
[
|
||||
"furion",
|
||||
0.337
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.324
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": null,
|
||||
"raw_best": "earthshaker",
|
||||
"score": 0.434,
|
||||
"margin": 0.128,
|
||||
"top3": [
|
||||
[
|
||||
"earthshaker",
|
||||
0.434
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.306
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.297
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": null,
|
||||
"raw_best": "necrolyte",
|
||||
"score": 0.412,
|
||||
"margin": 0.294,
|
||||
"top3": [
|
||||
[
|
||||
"necrolyte",
|
||||
0.412
|
||||
],
|
||||
[
|
||||
"omniknight",
|
||||
0.118
|
||||
],
|
||||
[
|
||||
"alchemist",
|
||||
0.111
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.711,
|
||||
"margin": 0.413,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.711
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.297
|
||||
],
|
||||
[
|
||||
"bristleback",
|
||||
0.241
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.657,
|
||||
"margin": 0.286,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.657
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.372
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.273
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.519,
|
||||
"margin": 0.336,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.519
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.183
|
||||
],
|
||||
[
|
||||
"nyx_assassin",
|
||||
0.16
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 8,
|
||||
"library_size": 127,
|
||||
"elapsed_ms": 362,
|
||||
"match_id": "0",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 14:41:31",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_144131.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "crystal_maiden",
|
||||
"raw_best": "crystal_maiden",
|
||||
"score": 0.454,
|
||||
"margin": 0.222,
|
||||
"top3": [
|
||||
[
|
||||
"crystal_maiden",
|
||||
0.454
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.232
|
||||
],
|
||||
[
|
||||
"visage",
|
||||
0.206
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.637,
|
||||
"margin": 0.277,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.637
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.361
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.298
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.688,
|
||||
"margin": 0.255,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.688
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.433
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.401
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 0.652,
|
||||
"margin": 0.523,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
0.652
|
||||
],
|
||||
[
|
||||
"primal_beast",
|
||||
0.129
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.127
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.693,
|
||||
"margin": 0.356,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.693
|
||||
],
|
||||
[
|
||||
"furion",
|
||||
0.337
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.324
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": null,
|
||||
"raw_best": "earthshaker",
|
||||
"score": 0.434,
|
||||
"margin": 0.128,
|
||||
"top3": [
|
||||
[
|
||||
"earthshaker",
|
||||
0.434
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.306
|
||||
],
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.297
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": null,
|
||||
"raw_best": "necrolyte",
|
||||
"score": 0.412,
|
||||
"margin": 0.294,
|
||||
"top3": [
|
||||
[
|
||||
"necrolyte",
|
||||
0.412
|
||||
],
|
||||
[
|
||||
"omniknight",
|
||||
0.118
|
||||
],
|
||||
[
|
||||
"alchemist",
|
||||
0.111
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.711,
|
||||
"margin": 0.413,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.711
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.297
|
||||
],
|
||||
[
|
||||
"bristleback",
|
||||
0.241
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.657,
|
||||
"margin": 0.286,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.657
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.372
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.273
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.519,
|
||||
"margin": 0.336,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.519
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.183
|
||||
],
|
||||
[
|
||||
"nyx_assassin",
|
||||
0.16
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": null,
|
||||
"raw_best": "elder_titan",
|
||||
"score": 0.446,
|
||||
"margin": 0.184,
|
||||
"top3": [
|
||||
[
|
||||
"elder_titan",
|
||||
0.446
|
||||
],
|
||||
[
|
||||
"warlock",
|
||||
0.262
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.222
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 1.0,
|
||||
"margin": 0.871,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"primal_beast",
|
||||
0.129
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.127
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "lion",
|
||||
"raw_best": "lion",
|
||||
"score": 1.0,
|
||||
"margin": 0.633,
|
||||
"top3": [
|
||||
[
|
||||
"lion",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.367
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.34
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "juggernaut",
|
||||
"raw_best": "juggernaut",
|
||||
"score": 1.0,
|
||||
"margin": 0.516,
|
||||
"top3": [
|
||||
[
|
||||
"juggernaut",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.484
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.395
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": null,
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 0.327,
|
||||
"margin": 0.052,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
0.327
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.275
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.266
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "death_prophet",
|
||||
"raw_best": "death_prophet",
|
||||
"score": 0.951,
|
||||
"margin": 0.551,
|
||||
"top3": [
|
||||
[
|
||||
"death_prophet",
|
||||
0.951
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.4
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.372
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "tidehunter",
|
||||
"raw_best": "tidehunter",
|
||||
"score": 0.528,
|
||||
"margin": 0.271,
|
||||
"top3": [
|
||||
[
|
||||
"tidehunter",
|
||||
0.528
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.257
|
||||
],
|
||||
[
|
||||
"lich",
|
||||
0.208
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.96,
|
||||
"margin": 0.703,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.96
|
||||
],
|
||||
[
|
||||
"warlock",
|
||||
0.257
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.201
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.912,
|
||||
"margin": 0.576,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.912
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.336
|
||||
],
|
||||
[
|
||||
"furion",
|
||||
0.324
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.706,
|
||||
"margin": 0.338,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.706
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.368
|
||||
],
|
||||
[
|
||||
"zuus",
|
||||
0.298
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 8,
|
||||
"library_size": 155,
|
||||
"elapsed_ms": 447,
|
||||
"match_id": "0",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 14:51:36",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_145136.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": null,
|
||||
"raw_best": "elder_titan",
|
||||
"score": 0.446,
|
||||
"margin": 0.184,
|
||||
"top3": [
|
||||
[
|
||||
"elder_titan",
|
||||
0.446
|
||||
],
|
||||
[
|
||||
"warlock",
|
||||
0.262
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.222
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 1.0,
|
||||
"margin": 0.871,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"primal_beast",
|
||||
0.129
|
||||
],
|
||||
[
|
||||
"naga_siren",
|
||||
0.127
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "lion",
|
||||
"raw_best": "lion",
|
||||
"score": 1.0,
|
||||
"margin": 0.633,
|
||||
"top3": [
|
||||
[
|
||||
"lion",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.367
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.34
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "juggernaut",
|
||||
"raw_best": "juggernaut",
|
||||
"score": 1.0,
|
||||
"margin": 0.516,
|
||||
"top3": [
|
||||
[
|
||||
"juggernaut",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.484
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.395
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": null,
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 0.327,
|
||||
"margin": 0.052,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
0.327
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.275
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.266
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "death_prophet",
|
||||
"raw_best": "death_prophet",
|
||||
"score": 0.951,
|
||||
"margin": 0.551,
|
||||
"top3": [
|
||||
[
|
||||
"death_prophet",
|
||||
0.951
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.4
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.372
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "tidehunter",
|
||||
"raw_best": "tidehunter",
|
||||
"score": 0.528,
|
||||
"margin": 0.271,
|
||||
"top3": [
|
||||
[
|
||||
"tidehunter",
|
||||
0.528
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.257
|
||||
],
|
||||
[
|
||||
"lich",
|
||||
0.208
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "lich",
|
||||
"raw_best": "lich",
|
||||
"score": 0.96,
|
||||
"margin": 0.703,
|
||||
"top3": [
|
||||
[
|
||||
"lich",
|
||||
0.96
|
||||
],
|
||||
[
|
||||
"warlock",
|
||||
0.257
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.201
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.912,
|
||||
"margin": 0.576,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.912
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.336
|
||||
],
|
||||
[
|
||||
"furion",
|
||||
0.324
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.706,
|
||||
"margin": 0.338,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.706
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.368
|
||||
],
|
||||
[
|
||||
"zuus",
|
||||
0.298
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "undying",
|
||||
"raw_best": "undying",
|
||||
"score": 0.877,
|
||||
"margin": 0.441,
|
||||
"top3": [
|
||||
[
|
||||
"undying",
|
||||
0.877
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.437
|
||||
],
|
||||
[
|
||||
"faceless_void",
|
||||
0.387
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "death_prophet",
|
||||
"raw_best": "death_prophet",
|
||||
"score": 1.0,
|
||||
"margin": 0.601,
|
||||
"top3": [
|
||||
[
|
||||
"death_prophet",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.399
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.382
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "dragon_knight",
|
||||
"raw_best": "dragon_knight",
|
||||
"score": 0.917,
|
||||
"margin": 0.629,
|
||||
"top3": [
|
||||
[
|
||||
"dragon_knight",
|
||||
0.917
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.289
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.209
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.955,
|
||||
"margin": 0.583,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.955
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.372
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.316
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 1.0,
|
||||
"margin": 0.641,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.359
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.321
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "vengefulspirit",
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 1.0,
|
||||
"margin": 0.728,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.272
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.269
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.98,
|
||||
"margin": 0.704,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.98
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.276
|
||||
],
|
||||
[
|
||||
"bristleback",
|
||||
0.263
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "tidehunter",
|
||||
"raw_best": "tidehunter",
|
||||
"score": 0.968,
|
||||
"margin": 0.71,
|
||||
"top3": [
|
||||
[
|
||||
"tidehunter",
|
||||
0.968
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.258
|
||||
],
|
||||
[
|
||||
"lich",
|
||||
0.199
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "zuus",
|
||||
"raw_best": "zuus",
|
||||
"score": 1.0,
|
||||
"margin": 0.691,
|
||||
"top3": [
|
||||
[
|
||||
"zuus",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.309
|
||||
],
|
||||
[
|
||||
"dark_willow",
|
||||
0.243
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.974,
|
||||
"margin": 0.631,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.974
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.342
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.327
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 10,
|
||||
"library_size": 167,
|
||||
"elapsed_ms": 497,
|
||||
"match_id": "0",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 15:10:21",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_151021.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "undying",
|
||||
"raw_best": "undying",
|
||||
"score": 0.877,
|
||||
"margin": 0.441,
|
||||
"top3": [
|
||||
[
|
||||
"undying",
|
||||
0.877
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.437
|
||||
],
|
||||
[
|
||||
"faceless_void",
|
||||
0.387
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "death_prophet",
|
||||
"raw_best": "death_prophet",
|
||||
"score": 1.0,
|
||||
"margin": 0.601,
|
||||
"top3": [
|
||||
[
|
||||
"death_prophet",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.399
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.382
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "dragon_knight",
|
||||
"raw_best": "dragon_knight",
|
||||
"score": 0.917,
|
||||
"margin": 0.629,
|
||||
"top3": [
|
||||
[
|
||||
"dragon_knight",
|
||||
0.917
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.289
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.209
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.955,
|
||||
"margin": 0.583,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.955
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.372
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.316
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 1.0,
|
||||
"margin": 0.641,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.359
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.321
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "vengefulspirit",
|
||||
"raw_best": "vengefulspirit",
|
||||
"score": 1.0,
|
||||
"margin": 0.728,
|
||||
"top3": [
|
||||
[
|
||||
"vengefulspirit",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.272
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.269
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "warlock",
|
||||
"raw_best": "warlock",
|
||||
"score": 0.98,
|
||||
"margin": 0.704,
|
||||
"top3": [
|
||||
[
|
||||
"warlock",
|
||||
0.98
|
||||
],
|
||||
[
|
||||
"treant",
|
||||
0.276
|
||||
],
|
||||
[
|
||||
"bristleback",
|
||||
0.263
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "tidehunter",
|
||||
"raw_best": "tidehunter",
|
||||
"score": 0.968,
|
||||
"margin": 0.71,
|
||||
"top3": [
|
||||
[
|
||||
"tidehunter",
|
||||
0.968
|
||||
],
|
||||
[
|
||||
"juggernaut",
|
||||
0.258
|
||||
],
|
||||
[
|
||||
"lich",
|
||||
0.199
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "zuus",
|
||||
"raw_best": "zuus",
|
||||
"score": 1.0,
|
||||
"margin": 0.691,
|
||||
"top3": [
|
||||
[
|
||||
"zuus",
|
||||
1.0
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.309
|
||||
],
|
||||
[
|
||||
"dark_willow",
|
||||
0.243
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "sven",
|
||||
"raw_best": "sven",
|
||||
"score": 0.974,
|
||||
"margin": 0.631,
|
||||
"top3": [
|
||||
[
|
||||
"sven",
|
||||
0.974
|
||||
],
|
||||
[
|
||||
"lion",
|
||||
0.342
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.327
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": null,
|
||||
"raw_best": "bounty_hunter",
|
||||
"score": 0.283,
|
||||
"margin": 0.01,
|
||||
"top3": [
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.283
|
||||
],
|
||||
[
|
||||
"grimstroke",
|
||||
0.273
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.257
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.793,
|
||||
"margin": 0.431,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.793
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.362
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.328
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 0.717,
|
||||
"margin": 0.448,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
0.717
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.27
|
||||
],
|
||||
[
|
||||
"spirit_breaker",
|
||||
0.178
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "invoker",
|
||||
"raw_best": "invoker",
|
||||
"score": 0.839,
|
||||
"margin": 0.385,
|
||||
"top3": [
|
||||
[
|
||||
"invoker",
|
||||
0.839
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.454
|
||||
],
|
||||
[
|
||||
"sand_king",
|
||||
0.445
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "silencer",
|
||||
"raw_best": "silencer",
|
||||
"score": 0.577,
|
||||
"margin": 0.43,
|
||||
"top3": [
|
||||
[
|
||||
"silencer",
|
||||
0.577
|
||||
],
|
||||
[
|
||||
"troll_warlord",
|
||||
0.147
|
||||
],
|
||||
[
|
||||
"necrolyte",
|
||||
0.137
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "skywrath_mage",
|
||||
"raw_best": "skywrath_mage",
|
||||
"score": 0.61,
|
||||
"margin": 0.282,
|
||||
"top3": [
|
||||
[
|
||||
"skywrath_mage",
|
||||
0.61
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.328
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.322
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "phantom_assassin",
|
||||
"raw_best": "phantom_assassin",
|
||||
"score": 0.621,
|
||||
"margin": 0.382,
|
||||
"top3": [
|
||||
[
|
||||
"phantom_assassin",
|
||||
0.621
|
||||
],
|
||||
[
|
||||
"crystal_maiden",
|
||||
0.24
|
||||
],
|
||||
[
|
||||
"drow_ranger",
|
||||
0.235
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "queenofpain",
|
||||
"raw_best": "queenofpain",
|
||||
"score": 0.686,
|
||||
"margin": 0.395,
|
||||
"top3": [
|
||||
[
|
||||
"queenofpain",
|
||||
0.686
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.291
|
||||
],
|
||||
[
|
||||
"drow_ranger",
|
||||
0.283
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "centaur",
|
||||
"raw_best": "centaur",
|
||||
"score": 0.71,
|
||||
"margin": 0.428,
|
||||
"top3": [
|
||||
[
|
||||
"centaur",
|
||||
0.71
|
||||
],
|
||||
[
|
||||
"sven",
|
||||
0.282
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.281
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "rubick",
|
||||
"raw_best": "rubick",
|
||||
"score": 0.623,
|
||||
"margin": 0.415,
|
||||
"top3": [
|
||||
[
|
||||
"rubick",
|
||||
0.623
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.208
|
||||
],
|
||||
[
|
||||
"elder_titan",
|
||||
0.153
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 9,
|
||||
"library_size": 177,
|
||||
"elapsed_ms": 492,
|
||||
"match_id": "8912562036",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 15:18:28",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_151828.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": null,
|
||||
"raw_best": "bounty_hunter",
|
||||
"score": 0.283,
|
||||
"margin": 0.01,
|
||||
"top3": [
|
||||
[
|
||||
"bounty_hunter",
|
||||
0.283
|
||||
],
|
||||
[
|
||||
"grimstroke",
|
||||
0.273
|
||||
],
|
||||
[
|
||||
"witch_doctor",
|
||||
0.257
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.793,
|
||||
"margin": 0.431,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.793
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.362
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.328
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "sniper",
|
||||
"raw_best": "sniper",
|
||||
"score": 0.717,
|
||||
"margin": 0.448,
|
||||
"top3": [
|
||||
[
|
||||
"sniper",
|
||||
0.717
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.27
|
||||
],
|
||||
[
|
||||
"spirit_breaker",
|
||||
0.178
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "invoker",
|
||||
"raw_best": "invoker",
|
||||
"score": 0.839,
|
||||
"margin": 0.385,
|
||||
"top3": [
|
||||
[
|
||||
"invoker",
|
||||
0.839
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.454
|
||||
],
|
||||
[
|
||||
"sand_king",
|
||||
0.445
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "silencer",
|
||||
"raw_best": "silencer",
|
||||
"score": 0.577,
|
||||
"margin": 0.43,
|
||||
"top3": [
|
||||
[
|
||||
"silencer",
|
||||
0.577
|
||||
],
|
||||
[
|
||||
"troll_warlord",
|
||||
0.147
|
||||
],
|
||||
[
|
||||
"necrolyte",
|
||||
0.137
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "skywrath_mage",
|
||||
"raw_best": "skywrath_mage",
|
||||
"score": 0.61,
|
||||
"margin": 0.282,
|
||||
"top3": [
|
||||
[
|
||||
"skywrath_mage",
|
||||
0.61
|
||||
],
|
||||
[
|
||||
"undying",
|
||||
0.328
|
||||
],
|
||||
[
|
||||
"ogre_magi",
|
||||
0.322
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "phantom_assassin",
|
||||
"raw_best": "phantom_assassin",
|
||||
"score": 0.621,
|
||||
"margin": 0.382,
|
||||
"top3": [
|
||||
[
|
||||
"phantom_assassin",
|
||||
0.621
|
||||
],
|
||||
[
|
||||
"crystal_maiden",
|
||||
0.24
|
||||
],
|
||||
[
|
||||
"drow_ranger",
|
||||
0.235
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "queenofpain",
|
||||
"raw_best": "queenofpain",
|
||||
"score": 0.686,
|
||||
"margin": 0.395,
|
||||
"top3": [
|
||||
[
|
||||
"queenofpain",
|
||||
0.686
|
||||
],
|
||||
[
|
||||
"lycan",
|
||||
0.291
|
||||
],
|
||||
[
|
||||
"drow_ranger",
|
||||
0.283
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "centaur",
|
||||
"raw_best": "centaur",
|
||||
"score": 0.71,
|
||||
"margin": 0.428,
|
||||
"top3": [
|
||||
[
|
||||
"centaur",
|
||||
0.71
|
||||
],
|
||||
[
|
||||
"sven",
|
||||
0.282
|
||||
],
|
||||
[
|
||||
"ringmaster",
|
||||
0.281
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "rubick",
|
||||
"raw_best": "rubick",
|
||||
"score": 0.623,
|
||||
"margin": 0.415,
|
||||
"top3": [
|
||||
[
|
||||
"rubick",
|
||||
0.623
|
||||
],
|
||||
[
|
||||
"death_prophet",
|
||||
0.208
|
||||
],
|
||||
[
|
||||
"elder_titan",
|
||||
0.153
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
{
|
||||
"radiant": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "clinkz",
|
||||
"raw_best": "clinkz",
|
||||
"score": 0.873,
|
||||
"margin": 0.478,
|
||||
"top3": [
|
||||
[
|
||||
"clinkz",
|
||||
0.873
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.395
|
||||
],
|
||||
[
|
||||
"centaur",
|
||||
0.392
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.962,
|
||||
"margin": 0.672,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.962
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.29
|
||||
],
|
||||
[
|
||||
"tiny",
|
||||
0.26
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "abyssal_underlord",
|
||||
"raw_best": "abyssal_underlord",
|
||||
"score": 0.878,
|
||||
"margin": 0.418,
|
||||
"top3": [
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.878
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.46
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.432
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "pudge",
|
||||
"raw_best": "pudge",
|
||||
"score": 0.884,
|
||||
"margin": 0.359,
|
||||
"top3": [
|
||||
[
|
||||
"pudge",
|
||||
0.884
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.526
|
||||
],
|
||||
[
|
||||
"centaur",
|
||||
0.443
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.902,
|
||||
"margin": 0.491,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.902
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.411
|
||||
],
|
||||
[
|
||||
"arc_warden",
|
||||
0.317
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"dire": [
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "life_stealer",
|
||||
"raw_best": "life_stealer",
|
||||
"score": 0.894,
|
||||
"margin": 0.525,
|
||||
"top3": [
|
||||
[
|
||||
"life_stealer",
|
||||
0.894
|
||||
],
|
||||
[
|
||||
"dawnbreaker",
|
||||
0.369
|
||||
],
|
||||
[
|
||||
"silencer",
|
||||
0.313
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "weaver",
|
||||
"raw_best": "weaver",
|
||||
"score": 0.877,
|
||||
"margin": 0.379,
|
||||
"top3": [
|
||||
[
|
||||
"weaver",
|
||||
0.877
|
||||
],
|
||||
[
|
||||
"sven",
|
||||
0.498
|
||||
],
|
||||
[
|
||||
"spirit_breaker",
|
||||
0.497
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "skywrath_mage",
|
||||
"raw_best": "skywrath_mage",
|
||||
"score": 0.998,
|
||||
"margin": 0.701,
|
||||
"top3": [
|
||||
[
|
||||
"skywrath_mage",
|
||||
0.998
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.297
|
||||
],
|
||||
[
|
||||
"sand_king",
|
||||
0.259
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.972,
|
||||
"margin": 0.621,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.972
|
||||
],
|
||||
[
|
||||
"phantom_assassin",
|
||||
0.351
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.305
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "storm_spirit",
|
||||
"raw_best": "storm_spirit",
|
||||
"score": 0.821,
|
||||
"margin": 0.306,
|
||||
"top3": [
|
||||
[
|
||||
"storm_spirit",
|
||||
0.821
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.515
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.46
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"recognized": 10,
|
||||
"ranked_overlay": true,
|
||||
"library_size": 187,
|
||||
"elapsed_ms": 507,
|
||||
"match_id": "8912632259",
|
||||
"trigger_state": "DOTA_GAMERULES_STATE_STRATEGY_TIME",
|
||||
"captured_at": "2026-07-25 16:24:04",
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_162404.png",
|
||||
"slots": [
|
||||
{
|
||||
"slot": 1,
|
||||
"hero": "clinkz",
|
||||
"raw_best": "clinkz",
|
||||
"score": 0.873,
|
||||
"margin": 0.478,
|
||||
"top3": [
|
||||
[
|
||||
"clinkz",
|
||||
0.873
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.395
|
||||
],
|
||||
[
|
||||
"centaur",
|
||||
0.392
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"hero": "witch_doctor",
|
||||
"raw_best": "witch_doctor",
|
||||
"score": 0.962,
|
||||
"margin": 0.672,
|
||||
"top3": [
|
||||
[
|
||||
"witch_doctor",
|
||||
0.962
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.29
|
||||
],
|
||||
[
|
||||
"tiny",
|
||||
0.26
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"hero": "abyssal_underlord",
|
||||
"raw_best": "abyssal_underlord",
|
||||
"score": 0.878,
|
||||
"margin": 0.418,
|
||||
"top3": [
|
||||
[
|
||||
"abyssal_underlord",
|
||||
0.878
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.46
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.432
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"hero": "pudge",
|
||||
"raw_best": "pudge",
|
||||
"score": 0.884,
|
||||
"margin": 0.359,
|
||||
"top3": [
|
||||
[
|
||||
"pudge",
|
||||
0.884
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.526
|
||||
],
|
||||
[
|
||||
"centaur",
|
||||
0.443
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"hero": "drow_ranger",
|
||||
"raw_best": "drow_ranger",
|
||||
"score": 0.902,
|
||||
"margin": 0.491,
|
||||
"top3": [
|
||||
[
|
||||
"drow_ranger",
|
||||
0.902
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.411
|
||||
],
|
||||
[
|
||||
"arc_warden",
|
||||
0.317
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"hero": "life_stealer",
|
||||
"raw_best": "life_stealer",
|
||||
"score": 0.894,
|
||||
"margin": 0.525,
|
||||
"top3": [
|
||||
[
|
||||
"life_stealer",
|
||||
0.894
|
||||
],
|
||||
[
|
||||
"dawnbreaker",
|
||||
0.369
|
||||
],
|
||||
[
|
||||
"silencer",
|
||||
0.313
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"hero": "weaver",
|
||||
"raw_best": "weaver",
|
||||
"score": 0.877,
|
||||
"margin": 0.379,
|
||||
"top3": [
|
||||
[
|
||||
"weaver",
|
||||
0.877
|
||||
],
|
||||
[
|
||||
"sven",
|
||||
0.498
|
||||
],
|
||||
[
|
||||
"spirit_breaker",
|
||||
0.497
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"hero": "skywrath_mage",
|
||||
"raw_best": "skywrath_mage",
|
||||
"score": 0.998,
|
||||
"margin": 0.701,
|
||||
"top3": [
|
||||
[
|
||||
"skywrath_mage",
|
||||
0.998
|
||||
],
|
||||
[
|
||||
"legion_commander",
|
||||
0.297
|
||||
],
|
||||
[
|
||||
"sand_king",
|
||||
0.259
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"hero": "skeleton_king",
|
||||
"raw_best": "skeleton_king",
|
||||
"score": 0.972,
|
||||
"margin": 0.621,
|
||||
"top3": [
|
||||
[
|
||||
"skeleton_king",
|
||||
0.972
|
||||
],
|
||||
[
|
||||
"phantom_assassin",
|
||||
0.351
|
||||
],
|
||||
[
|
||||
"invoker",
|
||||
0.305
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"hero": "storm_spirit",
|
||||
"raw_best": "storm_spirit",
|
||||
"score": 0.821,
|
||||
"margin": 0.306,
|
||||
"top3": [
|
||||
[
|
||||
"storm_spirit",
|
||||
0.821
|
||||
],
|
||||
[
|
||||
"wisp",
|
||||
0.515
|
||||
],
|
||||
[
|
||||
"skeleton_king",
|
||||
0.46
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
{
|
||||
"match_id": "8912704440",
|
||||
"captured_at": "2026-07-25 17:25:30",
|
||||
"duration_s": 111.0,
|
||||
"polls": 70,
|
||||
"self": {
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "life_stealer",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"1": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "queenofpain"
|
||||
},
|
||||
"2": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "lina"
|
||||
},
|
||||
"3": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "life_stealer"
|
||||
},
|
||||
"4": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "lion"
|
||||
},
|
||||
"5": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": null
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"queenofpain",
|
||||
"lina",
|
||||
"life_stealer",
|
||||
"lion",
|
||||
null
|
||||
],
|
||||
"dire": [
|
||||
"spectre",
|
||||
"lich",
|
||||
"earth_spirit",
|
||||
"undying",
|
||||
"necrolyte"
|
||||
]
|
||||
},
|
||||
"recognized": 9,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 13.8,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "lina"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"lina"
|
||||
],
|
||||
"dire": [],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172352.png"
|
||||
},
|
||||
{
|
||||
"t": 30.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "lion"
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "lich"
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "undying"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"lina",
|
||||
"lion"
|
||||
],
|
||||
"dire": [
|
||||
"lich",
|
||||
"undying"
|
||||
],
|
||||
"count": 4,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172409.png"
|
||||
},
|
||||
{
|
||||
"t": 52.9,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "life_stealer"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"lina",
|
||||
"life_stealer",
|
||||
"lion"
|
||||
],
|
||||
"dire": [
|
||||
"lich",
|
||||
"undying"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172431.png"
|
||||
},
|
||||
{
|
||||
"t": 54.6,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "queenofpain"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "spectre"
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "earth_spirit"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"queenofpain",
|
||||
"lina",
|
||||
"life_stealer",
|
||||
"lion"
|
||||
],
|
||||
"dire": [
|
||||
"spectre",
|
||||
"lich",
|
||||
"earth_spirit",
|
||||
"undying"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172433.png"
|
||||
},
|
||||
{
|
||||
"t": 82.2,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "necrolyte"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"queenofpain",
|
||||
"lina",
|
||||
"life_stealer",
|
||||
"lion"
|
||||
],
|
||||
"dire": [
|
||||
"spectre",
|
||||
"lich",
|
||||
"earth_spirit",
|
||||
"undying",
|
||||
"necrolyte"
|
||||
],
|
||||
"count": 9,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172501.png"
|
||||
}
|
||||
],
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_172530.png"
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
{
|
||||
"match_id": "8912791143",
|
||||
"captured_at": "2026-07-25 18:38:26",
|
||||
"duration_s": 124.4,
|
||||
"polls": 84,
|
||||
"self": {
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "drow_ranger",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"6": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "witch_doctor"
|
||||
},
|
||||
"7": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": null
|
||||
},
|
||||
"8": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "faceless_void"
|
||||
},
|
||||
"9": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "drow_ranger"
|
||||
},
|
||||
"10": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "queenofpain"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"razor",
|
||||
"nyx_assassin",
|
||||
"hoodwink",
|
||||
"zuus",
|
||||
"antimage"
|
||||
],
|
||||
"dire": [
|
||||
"witch_doctor",
|
||||
null,
|
||||
"faceless_void",
|
||||
"drow_ranger",
|
||||
"queenofpain"
|
||||
]
|
||||
},
|
||||
"recognized": 9,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 11.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "drow_ranger"
|
||||
}
|
||||
],
|
||||
"radiant": [],
|
||||
"dire": [
|
||||
"drow_ranger"
|
||||
],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183633.png"
|
||||
},
|
||||
{
|
||||
"t": 25.7,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "faceless_void"
|
||||
}
|
||||
],
|
||||
"radiant": [],
|
||||
"dire": [
|
||||
"faceless_void",
|
||||
"drow_ranger"
|
||||
],
|
||||
"count": 2,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183648.png"
|
||||
},
|
||||
{
|
||||
"t": 39.2,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "razor"
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "zuus"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "witch_doctor"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"razor",
|
||||
"zuus"
|
||||
],
|
||||
"dire": [
|
||||
"witch_doctor",
|
||||
"faceless_void",
|
||||
"drow_ranger"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183701.png"
|
||||
},
|
||||
{
|
||||
"t": 40.7,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "queenofpain"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"razor",
|
||||
"zuus"
|
||||
],
|
||||
"dire": [
|
||||
"witch_doctor",
|
||||
"faceless_void",
|
||||
"drow_ranger",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 6,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183703.png"
|
||||
},
|
||||
{
|
||||
"t": 62.9,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "nyx_assassin"
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "antimage"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"razor",
|
||||
"nyx_assassin",
|
||||
"zuus",
|
||||
"antimage"
|
||||
],
|
||||
"dire": [
|
||||
"witch_doctor",
|
||||
"faceless_void",
|
||||
"drow_ranger",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183725.png"
|
||||
},
|
||||
{
|
||||
"t": 96.8,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "hoodwink"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"razor",
|
||||
"nyx_assassin",
|
||||
"hoodwink",
|
||||
"zuus",
|
||||
"antimage"
|
||||
],
|
||||
"dire": [
|
||||
"witch_doctor",
|
||||
"faceless_void",
|
||||
"drow_ranger",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 9,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183759.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"venomancer",
|
||||
"obsidian_destroyer",
|
||||
"necrolyte",
|
||||
"silencer",
|
||||
"ursa",
|
||||
"nevermore",
|
||||
"lina",
|
||||
"lion",
|
||||
"magnataur",
|
||||
"ember_spirit",
|
||||
"snapfire",
|
||||
"alchemist",
|
||||
"windrunner",
|
||||
"sniper",
|
||||
"pudge",
|
||||
"abyssal_underlord"
|
||||
],
|
||||
"bans_loc": [
|
||||
"剧毒术士",
|
||||
"殁境神蚀者",
|
||||
"瘟疫法师",
|
||||
"沉默术士",
|
||||
"熊战士",
|
||||
"影魔",
|
||||
"莉娜",
|
||||
"莱恩",
|
||||
"马格纳斯",
|
||||
"灰烬之灵",
|
||||
"电炎绝手",
|
||||
"炼金术士",
|
||||
"风行者",
|
||||
"狙击手",
|
||||
"帕吉",
|
||||
"孽主"
|
||||
],
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_183826.png"
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
{
|
||||
"match_id": "8912846004",
|
||||
"captured_at": "2026-07-25 19:21:11",
|
||||
"duration_s": 65.7,
|
||||
"polls": 45,
|
||||
"self": {
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "juggernaut",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"2": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "juggernaut"
|
||||
},
|
||||
"3": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "shadow_shaman"
|
||||
},
|
||||
"4": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "death_prophet"
|
||||
},
|
||||
"5": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "tidehunter"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"necrolyte",
|
||||
"juggernaut",
|
||||
"shadow_shaman",
|
||||
"death_prophet",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"invoker",
|
||||
"hoodwink",
|
||||
"axe",
|
||||
"earthshaker",
|
||||
"skeleton_king"
|
||||
]
|
||||
},
|
||||
"recognized": 10,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 7.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "tidehunter"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192012.png"
|
||||
},
|
||||
{
|
||||
"t": 10.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "shadow_shaman"
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "hoodwink"
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "earthshaker"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"shadow_shaman",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"earthshaker"
|
||||
],
|
||||
"count": 4,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192015.png"
|
||||
},
|
||||
{
|
||||
"t": 13.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "juggernaut"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"spectre",
|
||||
"shadow_shaman",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"earthshaker"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192018.png"
|
||||
},
|
||||
{
|
||||
"t": 32.2,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "death_prophet"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"spectre",
|
||||
"shadow_shaman",
|
||||
"death_prophet",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"earthshaker"
|
||||
],
|
||||
"count": 6,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192038.png"
|
||||
},
|
||||
{
|
||||
"t": 36.6,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "axe"
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "skeleton_king"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"spectre",
|
||||
"shadow_shaman",
|
||||
"death_prophet",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"axe",
|
||||
"earthshaker",
|
||||
"skeleton_king"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192042.png"
|
||||
},
|
||||
{
|
||||
"t": 65.6,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "necrolyte"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "invoker"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"necrolyte",
|
||||
"spectre",
|
||||
"shadow_shaman",
|
||||
"death_prophet",
|
||||
"tidehunter"
|
||||
],
|
||||
"dire": [
|
||||
"invoker",
|
||||
"hoodwink",
|
||||
"axe",
|
||||
"earthshaker",
|
||||
"skeleton_king"
|
||||
],
|
||||
"count": 10,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192111.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"disruptor",
|
||||
"phantom_lancer",
|
||||
"lion",
|
||||
"zuus",
|
||||
"rubick",
|
||||
"silencer",
|
||||
"weaver",
|
||||
"dark_willow",
|
||||
"abyssal_underlord",
|
||||
"windrunner",
|
||||
"medusa",
|
||||
"razor",
|
||||
"pudge",
|
||||
"bristleback",
|
||||
"drow_ranger",
|
||||
"sniper",
|
||||
"huskar"
|
||||
],
|
||||
"bans_loc": [
|
||||
"干扰者",
|
||||
"幻影长矛手",
|
||||
"莱恩",
|
||||
"宙斯",
|
||||
"拉比克",
|
||||
"沉默术士",
|
||||
"编织者",
|
||||
"邪影芳灵",
|
||||
"孽主",
|
||||
"风行者",
|
||||
"美杜莎",
|
||||
"雷泽",
|
||||
"帕吉",
|
||||
"钢背兽",
|
||||
"卓尔游侠",
|
||||
"狙击手",
|
||||
"哈斯卡"
|
||||
],
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_192111_1.png"
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
{
|
||||
"match_id": "8912903800",
|
||||
"captured_at": "2026-07-25 20:03:24",
|
||||
"duration_s": 80.0,
|
||||
"polls": 51,
|
||||
"self": {
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "bloodseeker",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"1": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "pudge"
|
||||
},
|
||||
"2": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "venomancer"
|
||||
},
|
||||
"3": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "weaver"
|
||||
},
|
||||
"4": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "kunkka"
|
||||
},
|
||||
"5": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "bloodseeker"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"pudge",
|
||||
"venomancer",
|
||||
"weaver",
|
||||
"kunkka",
|
||||
"bloodseeker"
|
||||
],
|
||||
"dire": [
|
||||
"undying",
|
||||
"warlock",
|
||||
"luna",
|
||||
"hoodwink",
|
||||
"queenofpain"
|
||||
]
|
||||
},
|
||||
"recognized": 10,
|
||||
"timeline": [
|
||||
{
|
||||
"t": 5.7,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "pudge"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"pudge"
|
||||
],
|
||||
"dire": [],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200210.png"
|
||||
},
|
||||
{
|
||||
"t": 28.4,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "venomancer"
|
||||
},
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "hoodwink"
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "queenofpain"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"pudge",
|
||||
"venomancer"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 4,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200233.png"
|
||||
},
|
||||
{
|
||||
"t": 51.2,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "weaver"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"pudge",
|
||||
"venomancer",
|
||||
"weaver"
|
||||
],
|
||||
"dire": [
|
||||
"hoodwink",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200256.png"
|
||||
},
|
||||
{
|
||||
"t": 54.3,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "bloodseeker"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "undying"
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "warlock"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"pudge",
|
||||
"venomancer",
|
||||
"weaver",
|
||||
"bloodseeker"
|
||||
],
|
||||
"dire": [
|
||||
"undying",
|
||||
"warlock",
|
||||
"hoodwink",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200259.png"
|
||||
},
|
||||
{
|
||||
"t": 79.9,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "kunkka"
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "luna"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"pudge",
|
||||
"venomancer",
|
||||
"weaver",
|
||||
"kunkka",
|
||||
"bloodseeker"
|
||||
],
|
||||
"dire": [
|
||||
"undying",
|
||||
"warlock",
|
||||
"luna",
|
||||
"hoodwink",
|
||||
"queenofpain"
|
||||
],
|
||||
"count": 10,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200324.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"disruptor",
|
||||
"skywrath_mage",
|
||||
"phantom_assassin",
|
||||
"lich",
|
||||
"lion",
|
||||
"meepo",
|
||||
"riki",
|
||||
"tidehunter",
|
||||
"axe",
|
||||
"abyssal_underlord",
|
||||
"tusk",
|
||||
"phoenix",
|
||||
"legion_commander",
|
||||
"snapfire",
|
||||
"bounty_hunter",
|
||||
"sniper"
|
||||
],
|
||||
"bans_loc": [
|
||||
"干扰者",
|
||||
"天怒法师",
|
||||
"幻影刺客",
|
||||
"巫妖",
|
||||
"莱恩",
|
||||
"米波",
|
||||
"力丸",
|
||||
"潮汐猎人",
|
||||
"斧王",
|
||||
"孽主",
|
||||
"巨牙海民",
|
||||
"凤凰",
|
||||
"军团指挥官",
|
||||
"电炎绝手",
|
||||
"赏金猎人",
|
||||
"狙击手"
|
||||
],
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_200324_1.png"
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
{
|
||||
"match_id": "8913003670",
|
||||
"captured_at": "2026-07-25 21:07:27",
|
||||
"duration_s": 116.2,
|
||||
"polls": 76,
|
||||
"self": {
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "juggernaut",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"6": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": null
|
||||
},
|
||||
"7": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": null
|
||||
},
|
||||
"8": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": null
|
||||
},
|
||||
"9": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": null
|
||||
},
|
||||
"10": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "juggernaut"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"spirit_breaker",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"dire": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"juggernaut"
|
||||
]
|
||||
},
|
||||
"recognized": 2,
|
||||
"revisions": [],
|
||||
"timeline": [
|
||||
{
|
||||
"t": 42.0,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "spirit_breaker"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"spirit_breaker"
|
||||
],
|
||||
"dire": [],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_210612.png"
|
||||
},
|
||||
{
|
||||
"t": 56.9,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "invoker"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"spirit_breaker"
|
||||
],
|
||||
"dire": [
|
||||
"invoker"
|
||||
],
|
||||
"count": 2,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_210627.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"invoker",
|
||||
"zuus",
|
||||
"pugna",
|
||||
"silencer",
|
||||
"nevermore",
|
||||
"nyx_assassin",
|
||||
"magnataur",
|
||||
"lion",
|
||||
"pudge",
|
||||
"gyrocopter",
|
||||
"storm_spirit",
|
||||
"mirana",
|
||||
"axe",
|
||||
"pangolier",
|
||||
"abyssal_underlord",
|
||||
"sven",
|
||||
"legion_commander",
|
||||
"dawnbreaker"
|
||||
],
|
||||
"bans_loc": [
|
||||
"祈求者",
|
||||
"宙斯",
|
||||
"帕格纳",
|
||||
"沉默术士",
|
||||
"影魔",
|
||||
"司夜刺客",
|
||||
"马格纳斯",
|
||||
"莱恩",
|
||||
"帕吉",
|
||||
"矮人直升机",
|
||||
"风暴之灵",
|
||||
"米拉娜",
|
||||
"斧王",
|
||||
"石鳞剑士",
|
||||
"孽主",
|
||||
"斯温",
|
||||
"军团指挥官",
|
||||
"破晓辰星"
|
||||
],
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_210727.png"
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
{
|
||||
"match_id": "8913098624",
|
||||
"captured_at": "2026-07-25 22:02:49",
|
||||
"duration_s": 12.3,
|
||||
"polls": 8,
|
||||
"self": {
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "phantom_lancer",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"6": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "lina"
|
||||
},
|
||||
"7": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "phantom_lancer"
|
||||
},
|
||||
"8": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "necrolyte"
|
||||
},
|
||||
"9": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "zuus"
|
||||
},
|
||||
"10": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "lion"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"weaver",
|
||||
"huskar",
|
||||
"venomancer",
|
||||
"furion",
|
||||
"juggernaut"
|
||||
],
|
||||
"dire": [
|
||||
"lina",
|
||||
"phantom_lancer",
|
||||
"necrolyte",
|
||||
"zuus",
|
||||
"lion"
|
||||
]
|
||||
},
|
||||
"recognized": 9,
|
||||
"revisions": [],
|
||||
"timeline": [
|
||||
{
|
||||
"t": 2.1,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "weaver"
|
||||
},
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "huskar"
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "venomancer"
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "furion"
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "juggernaut"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "lina"
|
||||
},
|
||||
{
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "phantom_lancer"
|
||||
},
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "necrolyte"
|
||||
},
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "lion"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"weaver",
|
||||
"huskar",
|
||||
"venomancer",
|
||||
"furion",
|
||||
"juggernaut"
|
||||
],
|
||||
"dire": [
|
||||
"lina",
|
||||
"phantom_lancer",
|
||||
"necrolyte",
|
||||
"lion"
|
||||
],
|
||||
"count": 9,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_220239.png"
|
||||
}
|
||||
],
|
||||
"best_lineup": {
|
||||
"recognized": 10,
|
||||
"t": 0.1,
|
||||
"state": "STRATEGY_TIME",
|
||||
"heroes": [
|
||||
"weaver",
|
||||
"huskar",
|
||||
"venomancer",
|
||||
"furion",
|
||||
"juggernaut",
|
||||
"lina",
|
||||
"phantom_lancer",
|
||||
"necrolyte",
|
||||
"zuus",
|
||||
"lion"
|
||||
],
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_220249.png"
|
||||
},
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_220249.png"
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
{
|
||||
"match_id": "8913190580",
|
||||
"captured_at": "2026-07-25 22:54:09",
|
||||
"duration_s": 119.6,
|
||||
"polls": 81,
|
||||
"self": {
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "templar_assassin",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"6": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "windrunner"
|
||||
},
|
||||
"7": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "slardar"
|
||||
},
|
||||
"8": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "lina"
|
||||
},
|
||||
"9": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "templar_assassin"
|
||||
},
|
||||
"10": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "void_spirit"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"undying",
|
||||
"rubick",
|
||||
"death_prophet",
|
||||
"legion_commander",
|
||||
"luna"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"slardar",
|
||||
"lina",
|
||||
"templar_assassin",
|
||||
"void_spirit"
|
||||
]
|
||||
},
|
||||
"recognized": 9,
|
||||
"revisions": [],
|
||||
"timeline": [
|
||||
{
|
||||
"t": 12.6,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "lina"
|
||||
}
|
||||
],
|
||||
"radiant": [],
|
||||
"dire": [
|
||||
"lina"
|
||||
],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225222.png"
|
||||
},
|
||||
{
|
||||
"t": 29.8,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "rubick"
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "legion_commander"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "windrunner"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"rubick",
|
||||
"legion_commander"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"lina"
|
||||
],
|
||||
"count": 4,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225239.png"
|
||||
},
|
||||
{
|
||||
"t": 43.4,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "templar_assassin"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"rubick",
|
||||
"legion_commander"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"lina",
|
||||
"templar_assassin"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225252.png"
|
||||
},
|
||||
{
|
||||
"t": 64.6,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "undying"
|
||||
},
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "luna"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"undying",
|
||||
"rubick",
|
||||
"legion_commander",
|
||||
"luna"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"lina",
|
||||
"templar_assassin"
|
||||
],
|
||||
"count": 7,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225314.png"
|
||||
},
|
||||
{
|
||||
"t": 84.4,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "void_spirit"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"undying",
|
||||
"rubick",
|
||||
"legion_commander",
|
||||
"luna"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"lina",
|
||||
"templar_assassin",
|
||||
"void_spirit"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225333.png"
|
||||
},
|
||||
{
|
||||
"t": 90.0,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "death_prophet"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"undying",
|
||||
"rubick",
|
||||
"death_prophet",
|
||||
"legion_commander",
|
||||
"luna"
|
||||
],
|
||||
"dire": [
|
||||
"windrunner",
|
||||
"lina",
|
||||
"templar_assassin",
|
||||
"void_spirit"
|
||||
],
|
||||
"count": 9,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_225339.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"leshrac",
|
||||
"techies",
|
||||
"bane",
|
||||
"antimage",
|
||||
"puck",
|
||||
"queenofpain",
|
||||
"magnataur",
|
||||
"ringmaster",
|
||||
"lion",
|
||||
"snapfire",
|
||||
"juggernaut",
|
||||
"clinkz",
|
||||
"axe",
|
||||
"treant",
|
||||
"pudge",
|
||||
"rattletrap"
|
||||
],
|
||||
"bans_loc": [
|
||||
"拉席克",
|
||||
"工程师",
|
||||
"祸乱之源",
|
||||
"敌法师",
|
||||
"帕克",
|
||||
"痛苦女王",
|
||||
"马格纳斯",
|
||||
"百戏大王",
|
||||
"莱恩",
|
||||
"电炎绝手",
|
||||
"主宰",
|
||||
"克林克兹",
|
||||
"斧王",
|
||||
"树精卫士",
|
||||
"帕吉",
|
||||
"发条技师"
|
||||
],
|
||||
"best_lineup": {
|
||||
"recognized": 9,
|
||||
"t": 101.0,
|
||||
"state": "STRATEGY_TIME",
|
||||
"heroes": [
|
||||
"undying",
|
||||
"rubick",
|
||||
"death_prophet",
|
||||
"legion_commander",
|
||||
"luna",
|
||||
null,
|
||||
"slardar",
|
||||
"lina",
|
||||
"templar_assassin",
|
||||
"void_spirit"
|
||||
],
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_225408.png"
|
||||
},
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_225408.png"
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
{
|
||||
"match_id": "8913254704",
|
||||
"captured_at": "2026-07-25 23:30:32",
|
||||
"duration_s": 124.6,
|
||||
"polls": 84,
|
||||
"self": {
|
||||
"slot": 10,
|
||||
"team": "dire",
|
||||
"hero": "antimage",
|
||||
"role": "safe",
|
||||
"role_label": "优势路",
|
||||
"position": 1,
|
||||
"gsi_name": "refining"
|
||||
},
|
||||
"team_roles": {
|
||||
"6": {
|
||||
"position": 4,
|
||||
"label": "辅助",
|
||||
"hero": "snapfire"
|
||||
},
|
||||
"7": {
|
||||
"position": 2,
|
||||
"label": "中路",
|
||||
"hero": "sniper"
|
||||
},
|
||||
"8": {
|
||||
"position": 5,
|
||||
"label": "纯辅助",
|
||||
"hero": "abyssal_underlord"
|
||||
},
|
||||
"9": {
|
||||
"position": 3,
|
||||
"label": "劣势路",
|
||||
"hero": "undying"
|
||||
},
|
||||
"10": {
|
||||
"position": 1,
|
||||
"label": "优势路",
|
||||
"hero": "antimage"
|
||||
}
|
||||
},
|
||||
"final": {
|
||||
"radiant": [
|
||||
"vengefulspirit",
|
||||
"earthshaker",
|
||||
"ogre_magi",
|
||||
"bristleback",
|
||||
"enchantress"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"sniper",
|
||||
"abyssal_underlord",
|
||||
"undying",
|
||||
"antimage"
|
||||
]
|
||||
},
|
||||
"recognized": 10,
|
||||
"revisions": [],
|
||||
"timeline": [
|
||||
{
|
||||
"t": 22.9,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 8,
|
||||
"team": "dire",
|
||||
"hero": "abyssal_underlord"
|
||||
}
|
||||
],
|
||||
"radiant": [],
|
||||
"dire": [
|
||||
"abyssal_underlord"
|
||||
],
|
||||
"count": 1,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_232850.png"
|
||||
},
|
||||
{
|
||||
"t": 35.3,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 1,
|
||||
"added": [
|
||||
{
|
||||
"slot": 2,
|
||||
"team": "radiant",
|
||||
"hero": "earthshaker"
|
||||
},
|
||||
{
|
||||
"slot": 3,
|
||||
"team": "radiant",
|
||||
"hero": "ogre_magi"
|
||||
},
|
||||
{
|
||||
"slot": 6,
|
||||
"team": "dire",
|
||||
"hero": "snapfire"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"earthshaker",
|
||||
"ogre_magi"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"abyssal_underlord"
|
||||
],
|
||||
"count": 4,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_232902.png"
|
||||
},
|
||||
{
|
||||
"t": 47.5,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 9,
|
||||
"team": "dire",
|
||||
"hero": "undying"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"earthshaker",
|
||||
"ogre_magi"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"abyssal_underlord",
|
||||
"undying"
|
||||
],
|
||||
"count": 5,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_232915.png"
|
||||
},
|
||||
{
|
||||
"t": 61.5,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 1,
|
||||
"team": "radiant",
|
||||
"hero": "vengefulspirit"
|
||||
},
|
||||
{
|
||||
"slot": 4,
|
||||
"team": "radiant",
|
||||
"hero": "bristleback"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"vengefulspirit",
|
||||
"earthshaker",
|
||||
"ogre_magi",
|
||||
"bristleback"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"abyssal_underlord",
|
||||
"undying"
|
||||
],
|
||||
"count": 7,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_232928.png"
|
||||
},
|
||||
{
|
||||
"t": 91.5,
|
||||
"state": "HERO_SELECTION",
|
||||
"round": 2,
|
||||
"added": [
|
||||
{
|
||||
"slot": 7,
|
||||
"team": "dire",
|
||||
"hero": "sniper"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"vengefulspirit",
|
||||
"earthshaker",
|
||||
"ogre_magi",
|
||||
"bristleback"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"sniper",
|
||||
"abyssal_underlord",
|
||||
"undying"
|
||||
],
|
||||
"count": 8,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_232959.png"
|
||||
},
|
||||
{
|
||||
"t": 95.4,
|
||||
"state": "STRATEGY_TIME",
|
||||
"round": 3,
|
||||
"added": [
|
||||
{
|
||||
"slot": 5,
|
||||
"team": "radiant",
|
||||
"hero": "enchantress"
|
||||
}
|
||||
],
|
||||
"radiant": [
|
||||
"vengefulspirit",
|
||||
"earthshaker",
|
||||
"ogre_magi",
|
||||
"bristleback",
|
||||
"enchantress"
|
||||
],
|
||||
"dire": [
|
||||
"snapfire",
|
||||
"sniper",
|
||||
"abyssal_underlord",
|
||||
"undying"
|
||||
],
|
||||
"count": 9,
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_233002.png"
|
||||
}
|
||||
],
|
||||
"bans": [
|
||||
"invoker",
|
||||
"meepo",
|
||||
"sven",
|
||||
"clinkz",
|
||||
"chaos_knight",
|
||||
"juggernaut",
|
||||
"slark",
|
||||
"elder_titan",
|
||||
"ember_spirit",
|
||||
"necrolyte",
|
||||
"kunkka",
|
||||
"lion",
|
||||
"magnataur",
|
||||
"monkey_king",
|
||||
"drow_ranger",
|
||||
"legion_commander"
|
||||
],
|
||||
"bans_loc": [
|
||||
"祈求者",
|
||||
"米波",
|
||||
"斯温",
|
||||
"克林克兹",
|
||||
"混沌骑士",
|
||||
"主宰",
|
||||
"斯拉克",
|
||||
"上古巨神",
|
||||
"灰烬之灵",
|
||||
"瘟疫法师",
|
||||
"昆卡",
|
||||
"莱恩",
|
||||
"马格纳斯",
|
||||
"齐天大圣",
|
||||
"卓尔游侠",
|
||||
"军团指挥官"
|
||||
],
|
||||
"best_lineup": {
|
||||
"recognized": 8,
|
||||
"t": 89.6,
|
||||
"state": "HERO_SELECTION",
|
||||
"heroes": [
|
||||
"vengefulspirit",
|
||||
"earthshaker",
|
||||
"ogre_magi",
|
||||
"bristleback",
|
||||
null,
|
||||
"snapfire",
|
||||
"sniper",
|
||||
"abyssal_underlord",
|
||||
"undying",
|
||||
null
|
||||
],
|
||||
"frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_233032.png"
|
||||
},
|
||||
"last_frame": "C:\\Users\\Administrator\\Documents\\wrok\\dota2-draft-vision\\samples\\raw\\draft_best_233032.png"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"comment": "Ground truth for captured frames: 10 hero keys left to right, '?' for unknown. Used by evaluate.py and build_library.py. Paths are filenames under samples/raw/ (or samples/raw/<matchid>/ when using per-match folders).",
|
||||
"comment": "Ground truth for captured frames: 10 hero keys left to right, '?' for unknown. Used by evaluate.py. Paths are filenames under samples/raw/ (or samples/raw/<matchid>/ when using per-match folders).",
|
||||
"frames": {}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 21 KiB |