
简介面向机器学习和图像识别研究者的排球球体检测数据集涵盖室内场馆、室外沙滩排球场等多种真实比赛场景覆盖不同光照条件与拍摄角度。每张图像均经过严格标注准确框选球体位置细致标注静止、飞行、旋转等运动状态为训练高精度球体检测与追踪模型提供数据基础适用于球体检测、跟踪、动作识别等计算机视觉任务。压缩包共1099个文件含548张JPG图像、550个TXT标注文件及1个YAML配置文件整体约32.65MB目录按视频帧序列组织便于筛选与分批训练YAML配置可直接对接主流检测框架TXT标注与图像逐张对应降低数据预处理成本。目前已有144人浏览学习规模适中的样本量既能满足深度学习需求又可有效防止过拟合适合希望构建排球比赛实时检测系统的学生与算法工程师快速上手。1. 排球球体检测数据集比想象中难在球太小、飞太快排球球体检测数据集核心目标是在视频帧中稳定地框出那个直径二十多厘米的球。很多人以为这是通用目标检测的简单应用但排球在标准比赛画面里往往只占几十个像素加上高速飞行、旋转模糊、场地光照突变和看台背景干扰直接套用通用检测器很容易出现“训练时挺准、比赛视频里疯狂漏检”的情况。做运动分析、赛事自动剪辑或训练辅助的开发者都绕不开一个基础工作构建一份能真实反映比赛场景的排球球体检测数据集。这篇笔记就顺着这个数据集把数据采集、标注、训练和排错的全流程拆开讲新手能照做熟手能拿去核对边界。2. 构建排球球体检测数据集从公开资源到自采数据的完整流程2.1 先想清楚为什么不能拿通用数据集凑合通用目标检测数据集里确实有“球”这个超类里面可能包含足球、篮球、网球偶尔也会有几张排球图片。但问题在于这类数据集的标注逻辑和排球比赛场景差得远。排球在比赛画面中是典型的小目标球体占整幅图像的比例经常不到百分之一通用数据集更偏爱物体占画面较大的样本标注框也往往把球连同运动员的手臂一起包进去。拿这种数据直接训练模型学到的是“画面中较大的圆形物体”而不是“高速飞行的小球”。公开的体育类检测基准倒是有一些但检索一圈会发现两个麻烦。一是排球类别粒度粗很多基准把排球和其他球类统称为“球”模型区分不了排球和足球二是标注风格不统一有的只标排球有的把球员、球网、场地线都标了类别定义复杂反而干扰单类检测。所以我的习惯是先把公开数据当预训练来源再围绕自己的目标场景构建一份排球专项数据集。这份数据集不需要大但一定要贴合真实比赛镜头否则后续所有训练技巧都是空中楼阁。数据分布决定模型上限这句话在排球检测上体现得特别明显。如果你只收集同一机位、同一片场地的视频模型会把“这个场地的颜色”当成排球出现的背景特征换一块地胶颜色、换一种看台灯光推理时就漏成筛子。因此在动手采集前先花半小时把目标比赛视频的机位、场地、光线、镜头运动方式看一遍再决定数据组成这比盲目堆帧数重要得多。2.2 数据采集机位、帧率、分辨率是关键采集排球视频时最常被忽略的是帧率。普通比赛录像多为25或30fps人眼看着连续但排球在触地、扣杀瞬间的单帧位移可以超过球体直径的几倍标注时连人眼都很难判断球心在哪模型自然学不到稳定特征。我一般会优先找60fps以上的视频源或者从慢动作回放片段里抽帧作为补充。如果只能拿到30fps素材那就接受“部分帧标注不可用”的事实宁可少标不要硬标。分辨率同样直接决定标注上限。1080p视频里排球直径大约在三四十个像素4K视频能到七八十个像素后者标注出来的框要稳定得多。如果原始视频分辨率不够不建议用超分辨率算法硬拉那是给自己制造训练和推理时的额外复杂度。机位方面至少要覆盖主摄像机位和高处俯视两个视角正对球网的平视镜头和高处俯拍的透视差别很大模型很难自动跨视角泛化。真实比赛录像往往伴随镜头变焦变焦会让球的尺寸在几秒内剧烈变化这类样本要有但不能超过总量的三分之一。下面是我在采集阶段会固化成文档的参数表每次建数据集前先对着表过一遍。采集参数建议值原因源视频帧率至少60fps减少单帧位移提升标注与检测稳定性源视频分辨率1920x1080起步球体像素太少时标注和模型都难收敛机位数量至少2个覆盖平视与俯视视角防止过拟合单一视角场地类型尽量覆盖2种以上不同地胶颜色、看台背景会显著影响模型光线条件白天与夜间灯光各占一部分避免模型把照明条件当作分类特征变焦镜头不超过总量1/3变焦导致目标尺寸突变样例过多会干扰尺度学习如果你是自己用手机或运动相机拍素材记得固定机位、关闭自动对焦和自动曝光。自动对焦会在球飞近时疯狂拉风箱画面模糊的同时球的尺寸也在变标注时你根本分不清是该框一个边缘清晰的球还是框一个弥散的色块。手动固定焦距和曝光后素材的可用率会高很多。2.3 标注格式选型VOC、YOLO、COCO 怎么选数据集格式的选择取决于你后续准备用什么训练管线。如果已经决定走YOLO系一阶段检测器YOLO的txt格式最直接每张图一个文本文件每行一个目标如果还要做多模型对比、目标追踪或者需要加载COCO风格的评估脚本COCO的JSON格式更通用。VOC的XML格式现在更多被当作中间格式因为开源标注工具普遍支持VOC导出拿到手再转成其他格式。从VOC XML转YOLO txt是绕不开的一步这里给一个我常用的转换脚本。脚本负责读取VOC格式的标注框转换为归一化坐标的YOLO格式文本。import xml.etree.ElementTree as ET import os # 类别名顺序必须与训练配置中的 class_names 一致 CLASS_NAMES [volleyball] def voc_to_yolo(xml_file, out_dir): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in CLASS_NAMES: continue # 只保留排球类别 cls_id CLASS_NAMES.index(cls) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 归一化到 0~1 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_path os.path.join(out_dir, os.path.basename(xml_file).replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines)) # 遍历某个目录下的所有 XML xml_dir annotations_voc out_dir labels_yolo os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if xml_name.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_name), out_dir)这个脚本的逻辑很简单从VOC的XML中读取像素坐标求得中心点坐标和框宽高再分别除以图像宽度和高度得到0到1之间的归一化数值。关键点是类别顺序CLASS_NAMES里类别的索引就是YOLO训练时的class_id一旦顺序和训练配置不一致类别会错位。另外要注意如果图像有EXIF旋转信息有些标注工具导出的坐标没有做旋转校正转换前先用脚本把图片统一重编码避免横竖图混在一起时框全部偏掉。标注工具本身不必纠结主流的开源视觉标注工具都支持VOC/COCO导出。真正的问题在于导出后要保留一份“图片路径标注文件路径”的清单不要手动分文件夹复制来复制去容易漏文件。我习惯让工具直接导出到统一个数据集根目录下然后用脚本按视频id划分文件。2.4 标注实操球体被遮挡怎么办标注排球和标注人、车不太一样最大难点是遮挡和模糊。排球经常被运动员手臂、拦网、记分牌挡住一半也经常在快攻时拖出一道残影。对于这种情况如果标注规则不统一两个标注员对同一帧的框可以差出大半个球训练出来的模型会在模糊帧上输出抖动不定的框。我通常用下面这套规则约束标注员和自检流程场景是否标注说明球完整可见标注完整外接框标准情况球被遮挡超过一半不标注人工猜测框会引入噪声球在画面边缘被切掉不标注不完整的框会干扰回归训练球运动模糊但轮廓可辨标注框适当放大10%模糊帧标注小框反而会教坏模型扣杀瞬间球被手指遮挡不标注这种帧通常只有一两帧跳过不影响很多标注工具支持在两帧之间插值生成中间框这对排球很省力因为球在飞行段的轨迹基本是平滑抛物线每隔10帧标一次中间帧会自动生成。但扣球、拦网触球瞬间轨迹会突变插值出来的框会明显偏离。处理方式是先让工具生成插值再人工跳着检查突变段看到连续两帧的球心位置出现锐角拐弯就去修正。标注质量检查不能省。我的做法是让两个标注员分别独立标同一段100帧然后计算它们的平均IoU如果平均IoU低于0.8说明规则还没定死先别急着继续标。这一步能省掉后续训练时大量“玄学”排错时间因为很多训练异常最后都能追到标注噪声上。3. 用数据集训练排球检测模型从配置到评估的完整过程3.1 模型选型为什么一阶段检测器是默认起点排球球体检测的实际应用大多在比赛视频分析、自动集锦和训练辅助上这些场景要求模型至少能跑到实时或接近实时。两阶段检测器的定位更高但在高速镜头下成本偏高Transformer类检测器在公开数据集上表现好可数据量不够时反而难收敛。相比之下一阶段检测器尤其是YOLO系在速度和精度之间最平衡而且对小目标检测有相对成熟的优化手段所以我一般把它作为第一个落地方案。选模型还要看部署端。如果最终只在服务器上离线跑模型大一点没关系如果要塞进移动端或嵌入到直播流里就得选轻量版本并且接受精度下降。下面这个对比关系可以参考模型方向精度速度对排球场景的适配两阶段检测器高慢适合离线分析不适合实时视频流一阶段检测器YOLO系中高快默认首选小目标优化空间大Transformer检测器高中数据量小时容易欠拟合不优先不管选哪个方向有一点不会变模型输入分辨率对排球检测的影响比模型参数量大得多。排球太小输入分辨率一旦降到320以下球的特征基本就被卷积层抹平了。所以接下来所有训练参数的讨论都围绕“如何让模型在小目标上不丢信息”展开。3.2 数据划分与图像增强按视频划分而不是按帧划分排球数据集最典型的翻车点是划分训练集和验证集时按“帧”随机切。比赛视频的相邻帧之间只有几十毫秒的差异画面几乎没变按帧随机划分会让同一场比赛的画面同时出现在训练集和验证集里验证集mAP被虚高倒影撑到0.95真正换一场比赛立刻掉到0.3。正确做法是先把视频片段分组再按组划分。下面是一个按“视频片段id”划分的参考逻辑核心是先对视频片段洗牌再分配图片import os import random image_dir images # 图片按 video_xxx_framennn.jpg 命名 video_ids set() for fname in os.listdir(image_dir): video_id fname.split(_)[0] # 取前缀作为视频片段id video_ids.add(video_id) video_ids sorted(video_ids) random.seed(42) random.shuffle(video_ids) split_point int(len(video_ids) * 0.8) train_videos video_ids[:split_point] val_videos video_ids[split_point:] # 根据视频id把图片分配到不同目录 for fname in os.listdir(image_dir): video_id fname.split(_)[0] if video_id in train_videos: os.rename(os.path.join(image_dir, fname), os.path.join(images/train, fname)) else: os.rename(os.path.join(image_dir, fname), os.path.join(images/val, fname))这段代码里最关键的变量是split_point它决定训练集和验证集的比例。对于小球类数据集我建议把验证集比例提高到20%到25%因为验证集里一旦缺少某一类光线条件调参时很容易误判。划分完成后再从每个视频片段里统计抽帧保证同一片段最多抽出一半的帧避免连续帧重复信息撑爆训练集。增强策略要围绕“球小、易模糊、背景杂”来设计。我常用的增强组合是随机亮度扰动模拟场地灯光变化随机运动模糊模拟球在高速飞行时的拖影随机裁剪图像边缘模拟球被打出画面又回弹的镜头。左右翻转对排球也适用但翻转后场地广告牌上的文字会镜像模型可能会把“文字镜像”当成新特征所以翻转概率不宜过高建议设置在0.3到0.5之间。马赛克增强对通用检测很有效但在排球上要谨慎因为四张图拼接后每张图里的球会被进一步缩小反而加剧小目标问题如果要用就把输入分辨率同步提到1280。3.3 训练参数batch、imgsz、epochs到底怎么设训练YOLO系模型时最影响排球检测效果的参数不是learning rate而是输入分辨率imgsz。我用640分辨率跑过一版mAP50-95在0.35左右把imgsz提到1280后直接跳到0.52代价是训练时间和显存占用翻了四倍。对于球体只有几十像素的数据集这个成本是值得的。数据集描述文件里要明确指定类别名一个最小可用的配置长这样path: /path/to/volleyball_dataset train: images/train val: images/val nc: 1 names: 0: volleyball训练命令我习惯加上混合精度和早停避免无效的长时间空转yolo detect train \ datavolleyball.yaml \ modelpretrained.pt \ imgsz1280 \ batch16 \ epochs200 \ ampTrue \ patience50参数说明imgsz1280指训练输入图片的短边被缩放到1280像素batch16在8G显存下搭配1280分辨率基本是极限如果爆显存优先降低batch而不是降分辨率patience50表示50个epoch内验证损失不下降就自动停ampTrue开启混合精度加速训练但如果你用的是某些兼容性不太好的开源库amp会让loss曲线抖动明显关掉后再对比一轮。显存不够时梯度累加是更聪明的替代方案。比如batch4时每4次反向传播再更新一次权重等效于batch16但训练速度会慢不少。调训练参数一定要搭配早停来看不要硬跑满200轮排球数据集通常训练集只有几千到几万帧50到100轮内就该收敛继续硬跑只会把模型逼到背训练集。还有一个不太起眼但很值得调的点anchor设置。现代一阶段检测器很多已经自动学习anchor但如果你的库还保留手动anchor配置建议用小目标倾向的anchor尺寸。排球的外接框通常很接近正方形且宽度往往小于输入分辨率的十分之一可以对着训练集里的真实框尺寸聚类几个小anchor再把配置写进去。3.4 评估与调优mAP50、mAP50-95、置信度曲线训练结束后不要只看那一行mAP50。排球检测的定位精度直接影响后续轨迹追踪mAP50只要求IoU大于0.5对小球来说太宽松即使框偏移半个球体也能被算成正确检测mAP50-95会把IoU从0.5到0.95逐级评估能更真实反映“框贴不贴球”。如果mAP50-95明显低于mAP50说明模型能大概找到球但框的位置不够稳。评估时还要额外看两个东西召回率和误检来源。召回率决定漏检情况如果验证集上recall只有60%即使precision很高视频里依然会频繁丢球。误检来源可以看验证集预测结果里的置信度分布很多误检框的置信度集中在0.3到0.5之间而真实球的预测置信度往往分布在0.6以上如果两个分布重叠得很厉害说明模型没有把排球和背景圆物区分开此时优先补数据而不是继续调阈值。我通常会把验证集的PR曲线导出来找一个让precision和recall相对平衡的置信度阈值作为推理时的默认值。但这个阈值不是一劳永逸的换一个比赛场地后置信度分布会整体漂移部署时要保留一个快速调节阈值的配置入口最好是在运行配置里暴露一个环境变量而不是每次改代码。4. 排球球体检测数据集常见问题与排查五个踩坑记录4.1 loss震荡得像锯齿现象是训练曲线上下跳动每个epoch之间的损失值忽高忽低看起来像锯齿而不是平滑下降。原因有两类。第一类是标注框中心不一致同一颗球在相邻帧里有的标框偏左、有的标框偏右模型每个batch看到的回归目标相互矛盾第二类是学习率过大或batch太小优化过程不稳定。解决方法是先排除标注问题。用标注工具的连播功能把训练集抽查一遍重点看球快速运动的片段如果发现框位置像“跳帧”一样来回抖就把这部分视频重新标注或干脆删掉。确认标注没问题后再把学习率降到原来的五分之一并加一个warmup阶段。很多YOLO库默认warmup只有3个epoch对数据集比较小的排球项目不够我会手动调到10个epoch。4.2 验证集mAP很高比赛视频里却完全漏检现象是训练时验证集mAP50能到0.9导出模型放到另一场比赛视频里球一个都检不出来或者检测框满场乱跳。原因绝大多数是数据划分方式错了。如果训练集和验证集来自同一批视频甚至同一场比赛的不同帧模型记住的是这个场地、这个机位的整体特征并没有学会抽象出“排球”本身另一个原因是采集阶段机位太单一模型只见过正对球网的平视镜头换个俯视角就完全不认识。解决方法是先按视频片段重做数据划分保证验证集来自完全没参与训练的比赛场次然后把模型放到至少两种机位、两种场地的视频上测试。如果机位差异导致性能暴跌就在采集阶段补充对应机位的少量数据用这些数据对模型做微调而不是从头重训。4.3 把羽毛球、圆形场馆标志误检成排球现象是模型对真实的排球偶尔漏检却把羽毛球、场地上的圆形Logo、看台上的装饰气球框出来误检框的置信度还不低。原因是训练数据里“负样本”太少。模型没机会看到一堆没有球的比赛背景于是把“圆形白绿色调”当成排球的替代特征。加上排球类别只有一个模型不需要区分排球和其他球只要像圆形就会被激活。解决方法是专门收集一批不含排球的比赛视频帧帧里要有羽毛球、足球、圆形广告牌等干扰物放在训练集中的images/train目录下不给它们写标注文件。模型在训练时会把这些无标签图片当作背景误检会明显下降。另外一个操作细节是检查标注框是否把场地线、球网横杆包了进去如果标注框经常包含球网的白边模型也会把横杆特征当成排球特征。4.4 置信度阈值调低全是误检调高球也消失现象是推理时把置信度阈值从0.25调到0.7误检少了很多但真实的排球也一个都框不出来表现为“要么乱、要么瞎”。原因是排球目标本身太小、纹理弱模型的输出置信度整体偏低真实球的最高分可能只有0.5左右。这种情况下单纯调阈值不会有好结果因为阈值会把误检和真检一起切掉。解决方法是先提高输入分辨率到1280再重新训练让模型在更大尺度上提取球面特征同时检查图像的对比度如果球和场地颜色太接近比如白色排球在浅色地胶上靠模型很难区分只能在数据里增加这种低对比度帧并配合亮度增强。最后再画一遍置信度分布直方图找到误检峰和真检峰之间的谷底把阈值设在那里。4.5 标注了上万帧还是过拟合现象是训练集loss降到很低验证集loss反而回升模型对训练集里的每一帧都背下来了但换到新比赛就失灵。原因很反直觉数据量够但多样性不足。同一场比赛、同一机位连续抽帧一万帧的有效信息可能只相当于几百帧因为相邻帧背景完全一样球的位置差异很小。模型只需要记住“这块场地上的球”而不是“任何场地上的球”。解决方法是先统计数据集里包含多少个独立视频片段、多少种机位、多少种场地。如果不足10个片段就把每个片段的抽帧上限降下来优先补充新的比赛场次和不同机位再把增强强度适当提高尤其是随机亮度和运动模糊。用早停也能缓解过拟合但治标不治本关键还是让每一个视频片段贡献更多独立信息。5. 把数据集用出价值从球检测进阶到轨迹追踪与回放定位单帧检测只是第一步真正让数据集值钱的是把检测框串成排球轨迹。有了轨迹才能统计触球次数、判断扣球速度、定位关键回合。我常用的做法是在YOLO系检测结果之上接一个轻量级多目标追踪器用前一帧球的运动方向预测下一帧位置把置信度不高的候选框通过轨迹约束重新匹配。追踪器里最值得调的是“最大丢失帧数”排球被拦网遮挡后经常消失十几帧这个参数我一般设到30否则一个遮挡就会把轨迹切断。轨迹质量验证有个很实用的技巧把预测轨迹画在视频上人眼扫一遍。排球飞行段的运动方向是平滑变化的如果轨迹在某处出现锐角折线或突然跳到场地另一侧说明检测框错跟到了背景物体上找到这些折线帧后手动修正对应帧的标注框再把这部分数据加回去重训一轮。这样循环两三次数据集和模型会同步变好。用这个思路数据集的价值就从“训练一个检测器”扩展到“支撑运动分析”。基于稳定轨迹可以算飞行速度和落点密度这些指标才是教练和分析师真正想要的产出。关于这个方向值不值得做我的判断是如果只想做一个比赛视频里能找到球的Demo通用模型加少量数据就够但要做稳定的视频分析工具数据集必须按机位、场次、光线条件系统组织否则每次换场地都是一次返工。我踩过最大的坑就是早期猛标帧数以为数量够了模型自然会好结果在同一个机位下精度很高换一场室外比赛直接废掉。后来养成的习惯是先花一小时看目标比赛视频列清楚机位、光线、遮挡条件再定采集和标注规则然后把数据按视频片段严谨划分。这样做之后训练和排错的时间反而大幅缩短。希望帮到你。本文还有配套的精品资源点击获取