ARTICLE DETAIL

资讯详情

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

蛇类目标检测数据集:112张VOC/YOLO双格式标注图与YOLOv8训练实战

蛇类目标检测数据集:112张VOC/YOLO双格式标注图与YOLOv8训练实战 简介蛇类目标检测数据集同时提供VOC与YOLO两种标注格式适用于Fast R-CNN、SSD、YOLO等常见目标检测模型的训练与效果验证。包内共337个文件含112张jpg图片、112个xml标注文件、113个txt文件压缩包18.71MBjpg图像大小约1-500KBxml与txt分别对应VOC和YOLO格式可直接供主流检测框架读取使用。数据集由labelImg工具标注完成目标类别统一为snake并遵循精确框选边界、完整标注全部目标、一致性检查等标注规范有助于减少二次清洗成本。压缩包为rar格式无需解压密码内含图片、VOC标注、YOLO标注三个独立文件夹结构清晰便于查阅。目前已有74人浏览学习适合计算机视觉初学者或需要蛇类样本的算法工程师快速获取可用的标注数据用于模型调参、精度对比或数据扩充。1. 蛇数据集 VOC 和 YOLO 双格式112 张标注图能把检测模型推到哪一步做蛇类目标检测的人最头疼的不是模型是数据。公开数据集里蛇的标注图要么混在几十个类里要么标注框框得随心所欲想拿来做单类检测总得自己重新清洗一遍。这份数据集把最费时的活做完了112 张左右蛇类 jpg 原图VOC 格式的 xml 和 YOLO 格式的 txt 各一套类别统一为 snake用 labelImg 标注压缩包解压后三个文件夹直接躺好。它能解决的是目标检测的第一公里——不用爬图、不用清洗、不用从零标注整理目录、配好 data.yaml 就能喂给 YOLO 训练。适合两类人拿 YOLOv8/YOLOv5 做课程设计或毕设、需要一份干净数据快速跑通流程的学生以及想验证蛇类识别方案可行性的工程师先用这 112 张把评估脚本和训练链路跑起来再决定要不要自己扩数据。2. 先看懂两张标注格式VOC 的 xml 与 YOLO 的 txt 到底差在哪2.1 VOC 的 xml 里存了什么从 filename 到 bndbox 逐字段拆解用 labelImg 打开一张 snake_81.jpg框出那条蛇按 CtrlS 存盘得到的就是一个 PascalVOC 格式的 xml。这套格式最早是 PASCAL VOC 挑战赛定的后来几乎所有检测框架都兼容它。xml 是纯文本文本编辑器就能打开结构是树状的我拆给你看。根节点 annotation 下面挂着几类信息folder 写图片所在文件夹filename 写图片文件名path 写保存时刻的绝对路径这三个字段和模型训练基本无关最要紧的是 size 和 object。size 里有 width、height、depth 三个子节点宽度、高度是像素值depth 是通道数jpg 基本都是 3。object 节点才是标注本体每个 object 表示图里的一个目标这张数据集里一张图可能只有一条蛇但复杂场景下会有多个 object 并列。object 下面最常用的字段是 name 和 bndbox偶尔有 truncated 和 difficult。name 就是类别名这份数据集统一写 snake。bndbox 是矩形框坐标包含 xmin、ymin、xmax、ymax 四个子节点表示框的左上角和右下角。注意这套坐标是像素绝对坐标原点在图片左上角x 向右增大y 向下增大单位是像素不是比例。比如 xmin214 就是框左边界离图片左缘 214 个像素。数据集说明里提到的那三条标注规范——边界准确、目标标全、一致性检查——对应到 xml 上就是 bndbox 大多贴着蛇身轮廓而不是把蛇所在的整块草地都圈进去这对训练是好事。2.2 切换到 YOLO 后坐标为什么变了归一化与中心点换算YOLO 格式的标注不是 xml而是每个图片一个同名的 txt 文件每行代表一个目标一共五个数类别编号、目标中心 x、目标中心 y、目标宽、目标高。后四个都是 0 到 1 的归一化小数要除以图片的宽和高。比如把一份 xml 转成 txt核心换算长这样import xml.etree.ElementTree as ET tree ET.parse(labels_voc/snake_81.xml) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) obj root.find(object) 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) x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height print(fsnake 0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f})这段代码做的事情就是把 xml 里的像素框转成 YOLO 格式。逻辑分三步先从 size 节点读宽高做分母再从 bndbox 读四个角点最后用中心点加宽高的方式重写坐标。中心点公式是 (xminxmax)/2 再除以图片宽宽度是 (xmax-xmin) 除以图片宽高度同理。为什么要除以宽高因为训练时 YOLO 会把图片缩放到固定尺寸比如 640×640像素坐标在缩放后就变了归一化坐标不会变模型只看相对比例这也是同一条蛇从手机拍的和从监控截图的能共用一套标注的原因。输出里的snake 0 0.512345 0.345678 0.234567 0.156789第一个是类别名给人看的说明后面五个数才是真正写进 txt 的内容。注意类别编号从 0 开始不是从 1 开始这是 YOLO 的老规矩新手最容易在这里栽跟头。对比项VOCxmlYOLOtxt文件格式xml 树状结构纯文本每行一个目标坐标基准像素绝对坐标0~1 归一化比例框表示左上角 xmin/ymin 右下角 xmax/ymax中心点 x/y 宽高类别表示name 字符串从 0 开始的整数编号多个目标多个 object 节点多行文本2.3 labelImg 双格式导出的设置要点这份数据集的两个标注格式来源就是 labelImg 的两种保存模式。labelImg 是目标检测圈最常见的开源标注工具界面左边竖着一排按钮画框用 W调框用鼠标拖动底部有 PascalVOC 和 YOLO 的切换按钮点 YOLO 模式后会弹窗让你选一个 classes.txt 文件这个文件决定了 YOLO 模式下的类别列表和编号顺序。一个常见做法是新建一个 classes.txt里面只写一行 snake保存到项目根目录。然后切到 YOLO 模式继续标注之后 CtrlS 存出来的就是 txt。反过来切回 PascalVOC 模式存出的又是 xml。这解释了为什么这份数据集的 xml 和 txt 内容完全对应——它们是同一批图片用 labelImg 分别以两种模式各存了一遍或者由其中一种批量转换得出。拿到数据集后我自己也会做一次一致性抽检具体方法在第 6 章。3. 把数据集跑起来目录整理、配对校验与 YOLOv8 首轮训练3.1 解压后的目录结构三个文件夹该怎么组织压缩包里是三个文件夹一个装 jpg 原图一个装 VOC 的 xml一个装 YOLO 的 txt文件名一一对应。这个状态适合「用 labelImg 打开看图」和「人工核对」但不适合直接喂给训练框架。训练框架期望的典型结构是 images 和 labels 两个平级目录jpg 和 txt 分开放文件名相同。我一般会把它整理成这样snake_dataset/ ├── images/ # 112张左右jpg原图 ├── labels/ # YOLO格式txt与images同名 └── labels_voc/ # VOC格式xml保留作转换源和备份这样分层有三个好处images 和 labels 是训练框架的标准输入不需要改框架配置labels_voc 单独留一份是因为 xml 可读性好后续要改类别名或重新统计标注情况直接在 xml 上改再批量转回 txt三个目录平级增删图片时只需要同名增删文件不会把原始标注搞乱。如果你后面要自己扩标注labelImg 打开 images 目录直接画就行。3.2 用脚本快速校验图片与标注文件配对情况整理完目录千万别急着训练先跑配对校验。小数据集最常见的坑就是解压丢包、重命名后扩展名不一致、哪张图漏了标注文件——这类问题训练时会静默跳过或报错排查成本远高于提前校验。我一般会保存一个 check_dataset.py 在项目里每个数据集都用它过一遍。from pathlib import Path img_dir Path(images) txt_dir Path(labels) img_files {p.stem: p for p in img_dir.glob(*.jpg)} txt_files {p.stem: p for p in txt_dir.glob(*.txt)} for stem in txt_files: if stem not in img_files: print(f[txt没有对应jpg] {stem}) for stem in img_files: if stem not in txt_files: print(f[jpg没有对应txt] {stem}) txt txt_dir / f{stem}.txt if txt.stat().st_size 0: print(f[空标注] {stem} 需要回labelImg补标)逻辑只有三步用 Path.glob 收集两个目录里所有同扩展名文件以去掉扩展名的 stem 当主键对比两边的键是否一一对应顺带检查 txt 文件大小0 字节说明这张图没有任何标注。跑完输出为空说明配对没问题。112 张全量扫一遍耗时不到一秒我会把它挂在训练命令之前当成强制前置步骤。当初我偷懒跳过这步结果训练到一半才发现有 13 张图漏了标注白跑两轮血泪经验。3.3 在 YOLOv8 上完成第一次训练参数表与常见误区配对没问题就可以训练了这里以 YOLOv8 为例。先建 data.yamlpath: snake_dataset train: images val: images names: 0: snaketrain 和 val 暂时都指向 images是因为 112 张的规模再分 train/val 会让每份都太薄常见做法是先全量训练看 loss 曲线是否正常后面扩到几百张再正经划分。如果非得分按 9:1 切val 留 12 张左右足够观察趋势不要在 112 张里再抠 20 张当 test。然后跑训练yolo detect train modelyolov8n.pt datasnake.yaml epochs100 imgsz640 batch4 patience20几个参数说清楚。model 用 yolov8n.pt 预训练权重起步不要从 yolov8m 或 yolov8x 开始112 张数据根本不支持大模型n 系列参数量最小但在这种单类小目标场景完全够用而且收敛快、方便试错。imgsz 默认 640如果你的原图里蛇占面积大、边框很满可以降到 512 甚至 416减小缩放带来的形变。batch 看显存8G 显存跑 batch4 比较稳。epochs 给 100 够了配合 patience20 做早停实际训练到 final 之前就会停。第一次跑建议开着 runs/detect 目录盯 val 曲线正常走势是 mAP50 在前 20 个 epoch 快速上涨后变缓class loss 平稳下降如果 train loss 和 val loss 差距越拉越大就是过拟合信号第 5 章展开。参数取值说明modelyolov8n.pt小模型预训练权重112张首选imgsz640原图画幅大建议降到512batch48G显存稳妥值显存大可加epochs100配合早停不需要手动改patience2020轮val无提升即停4. 格式转换与二次加工写一个 xml→txt 转换脚本4.1 为什么要自己写转换脚本而不是手动另存有读者会问数据集的 xml 和 txt 不是都给了吗为什么还要自己转换两个原因。一是增量你后面自己加了 20 张图用 labelImg 默认存成 xml这时候手头就有一套 VOC 一套 YOLO 混着训练前必须把新增的 xml 转成 txt。二是改标注你想把类别从 snake 拆成 snake_class1、snake_class2或者把越界的框修正在 xml 文本上操作比在 txt 上操作安全得多改完再批量转。labelImg 虽然也能直接从 xml 切到 YOLO 再逐张另存但上百张图点几百次鼠标纯属浪费时间写个 40 行的脚本一次跑完才是工程做法。4.2 xml_to_yolo.py全量代码与逐段逻辑完整脚本如下保存成 xml_to_yolo.py 放到项目根目录运行。import os import glob import xml.etree.ElementTree as ET # 类别列表顺序必须和训练时 data.yaml 的 names 一致 CLASSES [snake] def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: print(f[跳过未知类别 {name}] {xml_path}) continue cls_id CLASSES.index(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) x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height w (xmax - xmin) / width h (ymax - ymin) / height # 越界防御坐标必须落在 [0,1] x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) img_name os.path.basename(xml_path).replace(.xml, .txt) out_path os.path.join(out_dir, img_name) with open(out_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: xml_dir labels_voc out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in glob.glob(os.path.join(xml_dir, *.xml)): xml_to_yolo(xml_file, out_dir) print(f[已转换] {xml_file})几个关键点拆开说。CLASSES 列表决定类别编号它的顺序必须和训练配置里 names 的顺序严格一致YOLO 训练不认类别名只看 txt 第一列的数字和 data.yaml 里 names 的索引两者错一位整个模型就废了。:.6f保留六位小数是 YOLO 训练的常见约定精度再高没意义再低会影响小目标的小框精度。clamp 那一行不是可有可无labelImg 里画框拖出图片边界的情况时有发生xml 里就会出现负数或超过宽高的坐标不夹取的话训练时会产生 NaN 或越界框mAP 直接崩。跑法很简单python xml_to_yolo.py输出会一路打印已转换的文件名。转换完以后labels 目录里应该出现和 images 目录一一对应的 txt 文件。注意脚本里对未知类别是 print 后跳过如果跳过的数量很多说明 xml 里有你没见过的类别名先打印出来核对一遍再决定是加入 CLASSES 还是删掉那些框。数据集的 xml 和 txt 是同源标注脚本跑出来的结果应该和压缩包里自带的 txt 完全一致这份数据本身可以作为脚本正确性的对照基准。4.3 转换后验证把归一化坐标还原回像素框转换不是跑完脚本就结束了还要验证输出。验证思路是反着来把生成的 txt 里五个数还原成像素坐标和 xml 里的 bndbox 比对误差应该为 0。单独写一段反向验证# 还原yolo坐标回像素坐标用于和xml的bndbox核对 w 640 # 改成该图实际宽度 h 480 # 改成该图实际高度 x_center, y_center, bw, bh 0.512345, 0.345678, 0.234567, 0.156789 xmin int(round((x_center - bw / 2) * w)) ymin int(round((y_center - bh / 2) * h)) xmax int(round((x_center bw / 2) * w)) ymax int(round((y_center bh / 2) * h)) print(xmin, ymin, xmax, ymax)这个脚本看着短但它是整个转换链路的照妖镜。xmin 由中心减半宽再乘宽度得到因为归一化时是除法还原时就是乘法完全可逆。如果还原出的 xmin 和 xml 里的 xmin 对不上要么是源 xml 被改过要么是脚本里 width/height 读错。配合第 6 章的可视化脚本一起用一张图就能看出所有问题。另外要注意xml 里如果没有 object 节点lines 就是空列表写出的 txt 是空文件训练时会跳过这张图配对校验脚本会把这个情况单独标出来不要忽略它。5. 避坑记录112 张数据集训练最容易翻车的五个实测点112 张的规模模型架构基本不会出问题真正的坑全在数据侧。以下五条是我拿小数据集训练时反复踩过的每一条按现象、原因、解决拆开写后面照方抓药。5.1 图片读取失败或路径乱码现象训练一开始就报 FileNotFoundError或者跑着跑着提示图片打开失败日志里的 image path 是一长串乱码。原因压缩包解压路径或项目路径里有中文和空格。labelImg 在 Windows 下生成的 xml 可能是 GBK 编码而 YOLO 框架默认用 UTF-8 读文件路径编码对不上就炸。解决项目根路径改成纯英文目录比如 D:\snake_work\不要出现「蛇数据集」这种中文文件夹名图片文件名保持 snake_81.jpg 这种原始命名别手动重命名成「蛇_81.jpg」。路径规范是第一坑新数据集拿到手先统一文件名和路径再动训练能省掉后面一大半莫名其妙的报错。5.2 类别编号错位mAP 一直为零现象训练正常跑完val 曲线里 mAP50 全程为 0box loss 也在降但怎么等都不涨看起来像模型没学到东西。原因txt 第一列的编号和 data.yaml 里 names 的索引对不上。比如 labelImg 的 classes.txt 里写了 snake但你在 data.yaml 里写成了1: snake或者你改了类别列表让编号从 1 开始但转换脚本固定输出 0模型按 0 去查 names 查不到对应类别评估自然全 0。解决先拿一张图的 txt 和 xml 手工对一遍txt 第一列应该是 0单类数据集必然是 0。然后检查 data.yaml单类的 names 必须从 0 开始编号。这条我检查过好几个人的项目十次有八次死在这。5.3 标注框越界导致 loss 爆掉或 BN 崩溃现象训练到第几个 epoch 时 train loss 突然跳到巨大值之后一路 NaN日志里 mAP 直接消失有说法是 BN 崩了。原因xml 里的 bndbox 坐标有负数或超出图片宽高例如 xmax 标成了 741但图片宽度只有 640。YOLO 对越界框的容忍度很低归一化后出现大于 1 的坐标反向传播时梯度异常表现为 BN 统计量乱掉最终全链路崩掉。BN 崩溃只是现象根因多半在标注框。解决转换脚本里加上 clamp 夹取第 4 章的脚本已经内置训练前再跑一次配对校验脚本把 txt 里所有坐标扫一遍任何一个数大于 1 或小于 0 就先回 labelImg 修框。从那以后我拿到任何数据集都会先扫一遍 txt 里的异常数值这个习惯救了我好几次。5.4 数据集太小val 分数虚高看着像过拟合现象训练到 40 个 epochtrain loss 还在降val mAP 已经到 0.9x 甚至 1.0但换一张没见过的蛇图实测就漏检。原因112 张的 val 集可能只有 10 张左右样本太少模型把训练图背下来了遇到新背景立刻露馅val 分数是在「刷熟脸」而不是「考能力」。解决val 分数只能当参考关键是拿没参与训练的图做盲测。常见做法是从总数据里抽 3~5 张「打死不进训练」的图训练完单独跑 predict 用肉眼看检测框。数据增强参数调大一点也有帮助第 6 章给具体方案。5.5 xml 缺一张、txt 多一张模型静默跳过样本现象训练日志里显示的 images 数量比实际少了或者某张图的 loss 对不上原图排查半天发现是标注文件没配对。原因解压时丢包、编辑时误删 xml、增量标注时只导出了部分文件。YOLO 训练时如果一张图没有对应 txt会静默跳过这张图不报错所以数量不对很难被发现。解决把 3.2 的校验脚本设为训练前置步骤train 之前强制跑一次输出为空才允许继续。小数据集手动检查也行但脚本可以复用成本为零靠人眼逐个数是靠不住的。6. 112 张用出 500 张的效果数据增强与标注质量终检6.1 针对蛇的增强方案112 张跑完首轮常见瓶颈是背景单一、光照分布窄。用 albumentations 做离线增强是最省事的方案先存增强后的图和对应的标注。import albumentations as A transform A.Compose([ A.RandomRotate90(p0.5), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.6), A.RandomScale(scale_limit0.2, p0.4), ], bbox_paramsA.BboxParams(formatyolo, label_fields[labels]))参数说明蛇体细长RandomRotate90 比任意角度旋转更稳避免把蛇旋成「拧麻花」的畸形样本RandomScale 的 0.2 控制框缩放幅度兼顾模拟不同拍摄距离BboxParams 的 formatyolo 表示接受的就是 txt 里那种归一化坐标增强后 bbox 会跟着图一起变换不需要自己重算。扩完一轮112 张变 300 多张训练时再用 YOLOv8 自带的 mosaic 增强效果比裸跑明显好。6.2 标注质量终检把框画回图上看最后一道工序永远是可视化终检。脚本思路是把每张图的标注框画回原图随机抽 30 张左右肉眼过一遍import cv2 img cv2.imread(images/snake_81.jpg) h, w img.shape[:2] # 从txt读第一条标注 with open(labels/snake_81.txt) as f: line f.readline().strip().split() cls_id, x_c, y_c, bw, bh int(line[0]), float(line[1]), float(line[2]), float(line[3]), float(line[4]) xmin int((x_c - bw / 2) * w) ymin int((y_c - bh / 2) * h) xmax int((x_c bw / 2) * w) ymax int((y_c bh / 2) * h) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.imwrite(check_snake_81.jpg, img)抽检时重点看三类框是否贴住蛇身轮廓、有没有框到旁边的树根或石块、蛇身细长弯折时框是否把中间空档也包了。这类问题不会报错但直接影响模型学到的特征。从那以后我每次拿到别人标注的数据集都强制先跑一遍可视化终检再进训练这张蛇数据集我也按这个流程过了至少省下两轮「训练完才发现标注有问题」的白跑调试。希望帮到你。本文还有配套的精品资源点击获取
返回列表