
简介这是面向目标检测开发者和游泳场所安全监控场景的数据集主要用于溺水行为识别模型的训练与验证。包内共包含2000个文件以874份xml标注文件与874份txt标签为主体并配套251张jpg图像与1个yaml配置压缩包大小约24.69MB。其中txt标签采用yolo格式xml采用voc格式分别存储于独立文件夹便于直接接入yolov5、v7、v8、v9、v10及yolo11等常见框架。数据集已提前完成训练集、验证集划分可节省自行整理标注的时间成本适合刚接触yolo系列算法的学习者上手实践也适合有溺水检测需求的项目开发者快速验证模型效果。截至目前已有302人浏览学习。1. 这份溺水检测数据集到底解决了什么做水域安全监控的工程师大概率都卡在同一个地方算法在公开数据集上跑得挺好一换到自家泳池摄像头误报多到值班员想砸屏幕。原因不复杂——溺水不是一个标准检测目标它是“人体姿态异常长时间静止运动轨迹异常”的组合状态单靠通用行人检测做不了。这份标题里的“yolo算法-游泳溺水检测数据集-874张图像带标签-溺水.zip”就是冲着这个痛点来的一张图里标好谁是正常游泳者、谁是溺水者直接用 YOLO 系列算法训练检测头让模型学着区分“正常游动的人”和“姿态不对的人”。874张图像听起来不多但对于溺水这种强姿态特征、弱语义差异的任务正好够把模型从零拉起来再迁移到真实水域。它的直接价值不是“装完就能用”而是给你一个对齐过标注口径的数据起点。适合谁用适合已经跑过 YOLO 基础训练、手头有一批自己的水域视频、但不知道从哪开始做异常姿态检测的人。如果你连 YOLO 训练流程都没搭过建议先用公开行人数据集把整套流程跑通再回来碰这份数据。2. 拆解溺水.zip图像、标签和标注口径检查拿到压缩包后第一件事不是解压后立刻开训而是把“标签到底怎么标、标了几个类、框有多准”查清楚。标注口径不明确后面所有训练和调参都是空中楼阁。2.1 先看目录结构YOLO 格式的标配长什么样常见的 YOLO 检测数据集压缩包解压后至少会有这样两层结构swim_drowning_dataset/ ├── images/ │ ├── train/ │ │ ├── img_00001.jpg │ │ ├── img_00002.jpg │ │ └── ... │ └── val/ │ ├── img_00020.jpg │ └── ... └── labels/ ├── train/ │ ├── img_00001.txt │ ├── img_00002.txt │ └── ... └── val/ ├── img_00020.txt └── ...文件名里的溺水.zip并不能说明内部结构一定如此有些打包者会把 train/val 混在一起有些会用_darknet风格把所有标签放同一个目录。所以拿到包后第一步是跑一个统计脚本先把家底盘清楚。我一般会写下面这段脚本几秒钟就能看到这个包的标注全景#!/usr/bin/env bash # 统计 images 和 labels 下的文件数检查图片和标注是否一一对应 echo 图片数量:; find images -type f | wc -l echo 标签数量:; find labels -type f | wc -l # 找出有图片没标签、或者有标签没图片的样本这些是训练时的定时炸弹 for img in $(find images -type f -name *.jpg); do base$(basename $img .jpg) if [ ! -f labels/$base.txt ]; then echo 缺标签: $img fi done这段脚本的核心逻辑是以图片文件名为基准去labels目录找同名的.txt文件。YOLO 系列严格要求图片和标签同名同前缀任何一张图缺标签训练时都会被随机跳过造成样本量缩水。如果跑完发现缺标签数量超过 20 个建议先找打包者确认不要直接进训练流程。2.2 解析标签内容一个 txt 里到底写了什么YOLO 的标注文件是纯文本每行对应一个目标框格式固定为五列数字class_id x_center y_center width height其中坐标全部归一化到 0~1 区间width和height是框的宽高占图像宽高的比例不是像素值。要注意这里x_center和y_center是框中心点的相对坐标不是左上角坐标新手最容易在这个地方换算错。用下面的 Python 脚本可以快速查看某一个标签文件的可读内容# 读取单个YOLO标签文件并解析顺便打印类别分布 from collections import Counter import glob label_files glob.glob(labels/train/*.txt) class_counter Counter() total_boxes 0 for label_path in label_files: with open(label_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {label_path} - {line.strip()}) continue class_id int(parts[0]) class_counter[class_id] 1 total_boxes 1 print(f共解析 {len(label_files)} 个标签文件{total_boxes} 个目标框) print(类别分布:, dict(class_counter))这个脚本的价值有两个一是发现格式异常行比如坐标越界、列数不对、类别 id 超出预期范围二是给出类别分布让你确定数据集到底标了几个类别。如果class_counter里只出现 0 和 1 两个类别通常意味着 0 是正常游泳者、1 是溺水者如果只有 0 一个类别那这个数据集的定位就变成了“只检测溺水者”训练时负样本全靠背景图。2.3 标注口径确认溺水与正常游泳的边界在哪打开几张图看一眼标注框重点观察三类样本正常游泳的人、挣扎中的人、已经静止漂浮的人。一个合格的数据集溺水者的标注框应该框住整个人体而不是只框头部或手臂正常游泳者的框应该同样完整不能出现一个类别的框大一个类别的框小否则模型会倾向于用框的尺寸大小来区分类别而不是用姿态特征。我遇到过最典型的口径问题打包者把所有“没有在游动的人”都标成了溺水结果换到实际泳池里站在池边休息的救生员也被误检成溺水。874 张图如果标注口径是“面部朝下漂浮”才算溺水那训练出来的模型在“面部朝上仰漂”场景下召回会很难看。建议抽出 30 张图用 OpenCV 或任何图像查看工具人工过一遍确认溺水样本里包含了多种姿态和多种拍摄角度而不是同一个视频连续截帧。3. 用 YOLOv8 训练溺水检测模型从数据划分到参数调优数据检查完毕后训练环节最核心的决策有三步划分数据集、选择模型规格、设置训练参数。874 张图属于小样本这三点一旦选错模型不是过拟合就是训练直接崩掉。3.1 按“视频来源”划分数据不是按随机比例很多人在这一步直接train_test_split按 8:2 随机切分这在溺水检测里是错误做法。溺水视频通常是一段连续录像切帧得到相邻帧之间背景、人体姿态高度相似随机划分会把同一段视频的帧同时塞进训练集和验证集验证集的指标会虚高到了真实场景直接原形毕露。正确做法是把同一来源的帧全部放进同一个集合。如果压缩包内已经有 train/val 目录先检查两个目录里是否有文件名前缀相同的帧如果数据没分目录按文件名里的序号段划分例如 0001~0700 做训练、0701~0874 做验证保证验证集完全没见过训练集的拍摄场景。这一步没有代码可抄靠的是对数据来源的判断。3.2 选择模型规格别一上来就用 YOLOv8x874 张图撑不起大模型。YOLOv8x 参数量接近 7000 万在这种数据量下必然过拟合反过来 YOLOv8n 只有约 300 万参数在小样本上表现反而稳健。我的建议是首选 YOLOv8m 或 YOLOv8s。这里有一个基本逻辑可循模型规格参数量级对 874 张图的适配度推荐场景YOLOv8n约 300 万高训练快误检略多快速跑通流程、边缘设备部署YOLOv8s约 1100 万高均衡常规项目首选YOLOv8m约 2500 万中需配合数据增强数据扩充后使用YOLOv8l/x5000 万以上低极易过拟合数据量到 5000 张以上再考虑考虑到溺水检测要部署在泳池、河道等固定机位场景我一般会先拿 YOLOv8s 跑一版看验证集 mAP 是否能过 0.5如果验证集 mAP 都能到 0.7 以上再尝试换 YOLOv8m 看是否有提升。3.3 最小可复现训练命令与参数说明使用 Ultralytics YOLOv8 训练前先写一个数据描述 YAML 文件。注意类别名称要和你统计出来的类别 id 顺序严格对应顺序错一位整个训练就白跑# drowning_dataset.yaml # 注意: class id 必须与标签文件里的数字一一对应 path: ./swim_drowning_dataset train: images/train val: images/val names: 0: swimmer 1: drowning训练命令行如下。这里不用急着追最新版本号ultralytics库的 API 一直保持兼容v8 系列的命令格式没有大的破坏性变更# 安装 ultralytics 库之后直接开训 yolo detect train \ datadrowning_dataset.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ patience30 \ device0 \ projectruns/drowning \ nameexp_with_aug参数含义逐个说清楚imgsz640是输入分辨率874 张图的分辨率如果本身就是 1080p 以上训练时缩放到 640 能显著加速如果原始图里溺水者框非常小低于 32×32 像素建议用imgsz1024重新训练一版。batch16在 874 张图的情况下够用显存不够就降到 8。patience30表示验证集指标连续 30 个 epoch 不提升就早停这是防过拟合的关键机制。epochs200看起来多但有早停机制在实际跑到 70~90 轮就会停。3.4 预训练权重怎么选用还是不用热词里“yolo预训练模型下载”经常被搜到说明很多人想直接加载 COCO 预训练权重。我的做法是首次训练用yolov8s.pt作为起点原因有三。第一COCO 里有“人”这个类别预训练模型已经学会了人体特征迁移到游泳场景只需要微调高层语义第二如果不加载预训练权重874 张图很难在有限轮数内收敛到可用状态第三COCO 预训练模型转换到二分类任务时只需要替换检测头YOLOv8 的模型结构会自动处理类别数变化。# 如果想对比: 不加载预训练权重, 从零开始训练 yolo detect train \ datadrowning_dataset.yaml \ modelyolov8s.yaml \ epochs200 \ imgsz640 \ batch16 \ device0差别只在model参数是传.pt还是.yaml。.pt是加载预训练权重继续训练.yaml是从网络结构随机初始化开始训练。小样本项目基本只跑第一种从零训练只在验证“预训练权重是否有效”时作为对比实验出现。4. 游泳溺水检测的避坑指南5 个必须提前知道的坑小样本 强姿态特征 环境千变万化这个任务的水远比想象中深。以下五条踩坑记录全部来自实际训练中的血泪经验每一条都按“现象 → 原因 → 解决”来写。4.1 白天误报暴增阳光直射水面时水面反光被识别成人影现象晴天下午的泳池画面上模型把水面波纹和池底瓷砖反光识别成溺水者误报率从早上的 5% 飙升到 40%。原因YOLO 学的是纹理特征而不是语义理解。水面波纹在阳光照射下会形成强边缘和高亮区域这些纹理模式和标签中“穿着浅色泳衣的人”高度相似。训练数据里如果缺少强反光环境下的负样本模型就会把这种纹理误判为人体。解决把晴天、阴天、室内灯光、室外自然光四种光照场景分开统计确认训练集里包含了至少两种光照下的溺水正样本。同时在预处理阶段增加“只检测画面下 2/3 区域”的约束一般泳池摄像头画面中上半部分是天空或看台不会出现溺水者直接裁剪掉可以减少大量误报。4.2 淹没后漏检溺水者已经下沉模型再也找不到目标现象测试视频中溺水者从开始呛水到完全沉入水下用时约 10 秒模型在前 5 秒能框住人一旦人彻底没入水中检测框消失后续再也没能恢复。原因数据集里的溺水样本大多是“水面漂浮姿态”比如仰面朝天或俯卧漂浮。但真实溺水过程是“挣扎 → 呛水 → 下沉”下沉后人体被水折射和波纹遮挡纹理特征完全改变模型从未见过这种形态。解决调整标注策略将“只有头部露出水面”的状态也纳入溺水类别如果数据集中没有这类样本从网上找公开的溺水救援视频截帧补标。还有一个工程师常用的办法跟踪上一帧的检测框位置如果当前帧该区域没有任何检测框就用光流法或者背景差分判断该区域是否有运动异常把下游判定移交给跟踪模块而不是单帧检测器。4.3 类别混淆集中爆发正常游泳者和溺水者都被标成同一个类别现象验证集混淆矩阵显示normal swimming 类别的样本有 30% 被预测成 drowning模型严重偏向于报警。原因标注数据里两类目标的姿态区分度不够。“狗刨式”游泳动作和“挣扎”动作在外观上非常相似尤其是镜头距离远、分辨率低的时候人眼都难分辨模型只能学到游泳者的共性特征导致swimmer的置信度永远低于drowning。解决降低误报优先级提升阈值。把conf0.25默认值调到conf0.5同时启用agnostic_nmsTrue避免两个类别的框互相抑制。最有效的做法是给swimmer类别加权重让模型在不确定时偏向输出正常游泳类别把误报控制住漏报量交给人去看。4.4 模型训练 loss 不降反升验证集 mAP 归零现象训练到第 20 个 epoch 时box_loss 和 cls_loss 突然跳动验证集 mAP 从 0.6 掉到 0之后再也无法恢复。原因学习率设置过高加上 label smoothing 开启后小样本数据集对梯度波动更敏感。Ultralytics 默认使用lr00.01初始学习率对 874 张图来说这个值偏大在训练后期容易跳过最优解。解决训练命令中加上lr00.005把初始学习率降为默认值的一半同时增大warmup_epochs到 10让模型在前 10 个 epoch 用较低学习率稳定预训练权重的特征提取层。这一条能解决小样本训练 80% 的损失函数异常问题。4.5 验证集 mAP 虚高部署后完全不能用现象训练过程中 mAP50 到了 0.85模型看起来完美。结果部署到现场摄像头后10 分钟内在空无一人的泳池上连续误报 6 次。原因验证集划分是随机的同一段视频的帧被同时分进训练集和验证集模型相当于开卷考试。这种情况在数据量小的项目里极其常见几乎每个拿到小数据集的人都会踩上一次。解决强制按视频来源划分数据。如果数据来自多个视频保证同一个视频的所有帧只出现在训练集或验证集其中一个如果一张图里有多个溺水者图像级别的划分必须包含所有标注框。做完这一步之后 mAP 会明显下降那才是模型真实水平的反映。5. 把 874 张图“变多”三类可靠的数据扩充手段874 张图做一个检测任务模型能收敛但泛化能力有限。要提升真实场景表现可以围绕这个数据集做扩充三条路按性价比排序。5.1 在线数据增强Ultralytics 自带参数不额外写代码最常见的 YOLO 训练流程里数据增强是训练时实时做的Ultralytics 默认开启 mosaic四张图拼接、随机仿射变换、HSV 扰动等增强策略。对于溺水检测这些增强并不全都适用mosaic 能显著提升小目标的检测能力但也会让水面纹理变得不真实建议保留但降低强度HSV 扰动不要开太大泳池水的颜色稳定性是这个任务的重要线索。在训练命令里用hsv_h0.015色调扰动幅度、degrees10旋转角度、scale0.3缩放范围控制增强强度。数值越大增强越激进但超过一定限度会让模型学到错误的水域颜色特征yolo detect train \ datadrowning_dataset.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ patience30 \ mosaic0.8 \ hsv_h0.015 \ hsv_s0.3 \ hsv_v0.2 \ degrees10 \ scale0.35.2 给自己的视频加标签最务实的扩充路径874 张图解决的是“从无到有”的问题真实项目还是要用自己的场景视频补充数据。常见做法是用你训练得到的第一个模型哪怕只有 0.4 mAP去跑一段新拍摄的泳池视频把模型高置信度输出的帧保存下来人工复核后直接作为训练样本追加进去。# 导出高置信度检测帧, 供人工复核后再进训练集 from ultralytics import YOLO import cv2 model YOLO(runs/drowning/exp_with_aug/weights/best.pt) cap cv2.VideoCapture(pool_camera_01.mp4) frame_id 0 saved 0 while True: ret, frame cap.read() if not ret: break frame_id 1 if frame_id % 30 ! 0: # 每秒取两帧, 避免连续帧冗余 continue results model(frame, conf0.5) if len(results[0].boxes) 0: cv2.imwrite(fcandidate_frames/frame_{frame_id:06d}.jpg, frame) saved 1 # 同时记录模型输出类别和坐标, 方便人工复核时比对 with open(fcandidate_frames/frame_{frame_id:06d}.txt, w) as f: for box in results[0].boxes: cls_id int(box.cls[0]) xywh box.xywh[0].tolist() f.write(f{cls_id} {xywh[0]/frame.shape[1]} {xywh[1]/frame.shape[0]} {xywh[2]/frame.shape[1]} {xywh[3]/frame.shape[0]}\n) cap.release() print(f导出 {saved} 帧, 请人工复核标签后再加入训练集)这段代码的核心思路是“用模型找候选用人做确认”。模型负责把可能有问题的帧挑出来人工只需要检查这些候选帧把误标和漏标修正掉再追加进原数据集重训。每跑一轮数据量就增加一次模型在真实场景下的表现也会同步提高。注意这里输出的是归一化坐标因为要直接追加到原数据集的 labels 目录格式必须保持一致。5.3 加负样本贴一张“无人泳池”标签进去溺水检测的误报一大半来自“空无一人的水域被识别成人”。解决办法是主动往训练数据里添加没有人的负样本也就是全背景帧。做法很简单从公开的泳池视频或自己拍摄的空镜中截取 200~300 帧放到一个新的images/background目录对应的标签文件写一个空文本。也就是说有图无标签即可。Ultralytics 训练时会把这张图当作没有目标的负样本让模型学会“什么都没看到时不要输出任何框”。# 空标签文件的内容: 没有任何行 # 对应图片: background_001.jpg (同样一张图, 标签文件就一个空txt)5.4 数据扩充后的重新训练策略扩充完数据后不要直接从头训练而是基于上一版权重继续训练。小样本数据集的每一轮训练都是在上一轮学习过的特征基础上增加新知识。我一般会保持modelruns/drowning/exp_with_aug/weights/best.pt把epochs降到 100因为新数据量不大模型不需要太多轮数就能收敛轮数过多反而容易遗忘原有的水域特征。6. 一个必须养成的习惯训练完先跑视频再谈效果训练指标再漂亮不如直接拿一段真实视频看模型表现。我自己的标准流程是训练完成后至少用两段视频做验证——一段包含正常游泳的人一段包含溺水模拟让会游泳的人配合演示挣扎动作分别统计误报率和漏检率。视频验证的指标与图像验证完全不同。图像只看 mAP视频要看时间连续性一个真实的溺水过程持续几十秒单帧检测结果必须在时间轴上保持一致不能出现“这一秒有检测框、下一秒消失、再下一秒又出现”的情况。我用下面这段脚本统计检测框的帧间稳定性# 统计检测框在视频连续帧中的稳定性 # 连续N帧都检测到且框位置变化小于阈值, 才认为是可靠报警 from ultralytics import YOLO import cv2 import sys model YOLO(runs/drowning/exp_with_aug/weights/best.pt) cap cv2.VideoCapture(drowning_simulate.mp4) prev_box None stable_frames 0 alarm_frames [] while True: ret, frame cap.read() if not ret: break results model(frame, conf0.5) boxes results[0].boxes if len(boxes) 0: stable_frames 0 prev_box None continue # 取置信度最高的框做稳定性判断 best_box boxes.xyxy[0].cpu().numpy() if prev_box is not None: # 计算中心点偏移量, 小于50像素视为同一目标持续存在 cx_diff abs((best_box[0]best_box[2])/2 - (prev_box[0]prev_box[2])/2) cy_diff abs((best_box[1]best_box[3])/2 - (prev_box[1]prev_box[3])/2) if cx_diff 50 and cy_diff 50: stable_frames 1 else: stable_frames 1 else: stable_frames 1 prev_box best_box # 连续15帧(约0.5秒)检测到同一目标, 触发一次报警 if stable_frames 15: alarm_frames.append(int(cap.get(cv2.CAP_PROP_POS_FRAMES))) print(f触发警报的帧序号: {alarm_frames}) print(f共触发 {len(alarm_frames)} 次警报)这个脚本给你的不是一个 mAP 数字而是一个可验收的标准如果这个溺水视频连续几十秒的溺水过程只触发了 1~2 次警报说明检测稳定性合格如果警报断断续续触发十几次说明模型在场景中的置信度波动太大部署后值班员会直接关掉警报功能。这个习惯我一直坚持每次训练完模型不管训练集 mAP 多高都必须跑一段“自己拍的真实视频”看效果。874 张图训练出来的模型本身就是一个需要不断迭代的半成品视频验证能告诉你模型在该场景下的真实短板在哪是补数据还是调阈值一目了然。希望这个流程能帮你少走一些弯路把更多时间花在真正有效的迭代上。本文还有配套的精品资源点击获取