
做红外探测方向的算法同学一定绕不开“红外弱小目标检测”这个任务。目标在图像里只占几个到几十个像素信杂比低到接近甚至低于1肉眼从云层、海天线、地物杂波里找出来都费劲更别说让算法在单帧图像里稳定检出。这个方向里2013年发表在IEEE TIP上的IPI算法Infrared Patch-Image Model算是一个里程碑式的思路它把困扰大家很久的弱小目标检测问题硬生生“翻译”成了一个矩阵低秩稀疏分解问题然后用鲁棒主成分分析RPCA那一套优化工具去求解效果确实惊艳但原理和复现的门槛也不低。这篇文章我会从数学模型到 MATLAB 代码把 IPI 的完整链路讲清楚。适合正在做红外探测、遥感图像处理或者刚入门想找一个能跑通的检测算法的同学参考。我尽量把“为什么要这样设计”也说透而不是只给代码。1. 红外弱小目标检测到底在检测什么1.1 “小”和“弱”是怎么定义的在红外成像系统里远距离目标由于成像尺寸小、辐照度低在探测器上往往只是一个或者几个像素组成的亮点没有纹理、没有形状、没有颜色这就是“小目标”的直观含义。工程上通常用目标像素数占整幅图像的比例来界定很多文献把小于图像总像素0.15%的目标视为小目标也有一些数据集以目标面积不超过9×9甚至6×6个像素为准。“弱”则是指目标与背景之间的对比度很低。在单帧红外图像里天空背景、云层边缘、海面波纹都可能产生很强的局部灰度变化目标淹没在其中。定量地说信杂比SCR通常指目标峰值与周围背景均值之差再除以背景标准差。当SCR小于2甚至小于1时人类观察者仅凭单帧图像都容易漏检算法要做到高检测率、低虚警率就更难了。1.2 为什么单帧检测比视频检测更麻烦有视频序列时可以用多帧累积、运动轨迹关联来提升信噪比因为目标在帧间有连续运动而背景杂波一般不具备时间一致性。但很多时候我们只有单帧图像比如精确制导的末段识别、远距离告警系统抓拍或者搜索雷达的某一帧静帧必须在没有时间维信息的情况下直接判断哪里是目标。这就倒逼算法必须在空间维上做文章。传统方法里有人用形态学Top-hat变换有人用Max-Mean/Max-Median滤波还有一类从人类视觉注意力机制出发的LCM、PCM算法。它们各有优点但普遍对复杂背景的适应性不够——要么背景估计得太粗糙导致目标被抹掉要么对云层边缘和强起伏区域产生大量虚警。IPI的思路完全不同它不是设计一个滤波器而是构造一个数学优化问题把“背景估计”和“目标提取”一步到位地求解出来。2. IPI算法的数学框架从图像到矩阵的一次“翻译”2.1 从像素图像到补丁图像IPI最关键的预处理操作是把一幅二维灰度图转换成所谓的补丁图像。具体做法是用一个大小为 p×p 的滑动窗口以步长 h 遍历整幅图像每到一个位置就取出一个局部块把这个块按列拉成一个长度 p² 的列向量。所有窗口对应的列向量按顺序并排最终拼成一个维度为 p² × N 的大矩阵。N 是窗口数量当 h1 时近似等于 (H-p1)×(W-p1)。这一步为什么要这么做因为原始图像里背景和目标是混合在空间位置上的直接对整幅图做低秩分解并不合理——整幅图的背景可能是天空、山地、海面拼接在一起低秩性并不明显。但转成补丁图像后每一个列向量都来自图像局部的一个小窗口局部背景在灰度结构上高度相关所以整个补丁矩阵的低秩性会大大增强。目标则因为只占据极少数窗口内的少量像素在补丁矩阵中呈现出明显的稀疏性。2.2 背景低秩、目标稀疏这两个假设成立吗任何模型都依赖假设IPI的两个支柱假设值得细扣。背景低秩红外自然背景通常是缓变的大面积灰度场云层、海面、天空内部都具有很强相关性。即便整幅图像包含多个不同区域在每个小窗口局部看灰度分布仍然可以用少量基底低频分量、梯度方向等近似表达。把所有局部窗口放在一起这个矩阵的秩是远小于矩阵维数的。当然如果图像里有剧烈的灰度突变边缘比如海天线、建筑物轮廓这部分不一定严格低秩这也是IPI在某些场景下背景残留的原因之一。目标稀疏单帧红外图像里的弱小目标像素占比通常远低于1%甚至只有几十个像素。在补丁矩阵中目标只会让极少数的列向量出现较大的非零突变整个矩阵非零项比例极低。不过要注意稀疏性并不是“目标区域是零、背景区域非零”那种空间稀疏而是在低秩背景被剥离后的残差矩阵中目标对应的元素数值显著其余接近零。这个区别很重要后面找阈值时用得上。2.3 IPI的目标函数与正则参数有了上述建模红外图像被表示成D B T ND 是补丁矩阵B 是低秩背景补丁矩阵T 是稀疏目标补丁矩阵N 是噪声项。求解时最经典的IPI形式是min ||B||_* λ||T||_1 s.t. ||D - B - T||_F ≤ ε这里 ||B||_* 是核范数也就是奇异值之和用于约束矩阵低秩||T||_1 是L1范数用于约束矩阵稀疏。原理上核范数是矩阵秩的凸松弛L1范数是向量/矩阵非零元数量的凸松弛两个都能让优化问题保持凸性从而有稳定高效的求解算法。正则系数 λ 的取值RPCA理论里的推荐值是 1/sqrt(max(m,n))m、n 是 D 的行列数。在IPI实践中λ 不定期需要小幅调整尤其是当目标能量特别弱、或者噪声水平特别高时λ 偏大容易把目标一起“压没”λ 偏小则会让背景残渣混进目标图。我在复现中一般以理论值为起点在 0.5倍到1.5倍之间做网格搜索选择检测率与虚警率最平衡的那个值。3. 低秩稀疏分解怎么求IALM求解全解析3.1 为什么要用增广拉格朗日法直接求解核范数加L1范数的最小化问题并不容易尤其当矩阵维度达到几千乘几万时通用凸优化求解器非常慢。实际使用最广泛的方案是增广拉格朗日法它是从经典拉格朗日法加二次惩罚项演变来的。对比ADMM等其它思路IALM在RPCA问题上收敛快、实现简单所以成了IPI复现里的首选求解器。把约束 ||D-B-T||_F ≤ ε 改写成等式更便于推导先在增广拉格朗日函数里引入拉格朗日乘子 Y 和惩罚系数 μ通过交替优化 B 和 T再更新 Y逐步逼近最优解。3.2 两个核心算子奇异值阈值和软阈值求解BP子问题和T P子问题时数学上会分别得到两个关键算子。第一个称为奇异值阈值算子Singular Value Thresholding, SVT。对任意矩阵 X 做一次SVD得到 X UΣV^T把奇异值对角阵 Σ 里的每个元素都减去 τ小于等于0的直接置0即 soft_thresh(σ, τ) sign(σ)·max(|σ|-τ, 0)再用新的奇异值重建矩阵。这个算子本质上是核范数最小化的闭式解。第二个是软阈值算子它对矩阵的每个元素独立做类似操作s_τ(x) sign(x)·max(|x|-τ, 0)。这是L1范数最小化的闭式解。在MATLAB里实现非常短一行代码就能搞定但它是整个迭代里被调用频率最高的函数一定要写成向量化形式不要用for循环逐元素处理。3.3 IALM的迭代步骤标准的IALM求解流程可以归纳为四步循环往复直到收敛。第一步用当前 T 和 Y 的线性组合构造成一个临时矩阵对它做奇异值阈值收缩得到新的 B。第二步用刚更新的 B 和当前 Y 构造另一个临时矩阵对每个元素做软阈值收缩得到新的 T。第三步更新拉格朗日乘子 Y补偿当前分解误差。第四步将惩罚系数 μ 按固定比例递增并计算当前残差如果残差小于预设容差就停止迭代。伪代码思路如下% 初始化 Y zeros(size(D)); mu 1.25 / norm(D, 2); rho 1.5; tol 1e-7; maxIter 500; for k 1:maxIter B svt_operator(D - T Y/mu, 1/mu); T soft_thresh(D - B Y/mu, lambda/mu); Y Y mu * (D - B - T); mu min(mu * rho, 1e6); err norm(D - B - T, fro) / norm(D, fro); if err tol break; end end实际操作时我建议最高迭代次数不要设得太小因为补丁矩阵有时需要几百次迭代才能达到理想分解效果但也不要超过一两千次否则时间成本太高。4. MATLAB从零复现IPI完整流程与代码4.1 环境与工具箱准备准备工作只需要一个基础 MATLAB 环境不需要额外的图像处理工具箱。代码里我会用到最基本的矩阵运算、svd、norm、reshape 这些。运行平台建议用R2018以上版本SVD在多线程支持上有比较大的性能提升。如果希望追求极致的运行速度可以考虑配合 PROPACK 工具箱做部分奇异值分解或者使用 MATLAB 内置的 svds但要注意 svds 是求解前 k 个奇异值/向量而 IPI 理论需要完整的奇异值收缩。实践中我发现当补丁矩阵规模非常大时svds 收缩主要几个主奇异值相当于把低秩近似和提取同时办了多数情况下效果影响很小但收敛行为会略有不同。新手复现时先用标准 svd跑通了再优化也来得及。4.2 补丁图像构建补丁图像构建是IPI的地基这一步做不对后面全白搭。我提供一个向量化程度较高、同时容易理解的版本把每个窗口的像素块 reshape 成列向量再拼接成矩阵。function D im2patch(im, patchSize, step) % im: 输入灰度图像double类型范围[0,1] % patchSize: 窗口边长建议大于目标最大尺寸 % step: 滑动步长常用1~3 % D: 补丁矩阵维度为(patchSize^2, numPatches) [H, W] size(im); rowStart 1:step:H-patchSize1; colStart 1:step:W-patchSize1; numPatches length(rowStart) * length(colStart); D zeros(patchSize*patchSize, numPatches, double); idx 0; for i rowStart for j colStart patch im(i:ipatchSize-1, j:jpatchSize-1); idx idx 1; D(:, idx) patch(:); end end end如果使用 MATLAB 的im2col函数有一行代码可以实现同样的功能那就是D im2col(im, [patchSize patchSize], sliding)。sliding 模式就是步长为1的滑窗采样。但请注意对于几百×几百以上的图像sliding 模式生成的补丁矩阵会非常占用内存500×500的图、窗口20×20列数将近23万这已经是数百MB级别再往上容易内存溢出。因此在大图上建议用分块或减小步长来控制矩阵规模。4.3 IALM求解器实现这一节直接给出我在复现过程中验证可用、也保留了一定可读性的IALM函数。function [B, T] rpca_ialm(D, lambda, tol, maxIter) % 基于IALM的RPCA求解 % D: 观测矩阵 % lambda: L1范数权重 % tol: 相对误差容差 % B: 低秩背景补丁矩阵 % T: 稀疏目标补丁矩阵 % % 注意这个版本是面向IR检测场景做过的简化未加入 % 原版IALM里两层循环的精细策略但收敛行为稳定。 if nargin 3, tol 1e-7; end if nargin 4, maxIter 500; end [m, n] size(D); % 对拉格朗日乘子Y做缩放初始化有助于减少迭代次数 norm2 norm(D, 2); normInf norm(D, inf); Y D / max(norm2, normInf / lambda); mu 1.25 / norm2; rho 1.5; muMax 1e6; B zeros(m, n); T zeros(m, n); for iter 1:maxIter % 1. 更新B: 奇异值阈值 X D - T Y / mu; [U, S, V] svd(X, econ); s diag(S); s max(s - 1/mu, 0); B U * diag(s) * V; % 2. 更新T: 软阈值 X D - B Y / mu; T sign(X) .* max(abs(X) - lambda/mu, 0); % 3. 更新拉格朗日乘子 Y Y mu * (D - B - T); % 4. 更新惩罚系数并检查收敛 mu min(mu * rho, muMax); err norm(D - B - T, fro) / norm(D, fro); if err tol break; end end end这个实现有几个细节值得讲。Y 的初始化不是从零矩阵开始而是用一个缩放后的 D这是参考了原始IALM论文的做法可以在前几次迭代就明显加速收敛。mu 的初始值取 1.25/norm(D,2)也是一个经验上很稳的设置既能避免惩罚项过大导致目标被吞掉又不会因为惩罚项太小而拖慢收敛。迭代到后期mu 使用 rho 逐步放大最终限制在 1e6防止数值异常。如果不想手写也可以在 MATLAB 的LRSLibrary工具库里直接调用inexact_alm_rpca它实现了更完整的IALM流程。但对于想彻底搞懂IPI的人我建议还是自己敲一遍这个函数因为后面调参和理解收敛性时只有亲手实现过才能快速定位问题。4.4 从稀疏补丁矩阵还原目标图像求解完成后得到的 T 是一个补丁矩阵它和 D 的行列一一对应。要得到一张可以直接看的二维目标图像需要把 T 的每一列重新 reshape 成 patchSize×patchSize 的小块放回原图对应的位置。如果窗口之间有重叠step小于patchSize时必然重叠就采用叠加平均策略——重叠区域累加像素值同时统计每个像素被覆盖的次数最后累加结果除以覆盖次数。function im patch2im(T, patchSize, H, W, step) % T: 补丁矩阵 % H, W: 原图尺寸 % step: 与原图构建时的step保持一致 cnt zeros(H, W); acc zeros(H, W); numPatches size(T, 2); rowStart 1:step:H-patchSize1; colStart 1:step:W-patchSize1; idx 0; for i rowStart for j colStart idx idx 1; patch reshape(T(:, idx), patchSize, patchSize); acc(i:ipatchSize-1, j:jpatchSize-1) acc(i:ipatchSize-1, j:jpatchSize-1) patch; cnt(i:ipatchSize-1, j:jpatchSize-1) cnt(i:ipatchSize-1, j:jpatchSize-1) 1; end end im acc ./ max(cnt, 1); end这里把 cnt 在除数为0的位置保护为1避免出现除零。重建后的目标图像中目标区域会形成高亮斑点背景残留一般很弱但不会完全等于0。接下来还需要一步分割。4.5 目标分割与检测结果输出对重建得到的目标图像最简单的分割方法是自适应阈值计算目标图像的均值 mu_t 和标准差 sigma_t用 threshold mu_t k * sigma_t 作为判定门限超过阈值的像素标记为候选目标。k 通常取 5 到 15具体值取决于虚警率要求。想要更严谨也可以对目标图像做Otsu全局阈值或者用区域生长把离散亮点连通起来。function [binaryImg, bbox] segmentTarget(targetImg, k) % targetImg: IPI重建的目标图像 % k: 阈值系数常用5~15 mu mean(targetImg(:)); sigma std(targetImg(:)); th mu k * sigma; binaryImg targetImg th; % 连通域分析输出包围框 cc bwconncomp(binaryImg, 8); bbox regionprops(cc, BoundingBox); end阈值选择要结合目标的能量来定。IPI分解得到的目标图和原始图像并非同一个灰度尺度它更像是目标相对背景的“稀疏残差”所以直接用固定灰度阈值并不合适自适应统计阈值更稳定。5. 实验评估用SCRG和BSF说话5.1 测试数据怎么准备复现IPI之后第一件事不是拿真实红外图盲跑而是先用合成图像验证流程。合成方式很简单找一张干净的红外背景图在随机位置放入一个高斯状的小亮斑作为目标高斯核大小可以根据目标尺寸设置比如 3×3 或 5×5峰值对比度控制在 SCR 为 1~3 之间。这样可以精确知道目标位置方便计算检测率和虚警率。真实场景测试可以使用公开的红外弱小目标数据集比如 NUAA 的红外小目标序列或者一些论文配套提供的红外云背景测试图。没有现成数据集时也可以从公开红外视频里自己裁剪背景再人工叠加上目标做标注。在使用真实图像时要注意IPI对图像中的强边缘如建筑轮廓很敏感这部分容易被误判为目标所以在评估时最好先把预处理阶段的异常高亮像素考虑进去看是否可以通过模板做抑制。5.2 SCRG和BSF到底代表什么信杂比增益 SCRG 和背景抑制因子 BSF 是弱小目标检测里两个非常重要的量化指标。SCRG 表示经过检测算法处理后目标与背景的对比度提升了多少倍。具体计算时先估计输入图像中目标位置的SCR值再估计输出目标图像中目标位置的SCR值做除法。SCRG越高说明目标从杂波中“浮出来”的效果越好。BSF 则衡量算法对背景的抑制力度定义为输入图像背景标准差与输出目标图像背景标准差的比值。BSF越大说明背景残留越少。但BSF不能单独看因为如果算法把目标也一起抹掉了BSF会很高但检测率极低。实际操作中我会同时统计检测率Detected Probability和虚警率False Alarm Rate在整套指标下评估算法实用性。5.3 与Top-hat和Max-Mean的对比一条经典对比链路是同一组测试图分别用 Top-hat、Max-Mean、Max-Median 和 IPI 处理然后比较SCRG和BSF。Top-hat 是形态学操作它对尺寸较小的亮目标很敏感但在云层边缘会产生大量高响应。Max-Mean 在平坦背景上效果稳定遇到复杂纹理容易过平滑。Max-Median 对脉冲噪声有更好的抵抗力但目标较大时容易漏检。IPI的优势在于它是全局优化不像滤波方法那样依赖局部窗口形状对复杂背景的低秩结构可以做自适应建模。代价是计算量明显高一个量级单帧图像处理耗时常常是传统方法的几十倍。因此在实际工程里IPI并不总是最佳选择如果实时性要求高传统滤波或基于HVS的算法可能更合适。6. 复现路上的坑参数、效率与质量平衡6.1 为什么输出图总有“棋盘格”很多人在第一次复现IPI时会得到一张带有规则格状条纹的目标图看起来像棋盘。这个问题的根源通常在补丁图像构建或重建阶段步长设置太大且重建时没有使用重叠平均。当步长大于等于patchSize时相邻窗口完全没有重叠重建时每个窗口边缘就会形成一道缝棋盘格自然出现。解决方法是把步长调小到能保证重叠比如步长2、窗口20重叠18像素几乎不会看到块状痕迹。另一个容易被忽略的原因是目标图像重建后做了归一化导致原有微小差异被放大于是在窗口边缘出现细线。可以先不归一化直接用原始重建值做阈值分割很多边缘线会消失。6.2 目标“消失”了首先排查这五个地方我遇到过很多次目标在T矩阵里完全不可见的情况每次都绕不开这几个原因。第一λ 太大L1惩罚过重目标被当成噪声一起压成零。这时把 λ 下调到理论的0.5倍试试。第二目标尺寸比窗口还大目标没有包含在任何一个完整窗口里这时重建后目标被拆碎且能量分散。第三目标本身能量太弱低于背景标准差导致IPI把目标当成噪声允许的误差范围分解后目标残差很小。第四迭代次数不够算法还未收敛就退出了目标还没被完全分离出来。第五输入图像没做类型转换uint8的像素值直接参与优化导致数值尺度不平衡λ 的理论值完全失效。建议所有图像在进入IPI之前统一转换为 double 并归一化到 [0,1]。6.3 速度太慢怎么办三种实用加速方案IPI最大的痛点之一是慢尤其是迭代中每次都要对 p²×N 的矩阵做SVD。如果图像是 640×480窗口20×20步长2N大概是 (640/2)×(480/2) ≈ 76000列每次SVD都会耗费大量时间。第一种加速是降采样。对输入图像做2倍或3倍降采样相应调整窗口尺寸目标在低分辨率下仍然保留稀疏斑点特征但矩阵规模大幅缩小速度可能提升一个数量级。缺点是对极小目标比如只有一个像素不友好降采样后目标可能直接消失。第二种加速是减小SVD计算范围。标准SVD要算全部奇异值而背景的低秩部分通常只占前十几个奇异值所以在迭代中可以对临时矩阵调用svds(X, r)只算前 r 个奇异值和向量r 取20~50。这相当于一个低秩投影加收缩的近似实测在很多图像上能保持95%以上的检测效果速度却快很多。第三种加速是工程层面的用mex把补丁构建和重建循环写成C代码或者把补丁构建矩阵化。补丁构建本身可以用 im2col 替代循环而重建里的叠加平均也可以用 accumarray 一次性完成避免每次迭代都重建图像——不过这里要注意迭代过程中的重建只是为了让目标图像可视化真正需要的是直到最后再重建一次所以不要在每一轮迭代里都调用 patch2im。6.4 参数怎么快速确定最后总结一下我常用的参数标定顺序。先根据图像中目标的最大可能尺寸定 patchSize一般取目标最大尺寸的3~5倍并且是偶数更好方便后续步长设置。然后设 step2 起步如果棋盘格不明显再尝试 step1 提升重叠度。λ 用理论值初始化如果在目标图上出现大量背景杂斑就增大 λ如果目标亮度太弱就减小 λ每次只调整0.1倍不要一下子放大一倍。迭代容差固定为 1e-7最大迭代次数 300 到 500 即可。我个人在实际操作中的一个体会是IPI的性能上限很大程度上取决于“背景低秩、目标稀疏”这两个假设是否成立。对于干净的晴空、海面背景IPI效果近乎完美但对城市建筑群、密集云边、剧烈热辐射变化这类背景不可能指望一个凸优化模型把所有复杂纹理都归为低秩。实际项目里我通常把IPI当作前端预检测器输出候选点后再用时序关联或简单分类器过滤虚警这样既保留了IPI对弱目标的敏感性又能规避它在复杂背景下的误检问题。最后再分享一个小技巧在做补丁矩阵构建时不要急着把整张图一次塞进内存先打印一下 D 矩阵的尺寸和内存占用。如果 D 超过 2GB果断减小 patchSize、增大 step 或者分块处理否则后面每一次 SVD 都会让人等到怀疑人生。IPI是个好算法但只有在合理的工程约束下才能发挥出真正的价值。