首页 / 资讯中心 / 文章详情

YOLOv8无人机航拍牧羊识别:从数据标注到边缘部署全流程

YOLOv8无人机航拍牧羊识别:从数据标注到边缘部署全流程 ★ FEATURED ARTICLE
简介本资源为基于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 对多边形支持更好但牧羊检测用矩形框就够了。标注完得到的是 JSONLabelme或 XMLLabelImgYOLOv8 要的是每张图对应一个.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, encodingutf-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(parentsTrue, exist_okTrue) (dst / labels / subset).mkdir(parentsTrue, exist_okTrue) 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_w3840, img_h2160)逻辑说明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.yamlpath: ./sheep_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: sheeppath是数据集根目录train/val/test是相对路径。nc是类别数牧羊场景通常就是 1。如果后续要区分「羊」和「牧羊犬」把nc改成 2names加一行同时标注时给狗单独一个 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 \ modelyolov8s.pt \ datasheep.yaml \ epochs150 \ imgsz1280 \ batch8 \ lr00.001 \ lrf0.01 \ patience30 \ device0 \ projectruns/sheep \ nameexp1 \ cacheTrue \ augmentTrue逐项说明imgsz1280是关键——航拍小目标在 640 下几乎消失1280 能让羊的像素数翻倍mAP 通常能涨 5~8 个点代价是显存翻倍。batch8配合 1280 输入在 8GB 显存的卡上刚好不爆如果显存不够降到 4 并加accumulate2模拟大 batch。lr00.001是迁移学习的保守值。patience30表示 30 轮验证集不涨就早停省时间。cacheTrue把图片缓存到内存数据集小于 10GB 时强烈建议开能减少 IO 瓶颈。augmentTrue开启默认增强但航拍场景要注意默认的 mosaic 增强会把四张图拼一起羊的尺度分布被打乱小目标可能更小。如果发现训练前期 loss 震荡大可以关掉 mosaicmosaic0.0再试。3.3 显存不够时的三个降级方案第一个方案是降imgsz到 1024 或 960mAP 损失约 2~3 个点但显存省 40%。第二个方案是换更小的模型yolov8n 在 1280 下显存占用只有 s 的一半适合快速验证流程通不通。第三个方案是梯度累积batch4 accumulate4等效 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) # 航拍图分辨率高推理时用 imgsz1280 保持和训练一致 results model.predict( sourcetest_flight.jpg, imgsz1280, conf0.25, # 置信度阈值航拍小目标建议 0.2~0.3 iou0.5, # NMS 的 IoU 阈值密集羊群可降到 0.4 max_det1000, # 一张图最多检测多少只羊群大时调高 saveTrue, save_txtTrue # 同时输出 txt 便于计数 ) # 统计检测到的羊数量 for r in results: count len(r.boxes) print(f检测到 {count} 只羊) # 可选画框保存 annotated r.plot() cv2.imwrite(result_annotated.jpg, annotated)参数说明conf0.25是平衡漏检和误检的起点。航拍小目标置信度普遍偏低如果发现漏检多降到 0.15 试试但误检把石头、白点当羊会增加。iou0.5控制 NMS 合并重叠框的力度羊群密集时相邻羊的框重叠多iou 太高会互相抑制导致漏检降到 0.4 能缓解。max_det1000默认是 300羊群超过 300 只时会被截断必须调高。4.2 视频流推理与计数逻辑无人机回传的是视频流逐帧检测后需要去重计数。简单做法是每隔 N 帧检测一次或者用跟踪算法ByteTrack给每只羊分配 ID统计唯一 ID 数。Ultralytics 内置了跟踪from ultralytics import YOLO model YOLO(best.pt) # 用 ByteTrack 跟踪persistTrue 保持跨帧 ID results model.track( sourceflight_video.mp4, imgsz1280, conf0.25, iou0.5, trackerbytetrack.yaml, persistTrue, streamTrue ) 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先导出 ONNXyolo export modelbest.pt formatonnx imgsz1280 opset12 simplifyTrueopset12兼容性最好simplifyTrue会做图优化。导出后务必用 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.5iou 设 0.5 以上就会漏合并。解决把iou降到 0.4同时开agnostic_nmsTrue如果只有一类则无影响。如果还不行检查标注时是否有重复框。5.3 现象模型把白色石头、云影误检成羊航拍背景中白色物体和羊的纹理相似。原因是训练集负样本不足。解决在数据集中加入 10%~20% 的纯背景图无羊的草原、有石头的区域标注为空 txt 文件。YOLOv8 会把这类图作为负样本降低误检。另外可以把conf从 0.25 提到 0.35牺牲一点召回换精度。5.4 现象训练到一半显存溢出CUDA out of memory除了降 batch 和 imgsz还有一个容易被忽略的点cacheTrue会把所有图片解码后存内存如果数据集是 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 个点就固定用分块。这个判断帮我省了很多无谓的调参时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站