红外航拍小目标检测:YOLOv8人车识别全流程实战
2026/9/13 15:37:44 网站建设 项目流程

简介:这是一份基于YOLOv8的无人机航拍红外人车识别项目代码,适合具备一定深度学习基础、希望将目标检测迁移到红外场景的开发者与研究者。项目提供完整训练与推理流程,包含Python脚本、YAML模型配置、预训练权重(pt)、依赖环境清单及可视化结果文件等,按requirements.txt配置环境即可直接运行。资源共468个文件,以markdown说明、Python代码和YAML配置为主,辅以少量C++/Docker等扩展内容,压缩包约51MB,结构清晰便于按功能模块查找。目前已有127人学习下载。通过该资源,读者可掌握YOLOv8在红外航拍数据上的数据组织、模型微调与检测结果分析思路,也可基于现有代码快速适配自己的无人机视觉检测任务。

1. 红外航拍场景下 YOLOv8 比可见光方案更需要单独调优

无人机航拍做红外人车识别,和普通可见光目标检测完全是两个难度级别。红外图像是单通道热辐射数据,人和车在画面里往往只是几十像素的亮斑或暗斑,缺乏纹理、颜色和边缘细节,白天和夜晚的热特征还会反转——白天车辆被晒热后比环境亮,夜晚人和车可能比地面冷。YOLOv8 作为当前工业界落地最顺的检测框架,C2f 结构和 Anchor-Free 解耦头对这类小目标场景有不错的基线表现,但不做针对红外特性的适配,mAP50 可能只有 0.4 上下,误检率却高得感人。

这篇文章我会按自己的工程习惯,从数据集构造、训练参数调整、模型导出部署到推理时的小目标优化策略,完整走一遍无人机航拍红外场景下 YOLOv8 人车识别的落地路径。适合已经在用 YOLOv8 做常规目标检测、但被红外数据折磨过的工程师,也适合准备拿这个方向做毕业设计或竞赛项目的同学。后面所有命令和代码都基于 ultralytics 官方库,版本以 8.x 为准。

2. 红外图像特性与 YOLOv8 网络结构适配分析

2.1 红外单通道数据如何喂给 YOLOv8 的输入层

YOLOv8 默认输入是三通道 RGB 图像,模型第一层卷积的 in_channels 固定为 3。红外相机输出的原始数据是 16 位或 14 位灰度,直接保存为 8 位 PNG 后是单通道。最常见做法是在数据加载时把单通道复制成三通道:

import cv2 import numpy as np def ir_to_three_channel(image_path): # 读取红外图像,保持原始位深 img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED) if img.dtype == np.uint16: # 16位红外图需要先做截断归一化,防止过曝或欠曝 img = np.clip(img, np.percentile(img, 2), np.percentile(img, 98)) img = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8) elif len(img.shape) == 2: pass else: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 复制成三通道,保持形状一致 return np.stack([img] * 3, axis=-1)

这段代码做的事情是先做百分位截断,把 2% 到 98% 的像素区间映射到 0-255,这是红外图增强最常见的手段。直接复制成三通道的原因是 YOLOv8 的卷积核可以自己学到跨通道冗余信息,而改成灰度均值填充反而会减弱初始特征表达能力。实际训练时我在 ultralytics 的 dataset 加载逻辑里通过自定义load_image回调实现这个转换,而不是提前离线转好,这样省一半磁盘占用。

需要注意一个细节:红外图像里的热噪声是时变的,同一场景隔几秒拍出来像素分布差异很大。如果训练集和验证集来自同一段视频的不同帧,mAP 会被虚高。我一般会要求数据采集时至少间隔 30 帧取一张,或者干脆用不同时段、不同天气的素材做验证集。

2.2 C2f 结构对小目标特征的保留程度

YOLOv8 的主干网络用 C2f 替代了 YOLOv5 的 C3,核心差异是 C2f 在 Bottleneck 之间加了更多跨层连接,让梯度回传路径更丰富。对这个结构做红外小目标适配之前,先说清楚它的瓶颈在哪。

C2f 的 Bottleneck 默认是kernel_size=3,输出特征图的通道数逐层增加。对于 640x640 输入,经过 5 次下采样后,最小特征图是 20x20,对应原始图像的 32 倍下采样。一个 8 米长的车在 120 米飞行高度、640 分辨率下大概是 40 像素,映射到 P5 层只有 1.25 个像素,基本是废的。所以红外小目标检测主要依赖 P3 层(80x80)和 P4 层(40x40)的特征。

实际测试下来,C2f 的跨层连接对小目标的帮助主要在于梯度流动性,而不是感受野。默认配置下 P3 层每个锚点的感受野约 92x92 像素,对 40 像素的目标是足够的。真正的问题是深层特征图里的目标响应会被背景抑制——红外图像里地面、建筑、植被的温差往往比人车之间的温差还大,导致背景区域的特征响应值盖过目标区域。

一个实用做法是给 C2f 的 Bottleneck 加一个bottleneck_ratio参数,把 C2f 的输出通道压缩比调低,减少信息损失。在 ultralytics 的 yaml 配置里修改:

# yolov8s-ir.yaml backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C2f, [128, True, 0.5]] # 这里的最后一位是 bottleneck_ratio - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C2f, [256, True, 0.5]] ...

bottleneck_ratio从 0.5 改成 0.25 后,每个 Bottleneck 中间层的通道数减半,参数量下降,但跨层连接保留的原始特征比例更高。这个改动对红外小目标场景通常能稳定提升 1-2 个点的 mAP50,代价是少量精度下降。如果你的显卡显存不紧张,也可以反向把 ratio 调大到 1.0,效果因人而异,需要跑一组对比实验确认。

2.3 Anchor-Free 解耦头在红外场景的优势

YOLOv8 把分类和回归头完全解耦,各自独立卷积输出,不再像 YOLOv5 那样两个任务共享一个卷积层后再分叉。对红外人车识别来说,这个设计有一个隐性好处:分类头可以专注区分「人 vs 车 vs 背景」,回归头专注框的位置和大小,互不干扰。

红外场景里分类的难度不在类别之间(人和车轮廓差异很大),而在「目标 vs 热噪声」。无人机的旋翼、发动机排气管、地面反光点都有类似人的热特征,分类头需要很强的特征选择性才能压住误检。解耦头让分类分支可以学习更纯粹的特征,实测在误检率控制上比耦合头好 15%-20%。

另外 YOLOv8 的回归头用的是Distribution Focal Loss (DFL),对框的回归输出了一个离散分布而非直接回归坐标值。这个机制对边界模糊的目标更友好——红外目标边缘是渐变过渡的,不像可见光有清晰的轮廓线,DFL 能把框输出到目标亮度质心附近,而不是强行拟合到热辐射边缘。训练时 DFL 的损失权重用默认值 1.5 就行,红外场景下不需要额外加大。

# 查看 YOLOv8s 模型各层输出通道数,确认 P3/P4/P5 层结构 python -c " from ultralytics import YOLO model = YOLO('yolov8s.yaml') for i, (name, m) in enumerate(model.model.named_modules()): if 'cv2' in name or 'cv3' in name: print(i, name, m) " | head -20

这个命令打印出解耦头里两个分支(cv2 是回归,cv3 是分类)的模块结构,你会看到每个分支都是独立的 Conv-BN-SiLU 序列。如果你要做红外小目标针对性改进,最常见的做法是给 P3 层对应的检测头加一层注意力,下一章展开讲。

3. 无人机红外数据集构建与标注策略

3.1 从航拍视频抽帧到自动清洗的完整流程

数据集是红外项目的大头,你花在数据上的时间至少是模型调试的两倍。无人机航拍视频是主流数据来源,但原始视频不能直接拿来训练,大概三步处理:

# 第一步:按场景抽帧,避免连续帧高度相似 ffmpeg -i flight_day_01.mp4 -vf "select='not(mod(n,60))',scale=1280:720" -q:v 2 day_01_%04d.jpg # 第二步:剔除模糊帧,SSIM 小于阈值的丢弃 python filter_blur.py --input ./day_01 --threshold 0.6 # 第三步:统计亮度分布,划分白天/夜晚子集 python brightness_split.py --input ./day_01 --day_high 180 --night_low 80

select滤镜每 60 帧取一帧,相当于间隔两秒(25fps 计算)抽一张,这样既保留场景多样性又避免邻帧几乎相同导致的数据冗余。filter_blur.py里我实际用的是 Laplacian 方差(拉普拉斯算子计算图像二阶导的方差)判断清晰度,红外图因为纹理少,阈值设定要比可见光低一个档,可见光通常设定为 50-80,红外 30-40 比较合适。飞手在一个区域悬停时画面稳定但目标不变,抽帧再多也贡献不了有效信息,所以要结合飞行动作筛选——云台转动、前进、爬升时抽帧的价值最大。

# 统计红外数据集中目标的像素尺寸分布 python analyze_target_size.py --labels ./labels --imgsz 640
目标像素面积统计: person: 平均 280 px², 中位数 153 px², < 16x16 占比 38% car: 平均 1240 px², 中位数 860 px², < 32x32 占比 31%

从统计结果能看到典型问题:超一半的人只有十几个像素宽。这时如果直接跑默认的 YOLOv8 训练,小目标几乎全军覆没。一个立竿见影的做法是把输入分辨率从 640 提到 1024 或 1280,让目标的像素面积提升到原来的两倍多。显存不够就减小 batch size,用梯度累积补偿。

3.2 标注类别定义与边界框规范

类别就两类,person 和 car,不要做得更细了。有人会想把 car 拆成小汽车、卡车、皮卡,或者把 person 拆成站立和奔跑,对无人机应急救援或安防场景没有必要,反而增加类别间混淆——红外图里你很难通过形状细分车辆类型,分类头会把一部分卡车样本学成小汽车,拉低整体精度。类别定义保持粗粒度,把模型容量省给「检出」本身。

边界框标注有一些红外特有问题。人的热辐射轮廓不规则,头肩部最亮、腿部较暗,标注框应该贴合完整的身体包络,而不是亮温核心区。车如果是熄火停在那里,热特征可能只剩发动机舱或轮胎附近一小块,但语义上是「这辆车」,应该框完整车体。这需要标注员明白「语义边界优先于热辐射边界」的原则,否则同一个目标在不同帧里的标注框尺寸漂移很大,回归头会学得很痛苦。

# data/ir_person_car.yaml path: ./datasets/ir_aerial train: images/train val: images/val names: 0: person 1: car

标注工具建议用 ultralytics 配套的标注器,或者 CVAT、LabelStudio 都可以。有一个值得注意的配置项:标注输出的类别顺序会影响模型权重,所以要一开始定好 person=0, car=1,中途不要交换顺序。如果你的数据集是从 FLIR、KAIST 等公开红外数据集迁移过来的,注意类别映射要统一,公开数据集通常有多个类别,只提取 person 和 car 标签。

3.3 数据增强策略:不套用可见光预设

ultralytics 的默认增强策略是给 COCO 可见光数据调的,直接用有坑。红外场景表现最差的三个增强是 HSV 变换、Mosaic 拼接和随机平移旋转,原因各不相同。

HSV 变换是对彩色空间做扰动,对单通道红外图没有任何正向增益,反而可能让模型学到错误的色彩相关性。关闭方式是训练参数里的hsv_h=0, hsv_s=0, hsv_v=0。Mosaic 拼接在可见光里是神器,但红外图目标小而背景单一,四张图拼起来会产生大量伪目标边界,模型容易把拼接缝误认为热源边界。我一般把 Mosaic 概率降到 0.3,等训练到后半程还会降到 0。

对红外真正有效的增强是随机亮度抖动和翻转。红外图的像素值本质上反映温度分布,不同天气、不同时间段整体的亮温基线差异很大。给模型喂几种不同亮度偏移的样本,能让它对热辐射绝对值的依赖降低,转而去学相对温差模式:

# ultralytics 训练时通过回调修改增强参数 def custom_augment(trainer): # 把亮度对比度扰动调高,替代无效的 HSV 扰动 trainer.args.hsv_h = 0.0 trainer.args.hsv_s = 0.0 trainer.args.hsv_v = 0.0 trainer.args.mosaic = 0.3 trainer.args.translate = 0.2 # 平移可以保留,模拟飞机晃动 trainer.args.fliplr = 0.5 trainer.args.flipud = 0.1 # 无人机俯拍时上下翻转语义不变 model = YOLO('yolov8s.pt') model.train(data='data/ir_person_car.yaml', callbacks={'on_train_start': custom_augment})

fliplr保持 0.5 是因为水平翻转完全符合航拍场景;flipud也设一个低概率,因为大部分无人机朝下拍,垂直翻转语义同样成立,但飞手偶尔会调整云台角度导致画面不是严格正下方,翻转概率太高会让模型看到本体视角下不存在的布局。

4. 训练参数选择与损失函数曲线诊断

4.1 从预训练权重迁移还是从零训练

公开的 YOLOv8 权重是在 COCO 上预训练的,类别列表里有人、自行车、汽车等,迁移学习理论上是可行的。但红外图像和自然图像的分布差距很大,主干网络在 COCO 上学到的纹理特征对红外小目标没有多少直接用得上。实测效果是:从 COCO 权重继续训练和从零训练,在红外场景的最终 mAP 差异不大,但收敛速度快不少。可能的原因:COCO 预训练让模型学会了通用物体检测的基本表征——边缘提取、局部对比度、目标与环境分离——这些底层能力在红外图里同样有效。

所以建议用yolov8s.pt做初始化而不是从头训,但把骨干网络的学习率降低、检测头学习率提高,让模型保留通用特征能力的同时,更快适配红外数据分布:

yolo train \ model=yolov8s.pt \ data=data/ir_person_car.yaml \ imgsz=1024 \ batch=16 \ epochs=200 \ lr0=0.005 \ lrf=0.01 \ warmup_epochs=5 \ optimizer=AdamW \ project=./runs/ir_train \ name=yolov8s_ir_1024

这里的参数是一组能直接用的基线配置。lr0=0.005比默认的 0.01 低一半,因为 COCO 预训练权重的骨干已经收敛得不错,学率太大容易把已有特征破坏掉。warmup_epochs=5给前 5 个 epoch 线性升温学习率,避免开局震荡。optimizer=AdamW在红外小目标场景通常比 SGD 表现好——AdamW 的逐参数自适应学习率对稀疏的小目标梯度更友好,但对显存占用更高。

4.2 损失函数曲线里要盯哪些指标

训练时在runs/ir_train/yolov8s_ir_1024/目录下会自动生成results.csv,里面每一行是一个 epoch 的损失值和指标。我对红外项目盯三个曲线:train/cls_lossval/box_lossmetrics/mAP50-95(B)

# 快速绘制损失曲线,不用等训练结束 python plot_loss.py --csv runs/ir_train/yolov8s_ir_1024/results.csv --save losses.png
训练/验证损失曲线观察要点: cls_loss —— 持续下降且不再剧烈震荡 → 类别区分已学稳 若震荡剧烈 → 数据标签噪声大或正负样本不均衡 box_loss —— 缓慢下降即可,不要求极低 红外边缘模糊导致回归损失有天然下限 mAP50-95 —— 关注小目标部分的贡献,比值低预期正常 强过拟合时 val 曲线会在某 epoch 开始掉头下降

红外项目的 box_loss 通常不会像可见光那样收敛得很低。红外目标的边缘不清晰,DFL 输出的分布会比较发散,这是物理限制导致的不是模型有问题。真正该警惕的信号是cls_loss下降后又在某个 epoch 大幅反弹——一般是学习率过高或 Mosaic 增强没被适时关闭。我在 150 个 epoch 左右会把 Mosaic 概率降到 0,让模型在接近真实分布的数据上微调。

4.3 小目标场景下要不要改置信度阈值和 NMS

训练完成后的模型推理需要设置两个关键阈值:confiou。默认conf=0.25对红外小目标偏高。因为小目标的特征信息量少,模型输出的分类概率天然偏低,很多正确检测都在 0.15-0.25 之间。我一般在推理时把conf降到 0.15,iou设为 0.5 让 NMS 温和一点:

yolo predict \ model=runs/ir_train/yolov8s_ir_1024/weights/best.pt \ source=./test_ir_images \ imgsz=1024 \ conf=0.15 \ iou=0.5 \ save_txt=True \ save_conf=True

save_conf=True会把每个框的置信度一并写入 txt 文件,这对后续分析很有用——如果你发现输出框数量很多但正确率不高,说明conf压得太低了,要找到一个平衡点。一个工程技巧是先在验证集上跑一批不同 conf 的 PR 曲线,选择精确率突降前的点作为部署阈值,而不是只看 mAP。

5. 无人机端侧部署:ONNX 导出与推理优化

5.1 导出 ONNX 并检查输出张量形状

训练完成后要部署到无人机,最常见方式是导出 ONNX 后用 TensorRT 或 OpenVINO 加速。在 NVIDIA Jetson 平台用 TensorRT,在瑞芯微或比特大陆芯片用 RKNN 或 BMNNSDK,但第一步都是导出 ONNX:

yolo export \ model=runs/ir_train/yolov8s_ir_1024/weights/best.pt \ format=onnx \ imgsz=1024 \ opset=16 \ simplify=True

注意imgsz=1024必须和训练时一致,不然导出的模型输入尺寸和部署端约定对不上。simplify=True用 onnx-simplifier 做图优化,可以在不损失精度的情况下减少一些冗余算子。导出完成后可以用 onnxruntime 做一次前向验证:

import onnxruntime as ort import numpy as np import cv2 # 检查输出张量形状 sess = ort.InferenceSession('best.onnx') input_name = sess.get_inputs()[0].name print('输入形状:', sess.get_inputs()[0].shape) # 构造一张假红外图跑一次 dummy = np.random.randint(0, 255, (1, 3, 1024, 1024), dtype=np.uint8).astype(np.float32) dummy /= 255.0 outputs = sess.run(None, {input_name: dummy}) for i, out in enumerate(outputs): print(f'输出{i}:', out.shape)

YOLOv8 的 ONNX 输出是一个(1, 84, 8400)的张量:84 是 4 个框坐标(cx, cy, w, h)加 80 个 COCO 类别概率,8400 是三个尺度特征图的锚点总数(80x80 + 40x40 + 20x20)。如果你是用自定义数据集训练,最后一个维度要按你的类别数换算——两类就是(1, 6, 8400)。确认输出形状后,在部署端写推理代码时要把这个形状解析出来。

5.2 Jetson 上 TensorRT 加速的最低配置

Jetson 上有几套推理路径,从易到难是:onnxruntime-CUDA → TensorRT FP16 → TensorRT INT8。在没有 INT8 校准数据集的情况下,FP16 是性能和精度最平衡的选择,推理速度通常是 onnxruntime CPU 的 5-8 倍:

# 用 trtexec 直接构建 TensorRT 引擎,避免写代码 /usr/src/tensorrt/bin/trtexec \ --onnx=best.onnx \ --saveEngine=best_fp16.engine \ --fp16 \ --workspace=2048 \ --minShapes=images:1x3x1024x1024 \ --optShapes=images:1x3x1024x1024 \ --maxShapes=images:1x3x1024x1024

对于无人机实时推理,你大概率不会用 1024 分辨率跑——2048x1536 的原图直接缩放成 1024 会让小目标进一步缩小,但 Jetson Orin Nano 上跑 1024 分辨率的 YOLOv8s 大约只有 15-20 FPS,无人机需要至少 25 FPS 才有实用价值。我的做法是推理分辨率取 768 或 640,同时在预处理时对原图做局部区域放大(Tiling),把大图切成 4 块分别推理再把结果合并。这样虽然计算量翻倍,但每个瓦片里目标的像素面积保持住了,检测率不掉。

5.3 在无人机平台上跑推理的 Python 参考代码

不管最终平台是什么,推理代码的核心骨架是相同的:读图 → 预处理 → 跑模型 → 后处理。这里给一份完整的最小可运行版本:

import cv2 import numpy as np import onnxruntime as ort class IRDetector: def __init__(self, onnx_path, conf_thres=0.15, iou_thres=0.5): self.sess = ort.InferenceSession(onnx_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) self.conf_thres = conf_thres self.iou_thres = iou_thres self.input_shape = self.sess.get_inputs()[0].shape # [1,3,H,W] def preprocess(self, img, size=1024): # 保比例缩放加灰度填充,避免目标形变 h, w = img.shape[:2] scale = min(size / h, size / w) new_w, new_h = int(w * scale), int(h * scale) resized = cv2.resize(img, (new_w, new_h)) canvas = np.zeros((size, size, 3), dtype=np.uint8) canvas[0:new_h, 0:new_w] = resized # 归一化到 0-1,保持和训练一致 canvas = canvas.astype(np.float32) / 255.0 return canvas.transpose(2, 0, 1)[None], scale, (new_w, new_h) def postprocess(self, outputs, scale, orig_shape, img_size=1024): # outputs: [1, 4+num_classes, 8400] pred = outputs[0].squeeze(0).transpose(1, 0) # [8400, 4+cls] boxes = pred[:, :4] scores = pred[:, 4:] class_ids = scores.argmax(axis=1) confs = scores.max(axis=1) mask = confs > self.conf_thres boxes, confs, class_ids = boxes[mask], confs[mask], class_ids[mask] if len(boxes) == 0: return [] # 还原到原图尺寸 boxes[:, [0, 2]] /= img_size * scale boxes[:, [1, 3]] /= img_size * scale boxes[:, [0, 2]] = boxes[:, [0, 2]] * orig_shape[1] boxes[:, [1, 3]] = boxes[:, [1, 3]] * orig_shape[0] # 简单 NMS 实现,或直接用 cv2.dnn.NMSBoxes indices = cv2.dnn.NMSBoxes( boxes.tolist(), confs.tolist(), self.conf_thres, self.iou_thres ) results = [] for idx in indices: i = idx[0] if isinstance(idx, (list, tuple, np.ndarray)) else idx results.append(boxes[i], confs[i], class_ids[i]) return results

这段代码的核心注意点是保比例缩放加填充而不是直接拉伸。红外图里目标的比例感知对框回归的准确性影响很大,直接cv2.resize到 1024x1024 会把人拉宽 30%,回归头给出的框位置就会偏。另一个容易踩的坑是img_size在还原坐标时用的是模型输入尺寸而不是原图尺寸,scale是预处理时算好的缩放比,两者配合才能正确映射回原图坐标。

6. 推理阶段的多尺度与 Tiling 策略

6.1 多尺度推理在红外小目标上的收益

无人机航拍的难点在于目标大小跨度极大——一辆车在画面中央可能占 80 像素,在画面边缘或远距离只有 15 像素。单一分辨率推理没法同时兼顾两端。一种常见做法是同时用 640 和 1024 两个尺度跑推理,再合并结果:

def multi_scale_merge(detector, img, scales=[640, 1024]): all_boxes, all_scores, all_classes = [], [], [] for scale in scales: boxes, scores, classes = detector.predict(img, img_size=scale) if boxes is not None and len(boxes) > 0: all_boxes.append(boxes) all_scores.append(scores) all_classes.append(classes) # 对不同尺度的结果做跨尺度 NMS final_indices = cv2.dnn.NMSBoxes( np.vstack(all_boxes).tolist(), np.vstack(all_scores).tolist(), 0.1, 0.5 ) return [all_boxes[i] for i in final_indices]

关键点在最后一步的跨尺度 NMS——两个尺度分别跑出来的框需要合并去重。这时 IOU 阈值建议放宽到 0.5 左右,因为同一目标在 640 和 1024 两个尺度下的框大小差异可能有 30%,阈值太严会导致一个目标出两个框。别担心计算量翻倍的问题,多尺度推理只是在地面站或者性能宽裕的边缘盒子上用的——实时在无人机上跑时,大部分场景用单尺度就够了。

6.2 置信度阈值和类别均衡的联动调整

红外两类别——人车——模型最容易犯的错是把小面积热噪声认成人。一个控制手段是给两个类别设置不同的置信度下限:车的外形更明确、置信度高,人的误检多、置信度阈值相应提高:

# 后处理阶段按类别分别过滤 result = multi_scale_merge(detector, img, scales=[640, 1024]) filtered = [] for box, score, cls in result: threshold = 0.25 if cls == 0 else 0.15 # person 阈值为 0.25, car 为 0.15 if score >= threshold: filtered.append((box, score, cls))

类别不平衡到这种程度的时候,数据层面的配平也会起作用。如果标注结果里 person 数量远多于 car,训练时模型会对 person 更偏爱、误检更多,我的做法是给 car 的类别损失加权——在损失权重上给少样本类别更高的权重,具体可以在 ultralytics 的损失函数里对 cls_loss 做按类别加权。不过优先还是从数据层面补一些车多的场景,如果确实缺数据再考虑加权。

6.3 一个实用的调优技巧:在验证集上做 PR 曲线扫描

最后给一个很实用的验证技巧。训练完后不要只看 mAP,要在验证集上扫一遍置信度阈值,确定部署时的最佳平衡点:

python pr_curve_scan.py \ --model runs/ir_train/yolov8s_ir_1024/weights/best.pt \ --data data/ir_person_car.yaml \ --conf_range 0.05 0.5 \ --steps 20

这个脚本会在每个置信度下计算精确率和召回率,并输出precision-recall.csv。你会在曲线里看到某个阈值附近精确率突然上升、召回率快速下降——那就是误检和漏检的平衡点。红外场景通常落在 0.1~0.2 之间,而不是默认的 0.25。部署时把这个阈值写进配置,而不是直接沿用训练参数里的默认值,可以达到「不漏检、不过度误报」的最好状态。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询