尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于YOLOv5的鸟窝检测:从数据标注到模型部署实战

基于YOLOv5的鸟窝检测:从数据标注到模型部署实战 简介目标检测是计算机视觉领域的核心任务之一其通过深度学习模型自动定位图像中的物体并给出类别与位置。YOLOv5作为一种高效且成熟的单阶段检测算法凭借其出色的速度与精度平衡在工业界得到广泛应用。在生态监测、电力巡检等场景中准确识别无人机航拍图像中的特定目标至关重要例如鸟窝的自动识别可有效辅助林业评估与线路隐患排除。本文基于YOLOv5构建了一套完整的鸟窝目标检测系统涵盖数据采集与标注、模型训练与调参、性能评估及ONNX部署等环节并详细分享了实际工程中的踩坑经验与解决方案为类似的目标检测项目提供了可复用的实施路径。 从拿到一批无人机拍摄的林地影像到最终交付这套“基于YOLOv5的鸟窝目标检测源码模型”前前后后我大概折腾了两周左右。这个项目说白了就是用YOLOv5训练一个能自动框出图像中鸟窝位置的检测模型然后把训练好的权重、推理脚本、说明文档一起打包成zip。如果你正在做生态监测、电力杆塔巡检、果园鸟害防治这类工作或者单纯想找一份完整的目标检测练手项目那这份内容应该能省掉你大量找资料、踩坑的时间。做这个项目的主要动机其实很简单人工去看几千张航拍图找鸟窝眼睛真的会瞎而且效率不稳定。鸟窝在图像里往往藏在树杈、屋檐、电线杆顶部颜色与环境接近人工判读主观性很强。用目标检测模型代替人眼不仅速度快还能保持统一的判断标准。我之前用YOLOv5做过不少检测任务这次就直接拿它来做鸟窝检测顺便把整个从数据准备到模型部署的流程重新梳理了一遍。下面我会把整个项目的实现过程、关键代码逻辑、以及我在训练和部署中踩过的坑都讲清楚内容尽量偏实操你可以直接照着复现。1. 项目背景与整体设计思路1.1 鸟窝检测到底要解决什么问题鸟窝检测在现实中不是个伪需求。林场工作人员需要掌握鸟类筑巢分布情况生态评估要统计鸟类繁殖密度电力公司要排查杆塔上的鸟窝因为会影响线路安全果园和农业设施也需要及时发现鸟窝以防鸟类啄食。可这些场景有一个共同点图像采集容易但目标识别难。鸟窝不是标准物体。有的鸟窝像一团杂草挂在树杈中间有的则是在人工建筑上垒出的紧密结构还有的只是几根树枝搭成的框架。再加上季节因素冬天树叶落了鸟窝暴露出来夏天枝叶茂盛鸟窝被遮挡大半。如果用传统图像处理方式靠颜色、纹理去识别很难覆盖这些形态变化。目标检测模型的好处在于能学习到鸟窝在局部区域内的结构特征而不仅仅是颜色或纹理规则。所以这个项目的核心目标不是做一个学术上的高精度模型而是工程化交付一个能用的检测器。最终我们需要的是给定一张图像程序输出鸟窝的位置和置信度。1.2 为什么选择YOLOv5而不是其他检测算法我选YOLOv5不是因为它在公开数据集上刷分最高而是因为这个项目要交付源码后续使用的人可能需要自己做二次训练。YOLOv5在这条路上非常友好。首先是生态成熟。YOLOv5的源码结构非常清晰train.py、detect.py、models、data、utils每个目录干什么一目了然。官方提供不同大小的模型n/s/m/l/x可以根据硬件灵活选择。社区讨论多遇到问题几乎都能搜到解决方案。其次是训练成本可控。鸟窝检测是单类别目标检测样本量不需要像COCO那种大规模数据集那么大。YOLOv5用迁移学习在几百到几千张图上也能训练出能用的模型。相比EfficientDet、DETR等模型YOLOv5的显存占用和训练时间友好很多。最后是部署方便。这个项目最终可能跑在服务器上做离线批量推理也可能部署到无人机机载设备或者嵌入式终端上。YOLOv5支持导出ONNX、TensorRT、CoreML、TFLite等格式从研究到生产链路的阻碍很小。1.3 项目交付物的边界与目录结构我把交付的zip分成这几块源码部分YOLOv5核心代码包含训练和推理脚本模型部分训练好的best.pt权重以及导出的onnx文件数据部分数据集的标注说明和划分脚本但不直接打包全部原始图片因为体积太大文档部分README.md写清楚环境安装、训练命令、推理命令和常见问题。源码我基于某个稳定版YOLOv5做了少量修改主要是为了处理单类别场景下的输出逻辑和标签颜色。目录结构大致如下birdnest_yolov5/ ├── data/ │ ├── birdnest.yaml │ └── convert_xml_to_txt.py ├── models/ │ └── yolov5s_birdnest.yaml ├── weights/ │ ├── best.pt │ ├── last.pt │ └── best.onnx ├── scripts/ │ ├── train.sh │ └── detect.sh ├── detect.py ├── train.py ├── requirements.txt └── README.md如果你拿到手重点看README里面已经把训练和推理命令写好不要自己去翻YOLOv5官方仓库免得不小心改了关键配置。2. 鸟窝数据集的构建与增强2.1 数据从哪里来采集与筛选数据是目标检测项目的下限。模型效果好不好很大程度上由数据决定。做鸟窝检测首先要保证图像里鸟窝本身足够清楚。我在这个项目里主要用了三种来源无人机航拍图树冠俯视视角鸟窝在树冠顶部和分叉处比较常见监控摄像头截图鸟窝出现在屋檐、电线杆上视角比较平背景是天空或建筑物公开数据集中筛选从一些鸟类图像库中筛选出带有明显鸟窝的照片。一共整理出大约2100张有效图片。但这里有个坑直接网上找图容易引入水印、过压缩的图片训练出的模型会学到奇怪的特征。因此筛选时我会把分辨率低于500x500、模糊、目标占比太小的图片全部删掉。数据集的多样性比数量更重要。如果图片全是从一个视角拍的模型很容易过拟合。我标注时就注意覆盖不同季节、不同天气、不同背景包括树叶密集和稀疏的情况。最终单类别class_id 0。2.2 标注工具与格式转换我用LabelImg标注它最省事支持VOC格式导出也支持YOLO格式。但LabelImg需要逐张框选2100张图我花了大概三天时间平均每张图四五十秒。标注时不能太随意尤其要注意鸟窝被遮挡时只标注可见部分不要把周围的树枝框进来背景中特别模糊、无法判断是否为鸟窝的区域直接跳过宁可不标也不硬标一个图中多个鸟窝每一个都要单独框出。LabelImg保存的XML是Pascal VOC格式YOLOv5需要的是txt格式每行内容为class_id x_center y_center width height其中x_center等值都是归一化后的比例值。我自己写了一个转换脚本import os import xml.etree.ElementTree as ET def convert_annotation(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls ! birdnest: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) dw 1.0 / w dh 1.0 / h x_center ((x1 x2) / 2.0) * dw y_center ((y1 y2) / 2.0) * dh box_w (x2 - x1) * dw box_h (y2 - y1) * dh lines.append(f0 {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)), w) as f: f.write(\n.join(lines)) # 遍历所有xml并转换 for xml_file in os.listdir(annotations): if xml_file.endswith(.xml): convert_annotation(os.path.join(annotations, xml_file), labels)转换后把图片和txt文件按照YOLOv5格式组织dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ ├── 002.jpg │ └── ... └── labels/ ├── train/ │ ├── 001.txt │ └── ... └── val/ ├── 002.txt └── ...最终按约8:1:1划分训练、验证、测试集训练集1700张左右验证集200张测试集200张。2.3 数据增强既要多样性又不能失真YOLOv5自带一组在线增强策略包括mosaic、随机仿射变换、HSV扰动、翻转等。默认超参文件hyp.scratch.yaml里的值已经调得比较合理我基本没有改。但有几个经验值得提Mosaic增强能把四张图拼成一张相当于变相增大batch多样性。这个策略对小目标检测很有帮助因为拼接后目标尺寸会相对变小模型需要学会在复杂背景下识别目标。对于鸟窝这种本身尺寸变化较大的目标mosaic很有用。不要为了追求“更多数据”而使用过强的离线增强。比如旋转90度、添加大量高斯噪声会让鸟窝形状严重变形模型反而学到了错误特征。我做了少量的离线增强主要用水平翻转、亮度和对比度调整用来补充一些光照变化大的场景。这些用OpenCV就能处理。关键点在于增强的目的是模拟真实环境中的变化。航拍图可能会因光线、季节、设备差异产生亮度变化因此亮度增强很有必要。但鸟窝始终是物理结构不会从天上突然变成水中倒影所以不用那些夸张的增强。3. YOLOv5环境搭建与源码准备3.1 从零搭建虚拟环境这个项目依赖Python和PyTorch。我推荐用conda建一个独立环境避免和系统Python环境冲突。直接用官方requirements.txt装依赖是最省事的方式conda create -n yolov5 python3.9 conda activate yolov5 pip install -r requirements.txtrequirements.txt里主要包含torch、torchvision、opencv-python、numpy、pandas、matplotlib、seaborn、pyyaml等。如果你在GPU机器上训练注意先根据CUDA版本安装对应的PyTorch不要直接pip install torch否则很可能装成CPU版本。我这次项目用的机器是单张RTX 3090显存24G所以后面训练参数比较宽裕。但在准备环境的时候我也测过CPU训练实在是太慢了跑一个epoch就要二十分钟不建议尝试。如果你的机器显存只有4G-6G需要选YOLOv5n或yolov5s模型并把batch size调到4左右。3.2 YOLOv5源码的关键目录功能YOLOv5源码拿到手后不需要一个文件一个文件读但要搞清楚几个关键位置train.py训练入口负责加载模型、数据、超参数然后开始训练循环。detect.py推理入口支持图片、视频、摄像头、文件夹等输入源。models/yolov5s.yaml模型结构配置文件包括depth_multiple、width_multiple以及每个模块的层数。修改这个文件可以控制模型的深度和宽度。data/hyp.scratch.yaml训练超参数包含lr0、momentum、weight_decay、warmup_epochs以及各种增强参数。utils/datasets.py数据加载器包括mosaic增强的逻辑都在这里。utils/loss.py损失函数计算基于anchor的检测头设计都在这里。如果要自定义数据集你主要改的是data/目录下的yaml配置文件以及models/下的模型yaml中的nc类别数。3.3 配置数据文件与模型文件首先在data目录下创建一个birdnest.yaml内容如下train: /path/to/dataset/images/train val: /path/to/dataset/images/val test: /path/to/dataset/images/test nc: 1 names: [birdnest]如果你把数据集放在了YOLOv5项目外路径建议写绝对路径避免因为当前工作目录不同导致找不到。然后在models目录下复制yolov5s.yaml命名成yolov5s_birdnest.yaml把nc字段改成1nc: 1 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10,13, 16,30, 33,23] - [30,61, 62,45, 59,119] - [116,90, 156,198, 373,326]anchor是在COCO数据集上学到的先验框虽然目标类别不同但anchor本身是通用的尺寸信息单类别检测不调整也可以。训练时YOLOv5会自动学习anchor的偏移量所以不需要手工重新聚类。模型yaml里的depth_multiple和width_multiple决定了模型规模。yolov5s大约是0.33和0.50如果想更快但精度低一点可以用yolov5n的配置如果精度优先用yolov5m或yolov5l。我的项目里选s是综合考虑了精度和速度。3.4 预训练权重和迁移学习YOLOv5官方提供了在COCO上预训练的权重。迁移学习的关键在于冻结部分特征提取层让模型在不必从头学习基础特征的情况下快速适应鸟窝数据集。下载yolov5s.pt放到weights目录后训练时通过--weights yolov5s.pt传入。训练脚本会自动加载预训练权重然后修改最后的输出层类别数。如果你直接使用随机初始化权重训练会慢很多而且精度很难上来。预训练权重不仅可以提高收敛速度还能有效避免因为样本量少产生的过拟合。即使鸟窝和COCO中的猫狗毫无关系但低层的边缘、纹理、颜色特征仍然是通用的。4. 模型训练与调参实战4.1 训练参数怎么选训练命令如下python train.py \ --weights weights/yolov5s.pt \ --data data/birdnest.yaml \ --cfg models/yolov5s_birdnest.yaml \ --img 640 \ --batch 16 \ --epochs 100 \ --name birdnest_exp关键参数说明img 640输入图像分辨率。如果目标尺寸小可以提高到640甚至1280但显存占用会急剧上升。航拍图里的鸟窝往往只有几十像素640足够了。batch 16一次性送入模型的图像数量。我3090显卡24G显存可以跑16如果是8G显存建议改8如果是4G只能改2或4。epochs 100训练轮数。实测60轮后mAP增长就变得很平缓100轮属于保守配置。name实验输出目录运行后会在runs/train/目录下生成对应的文件夹。4.2 训练过程中的Loss曲线解读训练时终端会打印每个epoch的box_loss、obj_loss、cls_loss以及precision、recall、mAP等指标。因为单类别任务cls_loss会很小重点关注box_loss和obj_loss。我第一次训练时前几个epoch的loss下降很快但到40轮左右开始出现轻微震荡这属于正常现象。如果val精度不升反降同时训练loss还在降说明过拟合了可以提前停止或者增加数据增强。YOLOv5默认在训练结束时会自动选择best.pt和last.pt。best.pt是验证集上mAP最高的模型last.pt是最后一轮的模型。对于后续部署一定用best.pt。4.3 单类别目标检测的调参心得单类别检测比多类别简单但有一些细节值得注意第一置信度阈值可以适当调低。多类别检测中类别之间的区分是一个难点所以通常将conf_thres控制在0.25以上。但鸟窝检测只有一类只要模型对“前景区域”敏感即使置信度低一点的候选框也可能是对的。我在验证时把conf_thres从0.25降到0.1召回率上升了好几个点虽然误检框也会增加但后续可通过NMS和规则过滤。第二如果数据中不同尺度的鸟窝差异很大可以考虑使用多尺度训练。YOLOv5的--img参数支持范围比如--img 640表示训练时随机在0.5到1.5倍范围内缩放图像这个自带的多尺度训练对鸟窝检测很有帮助不需要额外设置。第三训练最后10轮可以关闭mosaic增强。YOLOv5官方在最新版本中默认会最后10轮自动关闭mosaic因为合成图像和真实图像还是存在差异如果一直用mosaic模型在真实图像上的表现可能略微下降。如果用的是老版本可以手动在utils/datasets.py里找到对应逻辑把最后几轮的mosaic概率改为0。4.4 实际训练过程记录我按100轮训练总共耗时约4个小时。从第10轮开始val/box_loss稳定下降第35轮时mAP_0.5已经超过0.85最终在90轮左右达到峰值0.93。这个精度对单类别鸟窝检测来说已经不错了因为验证集中的很多图像鸟窝非常小且被树枝遮挡。训练过程中最让我意外的是模型在俯视视角航拍图上表现非常好但到了平视视角的监控图片上偶尔会漏检。回去翻了翻数据分布才发现俯视视角的图片占了大头平视的图片只有不到20%。后来我补了一些平视图片进行二次训练漏检率明显下降。所以数据平衡非常重要不只是在类别上视角和场景也要平衡。5. 模型评估、导出与部署5.1 评估指标不能只看mAP训练完成后的best.pt还要在测试集上评估一轮评估命令python val.py --data data/birdnest.yaml --weights weights/best.pt --img 640输出会包含Precision、Recall、mAP0.5、mAP0.5:0.95。对单类别任务mAP0.5是最直观的指标因为它只计算IoU在0.5以上的预测框。这个项目的最终mAP0.5大约0.93mAP0.5:0.95大约0.69。但指标只是一个参考真正好不好用要看实际预测效果。我建议跑一遍测试集上的所有图片把检测结果可视化出来。YOLOv5自带的val.py会在runs/val/exp目录下保存标注了预测框的图片你可以直接查看哪些图片漏检、哪些图片误检。我调高阈值后发现少量背景相似的树枝被误判为鸟窝处理方式是增加一些无鸟窝的负样本图片参与训练。这在很多目标检测项目中容易被忽略但它对降低误检率很有效。5.2 导出ONNX格式方便跨平台推理训练好的模型是PyTorch的.pt格式部署时可能不方便。ONNX是跨平台的中间表示几乎所有的推理框架都支持比如OpenCV DNN、ONNX Runtime、NCNN、TensorRT。导出命令python export.py --weights weights/best.pt --include onnx --opset 12导出后会在weights目录生成best.onnx。我在项目中额外提供了ONNX模型因为很多人的部署环境不一定会装PyTorch用ONNX Runtime推理就没那么重的依赖。ONNX导出后建议用onnx-simplifier做一次图优化能去掉一部分冗余算子推理速度会快一些。5.3 在嵌入式设备上跑起来的注意事项如果你的目标是嵌入式设备比如Jetson Nano、RK3588、树莓派不能直接把PyTorch模型放上去跑。通常有两条路一是把模型导出为TensorRTJetson平台或RKNNRockchip平台这对硬件优化最充分。YOLOv5官方提供TensorRT导出脚本但需要你手动安装TensorRT库并确认算子的兼容性。二是把YOLOv5s换成更小的YOLOv5n同时把输入尺寸从640降到416或320。鸟窝检测对实时性要求不算高但降分辨率可以显著减少推理耗时。代价是精度可能有微弱下降需要自己权衡。在实际推理时我们一般这样调用python detect.py \ --weights weights/best.pt \ --source /path/to/images \ --conf 0.3 \ --img 640 \ --save-txt--save-txt会把检测结果保存成txt文件每一行是类别、中心点坐标、宽高、置信度。如果你想直接输出每个鸟窝的坐标解析这个txt文件即可。6. 常见问题与排查技巧实录6.1 训练Loss不收敛我应该先查哪里最常见的原因是数据标注文件有问题比如坐标归一化后数值大于1或小于0或者图片和标签文件名不一致。我建议在训练前先写一个简单脚本读取随机一张图片和对应的txt标签把框画出来确认位置是否正确。这一步能排查掉80%的低级问题。其次是学习率设置不合理。YOLOv5默认学习率0.01多数情况没问题。但如果你自己调过lr0导致过大或过小loss都会出现异常。我建议先从默认参数开始尽量不要自己发明超参数。6.2 显存不足怎么解决遇到CUDA out of memory首先要做的就是减小batch size每次减半直到能跑起来。如果batch已经降到1还不够那就把img从640降到512或者416。还有一种技巧是开启梯度累积YOLOv5不支持直接配置但可以手动把梯度累积步数写进训练循环不过不太推荐新手修改。最稳妥的做法是换小模型。YOLOv5n在同样分辨率下显存占用只有yolov5s的四成左右对鸟窝检测这种单类别任务精度差距不会太大。6.3 检测效果不错但当数据变化时泛化不足如果你拿手机拍照测出来的效果不如验证集大概率是数据分布差异太大。比如训练集全是航拍俯拍角度但你拿平视手机图去测模型就懵了。解决办法不是换模型而是补充目标场景下的数据。另一个技巧是使用颜色空间增强在数据加载时增加更大幅度的亮度、对比度变化提升模型对不同摄像头色彩的适应能力。6.4 模型打包交付的这些细节你考虑过吗交付源码模型的zip包时记得把以下内容包含进去requirements.txt明确版本号否则后续复现代理环境不一致README.md不只写命令还要写清楚训练数据目录结构、模型文件作用、如何运行推理测试图片放几张非训练/验证集的真实图片让使用者一跑detect.py就能看到效果训练日志或曲线图方便使用者了解训练过程。最重要的是不要直接打包原始数据集尤其是标注文件里可能包含周边人物的隐私信息。如果确实要分享数据也要先脱敏和确认版权。我在实际使用中还有个习惯先跑通一个最小化流程比如用10张图像训练5个epoch确认整个链路没问题后再上全部数据做正式训练。这样能避开很多“数据集路径不存在”“标签索引越界”之类的低级错误。这套项目做完最大的收获倒不是模型本身而是建立了一条从数据到部署的标准化流程后面再接到类似的目标检测任务基本只需换数据和调几个参数就能跑通。本文还有配套的精品资源点击获取
返回列表