ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

112张蛇图目标检测数据集制作:VOC转YOLO格式标注全流程

112张蛇图目标检测数据集制作:VOC转YOLO格式标注全流程 简介蛇类目标检测数据集专为计算机视觉入门与实战设计整理约112张真实蛇类图片采用VOC与YOLO双格式标注适合需要训练检测模型的学习者、竞赛选手或算法工程师快速验证流程。压缩包内共337个文件其中jpg图片112个、xml标注112个、txt标注113个包体仅18.71MB轻量便于下载。图片均使用labelImg工具标注类别统一为snake标注过程强调贴合边界、覆盖全目标与一致性校验解压后可看到清晰的三子目录结构无需格式转换即可供YOLO系列、Faster R-CNN等常见检测框架读取。目前已有74人学习下载作为小型专用数据集既能帮助初学者对照图片与标注理解VOC/YOLO标签结构也可直接用于检测算法的快速测试、迁移学习或数据增强尝试与其它数据集搭配可进一步丰富训练样本多样性省去手工整理与标注时间。1. 蛇数据集 112 张的标注直觉VOC 与 YOLO 到底卡在哪做目标检测的朋友上手一个项目第一步往往不是调模型而是先问一句数据长什么样。蛇数据集 112 张左右、带 VOC 和 YOLO 两套目标标注这个体量在真实项目里非常典型不够大到直接丢给 YOLO 训练收敛但足够用来跑通标注流程、验证格式转换脚本、评估小样本下的检测可行性。很多人在这一步翻车不是不会用标注工具而是 VOC 的 XML 和 YOLO 的 TXT 坐标体系根本不是一回事转格式时坐标算错模型训练出来框全是歪的。这篇文章就沿着“标注制作 → VOC 与 YOLO 互转 → 小数据集训练验证”这条主线把 112 张蛇图怎么从零做成能用的数据集、每一步的参数怎么设、坑在哪讲清楚。适合谁想自己攒数据集但没跑通全流程的新手以及需要快速把 VOC 标注转成 YOLO 格式去喂模型的熟手。目标检测常用标注工具、yolo 预训练模型下载、yolo 训练开源平台这些配套环节也会在对应章节里带出来——但核心始终是这份蛇数据集本身怎么做出来、怎么转格式、怎么验证可用。2. 开箱先拆数据格式VOC 的 XML 与 YOLO 的 TXT 到底差在哪2.1 VOC 是「绝对像素框」YOLO 是「归一化中心点」先把两种格式的本质讲透。VOC 格式源自 PASCAL VOC 挑战赛标注信息存放在 XML 文件里每个目标用一个bndbox块描述里面是xmin、ymin、xmax、ymax四个整数值单位是图像像素。它描述的是一个“绝对位置”比如左上角在 (120, 80)、右下角在 (260, 300) 的矩形框。这种格式读起来直观人眼直接能看到框在哪但它有个弱点同一张图被缩放后XML 里的坐标数字不再匹配必须重新换算。YOLO 格式则完全不同。每个目标在 TXT 文件里占一行五个数值分别是class_id cx cy w h其中class_id是从 0 开始的类别索引cx和cy是目标中心点的归一化坐标w和h是归一化宽高。归一是相对图片宽高而言的取值范围一般在 0~1 之间。这种设计的核心好处是图片无论怎么缩放标注文件都不用改模型在训练时读入的始终是比例值。这两套体系的差异就是做蛇数据集时最容易踩坑的起点。标注工具导出的是 VOC 的绝对像素框但 YOLO 训练只认归一化中心点中间那一步换算必须你自己写脚本或找工具完成。2.2 一个典型 XML 里到底有什么拿一张 640×480 的蛇图举例LabelImg 保存的 VOC XML 长这样annotation folderimages/folder filenamesnake_001.jpg/filename size width640/width height480/height depth3/depth /size object namesnake/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax260/xmax ymax300/ymax /bndbox /object /annotation解析这段 XML 时先读size拿到图片宽高再遍历object读name和bndbox。这里有一个非常隐蔽的点filename只是字符串如果 XML 和图片文件名对不上后续划分数据集时会把 XML 归到错误图片上。我一般会在标注完成后立刻校验一遍文件名一致性而不是等到转 YOLO 才发现。difficult字段也值得注意。标注时如果某个蛇被树叶遮挡严重判断不清完整轮廓可以打上 difficult1。YOLO 格式没有对应字段转换脚本里如果保留这种样本训练时它会当作普通正样本参与 loss 计算反而拉低精度。常见做法是转换前直接过滤掉 difficult 样本或者放到验证集里只做评估不做训练。2.3 YOLO 的 TXT 每一行代表一个目标同样一张图转成 YOLO TXT 后大约是这样0 0.296875 0.395833 0.218750 0.458333五个数值依次是类别索引 0、中心点 x 归一化坐标、中心点 y 归一化坐标、宽度归一化、高度归一化。换算逻辑是cx (xmin xmax) / 2 / image_width cy (ymin ymax) / 2 / image_height w (xmax - xmin) / image_width h (ymax - ymin) / image_height注意w和h也是归一化值很多新手会写成像素宽高训练直接崩。另外TXT 文件名要和图片名完全一致只换扩展名YOLO 训练时会用图片路径去找同名 TXT文件名对不上等于这份标注白做了。2.4 目录结构两种格式各自的「标准布局」VOC 数据集的标准目录是VOC2007风格至少包含AnnotationsXML 文件、JPEGImages原图、ImageSets/Maintrain.txt、val.txt 列表文件三个子目录。YOLO 格式常见布局是images/train、images/val和labels/train、labels/val一一对应。112 张左右的蛇数据集我建议一开始就按 YOLO 目录组织因为最终要喂给 yolo 训练开源平台或本地 YOLO 训练VOC 阶段只作为标注中间产物保留。目录结构决定了下游脚本的复杂度。如果 VOC 阶段用Annotations JPEGImages ImageSets/Main转 YOLO 时三步走读 XML → 写 TXT → 按列表文件复制图片到 train/val 目录。每一步都有对应脚本第 4 章会全部给出。3. 用 LabelImg 落地 112 张蛇图标注从截图到 XML 的完整流程3.1 先解决「112 张图从哪来」的合规问题动手标注前先要凑齐 112 张蛇图。这个规模说多不多说少不少。常见做法有三种一是从公开学术数据集里筛选蛇类子集二是用公司自有监控或巡检测设备拍到的画面三是在允许的范围内用爬虫抓取网络公开图片。个人做学习项目第三种最普遍但要注意版权和来源合规不要拿有明确版权声明的照片直接商用。爬图时注意一个质量陷阱不要只搜“蛇”要搜蛇的常见姿态和场景词比如盘绕、爬行、伪装在枯叶中、树栖蛇类等。112 张图如果全是白背景标本照标注再规范训练出来的模型一到野外场景就废。我见过很多数据集就是栽在这一步背景单一导致模型只学到了背景特征换场景直接翻车。抓完图先人工筛选一遍删除模糊的、严重过曝的、重复的、以及蛇占画面不到 5% 的图。目标标注的目的是教会模型“蛇在哪”不是“图里有没有蛇”蛇太小或太模糊的图会让标注框质量很难保证。112 张这个体量筛图花 30 分钟后面标注能省至少 2 小时。3.2 标注工具选型和最小安装配置目标检测常用标注工具里LabelImg 是最经典的选择它天然支持导出 PASCAL VOC 的 XML 格式。安装有两条路pip 安装和源码运行。pip 安装最省事pip install labelImg labelImg # 启动图形界面源码方式适合需要改快捷键或定制类别文件的情况git clone https://github.com/HumanSignal/labelImg.git cd labelImg # Linux 下安装依赖 pip install pyqt5 lxml pyrcc5 resources.qrc -o resources.py python labelImg.py启动后在标签类一栏填入snake然后切换到 PascalVOC 模式默认就是。LabelImg 支持A键切换上一张、D键下一张、W建框、CtrlS保存全程鼠标配合键盘112 张图大约 1~1.5 小时能标完。参数层面有一个容易忽略的点LabelImg 的指针工具下勾选使用默认标签可以免去每张图选类别的重复操作。前提是数据集里只有一类目标蛇数据集通常就是单类这个选项能显著提速。3.3 标注蛇类目标的框定规范蛇和普通物体不一样它是细长条目标框定方式直接影响后续检测效果。我的习惯是用包含蛇全身的最小矩形框头尾各留 5~10 像素余量不要贴得太紧——太紧的框会让模型学到的边界特征过于苛刻推理时稍微露一点头尾就漏检。蛇盘成圈时框依然是外接矩形不要试图用多边形贴合轮廓。每张图我要求至少包含一个完整目标被遮挡到只剩头部或不足 20% 身体的蛇标记为difficult1或直接跳过。112 张图如果目标是计算样本量一张图里有多条蛇就标注多个框总目标数通常会超过 150这对后续训练更有利。标注完成后在标注目录里检查一遍有没有忘记保存的图。LabelImg 的“自动保存”模式有时会导致空 XML 生成——某张图打开却没标框直接 CtrlS也会生成一个没有object的 XML。这种空 XML 在转 YOLO 时会让脚本报 KeyError第 5 章避坑里会细讲。3.4 标注质量抽检写个小脚本快速核对112 张全部标完后不要立刻转格式。写一个几行的检查脚本统计 XML 里目标框的坐标是否越界、是否为空标注、类别名是否统一import os import xml.etree.ElementTree as ET xml_dir Annotations for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) objs root.findall(object) if len(objs) 0: print(f[empty] {xml_name}) for obj in objs: name obj.find(name).text 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 xmin 0 or ymin 0 or xmax width or ymax height: print(f[out-of-range] {xml_name}: ({xmin},{ymin},{xmax},{ymax}) vs ({width},{height})) if name ! snake: print(f[unexpected-class] {xml_name}: {name})跑完看到零输出再进入格式转换环节。这一步是给后面吃后悔药的机会——XML 阶段改标注只需在 LabelImg 里打开重改等转成 TXT 再发现类别名写错就得回头重新标非常费时间。4. VOC 转 YOLO 格式坐标换算脚本与数据划分的 4 个细节4.1 转换脚本的正解别用正则硬抠 XML用 ElementTree网上不少 VOC 转 YOLO 脚本用正则表达式匹配xmin遇到 XML 里属性顺序变化或注释行就会挂。正确做法是使用 Python 标准库xml.etree.ElementTree做结构化解析。下面是我在 112 张蛇数据集上实际用过的转换脚本单类场景干净直接import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, output_dir, class_list, img_width, img_height): 将单个VOC XML转换为YOLO TXT class_list: [snake] 类别名到索引的映射 tree ET.parse(xml_file) root tree.getroot() # 以预读的图片宽高为准避免依赖XML内size字段 lines [] for obj in root.findall(object): difficult int(obj.find(difficult).text if obj.find(difficult) is not None else 0) if difficult: continue # 过滤掉被遮挡严重的困难样本 name obj.find(name).text if name not in class_list: print(f[skip] {xml_file.name} 未定义的类别: {name}) continue class_id class_list.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标换算绝对像素 - 归一化中心点宽高 cx (xmin xmax) / 2.0 / img_width cy (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 越界保护归一化坐标理论上在0~1之间但浮点误差可能超过边界 cx, cy, w, h min(max(cx, 0.0), 1.0), min(max(cy, 0.0), 1.0), min(w, 1.0), min(h, 1.0) lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if not lines: print(f[warning] {xml_file.name} 没有有效标注生成空TXT) out_path Path(output_dir) / (Path(xml_file).stem .txt) with open(out_path, w) as f: f.write(\n.join(lines)) # 批量转换入口 if __name__ __main__: xml_dir Annotations yolo_label_dir labels_all os.makedirs(yolo_label_dir, exist_okTrue) img_dir JPEGImages class_list [snake] for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) img_name xml_name.replace(.xml, .jpg) img_path os.path.join(img_dir, img_name) # 用PIL读取实际宽高避免XML里size字段与真实图片不一致 from PIL import Image with Image.open(img_path) as img: img_width, img_height img.size voc_to_yolo(xml_path, yolo_label_dir, class_list, img_width, img_height)这段脚本有四个关键设计。第一图片宽高不依赖 XML 里的size字段而是直接用 PIL 读图取真实尺寸因为标注工具写入的 size 和图片实际尺寸偶尔会对不上一旦不一致归一化坐标全错。第二cx和w都做了min钳制浮点除法的余差会导致坐标偶尔等于 1.00000001某些 YOLO 版本会直接报坐标越界这里先卡了一道保险。第三difficult1的样本被过滤掉了避免污染训练集。第四输出 TXT 与 XML 同名后续复制图片时按列表文件操作。4.2 数据划分112 张怎么分 train/val/test 才科学112 张的训练集规模很小划分比例不能照搬大数据的 8:1:1。我建议按 85:10:17 左右的比例拆train 约 85 张、val 约 10 张、test 约 17 张。这个比例下验证集偏小但保住了训练量如果 test 只留 5 张最终的评估结果方差太大一张误检就拉低十几个点没有说服力。import random from pathlib import Path random.seed(42) # 固定随机种子保证划分结果可复现 image_paths list(Path(images_all).glob(*.jpg)) random.shuffle(image_paths) total len(image_paths) # 112左右 train_size int(total * 0.85) val_size int(total * 0.10) train_paths image_paths[:train_size] val_paths image_paths[train_size:train_size val_size] test_paths image_paths[train_size val_size:] def write_list(paths, filename): with open(filename, w) as f: for p in paths: f.write(str(p.resolve()) \n) write_list(train_paths, train.txt) write_list(val_paths, val.txt) write_list(test_paths, test.txt) print(ftrain{len(train_paths)}, val{len(val_paths)}, test{len(test_paths)})划分完成后按列表把图片和标签复制到images/train、images/val和labels/train、labels/val。复制时用绝对路径可以免去后续训练时相对路径拼错的困扰。4.3 单独建一个 classes.names 文件别靠记忆YOLO 训练读取类别名靠的是classes.names或data.yaml里的类别表。112 张单类数据就一行snake这里的顺序必须和 TXT 里的 class_id 一一对应。工具类项目尤其容易踩这个坑转换时 class_list 写[snake]id 是 0训练配置里类别写snake顺序一致就没事。如果后续扩展成snake, viper, cobra多类id 不能有跳号。转换脚本是工业级写法必须固化成一份classes.names跟着数据集走而不是每次拍脑袋填。4.4 转换后的「等价性验证」怎么确认没转错坐标换算对了没有肉眼检查比想象中重要。强烈建议写完转换脚本后随机挑 10 张图把 YOLO 坐标画回图片上和原 VOC 框对比是否重合。做法是读取 TXT反解出像素坐标import cv2 def draw_yolo_box(img_path, txt_path, img_width, img_height, class_names): img cv2.imread(img_path) with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue class_id int(parts[0]) cx, cy, w, h map(float, parts[1:5]) xmin int((cx - w / 2.0) * img_width) ymin int((cy - h / 2.0) * img_height) xmax int((cx w / 2.0) * img_width) ymax int((cy h / 2.0) * img_height) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, class_names[class_id], (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) return img如果 10 张里有一张框和原标注偏差超过 2 像素说明是脚本问题超过 10 像素大概率是图片宽高取错了。这一步不要偷懒112 张数据如果转格式就错了 30%后面训练再多轮数也是白费。5. 蛇数据集制作避坑5 个让标注白干的典型问题5.1 空 XML 文件导致批量转换脚本中断现象转换脚本跑到第 40 个文件就抛异常IndexError: list index out of range或KeyError: width。原因LabelImg 自动保存模式下打开图片未标注就保存生成了没有object、甚至没有size的空 XML。转换脚本里没有对空标注做防御直接访问就崩。解决在批量脚本开头加一个跳过逻辑检测object为空就打印警告并跳过。如果空 XML 比较多超过 10 个建议回到 LabelImg 里重新标注而不是跳过——跳过的图片没有标注训练时它不会参与 loss 计算但 val 阶段如果遇到未标注图片会把背景误判为漏检指标虚低。5.2 XML 里的 size 字段和真实图片尺寸不一致现象转换后画框验证发现每个框横向偏移越靠近图片右侧偏差越大左侧几乎正确。原因某些标注工具在图片预处理阶段缩放过但 XML 里的size没有同步更新或者图库下载时 EXIF 旋转标签导致 PIL 读取方向和标注工具读取方向不同宽高互换框全部 90° 错位。解决转换脚本一律以PIL.Image.open实际读到的宽高为准不读 XML 里的 size。已标注的图如果有 EXIF 方向问题趁早统一用ImageOps.exif_transpose修正并重新保存否则后续转 YOLO 和训练时图像预处理逻辑可能不一致出现“标注正确但框画歪”的诡异现象。5.3 类别索引错位class_id 填成了类别名字符串现象训练时报错Could not load labels或内存疯狂增长检查 TXT 发现内容是snake 0.5 0.5 0.3 0.3。原因转换时偷懒用str(name)直接拼进输出没有先查 class_list 拿到索引。YOLO 只认整数 id收到字符串后解析失败。解决转换脚本强制校验输出前用isinstance(class_id, int)断言。类别少时最容易手滑单类数据集更是如此——因为[snake]里 index 为 0直接输出0也是字符串必须用str(int())显式转换。5.4 归一化的坑w和cx都对了但h算成了比例值现象画框验证时发现框的高度和蛇身体长度不匹配上下方向正确的框但框住的内容被裁剪。原因个别手写脚本里h (ymax - ymin) / img_width把高度也除以了图片宽度。VOC 像素坐标里宽度和高度数值是绝对值归一化必须各自除以对应的宽和高混淆就会让框的宽高比例失真。这个代码 bug 很隐蔽因为单张图看起来框好像是“标少了点”不仔细对比无法发现。解决画框验证脚本里对每个框输出w_pix w * img_width和h_pix h * img_height和 XML 里xmax - xmin、ymax - ymin做逐项比较。5.5 BN 相关小数据集训练时 loss 突然跳成 NaN现象112 张数据丢进 YOLO 训练前 30 轮 loss 正常下降第 37 轮 loss 直接变成nan之后永久恢复不了。原因小数据集 小 batch size 时BatchNorm 层的统计量极不稳定。若 batch size 只有 2 或 4某一步里图片全是背景没有蛇目标正样本梯度缺失BN 统计量异常触发 NaN。这本质是序列化标注数据问题不是标注本身错误但常被误判成“数据集有问题”。解决训练配置里将 batch size 至少提到 8112 张图可分 14 步开启mosaic0.5做数据增强自动合成含目标的图片。如果仍崩把学习率初始值从 0.01 降到 0.001并开启 warmup 让 BN 统计量前几步平滑累积。这类问题一定程度是玄学遇到别慌先改 batch size 和增强开关。6. 112 张标注怎么真正用起来数据增强与小模型验证技巧标注做完、格式转对要回答“这 112 张能不能训出可用模型”。答案是可以的前提是你把它当成“验证标注质量”的快速迭代而不是追求最终精度的完整训练。第一步是选模型。用 YOLOv8 系列的 nano 版本约 320 万参数或 YOLOv5s 都合适这个体量数据喂大模型必然过拟合。训练参数我固定这样设imgsz640、epochs100、batch8、patience20。如果显存 8G 以下imgsz512标注的归一化坐标不受分辨率影响这是 YOLO 格式最大的便利。第二步是增强。蛇是细长条目标Mosaic 增强时蛇身容易被裁没了建议设置mosaic0.5比默认 1.0 低一半。HSV 扰动调高到 0.02 帮助模型应对野外色彩变化但不要超过 0.05否则蛇的伪装色特征会被抹掉。翻转增强对蛇特别有用蛇没有固定朝向左右翻转后语义不变fliplr0.5可以稳定开。第三步是验证。训练完成后打印混淆矩阵重点看snake类的 recall。112 张单类数据val 集 10 张里若有 3 张漏检recall 只有 0.7别急着加数据先检查那 3 张的标注框是否完整框住蛇身。很多情况下漏检根源是标注框贴得过紧头部露出一点就判定为背景。把漏检图找出来评估它的遮挡程度和框边距——重新标注这些图往往比加新图更有效。我还保留的一个习惯是训完 100 轮后把模型推理画框和原标注叠加保存成对比图按“预测框完全覆盖 / 覆盖不到 90% / 完全漏检”三档分类每档挑 3 张回看。如果 90% 覆盖但偏移几个像素的案例大量出现就用最终模型对全部 112 张做一次伪标注人工修正后微调 20 轮。这是小数据集中常见的“标注精修闭环”能再挤出几个点的 mAP——严格说它不是训练技巧而是把标注做成可迭代资产的数据工程。希望这一套流程能帮你把 112 张蛇图真正用起来而不是标完就躺在硬盘里。本文还有配套的精品资源点击获取
返回列表