游泳者溺水检测数据集VOC+YOLO格式解析与YOLO训练实战指南
简介面向游泳场景安防监控与智能救生研究的溺水检测数据集对应4599张游泳者图像样本覆盖溺水drowning与游泳swimming两个目标类别由人工使用labelImg画矩形框完成标注框数合计6017个其中溺水2578框、游泳3439框。数据同时提供Pascal VOC与YOLO两种格式可直接接入YOLO系列、SSD等主流目标检测框架训练与评估省去格式转换环节。资源包共2000个文件以xml标注文件为主并含yolo格式txt标注与使用必读说明压缩后约156.75MB。目前已有521人学习。数据集仅保证标注准确合理不承诺模型精度适合作为计算机视觉课程设计、毕业设计或水域安全检测课题的基础数据下载后可用labelImg查验标注自行划分训练集与验证集并复用主流框架的VOC/YOLO加载流程。1. 游泳者溺水检测数据集先搞清楚你手里拿到的是什么做泳池或公开水域的智能监控最头疼的就是数据。溺水这个正样本极其稀缺网上能找到的公开数据集要么是合成图像要么标注格式五花八门labelme、JSON、TXT 混着来根本没法直接丢进 YOLO 跑。这份「游泳者溺水检测数据集 VOCYOLO 格式 4599 张 2 类别」解决的就是这个痛点4599 张真实场景图像同时给了 VOCXML和 YOLOTXT两套标注拿到手解压就能用。按我平时训练自己的数据集的习惯这类双格式数据集是最省事的——省掉了格式转换的坑也省掉了标注格式不兼容导致训练直接崩掉的风险。适合谁用做边缘端部署、需要快速验证检测效果、或者实验室做安全监控方向的都值得先拿它跑通 baseline再决定要不要继续采集标注扩充。花几分钟把数据集结构和标注内容摸透比直接开训练重要得多这也正是这篇笔记要带你走的路。2. 数据集结构与格式转换VOC 和 YOLO 双格式的目录细节2.1 VOC 格式目录长什么样拿到 4599 张图的压缩包解压后第一件事不是看图片而是看目录结构。标准 VOC 格式的骨架是Annotations/、JPEGImages/、ImageSets/Main/三个目录。Annotations里是跟图片同名的 XML 文件每个 XML 描述一张图里所有目标的类别和真实框坐标xmin、ymin、xmax、ymax坐标是像素绝对值JPEGImages放的是原始图像数据ImageSets/Main/里通常是train.txt、val.txt、test.txt三个文本文件每行一个图片文件名不带后缀用来告诉训练脚本哪些图进训练集、哪些进验证集。这里有个常见误区很多人以为 VOC 格式一定带文件夹分割实际上ImageSets/Main里的划分文件往往没有分类就是纯文件名列表。如果是自己收集的杂图划分时要注意随机性避免同一段连续视频的帧全部落在训练集或验证集那会让模型在相同场景里自嗨换一个泳池直接翻车。2.2 YOLO 格式的 TXT 标注与归一化坐标YOLO 格式每个标注文件是对应同名图片的 TXT每行表示一个目标格式是class_id center_x center_y width height。特别注意这四个坐标值全部是归一化到 [0,1] 的浮点数分母是图片宽和高不是像素绝对坐标。类别编号从 0 开始0 和 1 在这份数据集里对应什么要看classes.txt或data.yaml里的类别名定义。这是 VOC 转 YOLO 时最容易出事的位置。我遇到过不少次写转换脚本时忘记除以图片宽度和高度训练出来的 loss 直接起飞边界框全跑到图像角落。还有一个隐蔽坑如果图片尺寸不是固定的泳池监控画面宽高比多为 16:9但手机拍摄的图可能是 4:3转换时必须以每张图的width和height为准不能用一个全局统一值。2.3 双格式互通一份可靠的 VOC 转 YOLO 脚本虽说这份数据集已经提供了两套格式但实际使用时经常会按照自己的拍摄场景补充少量图片并手动标注或者想把 YOLO 格式转回 VOC 给 Detectron2 用。这里给一个 VOCT 转 YOLO 的参考实现核心思路是解析 XML 后抽坐标、做归一化、写 TXT同时顺手生成classes.txt。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, output_txt_path, class_map, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in class_map: continue # 遇到未知类别直接跳过避免脏数据 cls_id class_map[cls_name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 越界裁剪防止负数坐标或超出图像边界的框 xmin max(0, min(xmin, img_width)) xmax max(0, min(xmax, img_width)) ymin max(0, min(ymin, img_height)) ymax max(0, min(ymax, img_height)) if xmax xmin or ymax ymin: continue # 无效框丢弃 cx (xmin xmax) / 2.0 / img_width cy (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(output_txt_path, w) as f: f.write(\n.join(lines)) return len(lines) # 像素坐标必须用保底手段获取例如 PIL 读图 from PIL import Image img Image.open(JPEGImages/000001.jpg) w, h img.size class_map {swimmer: 0, drowning: 1} voc_to_yolo(Annotations/000001.xml, labels/000001.txt, class_map, w, h)这段代码的逻辑很简单遍历 XML 里所有object节点抽取类别名和 bndbox 四个角点用图片真实宽高做归一化最后拼成 YOLO 格式的一行。几个关键参数要解释一下class_map决定了类别名到整数 ID 的映射它必须与后续data.yaml里的类别顺序完全一致img_width和img_height必须来自图片本身不能猜越界裁剪和无效框过滤能避免标注噪声毒害训练。如果你拿到的数据集里图片旋转过EXIF 方向信息那width和height会被反掉转出来的框全错解决办法是先用img.thumbnail或transpose把图像方向固定下来再读尺寸。2.4 .7z 解压与文件完整性检查这份资源的封装格式是 .7z压缩率高但不像 zip 那样系统原生支持。Windows 用户需要 7-Zip 或 BandizipLinux 服务器上常规的unzip不认这个格式用7z命令。解压后建议用sha256sum校验一下文件完整性防止传输过程中损坏。接下来确认 train/val 划分是给定的还是需要自己手动切。很多公开发行数据集会把ImageSets/Main/里的划分也直接给全那就直接用如果目录里没有划分文件就需要按 8:2 或 9:1 比例自己随机分。分割时务必带上随机种子代码里写死random.seed(42)或np.random.seed(42)否则每次跑出来的验证集都不一样对比实验结果没有意义。3. 游泳者与溺水者两个类别的设计逻辑与标注质量排查3.1 为什么只有 2 个类别反而更难做这份数据集只有 2 个类别直观理解是「游泳者」和「溺水者」二分类检测但实际上这个设计比多类别任务更考验标注一致性。游泳者的形态变化极大自由泳、蛙泳、仰泳、踩水、潜水、站在浅水区这些全部属于「非溺水」而溺水者的典型特征则表现在头面部长时间没入水中、肢体动作幅度异常小、身体姿态垂直漂浮。两者交叠区域非常大——一个人静止踩水不动的瞬间单帧画面上跟溺水几乎无法区分。所以标注时单帧图像里的「溺水」到底怎么定义直接决定了模型上限。我见过的公开数据集在标注规范上一般会区分「正在溺水」和「已经溺水」前者还有挣扎动作后者是完全静止。这份数据集的 2 类别很可能是swimmer和drowning但你在解锁后要立刻打开一个 XML 或 TXT 看看注释确认类别名称和分布比例。如果正负样本比例悬殊比如 drowning 只有 200 张swimmer 有 4399 张训练时就要考虑样本均衡或修改损失函数权重否则模型会学成「永远输出 swimmer」的躺平形态。3.2 可视化标注自检正确性的最小代码不管是官方给的标注还是自己补充的标注在训练前把所有标注框画到图上做一轮人工抽检是非常必要的。这里给一个轻量级可视化脚本用 OpenCV 直接把 YOLO 格式的 TXT 转画出来。import cv2 import os def draw_yolo_boxes(img_path, label_path, class_names, out_path): img cv2.imread(img_path) h, w img.shape[:2] if not os.path.exists(label_path): return with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) ! 5: continue # 格式异常的行跳过打印日志单独排查 cls_id, cx, cy, bw, bh parts cls_id int(cls_id) cx, cy, bw, bh float(cx), float(cy), float(bw), float(bh) xmin int((cx - bw / 2) * w) ymin int((cy - bh / 2) * h) xmax int((cx bw / 2) * w) ymax int((cy bh / 2) * h) color (0, 0, 255) if cls_id 1 else (0, 255, 0) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color, 2) cv2.putText(img, class_names[cls_id], (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(out_path, img) class_names [swimmer, drowning] draw_yolo_boxes(images/train/001.jpg, labels/train/001.txt, class_names, check/001.jpg)这个脚本没有做什么高级操作但能暴露两类典型问题一类是标注框完全错位框的中心点漂到身体外面另一类是尺寸异常比如框高度远大于人体把水花和倒影也框进去了。抽检时每类至少看 100 张发现同一错位模式的图超过 5%说明标注流程有系统性偏差不能直接训练先把标注修正干净。3.3 标注边界泳池环境的隐性干扰泳池场景有自己的干扰源水面反光会形成高亮区域水波纹会产生大量纹理泳池底的标线与泳道线类似条形码运动员头部有泳帽和泳镜这些都可能让模型把「非目标」误检成目标。我倾向于在这些情况里把标注框紧贴人体不要框住飞溅的水花和倒影保持统一标准。同时注意遮挡情况半身在水下、身体被浮标挡住、多个人体重叠时标注策略要提前定好。常见做法是「能明确辨认为该类别就标被遮挡超过 50% 就不标」而不是硬着头皮给每个模糊目标都画框模糊框只会把模型的特征学乱。4. 用 YOLO 训练自己的数据集从 data.yaml 到训练参数的配置路径4.1 准备 data.yaml类别名顺序必须与标注编号对齐拿到 4599 张图和两套标注之后要正式进入训练环节。先说明一点YOLO 系列从 YOLOv5 到 YOLOv8、YOLOv9 以及最新的 YOLO11都在同一套格式上运作只是个别细节上大版本有差异。最常见的落地路径是先用 YOLOv8 跑通再换 YOLO11 或改进版本。data.yaml 是数据集的地图告诉训练器标签文件路径和类别名。# data.yaml 文件内容 path: /home/user/drowning_dataset # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 nc: 2 # 类别数量 names: [swimmer, drowning] # 类别名列表顺序必须与TXT里编号一致参数说明path是绝对路径或相对于当前执行目录的路径建议使用绝对路径避免在不同机器间迁移时路径错乱train和val既可以是目录也可以是 txt 文件路径目录形式更省事txt 文件形式适合做精细控制nc必须与 labels 里出现的最大类别 ID 1 相等如果你训练时报class id out of range先查这里names列表里第 0 个名字对应 TXT 里编号 0 的类别顺序错了模型推理时类别名就是错的。4.2 训练参数怎么调一批能跑出效果但不至于过拟合的初始值泳池溺水检测不是那种刷榜单的竞赛任务更接近边缘场景下的可靠人员检测。我的经验是不要一开始就上大分辨率大 batch先用常规参数跑通验证集链路再回来调。一组稳健的起点参数如下yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ patience40 \ augmentTrue \ seed42逐个说参数modelyolov8s.pt表示使用 yolov8s 的预训练权重做迁移学习这比从头训练收敛快得多也是 yolo 预训练模型下载的标准用法epochs200对这个数据量级4599 张偏多但配合patience40连续 40 个 epoch 验证集指标未提升就早停就不会白跑通常实际训练 80120 个 epoch 就稳定了imgsz640是默认推理/训练分辨率如果你的图片里人形偏小远距离俯拍泳池可以改到imgsz960代价是显存占用大幅上升batch16在 16GB 显存上比较稳显存不够就降到 8。lr00.01是从预训练权重继续学时的初始学习率改动范围在 0.0010.02 之间。4.3 YOLOv5 老用户怎么迁移cfg 与数据集的衔接如果你还在用 YOLOv5 的代码库训练需要把数据准备成 YOLOv5 的标准布局即images/train、images/val、labels/train、labels/val四个目录。YOLOv5 的 data yaml 采用train:和val:指向 txt 文件列表的写法也可以用目录写法。区别在于 YOLOv5 默认要求 labels 和 images 的目录层级一一对应比如datasets/drowning/images/train/001.jpg对应标注必须在datasets/drowning/labels/train/001.txt放错位置训练时会报image 1/1 ... WARNING: no labels found这个提示就是告诉你标注路径没对上。排查时先看datasets/drowning目录结构是否符合上面的对应关系再看labels/train下文件数与images/train是否一致通常问题出在解压时目录嵌套多了一层。4.4 迁移学习还是从头训练正样本量决定策略4599 张图的规模不算大正样本假设是 drowning如果只有几百张直接拿 COCO 预训练权重迁移是最稳的选择。要搞清楚一个原则COCO 预训练权重里虽然没有「溺水」这个类别但模型已经学到了通用的低层特征边缘、纹理、形状构成这些特征对识别泳池里的人体非常有用。我通常的做法是冻结主干前 10 层或用freeze10参数前 50 个 epoch 只学检测头后面再解冻主干做全局微调。有项目组成员习惯一上来就解冻全部层结果 BN 层的统计量被小数据集带动剧烈波动出现 yolo 训练中 bn 崩溃的险情。稳妥起见先冻结主干跑不要一上来就追求「全参数微调」的极致。5. 训练避坑溺水场景下最容易翻车的 5 个环节5.1 标签类别错位看起来正常但精度永远上不去现象训练 loss 正常下降验证集 mAP 却停滞在低水平画出来的框位置对但类别张冠李戴drowning 被输出成 swimmer。原因data.yaml 的names顺序与标注 TXT 的类别编号不一致。比如标注文件里 0 是 drowning1 是 swimmer但 yaml 里写names: [swimmer, drowning]模型学到的 0 类语义就反了。这种现象在验证集上很难一眼发现因为指标是 mAP类别错位时 AP 仍可能比较高。解决训练前写一个脚本批量统计 labels 里所有出现的类别 ID 和数量然后与 data.yaml 核对。给一个快速统计代码思路遍历所有 txtset收集类别 ID打印{0: 3850, 1: 749}这样的分布直接跟 yaml 比对。5.2 输 NaN 或 loss 爆炸学习率与 BN 的博弈现象训练打印的 loss 从第 3 个 epoch 开始变成nan或者一轮内 loss 从 0.05 跳到 15 再跳回 0.01。原因最常见的有三种。一是初始学习率太大模型参数一步跨到深谷边缘二是梯度累积或 batch 太小导致 BN 层统计量不稳定三是标签里存在极端长宽比的框比如宽 0.9 高 0.05 的长条误标注让回归分支计算出的梯度异常。解决先把学习率降到 0.001 跑 20 个 epoch 验证能否正常收敛再决定要不要回升batch 至少保证 16不够时用accumulate2做梯度累积检查标注框的最小宽度和最小高度把小于 2 像素的框过滤掉。如果用了预训练权重且主干解冻太早就冻结主干层只训练检测头BN 统计量稳住后再解冻这也是防止 BN 崩溃的经验性手段。5.3 验证集会骗人同一个泳池场景导致评估虚高现象训练时验证集 mAP 高达 0.9但部署到另一个陌生泳池时漏检率直线上涨甚至在同一个泳池换一个视角就不工作。原因数据划分之前没有按照「场景」去分。比如数据集中 60% 的图像来自同一个摄像头同一个时间段随机划分后这部分图的相同背景画面同时进入训练集和验证集模型学到的是「这个泳池的背景 人员位置模板」而不是「人体形态的通用特征」。验证集 mAP 虚高的本质是数据泄漏。解决按视频片段或拍摄场景做分层划分即同一场景的连续帧只能全部落在训练集或验证集里不能穿越。实际操作时可以先看图片文件名有没有场景 ID比如poolA_0001.jpg、poolB_0001.jpg有就用文件名前缀做 GroupShuffleSplit没有就自己按哈希或拍摄时间聚簇。这一步偷懒后面部署一定会还回来的。5.4 正样本不够溺水者漏检与「安全优先」的设计取舍现象验证集上 swimmer 的 AP 0.95 而 drowning 只有 0.4查看混淆矩阵发现大量 drowning 被预测成了 swimmer。这属于「阴性偏好」模型永远输出占多数的类别。原因正负样本比例悬殊且模型在特征空间里没学到溺水者区别于游泳者的判别性特征——尤其是「静止」和「异常姿态」这类定义模糊的状态。解决先统计类别分布如果 drowning 不足 swimmer 的 1/5采用两种手段并行一是对 drowning 类做离线增强随机擦除、HSV 扰动、水平翻转、小角度旋转把正样本数量扩到与负样本接近二是在训练时配置类别权重YOLOv8 里通过cls损失系数放大或修改 loss 的class weights增强少样本类别。需要明确的是溺水检测系统的目标不是把 drowning 的 AP 刷到极致而是保证「真正的溺水不能被漏掉」哪怕牺牲一些 swimmer 的误报率。所以调参时优先关注recall指标而不是只看 mAP。5.5 误检高发区泳池边的救生员与跳台人员现象模型把站在池边穿制服的救生员识别为 swimmer或者把跳台上静止站立的人识别成 drowning。原因溺水者特征在某些帧与静止站立或漂浮状态相似且救生员和游泳者都穿着相似的衣服颜色与环境对比度低。解决这属于数据覆盖问题标注阶段就要把池边站立、行走、救生员、跳台上的人全部加进 swimmer 类别让模型知道「非水面的人不是溺水者」。如果你的数据里没有这些场景就用公开的泳池人员数据或自己补拍 100200 张以池边为主的图快而有效。另外一个技巧是训练完成后跑一次批量预测把所有「置信度在 0.40.7 之间」的预测框单独导出来排布成网格图一眼就能找出高误检区域这种抽检方法比盯着 PR 曲线有用得多。6. 从验证到落地混淆矩阵、时序后处理与自建小样本扩展模型训练到收敛后先不要急着部署拿最终的 best.pt 跑验证集重点看三个输出confusion_matrix.png、PR_curve.png、val_batch0_pred.jpg。混淆矩阵能告诉你错在哪里如果 80% 的误检来自「预测为 swimmer 但真值为 background」说明虚警集中在划船、漂浮物、水花这类背景干扰如果「预测为 drowning 但真值为 swimmer」比例高说明类别决策的特征边界还没有拉开。PR 曲线关注召回率在置信度 0.5 附近的值理想状态是曲线呈 L 形说明高置信度处仍能保持高召回。一种在溺水检测里特别有效的后处理是时序确认溺水是一个过程不是单个瞬间。单帧画面里一个人低头换气完全可能被误判为溺水但如果模型连续 510 帧都输出同一个位置的 drowning 框且置信度稳定那才是可靠警报。实现上维护一个滑窗队列记录每个跟踪目标的类别输出序列只有连续 N 帧属于同一类别时才触发真正报警能大幅降低瞬时误报。这就是从数据集本身延伸到部署的价值4599 张图训练出来的模型解决的是单帧检测而实际系统的可靠性来自多帧决策。我的习惯是拿到这个数据集之后先随机抽 20 张图做可视化确认标注质量再花一个下午跑通 yolov8s 的完整训练链路记录第一次的 mAP 作为 baseline之后才决定要不要在自建场景上补充数据做二次迭代训练。标注层面有泳池背景差异、遮挡规则、类别歧义这三个维度需要先定标注规范再成批处理。希望这份操作路径能帮你少走一些弯路让从数据集到可部署模型的这段路走得比我自己当初那次更顺希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →