ARTICLE DETAIL

资讯详情

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

YOLOv5红外小目标检测:夜间飞鸟与无人机识别方案

YOLOv5红外小目标检测:夜间飞鸟与无人机识别方案 简介这是一套面向红外场景下微小无人机、直升机、飞机、飞鸟四类目标检测的完整资源适合目标检测研究者也适合低空安防、反无人机、交通监控等应用开发者。内含训练好的YOLOv5模型权重以及4000多张红外图像数据集标签已经整理为YOLO格式并预先划分好train、val、test集合提供data.yaml配置文件使用YOLOv5、YOLOv7、YOLOv8等主流算法时可直接加载训练。压缩包共2000个文件以标注文件为主另有3个Python脚本和3份PDF教程整体体积约587.15MB便于离线使用和学习。配套的PyQt5图形界面支持对图片、视频以及调用摄像头进行实时检测可快速验证模型效果PDF教程覆盖YOLOv3至YOLOv8的环境配置和PyQt5界面使用说明能显著减少环境搭建与推理部署中遇到的常见问题。目前已有548人学习下载适合需要红外小目标数据、预训练权重与可运行交互界面的开发者直接上手。1. yolov5 红外小目标检测夜间飞鸟与无人机场景下的一个现成落地方案晚上用红外相机对着天空飞鸟、无人机在画面里只是几个像素大小的亮斑普通检测模型在这种场景下基本不可用而 yolov5 配合红外灰度数据训练后依然能把 Airplane、Bird、Drone、Helicopter 四类目标稳定框出来。这份资源就是把整套方案打包好了4000 多张已划分 train/val/test 的红外小目标数据集、配套的 data.yaml、训练好的 yolov5 权重外加一个支持图片、视频、摄像头三种输入的 PyQt 界面和两份环境配置教程。你不需要从抽帧、标注开始折腾解压配好环境就能先看到检测效果再决定要不要继续复训。适合谁想快速验证红外小目标检测方案、又没时间自己攒数据的从业者。我拆这套资源时最关注三件事——数据集类别分布是否均衡、权重和类别顺序对不对得上、PyQt 界面里推理线程会不会卡死窗口。后面逐一说清楚坑都提前替你们踩过了。2. 数据集的真实结构从 Roboflow xml 到 YOLO txt 的组织与校验2.1 目录结构images 与 labels 的对应关系解压后数据在 datasets/IRmini 下train、val、test 分开每部分内部都有 images 和 labels 两个子目录。images 里是红外 JPG 帧labels 里是同名 txt。txt 每一行表示一个目标框3 0.421875 0.536542 0.024305 0.037514 1 0.689236 0.710938 0.019097 0.031654 0 0.350694 0.432292 0.013889 0.020833每一列含义class_id、x_center、y_center、width、height。五个值都是归一化到 0~1 的浮点数不是像素坐标。class_id 从 0 开始对应 data.yaml 里 names 数组的下标0 是 Airplane1 是 Bird2 是 Drone3 是 Helicopter。如果 txt 里出现 class_id4要么标签错了要么 names 没对齐。顺带说一句空标签的事Roboflow 导出时如果原始图中目标太小被过滤掉就会留下「图片在、txt 空」的文件统计时单独列出来就行不必删图。拿到任何一个目标检测数据集第一步不是直接开训而是先跑一遍统计脚本看类别分布和标签完整性。我一般会写十几行检查代码# check_labels.py —— 统计每个类别的目标框数量找出空标签 import os from collections import Counter label_dir datasets/IRmini/labels/train stats Counter() empty_files [] for name in sorted(os.listdir(label_dir)): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] if not lines: empty_files.append(name) continue for line in lines: cls_id line.split()[0] stats[cls_id] 1 print(各类别框数量:, dict(stats)) print(空标签数量:, len(empty_files)) print(空标签示例:, empty_files[:10])逻辑很简单遍历训练标签目录逐行读取第一列是类别 id累加到 Countertxt 完全无内容就记进 empty_files。输出里如果某个类别框数量明显偏少比如 Drone 只有一两百个框另外三类各自上千那模型在 Drone 上的表现大概率不行需要补数据或做类别权重。空标签文件数量也顺手打出来数量太多就说明数据清洗环节没做好训练时这些图会反复贡献背景 loss。2.2 data.yaml 的配置与 xml 备份的作用data.yaml 是这个数据集的入口配置内容如下train: /path/to/IRmini/train/images val: /path/to/IRmini/val/images test: /path/to/IRmini/test/images nc: 4 names: [Airplane, Bird, Drone, Helicopter]train、val、test 三个路径指向对应划分的 images 目录。yolov5 训练时只读 train 和 valtest 在 val.py 做最终评估时才用。nc 必须和 names 长度一致这条很多人改漏改了 names 忘了 nc训练直接断言报错。names 的排列顺序决定 txt 里 class_id 的语义所以如果自己的类别集合跟这套四类不同names 要整体替换不能只删一个名字。压缩包里还带了一批 xml 文件文件名类似 IR_BIRD_027_frame_173_jpg.rf.ad13a23f0a417eaef8b89cdbf3d571b3.xml。从命名能看出原始来源IR 是红外BIRD 是场景标签027 是视频段编号frame_173 是第 173 帧——这套数据集是从多段红外视频抽帧得到的。rf 是 Roboflow 导出时生成的随机文件 idxml 本身是 Pascal VOC 格式里面记录 object 的 name 和 bndbox。也就是说txt 标签可以直接喂 YOLOxml 是原始标注备份方便日后重新划分数据集时从源头转换不用回到标注工具里手工改。2.3 VOC xml 转 YOLO txt 的脚本与易错点如果想在原有标签基础上重新划分数据集比如把 7/2/1 的比例改成 8/1/1就要拿 xml 重新生成 txt。转换脚本不长核心是坐标归一化# xml_to_txt.py —— 把 Pascal VOC xml 转换成 YOLO 格式 txt import xml.etree.ElementTree as ET class_names [Airplane, Bird, Drone, Helicopter] def convert(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) 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) cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines) \n)三个易错点。第一bndbox 的坐标先转成 float 再做除法不要整型直接相除否则归一化精度直接丢。第二有些 xml 的 xmax 比图像宽度大一个像素这是标注工具的老毛病转换前最好 clamp 一下否则框越界。第三xml 里出现 names 之外的类别时脚本里是 continue 跳过可能静默丢标签建议改成打印警告。转换完再用 check_labels.py 统计一遍对比前后类别框数量是否一致这步能拦住九成的手滑。提示txt 里 class_id 从 0 开始xml 里类名是字符串转换脚本里 class_names.index(name) 就是做这个映射。如果之后改了 names 顺序旧 txt 的语义就全变了训练前务必重建一次标签。3. 环境配置与模型加载把教程 PDF 里的坑提前绕开3.1 两份环境配置 PDF 的版本组合逻辑资源里带了两份环境配置教程 PDF覆盖 yolov3 到 yolov8 的通用环境搭建。这类教程在 Windows 下最常见的路线是 Miniconda PyTorch GPU 版我复现时按最常见的组合走Python 3.8 CUDA 11.1 PyTorch 1.8.1 torchvision 0.9.1。这套组合和 yolov5 5.0 前后的代码兼容性最稳教程 PDF 里的步骤也基本是按这个方向讲的。组件推荐版本说明Python3.8yolov5 老版本代码兼容最好CUDA11.1匹配 cu111 的 wheelPyTorch1.8.1稳定不必追新torchvision0.9.1必须与 torch 1.8.1 配套opencv-python4.x负责图像读写和摄像头采集如果你的显卡较新也没关系驱动支持 CUDA 11 以上的都可以装这套。注意用 conda 创建独立环境别动系统 Pythonconda create -n yolo python3.8 -y conda activate yolo pip install torch1.8.1cu111 torchvision0.9.1cu111 -f https://download.pytorch.org/whl/torch_stable.htmlcu111 表示 CUDA 11.1 编译的 wheeltorchvision 必须跟 torch 对应。装完马上验证 GPU 是否真的能用import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))第二行输出 False说明装成了 CPU 版或驱动太旧。这个验证步骤虽然基础但能拦住后面一半以上的玄学报错。我见过机器有集显和独显两块卡CUDA 默认设备号不对推理跑到了核显上速度惨不忍睹排查半天才发现。3.2 用 test.py 跑一次推理参数说明环境配好后解压权重文件在项目根目录执行python test.py --weights best.pt --source data/test/images --conf 0.35 --img 640--weights 指向训练好的权重--source 可以是单张图片、整个目录、mp4 视频或摄像头索引--conf 是置信度阈值默认 0.25这个参数对红外小目标影响很大--img 是推理输入分辨率默认训练是 640 就保持 640。如果你的 test.py 是二次封装过的先执行 python test.py --help 看参数列表有些封装会把 --conf 改成 --thresh字段名不同。权重文件注意区分 best.pt 和 last.ptbest 是验证集上指标最优的保存点部署用它last 是最后一个 epoch 的保存点一般只用来继续训练。跑完输出在 runs/detect 下。第一次跑建议挑一张包含多个目标的红外帧单独试别直接上整个测试集——红外测试集里可能混着大图推理耗时长容易被误判成卡死。3.3 置信度阈值、输入尺寸对小目标的影响这套模型处理的是红外小目标目标在 640×640 输入里往往只占不到 20×20 像素。置信度阈值从 0.25 调到 0.35能肉眼可见地减少误检但也会把低置信度的真目标一起滤掉。我习惯先按 0.1 跑一遍把所有框的置信度记录下来观察真目标和误检的分布区间如果两者在 0.2 附近有明显分界就取中间值当阈值。只看一两张图就拍板阈值后面换一段视频必翻车。输入尺寸可以往下调--img 480 推理速度能快 30% 左右但小目标本来就小降分辨率等于自废武功。往上调到 1280 对小目标提升明显显存占用翻倍RTX 3060 12G 勉强够6G 就别想了。资源里的权重如果是在 640 下训练的推理用 1280 也能出框只是 CPU 上会慢到没法用。4. PyQt 界面拆解图片、视频、摄像头三路输入的实现思路4.1 share.py 与 test.py 的分工压缩包里代码文件不多share.py、test.py、init.py。从命名和调用关系看share.py 是公共推理模块负责加载模型、前处理、把推理结果整理成界面能显示的数据test.py 是 PyQt 主程序负责画界面、接按钮事件、显示画面init.py 把目录变成包让 test.py 里可以写 from share import Detector 这类导入。这种拆分比全部逻辑堆在一个文件里干净界面代码不需要知道模型内部怎么做推理换权重时只改 share.py 里的路径不动界面逻辑。配套的 pyqt5 使用说明 PDF 里应该有完整的界面操作流程照着点一遍就知道三个入口分别对应什么参数。我的建议是不要直接改 test.py 去适配你的业务先把 share.py 里的模型加载部分独立出来在命令行验证通了再接回界面。界面调试成本远高于命令行底层先跑稳再谈按钮和布局。4.2 视频和摄像头检测为什么必须用 QThread图片检测很简单按钮点击后拿文件路径调一次推理函数把结果图转成 QImage 显示到 QLabel耗时几百毫秒主线程卡一下能接受。但视频和摄像头是持续流如果每帧的读取、推理、显示都放在主线程里Qt 事件循环被长时间占用窗口会直接转圈假死。这不是代码 bug是线程模型问题。常见做法是开一个 QThread在 run() 里循环读帧推理完成后通过信号把结果帧抛回主线程刷新# 摄像头/视频检测线程骨架 from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectThread(QThread): frame_ready pyqtSignal(object) def __init__(self, detector, src0, every_n3): super().__init__() self.detector detector self.src src # 0 表示默认摄像头也可以是视频文件路径 self.every_n every_n # 每 N 帧做一次推理其余直接显示原帧 self._running True def run(self): cap cv2.VideoCapture(self.src) if not cap.isOpened(): self.frame_ready.emit(None) return idx 0 while self._running and cap.isOpened(): ok, frame cap.read() if not ok: break if idx % self.every_n 0: frame self.detector.infer(frame) self.frame_ready.emit(frame) idx 1 cap.release() def stop(self): self._running False self.wait()几个关键点every_n 用来降频推理比如每 3 帧才检测一次中间 2 帧直接显示原帧画面流畅度不会太差推理负载也降了stop 里的 wait() 是等线程内循环退出再关闭程序否则退出时容易崩detector.infer(frame) 返回画好框的帧界面槽函数收到后刷新 QLabel 就行。信号传递的是 numpy 数组pyqtSignal 里用 object 类型不要用 QImage——跨线程构造 QImage 容易出问题。4.3 摄像头打不开、画面延迟两个高频问题摄像头打不开十个里有八个是索引问题。笔记本自带摄像头不一定是 0外接 USB 摄像头可能占 1 或 2。先撇开 PyQt单独跑几行验证import cv2 cap cv2.VideoCapture(0) if not cap.isOpened(): print(camera 0 failed) else: ok, frame cap.read() print(ok, frame.shape if ok else None) cap.release()索引从 0 换到 1、2 逐个试。全打不开就检查摄像头是不是被其他软件占用Windows 下微信、钉钉这类软件占着摄像头PyQt 程序自然抢不到。画面延迟超过 200ms 时优先调大 every_n 而不是降分辨率——红外小目标对分辨率敏感。实在不行再把推理输入尺寸从 640 降到 416但最好在界面里留一个开关方便对比检测质量而不是拍脑袋定死。5. 避坑记录这套红外目标检测资源复现时的 5 个关键坑5.1 训练一开始 loss 就变成 nan现象用这套数据集在 yolov5 上开训前几十个 batch 的 loss 直接变成 nan训练中断。原因红外灰度图的像素分布和自然图像差异大默认学习率 0.01 对这个数据偏高前向传播过程梯度爆炸另外如果标签里类别 id 超出 nc也可能在 loss 计算阶段出问题。解决把学习率降到 0.001 并开启 warmupyolov5 的 hyp 文件里 lr0 和 lrf 都调小同时用 check_labels.py 确认所有 txt 的 class_id 在 0~3 以内。我复现时直接用低学习率的 hyp 配置文件全程没有出现 nan。5.2 Drone 类别漏检严重、Bird 误检一堆现象推理时 Bird 频繁出框Drone 几乎检不出或者把 Bird 大量识别成 Drone。原因类别框数量不平衡Bird 样本可能占了一半以上Drone 样本少模型学不够。低分辨率红外图里鸟和无人机外形高度相似错误分类会被放大。解决先跑统计脚本看各类别框数分布悬殊超过 5:1 就先做样本均衡而不是急着调网络结构。快速做法是在 data.yaml 里加类别权重或对少样本类别提高 loss 权重正规做法是补 Drone 标注数据用原始红外视频抽帧后只标 Drone。数据分布决定模型上限调参只是补救。5.3 PyQt 界面点开始按钮就转圈假死现象在 test.py 界面里点「开始检测」窗口立刻无响应过几分钟才恢复。原因推理循环写在了主线程的按钮槽函数里Qt 事件循环被阻塞界面无法处理重绘和鼠标事件。解决按 4.2 的 QThread 结构把视频读取和推理移到子线程主线程只负责接收信号并刷新 QLabel。改完以后即使推理慢窗口也能拖动、能点停止。这个坑只在视频和摄像头场景出现所以很多人图片功能调试正常一接摄像头就暴露。5.4 换了自己的数据集后报类别数不匹配现象把自己的标注数据替换进目录用资源里权重继续训练报类似 class id 4 not in names 的断言错误。原因data.yaml 的 names 改成了自己的类别但权重文件还是四类的输出头模型最后一层卷积通道数和新的类别数对不上旧训练缓存没有清理。解决换数据时删掉 runs/ 下之前训练产生的缓存文件。如果目标类别数量变化不要用旧权重直接续训先从 COCO 预训练权重开始或 --weights 从头训练。想省时间用资源原权重微调也可以前提是类别数和 names 完全一致。5.5 mAP0.5 很高但实际红外图漏检现象val 阶段 mAP0.5 到 0.8 以上看着很好实际对着红外监控画面跑很多真目标没框出来。原因mAP0.5 只看 IoU 阈值 0.5 的匹配红外小目标框只有十几个像素偏移五六个像素 IoU 就掉到阈值以下指标虚高掩盖了定位精度差同时测试集和真实场景分布有差异。解决评估时看 mAP0.5:0.95这是 COCO 风格的多 IoU 平均指标。两者差距大说明框不准复训时把输入分辨率提到 1280或对回归分支增加 IoU 损失权重。红外模型评估我一律先看 mAP0.5:0.95低于 0.5 就认为没到部署标准。6. 验证模型精度与进一步复训分类别 AP 与迁移调整6.1 用 val.py 输出分类别 AP 再决定下一步模型好不好不能只看总 mAP要把每个类别的 AP 单独打出来。用 val.py 跑一遍测试集python val.py --data data.yaml --weights best.pt --img 640 --conf 0.001 --iou 0.6--conf 0.001 是为了评估时不丢候选框--iou 0.6 是 NMS 的 IoU 阈值评估场景常用。跑完在 runs/val/exp 下生成 results.csv 和各类别 AP 明细。如果 Bird 的 AP 0.9、Drone 的 AP 0.4就别讨论模型结构了老老实实补 Drone 数据。如果四类 AP 都及格只是某个方向框偏了再考虑调输入尺寸或数据增强。6.2 冻结 backbone 的迁移复训参数要在自己的红外场景继续提升精度用资源里的权重做迁移学习是最快路径。我一般冻结前 10 层骨干参数只训练颈部加检测头python train.py --data data.yaml --weights best.pt --freeze 10 --img 640 --epochs 100 --batch 16同时把 hyp 文件里的 hsv_h 调成 0——红外图本质是灰度图色相增强不仅无效还会干扰训练hsv_s 也可以调低。最后确认 batch size 和显存匹配6G 显存跑 640 分辨率 batch 16 比较稳12G 可以上 batch 32。从那以后我每次拿到一套红外权重都强制先跑一遍 val.py 看分类别 AP再谈部署和二次复训——被 Drone 漏检坑过一次就长记性了。希望帮到你。本文还有配套的精品资源点击获取
返回列表