尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

游泳者溺水检测数据集:VOC+YOLO双格式4599张图片

发布时间:2026/9/28 17:43:19

资讯中心
01
ARTICLE

游泳者溺水检测数据集:VOC+YOLO双格式4599张图片

游泳者溺水检测数据集:VOC+YOLO双格式4599张图片
简介这是一套面向游泳馆智能监控与溺水检测场景的目标检测数据集适合视觉算法工程师、科研人员及算法竞赛参与者进行YOLO、Faster R-CNN等模型的训练与验证。资源覆盖drowning溺水与swimming游泳两个类别共4599张游泳者图像标注框总计6017个其中drowning框2578个、swimming框3439个类别数量较为均衡。压缩包约156.75MB共2000个文件以VOC格式xml标注为主并附带使用说明txt采用7z打包。标注由labelImg绘制矩形框边界清晰规则统一提供VOC与YOLO两种格式便于直接接入不同训练框架。标注类别名称明确可直接作为检测模型的正负样本xml文件命名与图像编号有对应关系便于脚本批量切分与预处理。数据不含分割路径文件仅保留检测所需的标注信息降低使用门槛。目前已有521人学习/下载适合需要标准化溺水检测数据以快速搭建训练流程的开发者和研究者使用。1. 游泳者溺水检测数据集VOCYOLO双格式4599张先看清这2类6017个框做溺水检测的人最头疼的不是模型选型而是找不到一张带准确框的游泳者数据。网上公开的水上数据集要么是监控截图、框得乱七八糟要么只有分类标签没坐标跑YOLO这类目标检测模型等于没饭吃。这份资源是4599张真实游泳场景图片带完整的Pascal VOC xml和YOLO txt双格式标注类别就两类drowning溺水和swimming正常游泳总计6017个目标框。它能直接喂给YOLOv5/v8训练省掉自己爬图、画框、转格式的三天工作量。适合正在做泳池/水域监控、安全预警、姿态异常检测的算法工程师和学生。拿到手先别急着训练把格式、类别分布和图源特点摸清楚后面能少踩一半坑。2. 数据集构成与标注规则4599张图、2类别、6017个框先读懂文件再动手2.1 文件清单xml/txt/jpg一一对应firc_swim编号规律解压后能看到一个使用前必读.txt和一堆以firc_swim_为前缀的文件比如firc_swim_316.xml、firc_swim_260.jpg、firc_swim_260.txt。每个图片文件都对应一个同名xml和一个同名txt编号从两位数到四位数都有不是连续的比如316、50、453、260、3131、235、166、3129、3130。这种命名方式说明原始数据是分批次采集或筛选的删掉过模糊、重复、无目标的图所以编号有跳跃完全正常不影响训练。这里要注意一个细节标注数量是4599个xml、4599个txt、4599个jpg三者完全对齐没有出现“有图无标注”或“有标注无图”的情况。这是这个数据集做得比较干净的地方。用labelImg画框的时候只要点Save就会自动生成同名xml而yolo格式的txt通常是由转换脚本从xml生成的所以数量天然一致。你可以用下面这段代码快速校验文件是否一一对齐import os from pathlib import Path img_dir Path(images) # 假设jpg放在images目录 xml_dir Path(xml) # 假设xml放在xml目录 txt_dir Path(txt) # 假设txt放在txt目录 imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} txts {p.stem for p in txt_dir.glob(*.txt)} print(fjpg: {len(imgs)}, xml: {len(xmls)}, txt: {len(txts)}) print(图片缺少xml:, len(imgs - xmls)) print(图片缺少txt:, len(imgs - txts)) print(多余xml:, len(xmls - imgs)) print(多余txt:, len(txts - imgs))逻辑很简单三个集合取差集只要差集为空就说明文件一一对应。参数说明glob(*.jpg)只匹配当前目录下的jpg如果数据是子目录结构需要改成recursive遍历。我一般会把jpg、xml、txt分开放到三个子目录方便后续用脚本统一划分train/val。2.2 标注统计与类别平衡drowning 2578 vs swimming 3439注意稀疏摘要里写得很清楚drowning框数2578swimming框数3439总框数6017。也就是说平均每张图有1.3个目标大部分图里只有一个人在水里。这跟实际监控场景一致——泳池摄像头拍到的通常就是零星几个人不会像COCO那样一张图一堆目标。但类别不平衡是真实存在的swimming比drowning多861个框比例约1.33:1。对于二分类检测任务这个不平衡不算极端YOLO默认的BCEWithLogitsLoss本身能扛。但如果你只关注溺水报警会发现模型天然偏向预测swimming因为负样本正常游泳更多把正常游泳判成溺水的代价在损失函数里没有额外惩罚。所以后面训练时要特别看drowning类的recall必要时候给drowning类加权重。另外注意框数不等于人数。一个人在一张图里可能只标一个框但这个人在不同帧里会出现多次所以2578个drowning框不代表有2578个溺水者这是视频抽帧数据集的正常形态。做评估时不要按“人数”理解要按“目标实例”理解。2.3 标注工具与规则labelImg矩形框贴合目标边界使用必读里写了标注工具是labelImg标注规则是“对类别进行画矩形框”。这是最常规的目标检测标注方式没有分割多边形也没有旋转框。所以这个数据集适合YOLO系列、SSD、Faster R-CNN这类基于水平矩形框的检测器不适合需要旋转框的文本检测或者实例分割任务虽然有xml但xml里只有bndbox没有polygon或mask。从firc_swim_316.xml这类文件里能看到典型的VOC结构annotation folderfirc_swim/folder filenamefirc_swim_316.jpg/filename size width1920/width height1080/height depth3/depth /size object namedrowning/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin812/xmin ymin455/ymin xmax1023/xmax ymax851/ymax /bndbox /object /annotation注意这个field的宽高是1920x1080属于1080p监控画面。但也有可能其他图是1280x720或者更小训练时要统一做letterbox resize不要直接拉伸否则会改变目标的长宽比影响溺水姿态的判断。标注规则的关键点labelImg里用的“drowning”和“swimming”是英文文件夹命名但实际标注的语义需要你自己理解。从日常经验看“drowning”一般指头部没入水面、手臂乱挥、身体竖直下沉这类姿态“swimming”指自由泳、蛙泳、踩水等正常活动。但标注本身是人画的一定存在主观性。拿到数据后最好抽样看几十张图确认一下框是否紧贴身体还是包住整个人连带一圈水花。如果框偏大训练出来的预测框IoU阈值要相应放宽如果框偏紧NMS的阈值可以适当调高。3. VOC转YOLO的格式对照xml的object信息如何映射到txt的class cx cy w h3.1 VOC xml解析关键字段filename、size、objectVOC格式的核心是xml里嵌套的object节点每个object代表一个标注目标。你需要关注三个信息size图像宽高、object.name类别名、object.bndboxxmin/ymin/xmax/ymax。filename字段只用于校验不能作为读取图像的绝对路径因为xml里存的是文件名不是路径。这里有个容易翻车的细节bndbox坐标是整数像素值单位是像素不是归一化坐标。YOLO训练时要求坐标归一化到0~1之间而且类别索引从0开始。所以drowning和swimming在yolo txt里对应的是0和1不是“drowning1, swimming2”。这个顺序取决于你写的class.txt或yaml里的names列表而不是xml里的顺序。3.2 YOLO txt归一化坐标计算从xmin/ymin/xmax/ymax到cx,cy,w,hYOLO格式的txt每一行是class cx cy w h。cx、cy是目标中心点的归一化坐标w、h是目标宽高的归一化值。计算公式是cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height用Python实现批量转换非常直接import xml.etree.ElementTree as ET from pathlib import Path def convert_xml_to_yolo(xml_path, classes): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue # 跳过未定义类别 cls_id classes.index(name) 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) # 坐标裁剪防止越界 xmin max(0, min(xmin, width)) xmax max(0, min(xmax, width)) ymin max(0, min(ymin, height)) ymax max(0, min(ymax, height)) cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines classes [drowning, swimming] xml_file Path(firc_swim_316.xml) with open(firc_swim_316.txt, w) as f: f.write(\n.join(convert_xml_to_yolo(xml_file, classes)))参数说明classes列表的顺序决定了txt里的类别索引必须和后续训练yaml里的names保持一致。坐标裁剪那段很关键因为有些标注框可能稍微超出图像边界不裁剪会导致归一化坐标大于1训练时YOLO会报错或导致loss变成nan。这里保留6位小数精度足够。3.3 数据划分train/val/test比例建议与随机种子4599张图不算大建议按8:1:1划分train/val/test也就是约3680张训练、460张验证、459张测试。划分时一定要按“摄像头/视频片段”分不能纯随机打乱。因为同一段视频的连续帧高度相似如果不做场景级别的划分验证集会混入训练集的近邻帧mAP会虚高。我一般会先按文件名前缀或者采集时间分组再把组划分到不同集合。但这份资源的文件名是firc_swim_编号编号本身看起来是乱的没有明显的时间或场景分组信息。稳妥的做法是先用随机种子打乱整个文件列表再做划分同时检查验证集里有没有和训练集重复编号的图片——这个数据集编号不连续基本可以排除完全重复。# 在数据集根目录执行 find . -name *.jpg | sort all_images.txt shuf --random-source(yes 42) all_images.txt shuffled.txt total$(wc -l shuffled.txt) train_n$((total * 8 / 10)) val_n$((total - train_n - total / 10)) head -n $train_n shuffled.txt train.txt tail -n $((train_n 1)) shuffled.txt | head -n $val_n val.txt tail -n $((train_n val_n 1)) shuffled.txt test.txt参数说明shuf的--random-source用固定种子42保证可复现train_n取整后val和test按比例分配。注意这里只是生成了图片清单实际训练时需要根据清单把图片和对应xml/txt拷贝到对应目录或者写一个data.yaml用通配符。如果后续要跑YOLOv5直接按yolo要求的images和labels目录结构组织最省事。4. 训练配置与参数设定用YOLOv5/v8跑通这个数据集的要点4.1 数据集yaml文件写法path、train、val、nc、names拿到VOCYOLO双格式后最关键的一步是把数据组织成YOLO训练目录结构然后写data.yaml。YOLOv5和YOLOv8的data.yaml格式基本一致区别在于path字段的处理。以下是一个可直接用的配置# swim_data.yaml path: /your/path/firc_swim_dataset # 数据集根目录 train: images/train val: images/val test: images/test nc: 2 names: [drowning, swimming]这里的目录结构要求是根目录下images/train、images/val、images/test以及labels/train、labels/val、labels/test。每张图的txt和jpg同名放在对应的labels和images子目录里。参数说明names的顺序必须和txt里类别索引一致也就是说txt里0对应drowning1对应swimming。如果names写反了训练不会报错但预测结果会完全颠倒这是最隐蔽的坑。如果你的训练脚本是YOLOv8path字段可以写成绝对路径train和val也可以直接写txt文件的路径列表比如train: /path/train.txt每行一个图片路径。YOLOv5也支持这种方式。4.2 超参选择img640、batch、epochs以及针对小目标的建议这个数据集原始分辨率是1920x1080但游泳者在画面中占比不一定大如果人是远景目标可能只有几十乘几十像素。训练时img参数建议用640不要盲目用320去提速——分辨率太低会把小目标直接抹掉。如果显存够用img736或img1280会更好但训练时长会成倍增加。batch size根据显卡显存调整RTX 3090 24G可以跑batch16img640V100 32G可以跑batch32。epochs建议从100起步配合早停。由于数据量只有4599张模型很容易过拟合所以推荐在yaml里开启mosaic、mixup等增强但训练最后20个epoch关闭mosaic让模型适应真实分布。具体训练命令YOLOv8yolo detect train \ modelyolov8s.pt \ dataswim_data.yaml \ imgsz640 \ batch16 \ epochs100 \ patience20 \ device0 \ save_dirruns/swim_yolov8s参数说明model用yolov8s.pt作为预训练权重比从头训练收敛快很多patience20表示验证集mAP连续20个epoch不提升就早停save_dir指定输出目录方便后面找best.pt和last.pt。如果你想更稳一点可以用yolov5s.pt两者的数据加载方式和增强策略略有差别但对这个数据集的结论大体一致。4.3 训练后验证mAP、混淆矩阵、PR曲线解读训练完别只看loss下降要看验证集的mAP0.5和mAP0.5:0.95。对于溺水检测这个场景mAP0.5更实际因为安全监控不要求框特别精确只要报警框能锁定目标位置就行。打开runs/swim_yolov8s/confusion_matrix.png会看到一个2x2矩阵加背景类。重点关注这三项drowing被错分成swimming的比例这代表溺水漏报是最危险的错误。swimming被错分成drowning的比例这代表误报会频繁触发假警报。背景被识别成目标的比例误检可能是水花、浮标等。打开PR曲线看drowning类的曲线面积。如果recall到0.8就急剧下降说明某些溺水姿态没被充分学习。这时不要再盲目加epochs先看是不是数据里有难例或者类别权重不够。from ultralytics import YOLO model YOLO(runs/swim_yolov8s/weights/best.pt) results model.val(dataswim_data.yaml, imgsz640, batch1) print(results.results_dict)参数说明results_dict里包含mAP相关指标。如果val时发现drowning的recall明显低于swimming建议训练时给损失函数加类别权重。YOLOv8没有直接的class_weight参数但可以通过修改数据集的label分布来缓解不平衡比如对drowning类别做上下翻转、左右翻转、随机旋转等增强增加它的样本量。5. 避坑指南标注一致性、格式陷阱与训练崩坏排查5.1 现象训练时报错 “class index out of range”原因txt里出现了大于等于nc的数字或者类别名和yaml里的names对不上。这个数据集标注类别只有drowning和swimming按理不应出错但如果你用脚本转换时把classes顺序写错了比如用了[swimming, drowning]那么原xml里drowning的类别索引是1但你的txt里存的可能是0此时模型训练的语义就反了。解决写一个脚本扫描所有txt打印出现的类别索引集合cat labels/train/*.txt | awk {print $1} | sort -u正常应该只输出0和1。如果出现2或更大说明转换时混入了没定义的类或者txt文件是从别处拷贝错了。再用同样的方式扫描val和test。每年我都因为这个翻车所以现在训练前必跑这条命令。5.2 现象训练前检查发现txt里有坐标大于1或小于0原因转换脚本没做裁剪或者标注框本身越界。xml里的bndbox如果标注时手滑拖出了图像边界就会出现xmax大于width的情况。虽然YOLOv5/v8的加载器会自动clip但你会发现训练早期loss异常高或者预测框明显偏移。解决用第3.2节的裁剪逻辑转换时强制把坐标clip到[0, width]和[0, height]之间。写一个批处理脚本遍历所有xml重新生成txt覆盖不合法的行。血的教训坐标系里的一个0.000001误差在1920分辨率下就是2个像素虽然不影响大局但强迫症必须清理干净。5.3 现象xml和jpg文件名不匹配训练时图片加载不到label原因解压后文件名编号有跳号如果你用某些数据划分工具按序号自动重命名就会把firc_swim_316和firc_swim_50这种编号错配。解决始终以jpg文件名为基准xml和txt必须和jpg同名。批量重命名时用下面的脚本先备份再动手cp -r firc_swim_dataset firc_swim_backup find firc_swim_dataset -name *.xml | while read f; do base${f%.xml} if [ ! -f ${base}.jpg ]; then echo Missing jpg for $base fi done5.4 现象验证集mAP正常但测试集上漏检远处的小目标原因分辨率与训练尺寸不匹配。1920x1080的图像缩放到640x640相当于把整个画面压扁了一个100x100像素的游泳者变成33x33特征几乎消失。解决训练时用imgsz1280或者在推理时使用TTATest-Time Augmentation。YOLOv8推理命令加augmentTrue可以多尺度投票能提升小目标召回。如果显存不够可以在预处理时把图像切成四块分别推理再合并结果。这个方法对泳池监控特别有效因为溺水者可能是远景小目标。5.5 现象drowning类的recall低swimming类的precision也不稳定原因样本不平衡标注主观性。两类之间的视觉差异有时不明显——一个正在游自由泳的人手臂动作和溺水挣扎动作有相似帧踩水姿态更接近垂直状态容易被标成drowning或swimming。解决不要迷信评价指标。训练完抽样看预测结果把模型预测错的图单独挑出来人眼复核标注框是否合理。如果标注本身模糊可以考虑只保留置信度高于0.5的样本用于训练或者用投票机制同一目标在连续帧中至少有3帧被判定为drowning才触发报警。这个逻辑属于业务后处理不是模型能解决的。6. 进阶数据增强与难例挖掘把溺水检测的漏报率再压一截6.1 针对小目标的马赛克与复制粘贴增强默认的mosaic增强已经能提高泛化性但对小目标增强有限。经典的SAHISlicing Aided Hyper Inference思路是把大图切成小块推理能直接提升小目标召回率。训练侧我倾向在数据加载时做随机裁剪增强随机从原图裁剪一个区域缩放到640x640再和原图混合训练等效于让模型看到更近的视角。# 伪代码训练时动态添加裁剪增强 from PIL import Image def random_crop_and_resize(img, bboxes, crop_ratio(0.3, 0.7)): w, h img.size cw int(w * random.uniform(*crop_ratio)) ch int(h * random.uniform(*crop_ratio)) x random.randint(0, w - cw) y random.randint(0, h - ch) crop img.crop((x, y, x cw, y ch)).resize((640, 640)) # 重新计算bbox坐标裁剪掉完全在区域外的目标 new_boxes [] for (cls, cx, cy, bw, bh) in bboxes: xmin (cx - bw/2) * w ymin (cy - bh/2) * h xmax (cx bw/2) * w ymax (cy bh/2) * h x1 max(x, xmin) - x y1 max(y, ymin) - y x2 min(x cw, xmax) - x y2 min(y ch, ymax) - y if x2 x1 and y2 y1: new_boxes.append([cls, (x1x2)/(2*cw), (y1y2)/(2*ch), (x2-x1)/cw, (y2-y1)/ch]) return crop, new_boxes参数说明crop_ratio控制裁剪区域占原图的比例0.3~0.7让模型看到局部细节。新坐标计算必须基于裁剪区域重新归一化否则训练会崩。这种增强对远景小目标特别有效因为裁剪后目标在画面中的占比变大了。6.2 用训练后的模型做难例挖掘补充hard-mining数据4599张图训练出的模型漏检的往往不是普通场景而是逆光、水花飞溅、身体半沉这些极端情况。我的做法是先训练一个baseline用它在所有训练图上推理把置信度低于0.3的检测结果对应的图片挑出来人工复核并重新标注成难例加入训练集重训。yolo predict modelruns/swim_yolov8s/weights/best.pt sourceimages/train save_txtTrue conf0.3参数说明conf0.3设低阈值是为了把更多不确定的预测暴露出来。然后去看labels目录里每个txt的置信度筛选出所有预测框少于真实框数量的图片以及预测类别与真实类别不一致的图片。这些就是模型的“盲区”。把盲区图片单独拷贝出来用labelImg重新检查标注是否缺失或错误修正后再合并到训练集里。这个过程迭代两三轮drowning的recall通常能提升5~8个点。6.3 一个必须养成的习惯每次训练前都跑一遍格式校验脚本这数据集本身质量不错但你不能假设所有从网上下来的数据都这么规矩。我现在每次拿到新数据集都会强制走一遍校验流程先统计jpg/xml/txt数量是否一致再扫描txt的类别索引是否越界最后抽查10张图把框画出来人眼确认。# validate_yolo.py import cv2 import random from pathlib import Path img_paths list(Path(images/train).glob(*.jpg)) random.seed(0) samples random.sample(img_paths, 10) for img_path in samples: txt_path Path(labels/train) / (img_path.stem .txt) img cv2.imread(str(img_path)) h, w img.shape[:2] with open(txt_path) as f: for line in f: cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) color (0, 0, 255) if cls 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(check_ img_path.name, img)参数说明红色框是drowning绿色框是swimming。跑完看一眼生成的check_*.jpg如果发现框没贴目标、类别明显标反、或者框大小异常就可以直接定位到具体文件。从那以后我每次训练任何检测模型前都强制走一遍这10张可视化不通过不准开训练。这套流程帮我挡掉过至少三次因为文本编码或格式错乱导致的训练翻车。这份数据集本身的双格式对齐做得不错但真正的功夫都在训练前的校验和训练后的难例挖掘上。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。