恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于YOLOv8与ByteTrack的蜂鸟识别系统设计与实践
首页
资讯中心
/
基于YOLOv8与ByteTrack的蜂鸟识别系统设计与实践
基于YOLOv8与ByteTrack的蜂鸟识别系统设计与实践
发布时间:2026/9/20 5:55:02
先说结论colibri是我前前后后折腾了两周多才跑通的一套蜂鸟识别系统核心功能就三个从视频流里把蜂鸟框出来、跨帧跟踪它、再按行为分成“悬停”“飞行”“吸蜜”三类。项目名字取的是西班牙语“蜂鸟”的意思主要场景是架在院子里或阳台上对着花丛自动记录蜂鸟来访的时间、时长和吸蜜次数。做这个项目的直接原因很简单我家阳台种了几盆吊钟花蜂鸟几乎每天固定时段都会来一开始拿手机想抓拍结果它停留时间太短等我把相机对准它已经走了。后来干脆装了个固定摄像头再利用目标检测模型来做一个“只盯着蜂鸟看”的视觉系统省得一直手动蹲守。做完整套流程之后不光解决了我的观测需求也把目标检测、目标跟踪、边缘端部署这些常用的视觉技术栈从头到尾摸了一遍所以这篇主要把整个设计和实操过程整理出来给也想做类似小项目的朋友一个能直接参考的路线。1. 项目全貌这个叫 colibri 的东西到底在做什么1.1 项目缘起与命名逻辑先说命名。colibri 这个词在西语里就是蜂鸟法语里也这么拼。我起这个名字不是为了装文艺而是因为它把蜂鸟的三个特点概括得很准体型小、速度快、识别难度大。这也正好是这个项目要解决的三个问题所以整个视觉方案的设计都是围绕这三点展开的。项目最开始其实就一个想法能不能让摄像头自己识别蜂鸟并且自动记录下来市面上的鸟类相机我也看过有的确实能触发拍摄但基本都是红外或运动传感器拍一堆风吹草动的空镜头回来真正想要的蜂鸟吸蜜画面反而经常漏掉。与其靠传感器触发不如直接上视觉识别让模型判断“画面里有没有蜂鸟”再决定要不要记录。这就是 colibri 项目的起点。整系统做的事可以概括成一句话输入视频流输出蜂鸟的访问记录。拆开就是下面这几个环节。检测每一帧图像里有没有蜂鸟有的话给出位置框。跟踪同一只蜂鸟在连续帧里保持同一个 ID不乱跳。行为分类根据位置和动作状态判断它当前是在悬停、飞行还是吸蜜。记录把每次出现的时间、停留时长、行为结果写入日志文件。1.2 这套系统能解决什么问题我实测下来这套系统最大的价值是“不用人盯着也能积累数据”。以前想看蜂鸟得搬个小凳子坐阳台上看到它来了就赶紧拍。现在摄像头固定好以后只要模型跑着蜂鸟什么时候来、来了多久、在哪盆花上吸蜜都会自动记下来。我晚上回来翻日志就知道今天有几次访客、集中在什么时段比纯靠感觉去等要准确得多。这套系统适合两类人来参考。一类是和我一样想观测自家院子里鸟类的爱好者不一定非要蜂鸟换成麻雀、绣眼鸟、黄腰柳莺之类的小鸟也完全可以另一类是想入门目标检测和目标跟踪的开发者这个项目把从数据标注、模型训练到边缘端部署的完整链路都走了一遍我自己就是在这个过程中从只会调用现成 API到能独立把一套推理系统跑在边缘设备上。1.3 技术栈速览项目主要的技术选型如下检测模型YOLOv8n使用 ultralytics 库进行训练和推理。跟踪模块ByteTrack基于检测框做跨帧关联轻量且不需要额外训练。行为分类基于跟踪轨迹的形态学判断不需要单独的神经网络。部署环境Jetson Nano 上跑 ONNX Runtime普通 PC 上直接跑 PyTorch。日志存储CSV 文件每条记录一行方便后续用 Excel 或脚本做统计。后面几个章节会逐个讲清楚为什么选这些方案以及具体怎么落地。2. 整体设计与方案选型为什么用“检测跟踪”而不是一个大模型全干完2.1 先盘算需求再选方案任何项目在动手之前先想清楚跑在什么环境、满足什么场景不然很容易选错方向。我做 colibri 之前列了几个硬性约束。实时性要求高。蜂鸟在花丛里停留的时间通常只有几秒悬停的时候还会快速移动头部系统如果做不到接近实时的推理很可能漏掉关键动作。部署设备性能有限。我打算把系统放在阳台角落长期运行不会接一台游戏电脑在那儿晒着所以推理必须能在树莓派或 Jetson 这类小设备上跑得动。数据量不大。蜂鸟的公开数据集很少我自己攒的数据也就大几百张这种量级撑不起大规模预训练再微调的成本也不适合折腾特别复杂的模型。基于这三点方案基本上就被限定在轻量目标检测模型加一个轻量目标跟踪器。整体架构也简单摄像头出帧模型出框跟踪器出 ID规则判断行为最后写日志。2.2 为什么选 YOLOv8n 而不是更大更强的模型目标检测的模型选择很多从 R-CNN 系到 YOLO 系再到 DETR 系都有。我这边最后选了 YOLOv8n不选那些精度更高的大模型理由很简单蜂鸟太小、飞得太快模型太大根本跑不动实时推理。对比一下常用模型模型参数量输入尺寸 640 时的推理速度Jetson Nano备注YOLOv8n约 3.2M约 30 FPS体积小、速度快精度对单类检测足够YOLOv8s约 11.2M约 15 FPS精度略高但小设备上实时性压力大YOLOv5s约 7.2M约 18 FPS也可以但生态和工具链不如 v8 新Faster R-CNN约 41M低于 5 FPS精度高但太重不满足实时这里有一点要特别说蜂鸟检测不是那种需要“区分几十个细分类别”的任务我只需要判断“有蜂鸟”和“没蜂鸟”单类检测对模型能力的要求没那么高所以完全没必要为了那一点精度上涨去牺牲速度。YOLOv8n 的优势就在这个场景里很清晰模型小容易部署dfl 回归出的框只要置信度阈值调得合适识别效果就够用。ultralytics 的生态也帮了大忙。它对新手非常友好训练脚本三五行就能跑起来导出 ONNX、TensorRT 引擎也都是内置命令不用自己搭一堆编译环境。2.3 为什么用 ByteTrack 做跨帧跟踪检测模型只能输出当前这一帧的框但“这一帧里这只鸟和上一帧那只鸟是不是同一只”这种问题检测模型自己回答不了。所以要加一个跟踪器。我选了 ByteTrack主要原因有两个。第一它靠检测框之间的 IoU 做匹配不需要额外训练一个 ReID 模型这对数据量小的项目来说是巨大的成本节省。你不需要专门收集同一只鸟在不同角度的训练数据。第二ByteTrack 相比老牌的 SORT 和 DeepSORT多了一个处理低分检测框的策略。蜂鸟飞行速度快有时候检测模型给框的置信度很低SORT 会直接丢掉这些低分框导致跟踪 ID 断掉ByteTrack 会把低分框也拿来做二次匹配ID 连续性会好很多。实测下来蜂鸟快速飞过画面时ID 跳变的次数确实少了一大截。跟踪这一层我不建议自己造轮子。网上有很多简化版的 SORT 实现但实际测试下来会碰到匹配阈值不好调、ID 频繁切换的问题与其花时间调这些不如直接用 ByteTrack 的现成实现。2.4 行为分类为什么不单独训练一个模型蜂鸟的行为看着复杂但放在“摄像头固定不动”的场景里看其实很好分类。摄像头不动背景就基本不变目标的行为主要由它在画面里的运动模式决定。悬停蜂鸟在花朵前方静止或轻微位移翅膀快速扇动。飞行从一朵花飞到另一朵花中心点位移大。吸蜜蜂鸟把嘴伸入花冠头部会有一个明显的“探进去”的动作同时停驻时间较长。这些行为用跟踪轨迹上的位置变化和 bbox 形态变化就能区分开再用规则判断一下比再训练一个动作分类模型简单可靠得多。也正因如此colibri 的整体系统才不需要跑一个很大的视频理解模型实时性才能有保障。3. 核心细节解析与实操要点数据、标注、训练3.1 数据集怎么做才算够用这个项目里最花时间、也最容易决定最终效果的不是模型结构而是数据集。我一开始天真地以为随便找几百张带蜂鸟的图片就能训后来发现检测框质量、图片背景分布、姿态多样性对结果的影响非常大。我最后用的是三部分数据拼起来公开数据源iNaturalist 和 Caltech Birds 里筛了一批 CC 协议的蜂鸟图片大概 200 多张主要解决“多视角、多品种”的问题。自己拍摄摄像头架在阳台之后连续录了几天视频截出包含蜂鸟的帧大概 400 多张。这一步是效果提升最大的一步因为自家阳台的光线、背景、花盆角度才是真正要部署的环境。网上找的蜂鸟特写图主要是高分辨率图片用来测试模型在小目标上的表现。三部分合起来大概有 700 多张图。对单类检测来说这个量级不大但足够用。如果目标种类多比如同时识别好几种鸟那至少得翻到两三千张才靠谱。数据一定要覆盖常见的困难场景。我踩过一个大坑训练数据里蜂鸟基本都是侧面或悬停姿态结果部署之后蜂鸟正对着镜头飞过来的时候模型很多时候检测不到。后来专门从视频里把这些“正面冲刺”的帧挑了一批出来补进训练集这个问题才明显改善。3.2 标注时的几个细节标注工具我用的是 X-AnyLabeling因为可以直接加载 YOLO 格式导出的数据集界面也顺手。网上很多教程推荐 LabelImg那个也完全可以用只是新项目里 X-AnyLabeling 支持自动分割和半自动标注效率更高。标注时有三个经验值得记下来。框一定要贴着蜂鸟的身体但是不要把翅膀的残影框进去。蜂鸟扇翅膀频率高30fps 视频里两帧之间翅膀位置差别很大残影面积有时候比身体还大框大了模型会学到错误的目标范围。不要把花和蜂鸟一起框进去。有些图里蜂鸟正对着花苞鸟嘴和花冠几乎重叠标注的时候要注意框尽量包含蜂鸟的嘴部但不要连带着把整朵花都装进去否则模型会把花朵也算作蜂鸟的一部分导致误检。数据增强的 mosaic 可以开到 0.5 甚至 1.0。因为很多蜂鸟图片都是小目标mosaic 会把小目标拼成大图的一部分对模型学习小目标特征有明显帮助。3.3 训练配置与参数选择我用的是 ultralytics 的 YOLOv8n训练命令如下。注意把数据集路径改成你自己的。# hummingbird.yaml path: ./datasets/hummingbird train: images/train val: images/val names: 0: hummingbirdyolo train \ datahummingbird.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0关键参数这里解释一下。epochs 我设到 100但对 700 张图的小数据集来说50 轮以后基本就收敛了100 轮主要是为了保险。如果你的显卡显存不够batch 降到 8 也行最终效果差别不大。imgsz 我建议用 640不要为了小目标直接拉高到 1280因为蜂鸟目标太小提高输入分辨率确实有助于检测但推理速度会变慢而且训练显存需求会大幅增加。先在 640 下把流程跑通再考虑优化分辨率。训练完成后看验证集 mAP50 和 mAP50-95 两个指标。mAP50 好的情况下在 0.85 以上就算可用mAP50-95 因为目标小一般会低一些0.5 左右就不用慌。3.4 针对蜂鸟场景的推理参数调优模型训练出来后推理阶段的三个参数直接影响实际效果而且没有一个参数是通用的必须针对你的摄像头安装位置和画面大小去试。confidence置信度阈值我这边最后设在 0.35。太低会有很多把叶子、花朵影子误检成蜂鸟的情况太高又容易漏掉飞行中快速划过的小目标。你的安装角度如果让蜂鸟在画面里占的面积更大阈值可以适当调高到 0.45。IoU 阈值非极大值抑制用默认的 0.7 就行。这个参数对单类检测影响不大因为同一个目标一般不会产生多个重叠框。frame stride跳帧检测如果设备性能不足可以每两帧检测一次中间那帧直接用上一帧的检测框做跟踪预测。但蜂鸟动作快间隔一帧位置变化就很大我一般不推荐跳帧宁可降低输入分辨率也要保住帧率。4. 实操过程从视频流到蜂鸟行为记录4.1 整体代码流程整套系统的代码不算复杂核心逻辑就是一个循环。读取视频流 - 目标检测 - ByteTrack 跟踪 - 状态判断 - 日志写入我用的是 OpenCV 从摄像头读帧然后喂给 YOLOv8n 模型推理。检测结果是一个包含 boxes、scores、class_ids 的对象把置信度高于阈值的框拿出来丢给 ByteTrack 更新。ByteTrack 会返回每个目标的 track_id 和更新后的框。拿到这些信息后用上一章说到的规则判断行为最后把结果写入 CSV。4.2 关键代码实现下面这段是系统里最核心的检测加跟踪循环去掉了一些无关的日志和界面代码保留主要逻辑。import cv2 import numpy as np import pandas as pd from ultralytics import YOLO from boxmot import ByteTrack model YOLO(runs/detect/train/weights/best.pt) tracker ByteTrack(track_thresh0.35) cap cv2.VideoCapture(0) # 换成实际摄像头索引或视频文件路径 fps cap.get(cv2.CAP_PROP_FPS) or 30 records [] prev_positions {} prev_boxes {} hover_frames {} while True: ret, frame cap.read() if not ret: break results model(frame, conf0.35, imgsz640, verboseFalse) boxes results[0].boxes.xyxy.cpu().numpy() scores results[0].boxes.conf.cpu().numpy() # 手动过滤低置信度框 keep scores 0.35 boxes, scores boxes[keep], scores[keep] # ByteTrack 更新 tracks tracker.update(boxes, scores, np.array([]), frame.shape[:2]) for track in tracks: track_id int(track[0]) x1, y1, x2, y2 map(float, track[1:5]) cx (x1 x2) / 2 cy (y1 y2) / 2 area (x2 - x1) * (y2 - y1) prev prev_positions.get(track_id) prev_box prev_boxes.get(track_id) # 行为判断 behavior classify_behavior(prev, (cx, cy), prev_box, area) # 记录行为 records.append({ track_id: track_id, timestamp: cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0, behavior: behavior, cx: round(cx, 1), cy: round(cy, 1), area: round(area, 1) }) prev_positions[track_id] (cx, cy) prev_boxes[track_id] (area, behavior) # 写日志 if int(cap.get(cv2.CAP_PROP_POS_FRAMES)) % 30 0: df pd.DataFrame(records) df.to_csv(hummingbird_log.csv, indexFalse) # 可视化 for track in tracks: track_id int(track[0]) x1, y1, x2, y2 map(int, track[1:5]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{track_id}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(colibri, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里面 classify_behavior 函数是行为判断的核心我单独拆出来讲。4.3 行为判断逻辑与参数计算行为判断我用了三个信号组合中心点位移、bbox 面积变化、目标在画面中的持续帧数。我不训练额外模型因为这几个信号已经足够区分蜂鸟的三大类行为。先看中心点位移。蜂鸟悬停的时候非常稳中心点在两帧之间几乎不动飞行的时候位移明显。但如果摄像头有轻微晃动位移阈值设得太小会把静止目标误判成飞行设得太大又会把飞得慢的动作当成悬停。我的阈值计算方式是这样蜂鸟体长 7 到 10 厘米假设画面里蜂鸟的高度占 30 像素那么画面里 1 像素大约对应 3 毫米。悬停时蜂鸟头部会有小幅摆动但身体中心点应该稳定在 1 到 2 厘米范围内也就是 6 到 12 像素。如果摄像头以 30fps 工作我对单帧位移超过 10 像素以上的帧判定为飞行低于这个值则可能是悬停或吸蜜。这个值不是固定的需要根据摄像头到花盆的距离去调。再看 bbox 面积变化。吸蜜时蜂鸟的嘴会伸进花冠身体会稍微前倾面积不会发生特别剧烈的变化。但如果画面里还包含了花影面积可能会因为遮挡而抖动。我这里做了一个经验性判断如果连续 5 帧中心点位移都在阈值内且目标在画面中出现超过 10 帧就认为进入“悬停”在悬停基础上如果检测框高度比该 ID 历史平均高度缩小超过 10%就认为可能是在吸蜜并把吸蜜开始时间记录下来。下面是简化版的行为判断函数。def classify_behavior(prev_pos, curr_pos, prev_box, curr_area, pos_thresh10.0, area_thresh0.1): 判断蜂鸟当前行为. if prev_pos is None: return unknown dx abs(curr_pos[0] - prev_pos[0]) dy abs(curr_pos[1] - prev_pos[1]) dist (dx * dx dy * dy) ** 0.5 if dist pos_thresh: return flying # 位移很小可能是 hover 或 feeding if prev_box is not None: prev_area, prev_behavior prev_box area_ratio curr_area / max(prev_area, 1e-6) if area_ratio (1.0 - area_thresh) and prev_behavior hover: return feeding if area_ratio (1.0 area_thresh) and prev_behavior hover: # 面积突然增大可能是靠近相机 return flying return hover这个函数只是判断当前帧的行为不适合追踪完整的行为序列。实际项目中我另外加了状态机在一个 track_id 的连续片段里把多帧行为投票成“整个事件”。默认一个事件如果整体以 hover 为主且中间穿插 feeding就会记成一轮吸蜜否则记成一次飞过。4.4 部署到边缘设备的几个关键步骤模型的训练和推理在电脑上跑通后我把它搬到了 Jetson Nano 上放在阳台角落里长时间运行。这里有几个步骤和坑可以分享。先做模型转换。PyTorch 模型直接跑在 Jetson 上太慢需要先把权重导出成 ONNX再转成 TensorRT engine。命令如下yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640拿到 ONNX 文件之后用 TensorRT 提供的 trtexec 转成 engine。/usr/src/tensorrt/bin/trtexec \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16这里 fp16 半精度对 Jetson 特别重要能把推理时间从三十毫秒级降到十毫秒级。如果转换时报错优先检查 TensorRT 版本和 ONNX opset 版本把 opset 降到 12 通常能解决。转换完后运行时用 onnxruntime 加载 TensorRT 执行提供程序。这是我在 Jetson 上实际使用的加载方式import onnxruntime as ort sess_options ort.SessionOptions() sess_options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL session ort.InferenceSession( best.engine, sess_options, providers[TensorrtExecutionProvider, CUDAExecutionProvider] )部署到边缘设备上还有一个容易忽略的点长时间运行的稳定性。Jetson Nano 内存不大OpenCV 显示窗口如果一直开着且不释放资源跑几个小时可能把内存吃满最后进程被系统杀掉。我后来专门加了一个逻辑每 1000 帧主动释放一次显示画面同时每 30 分钟在日志里打印一次内存占用方便监控。5. 常见问题与排查技巧实录5.1 高频问题速查表做这个项目的过程中遇到的大坑不少我把典型问题整理成了表格方便你直接对照排查。现象可能原因解决方案绿色植物被识别成蜂鸟训练数据里背景单一模型没学到足够的背景区分能力增加负样本没有蜂鸟的纯植物图片重新训练蜂鸟快速飞过时没有任何框目标太小且运动模糊严重置信度被压得过低调低置信度阈值到 0.3或提高输入分辨率到 768同一只蜂鸟 ID 频繁切换检测框不稳定两帧间 IoU 太小调低 ByteTrack 的匹配阈值尽量保证同一 ID 连续白天正常傍晚误检率升高光线变暗检测模型泛化能力下降增加低光训练数据或启用摄像头的红外/补光模式长时间运行后内存占用暴涨日志和显示窗口资源没有释放定期清理历史帧、限制 OpenCV 显示窗口的刷新频率模型在 Jetson 上转换失败TensorRT 版本与 ONNX operator 不兼容用 opset 11 重新导出或更新 TensorRT 版本5.2 我实际踩过的几个坑和解决思路第一个坑是关于负样本的。刚开始训练时我用的全是蜂鸟的图片没有一张“纯花但没有蜂鸟”的图。结果模型训练完以后在阳台实测时频繁把吊钟花的花影和叶子边缘识别成蜂鸟。原因是模型只见过“花和蜂鸟一起出现”的正样本没见过“有花无鸟”的负样本相当于它没学到“蜂鸟和花之间的边界”。后来我在数据集中加了一批“只有花、只有叶子、空场景”的负样本数量大概是正样本的三分之一模型的误检率立刻降了下来。第二个坑是推理设备性能不足时不能直接用降分辨率来硬扛。我一开始为了提高 Jetson 上的帧率把输入分辨率降到 416结果蜂鸟在画面里本来就小缩到 416 之后小目标直接丢失检测率大幅下降。后来我把分辨率恢复到 640再用 TensorRT 的 FP16 模式推理速度反而够用。这个经验说明性能优化要优先考虑推理框架的加速而不是盲目降低输入质量。第三个坑比较隐蔽行为分类里“吸蜜”这个动作单看面积变化很容易误判。因为蜂鸟悬停时翅膀展开范围大一旦开始吸蜜翅膀会稍微收拢面积确实会缩小。但花朵本身也会随风晃动如果摄像头位置不够稳花朵的遮挡会让面积产生抖动导致误判。我最后的解决办法是吸蜜判断只会在“悬停超过 5 帧”之后才启用而且要求面积缩小的比例至少保持 3 帧这能过滤掉大部分偶发的抖动信号。5.3 调试数据闭环让系统越跑越准调试过程中最有价值的一步是我把每天误检和漏检的帧都自动保存下来然后定期用这些真实场景数据微调模型。具体做法是每天运行结束后把置信度在 0.25 到 0.45 之间且最终没有被跟踪器匹配的低分框对应的图像片段单独存到一个目录。每隔两三天把这些片段过一遍人工筛选把确实是蜂鸟的帧加进训练集。用增量方式重新训练模型初始权重用上一轮的 best.pt。这样做了十天左右模型在阳台真实场景下的表现提升非常明显漏检率从最初的一成多降到了不到百分之三。这个数据闭环的思路尤其适用于摄像头固定、场景固定的项目因为模型会越来越适应当前环境比单纯堆更多公开数据更高效。尾声关于 colibri 的一些后续想法项目跑到现在最让我意外的不是模型表现而是我发现判断“蜂鸟在干什么”这件事到最后拼的不是模型的复杂程度而是对场景细节的理解。摄像头安装的位置、花朵在画面里的大小、光线变化的规律这些都会直接影响检测和跟踪的效果。模型本身只是整个系统里相对标准化的一个环节真正需要反复打磨的是那些看起来琐碎的数据和规则。我自己在这套系统基础上已经在计划两个扩展。一个是加蜂鸟声音识别蜂鸟翅膀扇动会发出轻微的高频振动声虽然普通麦克风不好捕捉但用更高采样率的麦克风配合一个简单的音频分类器也许能补上夜晚或遮挡场景下的记录空缺。另一个是生成周报把访问量、高峰期、吸蜜时长这些信息汇总成一份可视化图表不用手动翻 CSV 去看。如果你也想做个类似的鸟类观测系统我的建议是先别急着堆功能把“检测一只鸟并稳定跟踪它”这件小事做好后面所有的数据和分析才有意义。真实环境里跑视觉项目最大的敌人从来不是模型精度不够而是你以为环境是可控的实际上它每一秒都在变。