智慧牧场航拍牛羊检测:远距离小目标数据集解析与YOLO训练实战

发布时间:2026/10/5 11:23:39
智慧牧场航拍牛羊检测:远距离小目标数据集解析与YOLO训练实战
简介面向智慧牧场航拍场景的牛羊检测数据集专为远距离小目标检测设计适合目标检测算法研究、无人机巡检项目开发及毕业设计等场景。数据集由1021张航拍图片构成共标注cattle、cow、sheep三个类别总框数14047个其中sheep类9518个、cattle类4128个、cow类401个类别分布不均可有效检验模型在稀疏与密集目标混杂、类别不平衡条件下的鲁棒性。压缩包内共2000个文件以Pascal VOC格式xml标注和YOLO格式txt标注为核心并配有对应jpg原图整体约97.58MB标注工具为labelImg采用矩形框规则标注所有标注经过准确性校验可直接用于YOLO、SSD、Faster R-CNN等主流目标检测框架的训练与评估。目前已有379人学习数据规模适中且场景真实既能满足初学者熟悉数据标注与训练流程的需求也可用于验证针对远距离小目标检测提出的各类改进方法。1. 智慧牧场航拍牛羊检测远距离小目标这份1021张3类别数据集先解决什么我第一次拆到这份“智慧牧场航拍牛羊检测远距离小目标数据集”时第一反应不是去看标签而是去翻图片。无人机飞个几十米上百米画面里的牛和羊往往只有二三十个像素有的甚至不到十个像素这在目标检测里就是典型的“远距离小目标”场景。这份数据集从VOC到YOLO双格式都给你备好了1021张jpg、1021份xml、1021份txt总共14047个标注框类别就三个cattle、cow、sheep。它解决的不是“用现成模型跑一遍”的问题而是“从零训练一个能在航拍画面里把牲畜从草场背景里抠出来”的问题。适合谁适合要做牧场巡检、无人机牲畜计数、草场监测这类视觉任务的从业者也适合拿小目标检测练手的人。数据量不算大但样本场景集中正好用来研究小目标检测的通病比如漏检、上下文误判、类别不平衡。我在后面的章节里会把双格式转换、小目标训练配置、踩坑记录全部展开你可以照着复现也可以直接拿去调教自己的检测模型。2. 先摸清数据集的底细文件构成、标签结构与类别分布2.1 文件清单与双格式并存解压后不是一堆乱糟糟的图片而是按照“每张图一套标注”的规矩排好的。图片是jpg标注有两种一种是和图片同名的xml文件里面是Pascal VOC格式另一种是和图片同名的txt文件里面是YOLO格式。这种双格式并存的安排对前期数据清洗和后期训练都很方便——VOC格式用来做标注编辑和可视化YOLO格式直接喂给训练框架两边不冲突。文件构成大概是这样文件类型数量作用jpg图片1021航拍原图画面中牛羊目标较小VOC格式xml文件1021存放绝对坐标的矩形框标注YOLO格式txt文件1021存放归一化坐标的矩形框标注xml文件是labelImg画出来的标准结构。打开一份来看里面最重要的几个字段是size和objectsize记录图片的宽、高、通道数object下面是类别名和bndboxbndbox里存的是xmin、ymin、xmax、ymax这组绝对像素坐标。YOLO的txt则完全不同一行一个目标格式是“类别id 中心点x 中心点y 宽 高”五个值用空格分隔其中坐标和宽高都是相对图片宽高的归一化小数。这两套坐标系是所有后续脚本的地基谁把这里搞混了后面转格式必翻车。双格式的另一个好处是方便交叉校验。遇到“某个目标在xml里有、在txt里没有”的情况可以写个脚本对比两边标注数量快速定位是导出遗漏还是文件被改动过。脱离这套双格式基底去盲目训练等于在不知道数据真实状况的情况下瞎跑后面所有指标都是黑匣子。2.2 用脚本盘清标签框数、面积分布与类别不平衡拿到标注后别急着训练先盘一盘家底。我从摘要里看到几个关键数字cattle框数是4128cow框数是401sheep框数是9518总框数14047。这个分布一旦画出来问题就很刺眼sheep占了绝对大头cow只有401框三个类别严重不平衡。如果直接拿原始标签训练cow类的召回大概率会惨不忍睹。下面这段脚本用来遍历xml文件按类别统计框数、平均宽高以及每个框相对图片的归一化面积这个面积能直观反映小目标占比。import os import xml.etree.ElementTree as ET from collections import defaultdict xml_dir Annotations stats defaultdict(lambda: {count: 0, area_sum: 0.0, w_sum: 0.0, h_sum: 0.0}) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) for obj in root.findall(object): cls obj.find(name).text box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) w xmax - xmin h ymax - ymin norm_area (w * h) / (img_w * img_h) stats[cls][count] 1 stats[cls][area_sum] norm_area stats[cls][w_sum] w / img_w stats[cls][h_sum] h / img_h for cls, v in stats.items(): avg_area v[area_sum] / v[count] avg_w v[w_sum] / v[count] avg_h v[h_sum] / v[count] print(f{cls}: 框数{v[count]}, 平均归一化面积{avg_area:.5f}, f平均归一化宽{avg_w:.5f}, 平均归一化高{avg_h:.5f})这段脚本的逻辑很简单遍历xml从每个object里读出类别和bndbox四个坐标用图片宽高把框的宽高和面积做归一化再按类别累加。运行完你就能看到三个类别的平均目标尺寸如果平均归一化面积低于0.02那基本等于说目标在图片里只占了很小一块区域训练时要用小目标策略。参数说明xml_dir指向解压后的Annotations目录如果标签在别的路径就改这里输出里norm_area是框占整张图的比例航拍场景下这个值通常很小。我一般还会顺手统计一下多少个框的边长小于32像素这个阈值常用来界定小目标。从摘要给的数据看sheep占比接近68%说明航拍画面里羊群大概率是密集出现的这也符合牧场实拍习惯。cattle和cow这两个类别需要格外注意它们在视觉上相似度很高如果你做的是通用牛羊检测而不是精细分类可以考虑把cattle和cow合并成一类或者调整损失函数里的类别权重。后面第5章避坑部分我会再展开讲这个数据不平衡带来的实际训练问题。3. 把VOC转成YOLO训练格式转换脚本与四个边界坑3.1 从xml到txt解析与归一化虽然这份数据集已经同时给了xml和txt但实际工作中你可能会自己用labelImg新标注一批图或者对现有xml做了修改这时候就必须自己完成VOC到YOLO的转换。而且亲自动手写一遍转换脚本能帮你真正理解两套格式的差异后面遇到标签异常时才不会慌。核心就是两步读xml的size拿图片宽高读object拿类别和绝对坐标再计算归一化的中心点坐标和宽高写进txt。import os import xml.etree.ElementTree as ET voc_dir Annotations yolo_dir labels os.makedirs(yolo_dir, exist_okTrue) # 类别映射表顺序就是YOLO类别id一旦确定不要随意改动 class_map {cattle: 0, cow: 1, sheep: 2} for xml_name in os.listdir(voc_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_name)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in class_map: print(f跳过未映射类别: {cls} 在 {xml_name}) continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 防御过滤非法框 if xmax xmin or ymax ymin: print(f跳过无效框: {xml_name}, {cls}, {(xmin, ymin, xmax, ymax)}) continue x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w_norm (xmax - xmin) / img_w h_norm (ymax - ymin) / img_h yolo_lines.append(f{class_map[cls]} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) txt_path os.path.join(yolo_dir, xml_name.replace(.xml, .txt)) with open(txt_path, w) as f: f.write(\n.join(yolo_lines))转换脚本里几个细节值得说清楚。第一从xml的size读取图片宽高而不是用cv2去重新读图因为xml里的尺寸是标注时labelImg记录的和图片本身一致直接省掉大量磁盘IO。第二class_map的字典顺序决定了YOLO类别id如果类别名在映射表里找不到脚本会跳过并打印警告这样你不会在不知情的情况下丢掉标签。第三xmax xmin之类的防御判断很有必要labelImg偶尔会画出角点倒置的框不过滤掉会让训练时越界报错。输出txt用6位小数就够了YOLO训练读浮点数没这么敏感6位能保证归一化坐标的精度。3.2 划分train/val与可视化回检转换完格式下一步是划分训练集和验证集。航拍数据集有个特点同一架次拍出来的相邻图片画面内容非常接近如果随手切分很容易把同一群牛羊的相邻帧同时分进训练集和验证集导致验证分数虚高。我一般按图片名做全量随机打乱再切片而不是按目录顺序直接截断这样能稍微打散同架次的相关性但严格做法是按架次划分这份资源没提供架次信息所以我们退而求其次按文件名随机。import os import random jpg_dir JPEGImages train_ratio 0.8 random.seed(42) img_names [f for f in os.listdir(jpg_dir) if f.endswith(.jpg)] random.shuffle(img_names) split_idx int(len(img_names) * train_ratio) train_names img_names[:split_idx] val_names img_names[split_idx:] with open(train.txt, w) as f: for name in train_names: f.write(os.path.abspath(os.path.join(jpg_dir, name)) \n) with open(val.txt, w) as f: for name in val_names: f.write(os.path.abspath(os.path.join(jpg_dir, name)) \n) print(f训练集 {len(train_names)} 张验证集 {len(val_names)} 张)这段脚本干了两件事先把jpg文件名全部打进列表用random.shuffle打乱再按train_ratio切分最后把绝对路径写进train.txt和val.txt。参数说明seed(42)是为了可复现你可以改成任意整数train_ratio一般取0.8或0.85如果图片总量少验证集可以压到0.15。拿到划分后的文件列表建议随手做一次可视化回检用OpenCV在图上画一遍框确认xml坐标转成YOLO坐标后没有偏差。import cv2 def visualize(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh parts xc, yc, bw, bh float(xc), float(yc), float(bw), float(bh) 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.putText(img, cls_id, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) return img img visualize(JPEGImages/firc_animal_491.jpg, labels/firc_animal_491.txt) cv2.imwrite(check_491.jpg, img)可视化回检不用每张图都做抽个三五张看一眼就有数了。这里要注意的是txt读取出来的中心点坐标和宽高都是归一化值乘回图片真实宽高才是像素坐标再画矩形框。如果画出来的框整体偏移或者面积明显不对那就是转换脚本里除错了分母比如把宽高搞反。这类问题在越界场景中特别容易出现下面集中讲。3.3 四个转换期的边界坑转换期看起来就几行代码但实际跑起来翻车的地方不少。我挑四个高频的坑按“现象→原因→解决”的节奏记录在这里。第一个坑YOLO文件的某一行为负值。现象是txt里出现0 -0.0231 0.45 0.12 0.33这种负数训练框架读的时候大概率报错或忽略该框。原因是labelImg画框时鼠标拖到了图片边界外xml里的xmin或ymin出现负像素值。解决方法是转换时对所有坐标做max(0.0, ...)和min(1.0, ...)夹取把越界坐标强行clip回[0,1]区间。第二个坑转换后txt文件行数比xml中object数量少。现象是两者的框数对不上。原因是类别映射表里漏了某个类别名比如xml里出现了一个不在class_map里的名字脚本直接用continue跳过了。解决方法是加一段统计代码打印被跳过的类别和数量确认是去掉了还是映射错了。第三个坑txt写在labels目录但训练时找不到对应图片。现象是ultralytics报image not found。原因是命名不一致比如把firc_animal_491.xml转成了firc_animal_491.txt但jpg文件名可能是firc_animal_491_1.jpg或者把txt放进了子目录。解决方法是转换脚本统一用xml_name.replace(.xml, .txt)作为输出名jpg、xml、txt三者保持同名前缀这是labelImg生态的习惯也是YOLO训练框架默认的匹配规则。第四个坑空标注图被遗漏。现象是某些图片里确实没有牛羊xml里没有任何object转换后txt是空文件ultralytics训练时会跳过这张图如果验证集里恰好有这类图模型整体指标会被拉低。原因是航拍场景中总有空镜头。解决方法是不要删除这些空txt保留空文件让训练框架按“无目标”处理评测时也可以把它们单独拎出来统计误检率。这四个坑踩完转换工作才算完整收尾。接下来要进入训练阶段但训练前的配置同样要花心思。4. 小目标视角下的训练配置yaml、超参与模型选型4.1 生成data.yaml路径、类别顺序与ultralytics的读取规则数据转换完毕之后给ultralytics框架准备一份data.yaml是绕不开的活。很多第一次训练的人把data.yaml当成一个过场随便写个路径就上结果训练跑到一半报类别对不上或者图片路径无效再回头排查反而浪费更多时间。实际上这份文件的核心就三件事train/val图片列表路径、类别数量、类别名字列表。类别名字的顺序必须和转换时class_map里的id完全一致否则模型学到的是一个错位的映射。# data.yaml train: /your_abs_path/train.txt val: /your_abs_path/val.txt nc: 3 names: [cattle, cow, sheep]这里train和val填的是上一节生成的txt文件路径注意我写的是绝对路径。如果你和图省事也可以直接指定图片目录比如train: /your_abs_path/JPEGImages但代价是框架会把目录下所有图片都当作训练集val就没有了验证过程就废了。所以我会坚持用train.txt和val.txt这种列表文件的方式指定。nc是类别数names列表顺序决定了预测输出层的类别顺序改动这里意味着要重新转换标签或重新训练成本很高。我一般习惯把这份data.yaml放在数据集根目录下方便每次训练时引用也方便同事之间交流复现。4.2 小目标训练的img_size、增强与超参取舍航拍小目标检测的训练配置不能照搬COCO那种常规参数。先说输入分辨率ultralytics默认imgsz640但这个默认值对远距离牛羊并不友好。目标本身只有几十像素缩放后细节进一步丢失。我的经验是把训练尺度尽量调大比如--img 896或者--img 1280当然这会直接增加显存占用和训练时间需要根据GPU情况折中。如果显存不够至少保持640训练但推理时用更大尺度这是后话。数据增强在航拍小目标场景下要谨慎。mosaic在YOLOv8的官方默认里是增强主力它把四张图拼成一张用来丰富上下文可航拍图本来就有很强的空间连贯性mosaic会把一大块草场切碎牛羊在拼接缝处被裁断反而给模型加了噪声。我的习惯是训练前期epoch少开mosaic后期关闭或者直接改成0。hsv_h、hsv_s这类颜色增强可以保留因为航拍时会遇到早晨、傍晚不同色温。fliplr和flipud可以开但flipud要谨慎垂直翻转会把阴影方向搞乱牛羊的影子是模型判断形体的重要线索。yolo detect train \ data/your_abs_path/data.yaml \ modelyolov8n.pt \ imgsz896 \ epochs150 \ batch8 \ patience20 \ mosaic0.5 \ imgsz_tune1280 \ project./runs/ranch \ nameexp_small_tgt这段命令是典型的航拍小目标训练配置。参数说明modelyolov8n.pt是从官方预训练权重开始微调比从零训练收敛快得多imgsz896把输入放大到接近推理尺度mosaic0.5把mosaic从默认的1.0调低缓解拼图切碎小目标的问题patience20代表验证指标20个epoch不提升就早停省时间project和name用来区分不同实验我习惯把每次实验单独放目录方便后期对比。如果你想换yolov11或者其他版本模型只要把model参数换成对应的yaml文件名data.yaml部分完全通用。训练中断了也冷静ultralytics支持resumeTrue接着跑权重文件和logs都在上次的运行目录里。4.3 训练过程中的监控与早停训练过程中别只是盯着终端看loss。yolo detect train跑完后会在runs/ranch/exp_small_tgt里生成一堆结果文件包括results.png、confusion_matrix.png和各类PR曲线。我习惯在训练中定期翻results.png看val/box_loss和val/cls_loss的下降趋势。如果box_loss在几十个epoch后不再下跌、曲线横盘那说明模型容量到了或者学习率过小。如果val/cls_loss反复震荡不收敛要先怀疑类别不平衡或标签错位。confusion_matrix里的信息量更大。航拍牛羊检测最常见的是sheep被当成背景也就是漏检而不是把其他类别搞混。如果confusion_matrix显示sheep大量落在背景那一列说明模型对密集小目标不敏感要么调高输入分辨率要么引入后面讲的NWD损失。cow如果既漏检又经常被预测成cattle那基本是类别过少造成的我上一章已经提醒过这个坑。训练完别直接看一个mAP就下结论先把每类AP分开看cattle、cow、sheep各自多少心里才有数。5. 远距离小目标训练避坑指南五个高频问题排查5.1 loss下降但mAP0.5:0.95几乎为0现象训练正常收敛loss稳定下降但验证集mAP0.5:0.95始终在0.1上下徘徊mAP0.5却可能有0.6。原因远距离小目标的像素面积太小IOU计算对偏移像素极其敏感目标本身只有二三十个像素预测框和真值框哪怕差5个像素IOU就会跌到0.3以下0.5的阈值都难过更不用说0.95。解决不要把mAP0.5:0.95当唯一指标重点看mAP0.5和小目标AP。如果小目标AP太低换用NWD损失替代IOU损失用归一化Wasserstein距离评估两个框的相似度小目标检测的表现会明显改善具体做法在第6章展开。5.2 验证集mAP高得离谱线下推理却漏检一大堆现象训练日志里验证mAP达到0.7以上但把模型部署到无人机航拍视频里羊群漏掉一半。原因验证集和训练集划分时没有按架次去重同一架次相邻帧画面高度相似模型相当于“背”过答案。另一个原因是用train.txt不严谨之前提过随手指定图片目录会把所有图都当验证集样本的“同源图”混进去。解决重新按文件名做严格随机划分并且验证集里确保不出现训练集中出现过的图片。如果条件允许最好按拍摄时间戳和GPS位置切分同一架次放进同一个集合这份资源没提供这层信息就退回到文件名随机但至少别用目录直读。5.3 cow类几乎不收敛验证集召回率为0现象训练结束后cattle和sheep都有可用的APcow的recall接近0。原因cow框数只有401而sheep有9518模型在分类分支上把绝大多数注意力分配给了sheep加上cow和cattle视觉特征相近分类分支很难区分。解决第一选择是合并cattle和cow成一个大类“牛”如果你的业务不需要区分母牛和肉牛第二选择是调整类别在损失函数里的权重给cow加大cls_loss的系数第三选择是给cow做简单的过采样训练时对含cow的图片多重复几个epoch或做副本增强。按业务需求选我通常会先合并因为智慧牧场计数场景不关心牛的细分种类。5.4 推理时把树影、车辆、屋顶误判成牛羊现象在真实航拍测试里模型把树木阴影和地面暗斑框成sheep还把停在草地边的车框成cattle。原因训练数据里的负样本太少模型没有见过足够多“看起来像但实际不是”的背景背景中的深色块和牛羊形状纹理相似靠纹理分类是压不住的。解决从训练集中挖一些无目标的空镜头作为负样本加入训练或者采集一批不含牛羊的草场和道路图标注为空。ultralytics支持在数据集中混入没有标签的图片模型会对这些图学习“无目标”的输出。如果来不及重新标注至少把推理的conf阈值从0.25往上调到0.4左右能砍掉一部分低置信度误检代价是略微增加漏检。5.5 训练跑到一半OOM或者黑匣子崩溃看不到日志现象训练十几个epoch后直接显存溢出CUDA out of memory或者进程被系统kill掉没有明确报错。原因imgsz调大了之后激活内存和中间特征图占用激增batch_size没同步调小另外某些机器上workers设太高会导致数据加载线程崩溃。解决把batch从8降到4或者把imgsz从896降到768再不行就用torch.cuda.amp的混合精度训练ultralytics默认开启AMP如果手动关过就重新开开。workers控制在4以内数据加载瓶颈一般不会出现在这个数据规模上。训练中断后不用从头跑加resumeTrue就能从上个checkpoint继续这算是训练过程中唯一的后悔药。6. 再提一档远距离小目标精度切片推理、NWD损失与验证口径6.1 SAHI切片推理大图上把小目标先放大再检测训练完成后如果发现直接整图推理漏检严重一个立竿见影的做法是切片推理。SAHI的原理很直观把一张航拍大图切成若干个640×640的tile每个tile分别送入检测模型再把所有tile的预测框合并重叠部分用NMS去掉重复框。小目标在整图里可能只有20像素切成tile后在tile里会被模型当作“正常大小”的目标来检测漏检率会显著下降。安装SAHI之后命令行就能直接操作。sahi predict \ --model_type yolov8 \ --model_path runs/ranch/exp_small_tgt/weights/best.pt \ --source test_images/ \ --image_size 640 \ --slice_width 512 \ --slice_height 512 \ --overlap_height_ratio 0.2 \ --overlap_width_ratio 0.2 \ --project sahi_output参数说明slice_width和slice_height设成512也就是每个切片的大小切得越小对小目标越友好但推理时间和重叠计算量也越高overlap_height_ratio0.2是相邻切片之间的垂直重叠比例重叠是为了保证跨切片的目标不被切断0.2是兼顾速度与效果的常用值。切片推理本质上是拿算力换精度部署时要评估一下显卡能否承受多路并行切片的开销切片增加倍数大约是(原图面积/切片面积)/(1-重叠率)倍。6.2 NWD损失替换换一把更适合小目标的尺子切片推理解决的是“看得到”的问题NWD损失解决的是“对得准”的问题。普通IoU损失会把小目标的预测偏移放大相距同样像素偏移大物体的IoU可能还有0.7小物体直接掉到0.2。NWD的思路是把每个检测框当成一个二维高斯分布用归一化Wasserstein距离来计算两个分布的相似度它对尺度不敏感小目标也能获得一个相对平滑的梯度信号。在ultralytics自带loss里把BboxLoss内部计算IoU的位置替换成NWD计算即可核心改动如下def wasserstein_loss(pred, target, eps1e-7): # pred和target为归一化坐标的框格式 (x1, y1, x2, y2) # 将坐标映射为高斯分布的均值与方差 mu_pred (pred[:, 0:2] pred[:, 2:4]) / 2 mu_tgt (target[:, 0:2] target[:, 2:4]) / 2 c_pred (pred[:, 2:4] - pred[:, 0:2]) / 2 c_tgt (target[:, 2:4] - target[:, 0:2]) / 2 # 计算中心距离和尺寸差异 center_dist (mu_pred - mu_tgt).norm(dim1)**2 wh_dist (c_pred - c_tgt).norm(dim1)**2 # 取log变成负距离作为相似度加1避免负值 nwd torch.exp(-torch.sqrt(center_dist wh_dist) / 2.0) return nwd这段替换的核心逻辑是把框转成均值中心和方差半宽高然后算两个高斯分布的Wasserstein距离取负指数后得到0到1之间的相似度。参数说明eps是数值稳定性用的实际计算里几乎用不到但保留无妨。注意pred和target的坐标必须是像素坐标或统一尺度下的坐标归一化坐标也行只要两者一致。替换到loss后训练时box分支不再用IoU作为相似度度量小目标在梯度回传时不会再因为像素偏移而梯度消失。血泪经验换NWD后mAP0.5:0.95的绝对值不一定会暴涨但小目标的recall通常在肉眼可见地回升尤其sheep这种密集小目标。6.3 验证口径标准基线策略的对齐最后一步也是最容易被忽略的验证口径。我见过太多人跑了一版新模型换着对比mAP结果基线权重、输入尺寸、测试集都不一样得出的结论根本不可信。我的习惯是先用默认参数跑一版baseline保持完全相同的data.yaml、imgsz和测试集再在这版基础上叠加切片推理或NWD改动每次只改一个变量对比时看三个维度mAP0.5、mAP0.5:0.95、以及单独统计像素面积小于32×32的目标子集的AP。只有三份指标同时对比才能判断到底哪个改动真的有效。做这份航拍牛羊数据集时我第一次跑就忘了先看标注框的面积分布直接用640默认配置去训结果sheep漏得一塌糊涂后来才意识到画面里的羊平均只有二十来个像素。从那以后我每次拿到新数据集第一件事就是跑一遍面积分布统计脚本再决定分辨率、增强和损失函数怎么配确认无误后才开始写yaml。这个习惯帮我省下了大量返工的时间也希望帮到你。本文还有配套的精品资源点击获取