ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

排球球体检测数据集全流程:从标注规范到YOLO训练避坑

排球球体检测数据集全流程:从标注规范到YOLO训练避坑

简介:这是一份面向机器学习与图像识别研究者的排球球体检测数据集,专门解决比赛场景中排球目标的精准定位、跟踪与动作分析问题,可用于计算机视觉算法训练与模型验证。压缩包共1099个文件,其中548张jpg图像、550个txt标注文件和1个yaml配置文件,总大小32.65MB,标注框准确清晰,便于训练目标检测模型。图像素材取自排球比赛视频片段,覆盖室内场馆、室外沙滩排球场等真实环境,在光照、拍摄角度和球体运动状态(静止、飞行、旋转)上保持丰富变化,能增强模型鲁棒性;标注流程严格,每张图像中的排球均被准确框选,为高质量检测器提供可靠基础。数据集规模设计适中,既提供充足样本满足深度学习对数据量的需求,又能降低过拟合风险、提升泛化能力,依托该数据可直接开展球体检测、跟踪及比赛视频分析等任务,节省采集与标注时间。目前已有144人浏览学习,适合需要高质量体育场景数据的研究者与工程师使用。

1. 排球球体检测数据集:一件被低估的小事

直接用一个开箱即用的通用检测模型去跑一场排球比赛视频,结果 200 帧只检出 3 个球,其中两个还是广告牌上的圆形 logo。换成一个自己整理的数据集,同样的模型结构,召回率能翻几倍,假阳性反而降下来。这不是玄学,是“排球球体”这个目标在图像识别里有它独特的难处:体积小、运动快、外观多变、遮挡频繁,通用数据集里根本没有足够多的这种样本。标题里“机器学习(图像识别):排球球体检测数据集”这三件事拆开说,就是找视频、抽帧、把球的位置标注出来,再整理成模型能训练、能验证的规范数据集。它解决的问题是体育视频分析里最上游的球体定位——球都找不到,后面的落点统计、战术复盘、集锦自动剪辑就全是空中楼阁。适合正在做体育视频分析算法的工程师、想完整走一遍检测数据流程的入门开发者,以及手里正好有排球录像、想把它变成可复用数据资产的人。

2. 先定任务边界:排球球体检测难点与数据集的 6 个控制维度

2.1 一个“球形”目标拆穿三类视觉难点

排球不是一个刚体,这是它和车辆、行人检测最大的不同。球体被击打、拦网、擦网时会产生形变,表面由拼接皮革和压印纹路构成,旋转起来每一帧的花纹都在变,再加上场馆灯光的高光反射,模型看到的“排球”其实是不断变化的纹理块和明暗渐变。如果数据集里只放静止状态的球、只放某一种姿态,模型学到的就不是“排球”这个类别,而是某一个特定花纹状态下的圆。

第一类难点是尺度小。以常见的 1080p 转播画面为例,排球实际直径约 21cm,只要镜头拉成中景或全景,球在画面里就只有几十个像素。把整帧统一缩到训练输入分辨率后,球的直径经常只剩二三十像素,特征信息量少到只有一团明暗。很多人把检测网络不断加深、加宽,召回率仍然上不去,原因往往不在网络容量,而是数据里根本没有足够多的高清小球样本。

第二类是运动模糊。发球和扣杀的瞬间,球速可以超过每秒 20 米,30fps 的转播画面里相邻两帧之间球的位移超过自身直径,球体边缘被拖成一条亮线。“圆”这个强先验在这种情况下完全失效,等到球速降下来模型又能识别了,一碰到触球瞬间就丢。

第三类是语义歧义。看台上观众拿的充气球、场地边广告牌上的圆形 logo、转播镜头里叠加的圆形队徽,都和排球在颜色、形状上高度相似。没有专门标注这类“像球但不是球”的负样本,模型很容易把它们一并框出来。下面这张表可以直观看出不同景别下球的像素级别差别:

场景类型 | 1080p 下球的直径范围 | 标注友好度 | 说明 近景特写 | 120px 以上 | 简单 | 球体纹理清晰,标注效率高,也最容易过量收集 中景跟随 | 50~100px | 中等 | 边缘需要仔细分辨,经常伴随半遮挡 全景或远端 | 20~40px | 较难 | 边缘模糊,人眼都容易看走眼,模型更难

2.2 反推数据集:6 个提前控制的维度

理解了难点,数据集的标准就能一条条反推出来。我一般会在动手采集前先写一页检查表,覆盖下面 6 个维度,每个维度设最低要求,避免数据集建到一半才发现比例失衡。

尺度覆盖维度:训练集里近景、中景、全景都要有,比例按实际应用场景定。如果只做转播机位分析,中景和全景占多数;如果做近景战术复盘,特写多一点。关键是不能缺了 20~30px 的小球样本,否则小球召回永远补不起来。

视角维度:排球主机位通常是斜俯视,但训练数据只有这一种视角的话,模型对平视机位、端线机位的泛化会很差。至少包含两个不同机位高度、两个不同拍摄方向的素材来源。

光照维度:室内比赛灯光、室外白天、阴天黄昏都要覆盖。特别要加入球体和背景亮度接近的样本,比如白色排球落在浅色地板上的画面,这类样本能抑制过曝导致的漏检。

遮挡比例维度:球被运动员的手、身体、球网、裁判挡住是比赛常态。遮挡样本建议占到训练集的 15%~20%,并且要把“遮挡过半”和“几乎全挡但留有一点边缘”两种情况分开标注,避免规则摇摆。

运动状态维度:发球前后的静止球、空中飞行球、触网减速球、刚被拦下后的近距离球,各自都要有样本。统计一下会发现纯飞行球占大多数,如果不刻意留一些静止和极端状态,模型会学出“只认飞行模糊状态”的偏差。

成像质量维度:不同码流、不同压缩级别、有没有台标、是不是手机拍摄,都会影响球的观感。低码流视频里小球边缘会被压缩成块状噪声,主动加入这类样本能提升模型在真实赛事素材上的适配能力。

2.3 先按比例规划,再动手收集,顺序比数量更重要

很多人在第一步就把量冲得很高,一口气抽了两万帧,结果发现其中一万多帧是重复的,球只在同一个位置打转。我的习惯是先画一个目标矩阵:每个尺度乘以每个遮挡等级至少需要多少张有效样本,再去决定找多少场比赛视频。

一个可用的排球球体检测数据集,很有参考意义的规模是近景 600 张、中景 1500 张、全景 900 张,其中遮挡样本加起来不少于 400 张,总量 3000 张左右。3000 张看起来不大,但要把每张都做成“有效样本”,难点在于让这些帧之间有足够的球体位置变化和状态多样性。这个步骤不产生进度条,却是后面所有训练效果的基础。

提示:动手前先在文档里写清目标,例如“发球局全景 200 张、扣球瞬间特写 150 张、拦网遮挡中景 120 张”。不写清单直接抽帧,很容易把大量时间浪费在重复帧的标注上。

3. 排球球体检测数据集搭建:采集、抽帧、清洗、标注一条龙

3.1 用 ffmpeg 抽帧,两步过滤掉大部分废帧

拿到比赛视频后,第一步不是直接标注,而是按目标矩阵抽帧。我会根据素材差异设定 2~5 帧每秒的抽帧率。30fps 全抽会让相邻帧几乎完全重复,2fps 则能在保留球体位置变化的同时显著减少相似帧。

ffmpeg -i match_src_01.mp4 -vf "fps=2,scale=1920:1080" -q:v 2 -start_number 0 frames/match01_%04d.jpg

这段命令的关键参数:fps=2是每秒取 2 帧,scale=1920:1080把素材统一到 1080p,避免标注框在不同分辨率之间换算时出错;-q:v 2控制 JPEG 质量,数值越小画质越高,一般取 2~3。如果原素材是 4K,建议先只做fps=2抽帧保留原分辨率,后续再评估是否下采样,因为 4K 下的小球信息远多于 1080p。

第一次抽完帧,需要把清晰度差和内容重复的帧过滤掉。重复帧的判定我使用感知哈希,两张图哈希距离不超过 2 就视为近似重复,只保留前一张。这一步能去掉大量同机位、同球位的画面,让标注员把时间花在真正有变化的样本上。

# 简易相似帧过滤脚本 import os, shutil from PIL import Image import imagehash src_dir = "frames" dst_dir = "frames_clean" os.makedirs(dst_dir, exist_ok=True) last_hash = None for name in sorted(os.listdir(src_dir)): if not name.lower().endswith((".jpg", ".jpeg", ".png")): continue img = Image.open(os.path.join(src_dir, name)) h = imagehash.phash(img, hash_size=16) if last_hash is not None and h - last_hash <= 2: continue # 与上一帧过于相似,丢弃 last_hash = h shutil.copy(os.path.join(src_dir, name), os.path.join(dst_dir, name))

脚本逻辑是按文件名顺序遍历,计算每帧的感知哈希值,h - last_hash表示两帧之间的汉明距离,小于等于 2 就认为画面几乎没变化。hash_size=16是哈希精度,越大对细节越敏感,排球场景下 16 兼顾速度和准确度。如果素材里镜头切换频繁,建议先做场景切分再按镜头抽帧,否则镜头切换瞬间的相似帧仍然混在结果里。

3.2 标注前先立规则:bbox 怎么画、模糊帧怎么办、遮挡怎么判

标注规则是数据集能否复用的关键,规则不一致会让模型学不到统一特征。以 YOLO 文本格式为例,每一帧对应一个同名 txt 文件,每行内容为“类别 中心点横坐标 中心点纵坐标 宽 高”,四个坐标都是相对图片宽高的比例值,取值范围 0~1。

bbox 标注规则我最常定的只有三条。第一,框住球体可见区域即可,不要主观外扩推测完整圆形;球被挡了一部分,就框可见的真实边界。第二,球边缘模糊时以明暗过渡带中线为界,不能一会儿框大一会儿框小。第三,手掌和球体重叠时,手掌覆盖的部分不算可见区域,按剩余可见部分标注。

模糊帧的判定是:人肉眼能准确判断球心位置的,保留并标注;肉眼都分辨不出球在哪里的,直接丢弃,不送进训练集。标注工具选开源免费的即可,我常用的流程是先框矩形,再导出成 YOLO 文本格式,中间用脚本做坐标归一化。

# 将绝对坐标标注转成 YOLO 归一化格式 def convert_and_save(txt_out, box, img_w, img_h, cls_id=0): x1, y1, x2, y2 = box # 左上角和右下角绝对像素坐标 box_w = (x2 - x1) / img_w box_h = (y2 - y1) / img_h cx = (x1 + x2) / 2 / img_w cy = (y1 + y2) / 2 / img_h with open(txt_out, "w") as f: f.write(f"{cls_id} {cx:.6f} {cy:.6f} {box_w:.6f} {box_h:.6f}\n")

这段代码里,box是标注工具给出的矩形坐标,img_w和img_h是图像宽高,cls_id在排球检测项目里固定写 0。核心是把中心点和宽高全部除以图像尺寸,使标签不随输入分辨率变化。归一化坐标最常见的坑是越界:球贴着图像边缘时x2可能超出图片宽度,脚本里要加越界裁剪,把x1、y1限制在 0 以上,把x2、y2限制在img_w - 1、img_h - 1以内。

3.3 切分训练/验证/测试:先按视频源切,再做场景均衡

数据切分方式直接决定验证结果可不可信。很多人直接随机打乱所有帧,结果训练集和验证集混进同一场比赛的大量相邻帧,验证 mAP 虚高,换到新视频立刻打回原形。正确做法是先按来源视频文件切,比如把一整场比赛完整放进验证集,其余比赛视频放进训练集,这样才能验证跨场泛化能力。

切分还要考虑尺度均衡。我通常会在切分后跑一个统计脚本,输出每个子集里 bbox 宽度的中位数和分布直方图,确认小球样本在训练、验证、测试中的占比接近原始分布。如果验证集里全是中景大球,小球指标就失去参考意义。

# 按视频前缀切分并检查 bbox 尺寸分布 import glob, random random.seed(42) all_frame_txts = glob.glob("labels/*.txt") # 假设文件名形如 frames/match01_00012.jpg video_id = lambda p: p.split("/")[-1].split("_")[0].split(".")[0] videos = sorted(set(video_id(p) for p in all_frame_txts)) random.shuffle(videos) split = {"train": videos[: int(0.7 * len(videos))]} val_idx = int(0.7 * len(videos)) split["val"] = videos[val_idx : int(0.85 * len(videos))] split["test"] = videos[int(0.85 * len(videos)) :]

这段切分是按文件名前缀识别视频来源,先把所有视频打乱,再按 70%、15%、15% 切成三份。用视频而不是帧作为最小切分单位,就是为了避免相邻帧进入不同集合导致验证结果失真。代码里的random.seed(42)固定随机种子,下次重跑能得到同样的切分结果,这是实验可复现性的起点。

标注完成后,建议对整个数据集做一次最终统计:图像总数、有效 bbox 总数、标签文件里空文件的数量、类别编号是否只出现 0。空文件问题最常见,标注员漏标一帧会生成空 txt,训练框架虽然会跳过,但会让样本分布产生偏差,直接删掉对应图片最省事。

4. 把数据集喂给检测模型:模型选型与训练参数

4.1 为什么选择单阶段回归类检测器做基准线

排球球体检测属于小目标检测,但它的特殊性是类别少、目标小且运动剧烈。在这种场景下,我会用单阶段回归类检测器作为基准线,而不是直接上更复杂的两阶段结构。原因有两个:单阶段结构推理速度快,体育视频分析经常面对小时级长视频,处理速度直接影响可用性;小目标对上下文依赖很强,深层区域提议网络不一定比直接特征回归更高效,数据质量不够时两者差距主要来自数据而不是网络结构。

具体选型时,我优先用 YOLO 系列里生态最成熟的主流版本,而不是追最新发布的大模型。理由很实际:踩坑记录多、部署工具全、自定义数据接入成本低。数据集质量一般时,新模型带来的收益远没有把标注规范和训练参数调好来得大。

4.2 一份可复用的数据集配置与训练命令

以 YOLO 系列为例,数据集准备完成后,需要写一个数据集配置文件,告诉框架图片路径、类别数量和类别名。

path: /home/you/volleyball_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: volleyball

配置里path是数据集根目录,train、val、test是相对根目录的图片文件夹路径。nc是类别数量,排球检测只有一类,所以是 1。names的索引必须和标注文件里的cls_id对应,如果标注文件类别编号从 1 开始而这里从 0 开始,验证 AP 会直接归零,这是最容易踩的低级错误。

训练命令以常见开源框架为例:

python train.py --data volleyball.yaml --model backbones/yolo_v8.pt --epochs 100 --batch 16 --imgsz 640 --device 0

--data指向刚才的 yaml 文件,--model是预训练权重,epochs100 是训练轮数,batch16 表示一次加载 16 张图,imgsz 640是训练输入分辨率,device 0指第一张显卡。显存不够时先把batch降到 8,再考虑缩小imgsz,因为imgsz对检测精度的影响远大于 batch。

对排球这种小球检测,建议打开 scale、hsv、mosaic 等增强开关,但增强强度一定要可控,具体怎么控看下一小节。

4.3 三个必调参数:imgsz、epochs、增强强度

参数层面,把精力集中在三个点上。

第一个是训练输入分辨率imgsz。很多人默认用 640,但 20~40px 的小球在 640 输入下只占画面的 3%~6%,信息量太少。我的调法是把imgsz从 640 提到 960 或 1280,同时把batch减半,因为大分辨率对显存占用上升明显。分辨率并非越大越好,超过一定程度后训练速度大幅下降、收益趋缓,排球场景里 960 通常是性价比最高的起点。

第二个是训练轮数epochs。小目标数据集的收敛速度往往比中大目标更慢,我用 100 轮作为起点,每 10 轮打印一次验证集 mAP50,观察曲线是否还在上升。如果 100 轮还在提升就再加 50 轮;如果早就进入平台期,就要怀疑是增强过强导致欠拟合,而不是盲目加轮。

第三个是增强强度。YOLO 系列里hsv_h、hsv_s、scale、mosaic是四个主要开关。排球球体外观受光泽和颜色影响明显,我会把hsv_h默认值调低,避免球体颜色被大范围替换成离谱色调;同时调大scale,保证缩放到 0.5 倍时仍然有小球样本可学。

参数 | 默认值 | 排球建议值 | 理由 hsv_h | 0.015 | 0.005 | 保留球体原有颜色特征,防止色调漂移 hsv_s | 0.7 | 0.4 | 降低饱和度波动,减少高光干扰 scale | 0.5 | 0.8 | 模拟更多更小的球体,弥补小球样本不足 mosaic | 1.0 | 1.0 | 正常开启,多尺度上下文对小目标有益

表里数值是一组合适的基线,不同素材要用小规模消融实验确认。固定其他参数,一次只改一个增强项,各跑 30 轮比对验证集 mAP50,用最少的训练时间把参数调到有理有据。

5. 排球球体检测训练避坑:5 个真实排查记录

5.1 验证集 AP 为 0,训练曲线却正常:标签类别索引错位

现象:训练过程中 mAP50 一直为 0,但损失曲线和置信度曲线看着都正常,单独拿一张图预测也能正确框出排球。原因是数据集 yaml 的类别编号和标注文件不一致,比如标注文件里写的是1 volleyball,而 yaml 里nc: 1、names: 0: volleyball,模型把所有目标当类别 0 学,验证集里却找不到类别 1 的对应目标,AP 自然归零。

解决:先用统计命令确认标注文件里只存在指定的类别编号,再核对 yaml 中names的键值是否和标注文件里的cls_id完全一致,两处统一成 0 就能解决。

5.2 小球漏检集中在远景画面:该调分辨率而不是换更大网络

现象:近景大球召回率 90% 以上,远景小球召回率只有 20% 左右,AP50 尚可、AP75 明显偏低。很多人会去换更深的骨干网络,结果训练时间翻倍,小球指标纹丝不动。

原因:远景球在 640 输入下只有十几个像素,骨干网络低层特征图里的空间信息已经几乎不可辨识,加大网络宽度并不能找回已经丢失的位置细节。

解决:把imgsz提到 960 甚至 1280,同时调低mosaic里容易造成小球失真的部分。再把远景小球单独复制一份,用切片重叠的方式把全景图切成四块、各自放大到 640 训练,推理时按同样方式切图后合并。小球召回率通常能明显上一个台阶。

5.3 模型把广告牌上的圆环当成排球:缺硬负样本

现象:验证集 mAP 看着不低,但放到整场比赛视频上,广告牌上的圆形 logo、看台上球迷手里的充气排球被大量框出,假阳性多到没法直接使用。

原因:训练数据里只包含“是排球”的正样本,没有足够多“不是排球但长得像排球”的负样本。模型学到的只是颜色加圆形的弱关联,并不理解球的尺度、运动轨迹和纹理特征。

解决:从同一批视频里截取完全没有排球但包含圆形物体的片段,单独标一个“背景”类别或者直接放进负样本池。负样本占训练集的 5%~10% 就能明显压低假阳性。推理端再补一条规则:检测框宽或高小于整幅图像宽度的 2% 时直接丢弃,这条下限阈值能挡住大部分广告牌误检。

5.4 高速扣球瞬间被漏掉,慢速球却能检出:抽帧与运动模糊

现象:发球、扣杀的高速瞬间几乎每帧都丢球,中速球检出正常。对比训练数据后发现,高速瞬间的帧很少,而且这些帧都带着明显运动模糊。

原因:固定帧率抽帧下,高速球在一帧内位移大,球体边缘被拖成亮线,训练集中这种“亮线状球”样本严重不足,模型没见过这个外观。

解决:针对球的高速片段提高抽帧率,比如扣杀瞬间用 10fps、中慢速段用 2fps。模糊严重的帧不要急着丢,只要肉眼能分辨球心位置就保留,并单独划出一部分模糊帧放进验证集,验证模型对高速运动的真实表现。推理端还可以采用连续帧上下文,把前后帧的检测框做位置关联后再决定保留还是丢弃当前帧的结果。

5.5 同一数据集两次训练 mAP 差 3 个点:随机种子和 checkpoint 选择

现象:同样的数据、同样的代码,第二次训练后验证 mAP 和第一次相差好几个点,让人误以为代码出了问题。

原因:数据加载和随机增强模块引入了随机性,数据集的随机 transform、shuffle 顺序在不同次运行时都会变。排列顺序变化会显著影响小目标样本被采样的节奏,最后体现在 mAP 波动。

解决:训练配置里固定随机种子,常见做法是训练命令加--seed 42,同时对验证集保持固定顺序。训练过程中每 5 轮保存一次 checkpoint,拿最后一次保存的模型和历史上最好的模型对比,以最好版本作为最终选择,能进一步降低随机性带来的结论偏差。

6. 最后一步:从“能框球”到“能追踪球”的验证技巧

数据集和模型都跑通后,真正决定能不能上业务的是连续视频上的表现。我手里有从零构建排球球体检测数据集的全流程,最值得先做的一个验证技巧是:在测试视频上跑完单帧检测后,加一层 IOU 关联逻辑,把相邻帧里属于同一个球的检测框串成轨迹。实现思路很简单,上一帧某个球在下一帧相近位置出现重叠检测框时,就认定为同一目标,并沿用目标编号。

实践里我按几个规则做:目标框中心点位移不超过自身宽度的 0.8 倍,相邻两帧 IOU 不低于 0.3,同一帧内每个目标最多匹配一个继承目标。有了轨迹后,统计的不再是单帧 mAP,而是“视频片段里球被连续跟踪的帧数占比”。只有轨迹连续率合格,才说明模型能真正落到长视频场景。

另一个技巧是加球体运动先验。排球不会静止在高空,也不可能瞬间反向穿越场地,所以当某条轨迹的帧内位移超过经验上限,比如 1/4 画面宽度,就判定为误检并丢弃该帧。再加上前面提到的尺寸下限,这层规则通常能把假阳性压到可用范围。

最后说投入回报的判断:排球球体检测数据集不值得一开始就追求 10 万张图的大工程。先用 3000 张、单类别、一个开源检测框架把全流程跑通,再用上一章的增强和推理规则去迭代,效果往往比盲目扩数据更明显。我踩过最大的坑,恰恰是觉得数据不够就反复加大抽帧量,结果是相似帧过多、样本多样性不足,白白浪费了大量标注时间。先把基础数据集做规范,再去补难负样本和运动极端状态,才是这个方向最省力的路径。希望这些经验能帮到你,也期待你在自己的场景里把这条链路完整跑起来。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进