简介:本资源为基于YOLOv8的无人机航拍牧羊目标检测项目代码,面向深度学习入门与进阶学习者、计算机视觉方向学生及需要落地航拍牲畜识别方案的开发者。项目围绕无人机视角下的羊群检测任务,提供从环境配置到模型训练、推理部署的完整工程结构,帮助读者快速复现并迁移到类似航拍目标检测场景。压缩包共468个文件,约26.23MB,以227个md说明文档、130个Python脚本、43个yaml与12个yml配置为主,另含pt权重、cpp推理源码、sh脚本、ipynb笔记及少量图片与样式文件,覆盖训练、推理、Docker部署等环节。按requirements.txt配置环境即可使用。目前已有85人学习关注,适合希望掌握YOLOv8实战流程、理解航拍小目标检测难点并积累项目经验的读者参考。
1. 从一段航拍视频说起:YOLOv8 无人机牧羊识别到底在做什么
把无人机飞到羊群上方两百米,回传画面里几百个白色小点缓慢移动,人眼盯十分钟就花,可牧场主需要知道头羊往哪走、有没有羊掉队、边界有没有被冲散。YOLOv8 无人机航拍牧羊识别要解决的就是这件事:用目标检测模型在航拍画面里把每一只羊框出来,再交给上层逻辑做计数、跟踪和越界判断。它适合两类人,一类是手里已经有无人机和视频流、想把 AI 接进现有巡检流程的工程团队,另一类是刚学完 YOLOv8 训练自己数据集、想找一个真实场景练手的开发者。这个方向的门槛不在模型本身,而在航拍数据的小目标、密集遮挡和光照剧变,下面把我实际跑过一遍的路径拆开讲。
2. 航拍牧羊数据集:从原始视频到 YOLOv8 可训练格式
2.1 为什么牧羊场景不能直接套公开数据集
公开的 COCO、VOC 里虽然有 sheep 这一类,但绝大多数是地面平视拍摄,羊在画面里占几百像素,背景是草地围栏。无人机航拍是俯视或大角度斜视,单只羊在 1080p 画面里往往只有 20 到 60 像素,还经常几只挤成一团,边界框互相重叠。直接拿 COCO 预训练权重去推理航拍画面,召回率会掉得很难看,因为模型学到的纹理和尺度分布完全对不上。常见做法是自己采数据,用无人机在目标牧场飞几个架次,覆盖早中晚三个时段和晴天阴天两种光照,每段视频抽帧后人工标注。我一般会按 8:1:1 划分训练、验证、测试,并且保证同一段视频的帧不要跨集合,否则相邻帧高度相似会造成验证指标虚高,这是血泪经验。
2.2 抽帧、去重与标注的完整命令
抽帧用 ffmpeg 最省事,关键是控制帧率,航拍视频 30fps 相邻帧几乎一样,按每秒 2 帧抽就够。下面这段脚本把视频目录批量抽帧并按视频名建子目录,方便后续追溯来源。
# 批量抽帧:每个视频每秒抽 2 帧,输出到 frames/视频名/ for f in raw_videos/*.mp4; do name=$(basename "$f" .mp4) mkdir -p "frames/$name" # -vf fps=2 表示每秒保留2帧,避免相邻帧冗余 ffmpeg -i "$f" -vf fps=2 -q:v 2 "frames/$name/%05d.jpg" done抽完帧先做去重,否则大量近似帧会让模型过拟合到某几个背景。用感知哈希做粗筛,汉明距离小于 5 的判为重复,只保留一张。
import imagehash from PIL import Image import os def dedup(folder, threshold=5): seen = [] kept = 0 for fn in sorted(os.listdir(folder)): if not fn.lower().endswith(('.jpg', '.png')): continue path = os.path.join(folder, fn) h = imagehash.phash(Image.open(path)) # 与已保留的哈希逐一比对,距离过近则丢弃 if any(h - s < threshold for s in seen): os.remove(path) else: seen.append(h) kept += 1 print(f"保留 {kept} 张,删除 {len(seen) - kept} 张")标注工具用 labelme 或 X-AnyLabeling 都行,类别只设一个 sheep。标完导出成 COCO 或 VOC 格式,再转 YOLO 的 txt。YOLO 格式每行是类别 中心x 中心y 宽 高,全部归一化到 0 到 1。转换时最容易翻车的是坐标越界和空标注文件,下面这个脚本顺手做了校验。
import json, os def coco_to_yolo(coco_json, out_dir, img_dir): data = json.load(open(coco_json)) # 建立 image_id 到文件名的映射 id2img = {im['id']: im for im in data['images']} os.makedirs(out_dir, exist_ok=True) for ann in data['annotations']: im = id2img[ann['image_id']] w, h = im['width'], im['height'] x, y, bw, bh = ann['bbox'] cx, cy = (x + bw / 2) / w, (y + bh / 2) / h nw, nh = bw / w, bh / h # 越界裁剪,防止归一化后超出 0~1 导致训练报错 cx, cy = min(max(cx, 0), 1), min(max(cy, 0), 1) nw, nh = min(nw, 1), min(nh, 1) line = f"0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n" txt = os.path.join(out_dir, os.path.splitext(im['file_name'])[0] + ".txt") with open(txt, 'a') as f: f.write(line)参数上,threshold控制去重强度,牧羊场景背景单一可以调到 8 更激进;fps=2是经验值,羊群移动慢可以降到 1,快速奔跑的牧羊犬场景要提到 5。标注阶段建议每张图不超过 30 个框,太密的目标在 640 输入下会严重粘连,后面训练时再想拆开就难了。
2.3 数据集目录结构与 data.yaml 写法
YOLOv8 对目录结构有固定要求,images 和 labels 必须平行,且文件名一一对应。我一般这样组织:
sheep_dataset/ images/ train/ val/ test/ labels/ train/ val/ test/ data.yamldata.yaml 里写清路径和类别名,注意path用绝对路径最稳,相对路径在不同工作目录下启动训练时经常找不到文件。
path: /data/sheep_dataset train: images/train val: images/val test: images/test nc: 1 names: ['sheep']nc是类别数,牧羊只检测羊就是 1。如果还想区分羊和牧羊犬,改成 2 并在 names 里按索引顺序写。写完用一行命令快速验证标签有没有越界或空文件:
# 检查是否存在空标签文件(无目标的图会被跳过,但空文件要警惕) find labels -name "*.txt" -size 0 | head3. 用 YOLOv8 训练牧羊检测模型:参数怎么设、曲线怎么看
3.1 环境搭建与模型选型
环境这块,Ubuntu 20.04 上装 CPU 版和 GPU 版差别很大。有 NVIDIA 显卡就走 CUDA,没有就用 CPU 版先跑通流程。装 ultralytics 一条命令,但要注意 Python 版本别太新,3.8 到 3.10 最稳。
# 创建虚拟环境并安装,CPU 版直接 pip 即可 python -m venv yolo_env && source yolo_env/bin/activate pip install ultralytics # 验证安装与版本 yolo checks模型选型上,航拍小目标优先考虑 YOLOv8s 或 YOLOv8m,n 版虽然快但小目标召回明显吃亏,x 版在单卡 1660Ti 上训练慢到怀疑人生。输入尺寸从默认 640 提到 960 或 1280 对小目标帮助很大,代价是显存和训练时间成倍涨。我一般先用 640 跑通全流程确认数据没问题,再切 960 正式训。
3.2 训练命令与关键参数逐条解释
训练用命令行或 Python 脚本都行,我习惯写脚本方便记录参数。下面这份是牧羊场景调过的配置。
from ultralytics import YOLO # 加载预训练权重,迁移学习比从头训收敛快得多 model = YOLO("yolov8s.pt") model.train( data="sheep_dataset/data.yaml", epochs=150, # 牧羊数据量通常几千张,150 轮够收敛 imgsz=960, # 小目标关键参数,640 会丢大量羊只 batch=8, # 1660Ti 6G 显存下 960 输入只能开到 8 device=0, # 0 表示第一块 GPU,CPU 训练写 'cpu' workers=4, # 数据加载线程,太多会抢内存 lr0=0.01, # 初始学习率,迁移学习常用 0.01 lrf=0.01, # 最终学习率 = lr0 * lrf momentum=0.937, weight_decay=0.0005, warmup_epochs=3, # 前 3 轮预热,防止初期梯度爆炸 patience=30, # 30 轮无提升就早停,省时间 augment=True, # 开启内置增强 mosaic=1.0, # 马赛克增强,对密集小目标有效 close_mosaic=10, # 最后 10 轮关闭 mosaic,稳定收敛 project="runs/sheep", name="v8s_960", )imgsz是牧羊场景最该动的参数,从 640 提到 960 通常能把小目标 mAP 拉高 5 到 10 个点。mosaic增强把四张图拼一张,能显著增加小目标出现频率,但训练末期必须关掉,否则框的位置会飘。patience设 30 是防止过拟合后白跑,验证指标连续 30 轮不涨就停。batch要根据显存调,爆显存就减半,别硬撑。
3.3 损失曲线和指标怎么读
训练完在 runs 目录下有 results.csv 和曲线图。重点看三件事:box_loss 是否稳定下降、mAP50 是否还在涨、有没有过拟合。画曲线用下面这段,把训练和验证损失放一起对比。
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/sheep/v8s_960/results.csv") df.columns = [c.strip() for c in df.columns] plt.plot(df['epoch'], df['train/box_loss'], label='train') plt.plot(df['epoch'], df['val/box_loss'], label='val') plt.xlabel('epoch'); plt.ylabel('box_loss'); plt.legend() plt.savefig("loss_curve.png")如果 train 损失一直降而 val 损失开始抬头,就是过拟合,加数据或加增强。如果两条都居高不下,多半是学习率太大或标签有问题。mAP50 在牧羊场景能到 0.85 以上算不错,mAP50-95 通常低不少,因为小目标定位精度天然吃亏,别拿它跟地面拍摄的指标比。
4. 推理部署与航拍视频流接入的避坑清单
4.1 从单张图推理到视频流实时检测
训练完先用单张图验证,再上视频。单张推理一行就够:
from ultralytics import YOLO model = YOLO("runs/sheep/v8s_960/weights/best.pt") # conf 置信度阈值,iou 是 NMS 的重叠阈值 results = model.predict("test.jpg", conf=0.35, iou=0.5, imgsz=960) results[0].save("out.jpg")视频流用 OpenCV 逐帧读,注意航拍视频分辨率高,直接喂原图会拖慢推理,先缩放到模型输入尺寸附近再送进去。实时性要求高就上 TensorRT 或 ONNX Runtime,RK3588 这类边缘板子部署 YOLOv8 也是常见路线,但模型转换和量化是另一个话题,这里先把 PC 端跑通。
4.2 航拍牧羊检测的五个高频翻车点
现象一:模型把白云、白石头当成羊。原因是背景里存在与羊颜色相近的干扰物,训练集没覆盖。解决是补采含这类干扰的负样本,或者在 data.yaml 里加一个 background 类专门吸收误检,再重训。
现象二:羊群密集时框大量重叠、计数偏多。原因是 NMS 的 iou 阈值设太高,重叠框没被抑制。解决是把推理时 iou 从 0.7 降到 0.5 甚至 0.45,密集场景还可以换 soft-NMS,但 ultralytics 默认没开,需要自己改后处理。
现象三:早晚逆光时召回骤降。原因是训练集光照分布不均,模型没见过强逆光。解决是抽帧时按时间段均衡采样,训练时开 HSV 增强,把 hsv_v 调大模拟明暗变化。
现象四:验证 mAP 很高但实际视频漏检严重。原因是训练验证集来自同一段视频,分布太像,指标虚高。解决是严格按视频划分集合,测试集必须来自没参与训练的架次。
现象五:训练到一半 loss 变 NaN。原因是学习率过大或某批标签坐标异常。解决是先把 lr0 降到 0.001 重跑,同时用前面的校验脚本扫一遍标签,把越界和宽高为 0 的框清掉。
注意:航拍数据标注时,被遮挡超过 70% 的羊建议标为忽略区域而不是硬标框,否则模型会学到一堆不完整目标,推理时反而更乱。
5. 把牧羊识别做稳的一个进阶技巧:分块推理加跨帧投票
单帧检测在密集羊群上总会有抖动,同一只羊这帧框到、下帧丢了,直接拿去做计数会跳变。我后来固定用一套组合拳:先把 4K 航拍图切成带重叠的 2x2 小块分别推理,再把结果映射回原图做全局 NMS,最后对连续 5 帧的检测框做一次简单投票,只有出现 3 帧以上的框才保留。分块推理解决的是小目标在整图缩放后像素太少的问题,跨帧投票解决的是单帧漏检和误检。切块时重叠比例设 0.2,太小会在切缝处丢目标,太大则重复框增多拖慢后处理。
import cv2, numpy as np from ultralytics import YOLO model = YOLO("best.pt") def tiled_infer(img, tile=2, overlap=0.2): h, w = img.shape[:2] th, tw = h // tile, w // tile boxes = [] for i in range(tile): for j in range(tile): y0 = max(0, int(i * th - th * overlap)) x0 = max(0, int(j * tw - tw * overlap)) y1 = min(h, int((i + 1) * th + th * overlap)) x1 = min(w, int((j + 1) * tw + tw * overlap)) crop = img[y0:y1, x0:x1] r = model.predict(crop, conf=0.3, imgsz=960, verbose=False)[0] for b in r.boxes.xyxy.cpu().numpy(): # 把局部坐标加回原图偏移 boxes.append([b[0] + x0, b[1] + y0, b[2] + x0, b[3] + y0]) return boxes这段只做了分块和坐标还原,跨帧投票需要维护一个历史框列表,用 IoU 匹配当前帧和过去帧的框,命中次数达标的才输出。参数上tile=2适合 4K 图,1080p 用 1 就行;overlap=0.2是平衡速度和召回的经验值。验证这套是否有效,别只看 mAP,直接拿一段没训过的视频跑,人工数一遍漏检和误检,比任何指标都直观。我踩过的最大坑是切块后忘了加偏移,框全挤在左上角,排查了半天才发现是坐标没还原。做视觉这行,玄学少,低级错误多,写完先拿一张图把框画出来看一眼,能省掉大量后悔药。希望帮到你。
本文还有配套的精品资源,点击获取