简介:本资源面向红外场景下的目标检测学习者与开发者,提供一套可直接复用的YOLOv5红外微小无人机、直升机、飞机、飞鸟检测方案,适合课程设计、科研实验与算法对比等场景。压缩包共约2000个文件,以1994个XML标注文件为主,另含3个Python脚本与3份PDF教程,整体约587MB,标注已整理为YOLO格式的txt标签,并划分好train、val、test,附有data.yaml,类别为Airplane、Bird、Drone、Helicopter四类,YOLOv5、YOLOv7、YOLOv8等算法均可直接训练。配套PyQt界面支持检测图片、视频及调用摄像头,并提供相应选择项,便于快速验证模型效果。已有548人学习下载,读者可据此获得训练好的权重、4000余张红外数据集、环境配置与PyQt使用说明,以及可运行的检测脚本,省去数据整理与界面搭建成本。
1. 红外小目标检测:为什么 4000 张数据集比模型结构更值得先看
可见光下巴掌大的无人机在 300 米外只剩几个像素,换成红外成像,它反而因为发动机热辐射变成一块亮斑——这就是很多人转做红外微小目标检测的直接原因。但真正上手后你会发现,卡住进度的往往不是 YOLOv5 的网络结构,而是手头那 4000 张红外数据集怎么标、怎么分、怎么增强,以及权重加载后 PyQt 界面里为什么框飘。这个方案要解决的是:在红外场景下,把无人机、直升机、飞机、飞鸟四类目标稳定检出,并封装成一个能点按钮就出结果的桌面工具。适合已经跑通过 YOLOv5 官方 Demo、想往红外小目标方向落地,或者手里有一批红外视频但不知道怎么变成可训练数据集的从业者。下面按数据、训练、推理、界面、排错五段推进,每一步都给出可复现的命令和参数。
2. 红外 4000 张数据集:从原始帧到 YOLO 标签的完整处理链
2.1 四类目标的标注边界怎么定才不打架
红外图像里飞鸟和无人机的形态在低分辨率下高度相似,都是几个到十几个像素的亮斑。如果标注时凭感觉画框,训练出来的模型会把两类互相误检。我一般按三条硬规则定边界:第一,看运动轨迹,飞鸟扑翼频率高、轨迹上下起伏,无人机多为平稳直线或悬停;第二,看热辐射分布,固定翼飞机和直升机在红外里通常有机身热区拖尾,飞鸟热区集中在躯干且面积更小;第三,看帧间一致性,连续 5 帧以上保持同一运动模式的才归入对应类别。标注工具用 LabelImg 或 CVAT 都行,导出 YOLO 格式,每张图对应一个同名 txt,每行class x_center y_center width height,坐标归一化到 0 到 1。
提示:四类目标的 class id 建议固定为 0 无人机、1 直升机、2 飞机、3 飞鸟,后续训练、推理、界面显示全部沿用这一套,中途改 id 是血泪教训。
2.2 4000 张怎么切分与增强才不泄漏
4000 张如果随机切分,同一段视频的相邻帧会同时进训练集和验证集,验证指标虚高。正确做法是按视频源切分:假设数据来自 20 段红外视频,取 14 段做训练、3 段做验证、3 段做测试,保证同一目标不同帧不跨集。增强方面,红外图像不适合做色彩抖动,但适合做亮度扰动、高斯噪声、随机缩放和平移。下面是一个可抄的切分与增强脚本片段。
import os, random, shutil from pathlib import Path # 按视频源前缀分组,避免同一段视频的帧跨集泄漏 def split_by_source(img_dir, out_dir, ratios=(0.7, 0.15, 0.15)): imgs = sorted(Path(img_dir).glob("*.jpg")) groups = {} for p in imgs: # 文件名前缀视为视频源,例如 vid03_frame0122.jpg -> vid03 src = p.stem.split("_")[0] groups.setdefault(src, []).append(p) sources = list(groups.keys()) random.seed(42) random.shuffle(sources) n = len(sources) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": sources[:n_train], "val": sources[n_train:n_train + n_val], "test": sources[n_train + n_val:], } for split, srcs in splits.items(): for s in srcs: for p in groups[s]: dst = Path(out_dir) / split / "images" dst.mkdir(parents=True, exist_ok=True) shutil.copy(p, dst / p.name) # 同步复制同名标签 lbl = p.with_suffix(".txt") if lbl.exists(): ldst = Path(out_dir) / split / "labels" ldst.mkdir(parents=True, exist_ok=True) shutil.copy(lbl, ldst / lbl.name) split_by_source("raw_images", "dataset", ratios=(0.7, 0.15, 0.15))这段脚本的核心逻辑是split_by_source按文件名前缀分组,random.seed(42)保证可复现,ratios控制训练验证测试比例。参数上,如果视频源少于 10 段,建议改成 0.8/0.1/0.1,避免验证集太小导致指标抖动。复制时同步搬运同名 txt,漏掉标签是新手最常见的翻车点。
2.3 生成 data.yaml 与目录结构自检
YOLOv5 训练前必须有一份 data.yaml 指向训练、验证、测试路径和类别名。目录结构建议统一为dataset/images/train、dataset/labels/train这种并列形式。下面是对应的 yaml 和自检命令。
# data_ir.yaml path: ./dataset train: images/train val: images/val test: images/test nc: 4 names: ['uav', 'helicopter', 'airplane', 'bird']# 自检:统计每个 split 的图片数和标签数是否一致 for s in train val test; do echo "$s images: $(ls dataset/images/$s | wc -l)" echo "$s labels: $(ls dataset/labels/$s | wc -l)" donenc必须等于 names 长度,写错会直接报维度不匹配。自检命令输出两个数字不一致时,说明有图片没标签或有标签没图片,训练时会被静默跳过,指标自然上不去。
3. 用 YOLOv5 在红外数据上训练:参数怎么设、权重怎么选
3.1 从官方权重微调还是从头训
红外图像和可见光图像分布差异大,但底层边缘、纹理特征仍有可迁移性。我一般用 YOLOv5s 或 YOLOv5m 的官方权重做初始化,冻结前几层先跑 10 个 epoch,再解冻全量训练。4000 张的规模,从头训容易过拟合,微调收敛更快。命令如下。
python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data data_ir.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --freeze 10 \ --project runs_ir \ --name exp01--img 640是输入尺寸,红外小目标如果原图分辨率高,可以试 960 但显存翻倍。--batch 16在 8G 显存上比较稳,爆显存就降到 8。--freeze 10冻结前 10 层,适合微调初期。--hyp用 low 增强配置,因为红外不适合强色彩增强。训练日志里重点看mAP@0.5和mAP@0.5:0.95,前者对小目标更敏感。
3.2 小目标检测的三个必调参数
红外微小目标在 640 输入下可能只有 4 到 8 个像素,默认 anchor 偏大,召回上不去。第一,调整 anchor,用python utils/autanchor.py --data data_ir.yaml重新聚类;第二,把--img提到 960 或 1280,代价是显存和推理时间;第三,在 hyp 里把box损失权重从 0.05 提到 0.08,让模型更关注定位。下面是对应修改片段。
# hyp_ir.yaml 关键项 box: 0.08 # 提高定位损失权重,小目标更敏感 cls: 0.5 # 分类损失保持默认 obj: 1.0 # 目标置信度损失 scale: 0.6 # 随机缩放幅度,小目标场景别太大 mosaic: 0.5 # 马赛克增强减半,避免小目标被拼没scale和mosaic调低是因为强增强会把小目标缩到消失或拼到边缘,反而伤害训练。改完 hyp 后重新跑训练,观察验证集里小目标的召回曲线是否抬升。
3.3 训练过程怎么判断该停还是该调
看runs_ir/exp01/results.csv,如果训练损失持续下降但验证 mAP 连续 20 个 epoch 不涨,就是过拟合,该早停或加数据。如果训练和验证都低,是欠拟合,加 epoch 或换更大模型。如果某一类 mAP 明显低于其他三类,比如飞鸟只有 0.3,回去查该类标注数量和标注质量。我一般用 TensorBoard 看每类 PR 曲线,比只看总 mAP 有用得多。
tensorboard --logdir runs_ir4. PyQt 界面集成:把权重变成能点按钮的桌面工具
4.1 界面线程与推理线程怎么分离
PyQt 主线程负责界面刷新,推理如果直接跑在主线程,点一次检测界面就卡死。正确做法是把 YOLOv5 推理放在 QThread 子线程里,通过信号槽把结果传回主线程画框。下面是最小可运行结构。
from PyQt5.QtCore import QThread, pyqtSignal import cv2, torch class InferThread(QThread): finished = pyqtSignal(object, object) # 原图, 检测结果 def __init__(self, model, img_path, conf=0.25, iou=0.45): super().__init__() self.model = model self.img_path = img_path self.conf = conf self.iou = iou def run(self): img = cv2.imread(self.img_path) # YOLOv5 推理接口,size 与训练时保持一致 results = self.model(img, size=640).pandas().xyxy[0] self.finished.emit(img, results)conf是置信度阈值,红外小目标建议从 0.25 起调,太低误检多,太高漏检多。iou控制 NMS 重叠阈值,四类目标重叠少可以设 0.45。size必须和训练输入一致,否则框会偏。信号finished把原图和 DataFrame 结果传回主线程,主线程再用 QPainter 或 OpenCV 画框后转 QImage 显示。
4.2 权重加载与四类颜色映射
界面里加载权重用torch.hub.load或直接attempt_load,四类目标用固定颜色区分,避免每次随机变色导致用户困惑。下面是一段颜色映射和画框逻辑。
COLORS = { 'uav': (0, 255, 0), # 绿 'helicopter': (255, 0, 0), # 蓝 'airplane': (0, 0, 255), # 红 'bird': (255, 255, 0), # 青 } def draw_boxes(img, df): for _, row in df.iterrows(): x1, y1, x2, y2 = map(int, [row['xmin'], row['ymin'], row['xmax'], row['ymax']]) name = row['name'] color = COLORS.get(name, (255, 255, 255)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, f"{name} {row['confidence']:.2f}", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return imgCOLORS字典的键必须和 data.yaml 里的 names 完全一致,大小写错了就取不到颜色。row['confidence']显示两位小数,方便现场判断可信度。画框线宽 2 在 640 图上够用,1280 图建议加到 3。
4.3 批量检测与结果导出
界面除了单张检测,通常还要支持文件夹批量跑并导出 CSV。批量时不要每张都重新加载模型,模型只加载一次,循环调用推理。导出 CSV 包含文件名、类别、置信度、四个坐标,方便后续统计。
import pandas as pd, os def batch_infer(model, folder, out_csv, conf=0.25): rows = [] for f in os.listdir(folder): if not f.lower().endswith(('.jpg', '.png', '.bmp')): continue path = os.path.join(folder, f) df = model(path, size=640).pandas().xyxy[0] df = df[df['confidence'] >= conf] for _, r in df.iterrows(): rows.append({ 'file': f, 'class': r['name'], 'conf': round(r['confidence'], 3), 'xmin': int(r['xmin']), 'ymin': int(r['ymin']), 'xmax': int(r['xmax']), 'ymax': int(r['ymax']) }) pd.DataFrame(rows).to_csv(out_csv, index=False, encoding='utf-8-sig')encoding='utf-8-sig'是为了 Excel 打开中文不乱码,这个细节很多人踩过。conf在批量时通常比单张调高一点,比如 0.3,减少导出里的噪声行。
5. 避坑与排查:红外小目标检测最常见的五个翻车点
5.1 验证 mAP 很高但实际框飘
现象是训练日志 mAP@0.5 到 0.9,实际跑视频框位置偏移明显。原因多半是训练和推理的输入尺寸或 letterbox 方式不一致。解决是确认推理时size与训练--img相同,并检查推理代码是否用了同样的 letterbox 填充。YOLOv5 默认 letterbox 会保持长宽比,如果自己写预处理忘了这步,坐标反算就错。
5.2 飞鸟被大量误检成无人机
现象是飞鸟类召回低、无人机类误检高。原因是两类在低分辨率下形态接近,且标注时边界规则没统一。解决是回到 2.1 的三条规则重新审查标注,把模棱两可的帧剔除或单独归为困难样本,再在 hyp 里适当提高cls权重让分类更敏感。
5.3 训练 loss 变 NaN
现象是几个 epoch 后 loss 突然 NaN。原因通常是学习率过高或某张图标签坐标越界。解决是先把--lr0从 0.01 降到 0.001,再写脚本检查所有 txt 里坐标是否在 0 到 1 之间,越界的直接修正或删除。红外数据集如果从其他格式转换而来,坐标越界很常见。
5.4 PyQt 界面点检测后无响应
现象是按钮点下去界面卡住,几秒后恢复或直接崩溃。原因是推理跑在主线程。解决是按 4.1 把推理放进 QThread,并确保信号槽连接正确。另一个隐蔽原因是模型加载放在按钮回调里,每次点击都重新加载权重,改成启动时加载一次即可。
5.5 批量导出 CSV 中文乱码
现象是 Excel 打开 CSV 类别名显示乱码。原因是默认编码不是带 BOM 的 UTF-8。解决是to_csv时加encoding='utf-8-sig',这个参数在 4.3 的代码里已经带上,照抄即可。
6. 进阶技巧:用切片推理把 1280 图上的小目标召回再抬一截
红外小目标在整图缩放到 640 后可能只剩 3 到 4 个像素,召回天然受限。一个我常用的技巧是切片推理:把 1280 或更大原图切成带重叠的 640 小块,每块单独推理,再把结果按原图坐标合并做一次 NMS。这样小目标在局部块里相对变大,召回明显提升,代价是推理时间成倍增加。下面是一个可复现的切片推理实现。
import cv2, numpy as np, torch def sliced_infer(model, img_path, slice_size=640, overlap=128, conf=0.25, iou=0.45): img = cv2.imread(img_path) h, w = img.shape[:2] step = slice_size - overlap boxes = [] for y in range(0, h, step): for x in range(0, w, step): x2 = min(x + slice_size, w) y2 = min(y + slice_size, h) x1 = max(0, x2 - slice_size) y1 = max(0, y2 - slice_size) patch = img[y1:y2, x1:x2] df = model(patch, size=slice_size).pandas().xyxy[0] for _, r in df.iterrows(): if r['confidence'] < conf: continue boxes.append([ r['xmin'] + x1, r['ymin'] + y1, r['xmax'] + x1, r['ymax'] + y1, r['confidence'], r['class'] ]) # 跨块 NMS,避免重叠区域重复框 boxes = torch.tensor(boxes) keep = torch.ops.torchvision.nms(boxes[:, :4], boxes[:, 4], iou) return boxes[keep]slice_size和训练输入一致,overlap一般取 slice_size 的 20% 到 30%,太小会漏掉跨块目标,太大推理次数增加。合并后必须做一次全局 NMS,否则重叠区同一目标会出现多个框。这个技巧在 1280 以上红外图上收益最大,640 图上提升有限,是否启用看你的实时性要求。
参数上还有两个可以微调的点:一是conf在切片推理时可以比整图推理低一点,比如 0.2,因为局部块里目标更清晰,误检反而少;二是如果四类目标尺寸差异大,可以按类别设不同 slice_size,飞机用大块、飞鸟用小块,但这样代码复杂度上升,建议先跑通统一尺寸再优化。
我自己的习惯是,任何红外小目标方案上线前,先用切片推理和整图推理各跑一遍测试集,对比每类召回和误检,再决定线上用哪种。这个对比表比任何主观判断都可靠。希望帮到你。
本文还有配套的精品资源,点击获取