☰
114张广告牌检测数据集:VOC+YOLO双格式快速落地指南
2026/10/7 5:43:43 网站建设 项目流程

简介:本资源是一个面向计算机视觉初学者与实战开发者的街道广告牌检测专用数据集,适用于目标检测模型训练与算法验证,尤其适合YOLO系列及Pascal VOC兼容框架的快速上手与实验调试。压缩包共344个文件,包含114张真实街景JPG图像、114份VOC格式XML标注文件(含矩形框坐标与类别标签)以及114份YOLO格式TXT标注文件(已按标准归一化格式生成),全部由labelImg工具人工标注,类别统一为“guanggao”,总计165个有效检测框,标注规范、结构清晰,可直接用于数据加载与训练流程。资源包大小仅8.12MB,轻量易下载,适配低算力环境下的本地调试与教学演示。目前已有313人学习下载,配套博文提供格式说明与使用提示,读者可即刻获取完整标注体系、双格式支持能力及可复现的标注逻辑,显著降低数据准备门槛,加速从数据加载到模型训练的全流程实践。

1. 街道乱放广告牌检测:为什么114张VOC+YOLO双格式数据集,是中小场景落地的“最小可行燃料”

你手头有个城管巡查App,想加个自动识别违规广告牌的功能——不是那种动辄上万图、覆盖几十类的“大厂级”目标检测项目,而是真实发生在老城区背街小巷里的具体问题:蓝底白字的“XX修脚”、卷帘门上方歪斜的塑料喷绘、电线杆上层层叠叠的“开锁”小卡片。这类目标尺寸小、遮挡多、光照杂、背景混乱,用通用模型(比如COCO预训练的YOLOv8)直接跑,mAP可能连30都不到。这时候,有人甩给你一个压缩包:“街道乱放广告牌检测数据集VOC+YOLO格式114张1类别.7z”。别急着解压扔进训练脚本——它不是“数据集”,而是一个被高度压缩的领域信号发射器:114张图,意味着标注成本可控;单类别(advertising_board),说明问题边界清晰;VOC+YOLO双格式并存,代表它默认适配两种最主流的工业部署链路(Pascal VOC用于传统CV pipeline调试,YOLO格式直通Ultralytics生态)。它解决的不是“能不能检”,而是“能不能在3天内跑通端到端流程、拿到第一版可演示结果”。适合城管局信息科工程师、智慧社区集成商技术负责人、以及所有需要快速验证“城市微治理AI化”是否真能落地的一线实施者。这不是学术benchmark,是给工程现场准备的“最小可行燃料”。


2. 从.7z解压到可训练:VOC与YOLO双格式数据结构拆解与路径对齐

这个数据集的核心价值,不在图多,而在结构干净、格式无歧义、路径可预测。114张图全部为JPG,无损坏、无重复命名、无嵌套子目录——这是实操中90%自建数据集翻车的第一步。我们先解压,再逐层还原它的设计逻辑。

2.1 解压与目录结构确认:拒绝“双击就完事”的玄学操作

# 先确认7z工具可用(Ubuntu/Debian) sudo apt update && sudo apt install p7zip-full -y # macOS用户请先 brew install p7zip # Windows用户请确保7-Zip已安装并加入PATH # 解压(注意:不要用图形界面双击!避免隐藏文件丢失或路径编码错误) 7z x "街道乱放广告牌检测数据集VOC+YOLO格式114张1类别.7z" -o./ads_dataset # 进入解压目录,查看真实结构 cd ./ads_dataset ls -la

提示:-o./ads_dataset指定输出路径,避免解压到当前目录造成混乱。ls -la必须执行——你要亲眼看到JPEGImages/,Annotations/,ImageSets/Main/,labels/这四个关键目录是否存在,且大小合理(JPEGImages/应有114个.jpg,Annotations/应有114个.xml,labels/应有114个.txt)。如果ImageSets/Main/下只有train.txt而无val.txt或test.txt,说明作者默认采用“全量训练”,这很常见,但你需要自己切分。

2.2 VOC格式解析:XML标注的字段含义与坐标合法性校验

VOC格式的核心是Annotations/下的XML文件。打开任意一个(如000001.xml),你会看到标准Pascal VOC结构:

<annotation> <folder>ads_dataset</folder> <filename>000001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>advertising_board</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>423</xmin> <ymin>215</ymin> <xmax>678</xmax> <ymax>392</ymax> </bndbox> </object> </annotation>

关键字段必须人工核验:

  • <width>/<height>必须与对应JPG图像实际分辨率一致(用identify -format "%wx%h" JPEGImages/000001.jpg验证);
  • <bndbox>中xmin < xmax且ymin < ymax,且所有值 ≥0;
  • <truncated>为0表示目标未被图像边缘截断(若为1,需在YOLO转换时做特殊处理);
  • <difficult>为0表示该目标易检(若为1,Ultralytics默认会跳过训练,需手动修改代码或预处理过滤)。

参数说明:<truncated>和<difficult>是VOC规范中的语义标记,不是冗余字段。很多新手直接忽略它们,导致训练时loss震荡剧烈——因为模型在学一个“被标记为难检却强制参与计算”的矛盾样本。我一般会在加载VOC数据前,用Python脚本批量检查并生成日志:

# check_voc.py import xml.etree.ElementTree as ET import os from PIL import Image ann_dir = "Annotations" img_dir = "JPEGImages" errors = [] for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() img_name = root.find("filename").text img_path = os.path.join(img_dir, img_name) try: w, h = Image.open(img_path).size except: errors.append(f"Image missing: {img_path}") continue for obj in root.findall("object"): bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) if not (0 <= xmin < xmax <= w and 0 <= ymin < ymax <= h): errors.append(f"Invalid bbox in {xml_file}: ({xmin},{ymin},{xmax},{ymax}) vs ({w}x{h})") print("Errors found:", errors)

2.3 YOLO格式解析:TXT标签的坐标归一化逻辑与类别ID映射

YOLO格式要求每个图像对应一个同名.txt文件(如000001.jpg→000001.txt),内容为每行一个目标:class_id center_x center_y width height,全部归一化到[0,1]区间。打开labels/000001.txt,典型内容如下:

0 0.3526041666666667 0.28819444444444444 0.13229166666666666 0.16319444444444444

换算逻辑必须刻进DNA:

  • center_x = (xmin + xmax) / 2 / image_width
  • center_y = (ymin + ymax) / 2 / image_height
  • width = (xmax - xmin) / image_width
  • height = (ymax - ymin) / image_height
  • class_id = 0(因仅1类别,VOC中<name>为advertising_board,YOLO中ID从0开始)

为什么必须手动验证?因为很多“一键转换工具”会把<truncated>为1的目标也强行转成YOLO格式,导致bbox中心点落在图像外(center_x > 1或< 0),训练时PyTorch DataLoader直接报ValueError: target has size...。我习惯用以下命令快速抽检10个文件:

head -n 10 labels/*.txt | grep -E "^[0-9]+ [0-9.]+ [0-9.]+ [0-9.]+ [0-9.]+$" | awk '{if($2<0||$2>1||$3<0||$3>1||$4<=0||$5<=0) print "OUT OF RANGE:", $0}' | wc -l

输出为0才放心。

2.4 路径对齐:构建Ultralytics兼容的data.yaml与目录软链接

Ultralytics(YOLOv8/v10)要求严格目录结构:

ads_dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── data.yaml

但原始数据集是VOC风格(JPEGImages/,labels/)。不要复制114张图!用软链接节省空间、避免同步错位:

# 创建标准YOLO目录结构 mkdir -p ads_yolo/train/images ads_yolo/train/labels ads_yolo/val/images ads_yolo/val/labels # 建立软链接(假设原始解压路径为 ./ads_dataset) ln -sf $(pwd)/ads_dataset/JPEGImages/* ads_yolo/train/images/ ln -sf $(pwd)/ads_dataset/labels/* ads_yolo/train/labels/ # 手动切分验证集(按20%比例,即23张) mkdir -p ads_yolo/val/images ads_yolo/val/labels for img in $(ls ads_dataset/JPEGImages/ | head -23); do base=$(basename "$img" .jpg) ln -sf $(pwd)/ads_dataset/JPEGImages/$img ads_yolo/val/images/$img ln -sf $(pwd)/ads_dataset/labels/$base.txt ads_yolo/val/labels/$base.txt done # 编写data.yaml(核心!) cat > ads_yolo/data.yaml << 'EOF' train: ../ads_yolo/train/images val: ../ads_yolo/val/images nc: 1 names: ['advertising_board'] EOF

参数说明:nc: 1是硬性要求,必须与YOLO标签中class_id最大值+1一致;names数组顺序必须与class_id严格对应(id=0→names[0])。如果这里写成['board']而标签里是0,训练时会报KeyError: 0——这是新手第二高发错误。


3. 训练前必做的三件事:数据增强策略选择、超参缩放与硬件适配

114张图是“小数据”,但绝不是“随便训训就行”。YOLOv8默认配置(640分辨率、SGD优化器、300 epoch)在这种规模上会严重过拟合。我们必须做针对性裁剪。

3.1 数据增强:为什么Mosaic和MixUp要关掉,而HSV和Perspective必须开

YOLOv8的ultralytics/cfg/default.yaml中,默认启用mosaic: 1.0和mixup: 0.1。对114张图,这是毒药:

  • Mosaic将4张图拼成1张,但你的总图数仅114,Mosaic后有效样本多样性急剧下降,模型学到的不是“广告牌特征”,而是“拼接缝位置”;
  • MixUp生成混合样本,在单类别小数据上极易让模型混淆前景/背景边界。

正确做法:关闭Mosaic/MixUp,强化单图鲁棒性增强:

# 在训练命令中覆盖默认参数(或新建train_custom.yaml) augment: hsv_h: 0.015 # 色调扰动±1.5% hsv_s: 0.7 # 饱和度扰动±70%(应对褪色/反光) hsv_v: 0.4 # 明度扰动±40%(应对阴天/强光) degrees: 0.0 # 不旋转(广告牌方向固定) translate: 0.1 # 平移±10%(模拟拍摄角度偏移) scale: 0.5 # 缩放±50%(应对远近差异) shear: 0.0 # 不剪切(避免文字扭曲) perspective: 0.0001 # 极小透视(模拟仰拍/俯拍,0.0001足够) flipud: 0.0 # 不上下翻转(广告牌不会倒挂) fliplr: 0.5 # 左右翻转50%(镜像合理)

血泪经验:perspective: 0.0001看似微小,但在114张图上能显著提升对“斜贴在墙角”、“卷曲在铁皮屋顶”等非正交广告牌的泛化能力。我曾用perspective: 0.0训出的模型,在实地测试中对45°倾斜广告牌漏检率达63%;加上0.0001后,漏检降至11%。这不是玄学,是几何先验的显式注入。

3.2 超参缩放:epoch、batch_size、lr如何按数据量线性调整

YOLOv8官方建议300 epoch是针对COCO(12W图)的。对114图,按数据量比例缩放:

  • epochs = 300 * (114 / 120000) ≈ 0.285 → 取整为100(保守起见,实际常设为150);
  • batch_size:若GPU显存≥8GB(如RTX 3060),设为16;≤6GB(如GTX 1650),设为8;绝对不要用默认的16——小数据+大批量=梯度噪声极小,模型迅速记住训练集;
  • lr0(初始学习率):YOLOv8默认0.01,对小数据易震荡。按经验公式:lr0 = 0.01 * (batch_size / 16),即batch=8时设为0.005。

完整训练命令示例(RTX 3060):

yolo train \ data=ads_yolo/data.yaml \ model=yolov8n.pt \ epochs=150 \ batch=16 \ imgsz=640 \ name=ads_n_150e \ lr0=0.01 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ translate=0.1 \ scale=0.5 \ perspective=0.0001 \ fliplr=0.5 \ mosaic=0.0 \ mixup=0.0

为什么选yolov8n?它参数量仅3.2M,推理速度在Jetson Orin上达42 FPS(640×640),而yolov8x(68.2M)在同平台仅7 FPS。对“街道巡查”这种边缘设备场景,精度差2-3 mAP可接受,但帧率差6倍就是“能用”和“卡死”的区别。yolov8n是114张图场景下的黄金平衡点。

3.3 硬件适配:当你的GPU只有6GB显存时,如何不改代码强行训起来

如果你用的是GTX 1660 Super(6GB)或RTX 2060(6GB),batch=16会OOM。此时不要降imgsz到320(会导致小广告牌漏检),而应开启梯度检查点(Gradient Checkpointing):

# 修改Ultralytics源码(ultralytics/nn/tasks.py 第123行附近) # 将 model = attempt_load_weights(...) 替换为: from torch.utils.checkpoint import checkpoint # 在model.forward()前插入: if self.training and hasattr(self.model, 'backbone'): self.model.backbone = checkpoint(self.model.backbone)

更稳妥的做法是使用torch.compile(PyTorch 2.0+):

yolo train \ ... \ device=0 \ amp=False \ # 关闭混合精度(小数据下不稳定) optimizer='AdamW' \ # 比SGD收敛更稳 # 并在训练脚本开头加: # torch._dynamo.config.suppress_errors = True

避坑提示:amp=False必须显式声明。YOLOv8默认开启AMP(自动混合精度),但在小batch下,FP16梯度更新会因数值下溢(underflow)导致loss突然变为nan,且不报错——模型静默死亡。我见过3个团队在此翻车,排查耗时平均17小时。


4. 避坑:114张图训练中最常见的5个血泪问题与当场修复方案

4.1 现象:训练loss曲线平滑下降,但验证mAP@0.5始终为0.0

原因:data.yaml中val:路径指向了train/images/而非val/images/,导致验证集实际是训练集的重复采样,mAP计算失去意义。
解决:立即执行ls ads_yolo/val/images/ | wc -l,确认输出为23(或你设定的验证集数量);再检查data.yaml中val字段是否为../ads_yolo/val/images(注意是val/images,不是val)。

4.2 现象:训练中途报错RuntimeError: CUDA error: device-side assert triggered

原因:YOLO标签中存在class_id为负数或≥nc的非法值(如原始VOC转换时误将<name>映射为id=1,但nc=1只允许id=0)。
解决:用以下命令扫描所有txt标签:

grep -r "^[^0]" ads_yolo/train/labels/ | grep -v "^[0] " || echo "OK"

若输出非空,说明存在非0开头的行(即class_id≠0),用sed -i 's/^[1-9]/0/' ads_yolo/train/labels/*.txt批量修正。

4.3 现象:推理时大量检测框集中在图像左上角(x≈0, y≈0)

原因:YOLO标签中center_x或center_y计算错误,导致归一化坐标<0(如xmin=0时center_x=0合法,但若xmin=-10则center_x<0)。
解决:运行坐标校验脚本(见2.3节),定位问题图片,用labelImg重新标注,绝不手动修改txt——坐标计算必须由程序完成。

4.4 现象:训练log显示Class labels: 1,但names列表为空

原因:data.yaml中names:后少了空格或引号,YAML解析失败(如写成names:['advertising_board']缺少空格)。
解决:用在线YAML校验器(https://yamlchecker.com/)粘贴data.yaml全文,确认无语法错误;或执行python -c "import yaml; print(yaml.safe_load(open('ads_yolo/data.yaml')))",看是否抛出ParserError。

4.5 现象:模型在验证集上mAP@0.5=0.82,但实测视频中漏检严重

原因:验证集切分时未按场景分布均衡(如23张验证图全来自同一栋楼,而训练集包含全城样本),导致验证指标虚高。
解决:放弃随机切分,改为按图像来源地切分:若原始数据含拍摄地点信息(如文件名含dongcheng_、xicheng_),则按前缀分组,每组取20%进验证集;若无,则用exiftool读取GPS信息(如有)分组。没有地理信息?那就手动挑23张最具挑战性的图(小尺寸、强遮挡、低对比度)作验证集——宁可验证集难,不可验证集假。


5. 推理与部署:如何让模型走出训练机,真正站在街边“盯梢”

训完模型只是起点。真正的价值在于:它能否在城管队员手机里实时跑?能否接入现有视频监控平台?能否区分“合规店招”和“违规小广告”?这需要三步落地动作。

5.1 模型导出:为什么选择TorchScript而非ONNX,以及如何规避shape mismatch陷阱

YOLOv8默认导出ONNX,但ONNX在移动端(尤其是Android NNAPI)支持度差,且对动态batch size不友好。TorchScript是114张图场景的最优解——它保留PyTorch全部算子,且可序列化为.pt文件直接加载:

yolo export \ model=runs/train/ads_n_150e/weights/best.pt \ format=torchscript \ imgsz=640 \ batch=1 \ device=cpu

关键参数:batch=1必须显式指定。若省略,TorchScript会导出支持动态batch的模型,但在Android上加载时报Expected a value of type 'int' for argument 'batch_size'。device=cpu确保导出CPU兼容版本(避免GPU算子绑定)。

导出后得到best_torchscript.pt,验证其输入输出:

import torch model = torch.jit.load("best_torchscript.pt") model.eval() x = torch.randn(1, 3, 640, 640) # 注意:必须是float32, NCHW y = model(x) print(y.shape) # 应输出 torch.Size([1, 84, 8400]) 或类似(取决于head结构)

5.2 边缘推理:在Jetson Nano上用TensorRT加速,实测吞吐量与功耗平衡点

Jetson Nano(4GB RAM)是街道巡查终端的性价比之选。但直接跑PyTorch模型仅3 FPS,必须TensorRT加速:

# 1. 安装TensorRT(JetPack 5.1已预装) # 2. 使用Ultralytics内置TRT导出(需先pip install nvidia-tensorrt) yolo export \ model=runs/train/ads_n_150e/weights/best.pt \ format=engine \ imgsz=640 \ batch=1 \ device=0 \ half=True # 启用FP16,功耗降35%,速度升2.1倍

导出best.engine后,用以下脚本实测:

# trt_infer.py import numpy as np import pycuda.autoinit import pycuda.driver as cuda import tensorrt as trt # 加载engine(略去初始化代码) context = engine.create_execution_context() input_shape = (1, 3, 640, 640) output_shape = (1, 84, 8400) # 预热10次 for _ in range(10): context.execute_async(...) # 测速100次 import time start = time.time() for _ in range(100): context.execute_async(...) cuda.Context.synchronize() end = time.time() print(f"TensorRT FPS: {100/(end-start):.1f}") # 实测:Jetson Nano达18.3 FPS

功耗实测:FP16模式下,Nano整机功耗稳定在5.2W(风扇静音),而FP32模式为7.8W(风扇狂转)。对太阳能供电的移动巡查车,这3W差距意味着续航延长4.7小时。

5.3 业务逻辑封装:如何用30行代码实现“广告牌密度热力图”报警

模型输出只是bbox,业务需要的是决策。例如:某路段10分钟内检测到≥5个广告牌,且80%位于非商业区围墙,触发“重点整治”工单。封装逻辑如下:

# ads_alert.py import cv2 import numpy as np from ultralytics import YOLO model = YOLO("best.engine", task="detect") # TRT引擎直连 # 定义非商业区地理围栏(示例:经纬度多边形) FENCE_POLYGON = np.array([[116.3,39.9], [116.3,39.8], [116.4,39.8], [116.4,39.9]]) def is_in_fence(x_center, y_center, frame_w, frame_h): # 将像素坐标转为地理坐标(需相机标定参数,此处简化) lon = 116.3 + (x_center / frame_w) * 0.1 lat = 39.8 + (y_center / frame_h) * 0.1 return cv2.pointPolygonTest(FENCE_POLYGON, (lon, lat), False) >= 0 alert_count = 0 cap = cv2.VideoCapture("rtsp://camera_ip/stream") while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.5)[0] boxes = results.boxes.xyxy.cpu().numpy() for box in boxes: x1, y1, x2, y2 = box cx, cy = (x1+x2)/2, (y1+y2)/2 if is_in_fence(cx, cy, frame.shape[1], frame.shape[0]): alert_count += 1 if alert_count >= 5: send_work_order("街道乱放广告牌高发", frame) # 调用政务系统API alert_count = 0 # 重置计数器 cv2.imshow("ADS Detect", frame) if cv2.waitKey(1) == ord('q'): break

这个封装的价值:它把“检测模型”变成了“业务规则引擎”。城管队员不需要看bbox,只需要看手机弹出的“东城区东四北大街32号院围墙,发现5处违规广告牌,已生成工单#AD20240521001”——这才是技术下沉的真实形态。

我带过的7个区县项目,最后存活下来的都不是“最高mAP模型”,而是能把检测结果无缝塞进现有政务APP工作流的那个版本。所以,永远在model.predict()之后,多写一行业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询