简介这份喝水检测数据集面向计算机视觉学习者与目标检测开发者用于训练和验证「喝水行为」相关模型可应用于课堂行为分析、驾驶疲劳监测或健康习惯识别等场景。数据集同时提供Pascal VOC与YOLO两种标注格式包含jpg原图及一一对应的xml、txt标注文件省去格式转换环节可直接接入主流检测框架。压缩包共2000个文件以997个txt标注、995个xml标注和8个jpg图片为主整体约39.23MB体积轻便易于下载与本地部署。标注共3类分别为drink、face、phone对应框数1040、1016和17总框数2073均使用labelImg按矩形框规则完成标注准确合理。目前已有198人学习下载适合作为小规模训练集或迁移学习起点帮助读者快速搭建喝水检测实验、验证模型效果并积累数据预处理经验。1. 喝水检测数据集实测995 张 VOCYOLO 双格式到底能训出什么前阵子接了个小活客户要在办公区工位上判断员工有没有在喝水用来做久坐提醒的触发条件。听起来简单真上手才发现公开的喝水检测数据集少得可怜——要么是杯子检测要么是饮水机检测真正标注「人正在喝水」这个动作的没几个。翻了一圈找到这份 995 张、3 类别的 VOCYOLO 双格式包体量不大但胜在拿来就能训。它解决的就是「从零标注喝水动作」这个最耗时的环节适合做行为识别入门、工位健康监测原型、或者想快速验证 YOLO 训练流程的从业者。995 张不算多但作为冷启动数据集够你把 pipeline 跑通、把基线模型立起来后面再靠自己的场景数据做增量。2. 拆开压缩包先看什么VOC 与 YOLO 双格式的目录结构与类别定义拿到一个目标检测数据集我习惯先不急着写训练脚本而是把目录结构和标注文件翻一遍。这一步花十分钟能省掉后面几个小时的报错排查。这份包解压后是标准的双格式布局VOC 和 YOLO 各一套图片共用或各存一份具体取决于打包方式。下面按我实际拆包的顺序讲。2.1 目录树与文件对应关系解压后典型结构长这样不同打包习惯会有微调但核心目录名不会变drink_detection_dataset/ ├── JPEGImages/ # 所有原始图片jpg 格式 ├── Annotations/ # VOC 格式的 XML 标注文件 ├── ImageSets/ │ └── Main/ │ ├── train.txt # 训练集图片名列表不带扩展名 │ ├── val.txt # 验证集图片名列表 │ └── trainval.txt # 训练验证合集 ├── labels/ # YOLO 格式的 txt 标注文件 │ ├── train/ │ └── val/ ├── images/ # YOLO 训练用的图片目录 │ ├── train/ │ └── val/ └── data.yaml # YOLO 训练配置文件关键点在于VOC 的 XML 和 YOLO 的 txt 描述的是同一批图片的同一批框只是坐标系不同。VOC 用左上角右下角的绝对像素坐标YOLO 用归一化后的中心点宽高。如果你只打算用 YOLO 训练那labels/和images/两个目录就够了Annotations/可以留着做备份或转其他格式用。提示先确认images/train和labels/train里的文件名是否一一对应除了扩展名。我遇到过打包时漏拷几张图的情况训练时直接报「找不到标签文件」查半天才发现是文件缺失。2.2 三个类别到底标了什么3 类别是这份数据集的核心信息。根据喝水检测这个场景类别划分通常是围绕「人-杯-动作」三个维度来的。常见做法是类别 ID类别名标注内容0person人体整体框1cup杯子或水瓶2drinking人正在喝水的动作区域这里要特别注意drinking这个类别的标注粒度直接决定模型能学到什么。如果标的是「人手举杯到嘴边」这个局部区域那模型学的是动作特征如果标的是整个人体框加上一个 drinking 标签那本质上还是分类逻辑套在检测框架里。打开几个 XML 看看object里的bndbox坐标范围就能判断标注策略。import xml.etree.ElementTree as ET import os from collections import Counter anno_dir drink_detection_dataset/Annotations class_counter Counter() box_sizes [] for xml_file in os.listdir(anno_dir)[:50]: # 先抽 50 个看看分布 tree ET.parse(os.path.join(anno_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] 1 bbox obj.find(bndbox) w int(bbox.find(xmax).text) - int(bbox.find(xmin).text) h int(bbox.find(ymax).text) - int(bbox.find(ymin).text) box_sizes.append((name, w, h)) print(类别分布:, class_counter) # 按类别看框的平均尺寸判断标注粒度 for cls in class_counter: sizes [(w, h) for n, w, h in box_sizes if n cls] avg_w sum(s[0] for s in sizes) / len(sizes) avg_h sum(s[1] for s in sizes) / len(sizes) print(f{cls}: 平均框尺寸 {avg_w:.0f}x{avg_h:.0f})这段脚本的作用是快速摸清标注分布。class_counter告诉你三个类别是否均衡——如果drinking只有几十个框那训练时这一类基本学不动得靠数据增强或调整 loss 权重来补。box_sizes则帮你判断标注粒度drinking的平均框如果明显小于person说明标的是局部动作区域如果两者尺寸接近那可能是整人框加标签。2.3 995 张的分布够不够用995 张图、3 类别平均下来每类三百来个框假设标注均衡这个量级做迁移学习是够的但从零训练肯定不够。我的经验是用 YOLOv8n 或 YOLOv8s 这种轻量模型冻结 backbone 先训 50 轮再解冻微调 100 轮mAP 能到 0.7 以上就算正常。如果drinking类样本偏少可以考虑用 copy-paste 增强把这一类的实例数翻倍。另外要注意图片的多样性。翻一遍JPEGImages看看是不是同一个场景、同一个人的连续帧。如果是视频抽帧来的那相邻帧高度相似随机划分 train/val 会导致验证集泄漏——验证集里的图和训练集里的图几乎一样mAP 虚高。这种情况得按时间或按人划分不能随机分。3. 从 VOC 到 YOLO 训练格式转换、data.yaml 配置与训练命令格式转换这一步很多人觉得网上脚本一搜就有复制粘贴就完事。但实际翻车最多的就是这里——坐标系搞反、类别 ID 对不上、图片路径写错训练时 loss 不降或者直接报错。下面把转换逻辑和训练配置拆开讲。3.1 VOC XML 转 YOLO txt 的坐标计算VOC 的bndbox给的是xmin, ymin, xmax, ymax单位是像素。YOLO 需要的是归一化后的class_id center_x center_y width height全部在 0 到 1 之间。转换公式center_x (xmin xmax) / 2 / img_width center_y (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_heightimport xml.etree.ElementTree as ET from PIL import Image import os def voc_to_yolo(xml_path, img_dir, out_dir, class_map): xml_path: VOC 标注文件路径 img_dir: 图片所在目录 out_dir: YOLO 标签输出目录 class_map: 类别名到 ID 的映射如 {person:0, cup:1, drinking:2} tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text img_path os.path.join(img_dir, filename) img Image.open(img_path) img_w, img_h img.size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_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, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_name os.path.splitext(filename)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) # 批量转换 class_map {person: 0, cup: 1, drinking: 2} for xml_file in os.listdir(Annotations): voc_to_yolo( os.path.join(Annotations, xml_file), JPEGImages, labels_all, class_map )几个容易出错的参数点class_map必须和data.yaml里的names顺序完全一致否则模型学出来的类别是乱的。边界裁剪那几行别省——有些标注框会超出图片边界不裁剪的话归一化后坐标可能小于 0 或大于 1YOLO 训练时虽然不报错但会引入噪声。{:.6f}保留六位小数是 YOLO 官方脚本的习惯精度够用。3.2 data.yaml 的路径与类别配置YOLO 训练靠data.yaml找数据。这份数据集如果自带data.yaml先打开核对路径和类别名如果没有自己写一个path: /home/user/drink_detection_dataset # 数据集根目录绝对路径 train: images/train # 相对 path 的训练图片目录 val: images/val # 相对 path 的验证图片目录 nc: 3 # 类别数 names: # 类别名顺序必须和 class_map 一致 0: person 1: cup 2: drinkingpath用绝对路径最稳相对路径在不同工作目录下跑容易找不到文件。train和val写相对于path的路径YOLO 会自动去对应目录找图片然后去同级的labels目录找同名 txt。这里有个隐含约定images/train对应的标签目录必须是labels/trainYOLO 会把路径里的images替换成labels。如果你把标签放在别的地方得用train_labels参数单独指定。注意names的键从 0 开始和转换脚本里的class_map值一一对应。我见过有人class_map里person给 0data.yaml里names第一行写cup训出来的模型把人都认成杯子。3.3 训练命令与关键超参环境装好 ultralytics 之后一条命令启动训练yolo detect train \ data/home/user/drink_detection_dataset/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ projectruns/drink \ nameexp1逐个说参数modelyolov8n.pt用预训练权重做迁移学习995 张图从零训基本没戏。imgsz640是 YOLO 的默认输入尺寸如果你的图片分辨率远大于 640可以调到 1280但显存占用会翻倍。batch16在 8G 显存上跑 640 尺寸刚好显存不够就降到 8。lr00.01是初始学习率迁移学习场景下这个值偏大可以降到 0.001 更稳。patience30表示 30 轮 mAP 不提升就早停防止过拟合。训练过程中重点看mAP50和mAP50-95两个指标。mAP50到 0.8 以上、mAP50-95到 0.5 以上说明模型基本可用。如果drinking类的 AP 明显低于其他两类回去检查这一类的样本量考虑用copy_paste0.3增强。4. 避坑与排查995 张数据集训练时最容易翻车的五个地方这一章是我自己踩过的坑也是帮别人排查时遇到最多的。每条按「现象 → 原因 → 解决」写你训练时对着查。4.1 训练 loss 不降mAP 一直是 0现象启动训练后box_loss和cls_loss在几个 epoch 内几乎不变验证集 mAP 始终为 0。原因最常见的是标签路径不对。YOLO 找不到标签文件时不会报错而是把所有图片当成「无目标」来训loss 自然不降。其次是data.yaml里nc和实际类别数不一致或者names顺序和标签里的 class_id 对不上。解决先跑一遍yolo detect train加--verbose看日志里有没有WARNING: No labels found。然后手动检查labels/train目录下 txt 文件数量是否和images/train里的图片数量一致。最后用yolo detect val加载训练好的模型看混淆矩阵如果所有预测都集中在一个类基本就是类别映射错了。4.2 验证集 mAP 虚高实际推理一塌糊涂现象训练日志里mAP50到 0.95 以上但拿新图片推理时框位置偏移严重或者漏检。原因验证集和训练集来自同一段视频的相邻帧图片高度相似模型相当于在「背答案」。995 张如果是视频抽帧来的这个问题几乎必然出现。解决按时间或按场景重新划分 train/val。如果图片文件名里有时间戳或序号按序号分段前 80% 做训练后 20% 做验证。如果已经随机分好了至少把验证集里的图片和训练集做一次相似度比对剔除高度相似的。4.3 drinking 类漏检严重现象person和cup检测正常但drinking类几乎检不出来AP 低于 0.2。原因drinking类的样本量太少或者标注框太小。995 张里如果drinking只有一两百个框模型学到的特征不够。另外如果drinking标的是手部举杯的局部区域这个区域在整图中占比很小YOLO 的 640 输入尺寸下可能只剩几十个像素特征提取困难。解决先统计drinking类的实例数如果少于 300用copy_paste增强把这一类的实例贴到其他图片上。如果标注框确实太小考虑把输入尺寸调到 1280或者改用 YOLOv8m 这种更大的模型。另一个思路是把drinking和cup合并成一个类先保证检出率再在业务层做逻辑判断。4.4 训练到一半显存溢出现象前几个 epoch 正常跑到中途报CUDA out of memory。原因YOLO 训练时如果开了mosaic增强每个 batch 的图片尺寸会动态变化某些 batch 拼接后的尺寸偏大显存占用飙升。另外如果cacheTrue把图片缓存到内存图片多的时候也会挤占显存。解决把batch降到 8 或 4或者设置close_mosaic10在最后 10 个 epoch 关闭 mosaic。如果用了cacheTrue改成cacheFalse或cacheram限制缓存量。显存实在紧张就用imgsz416先跑通流程。4.5 推理时框重叠严重NMS 压不住现象同一杯子上出了好几个框或者一个人身上既有person又有drinking框叠在一起。原因drinking和person在空间上高度重叠NMS 的 IoU 阈值默认 0.7如果两个框的 IoU 超过这个值得分低的会被压掉。但如果两个类别的框重叠YOLO 默认是跨类别做 NMS 的可能导致drinking框被person框压掉。解决推理时设置agnostic_nmsFalse让 NMS 按类别独立做。如果还是重叠严重调低iou阈值到 0.5让 NMS 更激进地压框。业务层也可以加逻辑如果drinking框和person框的 IoU 超过 0.5只保留drinking。5. 进阶用法用这份数据集做增量训练与推理验证995 张跑通之后真正的活才刚开始——你得把它用到自己的场景里。这一章讲两个方向一是怎么用自己的数据做增量训练二是怎么验证模型在真实场景下的表现。5.1 增量训练冻结 backbone 再解冻如果你手头有自己场景的喝水图片哪怕只有一两百张也可以在这份数据集训好的模型基础上做增量。做法是分两阶段第一阶段冻结 backbone只训检测头学习率设小一点第二阶段解冻全部用更小的学习率微调。# 第一阶段冻结 backbone只训 head yolo detect train \ datamy_data.yaml \ modelruns/drink/exp1/weights/best.pt \ epochs50 \ freeze10 \ lr00.001 \ projectruns/finetune \ namestage1 # 第二阶段解冻全部小学习率微调 yolo detect train \ datamy_data.yaml \ modelruns/finetune/stage1/weights/best.pt \ epochs100 \ lr00.0001 \ projectruns/finetune \ namestage2freeze10表示冻结前 10 层YOLOv8n 的 backbone 大概就是前 10 层。第一阶段学习率用 0.001第二阶段降到 0.0001避免把预训练学到的特征冲掉。自己的数据如果和原数据集场景差异大比如原数据集是办公室你的是车载第一阶段可以多训几轮让 head 先适应新场景。5.2 推理验证别只看 mAP看实际业务指标mAP 是学术指标业务上更关心的是「该报的有没有报不该报的有没有误报」。写个推理脚本在验证集上跑一遍统计每个类别的检出率和误报率from ultralytics import YOLO import os model YOLO(runs/drink/exp1/weights/best.pt) val_dir drink_detection_dataset/images/val results model.predict( sourceval_dir, conf0.25, # 置信度阈值业务上可以调 iou0.5, # NMS 的 IoU 阈值 saveTrue, save_txtTrue # 保存预测结果方便和 GT 对比 ) # 统计每个类别的检出数量 from collections import Counter det_counter Counter() for r in results: for cls_id in r.boxes.cls: det_counter[int(cls_id)] 1 print(预测类别分布:, det_counter) # 和验证集 GT 的类别分布对比看哪类漏检多conf0.25是默认值业务上如果漏检代价高可以降到 0.1如果误报代价高提到 0.5。save_txtTrue会把预测结果存成 YOLO 格式的 txt你可以写个脚本和 GT 做逐图对比算出每个类别的 precision 和 recall。这一步比看 mAP 更有意义——mAP 是全局平均业务上可能只关心drinking这一类的 recall。5.3 一个具体技巧用 TTA 提升小目标检出drinking类的框如果偏小推理时开 TTATest Time Augmentation能提一两个点。YOLO 支持augmentTrueresults model.predict( sourceval_dir, conf0.25, augmentTrue, # 开启 TTA对图片做翻转、缩放后分别推理再融合 saveTrue )TTA 的代价是推理速度慢三倍左右如果做实时检测比如 25 帧的视频流这个开销得算进去。我的习惯是离线验证时开 TTA 看上限实际部署时关掉用模型本身的能力扛。从那以后我每次拿到新数据集都强制先跑一遍类别分布统计和标注可视化确认没有漏标、错标、类别不均衡再动手训。这份 995 张的喝水检测数据集体量不大但结构完整VOC 和 YOLO 双格式省了转换的功夫3 类别的划分也贴合实际业务。拿它做冷启动再叠自己的场景数据做增量是条能走通的路。希望帮到你。本文还有配套的精品资源点击获取