news 2026/9/11 23:15:24

VOC与YOLO标注格式转换实战:黄鼠狼数据集制作与训练前检查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VOC与YOLO标注格式转换实战:黄鼠狼数据集制作与训练前检查

简介:黄鼠狼目标检测数据集面向目标检测研究者与算法初学者,提供一批经过精细标注的真实图像资源。数据集共收录427张黄鼠狼jpg图片,对应427个xml标注文件与427个txt标注文件,同时支持VOC和YOLO两种主流格式,可直接衔接YOLO系列、Faster R-CNN等常见检测框架,免去自行整理与格式转换的麻烦。所有标注类别统一为weasel,由labelImg工具完成,并遵循边界贴合、目标不遗漏及一致性校验等标注规范,适合用于模型训练、验证集构建与算法调参练习,该标注模式与主流检测框架的输入要求高度兼容,能帮助使用者省去大量重复性标注时间。压缩包为rar格式,整体约32.34MB,内含1282个文件,解压后分为jpg图片、xml标注、txt标注三个文件夹,无需密码即可使用,目录结构清晰便于分类加载。目前已有148人浏览学习,适合需要快速获取小型目标检测数据集开展实验的开发者。

1. 427张黄鼠狼数据集的定位:为什么同时备好VOC和YOLO两份标注

在做生态监测或养殖场防护时,经常要识别"黄鼠狼"这种中小型食肉动物。手头攒了427张左右图片,下一步是目标标注。但标注格式不是随便选的:Pascal VOC的XML能记录对象名、难易、遮挡等结构化信息,编辑也方便;YOLO训练时只认txt,每行一个"类别 中心点x 中心点y 宽 高",全部归一化到0到1。这两份格式不是竞争关系,而是上下游:VOC当母版,YOLO当训练输入。我一般会先把所有图标成VOC,再写脚本转成YOLO。这个顺序能减少重复劳动,也让后续换算法时不至于推倒重来。适合刚拿到图像、准备做检测训练的同学。

2. 制作VOC格式标注:目录结构、标注工具与XML生成

在动手标图之前,先把VOC的目录骨架搭好。不是非要严格照搬VOC2007那种结构,但如果后面要和公开数据集混用,按惯例来能省掉很多路径错误。我常用的布局如下:

2.1 VOC目录骨架与XML字段

weasel_dataset/ ├── JPEGImages/ # 427张jpg/png原图 ├── Annotations/ # 同名的xml标注 └── ImageSets/ └── Main/ # train.txt/val.txt/test.txt,放图片名

XML内部要包含图像尺寸和每个目标的包围框。一个标准的标注文件长这样:

<annotation> <folder>JPEGImages</folder> <filename>IMG_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>weasel</name> <truncated>0</truncated> <occluded>0</occluded> <difficult>0</difficult> <bndbox> <xmin>345</xmin> <ymin>128</ymin> <xmax>612</xmax> <ymax>390</ymax> </bndbox> </object> </annotation>

XML里的<folder>在转YOLO时常常被忽略,但保持正确的<filename><size>很重要。<bndbox>四值中,xmin/ymin是左上角,xmax/ymax是右下角,必须是像素整数,并且满足xmin < xmax、ymin < ymax。如果一张图里有多个黄鼠狼,就会出现多个<object>块。下面是字段用途速查表:

字段含义转YOLO时的用途
filename图片文件名拼接图片路径
size/width/height原图宽高归一化坐标的分母
object/name类别名映射成类别ID
bndbox像素坐标计算中心点与宽高
difficult难例标记转YOLO可忽略或过滤

组织图片时要注意命名不要带空格和中文,因为YOLO读取路径时对空格敏感。比如把"IMG 001.jpg"改成"IMG_001.jpg",否则后续生成txt列表后,框架按空格切分会把文件名截断。

2.2 用LabelImg产出XML的流程与参数

最常见的做法是装一个LabelImg,它直接输出PascalVOC格式的XML,省去一次导出。安装和启动的命令是:

pip install labelImg labelImg JPEGImages/ Annotations/

第一个参数是图片目录,第二个参数是标注输出目录。如果是在Linux桌面环境用conda装,可能需要先装pyqt5再执行,否则窗口起不来。打开后先把顶部按键切到"PascalVOC"模式,再设置"自动保存"。画框快捷键是W,切上一张/下一张是A/D,注意这个D在部分版本里是删除已选框,删除键是Del。保存时会自动生成同名XML。

标注427张图不是一次性能做完的事,建议每标30张就抽查一次。常见问题是框住了树影或远处的半截身体。黄鼠狼体形细长,尾巴占体积明显,框的边界我会把尾巴尖也包进去;如果只标躯干,训练时学习到的目标范围会偏紧,推理时往往只给出半截框。

2.3 批量核查XML字段完整性的脚本

手工标完,先别急着转格式。写个Python脚本一次性检查所有XML里的size和box是否合理。下面这段能跑在python3,无第三方依赖:

import xml.etree.ElementTree as ET import glob, os xml_list = sorted(glob.glob("Annotations/*.xml")) for xml_path in xml_list: root = ET.parse(xml_path).getroot() # 取文件名和size结点 filename = root.findtext("filename") w = int(root.findtext("size/width")) h = int(root.findtext("size/height")) for obj in root.findall("object"): name = obj.findtext("name") if name not in ("weasel",): print(f"{xml_path}: unknown class {name}") bnd = obj.find("bndbox") xmin = int(bnd.findtext("xmin")); ymin = int(bnd.findtext("ymin")) xmax = int(bnd.findtext("xmax")); ymax = int(bnd.findtext("ymax")) # 检查坐标顺序和图片边界 if not (0 <= xmin < xmax <= w and 0 <= ymin < ymax <= h): print(f"{filename}: bad box ({xmin},{ymin},{xmax},{ymax}) in {w}x{h}")

这段代码用findtext的路径语法直接从XML取值,省去一层层find。输出里只要出现bad box,就说明边界写错了。最常见的错误是xmin和ymin填了0——LabelImg里从左上角角落开始拖框会得到0,这本身合法,但如果xmin等于xmax,框就成了线,转YOLO后会算出宽度为0,训练时loss变成nan。核查时把这类极窄框也标出来,用xmax - xmin < 5再加一条打印。后面我在转换脚本里会做一步clip,但这里的检查能更早发现问题。

3. 把VOC标注转成YOLO:脚本、归一化坐标与数据集划分

YOLO系列(v5/v8/v11都是)训练时需要的标注是纯文本txt,每张图片一个同名txt。与VOC不同,YOLO的坐标是相对图片宽高归一化后的浮点数,所以转换的关键就是计算中心点和宽高再除以图像尺寸。

3.1 为什么YOLO格式只需要txt

VOC的XML信息多,但训练框架不直接消费XML。YOLO为了读取快,把每个目标压缩成一行:class_id x_center y_center width height。其中class_id从0开始,对应data.yaml里的类别顺序。x_center和y_center是目标包围框中心点在图片上的相对位置,width和height是框的相对宽度和高度。全部在[0,1]区间内。由于黄鼠狼数据集的图片宽度和高度各不相同(可能来自不同相机),不归一化就无法在一个batch里训练。

从bndbox到YOLO四值的公式是:

x_center = ((xmin + xmax) / 2) / img_width y_center = ((ymin + ymax) / 2) / img_height box_w = (xmax - xmin) / img_width box_h = (ymax - ymin) / img_height

先用整型的像素框计算中心,再除以宽高得到浮点。有的同学会问,为什么不用左上角坐标?因为YOLO预测的就是中心点加宽高,学习目标必须和预测目标对齐。对应关系可以看下面这个表:

VOC字段YOLO字段转换操作
bndbox/xmin, xmaxx_center(xmin+xmax)/2/w
bndbox/ymin, ymaxy_center(ymin+ymax)/2/h
xmax - xminwidth除以w
ymax - yminheight除以h

3.2 从XML到txt的转换脚本

下面这个脚本遍历Annotations目录,输出到labels目录。它同时生成classes.txt,并划分train/val。我用的是python + xml.etree,不需要额外库:

import xml.etree.ElementTree as ET import glob, os, random CLASSES = ["weasel"] # 类别名列表,顺序就是class_id os.makedirs("labels", exist_ok=True) xml_list = sorted(glob.glob("Annotations/*.xml")) random.seed(42) random.shuffle(xml_list) # 先打乱,再做划分 train_txt = open("ImageSets/Main/train.txt", "w") val_txt = open("ImageSets/Main/val.txt", "w") for idx, xml_path in enumerate(xml_list): root = ET.parse(xml_path).getroot() filename = root.findtext("filename") w = int(root.findtext("size/width")) h = int(root.findtext("size/height")) base = os.path.splitext(filename)[0] label_lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in CLASSES: continue # 忽略未定义类别 class_id = CLASSES.index(name) bnd = obj.find("bndbox") xmin = int(bnd.findtext("xmin")); ymin = int(bnd.findtext("ymin")) xmax = int(bnd.findtext("xmax")); ymax = int(bnd.findtext("ymax")) # 坐标越界就clip到图片范围内 xmin = max(0, xmin); ymin = max(0, ymin) xmax = min(w, xmax); ymax = min(h, ymax) if xmax <= xmin or ymax <= ymin: continue # 非法框直接丢弃 x_center = ((xmin + xmax) / 2) / w y_center = ((ymin + ymax) / 2) / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h label_lines.append(f"{class_id} {x_center:.5f} {y_center:.5f} {box_w:.5f} {box_h:.5f}") with open(f"labels/{base}.txt", "w") as f: f.write("\n".join(label_lines)) # 前90%进train,后10%进val if idx < int(len(xml_list) * 0.9): train_txt.write(f"{base}\n") else: val_txt.write(f"{base}\n") train_txt.close(); val_txt.close()

脚本的逻辑:先按类别表把VOC的name转成整数ID;clip操作负责处理边界框越界;label_lines用5位小数保留,训练时完全够用。划分比例按9:1硬切,如果你更在意验证集样本,可改成8:2。需要注意,这里是"先打乱再切",如果不shuffle,前90%可能都是同一拍摄时段的图像,场景不独立,验证指标会虚高。random.seed(42)保证每次跑出来的划分一致,方便复现。

注意:转换脚本里做clip后,如果一张图中目标一半在画面外,clip会保留画面内部分,但这样框不完整。严格做法是直接用原坐标参与归一化,让模型学习到越界框,而不是手动clip。上面脚本为了保险还是做了clip,实际项目里可以加个开关控制。

3.3 数据集划分与文件路径约定

YOLO系训练时,不是直接读这些txt找图片,而是通过train.txt/val.txt里的图片名(或路径)找到JPEGImages下的原图,再读同名txt。所以这里的train.txt内容是"IMG_001"这种不带扩展名的base名。实际使用时,ultralytics框架还支持直接给一个.txt文件,每行是图片的绝对路径。为了在不同机器上都能跑,我会把train.txt改成绝对路径,用一条命令即可:

awk '{print "/absolute/path/weasel_dataset/JPEGImages/"$0".jpg"}' ImageSets/Main/train.txt > ImageSets/Main/train_path.txt

注意,如果图片是png,需要把.jpg替换成.png。绝对路径方式在迁移服务器时容易失效,我一般更推荐把数据集目录固定放在某个路径下,再在data.yaml里用相对路径。427张图的数据量不大,划分后train大约384张,val约43张。对小数据集来说,val集里的场景应该覆盖不同角度和光线,否则验证时loss波动会很大。

4. 训练前的标注体检:类别映射、空标注与坐标越界的排查

转完YOLO格式,别急着开train。先做一轮体检。这一步能避开训练中途爆loss或mAP为0的问题。

4.1 类别映射表:VOC的name与data.yaml的通道一致性

YOLOv8的data.yaml里categories顺序必须和转换脚本里的CLASSES顺序完全一致。比如我们classes.txt只有一行weasel,那么data.yaml里就是:

path: /path/to/weasel_dataset train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 1 names: ["weasel"]

如果后面加了"background"或者其他动物,转换脚本的CLASSES和data.yaml的names必须同时加,且顺序相同。这里最容易出的错是:在VOC XML里有的类别叫"weasel",有的叫"weaseled",一个多了字母,转换时会落到class_id=0或直接被跳过。建议在转换脚本跑完后,统计一遍labels目录里出现的class_id分布。下面的检查表可以直接对照:

检查项方法判定标准
类别ID一致性对比classes.txt与data.yaml两者顺序完全一致
空标注ls -l labels/*.txt检查0字节文件空文件数低于图片总数5%
坐标越界统计txt中浮点数范围0 <= 值 <= 1
tiny box按原图宽高换算像素尺寸小于10px需复核

4.2 统计每张图实例数与类别分布

可以用一个Python脚本读取所有txt,输出直方图信息。下面的命令直接统计每个类别id出现次数和空标注文件数量:

for f in labels/*.txt; do if [ ! -s "$f" ]; then echo "empty: $f"; fi done awk '{count[$1]++} END {for (id in count) print id, count[id]}' labels/*.txt

第一段循环用-s判断文件是否为空,空文件会输出到终端。第二段awk按第一列类别ID计数。如果427张图里有30张空标注,说明这些图要么是背景图,要么漏标了。建议把空标注对应的图片挑出来再补标,因为训练时背景图不是完全没用,但如果比例太高,模型会倾向输出极小的框。对黄鼠狼这种单类别数据集,还可以数一下每张图平均实例数,如果平均不到1.2,说明很多图只有单目标,模型容易学到"图里只有一个目标"的偏置。

4.3 坐标越界与宽高为0的处理方式

转换脚本虽然做了clip,但clip不能解决标错位置的问题。比如一张720p的图,XML里写ymax=760,clip后变成720,框被拉到图像下边缘,如果原本目标没到边缘,标注就等于偏了。更隐蔽的是"退化为线":xmax-xmin=1这种,clip后仍然有宽度,但视觉上就是竖线,训练时让模型学到一个超扁平的框。

我一般再跑一次复核,把宽或高小于10像素的框挑出来看原图:

import os for txt in os.listdir("labels"): with open(os.path.join("labels", txt)) as f: lines = [l.split() for l in f.read().splitlines()] for line in lines: w = float(line[3]); h = float(line[4]) # 按参考分辨率估算实际像素,这里以1280x720为例 if w * 1280 < 10 or h * 720 < 10: print(txt, "tiny box", w, h)

这里的1280和720是按常见分辨率估算的比例,严格做法是从JPEGImages里读实际宽高。把这类tiny box对应原图挑出来,修正VOC的XML后再重新跑一次转换,而不是直接改txt。原因前面说过:VOC是源头,txt是派生。如果发现大量越界,还要检查是不是图片本身被裁剪过,比如从视频抽帧后分辨率变了,但XML里还是旧尺寸,这种时候要批量更新size字段,而不是单个改框。

5. 双格式在YOLOv8训练中的验证技巧

到了这一步,数据已经准备好。最后分享一个我常用的验证闭环:不直接train,先用YOLOv8做一次"干跑",确认data.yaml能被框架解析,再用预测结果把标注画回图上检查。

5.1 用ultralytics快速验证data.yaml与数据集

安装ultralytics后,运行:

yolo detect train data=weasel.yaml model=yolov8s.pt epochs=1 batch=8

训练1个epoch不会产出有效模型,但会在启动阶段检查数据集的路径、标签是否存在、类别数是否匹配。如果labels路径不对,框架会报"Dataset not found"或"Unable to read labels"。这里有个关键参数:data.yaml中的train指向的是图片路径列表文件,如果不写val,框架会采样一部分train做val,注意这种行为在验证指标上会产生偏差。427张图的规模下,建议显式把val指向一个单独的列表。

5.2 可视化标注叠加与常见误区

干跑通过后,随机抽10张图,把label画在图上肉眼检查。这个脚本直接用OpenCV画矩形:

import cv2, os img_dir = "JPEGImages"; label_dir = "labels" for img_name in os.listdir(img_dir): img = cv2.imread(os.path.join(img_dir, img_name)) base = os.path.splitext(img_name)[0] txt = os.path.join(label_dir, base + ".txt") if not os.path.exists(txt): continue h, w = img.shape[:2] for line in open(txt): cid, xc, yc, bw, bh = map(float, line.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, "weasel", (x1, max(0, y1-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite("visual/" + img_name, img)

画框时注意要把归一化坐标乘回原图宽高,用int()截断可能产生1像素误差,但不影响检查。最常见的误区是有人直接在高分辨率原图上画,然后看到框偏小,以为是标注错了,其实模型输入会resize成640x640,框在原始尺寸下本来就偏小。真正常见的错误是:坐标出现负值,或者框的中心点明显偏移,那就要回VOC里改XML,改完重新转格式。

一个实用的双格式同步技巧:把转换脚本保存为voc2yolo.py,每次改完标注只需python voc2yolo.py重跑一次,同时用git管理Annotations/labels/。在commit信息里写明改了哪张图,这样当训练指标异常时,能快速回溯到某一轮标注改动,而不是翻整个数据集。

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

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

汉明距离:原理、应用与优化实现

1. 汉明距离基础概念解析汉明距离(Hamming Distance)是信息论和编码理论中的一个基础概念&#xff0c;由理查德汉明在1950年首次提出。这个看似简单的度量标准&#xff0c;在现代计算机科学的多个领域都发挥着关键作用。1.1 定义与数学表达汉明距离严格定义为&#xff1a;两个等…

作者头像 李华
网站建设 2026/9/11 23:13:55

VOC垃圾分类数据集解析:目标检测标注规范与工业落地要点

简介&#xff1a;本资源是面向计算机视觉初学者与YOLO目标检测实践者的高质量垃圾分类检测数据集&#xff0c;专为真实场景下的垃圾细粒度识别任务设计&#xff0c;覆盖纸张、塑料、果皮、玻璃杯、易拉罐、厨余垃圾等10余类常见生活垃圾&#xff0c;可直接用于VOC或YOLO格式的模…

作者头像 李华
网站建设 2026/9/11 23:13:43

行人重识别实战:IBN-ResNet50+Triplet+Center Loss全流程解析

简介&#xff1a;本资源是一套面向计算机视觉研究者与算法工程师的行人重识别&#xff08;ReID&#xff09;实战项目&#xff0c;聚焦跨摄像头行人匹配与图像检索任务&#xff0c;适用于安防监控、智能交通等实际场景&#xff0c;兼顾算法原理理解与工程落地能力提升。压缩包共…

作者头像 李华
网站建设 2026/9/11 23:13:17

WeKnora 离线部署:Docker + Ollama 跑通文档问答全链路

WeKnora 离线部署&#xff1a;Docker Ollama 跑通文档问答全链路 【免费下载链接】WeKnora Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki. 项目地址: https://gitcode.com/G…

作者头像 李华
网站建设 2026/9/11 23:12:40

X光安检YOLO数据集:三格式统一与工业级训练适配

简介&#xff1a;本资源是面向计算机视觉初学者与YOLO目标检测实践者的X光安检场景专用数据集&#xff0c;解决真实工业场景下缺乏高质量、多格式标注数据的训练瓶颈问题。数据集包含5000张真实X光安检图像&#xff0c;配套VOC&#xff08;XML&#xff09;、COCO&#xff08;JS…

作者头像 李华