☰
YOLOv8无人机航拍牧羊识别:从数据标注到边缘部署全流程
2026/10/2 9:35:03 网站建设 项目流程

简介:本资源为基于YOLOv8的无人机航拍牧羊目标检测项目代码,面向深度学习与计算机视觉方向的学习者、研究者及需要落地航拍牲畜识别方案的开发者。项目围绕无人机视角下的羊群检测任务,提供从环境配置到模型训练、推理与部署的完整工程结构,适合具备一定Python与PyTorch基础的中高级读者参考复现。压缩包共468个文件,约26.23MB,以227个md说明文档、130个Python脚本、43个yaml与12个yml配置为主,另含pt权重、cpp推理源码、sh脚本、ipynb笔记及Dockerfile等部署文件,覆盖训练、验证、导出与容器化运行环节。目前已有85人学习下载。读者可据此快速搭建YOLOv8检测流程,理解航拍小目标数据组织方式,掌握模型训练调参与推理部署的排错思路,并借助requirements.txt完成环境配置,直接用于课程设计、科研实验或二次开发。

1. 无人机航拍牧羊识别:YOLOv8 在草原场景到底能不能落地

草原上几百只羊散开吃草,靠人眼数,数到一半羊群动了,前功尽弃。用无人机挂相机飞一圈,回来拿 YOLOv8 跑一遍检测,理论上几分钟出结果——这就是「YOLOv8 无人机航拍牧羊识别」要解决的事。它属于目标检测在垂直场景的落地:把羊当成一类目标,在航拍视角下做定位和计数。适合两类人:一类是手里有无人机、想用视觉做畜牧管理的工程团队;另一类是刚学完 YOLOv8 想找个真实数据集练手的开发者。但航拍牧羊和常规目标检测差别很大——目标小、密度高、遮挡重、背景纹理单一,直接拿 COCO 预训练权重去跑,召回率会低到让你怀疑模型没加载。这篇按「数据怎么来、模型怎么训、参数怎么调、坑在哪」的顺序讲清楚,能照着复现。

2. 航拍牧羊数据集:从采集到 YOLOv8 格式的完整链路

2.1 为什么航拍羊群数据不能直接套用常规标注流程

常规目标检测数据集(VOC、COCO)的标注对象通常占画面 20% 以上,标注框边界清晰。航拍牧羊场景里,一只羊在 4K 画面里可能只有 30×20 像素,羊群密集时相邻个体边界框重叠率超过 40%。这意味着标注时「框到哪算一只」本身就是个模糊问题。我一般会定三条规则:只标完整可见的羊,遮挡超过一半的不标;相邻羊的框允许重叠但中心点必须落在各自羊身上;画面边缘被截断的羊,如果可见面积小于 30% 直接丢弃。这三条规则写进标注规范文档,多人标注时一致性会好很多。

另一个问题是视角。无人机航拍有正射(垂直向下)和倾斜(带角度)两种。正射视角下羊是「一团白点」,倾斜视角能看到羊的侧面轮廓。两种视角混在一个数据集里训练,模型需要额外容量去学视角不变性,小数据集上反而拖累收敛。常见做法是:如果只做计数,优先正射;如果要做行为分析(比如识别卧倒、行走),用倾斜 30°~45°。别混。

2.2 用 Labelme 标注后转 YOLOv8 格式的脚本

标注工具用 Labelme 或 LabelImg 都行,Labelme 对多边形支持更好,但牧羊检测用矩形框就够了。标注完得到的是 JSON(Labelme)或 XML(LabelImg),YOLOv8 要的是每张图对应一个.txt,每行class_id cx cy w h,全部归一化到 0~1。

下面这个脚本把 Labelme 的 JSON 批量转成 YOLOv8 格式,同时按 8:1:1 划分训练/验证/测试集:

import json import os import random import shutil from pathlib import Path # 类别映射:牧羊场景通常只有一类,多类时按需扩展 CLASS_MAP = {"sheep": 0} def labelme_to_yolo(json_path, img_w, img_h): """将单个 Labelme JSON 转为 YOLO 格式行列表""" with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) lines = [] for shape in data['shapes']: label = shape['label'] if label not in CLASS_MAP: continue # 跳过未定义类别,避免训练时报错 points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] xmin, xmax = min(xs), max(xs) ymin, ymax = min(ys), max(ys) # 归一化并转为中心点+宽高 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 过滤掉宽高为0的异常框 if w <= 0 or h <= 0: continue lines.append(f"{CLASS_MAP[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") return lines def build_dataset(src_dir, dst_dir, img_w, img_h, split=(0.8, 0.1, 0.1)): """src_dir 下放 images/ 和 labels_json/,输出 YOLO 标准目录""" src = Path(src_dir) dst = Path(dst_dir) img_files = sorted([f for f in (src / 'images').iterdir() if f.suffix.lower() in ('.jpg', '.png', '.jpeg')]) random.seed(42) # 固定种子,保证每次划分一致 random.shuffle(img_files) n = len(img_files) n_train = int(n * split[0]) n_val = int(n * split[1]) subsets = { 'train': img_files[:n_train], 'val': img_files[n_train:n_train + n_val], 'test': img_files[n_train + n_val:] } for subset, files in subsets.items(): (dst / 'images' / subset).mkdir(parents=True, exist_ok=True) (dst / 'labels' / subset).mkdir(parents=True, exist_ok=True) for img in files: json_path = src / 'labels_json' / (img.stem + '.json') if not json_path.exists(): print(f"警告:{img.name} 缺少标注,已跳过") continue lines = labelme_to_yolo(json_path, img_w, img_h) if not lines: continue # 无有效标注的图不进入数据集 shutil.copy(img, dst / 'images' / subset / img.name) with open(dst / 'labels' / subset / (img.stem + '.txt'), 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': # 假设所有图片统一为 3840x2160,实际按你的相机分辨率改 build_dataset('./raw_data', './sheep_dataset', img_w=3840, img_h=2160)

逻辑说明:labelme_to_yolo负责单文件转换,核心是把 Labelme 的左上-右下坐标转成 YOLO 的中心点归一化格式;build_dataset负责划分和落盘。参数上,img_w和img_h必须和实际图片分辨率一致,写错会导致所有框偏移;random.seed(42)保证可复现,团队协作时别去掉;split按 8:1:1,如果数据量少于 500 张,建议改成 9:1:0,把测试集省给训练。

2.3 数据集配置文件与目录结构

YOLOv8 需要一个 YAML 描述数据位置和类别。在项目根目录建sheep.yaml:

path: ./sheep_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: sheep

path是数据集根目录,train/val/test是相对路径。nc是类别数,牧羊场景通常就是 1。如果后续要区分「羊」和「牧羊犬」,把nc改成 2,names加一行,同时标注时给狗单独一个 label。注意:改类别数后必须重新训练,不能拿旧权重直接微调最后一层了事——检测头的通道数变了,加载会报 shape mismatch。

3. YOLOv8 训练牧羊检测模型:参数怎么设、显存怎么省

3.1 从预训练权重到牧羊场景的迁移策略

YOLOv8 官方提供了 n/s/m/l/x 五个尺度的预训练权重。航拍牧羊的目标极小,理论上大模型(l/x)特征提取能力更强,但显存和推理速度会拖后腿。我的经验是:如果部署在 Jetson Orin 或 RK3588 这类边缘设备上,选 yolov8s 或 yolov8m;如果只在服务器上跑离线计数,yolov8l 能多拿 3~5 个点的 mAP。别一上来就上 x,航拍数据集通常几千张,大模型过拟合风险高,而且训练一轮的时间够你调三轮 s 模型了。

迁移学习的做法是加载 COCO 预训练权重,但冻结前几层还是全量微调?航拍视角和 COCO 的自然图像差异大,冻结 backbone 会导致特征不适应。我一般全量微调,但把初始学习率调低到 0.001(默认 0.01 的十分之一),让预训练特征缓慢适应新域。如果数据集超过 5000 张,可以恢复到 0.005。

3.2 训练命令与关键参数逐项说明

用 Ultralytics 的 CLI 训练,一条命令搞定:

yolo detect train \ model=yolov8s.pt \ data=sheep.yaml \ epochs=150 \ imgsz=1280 \ batch=8 \ lr0=0.001 \ lrf=0.01 \ patience=30 \ device=0 \ project=runs/sheep \ name=exp1 \ cache=True \ augment=True

逐项说明:imgsz=1280是关键——航拍小目标在 640 下几乎消失,1280 能让羊的像素数翻倍,mAP 通常能涨 5~8 个点,代价是显存翻倍。batch=8配合 1280 输入,在 8GB 显存的卡上刚好不爆;如果显存不够,降到 4 并加accumulate=2模拟大 batch。lr0=0.001是迁移学习的保守值。patience=30表示 30 轮验证集不涨就早停,省时间。cache=True把图片缓存到内存,数据集小于 10GB 时强烈建议开,能减少 IO 瓶颈。augment=True开启默认增强,但航拍场景要注意:默认的 mosaic 增强会把四张图拼一起,羊的尺度分布被打乱,小目标可能更小。如果发现训练前期 loss 震荡大,可以关掉 mosaic(mosaic=0.0)再试。

3.3 显存不够时的三个降级方案

第一个方案是降imgsz到 1024 或 960,mAP 损失约 2~3 个点,但显存省 40%。第二个方案是换更小的模型,yolov8n 在 1280 下显存占用只有 s 的一半,适合快速验证流程通不通。第三个方案是梯度累积:batch=4 accumulate=4,等效 batch 16,但训练时间会拉长。三个方案可以组合,我一般先降 imgsz 到 1024 跑通,再根据 mAP 决定要不要升回去。

训练过程中用 TensorBoard 看 loss 曲线:

tensorboard --logdir runs/sheep

重点看train/box_loss和val/box_loss的差距。如果 train 持续下降但 val 平了或反弹,就是过拟合,加 dropout 或减 epochs。如果两个都高,是欠拟合,检查标注质量或加大模型。

4. 推理与部署:从 PyTorch 权重到实际可用的检测脚本

4.1 单张航拍图的推理与结果解析

训练完得到best.pt,推理脚本如下:

from ultralytics import YOLO import cv2 model = YOLO('runs/sheep/exp1/weights/best.pt') # 航拍图分辨率高,推理时用 imgsz=1280 保持和训练一致 results = model.predict( source='test_flight.jpg', imgsz=1280, conf=0.25, # 置信度阈值,航拍小目标建议 0.2~0.3 iou=0.5, # NMS 的 IoU 阈值,密集羊群可降到 0.4 max_det=1000, # 一张图最多检测多少只,羊群大时调高 save=True, save_txt=True # 同时输出 txt 便于计数 ) # 统计检测到的羊数量 for r in results: count = len(r.boxes) print(f"检测到 {count} 只羊") # 可选:画框保存 annotated = r.plot() cv2.imwrite('result_annotated.jpg', annotated)

参数说明:conf=0.25是平衡漏检和误检的起点。航拍小目标置信度普遍偏低,如果发现漏检多,降到 0.15 试试,但误检(把石头、白点当羊)会增加。iou=0.5控制 NMS 合并重叠框的力度,羊群密集时相邻羊的框重叠多,iou 太高会互相抑制导致漏检,降到 0.4 能缓解。max_det=1000默认是 300,羊群超过 300 只时会被截断,必须调高。

4.2 视频流推理与计数逻辑

无人机回传的是视频流,逐帧检测后需要去重计数。简单做法是每隔 N 帧检测一次,或者用跟踪算法(ByteTrack)给每只羊分配 ID,统计唯一 ID 数。Ultralytics 内置了跟踪:

from ultralytics import YOLO model = YOLO('best.pt') # 用 ByteTrack 跟踪,persist=True 保持跨帧 ID results = model.track( source='flight_video.mp4', imgsz=1280, conf=0.25, iou=0.5, tracker='bytetrack.yaml', persist=True, stream=True ) seen_ids = set() for r in results: if r.boxes.id is not None: ids = r.boxes.id.cpu().numpy().astype(int) seen_ids.update(ids) print(f"累计唯一羊只数:{len(seen_ids)}")

注意:跟踪计数在羊群密集交叉时 ID 切换频繁,计数会偏高。实际落地时我一般用「峰值帧计数」——取检测数量最多的那一帧作为估计值,比累计 ID 更稳。

4.3 导出 ONNX 与边缘设备部署的注意点

如果部署到 RK3588 或 Jetson,先导出 ONNX:

yolo export model=best.pt format=onnx imgsz=1280 opset=12 simplify=True

opset=12兼容性最好,simplify=True会做图优化。导出后务必用 onnxruntime 验证输出和 PyTorch 一致,再转 RKNN 或 TensorRT。常见坑是:导出时 imgsz 必须和训练一致,否则 anchor 匹配错位,检测框全偏。另外 RK3588 的 NPU 对 1280 输入支持有限,可能需要降到 640 并接受 mAP 损失。

5. 避坑与排查:航拍牧羊检测翻车的五个真实场景

5.1 现象:训练 loss 正常下降,但验证集 mAP 始终低于 0.3

原因通常是标注框太小且数量少。YOLOv8 的损失函数对小目标不友好,如果数据集中超过 60% 的羊框小于 32×32 像素,模型很难学到有效特征。解决:把输入分辨率从 640 提到 1280,或者在数据加载时对小目标做复制粘贴增强(copy-paste augmentation),人为增加小目标样本密度。

5.2 现象:推理时同一只羊被检测出两个框

NMS 的 iou 阈值设太高(比如 0.7),相邻羊的框重叠没被合并。航拍羊群中羊与羊的框重叠率经常到 0.3~0.5,iou 设 0.5 以上就会漏合并。解决:把iou降到 0.4,同时开agnostic_nms=True(如果只有一类则无影响)。如果还不行,检查标注时是否有重复框。

5.3 现象:模型把白色石头、云影误检成羊

航拍背景中白色物体和羊的纹理相似。原因是训练集负样本不足。解决:在数据集中加入 10%~20% 的纯背景图(无羊的草原、有石头的区域),标注为空 txt 文件。YOLOv8 会把这类图作为负样本,降低误检。另外可以把conf从 0.25 提到 0.35,牺牲一点召回换精度。

5.4 现象:训练到一半显存溢出(CUDA out of memory)

除了降 batch 和 imgsz,还有一个容易被忽略的点:cache=True会把所有图片解码后存内存,如果数据集是 4K 图且数量多,内存先爆。解决:关掉 cache,或者先把图片缩放到 1280 长边再存一份训练副本。另外workers设太大也会占显存,设成 4 或 8 即可。

5.5 现象:导出的 ONNX 在边缘设备上推理结果和 PyTorch 不一致

最常见原因是预处理不一致。PyTorch 推理时 Ultralytics 自动做了 letterbox 填充,而手动写 ONNX 推理时如果直接 resize 不填充,坐标会偏。解决:在 ONNX 推理脚本里复现 letterbox:按长边缩放后,短边用 114 灰度填充到正方形,推理完再把框映射回原图坐标。这个坑我踩过两次,每次都是框整体偏移几十像素。

6. 把牧羊计数做准的一个技巧:分块推理与重叠合并

整张 4K 航拍图直接缩到 1280 推理,羊的像素数被压缩了 3 倍,小目标召回率会掉。我后来固定用一个笨但有效的办法:把原图切成 2×2 或 3×3 的块,每块单独推理,块之间留 20% 重叠,最后用 NMS 合并所有块的检测框。这样每只羊在某个块里都是「大目标」,召回率明显提升。

具体做法:切块时记录每块的左上角偏移,推理后把框坐标加回偏移量,再用torchvision.ops.nms做全局合并。重叠区域设 20% 是为了避免羊正好落在切割线上被切一半——有重叠就能保证至少一个块里有完整羊。代价是推理时间变成 4 倍或 9 倍,但离线计数场景不在乎这点时间。

参数上,块大小建议 1280×1280,重叠 256 像素。如果羊群特别密集,块可以再小到 960,但块数增加,合并时的 NMS iou 要降到 0.4 以下,否则跨块的重复框合并不干净。这个方案在 3000 只规模的羊群测试中,计数误差从整图推理的 15% 降到了 5% 以内。

我现在的习惯是:拿到任何航拍小目标检测任务,先跑一遍整图推理看基线,再跑分块推理对比。如果分块提升不到 3 个点,说明目标没那么小,不值得多花推理时间;如果提升超过 8 个点,就固定用分块。这个判断帮我省了很多无谓的调参时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询