尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

老虎目标检测数据集实战:VOC转YOLO格式训练与调参全流程

老虎目标检测数据集实战:VOC转YOLO格式训练与调参全流程 简介面向目标检测入门与进阶开发者这份老虎数据集按Pascal VOC与YOLO两种常见格式整理标注类别为Tiger适合用于单类动物检测模型训练、标注格式转换练习或迁移学习实验。压缩包共2000个文件以1999个XML标注文件为主体另有1个TXT说明文件整体大小约69.83MB下载与解压都比较轻量。XML标注文件遵循VOC标准可被多数检测框架直接读取说明txt则帮助快速了解包内结构与格式约定。目前已有144人学习下载适合作为教学演示或小型项目的基础数据。资源由labelImg工具标注Tiger类别累计标注框数2487个用户拿到后可直接进行数据集划分、格式校验或模型训练前的预处理相比从零标注能明显节省时间和人力成本。1. 老虎目标检测数据集2055张标注图单类检测模型能用它练到什么程度做目标检测最烦的不是训练那几步而是数据从哪来。这个老虎数据集就是冲着省事来的2055张真实场景图片每张都带目标框标注同时打包了VOC和YOLO两种格式解压出来就能直接喂给训练脚本。对于只检测老虎这一类的单类别任务这个量级配合预训练权重足够练出一个日常场景里能用的模型。反直觉的地方在于2055张听起来不多但单类别检测的收敛速度远快于多类别。从COCO预训练权重起步模型真正要学的只是老虎这个类别的特征。数据够不够用不只看得张数还看场景多样性、标注质量和train/val划分是否干净。标注里如果有大量半截老虎、框得离谱或者文件名对不上2080张也是白搭。这篇文章写给两种人第一次碰YOLO、想用现成标注数据跑通全流程的新手以及手里有自定义检测需求、想评估单类数据能压榨出多少性能的工程师。下文从格式拆解开始一路走到训练、参数调优和踩坑记录最后落到部署验证。2. VOC与YOLO两种标注格式目录结构、坐标换算与转换脚本拿到一个目标检测数据集第一件事不是解压就开训而是搞清标注格式。这份老虎数据集同时给了VOC和YOLO两种格式本质上是同一批框的两种表达方式VOC用xml存绝对像素坐标YOLO用txt存归一化中心点。转换关系不复杂但坐标越界、文件名错位、类别编号对不上这几个坑几乎每个数据集里都藏着几个。2.1 VOC格式的xml标注结构绝对像素坐标怎么读VOC是Pascal VOC比赛定下的格式特点是一张图配一个xml框的坐标是绝对像素值。常见打包目录是这样VOC/ ├── JPEGImages/ │ ├── tiger_0001.jpg │ └── tiger_0002.jpg ├── Annotations/ │ ├── tiger_0001.xml │ └── tiger_0002.xml └── ImageSets/ └── Main/ ├── train.txt └── val.txtJPEGImages放原始图片Annotations放同名xmlImageSets/Main里的txt记录train和val的文件名。很多下载的数据集打包时只保留前两个目录ImageSets可能是空的这种时候划分要自己做别指望里面自带。一个标准的VOC xml长这样annotation folderVOC/folder filenametiger_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nametiger/name truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax840/xmax ymax760/ymax /bndbox /object /annotation注意几个字段的实际含义xmin、ymin、xmax、ymax是目标框左上角和右下角在原图坐标系下的像素坐标单位是像素不是比例。truncated1表示目标被图像边缘截断训练时有人会直接过滤掉这类样本difficult1表示目标难以辨认竞赛里不计入评估但普通训练脚本默认不筛。同一张图如果有多个老虎xml里就会有多个object节点转换时一个都不能漏。像LabelImg、labelme这类目标检测常用标注工具导出VOC基本是默认动作所以网上能下载的目标检测数据集里VOC格式最常见。它的优点是人可读打开xml就能看明白框在哪缺点是每个框多一个文件文件碎片多训练框架读起来慢。这也是现代检测框架默认吃txt的根源。2.2 YOLO格式的txt标注归一化中心点怎么换算YOLO格式里每张图对应一个同名txt一行一个目标五个数字用空格分隔0 0.2500 0.3889 0.3750 0.6296 0 0.7800 0.2500 0.1800 0.2200五个数字依次是类别编号、目标中心点x、目标中心点y、目标框宽、目标框高。后四个值全部除以图片宽度或高度做了归一化数值范围在0到1之间。第一列类别编号从0开始如果这个老虎数据集只有一类所有txt的第一列应该全是0如果zip里还混了其他动物的标注就得找到打包时附带的classes.txt或names.txt按里面的顺序对编号。换算关系用上面的xml验证一下图片宽1920、高1080框xmin120、xmax840那么框宽是840-120720中心点x是(120840)/2480。归一化后cx480/19200.25w720/19200.375正好对上txt里第一行的前两个数值。反过来从txt还原像素坐标就是用cx、cy、w、h分别乘以图片宽高再算左上角和右下角。归一化坐标的好处有三条不受图片分辨率影响训练时从640改成1280都不用动标注数值范围固定模型回归目标稳定类别和坐标分离多类训练只改第一列。代价是人不直观打开txt看不出框在图上什么位置所以抽查标注时必须写脚本把框画回图片不能肉眼读小数。2.3 VOC转YOLO的Python脚本越界处理与批量转换下面这个脚本是我处理目标检测数据集时一直在用的读xml、取size和bndbox、算归一化坐标、写出txt。针对下载数据集的常见脏数据做了三道防护。import os import xml.etree.ElementTree as ET classes [tiger] # 类别表顺序就是类别编号 def voc_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) # 优先用xml文件名取主干不要信xml里的filename字段 xml_name os.path.basename(xml_path) stem os.path.splitext(xml_name)[0] lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in classes: continue # 不在类别表里的对象直接跳过 cls_id classes.index(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.0, min(xmin, img_w - 1)) xmax max(0.0, min(xmax, img_w - 1)) ymin max(0.0, min(ymin, img_h - 1)) ymax max(0.0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue # 宽度或高度为负无效框直接丢弃 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(output_dir, stem .txt), w, encodingutf-8) as f: f.write(\n.join(lines))三个细节值得说第一越界保护必须写。标注软件里偶尔会出现xmin写成负数、xmax超过图片宽这种手滑值不夹一下YOLO训练时letterbox缩放会引入非法坐标。第二xml里的filename字段不可信。标注工具重命名图片时经常忘记同步这个字段我直接用xml文件名取主干绕开这个坑。第三类别编号用classes列表的index不写死0。如果这个zip里只有老虎classes[tiger]没问题如果打开txt发现第一列有1说明打包时混了多类必须重建类别表。批量转换用一条bash循环mkdir -p yolo_labels for xml in VOC/Annotations/*.xml; do python voc2yolo.py $xml yolo_labels done转完后再做一步核对每张图片必须有同名txttxt为空表示这个框被过滤掉了这种图要么删掉要么手工补框。随机抽十张图用OpenCV把框画回去肉眼看一遍这一步能暴露几乎所有标注问题。import cv2 def draw_yolo_boxes(img_path, txt_path, out_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(out_path, img)如果你拿到的zip里YOLO的txt和VOC的xml能互相换算说明打包的人做了双格式同步如果两边对不上以你实际画框验证过的那份为准。我一般默认用YOLO的txt直接进训练VOC留作人工抽查毕竟训练框架不关心你标注格式有多少种它只认txt。3. 训练前的数据准备2055张图划分、tiger.yaml与YOLOv8最小训练格式确认没问题之后进入训练前最后三件事划分数据集、写data.yaml、跑通最小训练。这三件事看着简单却决定了后面调参时是省心还是闹心。划分不干净模型指标虚高yaml路径写错训练直接报错batch没算好显存来回试错浪费时间。3.1 train/val/test划分脚本随机种子与同源连拍泄漏2055张图不可能全拿去训练必须留出验证集和测试集。我的默认比例是8:1:1单类数据量小验证集给10%约两百多张足够统计出稳定的mAP。划分时固定随机种子保证每次重跑结果可复现。import os import random import shutil random.seed(42) # 固定种子复现结果的关键 src_img images train_dir images/train val_dir images/val test_dir images/test os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) os.makedirs(test_dir, exist_okTrue) imgs [f for f in os.listdir(src_img) if f.lower().endswith((.jpg, .jpeg, .png))] imgs.sort() random.shuffle(imgs) n len(imgs) train_imgs imgs[:int(n * 0.8)] val_imgs imgs[int(n * 0.8):int(n * 0.9)] test_imgs imgs[int(n * 0.9):] def move_files(files, dst): for f in files: shutil.move(os.path.join(src_img, f), os.path.join(dst, f)) label os.path.splitext(f)[0] .txt label_src os.path.join(labels, label) if os.path.exists(label_src): label_dst dst.replace(images, labels, 1) os.makedirs(label_dst, exist_okTrue) shutil.move(label_src, os.path.join(label_dst, label)) move_files(train_imgs, train_dir) move_files(val_imgs, val_dir) move_files(test_imgs, test_dir)这段脚本有两个容易忽略的点。第一图片和标注txt必须一起移动只移图片不移txt训练时会出现大量图片没有标注日志里labels数量明显小于images数量。第二dst.replace(images, labels, 1)假设图片和标注在同一个上级目录下、目录名分别是images和labels如果实际解压结构不是这样改成显式传参更稳。随机划分最大的隐患是同源连拍泄漏。如果这2055张里包含从同一段视频抽出来的连续帧——比如tiger_0120到tiger_0160来自同一段拍摄画面几乎一样——随机划分会把高度相似的帧同时分进训练集和验证集验证指标虚高模型一到新场景就露馅。处理办法是先看文件名如果名字带连续数字前缀按前缀分组把整个组划进同一个集合。这个zip里的文件具体怎么命名我无法替你确认解压后先扫一遍文件名再决定要不要分帧组。看到连续递增的编号直接联想到连拍这是做数据集的职业本能。3.2 data.yaml的路径写法相对路径、names编号与检查命令YOLOv8用yaml描述数据集单类检测的yaml是最短的一类# datasets/tiger/tiger.yaml path: ./datasets/tiger # 相对于当前工作目录 train: images/train val: images/val test: images/test names: 0: tigerpath是数据集根目录train、val、test是相对path的路径。这里最容易翻车的是绝对路径和相对路径混用写成绝对路径换机器必须改不写pathYOLO会把train理解成当前目录下直接叫images/train的路径。我的习惯在项目根目录运行训练命令yaml里全用相对路径换机器只需要把datasets整个目录拷过去。names定义一个类别编号0对应标注txt里第一列的0。如果txt第一列出现了1训练时会报num_class不匹配或者验证时显示unknown类。可以不写ncYOLO根据names自动推断写了就必须和names数量一致单类就是nc1多写个nc2会直接让矩阵混乱。检查txt里到底有哪些类别编号用下面这段cut -d -f1 labels/train/*.txt 2/dev/null | sort | uniq -c如果输出只有一行2055 0说明所有标注都是第0类yaml可以放心写单类。出现其他数字就得回到类别表重新映射了。3.3 最小训练命令显存不够先减batch还是imgsz数据准备好后用COCO预训练权重启动训练。yolov8n最省资源yolov8s精度更好。2055张单类数据我一般从s起步100个epoch足够看到结论。yolo detect train \ datadatasets/tiger/tiger.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0模型文件yolov8s.pt不需要提前手动下载ultralytics检测到本地没有会自动拉取。如果公司内网挡住外网下载需要提前把预训练权重放到当前目录或者在代码里指定权重路径这是yolo预训练模型下载最常见的拦路虎。显存不足时优先减batch而不是减imgsz。batch16在8G显存上通常能跑yolov8s还爆显存就降到batch8或者换yolov8n.pt。imgsz不建议低于512低于这个值远处的小目标被直接缩放没了对野生动物这类大尺度检测伤害明显。epoch也要理性看待单类数据100轮够用如果看到val mAP 50轮就平台期提前用best.pt收工没必要死等。4. 单类检测训练参数老虎目标在复杂背景下的增强与调参数据集结构和训练链路都通了剩下的是纯调参。这个阶段有个常见误区以为YOLO默认参数就是最优解。对于老虎这种野外目标丛林背景复杂度高、目标尺度变化大、光照乱有五个参数值得单独拎出来调。4.1 数据增强参数mosaic、hsv和水平翻转怎么取舍YOLOv8的训练增强默认值写在配置文件里核心几项是mosaic1.0、hsv_h0.015、hsv_s0.7、hsv_v0.4、fliplr0.5。对老虎检测这些默认值方向对但有几个点值得改。第一fliplr水平翻转保留。老虎不存在必须朝左站这种方向敏感性水平翻转等于白送一倍训练样本对只有2055张的数据集非常划算。第二mosaic1.0保留它把四张图拼一起训练强制模型学习小目标上下文对遮挡场景帮助明显。但mosaic有个副作用最后阶段模型尺寸感知会偏移所以YOLOv8默认在最后10个epoch自动关闭mosaic这个close_mosaic10不用改。第三hsv_v亮度增强保持默认0.4就可以不要为了让模型适应逆光调到0.8以上颜色失真会让模型丢掉老虎条纹这个关键特征。如果你训练后发现树荫里的老虎漏检很多优先调大translate和scale而不是动mosaic。translate控制目标在画面内平移范围scale控制随机缩放幅度这两个参数直接决定模型应对目标位置偏移和远近变化的能力。4.2 前景背景不平衡与loss权重别让cls_loss带偏回归单类检测没有多类别不平衡问题但有两个更隐蔽的点要处理。第一个是前景背景不平衡。平均每张图只有一个目标其他区域全是负样本模型很容易收敛到啥都不框也能把loss压到很低的状态。解决办法不是改loss权重而是保证训练充分——单类数据要跑到验证集mAP平台期不要看到训练loss降得慢就提前停。第二个是类别任务退化问题。只有一个类别时分类任务本质上退化成是不是老虎的二分类模型容量应该大部分花在bndbox回归上。这就是为什么我建议用yolov8s而不是yolov8n以及不要为了加速收敛去调大cls_loss的权重。cls_loss调大会诱发大量假阳框框了一片树影说它像老虎。box_loss、cls_loss、dfl_loss三者的默认权重在YOLOv8里已经是平衡好的单类场景下最该做的反而是什么都不动。4.3 训练过程盯什么loss曲线、mAP50与PR曲线的判断标准训练开始后终端里滚动的实时日志不用细看真正要盯的是runs/detect/train目录下的results.csv训练结束后还有一堆png图表。重点看三件事。第一loss曲线。box_loss和cls_loss在前30轮应当单调下降如果震荡剧烈甚至往上走大概率是学习率偏大。对单类小数据集我习惯把lr0从默认0.01改成0.005收敛慢一点但稳。第二mAP50。单类干净数据练到0.9以上是及格线0.95以上是好结果。如果只有0.7先怀疑数据问题而不是参数问题——回去画框看标注八成有框错位。第三PR曲线。曲线下面积大、向右上方鼓起说明precision和recall平衡如果曲线贴着顶边往下掉说明recall虚高、precision不足部署时需要把conf阈值往上提。除此之外每个epoch结束会输出混淆矩阵单类场景下就是一个2x2矩阵看TP的位置就知道模型把老虎当背景的比例高不高。这些图和csv都是黑匣子里最直接的证据不要只盯着epoch 50/100那行进度条。5. 避坑记录老虎数据集从解压到训练的五个常见问题数据集类的zip包坑几乎都集中在数据本身而不是模型。下面五条是我处理各种下载数据集时反复碰到的翻车现场每一条都写成现象、原因、解决三段方便你直接对号入座。5.1 解压后路径带中文或空格训练脚本读不到文件现象yolo命令报AssertionError: train: No labels in images/train或者No labels found检查目录文件明明都在。原因zip里目录名带中文或空格Windows上解压出来没问题换到Linux或Mac上路径解析不一致有些训练框直接读不到带空格的路径。这是zip解压后最常见的连锁反应。解决解压后立刻把目录名改成纯英文加下划线放到项目根目录的datasets下。yaml里全部用相对路径。如果已经训练到一半发现这个问题不需要重新下载直接把目录改名再改yaml里的path即可。5.2 图片名和标注文件名对不上一批图被无声跳过现象训练日志里出现N images found, M labels foundM明显小于N有些图片被跳过但没报错。原因转格式时用了xml里的filename字段而该字段和真实图片名不一致。下载的数据集里xml是标注工具自动生成的图片可能被后期重命名过两边就对不上了。解决以图片文件名为基准找出所有没有对应txt的图片看差异模式import os imgs set(os.path.splitext(f)[0] for f in os.listdir(images)) txts set(os.path.splitext(f)[0] for f in os.listdir(labels)) print(缺标注:, sorted(imgs - txts)[:20])如果缺的是后缀或前缀不同比如图片叫tiger_001.jpg而txt叫0001.txt写个批量重命名脚本统一如果完全对不上回到xml重新转换。5.3 标注框坐标越界或为负训练时loss变成nan现象训练一开始loss就是nan或者训练正常但验证框全部错位预测框跑到画面外。原因xml或txt里的坐标本身出错了比如xmin大于图片宽度、中心点出现负数。YOLO的letterbox虽然会对越界框做钳制但标注错得太离谱会让回归目标直接错乱造成loss发散。解决转换前先跑一遍越界检查把所有超出图片范围的框打出来决定丢弃还是钳制。2055张图里有那么几个坏框非常正常不要为这几个框手工重新标注整个数据集。我写的转换脚本里那三行越界保护就是专门干这个的。5.4 txt类别编号和names对不上模型把tiger学成第二类现象训练能跑mAP也不难看但预测输出的标签显示unknown或者类别名为空和自己yaml里对不上。原因txt第一列写的是1而yaml里的names从0开始编号两者错位了一个。遇到数据标注工具默认从1开始计数的情况这个坑就会存在。解决解压后先看txt第一列的唯一值分布cut -d -f1 labels/*.txt | sort | uniq -c如果出现1要么把所有txt第一列减1要么在names里把编号改成匹配的值。二选一关键是让txt和names对齐别在训练脚本里自动修正那会让复现变得更加玄学。5.5 划分数据集时拆散同一段连拍验证指标虚高现象训练时val的mAP50高达0.97自己拿着模型跑测试视频却频繁漏检落差很大。原因随机划分时同一段连拍的相似帧被同时分进了train和val模型在训练时就见过答案了。这个不是造假的虚高是划分方案带来的数据泄漏属于静态数据里最隐蔽的坑。解决按文件名前缀分组划分把连续编号的帧归到同一个集合。如果改完划分后mAP跳水说明之前那个成绩本来就不真实。这个修正越早做越好因为调参阶段的超参数选择都是对着val指标做的val指标脏后面所有判断都跟着脏。除此以外还要检查一种情况如果解压出来的zip不是正版打包而是被人二次压缩过里面可能混入了损坏的jpg或空txt。训练前跑一遍全量检查把无法解码的图片单独归到broken目录不要直接删万一后面还要核对原始数据呢。6. 从数据集到可部署检测器test推理验证与ONNX导出训练完先不要信results.csv里的数字用没进过训练也没进过验证的test目录做一次端到端推理这是模型上线前最便宜的一次体检。6.1 用测试集跑一次完整推理验证yolo predict \ modelruns/detect/train/weights/best.pt \ sourcedatasets/tiger/images/test \ conf0.25 \ save_txtTrue跑完后看两个东西。第一个是置信度分布被漏掉的老虎大多是半藏在草丛里或者拍得极小这类图单独拉出来分析如果集中在某个特定场景说明训练数据的场景多样性不够回数据层面想办法。第二个是重复框大量0.9以上的高置信框压在同一只老虎身上说明NMS阈值需要调如果低置信框一大堆conf从0.25往上提到0.4单类场景下保precision比保recall更实际。6.2 导出ONNX模型离开训练环境前的最后一步验证通过后把best.pt导出成ONNX供边缘盒子或服务端推理使用yolo export \ modelruns/detect/train/weights/best.pt \ formatonnx \ imgsz640导出时注意opset默认用12如果你的推理框架版本旧遇到底层算子不支持可以指定opset11或者开dynamic动态输入。这块是最容易因为版本搭配摆烂的地方排错时去查onnx和onnxruntime的版本对应关系别回头重新训练模型。我的习惯是划分脚本、随机种子、tiger.yaml和转换脚本全部提交进代码库任何人拿到这个老虎数据集zip都能复现我的结果。数据集的坑永远先于模型出现把数据链路钉死训练才有后悔药可吃。希望帮到你。本文还有配套的精品资源点击获取
返回列表