news 2026/10/1 5:29:25

广告牌检测数据集VOC+YOLO双格式114张,YOLOv8训练全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
广告牌检测数据集VOC+YOLO双格式114张,YOLOv8训练全流程

简介:面向城市管理与目标检测算法学习场景,街道乱放广告牌检测数据集提供114张真实街景图片,同时给出VOC与YOLO两种格式的标注框,类别统一为广告牌,共有165个矩形标注,适合用于违规广告牌识别模型的训练与验证。资源包共344个文件,包含114张jpg原图、114个xml标注文件及116个txt文件(其中114个为YOLO格式标注),整体仅8.12MB,目录结构清晰,便于快速加载与二次处理。目前已有312人学习下载,数据量适中、格式规范,既可作为目标检测入门练习数据,也适合做数据增强或迁移学习实验;使用labelImg工具标注,矩形框规则明确,有助于理解VOC与YOLO两种常用标注格式的对应关系。需要提醒的是,数据集只保证标注准确合理,不对模型精度作承诺,使用者可按需划分训练集和测试集。

1. 街道乱放广告牌检测数据集:114张VOC+YOLO双格式,单类目标检测怎么落地

城市管理巡检里,街道乱放广告牌检测是一个比较典型的单类目标检测任务,看着简单,真做起来却有两道坎:一是正样本要现场采集,违规广告牌形态五花八门;二是标注格式不统一,换训练框架就得重新转格式。我最近拆了一份街道乱放广告牌检测数据集,VOC+YOLO双格式直接打包,解压出来是114张jpg、114个xml、114个txt,标注类别只有guangguang一个,累计165个矩形框,全部用labelImg人工画框完成。对想快速验证YOLOv5/YOLOv8训练流程的从业者,这份数据省掉了爬图、清洗、标注、转格式的重复劳动;对做城管告警、门店违规识别项目的人来说,可以作为冷启动阶段的预训练样本。下面从解压、校验到训练、避坑,把整个流程过一遍。

2. 拆开压缩包看标注细节:VOC与YOLO两种格式的目录结构、xml字段与归一化换算

2.1 双格式目录结构:jpg/xml/txt三者怎么对应

拿到压缩包先不要急着解压训练,先把目录结构摸清楚。这份数据集的文件名前缀是firc_jd,后面跟编号,我拆开之后看到的实际对应关系是:每张图片有一个同名xml和一个同名txt,三者散落在同一层目录里,并不像很多开源数据集那样强制分成JPEGImages、Annotations、labels三个子目录。这对训练来说其实更自由,你自己按需归类就行。

文件类型数量内容说明
jpg114现场采集的街道场景原图
xml114Pascal VOC格式标注,labelImg默认产物
txt114YOLO格式标注,每行一个目标框
标注类别1guanggao(广告牌)
总框数165全部为目标框,平均每张约1.4个框

为什么我强调“双格式”这件事?因为labelImg保存时默认写VOC的xml,而YOLO系列训练读的是txt。很多数据集只给其中一种,你拿到后要么装labelImg重新导出,要么到处找转换脚本。这份直接把两种都给齐了,理论上解压后可以直接喂给YOLO训练。另外注意摘要里特别提了一句“不包含分割路径的txt文件”,意思是这份txt不是YOLO分割格式的多边形polygon坐标,而是每行一条检测记录,别当成分割标注喂给yolov8-seg,否则会直接报维度错误。

2.2 VOC的xml标签字段:object、size、bndbox逐一拆解

VOC格式的好处是结构直观,一个目标对应一个<object>节点。我拿其中一张图的结构做个示意,字段含义如下:

<annotation> <folder>JPEGImages</folder> <filename>firc_jd_13.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>guanggao</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>100</xmin> <ymin>80</ymin> <xmax>340</xmax> <ymax>260</ymax> </bndbox> </object> </annotation>

这里的<size>是原图宽高和通道数,<bndbox>是矩形框的左上角xmin/ymin和右下角xmax/ymax。<truncated>和<difficult>在labelImg里默认是0,表示目标没有被截断、不算难例,训练时这两个字段YOLO不读,但保留着不影响转换。需要注意一点:xml里的<filename>和实际jpg文件名必须完全一致,包括字母大小写,否则后面做数据划分时很容易出现“图片走了、标注没跟上”的情况。

2.3 YOLO txt的归一化坐标:从xml到txt的换算与类别映射

YOLO格式的txt每一行是class_id x_center y_center width height,坐标全部归一化到0到1之间。归一化公式我在实际项目里一般这样写:

def xml_to_yolo(xml_path, img_width, img_height): import xml.etree.ElementTree as ET tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): class_id = 0 # 本数据集只有guanggao,索引固定为0 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_width y_center = (ymin + ymax) / 2.0 / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return "\n".join(lines)

这段脚本的核心逻辑是:先解析xml拿到bndbox四个坐标,再除以图片宽高做归一化,最后按class_id cx cy w h拼成一行。因为这份数据只有一个类别,class_id固定写0;如果你的训练data.yaml里把类别顺序改了,这个数字也要跟着改。归一化后w和h是相对值,理论上永远小于1,如果跑出来某行w或h大于1,说明xml里的bndbox越界了,后面避坑章节会专门讲这个场景。

3. 从7z到可训练数据集:解压校验、框数核对与train/val划分脚本

3.1 解压7z:Windows与Linux两种方式及文件完整性检查

压缩包是7z格式,和zip不太一样,Windows系统自带的资源管理器解压不了,得装第三方工具。我一般在Windows上用7-Zip,右键“解压到当前文件夹”就能看到jpg/xml/txt三组文件。Linux环境下用命令行:

# Ubuntu/Debian 先装 p7zip sudo apt install p7zip-full # 解压到目标目录 7z x 街道乱放广告牌检测数据集VOC+YOLO格式114张1类别.7z -o./street_sign_dataset

解压参数说明:x表示保留完整目录结构解压,-o后面跟输出目录,注意-o和目录路径之间不要加空格,加了会被识别成两个参数。解压完第一件事不是看图片,而是数文件数量。我的习惯是跑一条ls | wc -l分别统计jpg、xml、txt的数量,必须都是114。这种标注数据一旦中间缺一个txt,训练时那张图就会被跳过,而且你不会立刻发现,等loss曲线异常才回头排查,非常浪费时间。

3.2 校验脚本:xml与txt框数、文件名一对一核对

解压正常不代表标注没毛病。我拆数据集的固定动作是先跑一个校验脚本,确认三件事:文件名一一对应、每个txt行数不为0、总框数等于165。

import os from pathlib import Path img_dir = Path("./street_sign_dataset") # 图片目录 xml_dir = Path("./street_sign_dataset") # xml目录 txt_dir = Path("./street_sign_dataset") # txt目录 imgs = {p.stem for p in img_dir.glob("*.jpg")} xmls = {p.stem for p in xml_dir.glob("*.xml")} txts = {p.stem for p in txt_dir.glob("*.txt")} # 1. 三集合必须完全一致 print("jpg数量:", len(imgs), "xml数量:", len(xmls), "txt数量:", len(txts)) print("缺xml:", imgs - xmls, "缺txt:", imgs - txts) # 2. 每个txt不能是空文件,且框数总和应为165 total_boxes = 0 for t in sorted(txt_dir.glob("*.txt")): lines = [line.strip() for line in t.read_text(encoding="utf-8").splitlines() if line.strip()] total_boxes += len(lines) if len(lines) == 0: print("空标注:", t.name) print("总框数:", total_boxes) assert total_boxes == 165, "框数不对,检查标注文件"

这个脚本的逻辑不复杂,但很实用:先用Path.stem取文件名不带后缀的部分,做差集判断;再逐行读取txt统计框数。注意txt的编码要按UTF-8读,如果解压工具在Windows上把文件名搞成乱码,这里集合差集会直接暴露问题。实际跑这份数据时,框数出来的结果就应该是165,多一行少一行都要停下来查。

3.3 按比例切分train/val并生成data.yaml

校验通过后做数据划分。114张图不算多,我一般按9:1切,也就是train约103张、val约11张。如果是做正式项目,这个比例还会再调整,但对小样本来说val太多反而让训练集更稀疏。

import random import shutil from pathlib import Path src = Path("./street_sign_dataset") train_dir = Path("./street_sign_dataset/images/train") val_dir = Path("./street_sign_dataset/images/val") train_lbl = Path("./street_sign_dataset/labels/train") val_lbl = Path("./street_sign_dataset/labels/val") for d in [train_dir, val_dir, train_lbl, val_lbl]: d.mkdir(parents=True, exist_ok=True) jpg_files = sorted(src.glob("*.jpg")) random.seed(42) random.shuffle(jpg_files) val_cnt = int(len(jpg_files) * 0.1) val_files = jpg_files[:val_cnt] train_files = jpg_files[val_cnt:] def move_pair(file_list, img_dst, lbl_dst): for jpg in file_list: xml = jpg.with_suffix(".xml") txt = jpg.with_suffix(".txt") shutil.copy(jpg, img_dst / jpg.name) shutil.copy(xml, lbl_dst / xml.name) shutil.copy(txt, lbl_dst / txt.name) move_pair(train_files, train_dir, train_lbl) move_pair(val_files, val_dir, val_lbl) print("train:", len(train_files), "val:", len(val_files))

这里我用的copy而不是move,是给自己留后悔药——万一划分不均或者想重新切,原目录还在。随机种子固定为42,保证每次跑出来的划分结果一致,方便复现。划分之后YOLO训练的data.yaml就能这样写:

path: /absolute/path/to/street_sign_dataset train: images/train val: images/val names: 0: guanggao

提示:path字段尽量写绝对路径。相对路径在YOLOv8里有时会因为启动目录不同而找不到图片,报dataset not found,排查起来很绕。

4. 用YOLOv8跑通单类小样本训练:dataset.yaml、增强参数与训练日志解读

4.1 模型选型与dataset.yaml:为什么单类114张优先选n或s

处理数据集用于YOLOv8训练时,第一关是选模型尺寸。很多人上来就选yolov8m甚至yolov8x,觉得大模型精度上限高。但对114张、单类别、165框的数据集,大模型几乎必然过拟合。我的经验是:n和s之间选一个,先跑通流程看loss和mAP,如果欠拟合再往上加。

模型参数量对114张小样本的适用性
yolov8n约3.2M首选,训练快,过拟合风险低
yolov8s约9.4M可试,augmentation拉满时也稳
yolov8m约25.9M不推荐,数据量撑不起
yolov8l/x43.7M/68.2M这个数据量基本别碰

这个选择逻辑不复杂:目标检测模型容量越大,需要的数据和训练轮次越多。广告牌检测不是猫狗分类那种纹理极度丰富的任务,n模型的特征提取能力已经足够覆盖“柜台上立一块牌子”这种中粒度目标。训练入口文件data.yaml也很简单,类别guanggao对应索引0,和txt里的class_id保持一致即可。

4.2 训练命令与增强参数:epochs、close_mosaic、batch怎么配合

数据量小,训练参数就得往“稳”字上靠。我实际跑这份数据的命令是:

yolo detect train \ data=street_sign_dataset/data.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=8 \ patience=30 \ close_mosaic=10 \ lr0=0.005 \ cache=ram

参数说明:model=yolov8n.pt会自动下载COCO预训练权重,不用手动找yolo预训练模型下载地址;epochs=200对小样本足够,多了反而记住噪声;patience=30表示30轮验证指标不涨就早停;close_mosaic=10很关键——mosaic增强在训练最后10轮自动关闭,这是YOLOv8官方推荐的策略,避免最后阶段还在拼图造成优化震荡;lr0从默认0.01降到0.005,小样本下初始学习率过高容易让BN层崩溃;cache=ram把图片缓存进内存,114张小图完全放得下,训练速度提升明显。

增强参数如果默认效果不好,我会在命令里追加:

参数默认值小样本建议作用
hsv_h0.0150.02色相抖动,模拟不同光照
hsv_s0.70.8饱和度抖动,应对黄昏/阴天
degrees0.010.0轻微旋转,广告牌不总是水平的
translate0.10.2平移,让目标出现在不同位置
fliplr0.50.3左右翻转,注意文字类目标慎用
scale0.50.5缩放模拟远近变化

fliplr对广告牌检测要谨慎:如果路边广告牌本身带文字,翻转后文字是反的,但检测任务只看矩形框位置,不看文字方向,所以影响不大。真正要留意的是degrees别开太大,广告牌在真实场景里很少大角度倾斜,转太多反而制造伪样本。

4.3 训练日志与混淆矩阵:loss、P/R、mAP50的读法与常见误读

训练完成后,YOLOv8会在runs/detect/train/下生成results.csv,里面每一列对应一个指标。我看这份数据时重点盯三个:train/box_loss是否持续下降且无尖峰、metrics/mAP50(B)在val上是否稳定、metrics/precision(B)和metrics/recall(B)是否出现严重失衡。小样本单类任务常见的现象是recall很高、precision偏低,因为模型把相似的招牌背景都当成广告牌。

还有一个容易被误读的地方:混淆矩阵。YOLOv8画出的混淆矩阵纵轴是真实类别占比,横轴是预测类别占比,格子加起来不等于100%很正常,它表达的是“召回率视角”的分布,不是联合分布。看到矩阵合计不是1别急着怀疑代码 bug,先看是不是没做归一化。这属于yolo混淆矩阵里最常被问的坑。

5. 避坑手册:114张小样本广告牌检测的五个常见坑

5.1 解压后文件名乱码或文件缺失

现象:用某些国产解压软件打开7z,解压出的jpg文件名变成乱码,或者xml/txt三件套少了一件。原因:压缩包内文件名按UTF-8编码,老版本解压工具按ANSI解码,中文文件名和前缀符号被转义破坏。解决:Windows一律用7-Zip 21以上版本,Linux用p7zip解压;解压完先跑一遍上文的集合差集脚本,发现缺文件就从原包重新解压,不要手工改名凑数。

5.2 txt里出现全0坐标或宽高越界

现象:训练时日志提示某行标注框无效,或者val的mAP一开始就是0。原因:xml里的bndbox如果出现xmax等于xmin,归一化后宽为0;如果标注框超出了图片边界,归一化后数值可能大于1,模型训练时算loss直接算不下去。解决:在校验脚本里加一条判断,凡是w或h小于等于0、大于1的行单独打印,定位到具体txt后人工回看图,用labelImg修正对应xml再重新生成txt。这种问题通常在数据生产阶段就存在,属于标注质量筛查,不是训练配置能救的。

5.3 val的mAP很高,实拍视频却漏检

现象:验证集mAP50到0.9以上,但拿现场手机拍的视频一测,远处广告牌完全不识别。原因:数据集里的原图拍摄距离偏近,目标框占整图比例较大;验证集和训练集来自同一采集批次,分布高度相似,评估结果虚高。实拍场景里目标小、亮度杂、背景乱,分布漂移直接击穿模型。解决:训练时可以用imgsz=768提升小目标分辨率,推理时对视频帧做ROI裁剪,只检测街道两侧的固定区域;同时加大实际场景数据的补充比例,把每天巡检拍的新图按周回流训练集。

5.4 把guanggao改成中文类别名后训练报错

现象:觉得类别名用拼音不够专业,把data.yaml里的guanggao改成“广告牌”,一跑训练就报错,要么KeyError要么标签读取为空。原因:Windows下记事本另存为时默认编码是ANSI(GBK),YAML按UTF-8读取时中文变乱码;即使强迫保存为UTF-8,cmd终端和Python解释器的编码环境不一致也会出问题。解决:类别名保持英文或拼音最稳妥,这是我在多个项目里的血泪经验;实在要显示中文,只改可视化标签映射,不动训练文件里的names字段。

5.5 训练中BN崩溃或loss出现NaN

现象:训练到一半train/box_loss突然变成nan,之后的epoch全部无效。原因:小样本下初始学习率偏高,加上最后阶段mosaic增强拼接了大量不自然样本,BN层的running_mean被带偏。解决:把lr0降到0.003到0.005之间,close_mosaic开到15到20轮,batch固定不要忽大忽小;如果还崩,用yolo detect train ... freeze=10把前10层backbone冻结,只训练neck和head,让训练更稳。这个毛病在yolo训练里不算罕见,看到nan先查学习率,别急着换模型。

6. 验证与业务落地:从mAP到置信度阈值与告警逻辑的联动

6.1 导出ONNX并在业务侧做一次快速验证

训练完的best.pt不能直接丢给线上服务,建议先导成ONNX,再用onnxruntime快速验证一张图:

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640

导出后用一段Python接口验证推理结果,核心参数是置信度阈值和NMS的IoU阈值:

import cv2 import onnxruntime as ort import numpy as np sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name img = cv2.imread("street_scene.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized = cv2.resize(img, (640, 640)) blob = resized.astype(np.float32) / 255.0 blob = np.transpose(blob, (2, 0, 1))[None] out = sess.run(None, {input_name: blob})[0] # out形状为 [1, 5+nc, 8400],取第一维做后处理

参数说明:ONNX导出的输出是[1, 6, 8400]形状,第一维batch,第二维是cx cy w h obj_conf class_conf,第三维是anchor解耦后的候选框数量。实际项目里我不会把后处理写死,而是用ultralytics库里的YOLO直接加载onnx做推理,减少重复造轮子。

6.2 置信度阈值与连续帧告警:业务侧怎么用

模型输出的是概率,业务侧要把它转成动作。我做城管告警类需求时,置信度阈值一般按场景分三档:

阈值适用场景行为
0.25初次检测、巡检抽查标记待确认,不直接告警
0.45正式业务告警触发工单
0.60夜间或雨天严控误报,只认高置信度

低阈值带来的误报,我用连续帧确认来过滤,而不是直接调高置信度:

class AlarmWindow: def __init__(self, min_frames=3, max_gap=5): self.min_frames = min_frames self.max_gap = max_gap self.countdown = 0 def update(self, det_conf): hit = det_conf >= 0.45 if hit: self.countdown += 1 else: self.countdown = max(0, self.countdown - 1) return self.countdown >= self.min_frames

这个逻辑很简单:同一路视频流里,连续3帧都检测到广告牌才触发告警,中间漏一帧可以通过countdown缓冲,不会立刻清零。相比单帧硬判,它能挡住大部分行人路过、车辆遮挡造成的闪烁误报。从那以后我每次拿到一个标注数据集,都强制先走一遍解压校验、划分、小参数训练、导出验证的流程,再谈业务指标,这份114张的广告牌数据集也是这样跑通的。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 5:29:24

Redis 在 AI 应用中的核心角色与工程实践指南

最近很多人在讨论"Redis 已正式接入 AI"&#xff0c;我的看法其实可以换个更务实的说法&#xff1a;AI 应用的基础设施里&#xff0c;Redis 正在从"可选"变成"标配"。做 AI 应用和做普通 Web 应用的缓存逻辑完全不同&#xff0c;模型推理的延迟、…

作者头像 李华
网站建设 2026/10/1 5:29:10

上传即用的PHP短视频解析源码:从部署到接口联调实战

简介&#xff1a;这是一套面向开发者与数据分析爱好者的短视频解析源码&#xff0c;主打“上传即可使用”&#xff0c;无需复杂配置即可提取视频链接、封面、标题、播放量、评论等关键数据&#xff0c;适用于内容监控、市场趋势研究与第三方应用开发等场景。资源包共14个文件&a…

作者头像 李华
网站建设 2026/10/1 5:28:33

Unity iOS手游Deep Link全链路实战:URL Scheme与Universal Links双通道集成

1. 项目概述&#xff1a;为什么 Deep Link 在 iOS 手游里不是“配菜”&#xff0c;而是“主菜” 你刚上线一款 Unity 开发的 iOS 手游&#xff0c;运营同学兴奋地发来一条短信链接&#xff1a;“快看&#xff01;用户点这个链接&#xff0c;直接跳转到游戏内‘周年庆活动页’&…

作者头像 李华
网站建设 2026/10/1 5:28:13

鸿蒙开发环境搭建全指南:DevEco Studio安装配置与模拟器运行实战

如果你正准备上手鸿蒙开发&#xff0c;那么DevEco Studio应该会是第一个绕不开的工具。它是华为官方推出的HarmonyOS应用开发IDE&#xff0c;基于IntelliJ IDEA定制&#xff0c;对ArkTS、ArkUI、Stage模型这些鸿蒙特性做了深度适配。这篇教程我按自己的实际操作流程来写&#x…

作者头像 李华
网站建设 2026/10/1 5:27:56

TensorFlow 2.0实战:用RNN生成唐诗,从数据到采样全流程

简介&#xff1a;这份资源面向深度学习与自然语言处理方向的初学者及课程实践者&#xff0c;围绕循环神经网络LSTM构建唐诗生成模型&#xff0c;解决从零搭建文本生成项目的入门难题。压缩包共12个文件&#xff0c;约20.18MB&#xff0c;包含6个Python源码文件、2个文本数据文件…

作者头像 李华
网站建设 2026/10/1 5:27:35

Jev大模型实测:申请密钥、接入Codex与编程创作全攻略

最近“Jev”这个词突然铺天盖地出现在我的信息流里&#xff0c;群里、朋友圈、技术社区&#xff0c;甚至一些完全不搞代码的创作者都在转发。点进去一看&#xff0c;有人拿它当编程助手&#xff0c;有人拿它写文章初稿&#xff0c;还有人专门在问“听说 Jev 能用在 Codex 里&am…

作者头像 李华