LOL英雄联盟角色检测:3000张图训练YOLOv8模型实战解析
发布时间:2026/9/28 16:56:25来源:尧图网络
简介面向LOL英雄联盟角色检测的目标检测数据集提供6类游戏单位——AllyMinions友方小兵、AllyTower友方防御塔、EnemyMinions敌方小兵、EnemyTower敌方防御塔、LUX、VAYNE——的矩形框标注总计24665个标注框适合目标检测方向学习者练习模型训练也可用于游戏AI识别、对战数据分析等场景。数据集按Pascal VOC与YOLO两种主流格式设计便于接入常见检测框架压缩包共2000个文件以1999个XML标注文件为主并附1个说明文档包体约135.85MBXML报文包含类别与边界框坐标可借助labelImg查看或二次标注。从内容预览可见XML文件按编号命名与原始图像一一对应从标注分布看敌方小兵框数最多10973框可让学习者练习处理类别不平衡问题。当前已有464人浏览学习数据集经人工准确标注虽不保证模型精度但可为算法评估和教学提供可靠的数据基础。1. LOL英雄联盟角色检测3000张图能训出什么水平的目标检测模型做过目标检测项目的人都明白真正让模型效果拉开差距的往往不是网络结构而是数据集的质量和贴合度。这份 LOL 英雄联盟角色检测数据集一次性提供了 3000 张游戏截图6 个类别24665 个目标框VOC 和 YOLO 两种格式都打包在同一个 zip 里解压后不需要再转换格式就能开始训练。类别覆盖了己方小兵、敌方小兵、防御塔以及 LUX 和 VAYNE 两个英雄正好对应游戏对局里最常见的可视目标。对做游戏 AI、电竞数据分析和视频理解的从业者它省掉了从零截屏、清洗和标框的完整流程对刚接触目标检测的新手它又是一个带有明显类别不平衡的真实场景数据集用来熟悉 YOLOv8 训练、调参和排错流程是再合适不过的样本。2. 数据集格式拆解VOC 与 YOLO 双格式、6 类目标与 24665 个框的组织方式2.1 压缩包里的三件套jpg、xml、txt 如何对应打开压缩包文件列表里是一长串以 xyxr_images_ 命名的文件。每一个样本在数据集里其实对应三个同名文件比如 xyxr_images_2703 这一组文件作用xyxr_images_2703.jpg游戏截图原图xyxr_images_2703.xml以 Pascal VOC 格式保存的标注包含类别名和像素坐标xyxr_images_2703.txt以 YOLO 格式保存的标注包含类别索引和归一化坐标这种三个文件同名共生的结构最大的坑在于解压、移动或改名后任何一个文件缺失训练时就会报标签不匹配。我拿到数据集的第一步不是急着写训练命令而是先跑一个完整性检查脚本for jpg in *.jpg; do base${jpg%.jpg} if [ ! -f $base.txt ]; then echo missing ${base}.txt fi if [ ! -f $base.xml ]; then echo missing ${base}.xml fi done echo check done这个 bash 循环遍历当前目录下所有 jpg 文件用同名规则判断 txt 和 xml 是否存在缺失的文件会被打印出来。数据集声称 jpg、xml、txt 各 3000 个理论上不会少但网盘转存导致文件级损坏或改名是常有的事这几十秒的检查能避免后续排查半天。另外留意摘要里那句话“不包含分割路径的txt文件”。意思是 txt 里只有类别和坐标数值没有像 images/train/xxx.jpg 这样的路径前缀路径信息需要你自己组织。这不算缺点反而让目录结构完全由你掌控符合 YOLO 系模型的读取习惯。2.2 VOC 格式 XML像素坐标与 size 节点xml 是 labelImg 直接保存的结果树状结构最外层是 annotation 节点里面包含 filename、size、object 列表。size 节点里是 width、height、depthobject 节点里是被检目标的 name 和 bndbox 坐标。解析时最容易翻车的两个点一是 bndbox 的坐标可能是浮点型字符串比如 480.5直接用 int() 转会报错二是 size 节点在部分旧版工具里可能缺失。稳妥的做法是用 float() 先转再操作import xml.etree.ElementTree as ET tree ET.parse(xyxr_images_2703.xml) root tree.getroot() w root.findtext(size/width) h root.findtext(size/height) if w is None or h is None: raise ValueError(size node missing in xml) for obj in root.findall(object): name obj.findtext(name) box obj.find(bndbox) xmin int(float(box.findtext(xmin))) ymin int(float(box.findtext(ymin))) xmax int(float(box.findtext(xmax))) ymax int(float(box.findtext(ymax))) print(f{name} {xmin} {ymin} {xmax} {ymax})这段代码把 xml 里的每个目标解析成一行可读文本。findtext 比 find().text 更安全目标节点不存在时返回 None 而不是抛异常。之所以先 float 再 int是为了兼容 labelImg 可能写出的浮点坐标。2.3 YOLO 格式 TXT索引映射与归一化坐标YOLO 格式的 txt 每行是五个字段类别索引、归一化中心 x、归一化中心 y、归一化宽、归一化高。比如某一行2 0.531250 0.468750 0.062500 0.093750索引 2 对应的类别要按摘要给出的顺序查AllyMinions 是 0AllyTower 是 1EnemyMinions 是 2EnemyTower 是 3LUX 是 4VAYNE 是 5。这一行的实际含义是一个 EnemyMinions 目标中心点在图像横向 53.1% 的位置纵向 46.9% 的位置宽约等于整图宽的 6.25%高约等于整图高的 9.375%。这里要特别强调txt 里的索引和训练时 yaml 文件的 names 顺序必须严格一致。如果顺序错位模型训练时每个类名的学习目标就全错了最棘手的是 val mAP 数字可能依然不低直到推理阶段才发现检出框的类名全部张冠李戴。这种错误返工成本极高所以类目索引表一定要固定住最好写进一个配置文件里所有脚本都从同一份映射读取。2.4 框数分布统计不平衡程度直接决定训练策略摘要里的框数可以整理成下面这张表类别框数占比EnemyMinions1097344.5%AllyMinions733929.8%VAYNE366914.9%LUX15316.2%EnemyTower6022.4%AllyTower5512.2%两个小兵类合计占比超过七成而两个防御塔类合计只有 4.6% 左右。这种极端不平衡下直接训练的结果一定是多数类精度虚高、少数类惨不忍睹。我处理这类数据时习惯分两步先统计每类的目标面积分布判断少数类是小目标问题还是单纯样本量不足再决定降采样多数类还是对少数类做增强。在这个数据集里Tower 类两样都占既要扩样本又要放大输入分辨率后面会给出具体方案。3. 把数据集跑进 YOLOv8 训练目录重组、yaml 配置与参数调整3.1 目录重组从扁平结构到 train/val 四层目录YOLOv8 的 DataLoader 会读取 images/train 下的图片然后自动到 labels/train 目录找同名 txt。所以解压出来的扁平结构必须重组。先建目录mkdir -p lol-dataset/images/train lol-dataset/images/val mkdir -p lol-dataset/labels/train lol-dataset/labels/valmkdir -p 在目录已存在时会静默跳过不会因重复执行报错。images 和 labels 两个分支各建 train/val 子目录之后移动文件import os import random import shutil random.seed(42) jpeg_files [f for f in os.listdir(.) if f.lower().endswith(.jpg)] random.shuffle(jpeg_files) train_set jpeg_files[:2400] val_set jpeg_files[2400:] for f in train_set: base os.path.splitext(f)[0] shutil.move(f, lol-dataset/images/train/) shutil.move(base .txt, lol-dataset/labels/train/) shutil.move(base .xml, lol-dataset/xml_backup/) for f in val_set: base os.path.splitext(f)[0] shutil.move(f, lol-dataset/images/val/) shutil.move(base .txt, lol-dataset/labels/val/) shutil.move(base .xml, lol-dataset/xml_backup/)用 2400 张做训练、600 张做验证是 8:2 划分。random.seed(42) 固定随机种子保证不同次运行得到完全相同的划分方便实验复现。xml 被单独移到 xml_backup 做备份因为 YOLOv8 训练时只认 jpg 和 txt混入 xml 虽然不会直接报错但会在数据缓存阶段产生额外的 I/O 开销没有任何收益。3.2 dataset.yaml 配置类名顺序是生死线在 lol-dataset 的上级目录创建 lol.yaml内容如下path: ./lol-dataset train: images/train val: images/val names: 0: AllyMinions 1: AllyTower 2: EnemyMinions 3: EnemyTower 4: LUX 5: VAYNEpath 是数据集根目录相对于 yaml 文件位置的路径train 和 val 填根目录下的二级路径。names 字典必须与压缩包里 txt 的类别索引一一对应不能按字母序重排——AllyMinions 和 AllyTower 恰好按字母序但 EnemyMinions、EnemyTower、LUX、VAYNE 的字母序和这里不同。一个字母序排序会直接把后面四个类全部错位。提示训练前用脚本随机抽取三个 txt把每行首个索引映射成类名和 yaml 里的 names 逐项对比确认一致再启动训练。3.3 训练参数imgsz、batch、patience 怎么取舍这个数据集的图像尺寸并不统一YOLOv8 会按 imgsz 统一缩放。对 Tower 类这种小目标输入分辨率越大越有利。我的建议是先跑一次 640 的基线观察各类别 AP 再决定是否提到 960yolo detect train datalol.yaml modelyolov8s.pt epochs150 imgsz640 batch16这是最保守的启动方式。yolov8s 参数量适中3000 张图在单卡上大概一小时内能跑完 150 轮。显存充足的话直接换 960yolo detect train datalol.yaml modelyolov8s.pt epochs200 imgsz960 patience30 batch16patience30 表示验证集指标连续 30 个 epoch 没有提升就早停。Tower 类样本少训练后期极易过拟合val mAP 长时间不涨时早停能帮你省下大把时间。关于类别不平衡单纯加大 epoch 效果有限。常用做法是在数据加载时对包含 Tower 类的图片提高采样权重让每个 epoch 里这些图片被抽到的概率更大。对这份数据我一般会把含 AllyTower 或 EnemyTower 的图片重复加入训练列表实际跑下来 Tower 的 AP 能明显改善。3.4 数据增强的边界mosaic 和少数类稀释问题YOLOv8 默认开启 mosaic、翻转、hsv 扰动。mosaic 把四张图拼成一张对小目标检测有正面作用。但问题是如果拼接时随机选到大量只含兵线的图生成的样本里 Tower 类反而更稀疏放大了不平衡效应。我的处理办法是在训练后段关掉 mosaicyolo detect train datalol.yaml modelyolov8s.pt epochs200 imgsz960 close_mosaic10close_mosaic10 表示最后 10 个 epoch 关闭 mosaic 拼接让模型在接近真实单图分布的数据上稳定收敛。这个技巧对多数小目标数据集都有效在这份 LOL 数据上效果尤其明显因为最后几个阶段如果还在拼图塔这个稀疏目标很容易被其他三张图的背景淹没。4. 避坑指南LOL 数据集实战中反复踩到的四个坑4.1 问题一两个小兵类混淆严重现象验证阶段看混淆矩阵AllyMinions 和 EnemyMinions 互相串框的比例特别高模型经常把己方小兵检成敌方小兵。原因两类在游戏画面上都是小尺寸密集目标视觉差异主要是红蓝阵营色调模型在局部特征上很难区分。加上 EnemyMinions 框数比 AllyMinions 多 3600 多个模型在不确定时天然倾向输出多数类。解决如果第一版模型只关注角色检测精度建议先把两个小兵类合并成一个 Minions 类把六分类问题简化为五分类。如果业务确实需要区分敌我就提高输入分辨率到 960 以上并对图片做重叠裁剪训练放大局部色调细节。还有一点hsv 增强参数不要开太大颜色扰动过强会把红蓝阵营的差异抹掉模型更没有区分依据。4.2 问题二Tower 类几乎检不出来现象训练结束后验证集 mAP50 里其他类都在 0.7 左右AllyTower 和 EnemyTower 常年在 0.1 附近真实塔框大量漏检。原因Tower 类总框数太少相加也不到全量 5%而且防御塔在截图中多数位于画面边缘目标小。imgsz640 时塔在缩放后的图像里往往只有十几个像素特征细节基本丢失。解决我的做法是单独抽取 Tower 类做微调。把 yaml 临时改成只有两个 Tower 类从全量模型权重继续训练 60 轮imgsz 提到 1280之后把微调结果和全量模型做集成推理。另一条思路是推理阶段对 Tower 类单独设一个更低的置信度阈值防御塔这类结构目标宁可多几个误检也要保证不漏。这样折腾一轮塔的 AP 从 0.1 拉到 0.4 是能做到的。4.3 问题三XML 与 TXT 坐标对不上现象某张图的 xml 里解析出 5 个目标txt 却只有 4 行坐标换算成像素后也和 xml 明显不一致。原因txt 在流转过程中被某段旧脚本用错误的宽高重新归一化过。不少工具从 VOC 转 YOLO 时会先读 xml 的 size如果图片中途被 resize 过而 xml 里的 size 还是 resize 前的值归一化坐标必然整体漂移。解决不要相信任何第三方转出来的 txt直接用 xml 里的 size 重建class_id_map { AllyMinions: 0, AllyTower: 1, EnemyMinions: 2, EnemyTower: 3, LUX: 4, VAYNE: 5, } def rebuild_yolo_txt(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() width int(float(root.findtext(size/width))) height int(float(root.findtext(size/height))) lines [] for obj in root.findall(object): name obj.findtext(name) if name not in class_id_map: continue cid class_id_map[name] box obj.find(bndbox) xmin int(float(box.findtext(xmin))) ymin int(float(box.findtext(ymin))) xmax int(float(box.findtext(xmax))) ymax int(float(box.findtext(ymax))) xmin max(0, min(xmin, width - 1)) xmax max(0, min(xmax, width - 1)) ymin max(0, min(ymin, height - 1)) ymax max(0, min(ymax, height - 1)) cx ((xmin xmax) / 2) / width cy ((ymin ymax) / 2) / height bw (xmax - xmin) / width bh (ymax - ymin) / height lines.append(f{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w, encodingutf-8) as fp: fp.write(\n.join(lines))注意这里 width 和 height 必须来自 xml 的 size 节点不能从图片文件重新读取。原始 jpg 可能被压缩或缩放像素尺寸和标注基准已经不一致只有 xml 里的 size 才是作者标注时的真实基准。4.4 问题四显存不够导致训练中断现象imgsz960、batch32数据集缓存阶段正常刚进训练第二个 iterationCUDA out of memory 直接报错。原因数据集图片原始分辨率跨度大部分截图接近 2Kmosaic 拼接时四张大图同时进显存实际占用远超 960x960 的单图推算值。解决batch 降到 16 并开启 cache 参数让数据加载更平滑yolo detect train datalol.yaml modelyolov8s.pt epochs150 imgsz960 batch16 cacheramcacheram 会把图片缓存进内存显存只在训练时被占用内存足够的话训练节奏更稳定。内存紧张就换成 cachedisk。如果 8G 显存这些手段都救不回来imgsz 降到 640 配合 close_mosaic 提前开启牺牲一部分小目标精度换取稳定训练。5. 数据自检与再处理把格式问题消灭在训练之前5.1 图像完整性检查穿透坏 jpg数据集里的原始截图如果有一两张损坏训练时会出现 warning 或加载失败。用 OpenCV 快速扫描import os import cv2 broken [] for f in os.listdir(.): if not f.lower().endswith(.jpg): continue img cv2.imread(f, cv2.IMREAD_UNCHANGED) if img is None: broken.append(f) print(broken images:, broken)cv2.imread 读不出内容时返回 None不会抛异常所以用 img is None 判断最直接。坏图确认后在训练列表里剔除对应三个文件而不是只删 jpg否则 labels 里的孤儿 txt 会干扰后续检查。5.2 XML 坐标合法性检查越界框和零面积框坐标超过图像边界或者 xmin 大于 xmax这类标注在 YOLOv8 里会被忽略或造成 loss 异常import glob, xml.etree.ElementTree as ET WARN [] for xml in glob.glob(*.xml): root ET.parse(xml).getroot() w int(float(root.findtext(size/width))) h int(float(root.findtext(size/height))) for obj in root.findall(object): box obj.find(bndbox) xmin int(float(box.findtext(xmin))) ymin int(float(box.findtext(ymin))) xmax int(float(box.findtext(xmax))) ymax int(float(box.findtext(ymax))) if xmin xmax or ymin ymax: WARN.append((xml, obj.findtext(name), zero area)) if xmin 0 or ymin 0 or xmax w or ymax h: WARN.append((xml, obj.findtext(name), out of bounds)) print(WARN)矩形框面积为零表示该标注没有实际意义越界坐标则需要按图像宽高裁剪或者直接弃掉这条标注。这里的合法性检查和 5.3 结合使用先通过合法性检查再用重建脚本生成训练用 txt能最大限度避免脏数据进入训练管线。5.3 重建 txt 后与原文件对比揪出隐藏不一致用前文写好的 rebuild_yolo_txt 把 xml 批量转成新 txt和数据集自带的 txt 做 diffmkdir -p rebuilt_txt python3 rebuild_all.py --xml_dir . --out_dir rebuilt_txt diff -rq original_txt rebuilt_txt | head -50diff 输出到目录级差异。如果 original_txt 目录为空或 diff 没产出说明数据集自带的 txt 和 xml 是一致的如果差异很多说明中间的 txt 生成环节出了问题直接用 rebuilt_txt 替换原始 txt 训练即可。这里千万不要跳过这步直接训练否则 4.3 里那种坐标漂移会变成看不见的暗病。5.4 分层抽样做 train/val 划分保证每类都出现在验证集普通的随机划分有可能把样本量少的 Tower 类全部划进训练集验证集里一个塔都没有mAP 报表全是虚的。分层抽样的思路是按样本是否存在稀有类来决定它的归属import random random.seed(42) rare_files [] normal_files [] for f in os.listdir(annotations): if not f.endswith(.xml): continue root ET.parse(os.path.join(annotations, f)).getroot() names [obj.findtext(name) for obj in root.findall(object)] if AllyTower in names or EnemyTower in names: rare_files.append(f) else: normal_files.append(f) random.shuffle(rare_files) random.shuffle(normal_files) val_rare rare_files[: int(0.2 * len(rare_files))] val_normal normal_files[: int(0.2 * len(normal_files))] val_set set(val_rare) | set(val_normal)这个抽样的关键在于让含 Tower 类的样本按比例进入验证集确保稀有类在验证阶段有足够的样本参与评估。同理训练集也会因为保留八成稀有样本而受益。分层抽样在样本不平衡的情况下比纯随机划分更适合这个数据集。6. 验证模型的小技巧置信度阈值和 NMS 调优的几个方向模型训练完最先要做的验证不是直接看 mAP而是挑几张典型对局截图跑推理观察每个类的实际检出表现。YOLOv8 的推理接口很直接from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(demo_frame.jpg, conf0.25, iou0.45) for r in results: for box in r.boxes: cls int(box.cls[0].item()) conf float(box.conf[0].item()) xyxy [round(v, 1) for v in box.xyxy[0].tolist()] print(f{model.names[cls]} {conf:.2f} {xyxy})conf0.25 是默认置信度阈值iou0.45 是 NMS 阈值。如果 Tower 类在输出里大量缺失把 conf 调低到 0.1 再跑一次看框是否出现如果出现说明模型其实学到了特征只是置信度偏低可以在推理阶段对 Tower 类单独降阈值。如果兵线类别框叠得乱七八糟适当把 iou 调高到 0.6让 NMS 合并更多重叠框减少同一个目标被输出两三个框的情况。还有一个经验验证时别只看单张图选一段动态视角的视频帧序列按时间顺序逐帧推理观察同一个塔在不同帧里的检出稳定性。单帧检出来不算本事五帧里能稳定检出三帧以上才有实际落地的可能。我记得第一次拿这份数据集训练时犯的最大错误就是没做格式校验就把数据直接丢进 YOLOv8结果 txt 索引错位的问题在推理阶段才暴露出来返工花了一整天。从那以后我每个新数据集都强制走一遍完整校验流程先查三件套完整性再重建 txt 对比差异最后分层抽样划分验证集全部通过才写训练命令。希望这篇能帮你省下我踩过的那些坑。本文还有配套的精品资源点击获取
网站建设高端定制企业官网