简介:本资源为LOL英雄联盟角色检测数据集,面向计算机视觉方向的目标检测学习者与算法工程师,可用于训练和验证游戏场景下的多目标识别模型。数据集共3000张jpg图片,每张均配有对应的VOC格式xml标注与YOLO格式txt标注,标注工具为labelImg,采用矩形框方式完成。标注类别共6类,包括队友小兵、己方防御塔、敌方小兵、敌方防御塔以及LUX、VAYNE两个英雄角色,总标注框数达24665个,其中敌方小兵框数最多,为10973个,VAYNE为3669个,LUX为1531个,类别分布较为丰富。压缩包为zip格式,内含1999个xml文件与1个说明txt文件,整体约135.85MB,目录结构清晰,便于直接接入YOLO或VOC训练流程。目前已有464人学习下载,适合需要游戏目标检测数据、开展模型训练与对比实验的读者参考使用。
1. 从一张截图说起:为什么3000张6类的角色检测数据集值得你花时间
做游戏画面分析的同学大概率遇到过这个场景:想给直播画面加一个自动导播,或者给录播做一波高光切片,第一步就卡在“怎么把画面里的角色、小兵、防御塔框出来”。通用检测模型在自然图像上表现不错,一进游戏画面就集体翻车——UI 元素干扰、角色贴图风格统一、小兵扎堆重叠、防御塔半透明血条遮挡,这些都不是 COCO 预训练权重能直接搞定的。这时候一个标注好的、类别定义贴合 MOBA 场景的检测数据集,价值就出来了。
标题里这份数据集的核心信息很明确:3000 张图像、6 个类别,覆盖队友、己方小兵、敌方小兵、防御塔、韦恩这类游戏内目标。它解决的不是“通用检测”问题,而是“游戏画面里谁是谁、属于哪一方”的细粒度识别问题。适合两类人:一是想快速跑通游戏目标检测 pipeline 的工程师,二是需要在自己的游戏分析项目里做迁移学习的开发者。下面我按“数据长什么样 → 怎么转格式 → 怎么训 → 坑在哪 → 怎么调优”的顺序,把这条链路拆开讲。
2. 先看清数据再动手:6类标注的语义边界与分布陷阱
2.1 六个类别的定义与容易混淆的地方
拿到一份游戏检测数据集,第一件事不是急着写 dataloader,而是把类别定义和标注规则吃透。这份数据集的 6 个类别里,队友和敌方小兵、己方小兵和敌方小兵之间的区分,靠的是颜色、血条颜色和模型贴图差异,而不是形状。韦恩作为一个具体英雄类别单独列出,说明标注时对英雄个体做了区分,而不是笼统归为“英雄”。
| 类别 | 判定依据 | 常见混淆对象 |
|---|---|---|
| 队友 | 友方血条颜色 + 模型轮廓 | 与敌方英雄颜色接近时易混 |
| 己方小兵 | 蓝色/绿色血条 + 小兵模型 | 与敌方小兵模型高度相似 |
| 敌方小兵 | 红色血条 + 小兵模型 | 同上 |
| 防御塔 | 固定建筑结构 + 底座 | 与水晶、基地建筑易混 |
| 韦恩 | 特定英雄模型 + 技能特效 | 与其他射手英雄轮廓接近 |
这里有个血泪经验:小兵类别的区分极度依赖血条颜色,如果图像分辨率不够或者血条被特效遮挡,标注一致性会明显下降。你在训练前最好抽样 100 张,用脚本统计每个类别的框数量和平均框面积,看看有没有某个类别样本过少或者框面积异常。
2.2 用脚本做一次数据体检
在训练之前,我一般会写一个轻量脚本,把标注文件读进来,统计类别分布、框的宽高比、以及每张图的框数量。这一步能提前暴露“某类只有几十个框”或者“框面积全是小目标”的问题。
import os import json from collections import Counter # 假设标注是 COCO 格式的 json,如果是 YOLO txt 需要另写解析 ann_path = "annotations/instances.json" with open(ann_path, "r", encoding="utf-8") as f: data = json.load(f) # 统计每个类别的框数量 cat_counter = Counter() for ann in data["annotations"]: cat_counter[ann["category_id"]] += 1 # 映射类别 id 到名称 id2name = {c["id"]: c["name"] for c in data["categories"]} for cid, cnt in cat_counter.most_common(): print(f"{id2name[cid]}: {cnt} boxes") # 统计每张图的框数量分布 img_box_count = Counter() for ann in data["annotations"]: img_box_count[ann["image_id"]] += 1 avg_boxes = sum(img_box_count.values()) / len(img_box_count) print(f"平均每张图框数: {avg_boxes:.2f}") print(f"框数最多的图: {img_box_count.most_common(3)}")这段脚本的逻辑很直接:先按 category_id 聚合,再按 image_id 聚合。参数上唯一需要注意的是标注格式,COCO 的 category_id 通常从 1 开始,YOLO 的 class_id 从 0 开始,转换时别搞错。如果发现某个类别框数低于总数的 5%,训练时就要考虑加权重采样或者用 focal loss 来缓解类别不平衡。
2.3 3000 张的规模意味着什么
3000 张图像、6 个类别,平均下来每类 500 张左右的量级。这个规模对于从零训练一个检测网络来说偏小,但对于微调一个预训练模型是够用的。我的建议是:不要从零训,直接用 COCO 预训练的 YOLO 或者 Faster R-CNN 做 backbone 初始化,只替换检测头。3000 张图在单卡上跑 100 到 150 个 epoch,配合数据增强,通常能收敛到一个可用的水平。
3. 把标注转成 YOLO 格式:转换脚本与四个边界坑
3.1 为什么优先选 YOLO 格式
游戏画面检测对推理速度有要求,尤其是你想做实时导播或者直播叠加的时候。YOLO 系列在速度和精度平衡上比较成熟,而且格式简单,一个 txt 对应一张图,每行class_id x_center y_center width height,全部归一化到 0 到 1。相比 COCO 的 json,YOLO 格式在训练时读取更快,也不容易因为 json 解析出问题导致整个训练卡住。
3.2 转换脚本:从 COCO 到 YOLO
假设你拿到的标注是 COCO 格式,下面这个脚本可以直接用。核心是把绝对坐标转成归一化中心点坐标,同时把 category_id 映射成从 0 开始的连续整数。
import json import os from PIL import Image def coco_to_yolo(coco_json, img_dir, out_dir): with open(coco_json, "r", encoding="utf-8") as f: data = json.load(f) # 建立 image_id 到文件名的映射 img_info = {img["id"]: img for img in data["images"]} # 建立 category_id 到连续 id 的映射 cat_ids = sorted([c["id"] for c in data["categories"]]) cat_id_map = {cid: idx for idx, cid in enumerate(cat_ids)} os.makedirs(out_dir, exist_ok=True) # 按 image_id 聚合标注 from collections import defaultdict ann_by_img = defaultdict(list) for ann in data["annotations"]: ann_by_img[ann["image_id"]].append(ann) for img_id, anns in ann_by_img.items(): info = img_info[img_id] w, h = info["width"], info["height"] lines = [] for ann in anns: x, y, bw, bh = ann["bbox"] # COCO: 左上角 x,y,宽,高 # 转成中心点归一化坐标 x_center = (x + bw / 2) / w y_center = (y + bh / 2) / h nw = bw / w nh = bh / h cls = cat_id_map[ann["category_id"]] lines.append(f"{cls} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}") # 写 txt,文件名与图片同名 base = os.path.splitext(info["file_name"])[0] with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(lines)) coco_to_yolo("annotations/instances.json", "images", "labels")逻辑说明:COCO 的 bbox 是[x_min, y_min, width, height],YOLO 需要的是中心点加宽高,且全部除以图像宽高做归一化。参数上要注意cat_id_map的构建,必须保证类别 id 连续,否则 YOLO 训练时会报类别索引越界。另外,如果某张图没有标注,脚本不会生成对应的 txt,训练时要么跳过这张图,要么生成空文件,取决于你的 dataloader 实现。
3.3 四个容易翻车的边界情况
第一个坑是图像尺寸和标注尺寸不一致。有些数据集里的 width/height 是原始截图尺寸,但你实际拿到的图片可能被压缩过。转换前一定要用 PIL 读一下实际图片尺寸,和 json 里的对不上就先缩放标注。
第二个坑是 bbox 越界。游戏画面里角色贴边时,标注框可能超出图像边界,转成 YOLO 后中心点可能小于 0 或大于 1。训练前加一行 clamp 把坐标限制在 0 到 1 之间,否则某些框架会直接报错。
第三个坑是类别 id 不连续。COCO 的 category_id 可能是 1、3、7 这种跳号,直接拿来当 YOLO 的 class_id 会出问题。必须重新映射成 0、1、2 这样的连续整数。
第四个坑是文件名冲突。不同文件夹下的图片可能重名,转换后的 txt 会互相覆盖。建议在输出文件名里保留原始子目录结构,或者加前缀。
4. 训练配置怎么定:从 anchor 到学习率的实操参数
4.1 选 YOLOv5 还是 YOLOv8
如果追求快速跑通,YOLOv5 的生态最成熟,配置文件清晰,社区里游戏检测的案例也多。YOLOv8 的精度略高,但配置文件结构变化较大,新手容易在数据 yaml 上卡住。我的建议是先用 YOLOv5s 跑一版 baseline,确认数据 pipeline 没问题,再考虑换更大的模型或者 YOLOv8。
4.2 数据 yaml 的写法
YOLO 训练需要一个 data.yaml,指定训练集、验证集路径和类别名称。路径建议用绝对路径,避免相对路径在不同工作目录下解析出错。
# data.yaml train: /data/lol_detection/images/train val: /data/lol_detection/images/val nc: 6 names: ['teammate', 'ally_minion', 'enemy_minion', 'tower', 'vayne', 'other']这里nc必须和实际类别数一致,names的顺序要和转换脚本里的cat_id_map完全对应。如果顺序错了,模型会把队友识别成敌方小兵,这种错误在验证集上 loss 正常但实际效果完全不对,属于典型的“黑匣子”问题。
4.3 关键训练参数与调整逻辑
python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data data.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyp.scratch.yaml \ --name lol_exp1--img 640是输入分辨率,游戏画面里小兵和英雄的尺寸差异大,640 在速度和精度之间比较平衡。如果显存够,可以上 1280,对小目标更友好。--batch 16在单张 12G 显存的卡上比较稳,显存不够就降到 8。--epochs 150是针对 3000 张规模的保守值,如果验证集 mAP 在 100 epoch 后还在涨,可以加到 200。
学习率方面,YOLOv5 默认的hyp.scratch.yaml里lr0是 0.01,对微调来说偏大。我一般会改成 0.001,配合 cosine 衰减。如果发现训练前期 loss 震荡厉害,再降到 0.0005。
4.4 anchor 需不需要重新聚类
游戏画面的目标宽高比和自然图像差异很大,防御塔是竖长条,小兵接近方形,英雄介于两者之间。用 COCO 的默认 anchor 不是不能跑,但召回率会受影响。建议用 YOLOv5 自带的kmeans.py对自己的标注做一次 anchor 聚类,把结果替换到模型配置里。这一步大概能带来 2 到 3 个点的 mAP 提升,属于性价比很高的操作。
5. 避坑与排查:训练不收敛、类别混淆、小目标漏检
5.1 训练 loss 不降反升
现象:前 10 个 epoch loss 正常下降,之后突然飙升或者震荡。原因通常是学习率太大,或者数据里有标注错误的样本。解决:先把学习率降一个数量级,如果还不行,用脚本抽查一批标注可视化,看看有没有框错类别或者框到 UI 上的情况。游戏画面里技能特效和血条很容易被误标,这类脏数据对训练伤害很大。
5.2 队友和敌方小兵互相误检
现象:验证集上这两个类别的混淆矩阵明显偏高。原因:模型主要靠颜色区分,但图像增强里的色彩抖动(hsv_h、hsv_s)把颜色信息破坏了。解决:把hsv_h降到 0.01 以下,hsv_s和hsv_v也相应调小,让模型更多依赖形状和纹理。如果还是不行,考虑在数据里补充一些颜色接近的困难样本。
5.3 小兵漏检严重
现象:mAP 整体还行,但小兵类别的召回率明显低。原因:小兵在画面里尺寸小,640 分辨率下可能只有十几个像素。解决:把输入分辨率提到 960 或 1280,同时在数据增强里减少 mosaic 的概率,因为 mosaic 会把小目标进一步缩小。另外可以调整 anchor,专门为小目标增加一组更小的 anchor。
5.4 防御塔被识别成背景
现象:防御塔的 AP 很低,模型几乎不输出这类框。原因:防御塔在画面里位置固定,且经常被 UI 遮挡,样本多样性不足。解决:检查标注里防御塔的框是不是只出现在固定区域,如果是,需要在数据增强里加随机裁剪和平移,让模型见到不同位置的防御塔。另外可以单独统计防御塔的框数量,如果太少,考虑过采样包含防御塔的图像。
5.5 验证集 mAP 高但实际推理效果差
现象:验证集 mAP 0.7 以上,但拿真实直播截图去测,漏检和误检都很严重。原因:验证集和训练集来自同一批录像,分布太接近,模型过拟合了。解决:重新划分验证集,确保验证集的录像片段和训练集不重叠。如果数据里不同地图、不同皮肤的画面差异大,验证集要覆盖这些变化。
6. 进阶技巧:用半自动标注和困难样本挖掘把 mAP 再推一截
6.1 用训练好的模型做半自动标注
3000 张标注完不是终点。实际项目里,你很快会遇到新地图、新皮肤、新英雄,重新标 3000 张成本太高。我的做法是:用当前模型对未标注的新画面做推理,把置信度高于 0.6 的框自动导出成标注,人工只修正漏检和误检的部分。这样一轮下来,新增 500 张标注的时间能从两天压缩到半天。
import torch from PIL import Image model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt') model.conf = 0.6 # 只保留高置信度框 img = Image.open('new_frame.jpg') results = model(img) # 导出为 YOLO 格式 results.save('auto_labels/')这里conf设 0.6 是个经验值,太低会引入大量误检,太高会漏掉困难样本。导出后一定要人工过一遍,尤其是小兵和队友这种容易混的类别。
6.2 困难样本挖掘的具体操作
训练完一版模型后,把验证集里 loss 最高的前 100 张图挑出来,看看模型错在哪。常见的高 loss 样本包括:团战画面(目标密集重叠)、技能特效遮挡(目标不完整)、极端光照(地图阴影区域)。把这些图单独复制一份,在下一轮训练里重复采样 2 到 3 次,能明显提升模型在这些场景下的鲁棒性。
6.3 一个验证模型是否值得上线的检查清单
在把模型推到实际业务之前,我一般会跑一遍这个清单:第一,用最近一周的真实直播截图做测试,mAP 不低于验证集的 80%;第二,单帧推理时间在目标硬件上满足实时要求,比如 30 FPS 对应 33ms 以内;第三,类别混淆矩阵里,队友和敌方小兵的误检率低于 5%;第四,防御塔和韦恩的召回率不低于 0.7。这四条都过了,才考虑上线。
6.4 我踩过的最大的一个坑
早期做游戏检测的时候,我图省事,直接把训练集和验证集按 8:2 随机划分。结果验证集 mAP 冲到 0.85,上线后实际效果惨不忍睹。后来才发现,同一局游戏的相邻帧被分到了训练集和验证集,模型相当于在“背答案”。改成按整局游戏划分之后,验证集 mAP 掉到 0.68,但这才是真实水平。这个教训让我养成了一个习惯:任何时序相关的数据,划分验证集时一定要按片段切,不能按帧随机切。希望帮到你。
本文还有配套的精品资源,点击获取