简介:基于CUHK Occlusion Dataset整理的行人检测数据集,面向需要快速上手YOLO目标检测的计算机视觉学习者、高校学生及赛事选手。资源已按YOLO训练要求完成数据集划分,并同时提供YOLO格式(txt)与VOC格式(xml)标注文件,省去自行采集图像、标注和格式转换的繁琐步骤。压缩包内文件以2125张jpg图像、2124个txt标注和1062个xml标注为主,整体约315.63MB,目录按set序列组织,便于按需取用。目前已有594人学习下载。数据集涉及行人遮挡场景,可用于行人检测、目标跟踪、模型鲁棒性对比等实验;配合基础YOLO训练脚本即可直接开展课程设计、期末大作业或毕业设计。由从事视觉算法十余年的工程师整理,标注与划分逻辑清晰,适合作为入门到进阶的实战数据支撑。
1. YOLO行人检测数据集:这个CUHK遮挡数据集为什么开箱即用
这份压缩包里的2125张图像来自CUHK Occlusion Dataset,是专门为行人检测、尤其是遮挡场景设计的子集。它最大的价值不是“有标注”,而是同时给了YOLO和VOC两套标签,并且已经帮你把训练集和验证集划分好了,解压后基本可以直接扔给YOLOv5或YOLOv8开训。对于做课程设计、期末大作业和毕设的同学,省掉标注和格式转换这两步,意味着你只需要关注训练参数和模型改进。我拆包时抽查了十几个标签文件,确认类别ID、归一化坐标都没问题,才敢放心写这份复现笔记。适合两类人:一是想快速跑通YOLO流程的初学者,二是在遮挡行人检测上做算法优化的人。下面我把目录结构、训练配置、转换原理和踩坑记录全部摊开讲。
2. 数据集结构拆解:从目录树到两种标注格式的对应关系
2.1 2125张图像与标签的目录组织方式
解压后你看到的不是常见的VOC或COCO标准目录,而是一堆带set00_set06-occ_309这类前缀的文件名。这套命名来自CUHK Occlusion Dataset采集时的序列号,set00表示第一个大场景,set06表示第六个摄像头角度,occ是occlusion的缩写,309是帧编号。压缩包里通常包含了四个核心目录:images放JPG原图,labels_yolo放YOLO格式的TXT标注,labels_voc放VOC格式的XML标注,ImageSets里是训练集和验证集的名单。
我拿到任何数据集的第一件事,是先统计图像和标签数量是否一致,防止解压过程中丢文件。用三条命令就能完成:
find images -name "*.jpg" | wc -l find labels_yolo -name "*.txt" | wc -l find labels_voc -name "*.xml" | wc -l三条命令的输出应该都是2125。如果某个目录少了100个,说明压缩包不完整,或者你的解压软件把长路径截断了。这里有个细节:find默认只搜当前目录级,如果你把图像分散到子文件夹,需要在路径里加-type f或直接find . -name "*.jpg"。我自己是从不在这一步省时间的,因为后面训练时“No labels found”的报错,十有八九是标签文件数对不上。
解压后建议顺便看一眼总容量。2125张图像加两个标签目录,实际大小不会超过500MB,但如果你的压缩包解出来超过1GB,里面可能混入了无关文件。这个数据集原始图像分辨率并不均匀,后面我会专门讲图像尺寸对训练的影响。
2.2 YOLO标签与VOC标签各自长什么样
YOLO格式是纯文本,每行五个浮点数:class_id x_center y_center width height,所有坐标都相对图像宽高做了归一化。随便打开一个标签文件:
head -5 labels_yolo/set00_set06-occ_309.txt输出示例:
0 0.521875 0.446429 0.156250 0.357143 0 0.687500 0.482143 0.084375 0.196429第一列是类别ID,这个数据集只有person一个类别,所以所有标签第一列都是0。后面四个数字是框的中心点x、中心点y、框宽、框高,取值范围在0到1之间。这意味着不管训练时把图像resize成640还是1280,YOLO都能通过归一化坐标直接映射回原图。
VOC格式是XML,结构比TXT复杂得多。一个典型的XML文件长这样:
<annotation> <folder>images</folder> <filename>set00_set06-occ_309.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>person</name> <bndbox> <xmin>210</xmin> <ymin>80</ymin> <xmax>310</xmax> <ymax>250</ymax> </bndbox> </object> </annotation>XML里的xmin、ymin、xmax、ymax是绝对像素坐标,YOLO训练不能直接吃。如果你要转换,必须用第4章的脚本处理。我强烈建议训练时只用labels_yolo,把labels_voc当作备份源。VOC格式的好处是结构清晰,用脚本读取不容易出错,而且在LabelImg里打开XML比打开TXT直观得多,适合做样本级复核。
2.3 文件命名规律与图像尺寸排查
文件名set00_set06-occ_309中的信息量比你想象的大。set00和set06是两个不同的拍摄集合,图像里的行人遮挡比例和背景结构差异明显。如果训练时不加任何处理,模型可能会对某个集合过拟合。所以我一般会把ImageSets/train.txt和val.txt交叉对比一下,确认两边都包含两个集合的图像,而不是某个集合全部分到测试集。这个数据集的划分相对均衡,但如果你要自己重新划分,记得先按文件名前缀做分层抽样。
图像尺寸是另一个容易忽略的坑。CUHK Occlusion Dataset的原图大多是640×480,但也不排除个别异常分辨率。用Python批量检查一遍最稳妥:
import os from PIL import Image img_dir = "images" min_w = 9999 min_h = 9999 max_w = 0 max_h = 0 for name in os.listdir(img_dir): if not name.endswith(".jpg"): continue w, h = Image.open(os.path.join(img_dir, name)).size min_w = min(min_w, w) min_h = min(min_h, h) max_w = max(max_w, w) max_h = max(max_h, h) if w != h: print("non-square:", name, w, h) print("min:", min_w, min_h, "max:", max_w, max_h)这段代码遍历images目录,PIL读取每张图的宽高,记录最小和最大值。对行人检测来说,如果图像中有大量尺寸不到200像素的行人,模型的感受野设计就要调整。实际执行后你会发现绝大多数图像是640×480,少数可能是320×240,这会导致这些低分辨率图像的标签在resize后出现坐标偏移,但YOLO的mosaic增强会缓解这个问题,不需要额外清洗。
2.4 用可视化脚本复核标签是否正确
虽然资源明确说“已标注可以直接使用”,但训练前花两分钟可视化几张图,能避免后期排查方向错误。我习惯写一个简单的画框脚本,把YOLO格式转成OpenCV矩形框画在图上:
import cv2 import os img_dir = "images" label_dir = "labels_yolo" names = ["person"] for file in ["set00_set06-occ_309.jpg", "set00_set06-occ_310.jpg"]: img_path = os.path.join(img_dir, file) label_path = os.path.join(label_dir, file.replace(".jpg", ".txt")) img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f.readlines(): cls_id, xc, yc, bw, bh = map(float, line.split()) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite("check_" + file, img)注意画框时要把归一化坐标乘回图像的宽高,x1和y1是左上角。如果你画的框和遮挡区域有明显错位,说明标签本身有问题。我抽查了几张,发现大部分框都紧贴行人的可见部分,而不是把被遮挡区域也包含进来,这个特点决定了直接训练时模型更偏向“可见区域检测”,而不是“全行人检测”,后面还会提到。
3. 直接跑通YOLO训练:从数据集配置到启动命令
3.1 数据集YAML配置写法
假设你已经把压缩包解压到datasets/cuhk_occ,而且本地装好了YOLOv5或YOLOv8代码库。训练前第一步是创建数据集配置文件cuhk_occ.yaml:
# cuhk_occ.yaml path: datasets/cuhk_occ train: images val: images nc: 1 names: 0: personpath是数据集根目录,可以写绝对路径或相对路径,看你的训练启动位置。train和val这里都指向images,是偷懒写法,因为资源里的划分文件ImageSets/train.txt和val.txt并不会被YOLO自动识别。如果你直接这么训,训练集和验证集会复用同一批图像,最后验证的mAP会虚高,过拟合判断完全失效。
正确做法是花5分钟把图像按划分名单拆开。我一般先读取train.txt和val.txt,然后复制图像到两个新目录,同时把对应的标签也复制过去。用bash一步到位:
mkdir -p train_images val_images cat ImageSets/train.txt | xargs -I {} cp images/{}.jpg train_images/ cat ImageSets/val.txt | xargs -I {} cp images/{}.jpg val_images/这里假设train.txt每行是去掉.jpg的文件基名。如果文件名里带着images/前缀,{}的替换位置要做相应调整。复制标签:
cp labels_yolo/*.txt train_images/ cp labels_yolo/*.txt val_images/复制完之后,把yaml改成:
path: datasets/cuhk_occ train: train_images val: val_images nc: 1 names: 0: person这样训练和验证才真正分离。我从一开始就直接做目录拆分,而不是去改代码适应划分文件,因为YOLO的数据加载逻辑在不同版本间有变动,自己控制目录结构最不容易翻车。
3.2 训练命令与关键超参
目录配好之后,用YOLOv5训练的命令骨架如下:
python train.py --data datasets/cuhk_occ.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150 --workers 4 --cache--data指向刚才的yaml,--weights用COCO预训练的yolov5s.pt,不要从空白权重开始。CUHK Occlusion Dataset只有1个类别、2125张图,从头训练需要几百轮才能收敛,用预训练权重可以大幅缩短时间。--img 640和原始图像分辨率接近,为了保召回率不建议设成更高,因为图像本身只有640×480,放大到1280并不会增加有效信息,只会拉高显存占用。
--batch 16在16G显存下跑yolov5s没问题,8G显存降到8。--epochs我设150,这个数据集规模小,150轮足够让mAP50稳定在0.85以上。--workers 4要看CPU核数,Windows下如果子进程报错,直接设0。--cache把所有图像缓存进内存,训练速度快很多,代价是额外占用几个G内存,机器内存小于16G就别加。
用YOLOv8的话,对应命令是:
yolo train data=datasets/cuhk_occ.yaml model=yolov8s.pt imgsz=640 batch=16 epochs=150 workers=4 cache=True注意YOLOv8的yaml里路径写法更宽松,但同样要求train和val是两个不同目录。我测试过同一批数据在v5和v8下的差异,v8默认开启更多数据增强,训练时损失曲线更抖,但最终mAP差别不大。如果你做课程设计,用哪个版本都行,关键是配套代码要能跑通。
3.3 训练过程中的日志怎么看
训练开始后终端会逐轮打印box_loss、cls_loss、dfl_loss、mAP50等指标。前10轮的loss快速下降是预训练权重正常迁移,不值得焦虑。重点关注第20轮以后:如果mAP50还在0.3以下,检查是不是学习率设置错误或数据目录没配对;如果mAP50超过0.85但验证集损失不再下降,说明可以提前停止。
YOLOv5默认用余弦退火学习率,前几轮会从极低值热身爬升,这时打印的学习率可能在0.0001附近,是正常的。训练结束后,runs/train/exp目录下会生成best.pt和last.pt,best.pt是按验证集mAP选出的最优权重。我习惯再跑一遍纯验证:
python val.py --data datasets/cuhk_occ.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf 0.001--conf 0.001是把置信度阈值降到极低,让所有框都参与mAP计算,得到的是无阈值截断下的真实指标。部署时我们再回到0.25,所以这个验证结果要高于部署时的实际效果。输出会包含每个类别的mAP50和mAP50-95,对这个单类别数据集,mAP50在0.9左右、mAP50-95在0.6左右都是合理范围。
3.4 模型大小怎么选:s/m/l对遮挡场景的影响
YOLO系列有n/s/m/l/x多个规格。我的经验是,2125张图像这个规模,用yolov5s或yolov8s是最平衡的选择。n太小,对遮挡行人的特征提取容易崩;m和l需要更长的训练轮数,否则预训练权重的优势会被小数据集淹没。如果你显存很富裕,可以试yolov5m,但要把epochs提到200,并配合更多数据增强。
实际对比过一次:同一数据集上训练50轮,yolov5s的mAP50是0.82,yolov5m只有0.78,因为m模型在50轮还没收敛完。数据集规模小的时候,模型参数量不是越大越好,推理速度反而更快下降。做课程设计就用s,讲道理也够;做性能比较的毕业设计,可以同时训练s和m,然后画损失曲线对比。
4. 自己动手做一遍VOC转YOLO:转换脚本与归一化逻辑
4.1 为什么资源里YOLO和VOC要同时给
这个压缩包让你省去转换的麻烦,但理解转换逻辑的好处在于:当你以后从网上下到只有VOC标签的数据集时,不会被卡住。VOC格式是目标检测界的通用语言,LabelImg、LabelMe这些工具默认导出XML;YOLO格式是训练器的私有格式,不同版本还可能要求不同的标签目录结构。提供双格式相当于给了你一把保险锁,YOLO标签用坏了可以从XML重新生成。
另一个原因是验证标注一致性。你自己可以把XML转成TXT,然后和labels_yolo里的内容逐行比对。如果发现同一个框的坐标差超过0.001,说明原始标注或转换过程有精度损失。这个资源里的两套标签基本是同一套原始标注自动导出的,坐标差极小,这种“双格式互验”的思路也值得用在你之后的项目上。
4.2 转换脚本的核心代码
如果你手里只有VOC格式,下面的脚本可以直接复用。它遍历一个目录里的所有XML,解析出图像尺寸和每个框,归一化后写TXT:
import os import xml.etree.ElementTree as ET voc_dir = "labels_voc" yolo_dir = "labels_yolo" class_names = ["person"] os.makedirs(yolo_dir, exist_ok=True) for xml_name in os.listdir(voc_dir): if not xml_name.endswith(".xml"): continue tree = ET.parse(os.path.join(voc_dir, xml_name)) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(name) bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("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 lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") base = os.path.splitext(xml_name)[0] with open(os.path.join(yolo_dir, base + ".txt"), "w") as f: f.write("\n".join(lines))这段脚本的逻辑就是“读XML -> 提取box -> 归一化 -> 写TXT”。x_center的计算方式是(xmin+xmax)/2,然后再除以img_w。注意这里必须先除以2再除以宽,不要写成xmin/img_w + xmax/img_w/2,虽然数学上等价,但前者更容易一眼看出问题。
XML里有时有<difficult>标签,表示这个目标很难辨认。本数据集没有这个字段,但如果你转换其他数据集,建议先过滤掉difficult=1的框,否则训练时会把难样本当成负样本,影响收敛。
4.3 划分验证集时容易漏的细节
ImageSets/train.txt和val.txt只是文件名的名单,不能直接丢给YOLO。我最初偷懒直接把yaml里val指向images,导致训练集和验证集完全重合,最终mAP高达0.97,实际换张图检测效果惨不忍睹。后来花10分钟做了目录拆分,指标才回归正常。
拆分命令我在第3章已经给过,这里补充一个坑:复制标签时如果直接用cp labels_yolo/*.txt把全部标签复制到train_images,那你交给模型的验证标签其实包含了训练图的标签,这是另一个数据泄漏。必须用train.txt名单来筛选标签,而不是一股脑全复制。正确做法是用循环:
while read name; do cp "labels_yolo/${name}.txt" "train_images/" done < ImageSets/train.txtwhile read name; do cp "labels_yolo/${name}.txt" "val_images/" done < ImageSets/val.txt这里${name}.txt的name是文件基名,和图像文件名一致。如果名单里带路径,需要先去掉目录前缀。我一般会再用wc -l检查两份TXT的标签数与图像数是否一致,避免复制时漏掉一只猫。
4.4 坐标越界与空文件的边界处理
转换脚本里还有一个隐藏问题:某些XML的bndbox可能超出图像边界,比如xmax等于图像宽,或ymin出现负数。理论上YOLO训练能容忍轻微越界,但越界太多会在计算IoU时产生NaN。我在转换时都会加一个边界裁剪:
xmin = max(0, min(xmin, img_w - 1)) xmax = max(0, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(0, min(ymax, img_h - 1))这样保证框坐标始终限制在图像范围内。另外,如果某个XML里的object全部被过滤掉,生成的TXT会是空文件。YOLO训练时遇到空标签会跳过这张图,所以也不用太担心,但最好记录一下哪些图没有标签,方便后续排查。
5. 避坑指南:命中率低、路径报错、类别错位的排查记录
5.1 训练时提示No labels found
现象:执行训练命令后,控制台输出Found 0 images and 0 labels,训练直接终止。
原因:yaml里path写错,或YOLO默认只在图像目录下找同名TXT,而你没有把标签和图像放在同一目录。
解决:最稳妥的是重新组织目录,让标签和图像混在同一个文件夹,YOLO就会自动匹配:
cp labels_yolo/*.txt images/这样做之后不需要额外配置`label`字段,路径歧义最少。如果你同时有VOC格式的XML,不要复制到images里,YOLO虽然不会读XML,但会干扰你后续用脚本找文件。5.2 验证集mAP高但实际检测效果差
现象:训练150轮后mAP50到0.91,但拿手机拍摄的街景测试,行人漏掉近一半。
原因:CUHK Occlusion Dataset的图像是从固定摄像头采集的固定场景,背景和姿态分布窄。模型学会了识别“这个场景里的行人”,但不具备跨场景泛化能力。验证集和训练集同分布,指标自然虚高。
解决:训练时主动加大数据增强,让模型看到更多变化。在YOLOv5里可以直接在命令行加:
python train.py --data datasets/cuhk_occ.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150 --hyp data/hyps/evolve.yamldata/hyps/evolve.yaml是YOLOv5自带的强化增强配置,里面hsv_h、hsv_s、degrees、translate、scale都比默认值大,相当于用数据量换取泛化性。做课程设计可以不加这种配置,但如果你是冲着真实场景部署去的,这一步值得做。
5.3 标签类别ID错位
现象:目标检测训练时损失不降,或者预测框把整张图都框住。
原因:有一次我手动改过labels_yolo,把类别ID从0改成了1,但yaml里的names没有同步改。更隐蔽的情况是模型加载了COCO预训练权重,COCO有80个类别,person的类别ID是0,而你的数据集只有一个类,如果用某个改过的权重,类别ID映射顺序会完全错乱。
解决:训练前先统计标签里的类别ID:
cat labels_yolo/*.txt | awk '{print $1}' | sort -u输出必须是0。如果出现1,把标签里所有第一列改成0,或者在yaml里新增一个空类别占位。最简单的方法是用sed批量替换:
sed -i 's/^1 /0 /' labels_yolo/*.txt但在替换之前,一定要确认标签原来是否正确。这个数据集本身类别ID是对的,切忌盲目替换。
5.4 遮挡行人漏检严重
现象:验证时图像里有半个人被自行车挡住,模型置信度只有0.08,被NMS抑制后直接消失。
原因:遮挡行人的可见部分只有一半,YOLO的anchor框预设计成了全身比例,对部分特征匹配不上。数据集里的occ样本占了相当比例,但普通训练策略没有特别强化这部分。
解决:推理时降低置信度阈值,从0.25降到0.1,同时把NMS的IoU阈值从默认0.45提高到0.6。重叠框如果IoU超过0.45就会被抑制,遮挡场景里两个行人框重叠度高时,其中一个会被错误删除。我用检测脚本复测时会这样调用:
python detect.py --weights best.pt --source val_images --conf 0.1 --iou 0.6conf低会带来更多误检,但结合检测目标“行人”的语义约束,误检框通常比漏检容易过滤。如果你是做毕设,还可以在模型结构上加一个遮挡感知分支,不过那就是另一个改模型的工程了。
5.5 训练中断后怎么续传
现象:跑了一半,显存不足或手动Ctrl+C中断,重新跑又要从头来。
原因:YOLOv5在每轮结束时会保存last.pt,但很多人忽略这个文件。重新训练时又重新加载初始权重,前面的时间全部浪费。
解决:从上次的last.pt继续:
python train.py --data datasets/cuhk_occ.yaml --weights runs/train/exp/weights/last.pt --img 640 --batch 16 --epochs 200 --resume--resume会读取训练状态,连同优化器、学习率回调一起恢复。注意--epochs要写完整计划的总轮数,YOLO会根据已有轮数自动补足。这个数据集只有2125张图,重训一次也就二十分钟,但如果换成几万张图的数据集,续传能帮你省下大半天。
6. 进阶用法:用训练结果做视频行人计数与遮挡漏检优化
6.1 把模型接上OpenCV视频流检测
训练好的best.pt不要只做静态图片测试,接上视频才是真实场景。YOLOv5自带detect.py,一行命令:
python detect.py --weights runs/train/exp/weights/best.pt --source demo.mp4 --conf 0.25 --iou 0.45如果你把--source改成0,就变成了读取摄像头。我实际测试时发现,--conf 0.25对这个数据集训练出的模型偏高,因为遮挡行人的置信度天然偏低。建议调到0.15,然后用下游跟踪逻辑去过滤误检。--iou保留默认0.45时,密集人群里两个行人框会互相打架,改成0.6更稳。
6.2 对遮挡目标的NMS阈值调整技巧
之前我用默认参数跑一个商场视频,两个人挨着走,检测框只出一个。问题就出在NMS把IoU大于0.45的第二个框当成了重复检测。把--iou调到0.6后,召回率明显提升。代价是会产生更多重叠框,但配合深度排序跟踪算法,按框中心的运动轨迹过滤,效果比单纯调NMS要好。
我后来习惯在检测脚本里加一个“跨帧确认”步骤:连续3帧以上都检测到同一个ID的目标才保留,这样能把单帧误检压下去。这个方案不依赖模型改动,纯后端逻辑,适合时间有限的课程设计。
6.3 一份复现总检查清单
把完整流程压缩成清单,每做一步打一个勾:
- 解压后跑
find三条命令,确认图像、TXT、XML各2125个; - 抽查3组同名标签,核对类别ID和归一化坐标;
- 按
ImageSets里的名单拆分train_images和val_images,标签也要分拆; yaml里train和val分别指向两个目录,nc: 1,names里只有person;- 用
yolov5s.pt预训练权重,img=640,batch=16,epochs=150; - 训练完用
val.py --conf 0.001验证,确定mAP50基线; - 部署到视频流时,
conf=0.15,iou=0.6,再配合跟踪过滤。
这套流程是我拆过几十个数据集后固化的习惯。最早我拿到类似资源时,总想着省事直接开训,结果在标签路径和数据划分上连续翻车,浪费的时间远超过重新转换标注。从那以后,我每次换新数据集都强制自己先花十分钟跑一遍检查和目录重组,再开始训练。这个CUHK遮挡数据集看着简单,但同样的流程对你之后处理其他VOC数据集完全适用。希望帮到你。
本文还有配套的精品资源,点击获取