
简介这份斑马线人行横道检测数据集面向计算机视觉目标检测入门与进阶学习者可用于解决模型训练中标注数据难获取、格式不统一的问题。资源主体为一百九十二张jpg原图并配套对应的VOC格式xml与YOLO格式txt标注文件图片统一采用矩形框标注zebra_crossing类别共五百四十二个目标框覆盖典型的人行横道视角。资料包共五百七十八个文件压缩后大小约三十四兆字节主要文件类型为jpg图片、xml标注和txt标签xml与txt可分别对接VOC系和YOLO系训练框架省去手动格式转换步骤。需特别说明图片为模拟拍摄而非真实街道场景适合算法验证与流程练习不适合追求真实场景泛化效果的项目。目前已有178人浏览学习。所有标注均使用labelImg完成类别和边界框信息规范完整且压缩包目录结构简洁便于按批次划分训练集与验证集下载后即可开展目标检测训练与评估。1. 斑马线人行横道检测数据集VOCYOLO格式192张图、542个框能不能拿来训模型“斑马线人行横道检测数据集VOCYOLO格式192张1类别”这几个关键字是我在给辅助驾驶项目找数据时搜到的。下载解压之后看了一圈192张jpg、192个xml、192个txt1个类别zebra_crossinglabelImg用矩形框标了542个目标。数据量不大而且是模拟拍摄图不是真实街景。直说结论它不适合直接当量产模型的训练集但特别适合三类人——刚入门目标检测想搞懂VOC和YOLO标注差异的、要拿小数据集验证训练pipeline能不能跑通的、准备自建数据集但还没摸清格式转换的。这篇文章就顺着“怎么看清格式、怎么验证质量、怎么绕开坑”往下走。2. 数据集结构先看清VOC与YOLO两套标注的对应关系解压拿到手最忌讳的是直接开训。先摸清包里的三类文件是怎么对应的再决定下一步。这个数据集同时给了VOC和YOLO两份标注意味着你既可以用labelImg生态里的脚本做可视化也可以直接丢给yolov8训练两条路都通但要先弄清它们之间的关系。2.1 VOC侧jpg、xml与labelImg的矩形框约定labelImg是VOC时代最常用的标注工具它保存出来的xml走的是Pascal VOC结构。先贴一个真实xml的骨架annotation filenamefirc_rxd_47.jpg/filename size width640/width height640/height depth3/depth /size object namezebra_crossing/name bndbox xmin120/xmin ymin210/ymin xmax460/xmax ymax380/ymax /bndbox /object /annotation这段xml的关键字段filename是图片名size里的width和height是原图尺寸bndbox里xmin/ymin/xmax/ymax是标注框左上角和右下角的像素坐标。注意这里只列了一个object节点实际一个xml对应一张图一张图里可能有多个斑马线区域object节点会相应地重复出现。训练yolov8自己的数据集时data.yaml里的names写的就是zebra_crossingxml里的name要和它严格一致大小写、下划线都不能改。文件类型数量内容在训练中的角色jpg192原始图片模型输入xml192VOC像素坐标与目标类别可视化、格式转换的数据源txt192YOLO归一化坐标yolov8/5直接读取的训练标签再强调两个容易看漏的点txt文件的内容里不含图片路径它只存坐标和类别文件名才是和jpg、xml关联的主键这份数据集也没有任何分割掩码路径去找segmentation相关节点属于白费功夫。2.2 YOLO侧txt一行一个目标坐标是怎么从xml换算的YOLO格式txt的每一行是class x_center y_center width height。class在yolov8里从0开始所以只有一个类别时zebra_crossing对应的class id就是0而不是字符串。换算公式如下x_center (xmin xmax) / (2 * W) y_center (ymin ymax) / (2 * H) width (xmax - xmin) / W height (ymax - ymin) / HW和H来自xml的size节点。坐标必须归一化的原因在于yolo损失函数在特征图的相对位置上回归边界框同一张图缩放到640×640还是1280×1280归一化坐标都不会变模型才能吃任意输入尺寸。如果给了像素坐标模型会把几百像素的偏移当成特征图上的偏移去回归梯度算出来通常是nan。下面贴一段解析xml并按上述公式输出YOLO格式文本的python脚本我每换一个数据集都会先跑一遍import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path): root ET.parse(xml_path).getroot() W int(root.find(size/width).text) H int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_id_map: continue cls_id class_id_map[name] b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) cx (xmin xmax) / 2.0 / W cy (ymin ymax) / 2.0 / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) return lines if __name__ __main__: class_id_map {zebra_crossing: 0} print(voc_to_yolo(Annotations/firc_rxd_47.xml, labels/firc_rxd_47.txt))逻辑说明class_id_map是类别到数字id的映射只有一个类别时写死成0即可。cx/cy/w/h保留6位小数yolov8训练时读float不会因为精度翻车而0.000001的误差换算到640px也就是0.4px左右不影响框的贴合度。xml里的xmin这些值有的是整数有的是字符串统一转float再参与除法可以避免整除带来的坐标偏移。这个脚本跑完还不算完我在第3章还会用另一个脚本把txt反算回像素坐标和xml做交叉验证这一步别跳过。常见做法是直接把整个Annotations目录循环跑一遍生成对应的labels目录。实际操作时注意xml目录里可能存在备份文件最好先过滤掉只处理严格以.xml结尾的文件。生成的txt命名要和jpg基名完全一致firc_rxd_47生成firc_rxd_47.txt这决定了后面训练集加载时能不能对得上。3. 用Python做三遍自检标注质量与格式一致性验证拿到标注数据就开训是新手最常犯的错。我一般会把下面三个脚本存成check_dataset.py每次换新数据先跑一遍全程依赖标准库不需要在PyCharm里额外pip install任何东西。三条检查分别解决“文件有没有缺”“坐标有没有越界”“两套格式是不是真的对应”这三个问题。提示三个脚本都基于文件名的基名做关联解压路径里不要有中文和空格Windows下尤其注意。3.1 数量一致性检查这个脚本把jpg、xml、txt三个目录下的文件名基名提取出来用set做差集。set比list查找快得多192张图差距不明显但自建数据集到几千张时这个习惯能节省不少时间import os base zebra_crossing # 解压后根目录按实际路径改 folders { jpg: os.path.join(base, JPEGImages), xml: os.path.join(base, Annotations), txt: os.path.join(base, labels), } def basename_set(folder, suffix): if not os.path.isdir(folder): return set() return {f[: -len(suffix)] for f in os.listdir(folder) if f.endswith(suffix)} jpgs basename_set(folders[jpg], .jpg) xmls basename_set(folders[xml], .xml) txts basename_set(folders[txt], .txt) print(fjpg{len(jpgs)} xml{len(xmls)} txt{len(txts)}) print(缺xml:, sorted(jpgs - xmls)[:5] or 无) print(缺txt:, sorted(jpgs - txts)[:5] or 无) print(多余xml:, sorted(xmls - jpgs)[:5] or 无) print(多余txt:, sorted(txts - jpgs)[:5] or 无)逻辑说明jpg是主键xml和txt的基名必须与jpg完全一致firc_rxd_47不行、firc_rxd_47_副本也不行。多余标注是好兆头的反面说明有张图被重复标过训练时数据会被重复采样验证集上出现同样内容就是数据泄漏mAP虚高。参数说明basename_set里的suffix只处理了.jpg有些数据集混着.png或.jpeg遇到那种情况把函数改造成接收一个后缀列表即可。3.2 VOC边界框越界与空框检查xml是labelImg直接生成的一般不会有大问题但笨办法最可靠。脚本遍历所有xml检查size节点是否存在、边界框是否越界、是否有空框import os import xml.etree.ElementTree as ET xml_dir Annotations problems [] for fname in os.listdir(xml_dir): if not fname.endswith(.xml): continue root ET.parse(os.path.join(xml_dir, fname)).getroot() size root.find(size) if size is None: problems.append((fname, 缺size节点)) continue W int(size.find(width).text) H int(size.find(height).text) for obj in root.findall(object): b obj.find(bndbox) if b is None: problems.append((fname, 缺bndbox)) continue xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) if xmin xmax or ymin ymax: problems.append((fname, 空框面积0)) if xmin 0 or ymin 0 or xmax W or ymax H: problems.append((fname, 越界)) if problems: for fname, err in problems[:10]: print(fname, err) else: print(所有xml坐标边界通过)逻辑说明labelImg画框时手抖一下可能存出xmin等于xmax这种退化框yolo系训练时w或h为0loss直接变nan。越界框在letterbox缩放后会被截掉一部分导致标注框和实际目标位置对不上可视化阶段迟早暴露。参数说明这里把“越界”判成xmax大于图片宽度或ymax大于高度严格模式。如果你的训练代码对越界框做了裁剪容错可以适当放宽但我建议还是先严格查一遍把有问题的图单独挑出来而不是让训练脚本悄悄吞掉。3.3 YOLO txt与VOC xml交叉验证前面只检查了VOC侧还不能保证txt和xml是一致的。我见过不少数据集xml是准的txt是别人用脚本批量转的转完对不上。这段脚本把txt的归一化坐标反算回像素再和xml逐字段比较import os import xml.etree.ElementTree as ET EPS 1.5 # 像素容差单位px def read_yolo(txt_path): with open(txt_path) as f: return [line.split() for line in f if line.strip()] for fname in os.listdir(labels): if not fname.endswith(.txt): continue base fname[:-4] xml_path os.path.join(Annotations, base .xml) if not os.path.exists(xml_path): continue xml_root ET.parse(xml_path).getroot() W int(xml_root.find(size/width).text) H int(xml_root.find(size/height).text) xml_boxes [] for obj in xml_root.findall(object): b obj.find(bndbox) xml_boxes.append((obj.find(name).text, float(b.find(xmin).text), float(b.find(ymin).text), float(b.find(xmax).text), float(b.find(ymax).text))) yolo_data read_yolo(os.path.join(labels, fname)) if len(xml_boxes) ! len(yolo_data): print(f{base}: 框数不一致, xml{len(xml_boxes)}, txt{len(yolo_data)}) continue for xml_box, yolo_line in zip(xml_boxes, yolo_data): cx, cy, w, h [float(v) for v in yolo_line[1:]] xmin cx - w / 2 ymin cy - h / 2 xmax cx w / 2 ymax cy h / 2 px_bbox [xmin * W, ymin * H, xmax * W, ymax * H] diffs [abs(xml_box[i 1] - px_bbox[i]) for i in range(4)] if max(diffs) EPS: print(f{base}: 坐标偏差 {max(diffs):.2f}px)逻辑说明反算时w/2受txt小数位影响6位小数换算到640px大约是0.4px的误差EPS给到1.5px比较合理不会误报。类别名也可以顺便比对yolo_line[0]在yolov8里通常是数字idxml_box[0]是字符串这一步要配合第2章里的class_id_map一起看这里就不重复展开了。框数不一致基本说明这个txt被增删过目标坐标偏差大说明转换脚本有bug必须回到第2章的换算公式逐项排查。三遍检查全部通过之后再谈训练的事。4. 避坑指南这五处翻车点我全替你踩过以下这些坑有些是这个数据集特有的有些是所有VOC转YOLO数据集的通病。按现象、原因、解决的顺序写你自己遇到时直接对照排查。4.1 坑一类别名大小写不一致loss迟迟不降现象头十几个epoch的loss下降极慢loss数值看起来也在掉但验证集mAP一直是0。原因xml里标注的是zebra_crossingyaml里names写成了Zebra_Crossing或zebraCrossing类别id错位模型把正样本当背景学。这种问题不会报错只会安静地让训练白跑几十个epoch。解决先跑第3章的3.3脚本把txt第一个字段和xml的name统一打印成一行肉眼扫一遍是否完全一致。我在这个数据集上就遇到过xml里某个文件把zebra_crossing标成了zebra_crossin差一个字符就是两种类别。yolov8的data.yaml写法是names: [zebra_crossing]只有一个类别时索引0映射到唯一元素写错就白训。每次训练前先打印出类别字典确认class id唯一再开始。4.2 坑二自己转的txt没归一化yolo损失函数直接nan现象某些epoch的loss打印成nan或者训练没崩但预测框全部挤在图像左上角。原因转换脚本没有除以W和H把像素坐标当归一化坐标喂给了yolo。yolo损失函数在特征图每个grid cell上做相对偏移回归输入中心点cx300这种值cell索引直接越界梯度算出nan或inf。解决严格按第2章公式cx/cy/w/h四项全部除以W或H。转完后随机抽三张图打开txt看一眼cx是否在0到1之间有一项超出就是没归一化。这是VOC转YOLO最容易出问题的一步没有之一。4.3 坑三xml的filename字段和实际文件名对不上现象自己写的可视化脚本画框框和斑马线错位某些图显示不出框程序直接报错。原因样本被重命名或resize过xml里filename字段还留着旧值labelImg另存的操作也会产生类似现象。解决别信任xml内部filename做可视化或转格式时直接用外部遍历到的jpg文件名作为主键。批量修正的思路是先读取xml里的size节点再强制把filename覆盖成jpg实际名称。这条是通用经验不适限于这个数据集。4.4 坑四192张图随机划分验证集可能一张正样本都没有现象yolov8训练正常loss正常下降但val/box_loss从一开始就很高val mAP恒为0。原因数据集里有一部分图是背景不含斑马线。192张按8:1:1随机划分样本量太小正样本没分进val的概率不低。解决先按txt是否为空把图片分成含目标和不含目标两组再从两组内分别抽验证集保证val里有正样本。固定seed之后每次跑同样的划分不要用随机seed不然踩了坑连复现都做不到import os import random random.seed(42) labels_dir labels pos [] neg [] for f in os.listdir(labels_dir): if f.endswith(.txt): path os.path.join(labels_dir, f) if os.path.getsize(path) 0: pos.append(f[:-4]) else: neg.append(f[:-4]) random.shuffle(pos) random.shuffle(neg) val_size max(1, len(pos) // 10) val pos[:val_size] train pos[val_size:] neg print(ftrain{len(train)} val{len(val)} 正样本val{len(val)})逻辑说明val_size按正样本数量的10%取整并保底1张确保val里至少有一张含斑马线的图。背景图全放进训练集是模拟场景的惯用策略给模型补充负样本比例。参数说明seed固定42换seed会改变划分结果正式实验前先记录下这个值。这份数据集的txt按摘要说明有192个但某些可能有内容为空的情况用getsize判断是否为空文件是最稳妥的。4.5 坑五7z解压后路径异常Windows与Linux表现不一致现象Windows下解压后文件名乱码yolov8读图直接路径报错Linux下输7z x报command not found。原因7z压缩包内的文件名编码在不同系统解释不同Windows默认解压工具按当前区域设置解码。Linux下没装p7zip解压命令当然不存在。解决Windows下建议用官方7-Zip软件右键解压到英文路径别用系统自带“全部提取”。Linux先装解压工具再执行apt install -y p7zip-full 7z x zebra_crossing.7z -ozebra_crossing参数说明-o后面紧跟目标目录注意-o和目录名之间不能有空格这是7z命令的固定写法。解压完成后检查一遍路径确保没有中文、空格和括号。yolov8在部分编译环境下用opencv读中文路径会失败表现就是train跑到一半报找不到图前功尽弃。5. 把这份数据用出价值先跑通流程再谈迁移模拟数据有个特点干净、光照单一、目标角度固定。用它训练出来的模型在模拟域内能收敛但到了真实街景mAP大概率会掉作者在说明里也明确不对精度作保证。所以我的用法是三层递进。第一步用yolov8n这类小模型跑通全流程。imgsz取640batch取8epoch先给30。重点不是刷mAP是确认数据加载、验证划分、评估指标这些环节一个不落地跑通。小模型在192张图上过拟合很快loss曲线会难看但框大致能贴合斑马线的走向就说明pipeline通了。第二步针对模拟场景做数据增强。mosaic开关建议保留它把四张图拼在一起等于变相扩大样本空间hsv的saturation增强幅度缓一点模拟图颜色本来就寡淡拉太大会让斑马线变成奇怪的颜色域。yolov8的超参数里hsv_s给0.3左右就够别学真实街景数据那套激进参数。第三步把这份数据和真实数据混训。模拟数据权重别给太高真实街景图加入后模拟数据的作用就变成了防过拟合的正则项。如果你想量化它有没有用修一个只用真实数据的baseline再来一个真实数据加本数据集的对照组各自训到同样epoch数比较val mAP和PR曲线。这个对比会直观告诉你模拟数据的贡献有多少也算给这个“不对精度作保证”的声明一个交代。最后说个习惯从那以后我拿到任何带“模拟拍摄”字样的数据集都会先跑第3章那三个自检脚本把格式验证通过当作开训的前提而不是训练翻车之后才回头查数据。希望帮到你。本文还有配套的精品资源点击获取