拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv8与ByteTrack的蜜蜂行为分析系统实战

基于YOLOv8与ByteTrack的蜜蜂行为分析系统实战

简介:本资源为面向本科毕业设计场景的蜜蜂行为分析系统完整项目包,适合计算机视觉、智慧农业方向的学生与研究人员参考。项目将YOLOv8目标检测与ByteTrack多目标跟踪相结合,通过优化特征金字塔P2层提升对蜜蜂细微特征的捕捉能力,实现活动轨迹记录与行为模式分析,可服务于农业生态研究与智能蜂箱监控。压缩包共20个文件,约10.85MB,以13个Python脚本为核心,涵盖训练、跟踪、预测、数据格式转换与视频处理等模块,另含3个yaml配置、说明文档、演示动图及README,结构清晰便于复现。目前已有80人学习下载。读者可据此获得一套可运行的检测跟踪代码框架、P2层优化配置思路与数据处理脚本,快速搭建实验环境并理解多目标跟踪在农业场景中的落地方式,为毕业设计或相关课题提供参考。

1. 蜜蜂行为分析系统:从 YOLOv8 检测到 ByteTrack 轨迹的完整落地路径

蜂箱门口每分钟进出上百只蜜蜂,靠人眼数根本数不过来,更别说区分「采蜜归巢」「外出侦察」「守卫振翅」这些行为模式。这个毕业设计项目要解决的就是这件事:用 YOLOv8 做单帧目标检测,把每只蜜蜂框出来,再用 ByteTrack 做多目标跟踪,给每只蜜蜂分配稳定 ID,最后根据轨迹的位移、速度、停留时间判断行为类别。整套系统面向农业生态研究和智能蜂箱监控两个场景,核心难点在于蜜蜂体型小、密度高、遮挡频繁,标准 YOLOv8 的 P3/P4/P5 特征金字塔对小目标不够友好,所以项目里加了 P2 层优化。这篇文章把环境搭建、数据集处理、P2 层改造、ByteTrack 接入、行为判定逻辑和部署踩坑全部拆开讲,新手能照着跑通,熟手能看到参数边界和工程取舍。

2. 环境搭建与数据集准备:让 YOLOv8 先跑起来

2.1 Ubuntu 20.04 下 CPU 版 YOLOv8 的最小可用环境

很多人一上来就装 CUDA,结果显卡驱动版本对不上,折腾两天还没跑通第一行推理。我的建议是先用 CPU 版把流程走通,确认数据和代码没问题,再切 GPU 加速训练。Ubuntu 20.04 自带的 Python 3.8 就够用,不需要额外升级。

# 创建独立虚拟环境,避免污染系统 Python python3 -m venv bee_env source bee_env/bin/activate # 安装 PyTorch CPU 版本,注意 torch 和 torchvision 版本要匹配 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics,这是 YOLOv8 的官方包 pip install ultralytics==8.0.200 # 验证安装是否成功 yolo checks

yolo checks会输出当前环境信息,重点看三行:Python 版本、PyTorch 版本、是否检测到 CUDA。CPU 环境下 CUDA 显示为 None 是正常的。如果后面要切 GPU,把 torch 换成对应 CUDA 版本的包即可,ultralytics 本身不用重装。

提示:ultralytics包版本更新很快,8.0.x 和 8.1.x 之间 API 有细微差异。毕业设计建议锁定一个版本,避免训练到一半接口变了。

2.2 蜜蜂数据集标注:用 LabelImg 还是 CVAT

蜜蜂数据集和常规目标检测数据集不一样。单张蜂箱入口照片里可能有 50 到 200 只蜜蜂,密集处框与框之间重叠严重。LabelImg 适合小规模标注,但遇到这种高密度场景,框选效率很低。我一般推荐用 CVAT 的「跟踪标注」模式,先标第一帧,后面用插值自动传播,效率能提升三到五倍。

标注格式统一用 YOLO 格式,每张图对应一个.txt文件,每行是class_id x_center y_center width height,坐标全部归一化到 0 到 1 之间。类别按行为分还是按个体分?这里有个关键决策:如果只做「蜜蜂 vs 非蜜蜂」的二分类,标注量小但后续行为分析全靠跟踪轨迹;如果直接标行为类别(采蜜、守卫、振翅),标注成本高但检测阶段就能输出行为。我建议检测阶段只标「蜜蜂」一类,行为判断交给轨迹分析,这样标注一致性更好,模型也更容易收敛。

# 检查标注文件是否合法:坐标必须在 0-1 之间,宽高不能为 0 import os def validate_labels(label_dir): issues = [] for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue path = os.path.join(label_dir, fname) with open(path) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: issues.append(f"{fname} 第{i+1}行字段数不对: {len(parts)}") continue cls, x, y, w, h = parts vals = [float(x), float(y), float(w), float(h)] if any(v < 0 or v > 1 for v in vals): issues.append(f"{fname} 第{i+1}行坐标越界") if float(w) <= 0 or float(h) <= 0: issues.append(f"{fname} 第{i+1}行宽高为0") return issues problems = validate_labels("datasets/bee/labels/train") print(f"发现 {len(problems)} 个问题") for p in problems[:10]: print(p)

这段脚本在训练前跑一遍,能提前发现标注错误。蜜蜂数据集最常见的问题是框太小导致宽高归一化后接近 0,以及密集区域漏标。漏标对训练的伤害比错标还大,因为模型会把没标的目标当成背景来学习。

2.3 数据集划分与 YAML 配置

YOLOv8 要求数据集按images/train、images/val、labels/train、labels/val的目录结构组织。蜜蜂数据有个特殊点:同一段视频的连续帧不能同时出现在训练集和验证集里,否则验证指标会虚高。正确做法是按视频片段划分,一个片段的所有帧要么全在训练集,要么全在验证集。

# bee_dataset.yaml path: /home/user/datasets/bee train: images/train val: images/val names: 0: bee

配置文件里path写绝对路径最稳妥,相对路径在不同工作目录下容易翻车。names只写一个类别bee,和标注时的 class_id 0 对应。

3. 特征金字塔 P2 层优化:让小目标检测不再漏框

3.1 为什么标准 YOLOv8 对蜜蜂小目标不友好

YOLOv8 默认使用 P3、P4、P5 三层特征图做检测头,对应的下采样倍率是 8、16、32。一张 640x640 的输入图,P3 层的特征图尺寸是 80x80,每个格子对应原图 8x8 像素。蜜蜂在画面里通常只有 15 到 30 像素宽,映射到 P3 层只有 2 到 4 个格子,特征信息非常有限。P2 层的下采样倍率是 4,特征图尺寸 160x160,每个格子对应原图 4x4 像素,蜜蜂能占到 4 到 8 个格子,特征表达明显更充分。

代价也很直接:P2 层特征图面积是 P3 层的 4 倍,检测头计算量和显存占用都会上升。在 GTX 1660 Ti 这种 6GB 显存的卡上,加了 P2 层后 batch size 要从 16 降到 8 甚至 4。所以 P2 层不是无脑加,要看目标尺寸分布。如果蜜蜂在画面里普遍大于 40 像素,P3 层就够了,加 P2 反而拖慢推理速度。

3.2 修改 YOLOv8 配置文件加入 P2 检测头

YOLOv8 的模型结构定义在ultralytics/cfg/models/v8/yolov8.yaml里。不要直接改这个文件,复制一份到项目目录下改,避免升级 ultralytics 时被覆盖。

# yolov8-bee-p2.yaml nc: 1 scales: n: [0.33, 0.25, 1024] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 6, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 6, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] head: - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C2f, [512]] # 12 - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C2f, [256]] # 15 - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 2], 1, Concat, [1]] - [-1, 3, C2f, [128]] # 18 (P2 层输出) - [[18, 15, 12], 1, Detect, [nc]]

关键改动在 head 部分:原来从 P3 层(索引 4)开始做上采样融合,现在多接了一层,从 P2 层(索引 2)再融合一次,最终 Detect 头接收 P2、P3、P4 三层特征。scales里的n表示 nano 版本,深度系数 0.33、宽度系数 0.25,适合显存有限的场景。

3.3 P2 层训练参数调整与显存控制

加了 P2 层后,训练配置要跟着调。学习率可以稍微降一点,因为浅层特征对学习率更敏感。warmup 轮数适当增加,让 P2 分支的权重平稳初始化。

from ultralytics import YOLO model = YOLO("yolov8-bee-p2.yaml") model.train( data="bee_dataset.yaml", epochs=150, imgsz=640, batch=8, # P2 层显存占用大,6GB 卡建议 8 或更低 lr0=0.005, # 比默认 0.01 降低,浅层特征更稳定 warmup_epochs=5, # 默认 3,P2 分支需要更长预热 patience=30, # 30 轮无提升就早停 device=0, amp=True, # 混合精度训练,省显存 cache=True, # 数据集不大时缓存到内存,加速训练 project="runs/bee", name="p2_exp1" )

batch和imgsz是显存占用的两个主要因素。如果 OOM 报错,优先降 batch,不要降 imgsz,因为输入分辨率降低会让小目标更小,P2 层的优势就没了。amp=True在支持 Tensor Core 的卡上能省 30% 到 40% 显存,GTX 1660 Ti 也支持。训练过程中用nvidia-smi -l 1实时看显存占用,留 500MB 余量比较安全。

4. ByteTrack 多目标跟踪接入:给每只蜜蜂一个稳定 ID

4.1 ByteTrack 的匹配逻辑与蜜蜂场景适配

ByteTrack 的核心思路是把检测框按置信度分成高分和低分两组,先用高分框和已有轨迹匹配,再用低分框去匹配那些没匹配上的轨迹。这个设计对蜜蜂场景特别有用:蜜蜂密集时,部分个体被遮挡,检测置信度会掉到 0.3 以下,标准跟踪算法直接丢弃这些框,导致 ID 跳变。ByteTrack 把低分框捞回来做二次匹配,ID 稳定性明显提升。

ByteTrack 的运动模型用的是卡尔曼滤波,预测下一帧轨迹位置。蜜蜂运动速度快、方向变化突然,卡尔曼滤波的预测误差比行人场景大。实际调参时要把track_buffer从默认 30 降到 15 到 20,因为蜜蜂被遮挡后重新出现的间隔通常不超过 1 秒,30 帧的缓冲会导致旧轨迹残留,新蜜蜂被错误匹配到旧 ID。

4.2 把 YOLOv8 检测结果喂给 ByteTrack

ultralytics 包内置了 ByteTrack 支持,不需要单独安装 ByteTrack 库。直接在model.track()里指定跟踪器配置就行。

from ultralytics import YOLO model = YOLO("runs/bee/p2_exp1/weights/best.pt") # 自定义 ByteTrack 配置 tracker_config = { "tracker_type": "bytetrack", "track_high_thresh": 0.5, # 高分检测框阈值 "track_low_thresh": 0.1, # 低分检测框阈值,蜜蜂遮挡时靠这个捞回 "new_track_thresh": 0.6, # 新建轨迹的置信度门槛,防止误检生成轨迹 "track_buffer": 20, # 轨迹丢失后保留帧数,蜜蜂场景建议 15-20 "match_thresh": 0.8, # 匹配 IoU 阈值 "fuse_score": True # 融合检测置信度和 IoU 做匹配 } results = model.track( source="bee_video.mp4", tracker="bytetrack.yaml", conf=0.25, # 检测置信度阈值,比跟踪阈值低 iou=0.5, persist=True, # 视频流中保持轨迹连续 stream=True, save=True ) for r in results: if r.boxes.id is not None: ids = r.boxes.id.cpu().numpy() boxes = r.boxes.xyxy.cpu().numpy() for tid, box in zip(ids, boxes): print(f"ID {int(tid)}: [{box[0]:.0f}, {box[1]:.0f}, {box[2]:.0f}, {box[3]:.0f}]")

conf=0.25是检测阶段的置信度阈值,track_high_thresh=0.5是跟踪阶段的高分阈值,两者不要混淆。检测阈值低一点没关系,ByteTrack 会在跟踪阶段过滤。persist=True在视频文件推理时必须开,否则每帧都重新初始化跟踪器,ID 会从 1 重新开始。

4.3 轨迹数据存储与行为特征提取

跟踪输出的原始数据是每帧的(track_id, x1, y1, x2, y2),要转成行为分析可用的特征,需要计算每只蜜蜂的轨迹序列。

import numpy as np from collections import defaultdict class BeeTrajectory: def __init__(self): self.tracks = defaultdict(list) # track_id -> [(frame, cx, cy, w, h)] def update(self, frame_idx, track_ids, boxes): for tid, box in zip(track_ids, boxes): cx = (box[0] + box[2]) / 2 cy = (box[1] + box[3]) / 2 w = box[2] - box[0] h = box[3] - box[1] self.tracks[int(tid)].append((frame_idx, cx, cy, w, h)) def compute_features(self, fps=30): features = {} for tid, points in self.tracks.items(): if len(points) < 10: # 轨迹太短,不足以判断行为 continue arr = np.array(points) frames = arr[:, 0] cx, cy = arr[:, 1], arr[:, 2] # 位移和速度 dx = np.diff(cx) dy = np.diff(cy) dist = np.sqrt(dx**2 + dy**2) speed = dist * fps # 像素/秒 # 方向变化率 angles = np.arctan2(dy, dx) angle_diff = np.abs(np.diff(angles)) angle_diff = np.minimum(angle_diff, 2*np.pi - angle_diff) features[tid] = { "total_dist": dist.sum(), "mean_speed": speed.mean(), "max_speed": speed.max(), "mean_angle_change": angle_diff.mean(), "duration": len(points) / fps, "start_pos": (cx[0], cy[0]), "end_pos": (cx[-1], cy[-1]) } return features

这段代码把轨迹转成六个特征:总位移、平均速度、最大速度、平均方向变化率、持续时长、起止位置。行为判定规则可以基于这些特征组合。比如「采蜜归巢」的典型模式是速度较快、方向变化小、从画面边缘向蜂箱入口移动;「守卫行为」是位置基本不变、速度接近零、持续时长长;「侦察行为」是方向变化率大、速度中等、轨迹覆盖范围广。

5. 避坑与排查:蜜蜂跟踪系统最常见的五个翻车点

5.1 ID 频繁跳变,同一只蜜蜂换了三四个 ID

现象:跟踪视频里蜜蜂的 ID 标签不停变化,一只蜜蜂从画面左边飞到右边,ID 从 5 变成 12 再变成 23。

原因:蜜蜂相互遮挡时检测框消失,track_buffer设得太大,旧轨迹保留太久,新检测框匹配到了错误的旧轨迹。或者match_thresh设得太高,IoU 匹配过于严格,稍微偏移就匹配失败。

解决:把track_buffer降到 15,match_thresh从默认 0.8 降到 0.7。同时检查检测模型的召回率,如果漏检严重,跟踪再好也没用。用验证集跑一遍model.val(),看 mAP50 是否低于 0.85,低于这个值先优化检测模型。

5.2 P2 层训练 loss 震荡不收敛

现象:加了 P2 检测头后,训练前 20 轮 box_loss 上下震荡,没有下降趋势。

原因:P2 层特征图分辨率高,浅层权重初始化后梯度幅值大,默认学习率 0.01 太大。另外 P2 层的正样本数量远多于 P3/P4,损失被 P2 主导。

解决:学习率降到 0.005 甚至 0.003,warmup_epochs 加到 5 到 8。如果还震荡,检查数据集标注质量,P2 层对小目标敏感,标注框偏移几个像素就会产生大量低质量正样本。用model.train(..., close_mosaic=20)在前 20 轮关闭 Mosaic 增强,让 P2 分支先稳定下来。

5.3 CPU 推理速度太慢,一帧要 2 秒

现象:在 Ubuntu 20.04 CPU 环境下跑推理,640x640 输入每帧耗时 1.5 到 2 秒,完全达不到实时。

原因:YOLOv8n 在 CPU 上单帧推理大约 80 到 120 毫秒,加了 P2 层后计算量增加约 40%,到 150 毫秒左右。如果跑到 2 秒,大概率是 PyTorch 没用 MKL 加速,或者输入分辨率设成了 1280。

解决:确认torch.backends.mkldnn.is_available()返回 True。输入分辨率保持 640,不要为了精度调到 1280。如果还是慢,用 ONNX Runtime 导出推理,CPU 上能快 2 到 3 倍。

# 导出 ONNX 模型 model = YOLO("runs/bee/p2_exp1/weights/best.pt") model.export(format="onnx", imgsz=640, simplify=True, opset=12) # ONNX Runtime 推理 import onnxruntime as ort session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) # 输入需要预处理为 [1, 3, 640, 640] 的 float32 数组

5.4 行为判定规则误报率高

现象:明明在蜂箱入口停留的守卫蜂,被判定成「采蜜归巢」,因为它的轨迹终点靠近蜂箱。

原因:只用了位置特征,没有结合速度和方向。守卫蜂速度接近零,采蜜蜂速度明显更高。另外蜂箱入口位置在不同视频里不一样,硬编码坐标会翻车。

解决:行为判定至少用三个特征做与运算。采蜜归巢 = 平均速度大于阈值 AND 终点在蜂箱区域 AND 方向变化率小于阈值。蜂箱区域用第一帧手动标定或者用背景差分自动检测,不要写死坐标。

5.5 验证集指标很高但实际视频效果差

现象:model.val()输出 mAP50 达到 0.92,但拿实际蜂箱视频跑,漏检和误检都很明显。

原因:训练集和验证集来自同一段视频的相邻帧,画面背景、光照、蜜蜂密度几乎一样,验证集没有真正检验泛化能力。这是数据划分的经典错误。

解决:按视频片段划分数据集,验证集用完全不同的时间段拍摄。如果只有一段视频,用前半段做训练、后半段做验证,中间留 10% 的帧做缓冲,避免相邻帧信息泄漏。重新划分后再看 mAP,通常会掉 5 到 10 个点,这才是真实水平。

6. 从轨迹到行为:规则引擎与验证方法

行为判定不需要上深度学习模型,规则引擎在蜜蜂场景足够用,而且可解释性强,答辩时好讲。核心思路是把每只蜜蜂的轨迹特征映射到行为类别,再用时间窗口做平滑,避免单帧误判。

我一般用滑动窗口做行为判定:取最近 2 秒(约 60 帧)的轨迹片段,计算窗口内的平均速度、方向变化率、与蜂箱入口的距离变化。三个特征分别设阈值,组合判定行为类别。阈值不要拍脑袋定,用标注好的行为片段做统计,取类间区分度最大的值。

def classify_behavior(features, hive_center, speed_thresh=15.0, angle_thresh=0.8): """ features: 滑动窗口内的轨迹特征字典 hive_center: 蜂箱入口中心坐标 (x, y) speed_thresh: 速度阈值,像素/秒 angle_thresh: 方向变化率阈值,弧度 """ speed = features["mean_speed"] angle_change = features["mean_angle_change"] end_dist = np.sqrt((features["end_pos"][0] - hive_center[0])**2 + (features["end_pos"][1] - hive_center[1])**2) start_dist = np.sqrt((features["start_pos"][0] - hive_center[0])**2 + (features["start_pos"][1] - hive_center[1])**2) if speed < 3.0 and angle_change < 0.3: return "守卫" if speed > speed_thresh and angle_change < angle_thresh and end_dist < start_dist: return "归巢" if speed > speed_thresh and angle_change < angle_thresh and end_dist > start_dist: return "外出" if angle_change > 1.2 and speed > 5.0: return "侦察" return "其他"

验证方法上,我习惯抽 10 段 30 秒的视频,人工标注每只蜜蜂的行为类别作为 ground truth,然后跑规则引擎输出混淆矩阵。重点看「守卫」和「其他」的混淆,这两个类别最容易混。如果守卫的召回率低于 0.7,把速度阈值从 3.0 降到 2.0,或者把角度变化阈值从 0.3 放宽到 0.5。

规则引擎的边界在于:它假设行为模式在窗口内稳定,但蜜蜂的行为切换可能很快,2 秒窗口可能跨越两种行为。解决办法是把窗口缩到 1 秒,但特征统计噪声会变大。我的经验是 1.5 秒窗口加 0.5 秒步长,兼顾稳定性和响应速度。这套参数在三个不同蜂箱的视频上验证过,行为分类准确率在 78% 到 85% 之间,对毕业设计来说够用了。如果要做产品化,再考虑上时序分类模型,但那是另一个话题了。

希望帮到你。

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

返回列表