
简介目标检测是计算机视觉的核心任务之一其原理是通过算法自动识别图像中特定物体的位置和类别。这项技术的价值在于能够将人力从重复性的视觉检查中解放出来广泛应用于安防监控、自动驾驶、工业质检及智慧城市等场景。在智慧城市环卫领域精准的道路垃圾检测是实现智能巡检的关键。本文聚焦于构建高质量、可直接用于YOLO模型训练的专用数据集详细解析了从数据采集、清洗、标注规范制定到VOC格式转换及YOLO适配的全流程。针对数据稀缺的细分场景文章深入探讨了使用LabelImg工具进行高效标注、通过脚本实现VOC到YOLO格式的无损转换以及利用数据增强和基线模型验证来提升数据集质量等工程实践为构建垂直领域检测数据集提供了完整解决方案。1. 项目概述从零构建一个可用的城市道路垃圾检测数据集做计算机视觉的朋友都知道数据是模型的上限。最近在做一个关于城市环卫智能巡检的项目核心需求就是让模型能自动识别出路面上的各类垃圾比如塑料袋、饮料瓶、烟头、纸屑这些。市面上公开的通用目标检测数据集很多但专门针对“城市道路垃圾”这个细分场景的高质量、标注规范的还真不好找。要么类别不对要么数据量太少要么标注格式不统一。所以自己动手丰衣足食就成了最靠谱的选择。这个项目标题“数据集VOC格式城市道路垃圾检测数据集-892张可用yolo训练”就精准地描述了我们最终要交付的成果一个包含892张图像、采用PASCAL VOC格式进行标注、专门用于城市道路垃圾检测、并且可以直接用于YOLO系列模型训练的数据集。别看标题简单里面蕴含了从数据采集、清洗、标注、格式转换到最终校验的一整套完整流程。今天我就把这套流程的完整思路、实操细节以及踩过的坑毫无保留地分享出来。无论你是刚入门的新手还是想快速构建垂直领域数据集的老手这篇内容都能给你提供一条清晰的路径和大量可复用的经验。2. 核心需求解析与方案设计2.1 为什么是“城市道路垃圾”和“892张”首先明确场景边界至关重要。“城市道路”意味着我们的图像来源主要是城市街道、人行道、辅路等公共区域不包括室内、工厂、乡村道路或高速公路。这决定了数据的采集环境光照多变、背景复杂但相对规整和目标尺度中近距离目标不会过小或过大。“垃圾”则定义了我们的目标类别需要进一步细分。我们初步定义了四类plastic_bag塑料袋、bottle瓶子、cigarette_butt烟头、paper纸屑。类别不宜过多在数据量有限的情况下聚焦于最常见、最影响市容的几类更容易让模型学得好。至于“892张”这个数量它不是一个随意数字而是权衡了模型效果、标注成本和时间后的一个“起步甜点区”。对于YOLO这类现代检测模型在单一场景下每个类别有200-300个实例模型就能学到不错的特征。892张图平均每张图包含2-3个目标总实例数大约在2000-2500个左右分摊到4个类别基本能满足初期训练和验证的需求。它保证了数据集不是“玩具级别”同时又避免了初期投入过大。我们的目标是先做出一个“可用”的基线数据集后续可以通过数据增强、主动学习或继续采集来扩充。2.2 为什么选择PASCAL VOC格式数据标注格式有很多种常见的有COCO JSON、PASCAL VOC XML、YOLO TXT等。选择VOC格式作为中间标注格式是基于以下几个考量工具兼容性极佳市面上绝大多数标注工具如LabelImg、CVAT、Roboflow等都原生支持导出VOC格式。这给了我们最大的工具选择灵活性。信息存储完整VOC的XML文件结构清晰包含了图像尺寸、通道数、每个目标的类别名和精确的边界框坐标xmin, ymin, xmax, ymax。这是一种“无损”的标注格式便于后续任何形式的转换。作为转换枢纽YOLO训练虽然需要特定的TXT格式但从VOC格式转换到YOLO格式的脚本非常成熟且稳定。反之从YOLO格式转回VOC或其他格式则可能丢失信息如图像尺寸。因此将VOC作为源格式和归档格式是最稳妥的。注意千万不要直接用标注工具导出YOLO格式作为唯一存档。一旦需要调整图像尺寸或用于其他框架没有XML源文件会非常麻烦。始终坚持“VOC XML为源其他格式为衍生”的原则。2.3 整体技术路线图我们的目标是得到一组能直接用于yolov5、yolov8等框架训练的images图像和labelsTXT标注文件夹。整体流程分为五个核心阶段数据采集与初筛获取原始图像并进行初步的筛选和去重。数据标注与规范制定使用标注工具进行精细标注并统一标注标准。数据集划分与组织按照机器学习标准划分训练集、验证集和测试集。格式转换与配置文件生成将VOC XML转换为YOLO TXT格式并生成YOLO所需的data.yaml配置文件。数据增强与基线训练可选但推荐应用增强策略并使用YOLO进行一轮快速的基线训练验证数据集质量。下面我们就深入每个阶段看看具体怎么做以及有哪些必须注意的细节。3. 数据采集、清洗与标注规范实战3.1 图像来源与采集技巧我们的892张图像主要来自三个渠道并各有优劣公开数据集爬取与筛选从一些开源的数据集平台或论文附带数据中寻找包含“street”、“trash”、“litter”等关键词的图像。例如可以从TACO、Open Images等数据集中筛选出场景符合的图片。优点是速度快缺点是场景可能不完全匹配且需要仔细检查版权许可必须选择允许商业和研究使用的如CC BY 4.0。互联网合规爬取使用搜索引擎的图片高级搜索功能筛选“知识共享许可”或“允许商业使用”的图片并搜索“street litter”、“garbage on sidewalk”等英文关键词往往能获得质量较高的图像。务必严格遵守版权规定这是红线。自行拍摄这是质量最高、最匹配的方式。使用手机或相机在多个城市、不同时间段早中晚、晴阴雨、不同角度俯拍、平拍进行拍摄。关键技巧拍摄时注意多样性同一堆垃圾不要连续拍太多张确保图像清晰、对焦准确避免行人正面清晰人脸涉及隐私如无法避免需后期模糊处理。最终我们混合使用了前两种方法获得了约1200张原始图片为后续清洗留出了余量。3.2 数据清洗与去重拿到原始图片后不能直接标注必须清洗去除无效图片删除完全模糊、过暗、过曝、严重失焦的图片。一个简单的自动化方法是使用OpenCV计算图像的拉普拉斯方差图像模糊度设定阈值进行初筛。import cv2 def is_blurry(image_path, threshold100): image cv2.imread(image_path) if image is None: return True gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) fm cv2.Laplacian(gray, cv2.CV_64F).var() return fm threshold近似去重使用感知哈希pHash或结构相似性SSIM来识别和删除内容几乎相同的图片比如连拍产生的系列图。imagededup这个Python库非常好用。pip install imagededupfrom imagededup.methods import PHash hasher PHash() encodings hasher.encode_images(image_dirraw_images) duplicates hasher.find_duplicates(encoding_mapencodings) # 根据duplicates结果手动或自动保留一份删除其余统一预处理将所有图像缩放到一个合理的尺寸范围内如最长边不超过1333像素以减轻标注和后续训练的压力。同时统一转换为.jpg格式。经过清洗我们得到了约950张候选图像从中最终精选出892张用于标注。3.3 标注规范制定与工具使用标注规范是数据集的灵魂必须在开始前就达成一致边界框Bounding Box原则紧密度框体应紧紧包裹目标物体边缘空隙尽量小。完整性必须包含目标的全部可见部分。对于遮挡只标注可见部分。如果遮挡超过50%考虑舍弃该实例或根据上下文谨慎标注。对于小目标对于像烟头这类小目标确保框体清晰。如果图像中像素小于10x10可以考虑是否值得标注取决于应用需求。类别定义plastic_bag: 各种颜色的塑料袋包括半透明的。揉成一团的也算。bottle: 塑料瓶、玻璃瓶、易拉罐。无论立着还是躺着。cigarette_butt: 烟头。可能很小需要仔细标注。paper: 纸片、纸盒、纸箱碎片。湿的、脏的也算。歧义处理装有垃圾的塑料袋按plastic_bag标注。压扁的易拉罐按bottle标注因为我们的“bottle”类别实际代表“瓶罐”类容器。无法明确区分的垃圾团如果不属于以上四类则不标注。我们选择LabelImg作为标注工具因为它轻量、开源、支持VOC格式。操作流程是批量的将892张图片放入一个文件夹用LabelImg打开该文件夹使用快捷键W创建框CtrlS保存进行标注。每标注完一张会自动生成同名的XML文件。实操心得标注过程中建议2-3人轮换进行并定期交叉检查例如每人标注300张后交换100张进行复核能有效减少主观误差和疲劳导致的标注质量下降。标注进度管理可以用一个简单的表格来跟踪。4. 数据集构建、格式转换与YOLO适配4.1 数据集目录结构设计一个清晰的结构是后续所有工作的基础。我们采用如下结构city_street_trash_892/ ├── annotations/ # 存放所有VOC格式的XML标注文件 │ ├── 000001.xml │ ├── 000002.xml │ └── ... ├── images/ # 存放所有图像文件 │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── ImageSets/ │ └── Main/ │ ├── train.txt # 记录训练集图片名不含后缀 │ ├── val.txt # 记录验证集图片名 │ └── test.txt # 记录测试集图片名 └── labels/ # 后续生成存放YOLO格式的TXT标注文件 ├── 000001.txt ├── 000002.txt └── ...4.2 数据集划分策略892张图片我们按7:2:1的比例进行划分训练集Train: 约624张用于模型参数学习。验证集Validation: 约178张用于训练过程中监控模型表现调整超参数。测试集Test: 约90张用于最终评估模型的泛化能力在整个训练过程中绝对不可见。划分时务必使用随机分层抽样确保每个类别在训练、验证、测试集中的分布比例大致相同。可以使用scikit-learn的train_test_split两次来实现。import os import random from sklearn.model_selection import train_test_split # 获取所有图片文件名不含后缀 all_images [f.split(.)[0] for f in os.listdir(images) if f.endswith(.jpg)] # 假设我们有一个函数能根据图片名获取其包含的类别列表这里简化处理 # 为了分层我们需要知道每张图片的类别标签这里用随机模拟一个多标签列表 # 实际应用中你需要解析XML文件来获取每张图的真实类别 random.seed(42) # 固定随机种子确保结果可复现 all_labels [random.sample([0,1,2,3], krandom.randint(1,2)) for _ in all_images] # 模拟标签 # 第一次分割分出训练验证 和 测试集 train_val_names, test_names, train_val_labels, _ train_test_split( all_images, all_labels, test_size0.1, random_state42, stratify[str(l) for l in all_labels] ) # 第二次分割从训练验证集中分出验证集 train_names, val_names train_test_split( train_val_names, test_size0.2, random_state42, stratify[str(all_labels[all_images.index(name)]) for name in train_val_names] ) # 将划分结果写入到ImageSets/Main/下的txt文件中 def write_list_to_file(filepath, name_list): with open(filepath, w) as f: for name in name_list: f.write(name \n) write_list_to_file(ImageSets/Main/train.txt, train_names) write_list_to_file(ImageSets/Main/val.txt, val_names) write_list_to_file(ImageSets/Main/test.txt, test_names)4.3 VOC转YOLO格式核心脚本这是最关键的一步。YOLO需要的TXT文件格式为class_id x_center y_center width height其中坐标是相对于图像宽度和高度的归一化值0到1之间。我们需要编写一个脚本读取每个XML文件进行转换。以下是核心转换函数import xml.etree.ElementTree as ET import os def convert_annotation(xml_file_path, output_txt_path, classes): 将单个VOC XML文件转换为YOLO格式的TXT文件。 tree ET.parse(xml_file_path) 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.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue # 跳过不在我们类别列表中的目标 cls_id classes.index(cls_name) xmlbox obj.find(bndbox) xmin float(xmlbox.find(xmin).text) ymin float(xmlbox.find(ymin).text) xmax float(xmlbox.find(xmax).text) ymax float(xmlbox.find(ymax).text) # 检查边界框是否有效 if xmax xmin or ymax ymin: print(fWarning: Invalid bbox in {xml_file_path}, skipped.) continue if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(fWarning: Bbox out of image boundary in {xml_file_path}, clipped.) xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) # 计算归一化中心坐标和宽高 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之间 x_center max(0.0, min(1.0, x_center)) y_center max(0.0, min(1.0, y_center)) width max(0.0, min(1.0, width)) height max(0.0, min(1.0, height)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 写入TXT文件如果该图片没有目标则写入一个空文件 with open(output_txt_path, w) as f: f.write(\n.join(lines)) # 定义类别列表顺序非常重要必须与后续data.yaml中的names顺序一致。 classes [plastic_bag, bottle, cigarette_butt, paper] # 遍历所有XML文件进行转换 annotations_dir annotations labels_dir labels os.makedirs(labels_dir, exist_okTrue) for xml_file in os.listdir(annotations_dir): if xml_file.endswith(.xml): xml_path os.path.join(annotations_dir, xml_file) txt_filename os.path.splitext(xml_file)[0] .txt txt_path os.path.join(labels_dir, txt_filename) convert_annotation(xml_path, txt_path, classes)4.4 生成YOLO配置文件data.yamlYOLO以YOLOv5/v8为例需要一个data.yaml文件来指明数据路径和类别信息。这个文件通常放在项目根目录。# data/city_street_trash.yaml path: /path/to/your/city_street_trash_892 # 数据集的根目录绝对路径 train: images/train # 训练集图像路径相对于path val: images/val # 验证集图像路径相对于path test: images/test # 测试集图像路径相对于path # 类别数量 nc: 4 # 类别名称列表顺序必须与转换脚本中的classes列表完全一致 names: [plastic_bag, bottle, cigarette_butt, paper]关键点这里的train、val、test路径指向的是图像文件夹。YOLO会自动在对应的labels文件夹与images同级中寻找同名的TXT文件。因此你需要根据之前的划分将images/文件夹下的图片分别复制或移动到images/train/,images/val/,images/test/子文件夹中。labels/文件夹下的TXT文件结构也应与此保持一致。5. 数据质量校验与基线模型验证5.1 标注一致性检查在转换完成后必须进行可视化检查确保转换无误。随机抽样检查写一个简单的脚本随机选择若干张图片用OpenCV或matplotlib将YOLO格式的标注框画回原图上看。import cv2 import random import os def plot_yolo_bbox(img_path, label_path, classes): img cv2.imread(img_path) h, w, _ img.shape with open(label_path, r) as f: lines f.readlines() for line in lines: cls_id, xc, yc, bw, bh map(float, line.strip().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, classes[int(cls_id)], (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(Check, img) cv2.waitKey(0) cv2.destroyAllWindows() # 随机检查几张 image_dir images/train label_dir labels/train img_files os.listdir(image_dir) for _ in range(5): img_name random.choice(img_files) img_path os.path.join(image_dir, img_name) label_path os.path.join(label_dir, os.path.splitext(img_name)[0] .txt) if os.path.exists(label_path): plot_yolo_bbox(img_path, label_path, classes)统计信息检查统计每个类别的实例数、每张图片的平均目标数、目标尺寸分布等。这能帮你发现数据集的潜在偏差。例如如果cigarette_butt的实例数远少于其他类别就需要考虑是否采集不足或标注困难。5.2 运行YOLOv8基线训练验证最直接的验证方式就是跑一个简单的训练看模型能否正常收敛并在验证集上获得合理的指标。# 假设你已安装ultralytics库 pip install ultralytics # 使用YOLOv8n最小的模型进行快速训练验证数据管道 yolo taskdetect modetrain modelyolov8n.pt data/path/to/city_street_trash.yaml epochs50 imgsz640 batch16关注以下几点训练日志观察训练损失train/box_loss,train/cls_loss是否平稳下降。验证指标重点关注metrics/mAP50-95(B)这是综合衡量检测精度的重要指标。对于892张图的小数据集初始训练50轮的mAP50能达到0.4~0.6就是一个不错的起点说明数据集基本可用。可视化预测训练结束后用验证集图片进行预测直观查看检测效果。如果发现某一类别完全检测不到或者误检非常多可能需要回溯检查该类别的标注质量或数据量。# 使用训练好的最佳模型在验证集上预测并保存结果 yolo taskdetect modepredict modelruns/detect/train/weights/best.pt sourceimages/val saveTrue6. 常见问题、避坑指南与优化建议6.1 标注阶段常见问题问题边界框标注不精确有空隙或超出物体。后果模型学习到的物体特征包含过多背景噪声或丢失部分物体特征影响定位精度。解决放大图像进行精细标注制定明确的标注规范并加强审核使用标注工具的“吸附到像素边缘”功能如果有。问题类别标注错误或混淆。后果导致模型类别识别混乱特别是对于相似类别如“纸盒” vs “塑料盒”但我们这里未定义塑料盒。解决制作清晰的类别示例图正例和反例供所有标注人员参考定期进行一致性测试。问题小目标如烟头漏标。后果模型对该类别的召回率Recall会非常低。解决标注时滚动鼠标放大查看可以设定一个最小像素面积如20像素低于此面积的酌情考虑是否标注根据业务需求。6.2 格式转换与训练阶段问题问题YOLO训练时提示“Labels missing”或“No labels found”。排查检查data.yaml中的path是否为绝对路径相对路径有时因工作目录问题出错。检查images/train和labels/train文件夹下的文件是否一一对应文件名不含后缀必须完全相同。检查labels/train下的TXT文件是否可能为空空文件是允许的表示该图无目标但需要确认是否预期内。检查TXT文件内的类别ID是否从0开始连续且小于nc类别数例如4个类别ID必须是0123。问题训练损失震荡不降或mAP极低。排查数据问题首先怀疑数据集。用第5.1节的脚本大量可视化检查看标注框是否离谱。类别不平衡查看数据集统计信息。如果某个类别实例数极少比如少于30个模型很难学习。可以考虑图像级或实例级的过采样复制或使用focal loss等损失函数。超参数问题对于小数据集学习率lr0不宜过大可以尝试从0.01降低到0.001。imgsz图像尺寸也可以尝试调小如640-320以减少计算量让模型在小数据上更容易拟合。模型问题对于只有892张图的数据集使用过大的模型如yolov8x极易过拟合。从yolov8n或yolov8s开始是最稳妥的。6.3 数据集优化与后续迭代建议数据增强Data Augmentation这是提升小数据集性能的利器。YOLO内置了强大的增强功能如Mosaic、MixUp、随机旋转、裁剪、色彩抖动等。在data.yaml中或训练命令里可以调整相关参数。对于垃圾检测可以考虑增加模拟遮挡、模糊、雨雪等天气效果的增强提升模型鲁棒性。难例挖掘Hard Example Mining在第一次基线训练后用模型在训练集上跑一遍预测找出那些模型预测错误漏检、误检、分类错的图片。重点分析这些“难例”是标注有问题还是场景太特殊针对性地补充类似的数据或修正标注能高效提升模型性能。主动学习Active Learning如果还有未标注的数据可以用当前训练好的模型去预测筛选出模型最“不确定”的图片例如预测概率处于中间阈值的进行人工标注然后加入训练集重新训练。这样可以用最少的标注成本获得最大的性能提升。类别粒度调整根据基线模型的表现和实际业务反馈可能需要对类别进行合并或拆分。例如如果bottle瓶罐类中模型总是分不清塑料瓶和易拉罐而业务又需要区分就可以考虑拆分成两个子类。反之如果plastic_bag和paper经常混淆且业务不需要区分可以考虑合并。构建一个高质量的定制数据集是一项需要耐心和细致的工作但它的回报是巨大的。一个干净、规范、匹配业务的数据集是任何优秀AI模型的基石。通过这892张城市道路垃圾数据集的构建过程我们不仅得到了一份可用的数据资产更关键的是跑通了一套从需求定义到最终验证的标准化流程。下次当你面临新的检测任务时这套方法论可以直接复用。记住在数据上投入的时间最终都会在模型性能上体现出来。本文还有配套的精品资源点击获取