红外图像里的目标检测,一直是视觉工程里又爱又恨的活儿。爱的是它能在夜间、雾天、强逆光这些可见光几乎报废的场景里稳定输出,恨的是它给你看的图实在谈不上友好:单通道灰度、对比度低、边缘糊成一片,目标往往就是一团亮斑,没有颜色、没有纹理、没有清晰的轮廓。你高高兴兴地把在COCO上训好的YOLO权重拿过来直接推理,mAP大概率是断崖式下跌,小目标漏成筛子,检测框在帧与帧之间抖得跟心电图似的。这一讲,就是围绕“红外图像目标检测”这个场景,聊聊我是怎么把YOLO和动态规划配套使用,用前者解决“空间上目标在哪”,用后者解决“时间上轨迹怎么串”,最终把这套方案落到一个稳定可用的状态。如果你是刚接触红外目标检测,或者正在为“单帧检测没问题但一上序列就露馅”发愁,这一讲应该能给你一个相对完整的解决方案。
1. 红外图像目标检测:它难在哪,为什么不能直接套用通用模型
1.1 红外图像与可见光图像的本质差异
红外热像仪接收的是物体自身的热辐射差异,工作波段常见的是3到5微米的中波红外和8到14微米的长波红外。目标只要和环境存在温差,就会在图像上表现为亮斑或暗斑,这个特性和可见光靠反射成像完全不同。可见光图像里的行人、车辆有丰富的边缘、颜色、阴影信息,而在红外图像里,这些信息几乎全部丢失。一个穿着深色衣服站在墙边的人,在可见光下很容易被检测出来,但在红外下他和背景的温差可能只有一两度,整个目标几乎被背景吞没。
这就是红外目标检测的第一个本质难点:信噪比低、目标淹没在背景杂波里。
另外一个容易被忽视的问题是红外图像的噪声结构。探测器像元响应不一致会带来非均匀性噪声,表现为固定的条纹状图案;随机噪声和坏点在图像上形成大量孤立亮点。这些亮点在灰度上和真正的小目标形态高度相似,肉眼都容易认错,更别说直接丢给目标检测模型。
1.2 通用YOLO模型直接迁移的三个典型翻车现场
我在实际项目里观察下来,直接把可见光预训练权重迁移到红外场景,几乎必然遇到三个问题。
第一个是预训练特征不匹配。YOLO的卷积核在ImageNet和COCO上学到的颜色、纹理特征,在红外单通道图像上根本没有对应物。输入从三通道变成一通道,第一层卷积的感受野就像翻译官突然到了一个语言不通的国度,特征图的激活模式完全乱套。
第二个是小目标漏检。红外场景里,目标在距离较远时往往只有几个像素到十几个像素大小。YOLO系列的主干网络逐层下采样,到最后特征图尺寸已经缩小到输入的八分之一甚至更小,几个像素的小目标在经过多次下采样后,特征几乎被抹平了。你调低置信度阈值能捞回来一些,但误检率也会同步飙升。
第三个是帧间检测结果不稳定。单帧视角下,置信度最高的框可能就是目标,但是这个框的坐标和大小在相邻帧之间经常发生非连续跳变。红外目标边缘模糊,检测框的定位精度天然低,逐帧独立检测出框后不做时序约束,表现就是在视频上目标框忽大忽小、偶然消失又出现,根本无法交付给下游的跟踪或者告警模块。
1.3 检测加关联的双层次破局思路
针对上面这些问题,我采用的思路不是一味换更大更强的检测模型,而是把整个任务拆成两个层次:检测层和后处理层。检测层仍然用YOLO,只负责在当前帧内找出所有可能是目标的候选框,重点是提高召回率,宁可多框出来一些也不要漏掉真实目标。后处理层用动态规划,把连续多帧的候选框按时间顺序串起来,利用目标运动的连续性和平稳性,把孤立的误检剔除掉,把短暂漏检补回来,最终输出平滑稳定的轨迹。
这样做的好处是模块职责清晰。检测模型只需要做它最擅长的事:在单帧图像里找目标。至于哪些框是真实目标、哪些是噪声,交给动态规划在时间维度上做全局最优决策。单帧检测置信度的抖动,在时间序列上会被平滑掉,这是任何一个只在单帧里做后处理的算法都做不到的。
2. 数据集与预处理:把红外图像调教成模型能吃的形状
2.1 数据集从哪来:公开资源与自建方案
做红外目标检测,第一步就是数据。目前有几类可以走通的渠道:一是公开的红外数据集,比如面向自动驾驶场景的带标注红外图像集、面向安防监控的行人红外数据集,这些数据的好处是开箱即用、标注规范,缺点是场景相对固定,不一定贴合你自己的应用环境。二是自建数据集,把红外热像仪架在实际现场,连续录制不同时间段、不同天气条件的视频,然后用标注工具逐帧框出目标。自建数据集的成本高,砍掉一半时间。其实红外图像的标注有一个特点:人眼在单帧上标注的难度比可见光高不少,特别是小目标和低对比度目标,所以自建数据时最好找有现场经验的人参与,优先保证标注的一致性和边界框的贴合度。
2.2 标注格式转换与关键陷阱
YOLO系列训练需要的标签格式是TXT文本,每行对应一个目标,格式为“类别ID 中心点x坐标/图像宽度 中心点y坐标/图像高度 目标框宽度/图像宽度 目标框高度/图像高度”。这个转换看起来简单,但有两个关键陷阱经常坑人。
一个是坐标归一化溢出。如果标注工具导出的坐标格式是左上角坐标加宽高,那么转换时要做一次坐标中心化计算,很容易在边界情况下出现x或y小于0或者大于1的错误数值。训练时模型看到这种标签,损失直接飞掉。
另一个是标注框过小导致的数据无效。红外小目标可能标注出来宽高只有几个像素,归一化后数值很小,在计算损失的时候这些框对loss的贡献微乎其微,等于白标。我的建议是训练时对标注框做一次过滤,宽或高小于一定像素(比如2像素)的目标要么直接保留原始分辨率做精细化训练,要么在增强阶段通过裁切放大方式让模型“看到”它们。
2.3 预处理三板斧:增强、归一化、数据扩充
红外图像的预处理,我总结成三板斧。
第一板斧是增强对比度。红外图像经常是整体偏暗、目标淹没在背景里,直接训练模型的收敛速度很慢。常见做法是直方图均衡化或者自适应直方图均衡化。这里要踩个坑:整图直方图均衡化容易把背景噪声同步放大,效果好一点的方案是限制对比度的自适应直方图均衡化,在局部区域做对比度拉伸,既能突出目标,又不会让整图变得过曝或过噪。
第二板斧是输入通道处理。绝大多数YOLO变体都接受三通道输入,而红外图只有一个通道。常见的处理方式有三种:把单通道复制成三个通道、把单通道图做伪彩色映射、在训练时把红外图和可见光图做成多模态融合输入。从实用角度,复制通道最简单,效果也能接受;伪彩色映射需要选好映射函数,不同的色彩映射对特征提取有一定影响;多模态融合效果最好,但需要同时具备红外和可见光两路相机,不是所有场景都具备这个条件。
第三板斧是数据扩充。红外场景的数据增广和可见光有些区别,常规的随机裁剪、翻转、缩放都能用,但要注意颜色扰动类的增广毫无意义,因为红外图就是灰度图。更有效的扩充方法是:模拟不同增益和积分时间导致的亮度变化、叠加不同强度的噪声、随机遮挡、小目标复制粘贴。这些扩充方式更贴近红外传感器的物理特性,能显著提升模型的泛化性。
3. YOLO模型选型与训练:把空间检测能力榨干
3.1 模型版本与规格选择
YOLO系列的选型,核心是精度、速度、模型体积三者的平衡。在红外目标检测场景里,我建议优先关注小目标的检测能力,而不是一味追求最大最重的模型。
解释一个我的判断依据。红外场景的目标尺寸整体偏小,检测器性能瓶颈往往不在分类能力上,而在定位和小目标召回上。一些轻量化版本的YOLO在小目标上的表现并不弱,而且推理速度快,尤其适合需要实时处理的嵌入式平台。重量级模型在红外下没有想象中那么大优势,因为红外图像本身信息量少,过大的网络反而容易过拟合到训练集的噪声上。
有一个可行的思路是采用“检测头增强的变体结构”,在原有Neck层上增加一个针对小目标的浅层检测头。浅层特征图分辨率高,保留了更多小目标的细节,虽然会增加一部分计算量,但对红外小目标检测的收益非常直接。
3.2 训练策略与超参设置
关于训练,我分享一组实际工作中验证过相对稳定的配置思路。
输入分辨率建议尽量提高。对红外小目标,输入分辨率从640×640提升到1280×1280,小目标的召回率往往有肉眼可见的提升。代价是训练速度和推理速度变慢,所以需要根据实际算力平衡。
训练轮数上,红外数据集规模通常远小于COCO,模型容易过拟合。我的做法是先用预训练权重初始化,冻结主干网络前几层,只训练检测头和Neck层,等损失曲线平稳后再解冻全部层微调。这会明显缩短收敛时间,同时保住预训练模型在底层特征提取上的能力。
批次大小和学习率要联动调。红外数据集小,NBS大容易导致震荡,建议学习率初始值设在0.001附近,配合余弦退火策略。损失函数方面,边界框回归用CIoU损失比用平滑L1损失对红外小目标的回归更友好,因为CIoU天然考虑了目标框的重叠面积、中心点距离和长宽比,对小目标的位置回归约束更强。
3.3 针对红外场景的增强改法
训练后如果发现误检还是偏高,可以针对红外图像特点做两个小改动。
第一个是在网络输入前增加预处理层,对红外图做背景抑制。背景抑制在传统红外图像处理里非常常用,核心是减去估计的背景,只留下目标区域。作为深度学习的前处理,可以用一个轻量的高通滤波核实现,也可以在数据加载时用形态学顶帽变换做一次背景扣除。实际效果是目标区域更突出,网络学起来更容易。
第二个是在特征提取阶段引入注意力模块。红外目标往往局部对比度低,但位置相对集中。通道注意力和空间注意力机制可以让网络更关注目标所在区域,抑制大范围均匀背景的干扰。这类模块在公开实现里非常多,组合到YOLO的Neck层即可,工程成本很低。
4. 动态规划后处理:把逐帧检测结果串成稳定轨迹
4.1 单帧检测的局限性
经过前面几步,YOLO已经能在大多数帧里框出目标了,但离“稳定可用”还差一截。核心问题是:单帧检测是独立决策,它对时间域没有任何感知能力。红外条件下的低信噪比决定了任何单帧检测算法都不可能完美,可能出现三种典型情况:真实目标因为对比度太低在某一帧没有被触发、运动目标在快速位移时检测框出现偏移、背景中的热源或噪声在某一帧生成了高置信度误检。
这三种情况靠调YOLO本身的参数很难同时解决。把置信度阈值调高,误检少了,但本来就弱的真实目标更容易丢;把阈值调低,召回率上来了,误检也碎了一地。这时候需要引入时间维度的决策。
4.2 动态规划建模:状态、转移与全局最优
把多帧检测结果串成轨迹,本质上是一个序列决策问题。动态规划是解这类问题的经典工具,它的核心思想是把一个复杂的全局最优化问题分解成一系列子问题,逐步求解并记录中间结果,最后通过回溯得到全局最优解。
我习惯把这个过程类比成拼一段积木路。每一帧的检测框是一块候选积木,单块积木的可靠性不同(置信度不同),相邻积木拼接时要考虑衔接是否顺畅(位置和尺寸是否连续)。动态规划的目标就是找出一条从第一帧到最后一帧都由候选框组成、总得分最高、衔接总成本最低的路径。
具体建模时,我定义状态为第t帧选择第j个候选框作为轨迹的当前终点时的最大累计得分。定义转移成本为上一帧的第i个候选框到当前帧第j个候选框之间的代价,代价通常由三部分构成:
一是位置距离。相邻帧之间目标位移有限,如果两个候选框的中心点距离很大,说明它们大概率不是同一个目标。
二是边界框重叠度。用IoU衡量,红外目标虽然边缘模糊,但同一目标在相邻帧的框通常会有较大的重叠区域。
三是外观相似度。红外图像没有丰富纹理,但可以用候选框区域内的灰度直方图或局部对比度模式做相似度度量。
综合后,转移得分等于位置距离的负惩罚加上重叠度的正奖励再减去外观差异的惩罚。最终的累计得分等于上一帧累计得分加上转移得分再加上当前检测框的置信度得分。求解时从第一帧开始逐帧计算,维护最优路径的指针,遍历完毕后回追溯找到全局得分最高的轨迹。
4.3 用类Viterbi算法求解最优轨迹
求解过程采用类似Viterbi算法的方式。每一帧的候选框数量通常不多(红外场景目标稀疏),所以状态空间规模可控,计算代价不高。
我用一个简化的伪代码来说明核心逻辑:
# 动态规划轨迹关联核心逻辑(简化版) def dp_trajectory(detections, max_gap=5): # detections: 每帧的候选框列表,每个候选框含坐标、置信度 # dp_scores[t][j]: 到第t帧选第j个候选框的最优累计得分 # back_ptr[t][j]: 记录最优路径中第t-1帧选的是哪个候选框 T = len(detections) if T == 0: return [] dp_scores[0][j] = detections[0][j].conf for t in range(1, T): for j in range(len(detections[t])): best_score = -inf best_prev = -1 for i in range(len(detections[t-1])): trans = transition_cost(detections[t-1][i], detections[t][j]) cur = dp_scores[t-1][i] + trans + detections[t][j].conf if cur > best_score: best_score = cur best_prev = i dp_scores[t][j] = best_score back_ptr[t][j] = best_prev # 回溯最优路径 last = argmax(dp_scores[T-1]) trace = [] for t in reversed(range(T)): trace.append(detections[t][last]) if t > 0: last = back_ptr[t][last] trace.reverse() return trace这段代码展示了单条轨迹的求解思路。实际多目标场景中,需要对候选框做全局分配,防止两条轨迹共用同一个检测框。我的处理方式是先对所有候选框做置信度和空间非极大值抑制,再在每帧之间用二分图匹配算法(比如匈牙利算法)做最优指派,确保每个检测框最多归属一条轨迹。动态规划负责单条轨迹的内部最优路径,指派算法负责轨迹之间的数据关联,两套机制结合就能处理多目标的红外视频。
5. 工程实现与参数调优:从理论到可落地的细节
5.1 检测结果的输出与组织
工程实现前,先把YOLO的检测结果输出格式定清楚。我一般让检测器输出五元组:目标框中心x坐标、中心y坐标、宽度、高度、置信度。如果加上类别,就是六元组。输出结果统一在原始图像分辨率下做组织,不要用预处理后的缩放分辨率,这样可以避免后续坐标换算的麻烦。
检测结果按帧索引存储。例如构造一个列表,下标是帧号,值是这一帧所有候选框组成的数组。如果某一帧没检测到任何目标,就保留一个空数组,不要跳过,因为动态规划需要按帧对齐时间轴。
5.2 核心代码逻辑:从YOLO框到DP轨迹
在得到每帧候选框序列后,整个后处理流水线我按下面几步实现:
第一步,对候选框做单帧内抑制。YOLO本身的非极大值抑制已经做了一部分工作,但在红外场景里,同一个目标可能会输出多个重叠的候选框,所以我会把IoU阈值设置为0.5左右再做一次合并,减少后续状态空间的开销。
第二步,构造状态转移矩阵。相邻帧之间的转移代价计算是最耗时的一步。工程上,我不需要对所有候选框两两计算完整代价,可以先做一个粗筛:中心点距离超过目标可能最大位移的候选框直接跳过,只有通过距离阈值筛选的才算转移代价。这一步能把计算量减少一个数量级。
第三步,执行上一节伪代码里的动态规划求解。求解时记录每个状态的最优上一帧索引和累计得分。
第四步,回溯路径并输出轨迹。轨迹以帧号加候选框坐标的形式存下来,方便后续画框或者计算统计指标。
核心的一个工程经验是在线处理时采用滑动窗口。视频流持续不断,不可能对整个无限长的视频做全局动态规划。我的做法是维护一个窗口长度(比如30帧),每来一帧检测结果就做一次窗口内的动态规划,输出轨迹的最新一段后移动窗口。这样既保留了时间平滑的效果,又保证了实时性。
5.3 轨迹生命周期管理
轨迹不能只创建不销毁,否则内存和误检控制都会失控。我把轨迹状态划分为:激活态、保持态、结束态。
激活态是轨迹连续关联到新检测框的状态。当轨迹在当前帧找到匹配框时,更新轨迹的末端位置,并累计得分。保持态是当前帧没有匹配到检测框,但轨迹在允许的最大间隔帧数内,此时轨迹暂停延伸,保留原有位置参数。结束态是轨迹连续超过最大间隔帧数没有匹配到新框,或者轨迹长度太短且平均置信度过低,判断为噪声轨迹并删除。
这个生命周期状态机,加上动态规划的关联机制,整个后处理就不是一个孤立的算法,而是一个能稳定运行的模块。我踩过的坑是忘记设置最小轨迹长度过滤,结果单个噪声帧上形成的瞬时轨迹被打上了告警标签,后续排查浪费了不少时间。
5.4 关键参数说明与调优建议
动态规划相关的参数虽然不多,但每个都很敏感,我整理成一张速查表:
| 参数 | 典型初始值 | 作用 | 调整建议 |
|---|---|---|---|
| 最大位移阈值 | 目标尺寸的2倍 | 限制相邻帧匹配搜索范围 | 目标运动速度快时调大,慢时调小 |
| IoU匹配阈值 | 0.3 | 划分同一目标关联的边界 | 目标框不重叠时调小,重叠时调大 |
| 置信度阈值 | 0.5 | 决定候选框是否纳入DP | 先降到0.3看召回,再通过DP抑制误检 |
| 最大缺失帧数 | 5 | 轨迹能忍受的漏检窗口 | 目标容易被遮挡时调大 |
| 最小轨迹长度 | 5 | 过滤伪轨迹的帧数下限 | 误检高时调大 |
| 滑动窗口长度 | 30 | 在线DP的帧范围 | 算力足时调大更稳,实时性要紧时调小 |
调参顺序也有讲究。我习惯先把置信度阈值降到能接受的最大误检水平以下很多,让YOLO把候选目标都交出来,然后用动态规划的最大缺失帧数和最小轨迹长度来控制最终输出的纯度。如果你反过来先调高置信度阈值,检测阶段就已经漏掉了不少真实目标,后处理再强也救不回来。
6. 常见问题与排查技巧速查
6.1 轨迹串扰:两个目标交替移动时轨迹交叉
这是多目标场景最典型的问题。两个行人相对而行,检测框在某一帧发生了重叠,动态规划在状态转移时可能因为IoU计算的歧义,把A的目标框错误关联到了B的轨迹上,形成轨迹跳变。
我的处理方案是在转移成本里引入外观相似度权重。红外图虽然纹理信息少,但目标区域的灰度均值和方差还是有区分度的,让这两个量参与转移得分计算,能把大量串扰场景区分开。另外,当两个候选框IoU极大时,可以暂停轨迹延伸一帧,等目标分开后再继续,这个保守策略在密集人群场景里非常好用。
6.2 目标长期遮挡导致轨迹中断
目标被电线杆、树木、墙体遮挡几十帧,再出现时位置变化很大。动态规划的最大缺失帧数如果设置得太小,轨迹会被提前结束,重新出现的目标会被当成新目标,导致轨迹数量虚增。
排查时先看目标消失的最大帧数是多少,然后把最大缺失帧数调整到比这个上限再大个30%左右。同时,在保持态期间用小窗口预测目标位置(比如线性外推),等目标再次出现时,在预测位置附近搜索,匹配概率会高很多。
6.3 计算性能不足影响实时推理
YOLO加动态规划,计算开销主要是检测网络推理加状态转移矩阵计算。检测网络推理时间可以通过输入分辨率、模型裁剪和推理框架加速来控制。动态规划的状态转移计算看似简单,但候选框数量多的时候,两两计算代价也会拖慢速度。
一个高效的技巧是限制每个候选框的匹配候选数量。前一帧的候选框数量假设是50个,当前帧也是50个,完整计算转移代价是2500次距离和IoU运算,冗余较大。先按中心点距离排序,只保留距离最近的10个候选框做完整转移代价计算,精度几乎不受影响,耗时能下降到原来的五分之一。
6.4 红外噪声引起的固定虚警
红外探测器坏点或非均匀性条纹会在固定位置持续产生亮点,这些点在时域上位置不动,动态规划很容易把它们的轨迹当成静态目标。
排查手段是分析轨迹的位置方差。真实运动目标的轨迹位置方差较大,固定噪声点在时间轴上的位置方差几乎为零,用一个简单的方差阈值就能过滤。如果固定噪声点非常多,最好回到底层图像处理,做一次坏点校正和非均匀性校正,从源头上把固定噪声消除。
7. 实验效果与复盘:这套方案到底提升了什么
7.1 实验设置与评价指标
为了验证方案的有效性,我在一个模拟的红外监控场景上做了完整的对比实验。数据来自某户外监控项目采集的红外视频,场景包括单目标走动的开阔地带、多目标穿梭的十字路口,以及有低对比度目标的清晨时段。训练集取视频前部帧,测试集取后部帧,保证同一目标的视频部分不会同时出现在训练和测试里。
评价指标不只看了平均精度,更关注三个工程指标:召回率(真实目标被成功关联出轨迹的比例)、误检轨迹数(把噪声当成目标的轨迹数量)、轨迹稳定性(真实目标的轨迹断裂次数)。
7.2 效果对比数据
实验对比了三种方案:纯YOLO输出、YOLO加简单IOU最近邻关联、以及YOLO加动态规划关联。
| 方案 | 召回率 | 误检轨迹数 | 轨迹断裂次数 |
|---|---|---|---|
| 纯YOLO | 0.72 | 12 | 24 |
| YOLO+最近邻关联 | 0.78 | 7 | 11 |
| YOLO+动态规划关联 | 0.86 | 2 | 3 |
数字说明问题。纯YOLO方案的召回率低,是因为检测阶段置信度阈值没法兼顾低对比度小目标。最近邻关联能在一定程度上改善断裂,但对丢失帧的容忍不足。动态规划方案通过全局最优路径搜索,把短暂漏检的帧用前后帧的上下文补齐了,所以召回率提升,同时把噪声引起的孤立误检过滤得比较干净,误检轨迹数降到了个位数。
7.3 复盘与教训
这次实验也给我留了几个值得记录的教训。
第一个教训是检测质量是后处理的天花板。动态规划再怎么调参,也无法从一堆根本没有检测到目标的候选框里凭空造出轨迹。我曾试图用动态规划弥补YOLO在低对比度帧上的整体失效,结果发现轨迹断裂始终无法根治,最后只能回到数据增强和训练策略上解决问题,把单帧检测的短板补上。
第二个教训是动态规划参数必须按场景单独调。同一个参数组在开阔地带效果很好,换到多目标密集场景里最大位移阈值就必须调小,否则轨迹串联乱得一塌糊涂。没有一劳永逸的参数组合,这是这类工程问题绕不开的宿命。
第三个教训是评价一个方案,一定要看序列级别指标,不能只看单帧车辆级别指标。单帧检测精度再高,上视频后如果轨迹抖动严重,甲方一样不买账。序列级指标虽然难做,但才真正反映客户的真实体验。
8. 一点点个人体会
红外图像目标检测这个方向,特别容易让人陷入“调模型、涨点、再调模型”的循环。YOLO本身非常强,但这个强是指它在理想光照、丰富纹理的场景下很强。到了红外这个模态,很多可见光时代的经验不再适用,与其死磕模型单帧精度,不如换个思路,把时间维度利用起来。动态规划在这套方案里做的工作不复杂,但它解决的是检测模型单独解决不了的问题:时序上的全局一致性。它不要求每一帧都完美,只需要大部分帧过得去,就能通过前后关系把轨迹补完整。
这个思路后续还可以继续扩展。比如把动态规划关联的结果反馈回检测器做在线难例挖掘,或者把轨迹信息作为特征输入给检测模块,让模型在做单帧预测时就能参考历史信息,那又是另一层提升了。这套基于YOLO加动态规划的框架,目前我已经在几个不同的红外场景里复用过了,每次只要针对场景调整参数和增强策略,都能快速落地。希望这讲的思路和踩坑记录,能给你省下几个调参的夜晚。