ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

游泳者溺水数据集VOC+YOLO格式2类别895张:YOLOv8训练避坑与实战指南

游泳者溺水数据集VOC+YOLO格式2类别895张:YOLOv8训练避坑与实战指南 简介这份数据集面向计算机视觉开发者、溺水检测算法研究者及安全监控项目实践者提供游泳者与溺水两类目标的检测训练素材可用于目标检测模型训练、行为识别预研及水域安全预警系统验证。资源包共约2000个文件压缩包315.71MB包含895张jpg图片、895个VOC格式xml标注文件与895个YOLO格式txt标注文件另附少量说明文本兼容Pascal VOC与YOLO两种主流训练流程无需额外转换即可直接投入训练。标注由labelImg完成采用矩形框方式共1530个标注框其中swimmer类别1433框、drowning类别97框数据从30段视频中截取标注覆盖多种游泳与溺水场景。目前已有1886人学习下载。需要特别留意的是溺水状态在图像中较难确认建议下载后按自身任务标准重新校正标签若仅需原始视频素材也可通过资源页获取对应视频文件便于自行截帧与补充标注。1. 游泳者溺水数据集 VOCYOLO 格式 2 类别 895 张这套数据到底能训出什么泳池和公开水域的溺水识别是视觉检测里一个很别扭的细分方向。它不像行人检测那样有海量公开数据兜底也不像通用 COCO 那样随便拉个预训练权重就能跑出能看的 mAP。真正做过泳池监控项目的人都知道最难的不是模型结构而是「正样本太稀缺」——溺水是个小概率事件你不可能蹲在池边等它发生。所以当我第一次看到「游泳者溺水数据集 VOCYOLO 格式 2 类别 895 张」这个标题时第一反应不是它有多大而是它够不够撑起一个可用的二分类检测基线。这套数据的定位很明确2 个类别通常是「swimmer正常游泳者」和「drowning溺水/疑似溺水」895 张图像同时提供 VOC 的 XML 标注和 YOLO 的 txt 标注。它解决的不是「训练一个 SOTA 模型」的问题而是「让你在半天内跑通一条从数据到推理的完整链路」的问题。适合谁适合做泳池安全监控预研的算法工程师、做课程设计或毕设的学生、以及想验证溺水检测这个方向到底值不值得投入的产品侧同学。895 张不算多但二分类 迁移学习足够你判断这条路走不走得通。2. 895 张二分类数据的真实含金量先算清楚再决定投不投2.1 895 张在 YOLO 训练里到底算多还是少先把数字摊开。895 张图像2 个类别如果按 8:1:1 划分训练集约 716 张验证集约 90 张测试集约 89 张。这个规模放在通用检测任务里是「玩具级」但放在溺水这个特定场景里它的价值取决于两个变量类别平衡度和场景多样性。我一般会先做一件事——统计每个类别的实例数而不是图像数。因为一张图里可能有 5 个游泳者但 0 个溺水者也可能反过来。用下面这段脚本快速摸清底细import os import glob from collections import Counter # YOLO 格式每行 class_id x_center y_center w h label_dir labels/train class_counter Counter() img_with_drowning 0 total_imgs 0 for txt in glob.glob(os.path.join(label_dir, *.txt)): total_imgs 1 has_drown False with open(txt, r) as f: for line in f: cls int(line.strip().split()[0]) class_counter[cls] 1 if cls 1: # 假设 1 是 drowning has_drown True if has_drown: img_with_drowning 1 print(类别实例分布:, dict(class_counter)) print(f含溺水目标的图像: {img_with_drowning}/{total_imgs}) print(f溺水图像占比: {img_with_drowning/total_imgs:.2%})这段代码的逻辑很直接遍历所有 YOLO 标注文件统计每个 class_id 出现的次数同时单独记录「至少含一个溺水目标」的图像数量。参数上label_dir指向你的训练集标签目录cls 1这个判断需要根据你实际的类别映射调整——有些数据集把 drowning 放在 0swimmer 放在 1跑之前先看一眼classes.txt或data.yaml。如果统计出来溺水实例占比低于 15%那 895 张里真正有效的溺水样本可能只有一百多个实例这时候直接训 YOLO 大概率会得到一个「全预测成 swimmer」的摆烂模型。常见做法是先用游泳者类别做预训练再对溺水类别做重采样或加 focal loss。2.2 VOC 和 YOLO 两套标注为什么都要留很多人拿到数据集第一反应是「我只要 YOLO 格式VOC 删掉」。我的血泪经验是别删。VOC 的 XML 保留了图像的原始尺寸、标注框的绝对坐标、以及可能的 difficulty 字段这些在做数据清洗和可视化核查时比 YOLO 的归一化坐标好用得多。VOC XML 的结构大致是这样size里有width、height、depthobject里有name、bndboxxmin/ymin/xmax/ymax。YOLO 的 txt 则是class_id x_center y_center w h全部归一化到 0~1。两者转换的核心公式x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height反过来从 YOLO 转 VOC 时要注意浮点误差导致的框越界。我一般会在转换后做一次 clip把坐标限制在[0, 1]再乘回原图尺寸。下面是一个 VOC 转 YOLO 的脚本处理 895 张大概几秒钟import xml.etree.ElementTree as ET import os # 类别映射顺序必须和 data.yaml 里的 names 一致 class_map {swimmer: 0, drowning: 1} def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() 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: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化并 clip防止越界 xc min(max((xmin xmax) / 2 / w, 0), 1) yc min(max((ymin ymax) / 2 / h, 0), 1) bw min(max((xmax - xmin) / w, 0), 1) bh min(max((ymax - ymin) / h, 0), 1) lines.append(f{class_map[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines)) # 批量处理 for xml_file in os.listdir(annotations): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(annotations, xml_file), labels)关键参数说明class_map的键必须和 XML 里name标签的内容完全一致大小写敏感:.6f保留 6 位小数是 YOLO 官方推荐的精度太少会导致小目标框偏移clip 操作看起来多余但实际转换中因为标注工具的四舍五入越界是常事不 clip 的话训练时 dataloader 会报 warning 甚至直接跳过该框。2.3 用 YOLOv8 跑通第一版基线的最小命令数据整理好之后目录结构应该是这样的dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml的内容path: ./dataset train: images/train val: images/val test: images/test nc: 2 names: [swimmer, drowning]然后一条命令启动训练yolo detect train datadataset/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20这里每个参数都值得说清楚。modelyolov8n.pt选 nano 版本是因为 895 张数据量小大模型直接过拟合imgsz640是 YOLOv8 的默认输入尺寸如果你的原图分辨率远大于 640小目标远处溺水者会被缩得几乎看不见这时候要考虑切图或提高 imgszpatience20表示 20 轮验证指标不提升就早停小数据集上这个参数能帮你省不少时间batch16在 8G 显存上跑 640 尺寸基本安全显存不够就降到 8。训练完成后用验证集看混淆矩阵和 PR 曲线。二分类任务里我最关注的是 drowning 类的 recall——漏检一个溺水者比误报一个游泳者严重得多。如果 recall 低于 0.7先别急着调模型回去看数据。3. 从标注到训练895 张数据集的清洗与增强实操3.1 标注质量核查三种必须肉眼过一遍的脏数据895 张听起来不多但真逐张看也要一两个小时。我一般用「抽样 脚本预筛」的方式重点抓三类问题。第一类是框错类别。游泳者和溺水者在水面上的姿态有时很接近标注员疲劳时容易标反。用脚本把每个类别的框裁剪出来拼成网格图一眼就能看出异常import cv2 import os import glob import numpy as np def crop_boxes(img_path, label_path, class_id, save_dir, max_crops50): img cv2.imread(img_path) h, w img.shape[:2] os.makedirs(save_dir, exist_okTrue) count 0 with open(label_path) as f: for line in f: parts line.strip().split() if int(parts[0]) ! class_id: continue xc, yc, bw, bh map(float, parts[1:]) x1 int((xc - bw/2) * w) y1 int((yc - bh/2) * h) x2 int((xc bw/2) * w) y2 int((yc bh/2) * h) # 边界保护 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) crop img[y1:y2, x1:x2] if crop.size 0: continue cv2.imwrite(os.path.join(save_dir, f{count}.jpg), crop) count 1 if count max_crops: return # 对 drowning 类做抽样裁剪 for img_file in glob.glob(dataset/images/train/*.jpg)[:100]: base os.path.splitext(os.path.basename(img_file))[0] label_file fdataset/labels/train/{base}.txt if os.path.exists(label_file): crop_boxes(img_file, label_file, 1, check_drowning)这段代码把 drowning 类的框全部裁出来存到check_drowning目录你只需要快速翻一遍就能发现标反的、框歪的、框到水花的。参数max_crops50是防止某个类别实例太多导致输出爆炸实际核查时可以调大。第二类是空标注文件。有些图像有对应的 txt 但内容是空的YOLO 会把它当成「无目标」的负样本。如果这种空文件大量集中在溺水类别里模型会学到「溺水无目标」的灾难性关联。用find labels -name *.txt -empty一条命令就能列出来。第三类是重复图像。895 张里如果有视频抽帧产生的近重复帧训练集和验证集会泄漏验证指标虚高。用感知哈希pHash快速去重import imagehash from PIL import Image import glob hashes {} for img_path in glob.glob(dataset/images/**/*.jpg, recursiveTrue): h imagehash.phash(Image.open(img_path)) if h in hashes: print(f重复: {img_path} - {hashes[h]}) else: hashes[h] img_pathpHash 对缩放、轻微压缩不敏感适合抓视频抽帧的重复。阈值上完全相同的哈希直接判重相近的汉明距离小于 5建议人工确认。3.2 小数据集的增强策略哪些能用哪些会翻车895 张的规模增强是必须的但不是所有增强都能用。溺水检测的场景特殊性在于水面的反光、波纹、以及溺水者与游泳者的姿态差异是模型区分两类的主要线索。如果你用了会破坏这些线索的增强等于在帮倒忙。我一般会开这几类增强在 YOLOv8 里通过augment参数或自定义 dataloader 实现增强类型推荐参数理由翻车风险HSV 色调抖动h0.015, s0.7, v0.4模拟不同时段光照和水色色调抖动过大会把蓝色水面变绿失真水平翻转flipud0.0, fliplr0.5泳池左右对称安全无随机缩放scale0.5模拟远近不同的目标缩放后小目标更小注意 imgsz马赛克增强mosaic1.0提升小目标检测溺水目标被拼到边缘时可能被裁掉旋转degrees10模拟摄像头安装角度偏差大角度旋转会让水面纹理不自然混合增强mixup0.1轻微使用可提升泛化溺水与游泳者混合会产生语义混乱慎用重点说马赛克增强。YOLOv8 默认开启 mosaic1.0它把 4 张图拼成 1 张对小目标检测很有效。但在溺水场景里如果一张图里的溺水者被拼到了拼接边界标注框可能被裁切导致训练时框和实际目标对不上。我的做法是训练最后 10 个 epoch 关掉 mosaicclose_mosaic10让模型在真实分布上收尾。另一个容易翻车的是 mixup。它把两张图按透明度叠加在通用检测里能提升鲁棒性但溺水者和游泳者的视觉特征本来就接近mixup 之后模型更难区分我一般把 mixup 设到 0.1 以下或者直接关掉。3.3 类别不平衡的处理从损失函数到采样如果 2.1 的统计显示 drowning 实例远少于 swimmer训练时会有两个选择改损失函数或者改采样策略。YOLOv8 默认用 BCEWithLogitsLoss 做分类你可以通过自定义 loss 给 drowning 类加权。更简单的做法是在data.yaml同级放一个hyp.yaml调整cls和box的权重# hyp.yaml 关键项 lr0: 0.01 lrf: 0.01 cls: 1.5 # 分类损失权重调高让模型更关注分类错误 box: 7.5 # 框回归权重 dfl: 1.5但权重调太高会导致训练不稳定我一般从 1.2 开始试观察验证集上 drowning 的 recall 和 precision 变化。如果 recall 上去了但 precision 崩了说明模型在乱报这时候应该回去补数据而不是继续调权重。采样策略上YOLO 的 dataloader 不支持直接的类别平衡采样但你可以通过复制含 drowning 的图像来间接实现。注意不要简单复制而是对复制出来的图做不同的增强否则模型会记住这张图的像素。提示类别不平衡没有银弹。895 张的数据量下如果 drowning 实例少于 100 个我建议先别训检测模型而是用「游泳者检测 溺水行为分类」的两阶段方案把问题拆小。4. 训练过程避坑895 张数据最容易翻车的五个地方4.1 现象训练 loss 正常下降但 mAP 一直是 0原因最常见的是data.yaml里的names顺序和标注文件里的 class_id 对不上。比如标注里 0 是 drowning但 yaml 里写成了[swimmer, drowning]模型学出来的类别全反了验证时按错误映射算 mAP 自然接近 0。解决跑训练前先用 2.1 的统计脚本确认 class_id 分布再和data.yaml的names逐一对齐。另一个可能是验证集的标签路径写错YOLO 找不到标签时会当成全负样本mAP 也会是 0。检查val:路径下是否有对应的labels目录。4.2 现象训练到一半 loss 突然变成 NaN原因学习率太大或者某张图的标注框宽高为 0。YOLO 在计算 box loss 时如果w或h是 0会产生除零或 log(0)几轮之后梯度爆炸。解决先用脚本扫一遍所有标注文件把w 0或h 0的行删掉或修正。学习率上YOLOv8 的lr00.01对 895 张数据偏大我一般降到 0.005 甚至 0.001。如果已经出现 NaN从最近的 checkpoint 恢复并把 lr 减半。4.3 现象验证集 recall 很高但实际推理全是误报原因验证集和训练集来自同一段视频或同一场景分布太接近模型过拟合了。895 张如果全是一个泳池的监控截图模型学到的是「这个泳池的水面纹理」而不是「溺水者的姿态」。解决划分数据集时按场景或时间段分不要随机分。如果数据来源单一那就在推理时降低置信度阈值观察误报的规律——如果误报集中在某个区域可能是摄像头固定噪声加个 ROI 掩膜就能解决。4.4 现象小目标远处溺水者完全检测不到原因原图分辨率高但imgsz640把远处目标缩得只剩几个像素。YOLO 的 P3 特征图 stride 是 8640 输入下最小检测目标大约 8x8 像素再小就无能为力。解决三个方向。一是提高imgsz到 1280显存不够就减小 batch二是切图推理把原图切成 4 块分别检测再合并三是用 YOLOv8 的--augment推理选项做 TTA但对小目标提升有限。我一般先试 1280如果显存爆了再考虑切图。4.5 现象混淆矩阵里 drowning 和 swimmer 大量互混原因两类目标的视觉特征确实接近尤其是溺水初期挣扎阶段和正常游泳的动作差异很小。895 张里如果这类模糊样本占比高模型很难学出清晰边界。解决回到数据本身把模糊样本单独拎出来要么重新标注比如把「挣扎」单独设一类要么在训练时对这类样本降权。另一个技巧是引入时序信息——单帧分不清的用连续几帧的光流或姿态变化来辅助判断但这已经超出单帧检测的范畴了。注意避坑的核心不是调参而是先确认数据本身没有系统性缺陷。895 张的数据量下数据质量对结果的影响远大于模型选择。5. 把 895 张用到极致预训练权重选择与推理端技巧5.1 预训练权重怎么选COCO 还是自监督YOLOv8 官方提供的yolov8n.pt是在 COCO 上训的COCO 里有「person」类和游泳者有语义重叠迁移过来有一定帮助。但溺水这个类别 COCO 里没有所以 drowning 分支基本是从零学。我的做法是先用 COCO 预训练权重训一版看 drowning 的 recall 能到多少如果低于 0.6考虑用更大规模的人体检测数据集比如 CrowdHuman先做一次中间预训练再迁移到溺水数据上。另一个选择是自监督预训练权重比如在 ImageNet 上训的 backbone。YOLOv8 支持加载自定义 backbone 权重但需要改模型配置文件。对 895 张这个量级我倾向于直接用 COCO 权重省事且效果不差。5.2 推理端的三个实用技巧第一置信度阈值分类别设。游泳者可以设 0.5溺水者设 0.3宁可误报不可漏报。YOLOv8 的predict接口支持conf参数但它是全局的。要分类别设需要在推理后处理里按 class_id 过滤from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(test.jpg, conf0.25) # 先用低阈值拿全部框 for r in results: boxes r.boxes for box in boxes: cls int(box.cls[0]) conf float(box.conf[0]) # 分类别阈值 if cls 0 and conf 0.5: # swimmer continue if cls 1 and conf 0.3: # drowning continue # 保留这个框做后续告警逻辑 print(f类别: {cls}, 置信度: {conf:.2f}, 坐标: {box.xyxy[0].tolist()})这段代码先以低阈值 0.25 拿到所有候选框再按类别做二次过滤。参数上conf0.25是召回优先的起点实际部署时根据误报率调整。box.cls[0]和box.conf[0]取的是第一个元素因为 YOLOv8 返回的是批量结果单张图时索引 0 即可。第二用跟踪做时序确认。单帧检测到溺水就告警误报率会很高。加一个简单的 IOU 跟踪器连续 5 帧里至少 3 帧检测到 drowning 才触发告警能大幅降低水面反光、跳水动作带来的误报。ByteTrack 或 BoT-SORT 都可以YOLOv8 已经内置了model.track()接口。第三导出 ONNX 或 TensorRT 做部署。895 张训出来的模型不会太大yolov8n 的权重约 6MB导出 ONNX 后推理速度在 CPU 上也能到 10 FPS 以上。导出命令yolo export modelruns/detect/train/weights/best.pt formatonnx opset12 simplifyTrueopset12是兼容性较好的版本simplifyTrue会做图优化去掉冗余算子。导出后建议用onnxruntime跑一遍验证确认输出和 PyTorch 一致。5.3 这套数据值不值得继续投入回到最初的问题。895 张、2 类别、VOCYOLO 双格式它的价值不在于能训出多强的模型而在于给你一个「低成本验证溺水检测可行性」的起点。如果你跑完基线发现 drowning 的 recall 能到 0.7 以上说明这个方向有戏接下来该做的是补数据——从公开水域监控、泳池摄像头里采集更多真实场景把数据量推到 5000 张以上再考虑模型改进。如果基线 recall 只有 0.3先别怀疑模型回去看标注质量和场景多样性。我自己踩过的最大的坑是拿到一套数据就急着调模型结构改了半天注意力机制最后发现是验证集里有一半的溺水框标到了水花上。数据清洗花的时间永远比调参值得。希望帮到你。本文还有配套的精品资源点击获取
返回列表