ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从images_xmls到yolov5:三轮车违规停放数据集转换与训练实战

从images_xmls到yolov5:三轮车违规停放数据集转换与训练实战 简介面向YOLOv5机器视觉识别与非机动车违规停放场景的三轮车标注数据集分包当前压缩包收录的是三轮车类别中tricycle1子类的全部数据共957张JPEG原图与953个XML标注文件文件总数1910个压缩包大小82.19MB。图片与标注一一对应XML采用VOC格式包含目标框类别与位置信息可直接用于YOLOv5训练、验证或迁移学习。该分包属于完整非机动车数据集的第一类完整体系还涵盖自行车、电动车、三轮车其余品牌子类方便按需分批下载。已有295人学习下载该资源适合正在积累非机动车检测样本、构建违规停放识别模型的研究者或开发者。拿到数据后可直接整理为YOLO格式并配合数据增强与模型微调完成三轮车检测任务有效解决真实场景中非机动车目标多样、标注成本高的问题。1. 非机动车违规停放识别为什么非得从三轮车“images_xmls”开始城市管理里的非机动车违规停放属于典型的“目标小、场景杂、要求却明确”的机器视觉识别问题。而三轮车tricycle又是里面最让检测模型头疼的种类车斗与货架形态差异极大颜色和背景混杂白天强光与夜晚阴影下轮廓会完全变形。这个标题里把“yolov5”和“已标注数据集”放在一起其实已经指明了落地路径——不是从零搭网络而是把手头已有的“images_xmls”也就是VOC格式的图片与XML标注转换成YOLO训练格式再把模型练出来。对刚接触yolov5训练自己的数据集的从业者来说这条路最难的不是模型结构而是数据工程和参数调优。这篇文章就是围绕这条路径把数据转换、训练命令、超参数调节和部署踩坑一次说清楚目标只有一个让你能尽快拿到一个在真实街景里也能稳定工作的违规停放检测器。2. 从images_xmls到yolov5训练格式转换脚本与四个边界坑2.1 VOC格式与YOLO格式的根本差异拿到“tricycle1_images_xmls”这类已标注数据集时首先要确认一件事标注文件的坐标系和存储结构。常见做法是图片放在images目录、同名XML放在xmls目录XML里的object节点保存每个目标的类别名和bndbox边界框xmin、ymin、xmax、ymax。这本质上是Pascal VOC格式坐标是绝对像素值。而yolov5训练需要的是每张图对应一个同名的.txt文件每行格式是class_id x_center y_center width height并且四个坐标值都归一化到0到1之间。这里class_id必须是整数从0开始计数顺序要和data/*.yaml里给出的类别列表一致否则训练出来的模型类别标签会全部错位。转换是整个流程里最容易出“暗坑”的一步。比如XML里类别名可能包含空格或中文注释直接拿来做目录名会出错又比如某些图片是EXIF旋转过的标注框坐标是按原始像素标的转换前必须先统一图片朝向。常见做法是先写一个小脚本把图片和XML成对读取先检查size节点里的width和height再计算归一化坐标最后按train/val随机划分输出。这里建议把脚本写成一次性工具不做太重的封装保存好原始数据集方便回滚。2.2 转换脚本的最小实现用python写一个voc2yolo.py核心逻辑如下import os import glob import random import xml.etree.ElementTree as ET # 类别列表顺序就是训练时的class_id CLASSES [tricycle, electric_bicycle, bicycle] def convert_xml_to_yolo(xml_path, out_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) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue # 跳过无关类别宁缺毋滥 class_id CLASSES.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.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 防御坐标超出[0,1]的框说明标注越界或图片尺寸读取有误 if not (0 x_center 1 and 0 y_center 1): print(f[warn] {xml_path} 中有越界框已跳过) continue lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if lines: with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines) \n) def main(): random.seed(42) xmls_dir xmls images_dir images out_labels_dir labels os.makedirs(out_labels_dir, exist_okTrue) xml_paths glob.glob(os.path.join(xmls_dir, *.xml)) random.shuffle(xml_paths) split_idx int(len(xml_paths) * 0.8) for i, xml_path in enumerate(xml_paths): base os.path.splitext(os.path.basename(xml_path))[0] img_path os.path.join(images_dir, base .jpg) if not os.path.exists(img_path): print(f[error] 缺少图片: {img_path}) continue out_txt os.path.join(out_labels_dir, base .txt) convert_xml_to_yolo(xml_path, out_txt) # 写入train/val划分文件 list_file train.txt if i split_idx else val.txt with open(list_file, a, encodingutf-8) as f: f.write(os.path.abspath(img_path) \n) print(转换完成已生成 labels 目录与 train.txt/val.txt) if __name__ __main__: main()这段代码里最关键的是归一化公式x_center (xmin xmax) / 2 / img_w它把绝对像素坐标变成相对坐标。很多新手第一次转换时会把xmin和ymin直接除以图片宽高结果框全部偏移到左上角训练出来的模型根本没法收敛。代码末尾把train.txt和val.txt写出来这是yolov5训练时--data参数需要的路径列表文件每一行指向图片的绝对路径模型会去找同名的.txt标签文件。2.3 数据目录结构和YAML配置文件yolov5训练时并不强制你把图片和标签放在同一个文件夹只要标签文件与图片同名同目录即可默认找到。我一般会用下面的目录结构dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── train.txt ├── val.txt └── data.yamldata.yaml里指定类别和路径train: ./dataset/train.txt val: ./dataset/val.txt nc: 3 names: [tricycle, electric_bicycle, bicycle]注意nc必须和names的长度一致否则训练会直接报错。这里有个非常容易被忽略的坑如果train.txt里是绝对路径那么迁移到别的机器时路径就失效了。我习惯把train.txt写成相对路径或者直接用images/train的目录路径交给yolov5自动扫图。2.4 已标注数据集的边界检查拿到手的数据集未必都是干净的尤其是“三轮车”这种特定类别标注质量会直接影响最终效果。建议在训练之前跑一遍脚本检查三个指标一是每张图的目标数量有的图可能只有1个框有的图有20多个极端不均衡会导致模型对少样本过拟合二是标注框面积占比如果大量框小于图片面积的2%说明这些目标过小机器视觉识别难度会显著上升需要考虑切图或者使用更大分辨率输入三是类别数量如果三轮车样本太少比如只有600张而电动车有18000张就需要考虑数据增强或者对三轮车类别做过采样。这些检查不需要写复杂脚本用几行python统计就行import os from collections import Counter label_dir labels/train class_counter Counter() box_area_ratio [] for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt)) as f: for line in f: parts line.strip().split() class_counter[int(parts[0])] 1 w, h float(parts[3]), float(parts[4]) box_area_ratio.append(w * h) print(各类别数量:, class_counter) print(平均框面积占比:, sum(box_area_ratio) / len(box_area_ratio))如果发现类别严重不均衡后面的训练参数就要做针对性调整后面章节会展开。3. yolov5训练自己的数据集网络结构、超参数与必要条件3.1 yolov5网络结构图里的三个关键部件为什么适本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进