
简介基于CUHK Occlusion Dataset整理的行人检测数据集面向目标检测学习者与研究者已完成YOLO格式的训练集与验证集划分并同时提供YOLO和VOC两种标注格式可直接用于YOLO系列模型训练、验证及效果对比。压缩包共2000个文件主要包括jpg图像、txt标注、xml标注等类型包体约315.63MB其中jpg为遮挡场景下的行人图像txt为YOLO格式的标签文件xml为VOC格式的标签文件满足不同检测框架或脚本的数据读取要求也便于用户根据自己的项目需要灵活选用。目前已有593人学习下载。内容上除完整数据集和双格式标注外还附带了参数化编程思路清晰、注释详尽的辅助说明与代码方便调整批次大小、类别数量、输入尺寸等训练参数并可参考作者对数据划分、路径配置和格式转换的处理经验适用于计算机、电子信息工程、数学等专业学生的课程设计、期末大作业与毕业设计对有遮挡行人检测、模型微调或数据格式转换需求的读者尤其有参考价值。1. 遮挡场景行人检测2125张标注图像里的边界情况在目标检测任务里密集人群中的相互遮挡一直是漏检率最高的场景——两个重叠的行人经常被模型合并成一个框甚至置信度低的那一个直接被NMS阈值干掉。CUHK Occlusion Dataset这个行人数据集收集的就是这类遮挡样本2125张图像全部带yolo和VOC两种格式的标注文件train/val划分也已经做好解压之后可以直接喂给yolo跑训练。这个数据集的量不算大但它的价值在于专门炮制“难例”适合做yolo目标检测课程设计、期末大作业也适合想验证自己的yolo改进算法比如改进NMS、换注意力模块能不能扛住遮挡场景的工程师。下面直接拆文件结构、标签格式和训练配置。2. 从VOC的XML到YOLO的txt标注格式的血缘关系2.1 两种格式的字段映射解压压缩包后Annotations目录里是VOC格式的XML文件labels目录里是yolo格式的txt文件。先打开一个XML看一下核心结构文件名和压缩包里的资源描述一致比如set00_set06-occ_309.xmlannotation folderJPEGImages/folder filenameset00_set06-occ_309.jpg/filename size width640/width height480/height depth3/depth /size object nameperson/name bndbox xmin120/xmin ymin80/ymin xmax310/xmax ymax350/ymax /bndbox /object /annotation这里只有person一个类别所以name字段始终是personbndbox里存的是绝对像素坐标左上角是(xmin, ymin)右下角是(xmax, ymax)。每一对object标签对应图像里的一个行人目标遮挡样本里同一张图会有多个object并且它们的框互相重叠。对应的yolo格式txt内容长这样0 0.335938 0.447917 0.296875 0.562500五个字段含义分别是class_id、x_center、y_center、width、height。第一个0是类别索引因为只有person一类所以所有行的第一个字段都是0后面四个值全部是归一化坐标范围在0到1之间。2.2 为什么yolo要做归一化而不是直接用绝对坐标VOC里存绝对像素坐标yolo里存归一化坐标这不是风格差异而是训练机制决定的。yolo在训练时会把输入图像resize到一个固定尺寸比如640x640不同原始分辨率的图像被拉伸或填充后如果用绝对坐标所有标注框的位置和大小都要跟着重新计算而归一化坐标是相对图像宽高的比例值resize前后保持不变省掉了这一层换算。提示归一化的另一个好处是让不同分辨率的图像在标注层面可比。CUHK Occlusion Dataset里图像分辨率不完全一致存绝对坐标的话标注数值的分布会被图像尺寸干扰模型的回归头学起来会更吃力。2.3 写一个VOC转YOLO的Python脚本格式转换的逻辑和边界情况可以通过一段脚本说清楚import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.iter(object): cls_id 0 # 本数据集只有person一类索引固定为0 xmin float(obj.find(bndbox/xmin).text) ymin float(obj.find(bndbox/ymin).text) xmax float(obj.find(bndbox/xmax).text) ymax float(obj.find(bndbox/ymax).text) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h yolo_lines.append( f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} ) base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(output_dir, base .txt), w) as f: f.write(\n.join(yolo_lines))逻辑说明root.find(size/width)取的是原始图像宽度必须用原图的宽高不能用resize后的值x_center先取像素中心的平均值再除以图宽得到归一化坐标box_w是框宽的像素值除以图宽。四个坐标值保留6位小数yolo训练脚本是读字符串后转float的保留位数太少会导致框回归的目标值被截断训练时box_loss降不下去。提示如果转换后txt里的坐标出现负数或大于1的值说明XML里的标注框越界了或者原图宽高读的是resize后的值。yolo训练遇到这种标签会直接跳过对应图像表现就是loss正常但mAP始终很低。2.4 YOLO转VOC反向转换的一个关键细节反过来把txt转成XML时最容易踩的坑是坐标越界。yolo的归一化值乘回图像宽高后x_center width/2计算出的xmax可能超过图像宽度贴近图像边缘的目标尤其容易出现。转换时要加一个clamp操作def clamp_to_bounds(value, low, high): return max(low, min(value, high))这个压缩包自带的标签本身是配对的一般不需要自己转格式。但如果后续用LabelImg增补了样本导出的XML和原有yolo标签混在一起这个转换函数就能派上用场。2.5 标签字段对照速查字段VOC格式YOLO格式说明类别nameperson/name第一个值0索引从0开始计数框中心X(xminxmax)/2第二个值归一化到 [0,1]框中心Y(yminymax)/2第三个值归一化到 [0,1]框宽xmax-xmin第四个值除以图像宽度框高ymax-ymin第五个值除以图像高度3. 数据集划分与yolo训练配置3.1 解压后的目录结构整理压缩包里的图像和标签目录结构需要整理成yolo训练框架能够识别的标准布局。以yolov5/yolov8工程为例建议整理成如下结构dataset/ ├── images/ │ ├── train/ # 训练集图像 │ └── val/ # 验证集图像 ├── labels/ │ ├── train/ # 训练集标签txt │ └── val/ # 验证集标签txt └── data.yaml资源描述里提到“做好了yolo格式数据集的训练和划分”说明train/val的划分结果已包含在压缩包中。需要注意一个硬性约束图像文件与标签文件必须同名set00_set06-occ_309.jpg对应的标签必须是set00_set06-occ_309.txt扩展名不同前面的文件名主体要完全一致多一个字符或少一个字符都会导致训练脚本跳过该样本。3.2 data.yaml的写法与参数坑在数据集根目录下创建data.yamltrain: ./images/train val: ./images/val nc: 1 names: [person]train和val的路径建议写成绝对路径。训练脚本的工作目录经常与数据集不在同一级相对路径会触发AssertionError: train: ... does not exist排查这个问题比训练本身更浪费时间。nc是类别数本数据集只有person一类必须写1。写大了不会立刻报错但训练时如果遇到txt中不存在的类别索引会在dataloader阶段崩溃。names列表的索引顺序必须和标注文件里的第一个字段对应。这里names[0]是person而所有txt里第一个字段都是0两者匹配才正确。3.3 用yolov8启动训练这里以yolov8为例因为它的命令行接口最简洁数据格式兼容yolo格式的标签yolo train datadata.yaml modelyolov8n.pt epochs100 batch16 imgsz640参数在这个场景下的取舍逻辑modelyolov8n.pt表示加载nano版本的预训练权重。2100多张的样本量对参数量较大的模型来说很容易过拟合用nano做迁移学习收敛速度和最终精度更均衡等验证集mAP涨不动了再换yolov8s.pt对比一次即可。imgsz640是该数据集的合理选择。图像源分辨率不高640的输入尺寸既能保留遮挡区域的细节又不至于因letterbox填充过多空白区域。batch16依赖单卡显存8GB显存建议降到8否则会在第一个epoch因OOM中断。3.4 训练日志中的关键指标训练过程中的标准输出Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 99 3.1G 0.7812 0 0.6255 4 640box_loss是定位损失cls_loss几乎为0因为只有一类且大部分图像的标签数量不多。dfl_loss是yolov8的分布焦点损失控制框边界预测的质量。每个epoch结束后还会输出mAP50和mAP50-95两项指标其中mAP50是对遮挡目标最敏感的指标——IoU阈值0.5在重度遮挡下已经算宽松这个值上不去说明模型根本没学到遮挡目标的特征。提示如果训练到20个epoch时box_loss仍在0.9以上不降优先检查标签坐标而不是调超参数。从val集里随机抽三张图把txt坐标还原成像素框画在原图上看看框是否贴合行人轮廓。4. 遮挡行人场景的训练难点与调优策略4.1 标签框重叠带来的训练信号冲突CUHK Occlusion Dataset的特点在名字里就写了occlusion。两张标签框的面积交并比明显高于普通街景数据集这意味着分类分支没问题模型需要识别出“这里有人”。回归分支的挑战在于两个重叠框的中心点可能非常接近特征图上同一个位置要输出两个差异很大的框尺寸。NMS阶段两个置信度都比较高的重叠框会在后处理时被合并掉一个导致漏检。训练这个数据集本质是在调教模型处理“目标不完整但必须识别”的边界情况。4.2 推理阶段NMS参数针对性调整训练完成后做预测默认的iou0.45对遮挡场景过于激进。两个行人的框IoU超过0.45时后处理会认为它们是同一个目标直接丢弃置信度较低的那个。调整方式yolo predict modelruns/detect/train/weights/best.pt sourceval/images iou0.3 conf0.2把NMS的IoU阈值降到0.3两个重叠框被合并的门槛变高同时保留它们的机会变大。代价是误检会增加需要再通过conf阈值来平衡。conf0.2属于较低的置信度门槛风格是“宁可多检不可漏检”适合行人检测这种漏检代价高的场景。4.3 数据增强的参数选择yolov8默认开启mosaic、scale、flip等增强策略针对遮挡数据集需要做两处调整增强参数默认值建议值理由scale0.50.3过大的随机缩放会把本来就遮挡严重的目标缩小到难以辨识translate0.10.2轻微平移可以模拟更多遮挡位置分布fliplr0.50.3左右翻转在密集场景会生成过度反常的重叠形态mosaic1.00.8保持开启但关小概率预留更多真实遮挡样本参与训练还有一个容易被忽略的增强手段是copy-paste把一张图中的行人裁剪后随机粘贴到另一张图上人为制造更多遮挡组合。这个功能在yolov8里默认关闭开启方式是在训练参数里加copy_paste0.3。4.4 预训练模型 vs 从头训练COCO预训练权重里已经有person类别的特征响应用它做初始权重比随机初始化收敛快得多而且val的mAP上限更高。COCO里包含大量完整行人的样本预训练权重对行人整体的纹理、形状先验是有积累的这个数据集的微调目标是在“被挡住一半甚至只露出头肩”的条件下唤醒这些特征。如果换用model从头训练在2125张图上通常要跑到250个epoch以上才能接近预训练权重的效果且结果不稳定不同随机种子下的mAP波动可能超过5个点。这里的数据量决定了迁移学习是更稳的选择。4.5 验证效果时用什么指标判断调优生效用model.val()拿到mAP50、mAP50-95和precision/recall之后额外关注一个容易忽略的数字recall。遮挡场景下模型宁可多框出来几个候选也不要漏人所以调优的目标不应该是“mAP最大”而应该是在mAP50下降不超过2个点的前提下把recall拉上去。每次改动NMS阈值、增强策略或损失权重后都跑一遍val()记录recall数值。5. 推理验证与常见报错排查5.1 对验证集做逐张推理训练阶段看到的mAP是批量统计的结果遮挡样本的表现还要落在具体图像上看。写一个推理脚本对验证集里的所有图像逐张输出检测结果同时打印置信度分布from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcedataset/images/val, conf0.2, iou0.3, saveTrue, save_txtTrue ) conf_values [] for r in results: for b in r.boxes: conf_values.append(float(b.conf[0])) print(f检测框数量: {len(conf_values)}) print(f置信度均值: {sum(conf_values)/len(conf_values):.3f}) print(f置信度最小值: {min(conf_values):.3f})这段代码的作用是量化检测结果的分布特征。如果置信度均值在0.5以下说明大部分预测框都处于模棱两可的状态模型对遮挡目标的特征学习还不够如果小于0.2的预测框数量占比超过20%说明背景误检偏多需要回调conf或者加数据清洗。5.2 标签格式引发的两类典型报错第一类是图像与标签数量对不上。解压后的压缩包里如果混入了未标注的图像yolo训练时不会主动跳过而会在loss计算阶段报一条指向空列表的索引错误。处理方式是用脚本核对images目录的jpeg数量应当等于labels目录的txt数量。第二类是标签内容与yaml配置不匹配。比如某个txt文件里出现了1开头的行而nc: 1训练会在数据加载阶段直接崩溃报错信息里会带上label ... is out of bounds的字样。遇到这种问题不要急着改yaml的nc值先定位是哪个文件的行首数字超过了本数据集的唯一类别0。5.3 框太小训练不收敛的补救CUHK Occlusion Dataset中的部分样本行人距离远导致目标框面积占整张图的比例极小。yolo对这类小目标的回归能力天然偏弱补救方案是调低输入图像resize时的降采样倍数或者将imgsz从640提到960。框在特征图上占据的像素变多回归头能学到的东西才会变多。显存不够时优先降低batch而不是降imgsz。5.4 保存推理结果到本地saveTrue会把带预测框的图像输出到runs/detect/predict目录但遮挡场景的对比还需要原始标注框叠加的效果这时利用压缩包里的VOC格式XMLpython draw_gt.py --xml Annotations --img JPEGImages --out gt_vis把XML还原成方框画在原图上再与yolo预测输出对比能直观看出哪些遮挡目标被漏检是NMS阈值压制的问题还是模型在训练时就没有拟合到该样本。这个环节在翻车排查时最省时间不用盲改参数。本文还有配套的精品资源点击获取