当前位置: 首页 > news >正文

用 OpenCV 实现云顶之弈装备识别:从英雄框到装备 ID 的工程化拆解

摘要

上一篇文章中,我们已经完成了云顶之弈截图中的英雄识别:系统可以从截图中定位英雄头像区域,并通过 HSV 颜色直方图粗筛 + 灰度 NCC 精匹配识别出英雄 ID。

但对于一个真正可用的 TFT 阵容分析系统来说,只知道“场上有哪些英雄”还不够。装备同样决定阵容强度,例如主 C 是否有三件套、前排是否有坦装、高费卡是否空装等。

本文继续基于 tft_screen_capture.py 和整个项目代码,重点拆解装备识别是如何实现的。相比英雄识别,装备识别更像是英雄识别链路的下游扩展:

识别英雄是谁
-> 定位英雄所在区域
-> 在英雄框下方截取装备候选区
-> 对装备图标做模板匹配
-> 输出到英雄 items 字段
-> 进入阵容摘要和 AI Agent 分析

一、装备识别在项目中的位置

项目中装备识别的核心文件仍然是:

tft_screen_capture.py
但它并不是单文件孤立工作的,而是和这些模块形成闭环:

tft_data_manager.py
-> 生成 tft_item_db.json 装备数据库

tft_fetch_assets.py
-> 下载装备图标模板到 tft_assets/items/

tft_screen_capture.py
-> 从截图中识别英雄和装备

tft_converter.py
-> 根据 items 字段生成装备摘要和装备问题

tft_rag_agent.py
-> 将装备信息交给 PowerAgent 做阵容强度分析

也就是说,装备识别不是简单地“看图猜图标”,而是一个数据驱动的工程流程。

二、装备模板从哪里来

装备识别依赖本地装备图标模板,目录是:

tft_assets/items/
模板文件名类似:

TFT_Item_Deathblade.png
TFT_Item_BlueBuff.png
TFT5_Item_DeathbladeRadiant.png
TFT16_Item_Bilgewater_JollyRoger.png

这些文件名非常重要,因为最终识别出的装备 ID 就来自文件名 stem。

例如模板命中:

TFT_Item_Deathblade.png
最终返回的装备 ID 就是:

TFT_Item_Deathblade

装备模板由 tft_fetch_assets.py 下载。它会先读取 tft_item_db.json,然后过滤掉教程、调试、内部机制、旧赛季无关物品等不适合作为识别目标的 item。

核心过滤逻辑是:

def _item_is_valid(api_name: str) -> bool: if _SKIP_RE.search(api_name): return False if api_name.startswith("TFT4_") and "Ornn" not in api_name: return False if api_name.startswith("TFT9_") and "Ornn" not in api_name: return False return api_name.startswith(( "TFT_Item_", "TFT5_Item_", "TFT16_Item_", "TFT16_TheDarkin", "TFT4_Item_Ornn", "TFT9_Item_Ornn", ))

这个设计很关键。装备数据库原始数量很多,其中有大量强化符文、机制物品、隐藏物品。如果全部拿来做模板匹配,误报率会明显上升。

三、装备模板加载:复用英雄识别的模板机制

在 tft_screen_capture.py 中,英雄模板和装备模板都通过 _load_templates() 懒加载。

装备模板缓存变量是:

_item_templates_gray: Dict[str, np.ndarray] = {}

加载装备模板的逻辑如下:

if ITEM_DIR.exists(): for p in sorted(ITEM_DIR.glob("*.png")): stem = p.stem color = _load_with_alpha(str(p)) if color is None or color.size == 0: continue g36 = cv2.cvtColor( cv2.resize(color, (TEMPLATE_SIZE, TEMPLATE_SIZE)), cv2.COLOR_BGR2GRAY ) _item_templates_gray[stem] = g36

这里有几个技术点:

第一,装备模板也会处理 PNG 透明通道。
项目统一使用 _load_with_alpha(),把透明区域合成到中性灰背景上,避免透明区域被 OpenCV 读成纯黑,影响匹配分数。

第二,装备模板会统一缩放到 TEMPLATE_SIZE x TEMPLATE_SIZE。
当前 TEMPLATE_SIZE = 64,所以装备模板在识别前会统一成 64x64 灰度图。

第三,装备识别只使用灰度模板。
英雄识别使用了 HSV 直方图 + NCC 融合,而装备识别目前更轻量,直接使用灰度模板匹配。

四、装备识别入口:identify_items()

真正负责装备识别的函数是:

def identify_items( img: np.ndarray, box: Tuple[int, int, int, int], threshold: float = ITEM_THRESHOLD ) -> List[str]:

它接收两个核心输入:

img:完整截图
box:某个英雄框的位置,格式为 (x, y, bw, bh)
这也体现了它和英雄识别的关系:装备识别并不自己从全图找目标,而是依赖前一步已经定位好的英雄框。

五、第一步:根据英雄框推断装备区域

云顶之弈中,英雄身上的装备图标通常显示在英雄头像下方。因此代码直接根据英雄框计算装备候选区域:

x, y, bw, bh = box h, w = img.shape[:2] iy1 = y + bh iy2 = min(h, y + bh + int(bh * 0.55)) ix1 = max(0, x - int(bw * 0.05)) ix2 = min(w, x + bw + int(bw * 0.05))

可以理解为:

纵向:从英雄框底部开始,向下取 0.55 个英雄框高度
横向:比英雄框稍微左右扩展 5%

这一步的意义是把搜索范围从整张截图缩小到一个很小的 ROI 区域。

如果没有这一步,而是在全图上匹配所有装备模板,计算量和误报都会非常高。

六、第二步:过滤过小区域,避免误识别

装备图标本身很小,如果截图分辨率低,或者英雄框下方区域太窄,模板匹配会变得非常不稳定。

代码中做了两层保护:

region = img[iy1:iy2, ix1:ix2] rh, rw = region.shape[:2] if rh < 20 or rw < 20: return []

以及:

item_size = min(rh, TEMPLATE_SIZE) if item_size < TEMPLATE_SIZE * 0.4: return []

这两段逻辑解决了一个很实际的问题:
当装备区域太小时,如果强行把模板缩到很小,比如 8x8 或 10x10,很多装备的细节都会消失,模板之间会变得非常相似,容易出现“三个水银”“三个相同装备”这类误报。

所以当前实现选择了一个保守策略:

区域太小就不识别

这比强行识别出错误装备更可靠。

七、第三步:灰度模板匹配

拿到装备候选区域后,代码会先转灰度:

gray_region = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY)

然后遍历所有装备模板:

for stem, tmpl in _item_templates_gray.items(): tmpl_scaled = cv2.resize(tmpl, (item_size, item_size)) result = cv2.matchTemplate( gray_region, tmpl_scaled, cv2.TM_CCOEFF_NORMED ) _, val, _, loc = cv2.minMaxLoc(result) if val < threshold: continue

这里使用的是 OpenCV 的:

cv2.TM_CCOEFF_NORMED

它可以理解为归一化相关系数匹配。返回值越接近 1,说明截图区域和模板越相似。

默认装备匹配阈值是:

ITEM_THRESHOLD = 0.45

如果某个装备模板在候选区域中的最高匹配分数低于阈值,就认为没有命中。

八、第四步:按位置去重,避免同一装备被多次命中

装备识别有一个典型问题:同一个图标位置,可能有多个相似模板都超过阈值。

例如某些基础装备和成装在灰度结构上比较接近,如果只看分数,可能同一个位置返回多个装备。

项目中使用了一个简单但有效的去重策略:按照 x 坐标分桶,每个位置只保留分数最高的装备。

position_best: Dict[int, Tuple[float, str]] = {} bucket_size = item_size lx = loc[0] bucket = lx // bucket_size if bucket not in position_best or val > position_best[bucket][0]: position_best[bucket] = (val, stem)

最后按横向位置排序,并最多返回 3 件装备:

items = [stem for _, (_, stem) in sorted(position_best.items())] return items[:3]

这非常符合云顶之弈的规则:一个英雄最多携带三件装备。

九、装备识别如何接入英雄结果

在普通棋盘识别函数 recognize_from_array() 中,装备识别是在英雄识别之后执行的:

stem, score = identify_champion(inner, champ_thr) star_region = img[max(0, y - int(bh * 0.35)):y + bh, x:x+bw] star = detect_star(star_region) items = identify_items(img, box, item_thr)

最终每个英雄会被组织成结构化字典:

champions.append({ "id": stem, "short_id": stem.replace("TFT16_", "").replace("TFT_", "") if stem else "", "name_en": stem.replace("TFT16_", "").replace("TFT_", "") if stem else f"unknown_{x}_{y}", "star": star, "cost": 0, "items": items, "position": {"row": row, "col": col}, "_score": round(score, 3), "_box": [x, y, bw, bh], })

这里的关键字段是:

"items": items

也就是说,装备识别不是单独输出一堆装备,而是挂载到具体英雄身上。

最终结果类似:

{ "id": "TFT16_Draven", "short_id": "Draven", "star": 2, "items": [ "TFT_Item_Deathblade", "TFT_Item_InfinityEdge" ], "position": { "row": 3, "col": 4 } }

这一步让后续阵容分析可以知道:哪个英雄带了什么装备

而不是只知道场上有哪些装备。

十、不同截图模式下的装备识别支持

项目当前支持多种截图模式:

board 标准棋盘模式
lineup 横向结算阵容图
global 全局八人阵容图
duel 双方对战回顾图
装备识别主要接入在三个模式中:

recognize_from_array()
recognize_lineup()
recognize_duel()
其中 duel 模式本质上会先把上下两个棋盘切开,然后分别调用通用识别逻辑:

board_result = recognize_from_array(board_img, champ_thr, item_thr, debug=False)

所以装备识别能力可以自然复用到对战回顾场景。

十一、装备识别结果如何进入阵容分析

识别出的 items 字段会继续传入 tft_converter.py 的 build_summary()。

其中装备相关逻辑包括:

for c in champions: if c.get("items") and cost >= max_cost: max_cost = cost main_carry = c.get("name_en") or c.get("id", "")

以及装备问题检查:

for c in champions: if c.get("cost", 0) >= 4 and not c.get("items"): issues.append(f"{name}(费用≥4) 无装备")

还会检查重复装备:

item_counter = Counter(all_items) for item, count in item_counter.items(): if count > 1 and item not in STACKABLE: issues.append(f"{item} 重复装备 x{count}")

在 tft_rag_agent.py 中,PowerAgent 也会读取装备信息:

items = c.get("items", []) all_items.extend(items) if not items and c.get("cost", 1) >= 4: no_item_carries.append(c.get("name_en") or c.get("name", ""))

这说明装备识别已经进入了 AI 分析链路:

截图识别
-> 装备 ID
-> 阵容摘要
-> 装备问题
-> Agent 给出战力建议

十二、相比英雄识别,装备识别有哪些不同

英雄识别和装备识别都使用模板匹配,但侧重点不同。

英雄识别更复杂,因为英雄头像存在颜色、姿态、边框、亮度、截图缩放等问题,所以项目采用:

HSV 颜色直方图粗筛
+ 灰度 NCC 精匹配
+ 分数融合
装备识别当前更轻量:

英雄框定位
-> 装备 ROI 裁剪
-> 灰度 matchTemplate
-> 阈值过滤
-> x 坐标分桶去重
这背后的原因是:

英雄识别要先判断“这是谁”
装备识别是在已知英雄位置后,判断“下方有哪些小图标”

装备识别依赖英雄识别的定位结果,因此它是上一篇英雄识别工作的自然延伸。

十三、当前装备识别技术进度

从项目当前代码看,装备识别已经完成了以下能力:

已完成装备数据库解析
已完成装备模板自动下载
已完成无效装备过滤
已完成透明 PNG 灰底合成
已完成装备模板灰度缓存
已完成基于英雄框的装备区域裁剪
已完成装备区域过小保护
已完成 OpenCV 灰度模板匹配
已完成按横向位置去重
已完成单英雄最多三件装备限制
已接入 board / lineup / duel 模式
已写入英雄 items 字段
已接入 build_summary 和 PowerAgent 分析链路
整体上,装备识别已经从“图像识别功能”推进到了“阵容分析数据源”的阶段。

十四、总结

本文在上一篇英雄识别的基础上,继续拆解了项目中的装备识别实现。

如果说英雄识别解决的是:截图中有哪些英雄?

那么装备识别进一步解决的是:这些英雄分别带了什么装备?
当前项目采用的是一套非常实用的 OpenCV 工程方案:

装备模板准备
-> 英雄框定位
-> 装备区域裁剪
-> 灰度模板匹配
-> 阈值过滤
-> 位置去重
-> 输出 items 字段
-> 进入阵容分析

它最大的特点是和英雄识别链路衔接紧密:不重新发明定位逻辑,而是复用英雄框,把装备识别变成英雄识别后的局部小目标匹配问题。

从技术进度看,这个模块已经不只是一个视觉 demo,而是接入了后续 summary、equipment_issues 和 PowerAgent 的真实分析流程。

http://www.jsqmd.com/news/723760/

相关文章:

  • 刚刚设置好我的jetson orin nano 4G上的yolo26 onnx
  • Java虚拟机精讲【2.0】
  • STM32F103C8T6驱动GY-30光照传感器:从I2C时序到数据校准的完整避坑指南
  • 从SATA到PCIe 4.0:一文看懂SSD速度进化史,你的老硬盘到底慢在哪?
  • 墨石教育全链路管理类联考辅导体系
  • 白城黄金回收怎么避免被骗,推荐能当天变现的靠谱品牌有哪些 - 工业设备
  • 前端性能优化:CSS 性能优化详解
  • 混合信号验证:SystemVerilog与Verilog-AMS协同架构实践
  • 大模型---FAISS/Chroma
  • “线上搓虾子 线下嘬虾子”燃动江城
  • 坤和静界·春藤计划:用“家庭系统干预“破解青少年休学难题的实践与思考
  • 认知虫洞穿越:软件测试中的时空探索与风险管控
  • 从浪潮服务器到VMware虚拟机:一份通用的Ubuntu 20.04 Netplan静态IP配置避坑手册
  • 说说全国口碑好的网球场地租赁品牌,梅江南网球俱乐部排第几? - 工业设备
  • 【仅限头部技术团队解密】:PHP订单分布式链路追踪黑盒——基于OpenTelemetry自研TraceID穿透方案,将平均排查耗时从43分钟压缩至86秒
  • Linux下cmake构建方法
  • 32位微控制器技术解析与应用选型指南
  • GitHub中文插件:3分钟破解代码协作的语言壁垒,让全球开发者平台说中文
  • 2025届毕业生推荐的六大降AI率神器横评
  • 2026年网红开会语音转文字app多维度实测对比,全面PK后,差距竟然这么大
  • VCS用户必看:Python脚本处理寄存器Excel的5个常见坑与避坑指南
  • 别再让多线程搞乱你的计数器!手把手教你用Linux内核atomic_t实现线程安全(附完整代码)
  • 探讨服务不错的网球俱乐部品牌,梅江南网球俱乐部口碑如何? - 工业设备
  • 算法训练营第十七天 | 151.反转字符串中的单词
  • 伊辛机副本耦合拓扑结构优化与误差缓解方法研究
  • 微信小程序自定义TabBar踩坑实录:TDesign组件与getTabBar接口的配合使用指南
  • 索引失效案例分析:5个让SQL不走索引的坑
  • C++信号处理
  • SeqTrack模型专题全面调研
  • 【附Python源码】基于MLP的波士顿房价预测