ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

525张鸡数据集训练YOLOv8:VOC转YOLO格式与避坑指南

525张鸡数据集训练YOLOv8:VOC转YOLO格式与避坑指南 简介一套针对目标检测任务的鸡类图像数据集共525张真实场景图片全部经labelImg标注为Chicken单一类别累计607个矩形框覆盖单类检测场景。数据同时提供Pascal VOCxml与YOLOtxt两种主流标注格式可直接对接YOLO、Faster R-CNN、SSD等常见检测框架也便于初学者对比学习两种格式的存储结构。资源采用7z压缩大小162.28MB解压后共1577个文件其中jpg原图525张、xml标注525个、txt文件527个目录整洁便于划分训练集和验证集。目前已有183人学习下载适合正在积累目标检测数据集的开发者或是需要快速构建鸡类识别模型的科研与农业应用团队。免去自行标注的繁琐流程解压即可用于模型训练同时能直接观察VOC与YOLO标注的转换逻辑在数据预处理和格式迁移方面具有实际参考价值。1. 525张单类别鸡数据集它到底解决谁的训练问题值不值得用解压一个“鸡数据集VOC格式yolo格式525张1类别.7z”看到的通常是典型的VOC目录Annotations里躺着xml标注JPEGImages里是jpg原图额外那层yolo格式的txt标注是为了让yolov8、yolov5这类模型能直接读取。这份数据集做的是单类别目标检测检测对象就一个“鸡”525张图不多不少。对刚接触yolov8训练自己的数据集的人来说这是个很顺手的练手对象样本量小但标注完整两种格式齐活正好覆盖格式转换、数据校验、划分、训练的全流程。对做养殖场巡检、散养鸡计数、实验动物行为分析的人它也能当迁移学习微调的基础数据。下面从格式差异讲起一路讲到训练命令和五个高频翻车点让你拿到手就能照着跑通。2. VOC转YOLO格式转换脚本、四个校验项与一次跑通的最小实现VOC和YOLO是目标检测里最常碰到的两种数据格式。下载的压缩包里经常两种都带着但“带两种格式”不等于“两种格式都对得上”转换也不是简单改个文件后缀。先把底层差异讲透再给一个能直接跑的转换和校验脚本后面训练阶段翻车的概率会小很多。2.1 VOC的XML和YOLO的txt差在哪绝对坐标、归一化坐标与类别编号VOC格式下每张图对应一个同名的xml文件xml里的object节点记录类别名和bndbox的绝对像素坐标。它描述的是“这张图里有什么物体、物体在哪个像素区间”人眼可读labelimg也能直接编辑但模型训练不能直接拿它当输入。annotation filenameIMG_0001.jpg/filename size width640/width height480/height /size object namechicken/name bndbox xmin100/xmin ymin50/ymin xmax300/xmax ymax200/ymax /bndbox /object /annotation这个xml表达的是IMG_0001.jpg里有一只鸡检测框左上角在(100, 50)右下角在(300, 200)。注意bbox坐标是相对于原图的像素值直接拿给模型用会有问题——同一只鸡在不同分辨率图片里像素值完全不同必须归一化。YOLO格式用同名txt存同一张图上的所有目标每行5个值类别编号、归一化中心点x、归一化中心点y、归一化宽、归一化高。上面这个xml如果转成yolo格式对应的txt内容应该是0 0.312500 0.260417 0.312500 0.312500来历很简单x中心点(100300)÷2÷6400.3125y中心点(50200)÷2÷480≈0.260417宽(300-100)÷6400.3125高(200-50)÷480≈0.3125。所有值都在0到1之间与图片原始分辨率解耦。所谓“VOC格式转yolo格式”本质上就是把xml里的[xmin, ymin, xmax, ymax]加上图片宽高换算出[class_id, x_center, y_center, w, h]。这里面最容易出问题的不是公式而是类别名的映射xml里写的是chicken还是Chicken类名多一个空格、换一种大小写class_map匹配不上这个框就会被静默丢掉。我见过有人转换完发现一张图13个框只剩8个查了半天是标注时手滑把大小写写混了。所以转换脚本里一定要打印所有出现过的类别名人工核对一遍。2.2 批量转换脚本从Annotations目录的XML生成labels目录的txtimport os import xml.etree.ElementTree as ET from pathlib import Path # class_map: 把xml里的类别名映射成yolo类别编号 class_map {chicken: 0} def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) if filename is None: filename os.path.basename(xml_path).replace(.xml, .jpg) # 宽高优先从xml的size节点读读不到再从图片本身读取 w_node root.find(size/width) h_node root.find(size/height) img_path Path(img_dir) / filename if w_node is not None and h_node is not None: w, h int(w_node.text), int(h_node.text) else: from PIL import Image im Image.open(img_path) w, h im.size lines [] for obj in root.findall(object): name obj.findtext(name) if name not in class_map: print(f未登记的类别: {name}, 已忽略) continue cls_id class_map[name] bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 边界修正: 坐标不能超出图片范围 xmin max(0.0, min(float(w), xmin)) xmax max(0.0, min(float(w), xmax)) ymin max(0.0, min(float(h), ymin)) ymax max(0.0, min(float(h), ymax)) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h # 过滤宽或高为0的无效框 if box_w 0 or box_h 0: continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) out_path out_dir / (Path(filename).stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) return filename, len(lines) if __name__ __main__: xml_dir Path(Annotations) img_dir Path(JPEGImages) out_dir Path(labels) for xml_path in sorted(xml_dir.glob(*.xml)): filename, count voc_to_yolo(xml_path, img_dir, out_dir) if count 0: print(f警告: {filename} 没有生成任何标注行)转换完不要急着训练先做两件事。第一打开labels目录里几个txt文件按上面那组手算值核对一遍确认中心点不是左上角、宽高不是右下角减左上角的像素差。第二统计一下总标注框数和xml里的object总数对比如果少了很多多半是某个类名没进class_map。xml的size节点有时是缺失的脚本里做了兜底读不到就从图片文件里取宽高。这个兜底不是锦上添花因为部分打标工具导出时并不写size等你训练发现坐标全乱再去查就晚了。另外注意如果压缩包里作者已经带了labels目录你仍然建议自己跑一遍转换逻辑覆盖生成再对比差异。txt标注和xml标注经常不是同一时期生成的xml更新过而txt没同步的情况在下载的数据集里很常见。2.3 转换后的校验用脚本一次查清图片损坏、坐标越界、空标注与文件名错位from pathlib import Path from PIL import Image def validate(img_dir, label_dir): img_dir Path(img_dir) label_dir Path(label_dir) imgs sorted(img_dir.glob(*)) for img_path in imgs: if img_path.suffix.lower() not in (.jpg, .jpeg, .png, .bmp): continue # 1. 图片能否正常打开 try: im Image.open(img_path) im.verify() im Image.open(img_path) # verify之后要重新打开 w, h im.size except Exception as e: print(f图片损坏: {img_path} - {e}) continue # 2. 同名txt是否存在 txt_path label_dir / (img_path.stem .txt) if not txt_path.exists(): print(f缺少标注: {img_path.name}) continue # 3. 逐行检查yolo行格式与坐标范围 content txt_path.read_text(encodingutf-8).strip() if not content: print(f空标注: {img_path.name}) continue for line in content.splitlines(): parts line.split() if len(parts) ! 5: print(f格式错误 {txt_path.name}: {line}) continue try: cls_id parts[0] x, y, bw, bh map(float, parts[1:]) except ValueError: print(f非数字内容 {txt_path.name}: {line}) continue if not (0 x 1 and 0 y 1): print(f中心点越界 {txt_path.name}: {line}) if bw 0 or bh 0: print(f宽高异常 {txt_path.name}: {line}) if x bw / 2 1.0001 or y bh / 2 1.0001: print(f框超出右/下边界 {txt_path.name}: {line}) if __name__ __main__: validate(JPEGImages, labels)这个校验脚本覆盖了我每次拿到新数据集都会查的四项。第一图片损坏PIL打不开的图训练时会在读数据阶段直接报错中断。第二有图无标如果一张图没有对应txtyolov8会跳过它但你不知道它被跳过了最后训练集实际用到的图片数比预期少模型学不充分。第三坐标越界框画出了图片边界训练时loss会来回震荡验证集的mAP看起来也忽高忽低。第四空标注txt是空文件可能是转换时类别名没匹配上也可能这张图本来就没有目标。对525张小数据集来说任何一个问题都值得修干净再训练。提示对于空标注文件需要人为判定。如果图片里确实有鸡但txt为空是转换问题必须修如果图片本身是背景图空标注是合理的这类图可以作为负样本参与训练后面第4章会专门讲。3. 用yolov8训练自己的数据集划分、yaml与五个关键参数数据校验通过后下一步就是把数据集整理成yolov8认得的目录结构写对yaml把训练命令跑通。这一章每一步都可以直接照抄但每条我都会说清楚为什么这么设。3.1 按8:2划分训练集和验证集固定随机种子才能复现结果import random from pathlib import Path import shutil random.seed(42) # 固定种子保证每次划分结果一致 src_images sorted(Path(images).glob(*.jpg)) sorted(Path(images).glob(*.png)) random.shuffle(src_images) val_ratio 0.2 val_count int(len(src_images) * val_ratio) for i, img_path in enumerate(src_images): split val if i val_count else train label_path Path(labels) / (img_path.stem .txt) dst_img Path(split) / images / img_path.name dst_label Path(split) / labels / (img_path.stem .txt) dst_img.parent.mkdir(parentsTrue, exist_okTrue) dst_label.parent.mkdir(parentsTrue, exist_okTrue) shutil.move(str(img_path), str(dst_img)) if label_path.exists(): shutil.move(str(label_path), str(dst_label)) print(ftrain: {len(src_images) - val_count}, val: {val_count})划分脚本里最关键的是random.seed(42)。不固定种子的话每次运行划分结果都不一样上午训练和下午训练用的验证集不同mAP没法横向对比调参全靠猜。固定种子之后无论跑多少次同一个数据集划分出来的train/val归属完全一致结果可复现。划分完的目录结构要长成下面这样train和val下面各有一个images和labels子目录别把图片和标签分层放错位chicken_dataset/ ├── train/ │ ├── images/ │ └── labels/ └── val/ ├── images/ └── labels/图片和标签必须成对移动。脚本里已经做了判断图片移到train/images同名txt跟着移到train/labels。如果一张图本身没有txt脚本不会报错只是图片单独进了train/images这张图在训练时会被当无目标样本处理——具体要不要保留回到第2.3节末尾说的判定标准。划分比例我一般用8:2。525张的话train约420张val约105张。测试集这个阶段不单独划分数据量太小单独留测试集只有二三十张统计意义不大先把验证集守住用它观察泛化趋势。如果你的图片来自视频抽帧抽帧间隔不要小于10帧否则相邻帧极度相似划分后验证集里全是“见过”的样本mAP虚高得厉害上线就被打回原形。3.2 dataset.yaml的正确写法路径、nc和names一一对应path: /home/user/chicken_dataset train: train/images val: val/images nc: 1 names: 0: chicken这个yaml最容易写错的是path。填相对路径时yolov8以当前运行目录为基准去拼接换个目录跑就报“No labels found in train set”问题通常不在yaml本身而在运行环境。我一般直接写绝对路径一劳永逸。train和val字段填的是相对path的子目录注意必须写到images这一层不是写到train/val根目录。否则模型在train目录里找jpg找到的是images子目录依然报找不到标签。nc和names必须对应。names的下标就是txt第一列那个数字。单类别时txt第一列永远是0如果只定义了{0: chicken}而txt里出现了1那一定是第2章转换时class_map写漏了类别。多类别时更要注意顺序names: {0: chicken, 1: duck}意味着txt里0必须是鸡1必须是鸭顺序错位不会报错只会让模型把鸡学成鸭。如果你用的是yolov5而不是yolov8这个yaml依然有效字段名完全一样只是启动命令从yolo detect train换成了python train.py --data chicken.yaml。很多人纠结该用v5还是v8对小数据集来说差别不大v8的默认训练参数更省心v5的社区资料更多。都可以关键是先把这条数据链路跑通。3.3 yolov8训练命令的五个关键参数epochs、batch、imgsz、pretrained与deviceyolo detect train \ datachicken.yaml \ modelyolov8s.pt \ epochs100 \ batch16 \ imgsz640 \ patience20 \ device0 \ cacheTrue参数建议值说明modelyolov8s.pt使用coco预训练权重做迁移学习不是随机初始化epochs100单类别小数据100轮足够通常30~60轮就收敛batch168G显存可跑yolov8s显存不够降8甚至4imgsz640默认值目标很小可试960或1280patience20验证集连续20轮不提升就提前停止device0指定第一张GPU无独立显卡则写cpucacheTrue把图片缓存进内存525张图IO压力很小modelyolov8s.pt是迁移学习的开关。模型会从coco上训练好的yolov8s权重开始而不是从零随机初始化对525张小数据集的收敛速度和最终精度提升都非常明显。想要训练更快就换yolov8n.pt想要精度更高可以试yolov8m.pt8G显存跑m也能接受只是时间翻倍。epochs100不是必须的。patience20会帮你在验证集连续20轮不涨的时候自动停实际可能50轮就停了。epochs设大一点没坏处设太小反而可能没训够就被patience截断。batch16在8G显存上跑yolov8s没有问题。显存不够就降到8小数据集batch小一点还有正则化效果泛化不一定更差。不必为了跑batch32去上大显存卡525张图撑不起那么大的batch。imgsz640是大多数情况下没问题的默认值。如果你的鸡在画面里只占很小一块比如航拍俯视的鸡群640下目标可能只有十几个像素检测头学不到特征。这种情况把imgsz提到960或1280会有明显改善代价是训练时间和显存占用同步上升。device0指定用第一张显卡。没有独显就devicecpu525张的yolov8s用CPU大约半小时到一小时能忍。Windows下默认worker数偶尔会卡住可以追加workers2。报错时先看日志前几行data路径错误就去查yaml的绝对路径CUDA out of memory就把batch降到8或4。4. 避坑清单小样本鸡检测训练最容易翻车的五个现场训练能启动只是开始模型能用才是目标。这一章把我在类似小样本单类别数据集上踩过或见别人踩过的坑列出来每条按“现场→原因→解决”展开。你训练时如果遇到诡异现象优先来这章对照。4.1 现场loss在降但mAP0.5卡在0.3上不去现象训练日志里box_loss一路向下曲线很漂亮但验证集mAP0.5始终在0.3附近徘徊预测结果是大量漏检。原因数据增强开太猛。yolov8默认mosaic开启且概率为1每张训练图有80%概率参与马赛克拼接。小图里的鸡被随机拼贴后本来占比就不大的目标很容易被裁掉一半525张样本也扛不住每轮都做高强度几何扰动模型在增强后的样本上越学越乱。解决在训练命令里显式调低增强强度。在命令末尾追加hsv_h0.015 hsv_s0.5 hsv_v0.4 mosaic0.5 mixup0.0这几个参数能直接覆盖默认值。如果场景是低矮视角的密集鸡群mosaic取0.5通常够了。mixup对小样本不一定友好先关掉等mAP上到0.8再考虑加回来。调完增强参数后观察前两轮loss是否比之前降得更快更稳这是最直接的反馈。4.2 现场日志提示训练集里没有标签训练“正常”结束但预测为空现象训练能启动日志第一行就报WARNING训练集中找不到任何标签。但进程没崩训练照常跑完best.pt也能生成拿它去推理时一张图都检不出来。原因yaml的path写错通常是用相对路径但运行目录不对导致train/images指向了不存在的目录。另外labels目录名写错比如写成了label而不是labelsyolov8按固定结构找train/labels名字对不上就全部跳过。解决先在运行目录里手动ls确认一遍train和val下确实有images和labels两个子目录。再把yaml里的path改成绝对路径。训练前可以跑一行验证图片能否被正确索引python -c from pathlib import Path; print(list(Path(train/images).glob(*.jpg))[:3])能打印出图片路径就说明目录结构没问题。还没解决就检查标签后缀是不是.txt有没有被编辑器改成别的编码格式。4.3 现场验证结果里预测框全部飘出画面边界画出来是歪的碎块现象val_batch的预测图上框的位置和鸡完全对不上有的框中心点快贴到图外有的框宽度占整张图的一半以上。原因VOC转YOLO时归一化除错了。常见的有两种一是把txt里已有的归一化数值又除了一遍双重归一化导致框急剧缩小二是读的宽高是xml里记录的旧尺寸而图片已经被resize过坐标和图片尺寸不匹配。解决回到第2.2节的转换脚本确认width和height取的是当前图片文件的实际尺寸不是xml里可能过期的记录。转换后再跑一遍第2.3节的校验脚本把越界项全部清掉。调试时最直观的做法是单张推理看效果yolo predict modelbest.pt sourceval/images/0001.jpg画出来的框一眼就能看出问题。与其盯着数字猜不如直接看图。4.4 现场labelimg刚打完标类别编号整体错了一位现象用labelimg打标完yolo格式的标打开txt一看第一列是1或者2但class_map里只定义了0。训练时模型把所有鸡都学成了未定义类别或干脆学不出来。原因labelimg打标时如果在xml模式下直接手填了name或者从现有的pascal voc工程导入类别id不是连续的。还有一个常见来源同一个物体部分xml写chicken部分写Chickenclass_map按小写匹配时大写那些框全部匹配失败剩下的框按顺序编号自然错位。解决转换时用显式dict做映射不要用“按出现顺序自动分配编号”的写法。转换脚本里的class_map {chicken: 0}就是显式映射新类别出现时脚本会打印警告而不是默默分配新编号。如果已经打标完yolo格式的标但编号是乱的不要手动批量改第一列重新按class_map的顺序生成一遍txt更稳妥。4.5 现场模型学会了“图片里有草就是鸡”现象一张没有鸡的养殖场背景图上模型自信满满地画了一个框把栅栏当成了鸡。原因525张图如果全是含鸡的正样本模型会钻空子把背景纹理也学进特征里去。没有负样本相当于告诉模型“只要见到地面纹理就该出框”。这是小样本单类别训练里最隐蔽、也最容易被忽视的坑。解决从同一场景视频里抽帧挑出确实没有鸡的几十张背景图每张放一个空标注txt混进训练集。空标注图会被当作负样本参与损失计算模型被迫学会区分“这个画面里没有鸡”。负样本数量控制在正样本的10%到30%就够不用做严格的正负平衡反而能让模型收敛得更干净。5. 最后一公里迁移学习、切片推理与可视化验证闭环在只有525张图、单类别的情况下模型能达到什么水平很大程度取决于你怎么把训练、验证、推理串成一个闭环。我现在的习惯是先做迁移学习跑通再用可视化确认模型真正理解了“鸡”这个概念最后用小目标切片补漏。这个顺序能防止把时间浪费在无效调参上。5.1 先用coco预训练权重再解冻微调训练命令里使用yolov8s.pt而不是yolov8s.yaml就是最省事的迁移学习骨干、颈部、检测头全部用coco预训练权重初始化。对小样本数据集我还会在第一次训练时加freeze10冻结骨干网络的前10层前几十轮只让检测头参与训练让模型先专注学习“框怎么画”解冻后再全量微调。具体操作是第一次训练用带freeze的命令跑完拿best.pt当第二次训练的model参数第二次不加freeze。5.2 用可视化与指标构成验证闭环训练结束后不要急着看mAP数字。先打开results.png里的PR曲线和混淆矩阵再亲自跑一次yolo predict modelbest.pt sourceval/images把预测图翻出来看。框的位置准不准、置信度合不合理、密集处小鸡有没有漏检这些信息比单一指标直观得多。如果mAP0.5到了0.85但错误很典型优先处理典型错误而不是继续压指标。5.3 高密度小目标场景imgsz提到960或做切片推理如果鸡群在画面里占比很小imgsz640时目标只有十几个像素检测头很难学到有效特征。先把imgsz提到960重训一版通常有可感知的提升。依然漏检的话可以试切片推理把原图切成有重叠的小块分别预测再按坐标合并结果。525张的训练集跑SAHI这类工具压力不大值得一试。这是我最想分享的一个习惯小样本训练不要只盯训练曲线一定要拿真实场景图去检验。模型学没学会“峰值mAP多高”说了不算画出来看才算数。希望帮到你。本文还有配套的精品资源点击获取
返回列表