☰
YOLO溺水检测实战:339张数据集处理与训练全流程
2026/10/11 0:36:24 网站建设 项目流程

简介:这是一份面向目标检测实战的YOLO系列算法溺水数据集,共339张图像,覆盖溺水、出水、游泳等场景,适合yolov5、yolov8、yolov9、yolov10及yolo11等主流版本直接训练与验证。包体共1018个文件,每张jpg图像均配套txt格式的YOLO标签和xml格式的VOC标签,另含1个yaml配置文件;标签采用归一化坐标记录目标框位置与类别索引,两种格式分目录存放,方便数据格式切换。整个压缩包仅14.6MB,数据集已预先划分,能有效降低模型复现与对比实验的准备工作量。目前已有303人学习下载,尤其适合计算机视觉初学者、算法工程师及水上安全监控项目开发者。借助这份数据,可快速完成数据加载、模型微调、精度评估等环节,免去自行采集标注的繁琐流程,是验证YOLO系列检测性能的实用素材。

1. yolo算法做溺水检测,数据才是真正的门槛

yolo算法做溺水检测,最卡人的往往不是模型选型,而是数据。这套 339 张带标签的溺水数据集,把爬图和人工标注的时间一次省掉了:图像、标签、zip 打包在一起,解压就能开始做格式整理和训练。它覆盖溺水、出水、游泳三类目标,适合拿来做智慧泳池、水库监控的可行性验证,也适合学生用最小成本把 YOLO 检测全流程跑通。但要提前说明:339 张图只够验证流程,撑不起生产级精度。标签口径、类别映射、验证集划分这些坑,一个都不会少。

2. 打开 zip 先别训练:把 339 张图的「家底」摸清楚

拿到 zip 的第一反应多半是解压、配环境、直接开训,但我建议先花 15 分钟做数据体检。小数据集最大的风险不是模型训不出来,而是脏标签把整个训练带偏:图与标签对不上、两种标签格式混用、类别字符串和标题对不上号。这些问题在 339 张的量级上,靠肉眼根本看不完,用几条命令就能查清。

2.1 压缩包里到底有什么:文件清单与命名规律

先建工作目录再解压,把 zip 里的大目录结构打平,避免后面路径套路径。

mkdir -p yolo-drowning && cd yolo-drowning unzip ../yolo算法-溺水数据集-339张图像带标签-溺水出水游泳.zip # 看两层目录结构,识别 images/labels 或 JPEGImages/Annotations find . -maxdepth 2 -type d | sort # 图像与标签各自的数量,这是第一道体检 find . -name "*.jpg" -o -name "*.png" | wc -l find . -name "*.txt" -o -name "*.xml" | wc -l

unzip 之后先 find 目录而不是直接 ls,是因为压缩包里除了图像目录,往往还藏着 README 或已经划分好的 train/val 子目录,这些信息直接影响后面的训练配置。数量对不上是常态,常见做法是先把多出来的孤儿图或孤儿标签找出来单独放一边,不要急着删。命名规律通常是同名的 .jpg 和 .txt/.xml 一一对应;如果压缩包自带 train.txt/val.txt 这样的划分文件,后面切验证集能省不少事——但要先确认这份划分和实际文件对得上。

如果 find 输出的数量差距明显,可以用一个遍历脚本找出具体差集,而不是只看到一个总数。

# 找没有标签的图(假设标签后缀是 .txt) for img in $(find . -name "*.jpg"); do base="${img%.jpg}" if [ ! -f "$base.txt" ] && [ ! -f "$base.xml" ]; then echo "缺失标签: $img" fi done

文件名比对要按完整相对路径做,因为不同子目录下可能有同名文件,只比对 basename 会误判。缺失标签的图有两种处理方向:生成空 txt 文件把它当负样本,或者直接删掉,取决于你后面是想压制误报还是只想保持训练集干净。

2.2 标签是 YOLO 还是 VOC:一眼识别与统一口径

随便抽一个文件用 head 看内容首行,格式立刻见分晓。YOLO 的 txt 每行是 5 列纯数字,VOC 的 xml 就是标准 XML 结构,里面有 object/name 和 bndbox。

head -5 labels/drowning_001.txt # 5 列数字:class x_center y_center width height head -30 Annotations/drowning_001.xml # <object><name>drowning</name><bndbox>...

两种格式不能混用,这是训练前必须统一的硬前提。YOLO 坐标是相对图像宽高的 0~1 归一化值,VOC 是像素绝对坐标,同一个框用两种格式表达,数值差几十倍。混在一个数据集里,模型会同时学到两套坐标系,损失曲线直接发散。

对比项YOLO txtVOC xml
每行内容class x_center y_center width heightobject/name/bndbox 嵌套结构
坐标范围0~1 归一化像素绝对值
类别表达整数 id字符串
读取方式ultralytics 直接读需要 xml 解析

我的建议是统一转成 YOLO txt。ultralytics 训练时默认在图像同目录找同名 txt,转换一次之后不再依赖解析工具;xml 还得写解析脚本,出错时多一层黑匣子,排查成本高。

2.3 类别到底有几个:溺水/出水/游泳的标签口径

标题写了「溺水出水游泳」三个词,但标签里到底有几个类别、类别的 id 对应哪个动作,要靠统计而不是猜。这一步是后面类别映射的唯一依据。

# YOLO txt 按第一列统计类别 id 分布 cat labels/*.txt | awk '{print $1}' | sort -n | uniq -c # VOC xml 按 <name> 文本统计 grep -h "<name>" Annotations/*.xml | sed 's/.*<name>\(.*\)<\/name>.*/\1/' | sort | uniq -c

统计结果会告诉你三件事:类别总数是不是 3;如果出现 0、1、2 之外的 id,说明格式并不干净;如果 xml 里的 name 是 person、swimmer 这类字符串,就要在转换脚本里做一次字符串到 id 的映射。只拿到 id 列表还不够,最好随机抽 10 张图人工确认类别名对应关系——我见过数据包的标注把「出水」标成「游泳」,训练完混淆矩阵里这两类互相打架,调参根本调不好。

2.4 图像质量与分辨率:339 张里有多少能直接用

目标检测对图像分辨率不挑剔,但一个训练集里混着 320 的小图和 1920 的大图,YOLO 统一 resize 到 imgsz 时会产生不少失真样本。用 python 快速统计宽高分布,心里先有个底。

from PIL import Image from pathlib import Path imgs = list(Path("images").glob("*.jpg")) sizes = {} for p in imgs: with Image.open(p) as im: w, h = im.size # 按 100 像素分桶,避免散点干扰判断 key = (w // 100 * 100, h // 100 * 100) sizes[key] = sizes.get(key, 0) + 1 for k, v in sorted(sizes.items()): print(f"{k[0]}x{k[1]}: {v} 张")

这段脚本把分辨率压到 100 像素的档位再统计,一眼就能看出主流分辨率。如果绝大多数是 1920x1080,训练时 imgsz 用 640 会让溺水的人形目标缩得很小;如果混着一批 320x240,imgsz 用 640 又会拉伸糊掉。合理做法是按主流分辨率定 imgsz,把离群图离线缩放到主流分辨率附近,而不是丢给 YOLO 在线 resize 硬扛。

另一个容易被忽略的是重复图。爬虫采集的数据集常有连拍或重复下载,重合度高的图在训练里被反复计入,验证集里也会虚高。先用 md5 查一遍。

md5sum images/*.jpg | awk '{print $1}' | sort | uniq -d | wc -l

到这里,格式、类别、分辨率三个底数基本摸清。这些信息是第 3 章转换脚本和第 4 章训练参数的直接输入,跳过这一章直接训练,大概率会在中间某个环节返工。

3. 把标签统一成 YOLO txt:类别映射与坐标转换脚本

不管压缩包里原本是 xml 还是乱序的 txt,训练前都要统一成「类别 id 从 0 开始、每行 5 列、坐标 0~1 归一化」的标准 txt。这一步看着简单,实际是溺水检测数据里翻车最频繁的地方:类别名拼写不一致、坐标越界、标签文件对不上。下面给出可以直接复制进项目用的转换脚本。

3.1 确定类别顺序:0 是溺水还是游泳,得先定死

类别顺序一旦写进数据集 YAML,整个训练产物的读取就依赖它。后面生成的 best.pt、混淆矩阵、每个类别的 PR 曲线,全部按这个顺序对齐,中途改动等于重训。先约定一个常见顺序,也给出每个类可操作的判断标准。

id类别判断标准
0drowning头部在水面以下或大量呛水,身体姿态失控
1emerging头部刚露出水面,手在扑腾,处于出水瞬间
2swimming正常泳姿,头部有规律地浮出换气

实际标签里可能是别的顺序,转换脚本里的 CLASS_MAP 按压缩包的真实内容改即可。关键是先定死再写代码,不要边写边改。

3.2 从 VOC xml 到 YOLO txt:坐标归一化转换脚本

import xml.etree.ElementTree as ET from pathlib import Path # 类别映射:xml 里的 <name> 文本 -> 训练 id,顺序按 3.1 节定义 CLASS_MAP = {"drowning": 0, "emerging": 1, "swimming": 2} def convert_xml(xml_path: Path, out_dir: Path): tree = ET.parse(xml_path) root = tree.getroot() # 宽高从 xml 的 size 节点取,不要重新读图, # 否则遇到 exif 旋转或批量裁剪过的图,坐标会全部错位 size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: print(f"[skip] 未映射类别: {name} in {xml_path.name}") continue cls_id = CLASS_MAP[name] box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # 左闭右开,不做 +1/-1,避免不同标注工具的口径差异 x_center = (x1 + x2) / 2 / w y_center = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_path = out_dir / (xml_path.stem + ".txt") out_path.write_text("\n".join(lines)) xml_dir = Path("Annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) for xml_file in xml_dir.glob("*.xml"): convert_xml(xml_file, out_dir)

坐标保留 6 位小数,在 640 输入下误差远小于一个像素,不需要更高精度。未映射类别先打印跳过而不是抛异常,跑完数一下 skip 的数量,就知道有多少脏标签要人工回放。x_center 用(x1 + x2) / 2 / w而不是拆开两次除法,两种写法数学等价,但前者只在最后除一次,浮点误差小一些。输出文件名用xml_path.stem,保证和 jpg 同名。

3.3 如果标签本来就是 YOLO txt:重映射与脏数据清洗

压缩包里直接给 txt 的情况下,也别以为能直接训练。常见问题包括:类别 id 从 1 开始而不是从 0 开始;坐标出现大于 1 或小于 0 的越界;某几行只有 3 列或 4 列。这些脏数据用命令行就能洗一遍。

# 把 id 从 1~3 平移到 0~2,并过滤掉越界行 awk '{ if ($1 >= 1 && $1 <= 3 && $2 >= 0 && $2 <= 1 && $3 >= 0 && $3 <= 1 && $4 >= 0 && $4 <= 1 && $5 >= 0 && $5 <= 1) { print $1-1, $2, $3, $4, $5 } }' labels_raw/*.txt > labels/clean.txt

这段 awk 对每行做「类别范围 + 坐标范围」双检查,符合条件的输出平移后的新 id,其他行静默丢弃。越界坐标宁可直接丢,也不要带进训练——归一化坐标大于 1 的框,会让损失函数在前期算出异常大的值,拖慢收敛。输出文件要单独放目录,不要覆盖原始标签,后面排查时还要回看原始数据。

命令行过滤有一个局限:它不关心同一张图是否存在同名标签文件。清洗完之后补一步数量核对。

for f in images/*.jpg; do b="${f##*/}"; b="${b%.jpg}" [ ! -f "labels/$b.txt" ] && echo "$b 缺标签" done

缺标签的图有两个处理方向:生成空 txt 文件,把这张图当作纯负样本,适合压制「无人水域误报」;直接删图,保持训练集干净。对溺水检测,我会保留一部分空背景图当负样本,后面第 4 章的 YAML 里还能用它们做验证集,一举两得。

4. 用 YOLOv8 把溺水模型跑起来:数据集 YAML 与三个关键超参

339 张图属于典型的小样本检测,模型选型和超参设置比大数据集敏感得多。用 YOLOv8n 这种轻量骨架是稳妥起点,nano 系列本身只有约 300 万参数,在小数据上不容易一开始就过拟合。下面给一份能直接跑通的 YAML 和训练命令,再教你读懂训练日志。

4.1 数据集 YAML 的写法与路径坑

ultralytics 的数据集描述文件,是训练前必须写对的东西。最常见翻车点是 path 用相对路径,然后换了一个工作目录运行,整个数据集找不到。我一般直接在 path 写绝对路径。

# dataset.yaml path: /home/you/projects/yolo-drowning # 改成你自己的绝对路径 train: images val: images # 还没切验证集时先指向训练集,只用于跑通流程 test: # 留空即可 names: 0: drowning 1: emerging 2: swimming

train 和 val 的值可以是目录名,也可以是 txt 文件路径列表;目录名相对 path 解析。val 先指回 images 只在第一轮跑通时用,原因后面第 5 章会细讲:一旦用训练集当验证集调参,验证指标就不再代表真实水平。yaml 里不要写 download 字段,小数据集没必要触发下载逻辑,省得联网失败报一堆无关错误。names 的数量必须和标签里最大的类别 id + 1 对齐,少一个都会在读取时报 label shape 错误。

提示:dataset.yaml 改完后,先跑一个 epochs=1 的训练,观察数据加载阶段的日志,确认标签被正确读取后再正式训练。这个习惯能省掉大量无效等待。

4.2 训练命令与最小参数

yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ imgsz=640 \ epochs=80 \ batch=8 \ workers=4 \ optimizer=SGD \ lr0=0.01 \ patience=10 \ project=runs/drowning \ name=exp_v1

逐参数说明:

  • model=yolov8n.pt:nano 版,339 张图用 s 或 m 会在十几个 epoch 后就开始过拟合,n 先跑通再考虑放大。
  • imgsz=640:默认值,适合大多数监控画面。如果第 2.4 节统计发现主流分辨率偏低,降成 320 省显存;溺水目标普遍很小才用 960。
  • epochs=80:小数据集建议 80 起步,配 patience=10 早停。数据集再小也不建议低于 50,否则损失还没收敛就停了。
  • batch=8:取决于显存。12G 的卡跑 yolov8n 可以到 16,8G 就乖乖用 8。
  • workers=4:数据加载进程数。Windows 上偶尔会因为 num_workers 报错,改成 2;在 AGX Orin 这类嵌入式设备上,workers 太高反而拖慢整体速度。
  • optimizer=SGD:小数据下 SGD 比 Adam 稳,Adam 前期收敛快但容易记住噪声。
  • lr0=0.01:SGD 常用起点,训练日志显示 loss 剧烈震荡时减半。

这组参数不是最优,而是「不会翻车」的起点。先跑通一次,再往目标方向调。第一次跑通后我会优先动两个地方:imgsz 和类别损失权重,而不是盲目加 epochs。

4.3 训练日志怎么看:loss 降不下去不代表数据差

训练过程的黑匣子就是每轮打印的 loss 曲线。YOLOv8 的损失函数由三个分量组成:box_loss 负责预测框和真值框的 IoU 误差,cls_loss 负责分类置信度,dfl_loss 负责框的分布建模。三个分量一起降,说明模型在正常学;只降 box 不降 cls,基本可以断定类别映射或数据不均衡有问题。

看日志也有技巧。前 20 个 epoch 内损失曲线不是直线下降而是有波动,这是正常的;但如果 30 个 epoch 后 cls_loss 仍在一个高位原地抖动,优先检查标签而不是加大学习率。小数据集上最隐蔽的问题是模型把背景错当成目标又没在损失里体现出来——这时要结合验证集 PR 曲线看,而不是只看训练 loss。遇到 loss 数值异常偏大,先把 imgsz 调到和图像分辨率接近再试,在线 resize 太狠会把损失曲线带歪。

5. 溺水数据集训练的 5 个常见坑与排查办法

小数据集训练遇到的问题,七成和模型无关,而是数据与配置的「脏」。按踩坑频率排序,下面五条基本覆盖了 339 张这种量级的典型事故。

5.1 训练报错 label shape,连第一个 epoch 都进不去

现象:ultralytics 启动后打印 label 信息时报错,提示 label shape 不是 (N, 5)。

原因:标签列数不对,或者类别 id 超过 names 数量。常见于 xml 转换脚本漏了归一化、原始 txt 里混了 4 列数据。

解决:先用统计命令定位坏文件。

# 查看列数分布 awk '{print NF}' labels/*.txt | sort | uniq -c # 查看最大类别 id awk '{print $1}' labels/*.txt | sort -n | tail -1

看到有文件列数不是 5,用 grep 找出具体文件名回到转换脚本重新处理;id 最大值大于等于 names 数量,说明类别映射没做干净。这类问题排查从不超五分钟,因为统计命令把可疑范围缩得很小。

5.2 mAP 虚高:验证集和训练集来自同一批连续帧

现象:训练完 val 的 mAP50 能到 0.9,拿真实泳池视频一测,框乱飘,溺水的人完全检不到。

原因:339 张图里大量是同一场景的连续帧,随机切分时同一段连续帧同时进了训练集和验证集,模型等于见过答案再考试。加上 4.1 里 val 指向 images,指标虚高更严重。

解决:按「场景」而不是按「单张图」切分,同一段连续帧只能落在一侧。假设文件名里有场景编号,先用前缀分组,再对组做随机 8:2 划分。

# 提取场景前缀(假设命名是 scene_001_0001.jpg 这类),再去重 ls images/*.jpg | sed 's/.*\///; s/_[0-9]*\.jpg//' | sort -u > scenes.txt # 从场景列表里随机抽 20% 作为验证场景 shuf -n $(($(wc -l < scenes.txt) * 2 / 10)) scenes.txt > val_scenes.txt

保证验证集里出现的地点人物训练集没看过,mAP 才有参考价值。如果文件名没有场景编号,只能靠录制的连续性人工分组,麻烦但躲不掉。

5.3 类别极度不均衡:游泳框占七八成,溺水框只有个位数

现象:训练日志里 cls_loss 降得慢,验证集每个类别的 AP 差距巨大,溺水那类 AP 接近 0。

原因:339 张图的类别分布天然不均衡,溺水本身是罕见事件,标注框自然少。模型学到的最优策略是全预测成游泳,因为这样总体损失最小。

解决:先看 2.3 的统计结果,如果溺水框占比低于 10%,优先放大少数类的影响。ultralytics 的 YAML 里没有现成的类别权重字段,常见做法是自定义 loss 或在数据侧做类别重采样,我一般倾向先做离线增强:专门把溺水目标抠出来,贴到没有目标的泳池背景图上,每次贴 2~3 个目标,生成 50~100 张增强图。注意贴图时目标大小要与原图目标比例匹配,否则学到的全是异常尺度,推理时对真实尺度反而失灵。

5.4 drowning 和 emerging 互相混淆:标注口径不一致

现象:验证集的混淆矩阵里,drowning 和 emerging 这两个类别互相认错,比例高达三四成。

原因:标注阶段对「溺水」和「出水」的判断标准不统一,同一个动作有人标 0 有人标 1。

解决:回到标签源头定死规则:头部在水面以下算 drowning,头部刚露出且手在挣扎算 emerging。标注完再抽 20 张图做一致性复核。如果项目周期短到没法复核,把两个类合并成一个 danger 类,先保证检出率,再谈细分。这种合并损失的是类别多样性,但溺水检测业务里,漏检比错分危险得多。

5.5 显存不够:batch=16 直接 CUDA OOM

现象:训练启动后几秒内报 OutOfMemory,或者显卡几乎满载导致系统卡死。

原因:imgsz 和 batch 的乘积超出显存;Jetson 之类设备上还要算上数据加载进程占的内存。

解决:换小 batch 或让 ultralytics 自动探测一个可用 batch。

# 自动探测可用 batch yolo detect train data=dataset.yaml model=yolov8n.pt imgsz=640 batch=-1
显存推荐 batch备注
8G8不推荐开 mosaic 之外的增强
12G163060/4070 级别常见
24G32可以换 yolov8s 试

在 AGX Orin 这类嵌入式设备上,除了调小 batch,还要把 workers 降到 2,不然 CPU 数据加载会成为瓶颈。更省显存的做法是 imgsz 降到 480,效果和 640 差别不大,训练速度却快一截。

6. 339 张图的模型怎么验收:留出法、测试视频与一个小目标增强

验证不等同于跑一次 val 命令。我的习惯是三层验收:第一层用严格切分的验证集算 mAP,第二层拿一段模型没见过的真实泳池视频做冒烟测试,第三层才回到训练集里数错检样本。

# 验证集评估,按类看 AP yolo detect val \ data=dataset.yaml \ model=runs/drowning/exp_v1/weights/best.pt \ imgsz=640 \ conf=0.25

重点看每个类别的 AP,而不是总和。溺水这类少数类 AP 如果明显低于游泳,回到 5.3 做类别加权或增强。盯同一个目标:误报宁可少一个,漏检必须为零。339 张图训出来的模型,我一般不会直接上线,而是带着现场视频回到标注工具,把最容易误报的帧(水花、波光、泳镜反光)补成负样本,跑第二轮。这个循环跑两三次,比换更大的模型有效得多。

数据增强的优先级也要调整:mosaic 对小目标有奇效,但溺水的人常常只有几十像素,mosaic 会把它切碎,这时候关掉 mosaic,改成离线放大目标区域再训练,或者用 imgsz=960 保持目标分辨率。如果后续要区分泳姿,可以往 YOLO 实例分割方向走,但那是数据量翻几倍之后的事,现在这个阶段先把检测做稳。

yolo算法做溺水检测,数据的真实程度决定模型上限。我第一次拿这套数据时图省事没做类别重映射,训练没报错,但 metrics 一塌糊涂,事后查是标签 id 对错了位,血泪经验。后来老老实实把第 5 章的坑一个个过掉,溺水类 AP 才从 0.2 拉到 0.7。这份 339 张的数据集适合用来建立基准和验证流程,真正要落地,还是得往里面补自己场景的视频帧。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询