资讯动态

VOC格式电视检测数据集详解:从XML标注到YOLO训练实战

发布时间:2026/10/2 9:05:33 来源:尧图企业网站定制
简介VOC电视检测数据集是一份面向目标检测研究与深度学习初学者的专项资源主题聚焦电视/显示器类别的识别适用于智能监控、家电自动识别、增强现实叠加等方向。压缩包共838个文件体积约21.27MB含279张jpg图像并配套279个xml与280个txt标注文件其中xml记录边界框、类别等详细元数据txt给出简洁坐标便于直接接入Faster R-CNN、YOLO、SSD等常见检测框架。目前已有282人学习下载。这批数据源自PASCAL VOC 2007基准只保留tvmonitor类别规模适中适合快速迭代与算法验证读者既能用于训练模型准确框出电视屏幕也可借此理解两种标注格式在实际训练中的差异与用途。无论是初步验证检测算法还是为后续扩展至全量VOC或自建数据集打下基础这套资源都能提供清晰、轻量的起点整体结构简洁适合作为目标检测领域的入门练习数据。1. 一个能直接喂给目标检测模型的电视检测数据集tvmonitor_VOCtrainval2007.zip 到底能干什么做电视、显示器或屏幕类目标检测的工程落地最磨人的往往不是模型选型而是数据准备。VOC电视检测数据集 tvmonitor_VOCtrainval2007.zip 是一个遵循 PASCAL VOC 2007 规范整理的图像与标注压缩包所有标注里统一使用tvmonitor这个类别对应日常见到的电视、显示器、广告屏等显示设备。它的价值在于你拿到手解压就能看到JPEGImages、Annotations、ImageSets三层目录配合 YOLO、Faster R-CNN 等主流检测框架不需要再做大量清洗工作。这篇文章把拆包、转格式、训练验证和常见坑串起来适合刚接触 VOC 格式的数据标注工程师也适合正在找现成数据集的算法岗同学。2. 认识 VOC 标注体系JPEGImages、Annotations 与 tvmonitor 类别的边界框定义2.1 为什么选 VOC 2007 而不是 COCO 或 YOLO 格式PASCAL VOC 2007 是目标检测领域最经典的 benchmark 之一官方定义了 20 个常见类别tvmonitor就是其中之一。很多早期标注工具和公开数据集都沿用了 VOC 的 XML 格式即使到今天你在 GitHub 上找到的检测项目里仍有相当一部分数据层是用 VOC 组织起来的。原因很直接XML 标注格式比 COCO 的 JSON 扁平比 YOLO 的 txt 信息更完整它除了给出边框还保留了truncated、occluded、difficult、pose等字段方便你在训练前做过滤。不过实际工程里大多数人最终会转向 YOLO 格式因为 YOLO 的标注是一行五个数加载快、显存占用小。所以拿到这个数据集的第一件事不是直接喂给模型而是先把 VOC 的标注结构吃透。tvmonitor_VOCtrainval2007.zip这个名字里已经带上了VOCtrainval2007说明它的划分遵循 VOC 2007 的 train/val 体系tvmonitor作为唯一类别被单独抽取出来这份资源可以直接用来训练电视检测器也可以作为母数据集再补充你自己的真实场景图片做迁移学习。需要特别注意的是VOC 2007 中的tvmonitor和现在常说的television是同一个概念但官方类别名就叫tvmonitor。如果你在标注软件里习惯写tv或screen训练前必须统一映射回tvmonitor否则类别索引会错位我后面会专门讲这个坑。2.2 解压 zip 之后你一定会看到的三个目录用解压工具打开这个 zip正常会得到如下结构目录或文件作用说明JPEGImages/存放所有原始图片文件名如 000001.jpg与标注一一对应Annotations/存放每个图片的 XML 标注文件名与图片同名例如 000001.xmlImageSets/Main/存放 train.txt、val.txt、trainval.txt 等划分文件每行一个不带扩展名的图片编号其他可选的目录如 labels、segmentation取决于资源打包者是否额外提供图片和标注是一一对应的这一点非常重要。训练框架在读取时会根据文件名去找到对应的 XML 或 txt。如果其中一张图缺少标注或者标注文件里没有tvmonitor这个对象模型会把它当成背景图从而影响正样本数量。需要提醒的是在 Windows 上千万不要用右键菜单里的“压缩为 zip 文件夹”来解压或者重新压缩这类数据集。Win10 自带的 zip 处理对中文路径和部分 UTF-8 编码支持得不好解压后容易出现目录丢失或乱码。我一般会直接用 7-Zip 的“解压到当前文件夹”命令或者用 Python 的zipfile模块在脚本里解压这样目录结构和编码都不会出问题。2.3 XML 标注里到底存了什么解析一个 tvmonitor 的真实标注打开任意一个 Annotations 下的 XML内容大致是这样的annotation folderVOC2007/folder filename000001.jpg/filename source databaseThe VOC2007 Database/database annotationPASCAL VOC2007/annotation /source size width500/width height375/height depth3/depth /size segmented0/segmented object nametvmonitor/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin100/xmin ymin120/ymin xmax350/xmax ymax300/ymax /bndbox /object /annotation这里的bndbox给出的是绝对像素坐标xmin/ymin是目标的左上角xmax/ymax是右下角单位是像素不是归一化值。size子节点记录了图片原始宽度、高度和通道数转换 YOLO 格式时必须用这个宽高来归一化而不是用你代码里读出来的尺寸因为有的图片可能带有 EXIF 方向信息读出来会不一致。difficult字段代表这个目标是否难以辨认。在 VOC 原始评测中difficult1的样本一般不算入最后统计但在做训练时通常建议保留这些样本因为它们虽然是难例但也是有效信息会让模型更鲁棒。如果某个 XML 里完全没有object节点说明这张图没有标注目标里面只有背景这类图在转换时要么跳过要么生成一个空标签文件我在下一章会给出具体脚本。3. 动手把 VOC 转成 YOLO 格式XML 解析脚本与归一化参数细节3.1 为什么要转格式以及 YOLO 与 VOC 的坐标换算差异VOC 的 XML 格式虽然信息完整但是训练的时候每次都要解析 XML速度很慢。YOLO 系列框架要求每张图片对应一个同名 txt 文件文件里每行是class x_center y_center width height其中坐标都是相对于图片宽度和高度的比例值取值在 0 到 1 之间。所以要做这一步转换核心操作就是把绝对像素坐标转成归一化坐标。换算公式很简单x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height w (xmax - xmin) / width h (ymax - ymin) / height但这里有几个容易混淆的边界点VOC 的坐标有些标注工具是从 0 开始计数的有些是从 1 开始转换的时候一般不需要刻意减 1因为目标框稍微偏移一两个像素对训练结果几乎没有影响。真正需要关心的是坐标是否超出图像边界。由于标注时鼠标拖拽的误差xmax可能等于width甚至大于width如果你直接除以宽度得到的结果可能略大于 1YOLO 训练时会报错。所以转换脚本里要对坐标做一次截断处理。另一个容易被忽略的是类别 ID。YOLO 的类别 ID 必须从 0 开始连续递增比如只有一个类别就是 0。如果你的数据集里有多个类别需要提前建好一个字典把字符串类的类别名映射成整数 ID千万不要直接在 txt 里写tvmonitor这种字符串。3.2 一份可直接跑的转换脚本从 Annotations 生成 labels 与 train.txt下面这个脚本是我常用的转换方案它遍历 Annotations 下的所有 XML输出 YOLO 格式的 txt 到labels目录同时生成train.txt和val.txt路径配置好直接跑import os import xml.etree.ElementTree as ET from pathlib import Path SAVE_DIR Path(datasets/tv) # 数据集根目录按需修改 IMG_DIR SAVE_DIR / JPEGImages ANN_DIR SAVE_DIR / Annotations LBL_DIR SAVE_DIR / labels IMG_DIR.mkdir(exist_okTrue) LBL_DIR.mkdir(exist_okTrue) # 类别字典只有一个类别也要保证 ID 从 0 开始 LABEL_MAP {tvmonitor: 0} def convert_xml(xml_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.findall(object): name obj.find(name).text if name not in LABEL_MAP: print(fskip unknown class: {name} in {xml_path.name}) continue class_id LABEL_MAP[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界截断防止归一化后大于1 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) # 没有目标也生成空txt保持文件一一对应 out_path LBL_DIR / (xml_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) # 遍历所有XML for xml_path in ANN_DIR.glob(*.xml): convert_xml(xml_path)这段代码里最重要的参数是LABEL_MAP它决定了类别字符串和 ID 的映射关系。如果你的数据集里除了tvmonitor还有其他类别把这个字典继续往下加就行注意 ID 必须从 0 连续递增。另外SAVE_DIR这个根目录路径建议使用绝对路径否则后续训练时 YOLO 框架会因为相对路径对不上而报找不到图片。生成 labels 之后还要根据 ImageSets/Main 下的 train.txt 和 val.txt 生成 YOLO 需要的训练/验证图片列表。这两个 txt 里的每一行是图片编号不带扩展名拼接上JPEGImages/前缀和.jpg后缀即可。这个步骤可以直接用 shell 完成也可以并到上面的 Python 脚本里我比较习惯在转换脚本里顺手写完def write_image_set(set_file, out_file): image_paths [] for line in set_file.read_text().strip().splitlines(): image_id line.strip() if not image_id: continue image_paths.append(str(IMG_DIR / f{image_id}.jpg)) out_file.write_text(\n.join(image_paths), encodingutf-8) write_image_set(SAVE_DIR / ImageSets/Main/train.txt, SAVE_DIR / train.txt) write_image_set(SAVE_DIR / ImageSets/Main/val.txt, SAVE_DIR / val.txt)write_image_set这个函数两个参数输入是 VOC 官方给的划分文件输出是供 YOLO 使用的图片路径列表。注意路径一定要写到.jpg如果数据集里的图片是.jpeg或.png记得改后缀这也是最常见的翻车点。3.3 转换后必须做的三项自检文件计数、类别分布、空标签转换完成不要急着训练先做三项自检。第一统计 labels 目录下的 txt 数量必须和 Annotations 下的 XML 数量一致如果少了文件说明某个 XML 解析报错被漏掉了。第二统计所有 txt 第一列的类别 ID 分布确认只有 0 这一个 ID如果出现其他数字说明有未知类别混进来了需要回到类别字典里排查。第三检查空标签一个只有背景没有目标的图会生成 0 字节的 txt这类文件分到训练集里没问题但如果全部都是空标签那说明标注文件根本没用对。下面这段 shell 可以做快速检查# 检查文件数量 echo xml: $(ls Annotations/*.xml | wc -l) echo txt: $(ls labels/*.txt | wc -l) # 检查空标签 find labels -name *.txt -size 0 | wc -l # 检查类别ID分布 cut -d -f1 labels/*.txt | sort | uniq -c第一个wc -l用来对比数量第二个find ... -size 0列出空标签文件第三个cut统计每个类别 ID 出现了多少次。如果空的 txt 太多了比如超过总数的 5%最好回头看看 XML 里是不是有很多object标签为空的情况。在只有一个类别的数据集里如果出现了 ID 1 或者 2几乎可以肯定是某个 XML 里混入了非tvmonitor类别的对象这时候要用grep -L nametvmonitor/name Annotations/*.xml找出那些没有电视目标的文件单独处理。4. 避坑zip 解压、伪加密和标注框的经典翻车现场4.1 现象右键解压后看不到 JPEGImages 目录有人反馈说解压这个 zip 后打开只有一个文件夹里面没有JPEGImages或者直接提示文件损坏。原因是部分打包工具把根目录设置到了二级路径比如所有文件都被包在一个名为tvmonitor_VOCtrainval2007的父文件夹里Win10 自带的 zip 工具解压时可能丢失这层目录造成路径错乱。解决方法是先用 7-Zip 打开 zip查看它的顶层条目如果有父文件夹就直接解压到指定目录如果没有说明压缩包本身就没带顶层目录解压时自动创建一个根目录再释放。另外一个常见原因是 Win10 右键“压缩为 zip 文件夹”会生成一些兼容性标记导致 Linux 下解压时提示missing zip entry solution遇到这种问题不要硬刚改用 Python 的zipfile模块解压它能跳过这些异常条目。我一般都在脚本里处理解压顺便把文件名编码也纠正了。4.2 现象Win10 右键“压缩为 zip 文件夹”生成的 zip 在 Linux 上解压出现 missing zip entry这是很多同学在 Windows 上重新打包数据集时踩过的坑。你用 Win10 的“压缩为 zip 文件夹”把一个文件夹压缩传给别人别人在 Linux 上用unzip解压报错包含missing zip entry或者直接卡住。原因是 Windows 压缩工具对某些文件属性做了特殊标记Linux 的 Info-ZIP 解压时读不到这些条目。解决方式很简单不要在 Windows 上用自带压缩改用 7-Zip 的 zip 格式或者在 Linux 上解压时用 Python 标准库python -c import zipfile; zipfile.ZipFile(tvmonitor_VOCtrainval2007.zip).extractall(datasets/)Python 的zipfile容错性比unzip好很多遇到多出来的条目会直接跳过而不是报错。如果你要重新压缩数据集再分发也建议用 7-Zip右键菜单里的“压缩为 zip”选项能不用就不用。4.3 现象zip 提示伪加密密码根本不存在有同学下载之后用解压工具一打开提示需要输入密码可资源页面明明说没有密码。这多半是 zip 伪加密在作怪。伪加密的原理是人为修改了 zip 文件头部的加密标志位让解压软件误以为这个 zip 是加密的实际上数据本身没有加密。这种手法经常被用来防止别人直接解压转发。解决方法是把每个条目加密标志位里的0x0004改成0x0000网上有很多“zip伪加密修复”工具也可以自己写 Python 脚本。但我更建议你先确认来源如果是公开数据集压缩包伪加密往往只是打包者误操作。我记得以前帮同事处理过一个数据集也是提示伪加密最终我用一个三段式 Python 脚本遍历二进制数据找到PK\x01\x02的中央目录区域把通用位标记的 bit 0 清零就解开了。如果你不想写脚本直接用 7-Zip 打开有时它会无视伪加密直接显示文件列表但这并不保证所有工具都认最稳妥的还是修复后重新打包。4.4 现象转换后的标签框偏离电视区域整体错位这个坑最隐蔽。转出来的 YOLO 标签从数值上看完全正常但训练时验证集可视化发现预测框比真实电视区域偏了一大截。原因出在图片读取方式上。VOC 的 XML 里记录的宽高是原始图片的尺寸但像 JPEG 这种格式可能带有 EXIF 方向信息手机拍的图尤其常见。有的程序用 OpenCV 的cv2.imread读取默认忽略 EXIF 旋转读出来的数组形状还是原始宽高而有的程序用 PIL 的Image.open读取PIL 会按照 EXIF 信息自动旋转导致图片实际方向和坐标不一致。解决方法是统一用 OpenCV 读取图片并检查实际尺寸与 XML 的size字段是否一致可以在转换脚本里加一行判断import cv2 img cv2.imread(str(img_path)) h, w img.shape[:2] if h ! img_h or w ! img_w: print(fsize mismatch: {xml_path.name}, xml({img_w},{img_h}), actual({w},{h}))遇到这种不一致要么修改 XML 里的尺寸要么把图片重新保存成去掉 EXIF 方向的版本从根本上杜绝偏移。4.5 现象训练时发现没有“television”类只有“tvmonitor”这个坑更多是认知层面的不算 bug 但容易让人原地打转。你打开 XML 看到object/name是tvmonitor搜索关键词时却总想找电视。PASCAL VOC 2007 的官方类别定义里tvmonitor指的就是电视/显示器所有标注软件导出的名字都叫这个而不是television或者tv。如果你在整理自己的数据集时把tvmonitor改成了television那么 VOC 原始格式和 YOLO 格式之间的类别字典就对不上了。正确做法是保留原始类别名在训练配置的names列表里写tvmonitor不要因为语义符合直觉就随意重命名。我见过有人为了好看把类别名改成中文“电视”结果训练框架直接报类别索引越界白白浪费一下午。5. 小样本训练快速验证从划分数据集到跑通一个最小 YOLOv8 流程5.1 按 ImageSets/Main 的 trainval 划分数据避免自作主张VOC 2007 已经为你准备好了train.txt、val.txt、trainval.txt这些文件严格按照官方划分保证训练集和验证集互不重叠。很多初学者习惯在拿到数据集后自己shuffle一遍再划分但这会破坏与原始标注的对应关系甚至可能把同一张图片同时分到训练和验证里。最稳妥的做法就是直接用ImageSets/Main下现成的文件然后生成 YOLO 需要的数据列表。假设你的数据集根目录是datasets/tv把 trainval 中每个图片 ID 拼接成完整路径mkdir -p datasets/tv/images/train datasets/tv/images/val while read id; do cp datasets/tv/JPEGImages/${id}.jpg datasets/tv/images/train/; done datasets/tv/ImageSets/Main/train.txt不过更推荐用 Python 脚本处理因为cp容易复制到重复文件。你可以把上一章的write_image_set函数扩展一下把图片按划分分别软链到images/train和images/val这一步为的是让 YOLOv8 的数据加载器更直观地找到图片。注意 YOLOv8 的yaml配置需要指定训练和验证图片的目录路径而不是 txt 文件列表所以这个目录结构必须建好。5.2 YOLOv8 数据文件怎么写一行一行填给 yamlYOLOv8 用 yaml 文件描述数据集内容非常简洁。对于本数据集新建一个tv.yaml内容如下path: D:/datasets/tv # 数据集根目录的绝对路径 train: images/train # 训练图片目录相对于 path val: images/val # 验证图片目录相对于 path nc: 1 # 类别数量 names: [tvmonitor] # 类别名称顺序必须与类别ID一致path字段建议写绝对路径尤其是 Windows 环境下写相对路径经常因为当前工作目录变动导致找不到图片。train和val是相对于path的目录名也可以用绝对路径但在多个机器上迁移时不方便。nc的值必须与names列表长度一致如果这里是 1但 labels 中的类别 ID 出现了 1说明标签有问题训练时 YOLO 会直接报错。5.3 用预训练权重训练 50 个 epoch命令与参数解释数据集很小比如只有一两百张图我推荐用一个轻量的预训练模型快速验证。运行下面的命令yolo detect train datatv.yaml modelyolov8n.pt epochs50 imgsz640 batch16 patience10 projectruns nametv_exp参数含义很直接data指向刚写的 yamlmodel用yolov8n.pt这是 nano 版本参数量最小适合小数据集epochs50对几百张图来说已经足够再多很容易过拟合imgsz640是输入分辨率如果图片本身很小可以降到 416训练更快batch16是批量大小显存不够就降到 8 或 4patience10表示连续 10 个 epoch 验证集指标没有提升就提前停止小数据集上过拟合很快这个值不要设太大。训练过程中YOLOv8 会在每个 epoch 结束后输出metrics/mAP50(B)等指标。你要盯的重点是验证 loss 是否明显低于训练 loss如果是说明模型已经快背住训练集了需要提前停止或加数据增强。小样本场景下我看到很多人忽略这一点看起来 mAP 很高但换到真实场景基本无法用。所以我在训练命令里通常会加一句参数augmentTrue让 YOLO 自动启用 HSV 扰动和翻转能稍微缓解过拟合。5.4 常见失败显存溢出、标签为空、类别ID不连续显存溢出在小数据集上也很常见尤其是你用imgsz640但显卡只有 4G 显存时。解决方法是把batch降到 4甚至 2同时把imgsz降到 416。如果你发现即使用了batch2仍然溢出先检查是不是有其他程序占了显存或者换用 CPU 训练只是验证流程但 CPU 训练 50 epoch 可能要一小时不建议。标签为空导致的报错通常是no labels foundYOLO 会在训练前扫描images/train对应目录下的标签文件如果发现某个图片没有同名 txt会默认它没有目标但不会直接报错。真正的报错是标签文件存在但内容为空且nc1训练时会出现 negative samples 过多loss 降不下去这时候要回头检查 3.3 的自检。类别 ID 不连续的报错信息是class index out of range多半是你手动改过 labels 里的第一列数字或者类别字典没有从 0 开始把 yaml 的nc改成你期望的类别数同时输出 labels 检查一下。6. 进阶技巧用电视检测特有的类别平衡与 mAP 回调验证数据质量6.1 电视样本在 VOC 2007 中占比少先做类别分布审计VOC 2007 里tvmonitor本来就是个相对少见的类别如果你后续要合并其他数据集比如把电视检测和房间场景识别放在一起训练正负样本比例很容易失衡。我在拿到任何 VOC 数据集后第一件事就是统计每个类别的框数量而不是看图片数量。命令很简单grep -o nametvmonitor/name Annotations/*.xml | wc -l这一步能告诉你这个数据集里tvmonitor的实际实例数。如果只有几十个框训练出来的模型基本只能当作 demo真正落地至少需要几千个不同的场景。基于这个统计我会决定要不要做数据增强或者是否需要在 loss 里提高这个类别的权重。6.2 用 predict save_txt 输出可视化结果验证标注是否准确训练完best.pt后不要只看 mAP 数字我会强制自己对验证集跑一次可视化预测检查预测框和原始标注的重合程度。命令是yolo predict modelruns/tv_exp/weights/best.pt sourcedatasets/tv/images/val save_txtTrue save_confTrue conf0.25save_txtTrue会让 YOLO 把预测结果存成 txtsave_confTrue在 txt 里附加置信度conf0.25过滤掉低置信度框。重点是打开保存的预测图观察电视边缘是否对齐、是否出现重复框、有没有把相似物体误检成电视。这一步比 mAP 更能暴露数据问题比如标注框 missed 一半的屏幕、或者图片翻转后标注没跟着翻。从那以后我每次拿到新数据集都强制先跑一遍这个可视化流程确认预测框和标注框互相咬合再决定这个数据集能不能用于正式训练希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑