简介面向计算机视觉目标检测任务推出的牛只检测数据集共包含1968张已标注图像采用Pascal VOC与YOLO双格式存储配套xml与txt标注文件可直接用于YOLO系列、Faster R-CNN等主流检测框架的训练和评估。类别仅cow一种使用labelImg按矩形框规则绘制共8014个标注框样本量较为充足适合作为迁移学习、数据增强或模型精度对比的基础数据。资源包约382.76MB共2000个文件以xml标注文件为主体配合txt标签及说明文件目录结构清晰文件命名连续便于脚本批量读取和划分训练集。数据集附带说明强调标注准确且合理但不保证训练后模型精度适合需要质量可靠标注数据的算法工程师、在校学生及智能养殖方向研究者。目前已有549人学习下载可快速获取高质量牛标注数据并投入实验。1. 牛检测数据集 1968 张 VOCYOLO 格式先核三件事再开训牛检测数据集 1968 张 VOCYOLO 格式单看数字平平无奇真落到牧场场景里就具体了围栏边低头吃草的、树荫下躺着的、半截身子被柱子挡住的摄像头还要在逆光和扬尘里把每一头都框出来。这个数据集要服务的是视觉计数、群体验收和异常徘徊检测给 YOLO 这类检测算法当训练底料。它同时给了 VOC 和 YOLO 两种标注格式省去手动转换的时间但我拿到手不会直接开训而是先核三件事图片和标签文件是否一一对应、边界框有没有越界或退化、1968 张里有没有重复帧。这三件事不查清楚后面训练出来的 mAP 再高都是虚的。2. 牛检测为什么不能直接套通用检测VOC 与 YOLO 的格式转换很多开源检测数据集只给一种标注格式这个牛检测数据集一上来就摆出 VOC 和 YOLO 两套看起来省事。但省事的前提是你知道两套格式各自的组织方式以及从 VOC 转 YOLO 时哪里容易翻车。VOC 是给人看的YOLO 是给训练管线读的两者差的不只是文件后缀而是坐标语义和标签组织方式。2.1 牛检测的任务特点大目标、群聚和遮挡决定了标注策略牛检测和行人、车辆检测有个明显区别牛没有固定的外观姿态。站着、躺着、低头、侧身毛色从纯黑到花白都有同一种牛在不同季节拍出来差异也很大。所以模型不能靠“长得像牛”这个表面特征去学而是要学会用肩部、脊背、耳朵位置这些局部线索做判断。标注的时候如果只框头部模型学到的特征太少我一般会框可见的躯干部分哪怕被围栏挡住一半也保留这样模型能学到“有遮挡但存在”。群聚是这个任务里最容易让模型崩溃的场景。几十头牛挤在围栏角落目标与目标之间几乎没有间隙检测框重叠严重。这时候如果标注框习惯性放大一点NMS 阶段很容易把相邻的两头牛并成一个框。标注时宁可紧贴目标边缘也不要给每头牛留“余量”边界框稍微往里收一点训练出来的框反而更准。另一个要注意的是目标尺度极端不均。同一张画面里近处一头牛可能占 800 像素高远处一群牛每头只有 30 像素。YOLO 的默认输入尺寸对这类尺度分布并不友好后面训练参数里需要单独处理。这也是很多通用检测模型在 PASCAL VOC 上表现不错、到了牛检测数据集上却失灵的核心原因。2.2 VOC 标注文件里到底有什么xml 结构逐段拆拿到 VOC 格式先别急着转随便打开一个 xml对照下面这个结构看annotation folderVOCdevkit/folder filenamecattle_003.jpg/filename size width1920/width height1080/height depth3/depth /size object namecattle/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax640/xmax ymax420/ymax /bndbox /object /annotationfilename 是图片文件名size 里的 width 和 height 必须和真实图片完全一致否则归一化出来的 YOLO 坐标全是错的。object 的 name 是类别名这张数据集的类别就是 cattle但也有可能出现 cattle_calf 之类的二级分类转格式时要把所有类别名都列出来核对一遍。bndbox 里的 xmin、ymin、xmax、ymax 是像素坐标VOC 的坐标习惯是从左上角开始xmax 和 ymax 可以等于图片宽高但不允许超过宽高。人工标注时常见的错误是 xmax 写成 1920而图片宽度也是 1920这个值在转归一化坐标时会让 width 等于 1.0看起来没问题但如果图片宽度实际是 1919就超界了。truncated 和 difficult 是 VOC 特有的属性YOLO 里没有对应字段如何处理会在下节说明。2.3 把 VOC xml 转成 YOLO txt转换脚本与四个参数细节常见做法是写一个小脚本遍历 Annotations 目录把每个 xml 转成同名 txt。下面的脚本保留了 difficult0 的标注过滤掉 difficult1 的样本因为 YOLO 没有难例字段强行保留会把cls_loss的标签搞乱。import os import glob import xml.etree.ElementTree as ET def voc_to_yolo(voc_root, yolo_root, class_names): os.makedirs(os.path.join(yolo_root, labels), exist_okTrue) xml_list sorted(glob.glob(os.path.join(voc_root, Annotations, *.xml))) for xml_path in xml_list: tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) base os.path.splitext(os.path.basename(xml_path))[0] out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue cls_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界保护人工标注偶尔会把坐标写到图外 xmin max(0.0, min(xmin, img_w - 1)) ymin max(0.0, min(ymin, img_h - 1)) xmax max(0.0, min(xmax, img_w - 1)) ymax max(0.0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue # YOLO 归一化中心点 x, 中心点 y, 宽, 高全部除以图宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h out_lines.append( f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} ) # 空标签也写出来保持图片和标签一一对应 label_path os.path.join(yolo_root, labels, base .txt) with open(label_path, w, encodingutf-8) as f: f.write(\n.join(out_lines))class_names 的顺序就是 YOLO 类别 ID转好之后训练时的 data.yaml 里 names 必须保持相同顺序这是最容易埋雷的地方。坐标保留 6 位小数足够因为 1920 宽度的图6 位小数对应 0.002 像素精度再高只会让 txt 变大。遇到 xmax 小于 xmin 的框直接丢掉这种标注多半是人工拖框拖反了留着只会让 loss 变成 nan。空 txt 要保留不能因为没框就删文件否则训练时图片数量对不上部分图片会被静默跳过。提示转完之后不要急着开训先随机抽 20 张图把 txt 里的坐标画回图上肉眼确认框的位置。这一步能发现 90% 的格式问题。3. 用 YOLOv8 训练这套牛检测数据集数据划分、训练命令与参数格式确认无误后下一步是把数据集划分好再用 YOLOv8 跑一轮小规模训练。注意我这里说的是“一轮小规模”不是直接上来跑 300 个 epoch先把流程跑通再谈精度。3.1 训练前先做数据体检统计框分布、尺寸和重复样本训练前花十分钟做数据统计比训练后花三天找问题强。下面这个脚本会扫描所有 label 文件统计框数量、空标签数量和异常行。import os import glob label_paths glob.glob(yolo/labels/*.txt) total_boxes 0 empty_files 0 bad_lines 0 for label_path in label_paths: with open(label_path, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] if len(lines) 0: empty_files 1 continue for line in lines: parts line.split() if len(parts) ! 5: bad_lines 1 continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) if cls_id 0 or w 0 or h 0 or w 1 or h 1: bad_lines 1 total_boxes 1 print(f总标注框: {total_boxes}, 空标签: {empty_files}, 异常行: {bad_lines})这段脚本能快速暴露三个问题空标签占比过高说明数据集里大量图片没有牛训练时正负样本失衡异常行说明转换过程有脏数据必须修掉如果总框数除以有框图片数小于 1说明很多标称“牛检测”的图片其实没有目标需要单独看这些负样本对评估指标的影响。除了 label 文件还要检查图片本身有没有重复帧。视频抽帧做成的数据集经常出现连续几帧几乎一模一样的画面这种重复样本会让验证集指标虚高。常见做法是用感知哈希做快速粗筛from PIL import Image import imagehash, glob, os image_paths glob.glob(yolo/images/*.jpg) seen {} for img_path in image_paths: h imagehash.phash(Image.open(img_path), hash_size8) if h in seen: print(f疑似重复: {img_path} 与 {seen[h]}) else: seen[h] img_pathhash_size8 时哈希距离小于等于 3 的两张图大概率是连续帧如果重复比例超过 5%最好按场景分组后再划分验证集否则最终报告的 mAP 不能反映现场真实效果。3.2 划分训练集和验证集按图片维度切不要按帧切下一步是把图片分成 train 和 val 两个目录同时把对应标签文件一起搬过去。下面这段脚本按文件名硬划分简单直接import os import random import shutil random.seed(2025) img_dir yolo/images label_dir yolo/labels images sorted([ f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png)) ]) random.shuffle(images) split_idx int(len(images) * 0.8) train_imgs images[:split_idx] val_imgs images[split_idx:] for split_name, split_imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(f{split_name}/images, exist_okTrue) os.makedirs(f{split_name}/labels, exist_okTrue) for img in split_imgs: ext os.path.splitext(img)[1] label img.replace(ext, .txt) shutil.move(os.path.join(img_dir, img), os.path.join(split_name, images, img)) shutil.move(os.path.join(label_dir, label), os.path.join(split_name, labels, label))随机划分的问题在于连续帧会被拆到两个集合里导致验证集和训练集太像。更稳妥的做法是按文件名前缀分桶比如用拍摄时间或无人机架次做分组同一个批次的照片整体进训练集或验证集。这个牛检测数据集如果来源是多个视频文件名里通常带时间戳或机位号切分前先把文件名 pattern 打出来看一眼。我用 8:2 的比例是因为 1968 张的总量不算大验证集留 20% 能保证评估时每类目标至少有几十个框。如果验证集里只有十几头牛mAP 波动会非常大这时宁可改成 9:1。3.3 最小 train 命令yaml 配置与常用参数划分完成后创建 data.yaml内容如下path: /data/cattle train: train/images val: val/images nc: 1 names: 0: cattlepath 用绝对路径避免 YOLO 在不同工作目录下启动时找不到数据。train 和 val 填图片目录注意是 images 目录而不是 labels 目录ultralytics 会按照同名规则自动到 labels 目录找对应 txt。训练命令用一行就能跑起来yolo detect train \ data/data/cattle/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/cattle \ nameexp1 \ cacheTruemodelyolov8n.pt 会自动下载预训练权重到本地用 COCO 预训练权重做迁移学习比随机初始化收敛快很多。牛检测数据集类别只有 1 个COCO 里虽然没有专门的 cattle 类别但预训练模型已经学会纹理、边缘、轮廓这些底层特征对牛检测是有效的。常用参数里imgsz 对精度影响最大。640 输入下一张 1920 宽的画面会被压缩到原来的三分之一原本 30 像素的远距牛在压缩后只剩 10 像素基本就没了。如果显存允许牛检测我一般直接上 960 或 1280。batch 16 在单张 16G 显存下跑 640 输入没问题换成 1280 输入就得降到 8 甚至 4。epochs 先设 100牛检测是单类任务一般 60 轮左右就过了收敛拐点后面主要是震荡。参数速查表参数推荐值说明imgsz640 / 960 / 1280目标越小、越远分辨率越要往高设batch16640/ 81280显存不够就减半不要硬跑cacheTrue / disk小数据集直接缓存到内存大数据集缓存到磁盘cos_lrTrue配合 AdamW 用后期 loss 更平稳ampTrue出现 nan 时先改成 False 排查3.4 训练到一半怎么看日志loss、mAP50 和 BN 崩溃训练开始后终端会实时打印每个 epoch 的 box_loss、cls_loss、dfl_loss 和验证集 mAP50。只看终端还不够ultralytics 会把指标同步写进 runs/cattle/exp1/results.csv用下面命令盯日志tail -f runs/cattle/exp1/results.csv正常情况下box_loss 和 cls_loss 前 10 轮下降明显后面变缓但不反弹。如果 loss 曲线在第 20 轮开始往上涨通常是学习率太大或者标签有误不要等 100 轮跑完才去查。mAP50(B) 这个指标才是最终要看的训练集 loss 低但验证集 mAP 低说明过拟合了大概率是训练集里大量相似帧造成的。另一个常见异常是 BN 崩溃loss 突然变成 nan验证集指标从正常值瞬间消失。原因是混合精度计算时 BN 层的方差变得不稳定或者数据里有 nan 标签。排查顺序是先关掉 amp再检查标签文件最后把学习率调低一个量级三条路走一遍基本能定位。4. 牛检测训练避坑指南5 条常见问题与排查记录标注格式是 VOCYOLO 双份不等于转换后就能直接训练。下面这些坑按现象、原因、解决三行记录处理类似牛检测数据集时可以直接照着排查。4.1 坐标越界框全跑到图外现象训练前可视化检查发现大量框超出画面边界或者宽和高显示为 0模型训练时 loss 忽高忽低。原因VOC 标注的 xmax 有时会填成图片实际宽度比如宽度 1920 的图右下角坐标被人为记为 1920, 1080而有效像素范围应该是 0 到 1919。转换脚本直接除以图宽后归一化宽度变成 1.0005越界一点点YOLO 内部处理这种框时行为不可控。解决转换阶段对坐标强制 clamp并过滤掉宽度小于等于 1 像素的退化框。clamp 逻辑不要只在脚本里写一遍训练脚本的 Dataset 加载处也可以留一个 sanity check打印所有越界框的文件名和坐标省得下次跑别的新数据集时重新踩坑。4.2 loss 直接变 nan验证集指标消失现象训练从第 1 个 epoch 起就打印 nan或者前几个 epoch 正常中途突然 nan之后所有指标全消失。原因最常见的是标签文件里有脏数据比如 txt 里混入全角空格、某个框的坐标写成了字符串、类别 ID 超出 nc 范围。其次是混合精度和 BN 叠加后数值溢出在小 batch 情况下更容易发生。解决先扫描 labels 目录单类数据集可以直接用 awk 检查每一行是不是恰好 5 个字段、第一个字段是否为 0awk { if (NF ! 5 || $1 ! 0) print FILENAME, $0 } labels/*.txt多类数据集把$1 ! 0改成$1 0 || $1 nc。扫完如果没异常把训练命令里的 amp 改成 False让模型先跑几个 epoch 验证数据本身没问题。最后再看学习率batch 越小学习率越要保守lazy 一点直接设 lr00.0001 也能避开这个问题。4.3 mAP 不低现场视频却漏检现象验证集 mAP50 能到 0.85看起来不错但把模型接到现场视频上远处成群的牛漏掉一半站着的牛能检躺着的牛检不出。原因1968 张训练图里近景大牛占比高验证集和训练集同分布指标只能说明“在这个数据分布内”的效果。现场画面是远景、逆光、目标密集叠加分布已经完全不一样。另外 mAP 对置信度阈值不敏感指标高不代表在 conf0.5 下不漏检。解决从现场视频抽帧把漏检帧挑出来手动补框后加入训练集这是最有效的办法。推理时把置信度阈值降下来比如 conf0.15配合 iou0.5 做 NMS宁可多出几个假框靠后续跟踪算法过滤也比漏检一头牛更安全yolo detect predict \ modelruns/cattle/exp1/weights/best.pt \ sourcefield_video.mp4 \ conf0.15 \ iou0.5 \ imgsz12804.4 GPU 利用率上不去训练慢在 IO现象nvidia-smi 显示 GPU 利用率只有 30% 到 50%训练一个 epoch 要等很久但显存占用并不高。原因牛检测图片很多是 2K 甚至 4K 的航拍帧读取、解码、resize 都要时间。如果数据放在机械硬盘上IO 就成了瓶颈GPU 大部分时间在等数据。解决训练命令里加 cacheTrue把图片和标注一次性缓存到内存内存不够就用 cachedisk写进磁盘缓存后读取压力小很多。同时把 workers 调到 8 或 16让数据加载线程数跟上 GPU 消费速度。数据文件尽量复制到本地固态硬盘不要在共享存储上直接训练。4.5 小牛检不出先别改损失函数现象大目标牛框得很准小目标牛大概率漏检验证集按目标尺寸分组后小目标 AP 可能只有大目标的一半。原因640 输入下一个 30 像素的小牛被压缩后只剩不到 10 像素经过多层下采样后特征图上基本没有响应。这本质是输入分辨率和特征金字塔的问题不是损失函数系数不对。解决先把 imgsz 提高到 960 或 1280 试一轮看小目标 AP 有没有回升。如果显存限制跑不动 1280就做切片推理把大图切成 640 块分别检测再把结果映射回原图。损失函数层面YOLOv8 的 box_loss 和 dfl_loss 对微小像素偏移也不敏感分辨率上来之后损失才有机会正常工作所以不要一上来就调 loss 权重。5. 牛检测数据集的进阶用法用大图切片推理把漏检补回来训练完 best.pt第一步不是拿着验证集 mAP 表交差而是先看混淆矩阵。注意 YOLO 的混淆矩阵行列总和并不唯一因为一个真实框可能对应多个预测框或者被忽略的难例没进任何一格所以不用纠结总和是不是刚好等于样本数只看漏检分布在哪类目标上就够了。牛检测数据集的混淆矩阵里最常见的问题是远距小牛被预测成背景这时候切片推理是比调参更直接的补救方案。切片推理的思路是把现场航拍大图切成 640×640 的小块每个小块独立送入模型检测再把所有检测框映射回原图坐标。这样做的好处是模型输入里的小牛尺寸没有被过分压缩。常见做法是直接用 SAHI 包它封装了完整的切片和合并流程from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, # 对应 ultralytics 模型 model_pathruns/cattle/exp1/weights/best.pt, confidence_threshold0.25, image_size640, devicecuda:0, ) result get_sliced_prediction( ranch_panorama.jpg, detection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, ) result.export_visuals(export_dirdemo_output/)slice_height 和 slice_width 决定每个切片大小我习惯和训练 imgsz 保持一致。overlap 比例设 0.2避免一头牛正好被切到两块切片接缝处导致漏检切片重叠后同一个目标可能被检出两次SAHI 会在合并阶段用 NMS 去重。不引入 SAHI 也可以自己写滑动窗口把裁剪出的 patch 依次送入模型得到的检测框加上 patch 左上角偏移量就是原图坐标最后对全图所有框做一次 NMS 就行。这个方案对 GPU 显存更友好缺点是代码里要处理边界裁剪和重叠去重容易在坐标映射上翻车。我现在的习惯是拿到牛检测数据集先转格式跑十轮小模型确认 loss 正常再上大分辨率、补现场样本、做切片验证。这个顺序帮我跳过了大多数训练事故也让我对每一版模型的真实水平心里有数。希望帮到你。本文还有配套的精品资源点击获取