
简介这是一套面向目标检测任务、可直接用于YOLO系列算法训练与验证的仓库工人数据集聚焦仓储环境下的工人安全场景尤其适合计算机视觉初学者与算法工程师快速上手。压缩包共收录1870个文件包含623张JPG图像、623个TXT格式的YOLO标签、623个XML格式的VOC标签以及1个YAML配置文件整体大小约30.25MB目录结构清晰便于直接使用。标注提供两类主流格式TXT文件按类别 中心点x 中心点y 宽 高记录归一化边界框XML文件则遵循VOC标注规范数据集已预先划分好训练集与验证集无需额外处理即可投入YOLOv5/v7/v8/v9/v10/YOLO11等模型训练。已有43人学习下载对于仓储安全分析、工人行为识别等场景的开发者而言这份资源能有效节省人工标注时间开箱即用适合算法对比、模型测试与快速迭代尤其适合需要特定场景数据做迁移学习或算法落地的项目。1. 仓库工人检测数据集为什么值得拿来当yolo入门样本仓库场景的视觉感知和开放世界检测不一样工人、叉车、货架、安全帽这些目标大多小尺寸、互相遮挡、光照不均匀而且监控视角固定。这个623张图像带标签的仓库工人数据集正好把yolo系列算法训练中最容易出问题的几个点都压在了同一批数据上——目标比例小、类间形态接近、正负样本不均衡。我拆过不少开放数据集绝大多数训练效果差不是模型不行而是数据集的标注格式、划分方式和类别定义没有先理清楚。这个数据集给的是yolo和voc两套标签训练集、验证集已经划分好yolov5、v7、v8、v9、v10、yolo11都能直接用。适合两类人一是刚接触yolo目标检测想拿真实场景数据跑通完整训练流程的工程师二是要做仓库安防、人员统计、行为识别业务需要先用公开数据验证模型基线再上自有数据的团队。后面几章我会从标签坐标系、目录组织、训练复现到自定义转换脚本一步步把这个数据集的每个技术细节拆开。2. yolo标签格式与voc标注的坐标换算逻辑2.1 yolo格式为什么用归一化坐标yolo的txt标签每一行是class x_center y_center width height四个框坐标都是相对于图片宽高的比例值范围在0到1之间。也就是说一张1920x1080的图里一个中心点在(960, 540)的框记录为x_center0.5、y_center0.5。这套设计的直接好处是模型训练时不必关心输入图片的绝对分辨率——无论原图是1280x720还是640x640读取后都会做letterbox缩放到统一尺寸如果标签存的是绝对值缩放后所有框全错位。yolo从v3开始就要求标注归一化就是这个原因。实际拿到这个仓库数据集时先用下面这段代码抽查几个txt文件的数值范围可以快速判断标签是否有效import os label_dir datasets/warehouse/labels/train for fname in os.listdir(label_dir)[:5]: path os.path.join(label_dir, fname) with open(path, r) as f: lines f.readlines() print(f {fname} ) for line in lines: parts line.strip().split() cls int(parts[0]) xc, yc, w, h map(float, parts[1:]) # 越界校验中心点和宽高都必须在(0,1]区间 assert 0 xc 1 and 0 yc 1, center out of range assert 0 w 1 and 0 h 1, size out of range print(fclass{cls}, center({xc:.3f}, {yc:.3f}), fsize({w:.3f}, {h:.3f}))这里对每个标签做了一次合法性断言xc和yc是边界框中心点的归一化坐标w和h是归一化宽高。跑完后如果没有任何AssertionError说明这批标签坐标没有越界如果某几行的宽高为0训练时会被当成无效目标跳过但不会报错会直接影响收敛效果。我之前遇到过标签里出现负值的脏数据yolov8能跑完训练但mAP一直上不去最后就是用这个方式排查出来的。2.2 VOC的xml格式与坐标绝对值voc格式把标注信息存在xml文件里每个目标对应一个object节点里面用bndbox记录xmin、ymin、xmax、ymax四个整数单位是像素绝对值。同样一张1920x1080的图中心点(960, 540)的框在voc里就是一组具体像素坐标它依赖图片原始分辨率。这个数据集同时保留两种格式意味着同样一份标注yolo和voc各自存了一份副本。它们的物理坐标与归一化坐标之间的换算关系是x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height box_width (xmax - xmin) / image_width box_height (ymax - ymin) / image_height# 用python内置的xml.etree解析一个voc标注文件 python - EOF import xml.etree.ElementTree as ET tree ET.parse(datasets/warehouse/annotations/train/img_0509_92.xml) root tree.getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) for obj in root.iter(object): box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height print(f{obj.find(name).text}: fyolo({x_center:.4f}, {y_center:.4f}, {w:.4f}, {h:.4f})) EOF这段命令把voc的像素坐标转成了yolo的归一化坐标。注意xml里的size/width和size/height才是图像原始尺寸不能用缩放后的尺寸去算否则转出来的坐标整体偏移。常见的一种误用是把xml里没有size节点就硬编码一个固定分辨率这在监控视角数据集上尤其致命——同一套摄像头采集的图分辨率确实一致但一旦换了测试集就会暴露。2.3 两种标签格式共存时的目录设计目录内容格式labels/train每个jpg对应的txt标签yolo格式labels/val每个jpg对应的txt标签yolo格式annotations/train每个jpg对应的xml标注voc格式annotations/val每个jpg对应的xml标注voc格式images/train训练图片jpgimages/val验证图片jpg这个目录布局的好处是yolo训练时直接指定data.yaml里train和val的图片路径框架会自动在相邻的labels目录里找同名txt。比如图片路径是images/train/img_0509_92.jpgyolov8会去labels/train/img_0509_92.txt找对应标签。如果你要按自己的习惯重排目录一定保持这种图片和标签同名同目录前缀的结构否则会报found no labels的警告。3. 训练前的数据集体检与图像组织分析3.1 用脚本统计标注框的尺寸分布和遮挡情况拿到数据集不要直接开训先做一次标注分布统计。623张图虽然不算大但仓储场景里工人目标往往只占画面的5%到15%小目标比例如果过高默认的yolo参数跑出来会漏检严重。统计每个标注框的相对面积、宽高比能判断该不该调anchor或者是否要加tiling策略。import os import numpy as np label_dir datasets/warehouse/labels/train all_boxes [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line in f: parts line.strip().split() if len(parts) 5: continue _, xc, yc, w, h parts all_boxes.append([float(xc), float(yc), float(w), float(h)]) boxes np.array(all_boxes) areas boxes[:, 2] * boxes[:, 3] print(ftotal boxes: {len(areas)}) print(farea min/mean/max: {areas.min():.6f}, f{areas.mean():.6f}, {areas.max():.6f}) print(fboxes smaller than 0.01: {(areas 0.01).sum()} f({(areas 0.01).mean()*100:.1f}%))这段统计里areas是每个框相对图片面积的占比小于0.01意味着这个框只占图片面积的1%按640x640输入算基本就是64x64像素以下的目标。这个仓库数据集如果小目标占比超过30%我一般会建议配合yolo的sahi切片推理或者把imgsz从640提到960再训练。不要一上来就换模型结构先看数据分布。3.2 图片名称序列与样本边界情况从文件名能看到img_0509_92.jpg这类序号同一个前缀img_0509说明这大概率是从一段连续监控视频里抽帧得到的相邻帧之间画面相似度高。这种抽样方式有个隐患训练集和验证集如果来自同一个视频片段模型会通过背景记忆目标而不是真正学习目标的视觉特征泛化能力虚高。验证方法也很直接把训练和验证集的文件名前缀分组看看有没有重叠ls datasets/warehouse/images/train | sed s/_[0-9]*\.jpg// | sort -u train_prefix.txt ls datasets/warehouse/images/val | sed s/_[0-9]*\.jpg// | sort -u val_prefix.txt comm -12 train_prefix.txt val_prefix.txt如果comm的输出有公共前缀说明训练和验证存在同源片段。这种场景下的解决方案是重划分按前缀分组做group k-fold保证同视频片段全部落在同一折里。这个数据集已经分好了目录但如果你的下游业务需要更高的泛化保证这一步值得做。4. 用yolov8复现训练与验证流程4.1 准备data.yaml配置yolo系列所有算法版本都通过一个yaml文件约定数据路径、类别数和类别名。这个数据集已经划分好train和val我习惯在数据集根目录放一个warehouse.yaml内容如下path: /data/warehouse # 数据集根目录的绝对路径 train: images/train # 相对path的训练图片路径 val: images/val # 相对path的验证图片路径 nc: 2 # 类别数量按数据集实际标注类别修改 names: 0: worker 1: forklift注意path字段在yolov5和yolov8里都支持但在yolov6、yolov7的部分实现里并不识别这个字段需要把train和val写成完整路径比如/data/warehouse/images/train。另一个坑是nc必须和标签里的类别索引最大值匹配如果txt里出现class2而nc2训练时会静默跳过该样本loss曲线会异常波动。4.2 执行训练命令与关键参数解析cd /data/warehouse # 安装依赖如果还没有ultralytics pip install ultralytics # 训练yolov8s 是小模型适合在单卡上验证数据集质量 yolo train datawarehouse.yaml modelyolov8s.pt epochs100 imgsz640 batch16 workers4 # 训练完成后在best.pt上跑验证集 yolo val modelruns/detect/train/weights/best.pt datawarehouse.yaml batch16第一次跑训练建议把epochs从100降到50imgsz用640目的是快速看到收敛趋势而不是追求最终精度。batch根据显存调整12G显存跑yolov8s可以用1624G可以到32太小会让batch normalization统计不稳定。workers是数据加载线程数Windows下建议不超过4Linux可以到8过高反而会因为磁盘IO竞争拖慢每个epoch。训练日志要重点看两个指标一个是box_loss是否持续下降如果前20个epoch loss波动不降甚至上升大概率是学习率过大或者数据标签有脏数据另一个是验证集的mAP50和mAP50-95的差距mAP50高但mAP50-95低说明模型学到了大致位置但框的边界不精确这种情况下可以适当增加epochs或者换用yolov8m。4.3 损失函数视角下看小目标训练yolo系列的损失函数由三部分组成边界框回归损失、分类损失、置信度损失。yolov8去掉了objectness分支改为直接在分类分支输出目标度但对小目标而言边界框回归的损失权重和IoU计算方式影响更大。yolov8默认使用CIoU loss它同时惩罚框的重叠面积、中心点距离和长宽比对仓库工人这种小尺寸目标CIoU在训练初期梯度更稳。如果在训练过程中发现box_loss在60个epoch后还有明显下降空间可以尝试打开数据增强里的mosaic和mixupyolov8默认在训练最后10个epoch自动关闭mosaic以避免过度扰动。对623张的小数据集这种自动调整反而可能减轻过拟合但如果你的业务场景要求精确定位工人位置建议把close_mosaic调大到15让最后的精调阶段更充分。5. 自定义voc到yolo的批量转换脚本与标注质量核验5.1 完整转换脚本与边界条件处理虽然这个数据集两种格式都已给全但实际工作中经常遇到只有voc标注的自有数据需要批量转成yolo格式。核心脚本如下import os import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_path, img_w, img_h, class_mapping): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_mapping: continue cls_id class_mapping[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 越界裁剪标注框偶尔会超出图像边缘 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) # 过滤宽高为0的无效框 if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return \n.join(lines) class_mapping {worker: 0, forklift: 1} xml_list glob(annotations/train/*.xml) img_w, img_h 1920, 1080 # 按数据集实际尺寸设置 for xml_path in xml_list: txt_path xml_path.replace(annotations/train, labels/train).replace(.xml, .txt) content voc_to_yolo(xml_path, img_w, img_h, class_mapping) with open(txt_path, w) as f: f.write(content)这段脚本做了一层越界裁剪和无效框过滤是为了防止voc标注里出现极端情况比如某个框的xmax大于图片宽度或ymin小于0。如果原始voc标注没问题这两步就是纯防御。5.2 可视化验证标注与类别平衡检查转换完成后不要只看txt里的数字把框画到原图上逐张目检是最快的质量验证方式。我一般用OpenCV随机抽50张图把标注框直接画出来存到debug_bboxes目录import cv2 import os import random image_dir datasets/warehouse/images/train label_dir datasets/warehouse/labels/train out_dir debug_bboxes os.makedirs(out_dir, exist_okTrue) images os.listdir(image_dir) random.shuffle(images) for fname in images[:50]: img cv2.imread(os.path.join(image_dir, fname)) h, w img.shape[:2] txt os.path.join(label_dir, fname.replace(.jpg, .txt)) if not os.path.exists(txt): continue with open(txt) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls, xc, yc, bw, bh int(parts[0]), *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) color (0, 255, 0) if cls 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, str(cls), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(os.path.join(out_dir, fname), img)画框时先算归一化坐标到像素坐标的还原x1、y1是左上角x2、y2是右下角这一步和前面2.2节的转换是互逆的过程。如果画出来的框大面积偏移问题往往出在标签归一化引用了错误的分辨率。这个仓库数据集如果看到某个框把货架的立柱都括进去了是标签本身框得松散不影响训练但不适合直接上线。5.3 类别平衡统计import collections cls_counter collections.Counter() label_dir datasets/warehouse/labels/train for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname)) as f: for line in f: if len(line.strip().split()) 5: continue cls_id int(line.strip().split()[0]) cls_counter[cls_id] 1 for cls_id in sorted(cls_counter): print(fclass {cls_id}: {cls_counter[cls_id]} instances)假设输出发现class 0有2100个实例class 1只有300个训练出来的模型对class 1的召回率会明显偏低。对623张的小数据集缓解手段不是简单加样本而是针对少数类调cls_loss权重——yolov8里可以通过传入weight参数按类别频率反向加权或者直接用OpenCV离线复制少数类样本做重复采样。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。