ARTICLE DETAIL

资讯详情

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

鹿数据集VOC+YOLO双格式实战:504张图664个框,从标注校验到YOLO训练全流程

鹿数据集VOC+YOLO双格式实战:504张图664个框,从标注校验到YOLO训练全流程 简介本资源为鹿类目标检测数据集面向从事计算机视觉、深度学习目标检测训练与验证的学生、算法工程师及科研人员可解决鹿只识别场景下样本获取与标注成本高的问题。压缩包共1514个文件包含504张jpg原始图像、504个VOC格式xml标注文件、504个yolo格式txt标注文件及少量说明文本整体约177.8MBxml与txt分别适配Pascal VOC和YOLO两种主流训练框架无需额外转换即可直接投入训练。标注采用labelImg完成仅设Deer一个类别共标注664个矩形框图像命名规范、类别统一适合作为单类别检测的基线数据或迁移学习素材。目前已有126人学习下载读者可据此快速搭建鹿只检测实验验证模型在真实场景下的识别效果并对比VOC与YOLO两种标注格式的读取与训练流程。1. 鹿数据集 VOCYOLO 双格式504 张图、664 个框这份资源到底能不能直接开训手上有一批野生动物红外相机照片想跑一个鹿类目标检测最卡脖子的往往不是模型结构而是标注。自己拉框、写 XML、再转 YOLO 的 txt一套流程走下来几百张图能耗掉两三天。这份「鹿数据集 VOC 格式 yolo 格式 504 张 1 类别」就是冲着这个痛点来的504 张 jpg 图片配套 504 个 Pascal VOC 的 xml 标注文件和 504 个 YOLO 的 txt 标注文件单类别Deer总框数 664用 labelImg 画的矩形框。它解决的是「从零标注」到「直接进训练脚本」之间那段最枯燥的体力活适合做野生动物检测、边缘端小模型验证、或者单纯想拿一份干净单类数据集练手 YOLO 全流程的人。下面我按「这份资源是什么 → 怎么接进训练 → 哪里会翻车」的顺序拆一遍能抄的步骤我都落成代码。2. 先看清目录结构和标注口径VOC 与 YOLO 到底差在哪拿到压缩包别急着解压进训练目录先花五分钟把结构摸清楚。这份资源的摘要写得很明确只有 jpg 图片、VOC 的 xml、YOLO 的 txt不包含分割路径的 txt 文件也就是没有 train/val 划分清单。这意味着划分训练集验证集这一步得你自己做别指望开箱就有train.txt。2.1 两种格式的坐标口径差异VOC 的 xml 里bndbox存的是绝对像素坐标xmin, ymin, xmax, ymax原点在左上角。YOLO 的 txt 每行是class_id x_center y_center width height全部归一化到 0~1且是相对整张图宽高。这两套口径如果混用最常见的事故就是框整体偏移或者缩成一团。判断一份数据集是否「双格式自洽」可以抽几张图把 xml 和 txt 都读出来反算回像素坐标对比。import xml.etree.ElementTree as ET from PIL import Image def voc_to_pixel(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) boxes [] for obj in root.iter(object): name obj.find(name).text bb obj.find(bndbox) xmin float(bb.find(xmin).text) ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text) ymax float(bb.find(ymax).text) boxes.append((name, xmin, ymin, xmax, ymax)) return w, h, boxes def yolo_to_pixel(txt_path, w, h): boxes [] with open(txt_path) as f: for line in f: cid, xc, yc, bw, bh line.split() xc, yc, bw, bh map(float, (xc, yc, bw, bh)) # 反归一化回像素 xmin (xc - bw / 2) * w ymin (yc - bh / 2) * h xmax (xc bw / 2) * w ymax (yc bh / 2) * h boxes.append((int(cid), xmin, ymin, xmax, ymax)) return boxes w, h, voc_boxes voc_to_pixel(firc_Deer_11.xml) yolo_boxes yolo_to_pixel(firc_Deer_11.txt, w, h) print(VOC:, voc_boxes) print(YOLO:, yolo_boxes)这段代码的逻辑是VOC 侧直接读绝对坐标YOLO 侧把归一化值乘回宽高。跑完对比两组数如果 xmin/ymin/xmax/ymax 对得上允许 1~2 像素取整误差说明这份资源的双格式是自洽的可以放心二选一使用。参数上注意class_id从 0 开始单类别就是 0对应Deer。2.2 类别映射与文件名对应关系单类别的好处是不用纠结类别顺序但坏处是很多人会忽略「xml 里的 name 和 txt 里的 class_id 必须能对上」。这份资源类别名是Deertxt 里应该全是 0。建议写个脚本全量扫一遍确认没有空标注文件、没有越界框、没有 name 拼写不一致。import os, glob img_dir images xml_dir annotations_xml txt_dir labels_yolo imgs {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f{img_dir}/*.jpg)} xmls {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f{xml_dir}/*.xml)} txts {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f{txt_dir}/*.txt)} print(图片数:, len(imgs), xml数:, len(xmls), txt数:, len(txts)) print(缺xml:, imgs - xmls) print(缺txt:, imgs - txts) print(多余xml:, xmls - imgs)逻辑说明用文件名主干做集合运算快速定位「有图没标注」或「有标注没图」的脏数据。参数上把三个目录换成你解压后的实际路径即可。这一步是血泪经验——很多数据集看着数量对实际文件名大小写、后缀不一致训练时直接报找不到标签。2.3 664 个框意味着什么504 张图 664 个框平均每张 1.3 个框说明大量图片是单只鹿或者空背景。这对训练有两个影响一是正样本偏稀疏如果直接按图随机划分验证集里可能出现整批无目标图mAP 波动大二是要警惕「背景图」被误当成负样本处理。常见做法是划分时按框数分层抽样保证 train/val 的正样本比例接近。下面给一个可复现的划分脚本。import random, os, glob random.seed(42) names sorted(os.path.splitext(os.path.basename(p))[0] for p in glob.glob(labels_yolo/*.txt)) random.shuffle(names) split int(len(names) * 0.8) train, val names[:split], names[split:] os.makedirs(splits, exist_okTrue) for tag, subset in [(train, train), (val, val)]: with open(fsplits/{tag}.txt, w) as f: for n in subset: f.write(fimages/{n}.jpg\n) print(train:, len(train), val:, len(val))逻辑说明固定随机种子保证可复现8:2 划分输出 YOLO 训练常用的图片路径清单。参数seed换成别的值会得到不同划分做对比实验时建议固定。注意这里写的是图片绝对/相对路径YOLO 训练时标签路径默认由图片路径替换images为labels推导所以目录命名要规范。3. 接进 YOLO 训练从 data.yaml 到第一次跑通格式确认没问题后真正落地就是把它喂给 YOLO。不管你是 YOLOv5、v8 还是更新的版本数据组织逻辑基本一致一个data.yaml描述路径和类别图片和标签分目录放。这一章把配置、命令、参数含义讲透新手照着能跑熟手能直接改。3.1 目录组织与 data.yaml 写法推荐的结构是图片和标签平级分目录标签目录名用labels图片用images这样 YOLO 能自动推导。把解压出来的 jpg 放进imagestxt 放进labelsxml 单独存一份备用VOC 训练或转其他格式时还要用。# data.yaml path: /data/deer_dataset train: splits/train.txt val: splits/val.txt nc: 1 names: 0: Deer逻辑说明path是数据集根目录train/val指向刚才生成的清单文件nc是类别数names是索引到类名的映射。参数上最容易错的是names的写法——有的版本要求列表[Deer]有的要求字典{0: Deer}以你用的 YOLO 版本文档为准。单类别时nc: 1索引只能是 0和 txt 里的 class_id 对齐。3.2 训练命令与关键参数以常见的 YOLO 训练入口为例一条命令能跑起来但参数决定成败。yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ projectruns/deer \ nameexp1 \ seed42逻辑说明model选 nano 版本是因为 504 张图属于小数据量大模型容易过拟合先用小模型验证流程通不通。imgsz640是通用起点如果你的原图分辨率远大于 640小目标会被缩没需要调大或做切图。batch16在显存不够时降到 8 或 4。workers是数据加载线程Windows 下建议设 0 避免多进程报错。seed固定保证划分和增强可复现。第一次跑建议epochs先设 10确认 loss 正常下降、验证能出结果再拉长。3.3 训练前用脚本做一次标注可视化在正式训练前把标注画到图上肉眼过一遍能提前发现框错位、漏标、类别错。这一步比看 loss 曲线更直接。import cv2, glob, os for txt in glob.glob(labels_yolo/*.txt)[:20]: # 先抽查20张 name os.path.splitext(os.path.basename(txt))[0] img cv2.imread(fimages/{name}.jpg) h, w img.shape[:2] with open(txt) as f: for line in f: 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.imwrite(fvis_{name}.jpg, img)逻辑说明把归一化坐标还原成像素画框抽查 20 张足够看出整体质量。参数上[:20]控制抽查数量正式检查可以全量跑但会慢。如果发现框普遍偏小或偏移多半是归一化时用错了宽高比如用了缩放后的尺寸回到 2.1 的对比脚本排查。提示可视化输出目录别和训练数据混在一起否则 YOLO 扫描图片时可能把带框的图也当训练样本。4. 避坑与排查这份数据集最容易翻车的五个地方数据集本身不复杂但「简单」往往让人跳过检查结果在训练中途才暴露问题。下面五条是我在实际项目里反复遇到的按现象、原因、解决写清楚。4.1 训练报「找不到标签」现象启动训练后立刻报No labels found或Label class x is not in names。原因通常是图片和标签目录没对齐YOLO 默认把图片路径里的images替换成labels去找 txt如果你把 txt 放在别的目录名它就找不到。解决严格用images/和labels/命名或者用清单文件显式指定标签路径。另一个原因是 txt 里 class_id 不是 0而names只定义了 0直接越界。4.2 验证集 mAP 忽高忽低现象同一份数据换个随机种子 mAP 差十几个点。原因504 张图里正样本稀疏随机划分可能让验证集集中了难样本或空背景。解决按框数分层抽样或者用固定种子多跑几次取平均。更稳的做法是做 5 折交叉验证虽然费时间但结论可靠。4.3 框整体偏移或缩成一点现象可视化时框位置明显不对或者所有框挤在左上角。原因VOC 转 YOLO 时归一化用错了基准尺寸比如用了统一 resize 后的 640而实际图片是原始分辨率。解决归一化必须用每张图自己的原始宽高回到 2.1 的对比脚本逐张核对。这类问题在双格式数据集里尤其要警惕因为两套文件可能不是同一次导出生成的。4.4 小目标被 imgsz 缩没现象训练 loss 正常降但推理时小只鹿检测不到。原因原图分辨率高鹿在画面里占比小统一缩到 640 后目标只剩几个像素。解决调大imgsz到 960 或 1280或者用切图推理SAHI 之类。代价是显存和速度边缘端部署要权衡。4.5 过拟合训练集 mAP 很高验证集上不去现象训练几十轮后训练指标接近 1.0验证集停滞。原因504 张图对检测模型偏少加上单类别模型容易记住背景。解决开强增强mosaic、mixup、随机缩放裁剪加早停或者用预训练权重冻结主干先训头部。别一上来就上大模型nano 或 small 足够。5. 进阶把 VOC 和 YOLO 双格式用出额外价值这份资源同时给了 xml 和 txt很多人只用一种另一种就浪费了。其实双格式可以互相校验还能支撑一些单格式做不到的玩法。下面讲两个我常用的技巧一个用于数据质量验证一个用于格式转换兜底。5.1 用双格式交叉校验标注一致性既然同一张图有两份标注就可以写脚本逐张比对把不一致的挑出来人工复核。这比单看一种格式可靠得多因为转换脚本的 bug 往往只在部分图上暴露。import glob, os, xml.etree.ElementTree as ET def load_voc(xml_path): root ET.parse(xml_path).getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) boxes [] for obj in root.iter(object): bb obj.find(bndbox) boxes.append(tuple(float(bb.find(k).text) for k in (xmin,ymin,xmax,ymax))) return w, h, boxes def load_yolo(txt_path, w, h): boxes [] with open(txt_path) as f: for line in f: _, xc, yc, bw, bh line.split() xc, yc, bw, bh map(float, (xc, yc, bw, bh)) boxes.append(((xc-bw/2)*w, (yc-bh/2)*h, (xcbw/2)*w, (ycbh/2)*h)) return boxes bad [] for xml in glob.glob(annotations_xml/*.xml): name os.path.splitext(os.path.basename(xml))[0] txt flabels_yolo/{name}.txt if not os.path.exists(txt): bad.append((name, missing txt)); continue w, h, vb load_voc(xml) yb load_yolo(txt, w, h) if len(vb) ! len(yb): bad.append((name, fcount {len(vb)} vs {len(yb)})); continue for a, b in zip(sorted(vb), sorted(yb)): if max(abs(a[i]-b[i]) for i in range(4)) 2: # 允许2像素误差 bad.append((name, coord mismatch)); break print(异常样本:, bad)逻辑说明逐张读两种格式先比框数量再比坐标允许 2 像素取整误差。参数 2是容差可以按需收紧。输出bad列表就是需要人工复核的图。这个脚本在数据交付验收时特别有用能挡住大部分「看着没问题、训起来出事」的隐患。5.2 格式互转的兜底脚本有时候你拿到的只有一种格式或者训练框架只吃某一种互转就是刚需。VOC 转 YOLO 的核心是归一化YOLO 转 VOC 的核心是反归一化并补全 xml 头信息。import xml.etree.ElementTree as ET from PIL import Image def voc2yolo(xml_path, out_txt, class_map): root ET.parse(xml_path).getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text bb obj.find(bndbox) xmin float(bb.find(xmin).text); ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text); ymax float(bb.find(ymax).text) xc (xmin xmax) / 2 / w; yc (ymin ymax) / 2 / h bw (xmax - xmin) / w; bh (ymax - ymin) / h lines.append(f{class_map[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) voc2yolo(firc_Deer_11.xml, firc_Deer_11_converted.txt, {Deer: 0})逻辑说明class_map把类别名映射到索引单类别就是{Deer: 0}。归一化保留 6 位小数是 YOLO 常见精度够用且不冗长。反方向转换时记得补folder、filename、size这些 VOC 必需节点否则 labelImg 打不开。注意转换脚本一定要在完整数据集上跑一遍再用 5.1 的校验脚本回验别只测一张就批量执行。从那以后我每次拿到新数据集不管它标称多干净都强制走一遍「数量核对 → 双格式交叉校验 → 可视化抽查 → 小轮次试训」这四步宁可前期花半小时也不愿训练到一半才发现标注是错的。这份鹿数据集结构清晰、双格式齐全按上面的流程接进 YOLO 基本能一次跑通剩下的就是调参和增强的事了。希望帮到你。本文还有配套的精品资源点击获取
返回列表