ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VOC车辆检测数据集:XML与TXT双格式详解及YOLO训练避坑指南

VOC车辆检测数据集:XML与TXT双格式详解及YOLO训练避坑指南 简介面向目标检测模型训练与验证的车辆检测数据集涵盖公交、轿车、SUV、出租车和卡车五类常见车型可接入YOLO、SSD等主流算法流程适合算法研究者、竞赛选手以及需要自建数据集的开发者使用。压缩包共2000个文件主要包括699张JPG车辆图片、700个TXT标签和699个XML标签TXT与XML分别存放在独立文件夹中对应YOLO与VOC两种常用标注格式整个压缩包约466.3MB。目前该数据集已有961人学习下载可用于车型识别、车辆统计、检测算法效果对比等实验任务。TXT标签便于直接参与YOLO系列训练流程XML标签保留标准结构方便迁移至其他检测框架或二次转换图片覆盖多种拍摄场景五类车型均有较多样本能有效支撑模型训练、验证与部署前评估大幅节省数据采集与人工标注时间。1. VOC格式车辆检测数据集双格式标注怎么读才不翻车做目标检测的绕不开VOC格式的数据集尤其是车辆检测这种经典场景。这份数据集最有价值的部分不是那些jpg而是每张图对应的两套标注xml一套、txt一套五个类别bus、car、suv、taxi、truck覆盖城市道路和停车场的绝大多数机动车形态。我拿到手的第一反应是训练前必须想清楚一件事——这份数据的标注到底该信哪一份。xml保留完整VOC语义供Faster R-CNN这类检测框架使用txt是YOLO系训练直接吃的归一化坐标。两套格式并存既是方便也是陷阱读错一处训练白跑。适合谁用刚接触检测、需要一份干净数据跑通pipeline的新手以及要做车辆检测预研、想快速评估模型表现的工程师。下面按读标注、转格式、做体检、避坑、看指标这条线把这份资源完整拆一遍。2. 标注文件长什么样xml与txt双轨格式逐字段拆解图片只是素材标注才是这台模型的燃料。图片只能告诉你“路上有车”标注才能告诉模型车在哪里、是什么车型。这份数据集把同样的标注内容用xml和txt各存一份初看冗余实际是故意的xml保留完整的VOC语义给人看、给VOC系训练脚本用txt把坐标归一化给YOLO系训练直接吃。下面把两部分拆开看。2.1 五种车辆类别与文件命名规律从文件名就能把类别认个大半car_125.jpg、car_102.jpg前缀是carSUV_77.jpg、SUV_74.jpg前缀是SUV对照类别表还能看到bus、taxi、truck前缀的同名文件。这种“类别前缀序号”的命名方式在自采数据集里非常常见优点是定位某类样本时直接用通配符就能筛出来比如在终端执行ls SUV_*.jpg就能把SUV类所有图片单独列出来看。不过文件名前缀只能当参考真正的类别依据是xml里object的name字段。原因很简单人工收集图片时可能把一辆外观偏圆润的SUV归到car目录或者某辆停在路边的bus被拍成了truck视角这些误标只会在标注文件里暴露。所以后续所有类别统计都以xml里的name为准不以文件名为准这是处理这类数据集的一个基本习惯。类别上共5类bus、car、suv、taxi、truck正好覆盖城市道路和停车场最常见的机动车类型。对检测任务而言这5类外形差异够大——公交车是典型的大矩形目标小轿车是中尺寸目标SUV高度和长度接近轿车但轮廓更敦实出租车靠涂装和顶灯区分卡车有长车厢。用同一套训练参数能跑出可区分性各类别之间也不至于互相严重干扰作为目标检测入门或车辆场景预研的数据是够格的。2.2 VOC XML标注字段逐项拆解VOC格式的xml每张图对应一个文件文件名和jpg同名。打开任意一个xml结构基本如下annotation folderJPEGImages/folder filenamecar_125.jpg/filename size width1920/width height1080/height depth3/depth /size object namecar/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin420/xmin ymin300/ymin xmax1180/xmax ymax760/ymax /bndbox /object /annotation重点看几个字段。size节点里的width和height是原始图像的像素尺寸这是后面所有归一化坐标运算的分母读错一个数整张图的框全部偏移。object节点里的name是类别名必须和类别文件里的名字逐一对应任何一个字符不一致包括大小写差异都可能导致训练时类别错位或目标被忽略。bndbox里的xmin、ymin、xmax、ymax是目标框的四个像素坐标在VOC规范里xmax、ymax指框右下角像素的真实坐标值也就是说框占用的像素是xmin到xmax这个闭区间。转YOLO格式时这一两个像素要不要处理不同实现口径不同对训练影响很小但你心里得知道这个差异存在。还有两个容易忽略的字段truncated和difficult。truncated表示目标是否被图像边缘截断difficult表示目标是否属于难例比如严重遮挡或极小目标。很多自采数据集不填这两个字段有的直接省略节点。如果xml里根本没有difficult节点解析脚本必须做判空容错否则训练到一半报KeyError回头一查又是标注文件的老问题。这个坑在踩坑章节会再展开。2.3 YOLO txt标注与XML的映射关系另一份txt标注是给YOLO系吃的每行代表一个目标格式是固定的五个数值类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。对应到实际文件里大致长这样0 0.4167 0.4907 0.3958 0.4259 2 0.6234 0.3750 0.1875 0.2037第一个数字是类别id0对应类别文件里的第一行。如果你把类别文件按bus、car、suv、taxi、truck排序那么0就是bus2就是suv。后面四个小数全部是0到1之间的归一化值不携带任何像素单位。以第一行为例0.4167这个x_center用xml里的width1920反算回去0.4167 × 1920 800.06正好等于上面示例里(xmin xmax) / 2 (420 1180) / 2 800。这套映射关系搞明白后面写转换脚本就是套公式的事。txt和xml双份存在的最大价值是互相校验。把txt里的四个归一化值反算回像素后和xml的bndbox对一遍对得上说明数据干净对不上要么图片被resize过而标注没跟着重算要么转换过程出了差错。我一般把这种不一致当作数据质量的硬伤因为训练时模型读txt做错误分析时人看xml两边不一致会让人怀疑到底该信哪份这种数据宁可先修掉再进训练管线。3. 把数据集喂给YOLOXML转txt的换算公式与转换脚本如果只训YOLOtxt这份已经能直接用但我还是建议你把xml转一遍。理由有两个。第一自己跑一遍转换脚本等于给全部标注做了一次格式体检脚本能正常跑完说明每张图的xml结构完整、坐标没有越界这比肉眼抽查可靠得多。第二这份txt只覆盖现有的图片将来你补拍一批新照片新标注大概率还是以xml格式存下来到那时还是得转。与其临场翻旧代码不如现在就把转换逻辑吃透顺手沉淀成自己的工具脚本。3.1 从VOC像素坐标到YOLO归一化坐标计算口径先立住YOLO训练时读的txt要求相对图像的归一化坐标好处是不同分辨率的图片可以放进同一个batch模型不需要关心输入图片的绝对像素值。换算公式只有四条我把它们和VOC字段的对应关系放到一起看更直观VOC XML字段YOLO txt字段换算公式本例数值1920×1080xmin / xmaxx_center(xmin xmax) / 2 / width(4201180)/2/1920 0.4167ymin / ymaxy_center(ymin ymax) / 2 / height(300760)/2/1080 0.4907xmax - xminbox_width(xmax - xmin) / width(1180-420)/1920 0.3958ymax - yminbox_height(ymax - ymin) / height(760-300)/1080 0.4259四条公式全部要除一个分母图片宽高。所以解析xml时必须先把size节点里的width和height读出来否则算出来的坐标全是错的。这里最容易踩的坑是猜尺寸看到文件名里带1080p就觉得是1920×1080实际上很多图是原图直接存的宽高比不一定是16:9。老老实实从xml里读别猜。另一处口径问题在前面提过VOC里xmax是框右下角像素坐标用xmax - xmin算宽会比真实目标宽度多1像素对1920宽的图来说影响在0.05%量级训练场景可以忽略如果做精度敏感的测量任务可以改成xmax - xmin 1看你的项目要求我通常直接用不减1的版本。提示所有归一化坐标都以xml里size节点的width和height为分母训练时即使开了rect或multi-scale也不会帮你修正错误的归一化坐标源头错了后面全是错的。3.2 一个可以直接跑的转换脚本以下脚本把Annotations文件夹下的xml逐张转成YOLO格式txt。这份数据集本身自带txt你可以把脚本输出当参照和自带txt比对行数一致基本就能确认数据干净。import xml.etree.ElementTree as ET import os class_names [bus, car, suv, taxi, truck] def convert_xml(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) if img_w 0 or img_h 0: print(f[skip] {xml_path}: invalid size {img_w}x{img_h}) return lines [] for obj in root.findall(object): name obj.find(name).text difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue if name not in class_names: print(f[warn] {xml_path}: unknown class {name}) continue cls_id class_names.index(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) if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(f[warn] {xml_path}: bbox out of range for {name}) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) xml_dir Annotations txt_dir labels_yolo os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue base os.path.splitext(xml_file)[0] convert_xml(os.path.join(xml_dir, xml_file), os.path.join(txt_dir, base .txt))脚本里有几处是刻意加的防护。difficult为1的目标直接跳过因为VOC原版里这类目标要么严重遮挡、要么极小强行训练容易把梯度带偏转换时就该过滤掉。unknown class的warn用来抓xml里手滑打错的类名比如taxi拼成txxi这种错误会直接导致后续类别id错位。bbox out of range的warn查坐标是否越界越界框在mosaic这类需要裁剪拼接的增强里最容易产生空标注提前定位能省不少排查时间。输出格式里保留6位小数足够表达亚像素精度又不至于让txt文件体积膨胀。执行很简单python convert_xml.py跑完先对比txt文件夹里同名文件的行数和自带txt是否一致。一致大概率没转错不一致回到warn输出里逐条看基本就是类名拼写或difficult过滤导致的差异。3.3 类别文件与data配置的顺序敏感性YOLO系训练还需要一个类别列表和一个data配置文件。类别列表一行一个类名顺序必须和转换脚本里的class_names完全一致yolov5的yaml里这样写names: 0: bus 1: car 2: suv 3: taxi 4: truckdata配置则把训练路径、验证路径、类别数和类别名指给训练器train: ./images/train val: ./images/val nc: 5 names: [bus, car, suv, taxi, truck]三个地方必须保持同一套顺序转换脚本里的class_names、yaml里的names、txt里的类别id。最常见的翻车方式是把suv写在car前面结果所有标注为car的txt都成了1类模型训练完把轿车认成SUVmAP还显示正常——因为mAP只算预测框和真值框的匹配度类别错位它根本检查不出来。我一般拿到数据集先跑统计脚本确认各类别id数量正常再把上一章的txt反算回像素与xml对比做一遍两边对上才敢开训。数据源头错了后面调参全是白费功夫先花十分钟把格式这条链路同步好比训练时反复试参数划算得多。4. 训练前的数据体检类别统计与标注可视化很多新手拿到数据集就开训训完发现某一类mAP特别低回头查才知道这一类总共才十几张样本前面几轮训练全在做无效功。数据体检这一步花十分钟能省掉后面好几轮调参时间。体检分三块类别分布统计、标注框可视化检查、不平衡样本的处理方案。4.1 统计脚本先看各类别样本量再决定策略统计不是数文件名前缀而是按xml里object的name字段数脚本很短import os import xml.etree.ElementTree as ET from collections import Counter xml_dir Annotations counter Counter() for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text if name: counter[name] 1 for cls_name, count in counter.most_common(): print(f{cls_name}: {count})输出大概长这样具体数值以你实际统计为准car: 486 suv: 312 truck: 198 bus: 154 taxi: 112这个统计结果直接决定后面的策略。如果最少的类比如taxi样本量是car的三分之一左右属于可接受范围用默认参数训练就能有不错效果如果差距超过5倍就得在重采样、copy-paste增强或损失加权里选一种否则少样本类会被多样本类持续压制AP很难提上去。顺着这个脚本还能追加一个目标面积统计把每张图里目标的宽高乘像素宽高得到面积再除以图片面积算占比。车辆检测的典型难点就是bus、truck大目标与car、suv远距离小目标并存面积分布能直接告诉你数据里小目标占比有多高。4.2 标注可视化隐患都在框上统计只能看数量框画得准不准必须靠眼睛。我习惯在训练前随机抽20张图用OpenCV把xml里的框画出来存成预览图扫一遍就能发现三种典型问题坐标偏移、类别标注错、重复框。import cv2 import os import xml.etree.ElementTree as ET import random xml_dir Annotations img_dir JPEGImages out_dir preview os.makedirs(out_dir, exist_okTrue) sample_files random.sample( [f for f in os.listdir(xml_dir) if f.endswith(.xml)], 20 ) def draw_xml(img_path, xml_path): img cv2.imread(img_path) tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(float(bbox.find(xmin).text)) ymin int(float(bbox.find(ymin).text)) xmax int(float(bbox.find(xmax).text)) ymax int(float(bbox.find(ymax).text)) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, name, (xmin, max(ymin - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img for xml_file in sample_files: img_path os.path.join(img_dir, xml_file.replace(.xml, .jpg)) xml_path os.path.join(xml_dir, xml_file) preview draw_xml(img_path, xml_path) cv2.imwrite(os.path.join(out_dir, xml_file.replace(.xml, .jpg)), preview) print(f预览图已保存到 {out_dir}共 {len(sample_files)} 张)画框用绿色、类别名用红色是固定参数你要批量标注检查可以按类别换色。检查时重点看三处框是否包含完整车体而不带过多背景车头朝向不同时框是否都贴住边缘有没有一辆车被多个框重叠覆盖的情况。任何一处出问题优先改标注再训练不要指望模型自己学出正确边界标注错的数据对模型来说就是噪音。预览图生成后存在preview文件夹里用看图工具快速翻一遍比在终端里看日志直观得多。4.3 类别不平衡的三种处理思路如果统计发现某类样本明显偏少三种方案按需选。第一是重采样训练时让少样本类的图片在每个epoch里多出现几次yolov5里的--img-weights配合--multi-scale能做第一轮按类别权重的预选但实现比较隐晦不够可控。第二是离线copy-paste把少样本类目标从原图裁剪下来随机粘贴到其他图片的空白区域同时同步更新标注对车辆这种不依赖背景的目标效果很好但要注意粘贴时别遮住原有目标也别贴到道路外的异常位置。第三是损失函数加权在loss计算时给少样本类乘一个大于1的系数这个手段对中后期精度提升有限更适合作为微调手段。我的习惯是先用重采样把类别比例压到3倍以内再配合copy-paste把最少的类补上一部分。如果你的数据统计下来比例本身正常直接跳过这节开训完全没问题体检的价值就在于让你知道该不该花这个功夫。5. 踩坑记录VOC车辆数据集训练中的四个高频问题下面把我在类似车辆数据集上反复翻过的车集中记录一下。每条都是真实遇到过的场景按现象、原因、解决三段写你跑的时候遇到同样问题直接对照着查。5.1 一类mAP始终为零loss却正常下降现象训练loss正常下降验证集五个类别里四个都在爬升唯独某一类的mAP从第一个epoch开始就是0训练结束依然纹丝不动。这不是训练轮数不够而是数据或标注在训练前已经埋了雷。原因xml里difficult字段为1的难例被当成了正样本。这类目标通常是严重遮挡、极小或轮廓不清的车辆标注者打上difficult标记后VOC官方评测会直接忽略它们。但很多转换脚本没有过滤逻辑把它们全部转成txt送进训练模型在loss里反复被这些不可学的样本带偏表现为某一类mAP一直上不去。解决转换脚本里加一行过滤见第3章代码里这段difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue如果你的xml里连difficult节点都没有记得先判空再取整不要直接int(obj.findtext(difficult))那个写法在节点缺失时直接抛异常。从那以后我拿到带xml的数据集第一件事就是统计difficult样本数量做到心里有数。5.2 txt和xml坐标对不上而且差的是固定倍数现象同一个目标xml里xmin是420把txt的x_center反算回像素却是630整体往右下偏移而且每张图偏移比例接近。模型训练时框的位置整体偏val的mAP低得不正常。原因txt不是从这份xml对应的原图转出来的而是从另一份不同分辨率的图片标注转的。车辆数据集经常被二次压缩或resize原图1920×1080压成1280×720后如果标注没跟着重算归一化坐标会整体错位。我之前就遇到过图片被脚本批量resize但txt沿用旧标注的情况训练出来所有框都偏。解决抽三张图做反算验证把txt第一个坐标乘以当前图片的实际宽度看是否等于xml里的x_center像素值。图片实际尺寸用cv2.imread读出来确认不要看文件名后缀猜。对不上的数据要么用对齐后的xml重新转一遍txt要么整张图作废千万别留着继续训。5.3 小目标漏检严重大目标一切正常现象训练完跑val公交车、卡车这类大目标检测效果不错远处的小轿车和SUV漏检率很高看起来像模型对大目标过拟合。原因图片尺寸不统一训练时统一resize到640×640远处车辆被压到十几个像素特征几乎消失。这类数据集的图片来源杂有行车记录仪截图、有监控画面、有手机随手拍分辨率差异很大直接统一resize会把小目标压没。解决训练时开--multi-scale让模型每个batch随机换输入尺寸等效于尺寸扰动增强同时把--img设成数据集中大多数图片的实际尺寸而不是默认值。如果小目标占比仍然高可以在mosaic增强阶段给小目标单独做一次上采样放大保证它在训练图片里至少占到几十个像素。对这份车辆数据集bus、truck是天然大目标car、suv里混着很多远距离小目标这条尤其值得重视。5.4 所有类别mAP都很低模型像随机猜测现象训练能正常跑完epoch数也不少但验证集所有类别的mAP都不到0.5比随机好不了多少。这时候人容易怀疑模型结构或超参先冷静查数据。原因类名大小写和空格造成的类别错位。Windows下SUV和suv在文件系统里不区分大小写但检测框架按字符串建类别字典两者是两个完全不同的键。如果xml里写的是SUV类别文件里写的是suv这个类就会被跳过或归到背景模型等于少学了一类。解决全流程统一类名口径。先跑脚本把xml、txt、yaml、class文件里的类名全部小写化并去掉首尾空格再跑一次类别统计确认五个类都能数到。快速检查xml里到底出现了哪些类名用这一条命令最直接grep -rh name Annotations | sort | uniq -c凡是和class_names有字符差异的全部在转换脚本的warn输出里能看见。类名这种细节错一个字符就是半天的无效训练排查时永远先把数据链路完整跑一遍再动模型。6. 验证这一份数据集mAP指标与检测结果可视化训完不能只看训练loss要拿独立验证集算指标再把检测结果画出来和标注并排看这一步才算真正把数据集验收掉。6.1 双指标判断训练效果我习惯同时看mAP0.5和mAP0.5:0.95两个指标。前者反映框大致位置对不对后者对边框精度要求苛刻两者差距大说明框的定位不够准。车辆检测里小目标多mAP0.5:0.95普遍偏低属于正常现象但如果mAP0.5很高、0.5:0.95极低优先怀疑标注框本身就不贴边回到第4章的可视化检查重新过一遍标注。6.2 检测可视化与置信度阈值跑val模式输出带预测框的图片用yolov5的detect是python detect.py --weights runs/train/exp/weights/best.pt \ --source data/images/val \ --conf-thres 0.25 --iou-thres 0.45conf-thres设0.25是兼顾召回和误检的常规值正式测试时分别试0.1、0.25、0.5三档观察误检和漏检的权衡。如果0.25下大量出现低置信度框说明模型对目标把握不足优先加数据或增强不要盲目堆训练轮数。数据集说明里的参考文章贴过这套数据的检测效果示例把你自己跑出来的可视化结果和它对一遍水平接近说明流程没跑偏差得远就按前几章逐项复查数据链路。6.3 copy-paste增强的一个小参数对少样本类做copy-paste时粘贴的目标要做小幅缩放和旋转别直接原尺寸贴上去否则模型会记住固定尺寸和角度泛化反而变差。缩放范围0.8到1.2、旋转±15度这两个参数是我试过比较稳的范围。从那以后我每次拿到新的检测数据集都会强制走一遍“读标注→转格式→统计→可视化→试训→可视化验证”的完整流程哪怕是只有几十张的小数据集也不跳过。这套流程帮我在多种数据集上少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表