ARTICLE DETAIL

资讯详情

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

630张鸭子数据集VOC与YOLO格式转换及YOLO训练实战

630张鸭子数据集VOC与YOLO格式转换及YOLO训练实战 简介这是一份面向目标检测初学者与算法工程师的鸭子识别数据集采用Pascal VOC与YOLO双格式标注可直接用于训练和验证单类别检测模型省去自行采集与标注的成本。压缩包共1892个文件包含630张jpg原图、630个VOC格式xml标注文件以及630个yolo格式txt标签文件另附少量说明文本整体约199.6MB图片与标注一一对应方便按需转换或直接投入训练。标注由labelImg完成类别仅Duck一类共1149个矩形框平均每图约1.8个目标覆盖多种姿态与场景适合做小样本单类检测、迁移学习或数据增强实验。目前已有217人学习下载可作为课程设计、毕业项目或算法对比的现成数据基础帮助读者快速搭建训练流程、验证模型效果并复现检测结果。1. 鸭子数据集 VOC 格式 YOLO 格式 630 张 1 类别小样本检测到底能不能打拿到一个 630 张、单类别、同时给了 VOC 和 YOLO 两套标注格式的鸭子数据集第一反应往往不是兴奋而是犯嘀咕这点量够不够训一个能用的检测模型我一开始也这么想直到在几个实际的小场景里反复验证过——630 张单类别只要标注干净、划分合理、增强到位训出一个能在固定机位下稳定框住鸭子的模型完全可行。它解决的是「我不想从零标几千张只想快速验证一个单目标检测需求」的问题适合做农业养殖计数、水域巡检、校园或公园场景的固定摄像头分析也适合刚入门 YOLO、想拿一个真实小数据集把训练到推理整条链路跑通的人。这篇就把 VOC 和 YOLO 两套格式怎么选、怎么转、怎么训、坑在哪按我实际踩过的顺序讲清楚。2. VOC 与 YOLO 标注格式先搞懂差异再动手2.1 两套格式到底差在哪VOC 格式的核心是每张图对应一个 XML 文件里面用object节点描述每个目标坐标是绝对像素值记录的是xmin、ymin、xmax、ymax也就是左上角和右下角。它天然支持一张图多个类别多个目标可读性好用文本编辑器打开就能看懂所以很多标注工具默认导出 VOC。YOLO 格式则是每张图对应一个.txt文件每行一个目标格式是类别索引 中心x 中心y 宽 高而且这四个值全部是相对整张图宽高的归一化值范围在 0 到 1 之间。类别不再是字符串名字而是一个从 0 开始的整数索引索引和类别名的对应关系单独写在一个classes.txt或data.yaml里。这个差异带来的直接后果是VOC 的坐标依赖图片实际尺寸换一批分辨率不同的图坐标数值就没法直接复用YOLO 的归一化坐标则和分辨率解耦同一张图缩放后标注依然成立。这也是为什么训练时大家更愿意用 YOLO 格式——数据增强里的缩放、裁剪、拼接处理归一化坐标要省心得多。单类别的情况下YOLO 每行的第一个数字永远是 0看起来有点多余但别手动去删格式必须完整否则解析会错位。2.2 630 张单类别先做一次数据体检在转换格式之前我一般会先做一次体检而不是直接开训。630 张听起来不多但如果里面混了重复图、坏图、漏标图实际有效样本可能只有五百出头那训练效果会明显打折。体检主要看四件事。第一是图片能不能正常打开有没有下载或解压过程中损坏的零字节文件。第二是标注文件数量和图片数量是否一一对应VOC 下就是 XML 数是否等于图片数缺一个都可能是漏标。第三是看标注框有没有越界xmin小于 0 或者xmax大于图片宽度这种框在转 YOLO 归一化后会变成负数或大于 1训练时容易报错或产生异常梯度。第四是看有没有空标注文件也就是图片在但 XML 里一个object都没有这种图要么补标要么剔除。下面这段脚本就是做体检用的跑一遍能快速定位问题文件import os import xml.etree.ElementTree as ET from PIL import Image img_dir JPEGImages xml_dir Annotations bad_imgs, bad_xmls, empty_xmls, oob_boxes [], [], [], [] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .png, .jpeg)): continue img_path os.path.join(img_dir, name) # 检查图片是否可读 try: w, h Image.open(img_path).size except Exception: bad_imgs.append(name) continue stem os.path.splitext(name)[0] xml_path os.path.join(xml_dir, stem .xml) if not os.path.exists(xml_path): bad_xmls.append(name) continue tree ET.parse(xml_path) objs tree.findall(object) if len(objs) 0: empty_xmls.append(name) continue for obj in objs: bnd obj.find(bndbox) xmin int(float(bnd.find(xmin).text)) ymin int(float(bnd.find(ymin).text)) xmax int(float(bnd.find(xmax).text)) ymax int(float(bnd.find(ymax).text)) # 越界判断 if xmin 0 or ymin 0 or xmax w or ymax h or xmax xmin or ymax ymin: oob_boxes.append((name, xmin, ymin, xmax, ymax)) print(坏图:, bad_imgs) print(缺XML:, bad_xmls) print(空标注:, empty_xmls) print(越界框:, oob_boxes)逻辑上就是遍历图片目录逐张尝试打开再去找同名 XML解析出每个目标框做边界检查。参数上img_dir和xml_dir换成你解压后的实际目录名即可VOC 标准结构里图片通常在JPEGImages标注在Annotations。跑完如果四类列表都是空的说明数据干净可以进入转换如果有内容先按列表逐个处理别带着脏数据往下走。提示越界框不一定是标注错误也可能是标注工具导出时四舍五入导致的 1 像素溢出这种情况把坐标裁剪回[0, w]和[0, h]即可不用整张重标。3. 把 VOC 转成 YOLO转换脚本与四个边界坑3.1 转换的核心公式VOC 转 YOLO 的数学部分其实就四行。设图片宽W、高H框的xmin、ymin、xmax、ymax那么中心 x (xmin xmax) / 2 / W中心 y (ymin ymax) / 2 / H宽 (xmax - xmin) / W高 (ymax - ymin) / H四个值都落在 0 到 1 之间。单类别时类别索引固定写 0。理解了这个公式转换脚本就是把它翻译成代码再套上目录遍历和文件写入。3.2 可直接复用的转换脚本import os import xml.etree.ElementTree as ET from PIL import Image img_dir JPEGImages xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) # 单类别类别名到索引的映射 classes [duck] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .png, .jpeg)): continue stem os.path.splitext(name)[0] xml_path os.path.join(xml_dir, stem .xml) if not os.path.exists(xml_path): continue w, h Image.open(os.path.join(img_dir, name)).size tree ET.parse(xml_path) lines [] for obj in tree.findall(object): cls_name obj.find(name).text.strip() if cls_name not in classes: continue # 只保留鸭子这一类 cls_id classes.index(cls_name) bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 裁剪到图像范围内防止归一化后越界 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 保留6位小数够用且文件不至于太大 lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines))逻辑说明脚本遍历图片找到同名 XML解析每个目标过滤出鸭子类别做坐标裁剪后套公式归一化最后每个图片写一个同名 txt。参数上classes列表就是类别定义单类别只放一个duck如果你后面要扩类在这里加名字、索引会自动顺延。:.6f是保留六位小数YOLO 官方实现读取时对精度不敏感六位足够写太多位反而让文件变大。3.3 四个容易翻车的边界坑第一个坑是坐标越界。前面体检如果没做转换时xmax超过图片宽度归一化后宽高可能大于 1训练时损失计算会出现异常值。脚本里已经用max/min裁剪兜底但最好在体检阶段就修掉。第二个坑是类别名大小写和空格。VOC 里name节点可能是duck、Duck、 duck 带空格或大小写不一致直接比对会漏掉目标导致某些图变成空标注。稳妥做法是先strip()再统一小写比对。第三个坑是图片格式混用。目录里同时有.jpg和.JPGendswith判断如果只写小写就会漏掉大写后缀的图转换后这些图没有对应 txt训练时被当成无标注图跳过。判断时统一转小写再比。第四个坑是文件名里的特殊字符。有些图名带空格或中文XML 同名但路径拼接时容易出错建议转换前把文件名统一成纯英文数字下划线省掉后面一连串编码问题。3.4 划分训练集和验证集630 张单类别我一般按 8:2 划分训练 504 张、验证 126 张。划分时要注意同一只鸭子、同一段连续视频抽帧的图不能同时出现在训练和验证里否则验证指标会虚高看着 mAP 很高实际换一批图就崩。如果是视频抽帧来的数据按时间顺序切分前 80% 做训练后 20% 做验证比随机打乱更接近真实泛化表现。划分脚本很简单生成两个 txt 列表文件每行一个图片路径import os, random img_dir JPEGImages names [n for n in os.listdir(img_dir) if n.lower().endswith((.jpg, .png, .jpeg))] random.seed(42) # 固定种子保证可复现 random.shuffle(names) split int(len(names) * 0.8) with open(train.txt, w) as f: f.write(\n.join(os.path.join(img_dir, n) for n in names[:split])) with open(val.txt, w) as f: f.write(\n.join(os.path.join(img_dir, n) for n in names[split:]))random.seed(42)是为了每次划分结果一致方便复现和对比实验。如果你是按视频切分就把shuffle去掉直接按顺序切片。4. 用 YOLO 训练 630 张单类别配置、参数与增强策略4.1 数据配置文件怎么写YOLO 训练前要准备一个数据配置文件通常叫data.yaml内容如下path: ./duck_dataset train: train.txt val: val.txt nc: 1 names: [duck]path是数据集根目录train和val是相对根目录的列表文件路径nc是类别数单类别写 1names是类别名列表顺序必须和转换时classes里的索引一致否则模型学到的类别会错位。这个文件是训练入口路径写错是最常见的启动失败原因报错通常是找不到图片或标签。4.2 训练命令与关键参数以常见的 YOLO 训练入口为例一条典型命令是这样yolo detect train \ dataduck_dataset/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ projectruns/duck \ nameexp1逐项说参数。model选 nano 版本630 张单类别用大模型容易过拟合nano 参数量小、收敛快是这类小数据集的稳妥起点。epochs150给足轮次配合patience30早停验证指标 30 轮不提升就停避免无效训练。imgsz640是通用输入尺寸如果你的鸭子在图里占比很小可以提到 960 或 1280但显存和速度要重新权衡。batch16是常见起点显存不够就降到 8 或 4。lr00.01是初始学习率小数据集不建议太大否则前期震荡明显。4.3 小数据集该开哪些增强630 张的规模增强是决定成败的关键。默认增强里我一般会重点调这几个参数作用小数据集建议mosaic四图拼接增加背景多样性保持开启但最后 10 轮关闭fliplr水平翻转0.5鸭子左右对称安全flipud垂直翻转0鸭子很少倒挂开了反而引入噪声hsv_h / hsv_s / hsv_v色调饱和度亮度扰动默认即可应对不同光照scale随机缩放0.5模拟远近变化degrees旋转角度0 到 10别太大否则框会失真mosaic 在最后若干轮关闭是个实用技巧因为拼接图里的目标偏小、边界被裁切训练末期关掉能让模型在真实完整图上收敛得更稳。这个开关在配置里通常通过close_mosaic参数控制设成 10 就表示最后 10 轮关闭。4.4 训练过程看什么指标启动后重点盯三个数box_loss、cls_loss和验证集的mAP50。单类别时cls_loss会降得很快因为它只需要区分「是鸭子」和「是背景」难度低。真正反映定位质量的是box_loss和mAP50-95。如果box_loss一直不降多半是标注框质量问题回去查体检结果。如果训练集指标很高但验证集mAP上不去就是过拟合减模型容量、加增强、或者补数据。注意单类别数据集里mAP50容易虚高因为负样本简单。别只看这一个数mAP50-95和实际推理效果才是判断能不能用的依据。5. 避坑与排查小样本鸭子检测的五个真实翻车点5.1 训练 loss 变 NaN现象训练刚开始几轮box_loss突然变成nan之后再也不恢复。原因最常见的是标注里有越界框或宽高为 0 的框归一化后出现除零或超大值其次是学习率设得过高前期梯度爆炸。解决回到第 2 章的体检脚本把所有越界框和零面积框清掉同时把lr0从 0.01 降到 0.001 再试。如果还不行检查图片里有没有尺寸为 0 的损坏文件。5.2 验证集 mAP 很高但实际推理框不住现象训练日志里mAP50到 0.9 以上但拿新图去推理鸭子要么漏检要么框歪。原因训练验证划分时同一段视频的相邻帧被分到了两边验证集和训练集高度相似指标虚高。解决按视频或时间顺序切分确保验证集是模型没见过的场景。如果数据来自多个拍摄地点按地点划分更可靠。5.3 模型只认大鸭子小鸭子全漏现象图里近处的大鸭子框得很准远处的小鸭子一个都检测不到。原因630 张里小目标样本占比低加上imgsz640下小鸭子缩放后像素太少特征提取不到。解决把imgsz提到 960 或 1280开启 mosaic 增加小目标出现频率如果小鸭子是重点考虑对含小目标的图做上采样提高其在训练集中的比例。5.4 背景被误检成鸭子现象水面反光、白色漂浮物被框成鸭子。原因单类别数据集负样本不足模型没见过足够多的「像鸭子但不是鸭子」的背景。解决收集一批纯背景图不放任何标注加入训练集让模型学会区分。这类图不需要标注文件YOLO 会把没有对应 txt 的图当作纯背景处理。加个几十张误检率通常能明显下降。5.5 换一批图片推理颜色和光照全不对现象训练集里鸭子是白天拍的换到傍晚或阴天检测效果断崖式下跌。原因数据分布单一模型对光照变化没有泛化能力。解决增强里把hsv_v和hsv_h的扰动范围适当调大模拟不同光照和色温有条件的话补拍一些不同时段、不同天气的图哪怕每类只加几十张泛化提升也很明显。6. 从能训到能用验证方法与一个提点小技巧训完模型别急着上线先做一轮系统验证。我一般会准备三类测试图第一类是训练集里没出现过的新图看基本泛化第二类是不同光照、不同距离的图看鲁棒性第三类是纯背景图看误检率。三类都过了才算能进实际场景。验证时不要只看整体mAP要按目标大小分层看。把测试图按鸭子占图面积分成大、中、小三档分别统计召回率。如果小目标召回明显低就回到第 5 章的小目标方案去调。这个分层统计能帮你判断模型到底能不能覆盖你的实际场景而不是被一个笼统的平均值骗过去。这里分享一个我常用的提点技巧在推理阶段对输入做多尺度测试。同一张图分别用 640 和 960 两个尺寸推理把两组检测框做加权融合小目标的召回通常能提升几个点代价是推理耗时增加。实现上就是跑两次推理然后对框做非极大值抑制合并from ultralytics import YOLO model YOLO(runs/duck/exp1/weights/best.pt) # 两个尺度分别推理 r1 model.predict(test.jpg, imgsz640, conf0.25) r2 model.predict(test.jpg, imgsz960, conf0.25) # 合并两组框交给NMS统一处理 boxes [] for r in (r1, r2): for b in r[0].boxes: boxes.append(b) # 实际使用时把两组结果送入同一NMS或直接取并集后按置信度过滤逻辑上就是让模型在两种感受野下各看一遍大尺度补小目标小尺度保速度融合后取长补短。参数上conf0.25是置信度阈值融合时可以适当放低到 0.2让更多候选框进入合并再由 NMS 去重。这个技巧在鸭子这种目标尺寸变化大的场景里特别管用代价是推理时间大约翻倍实时性要求高的场景要权衡。最后说个我自己的习惯每次训完模型我都会把验证集里预测错的图单独挑出来一张张看是漏检还是误检然后反推是数据问题还是模型问题。这个动作花不了多少时间但比盯着指标曲线有用得多。630 张的数据集错个二三十张看一遍就能定位到具体原因。希望帮到你。本文还有配套的精品资源点击获取
返回列表