做雷达数据处理的朋友都有过这种体会:屏幕上的点迹明明很多,但里面哪几个属于同一个目标,要是不处理,后面跟踪、滤波全都没法谈。航迹起始要解决的,就是在不知道目标数量、不知道点迹归属、杂波密度又很高的情况下,从最初几帧量测里把属于同一条运动轨迹的点捞出来。这篇文章我会把标准Hough变换、修正Hough变换和序列Hough变换这三种典型航迹起始算法放在一起讲,重点聊各自的思路、Matlab代码骨架、参数调试经验,以及什么场景该选哪一种。如果你正在做雷达数据处理、多目标跟踪,或者课程设计里要仿真一个航迹起始模块,这篇应该能帮你从“看懂公式”直接跳到“跑出结果”。
1. 航迹起始为什么要找Hough变换:雷达数据处理里的第一道坎
1.1 起始问题的本质:不知道目标数,不知道点归属,还要和杂波对抗
目标跟踪的标准流程里,航迹起始是第一道坎。后面的点迹-航迹相关、滤波、预测,全都默认你已经有一条“初步可信”的航迹了。航迹起始难在哪?第一,雷达每一帧会输出很多点迹,但真正来自目标的点可能只有一两个;第二,杂波产生的虚警点位置是随机的,它们在多帧扫描里可能恰好凑成一条假直线;第三,你事先并不知道监视区域里有几个目标,航迹起始算法要自动告诉你“这里有K条候选航迹”。
常用方法分两大类。一类是启发式逻辑法,最典型的是“滑窗+M/N逻辑”:如果一个候选航迹在连续N帧里至少有M帧能关联到点迹,就判为航迹成立。它简单、计算量小,但杂波一多,点迹-航迹关联的组合数会爆炸,而且它对“目标整体呈直线运动”这个信息利用得不够。另一类就是Hough变换家族。Hough变换擅长在强噪声背景下找直线,匀速目标的点迹在平面上恰好近似排成直线,所以Hough特别适合拿来做“先粗检测、后细确认”的航迹起始。
这里我要先纠正一个常见误区:Hough变换不是用来做精确拟合的。它做的是“检测”,告诉你数据空间里哪些位置存在可能的轨迹;精确的航迹参数,应该在Hough输出候选航迹后,再用最小二乘或者滤波器去细化。很多新人把Hough拟合出的直线直接当成最终航迹,后续跟踪质量会差不少。
1.2 匀速目标的“直线形状”:为什么Hough比逻辑法更占便宜
假设目标做匀速直线运动,那在X-Y平面上,目标每一帧的位置都会落在同一条直线上。雷达量测有噪声,检测概率也不是100%,所以实际看到的并不是严格共线的点,而是围绕这条直线散布的一团点,同时还混着大量杂波点。
Hough变换的核心理念是“以线换点”:把数据空间里的一个点,映射成参数空间里的一条曲线;数据空间里共线的多个点,映射成参数空间里交于同一点的一簇曲线。于是“找共线点”变成了“找参数空间里的峰值”。这个思路天然适合航迹起始,因为航迹起始恰恰不需要你先回答“每个点属于谁”,只需要回答“哪些点大致在同一条线上”。这也解释了一个现象:标准Hough在低信噪比场景下往往比逻辑法更稳,因为逻辑法对点迹组合做的是排列组合,而Hough把所有点的投票在参数空间里累加,相当于做了一次隐式的密度聚类。
但恰恰是“所有点都能投票”这个机制,让标准Hough在高杂波环境下吃了亏。杂波点随机分布,它们也会在参数空间形成峰值,目标本身的峰值可能因为点迹残缺反而没那么突出。后面出现的修正Hough和序列Hough,本质上都是在标准Hough的框架上增加约束:要么约束“谁跟谁才能一起投票”,要么约束“旧信息该如何衰减”。先懂这个脉络,后面三节读起来就顺了。
2. 标准Hough变换航迹起始:让每个点迹都在参数空间里投票
2.1 法线式参数空间:r = xcosθ + ysinθ
标准Hough变换使用直线的法线式方程:r = xcosθ + ysinθ。其中θ是直线法线与X轴的夹角,r是原点到直线的垂直距离。一条直线由(r, θ)唯一确定;反过来,平面上一个点(x, y),所有经过它的直线族在参数空间里对应一条正弦曲线。多个点如果共线,它们各自的正弦曲线会相交于同一个(r, θ),交点处的累加器数值就反映了落在该直线上的点数。
实现时要先把参数空间离散化。θ一般取0到π,步长Δθ大约0.5°到2°;r的取值范围根据监视区域确定,如果监视区最大距离是Rmax,r大致在[-Rmax, Rmax]之间,步长Δρ和距离分辨能力相当,比如10米或20米。离散化粒度直接影响检测效果:步长太小,参数空间太大,计算和内存开销高;步长太大,同一个直线参数会跨多个网格,峰值被摊平,门限也不好设。
代码里最核心的动作就一句:对每个量测点,遍历所有θ,算出对应ρ,给累加器A(ρ_index, θ_index)加1。但实际工程有个细节:雷达原始量测通常是极坐标(距离、方位),要先用坐标转换变成直角坐标。转换时距离误差和方位误差会耦合,如果雷达量测噪声很大,转换后的点迹分布会带方向性,这会影响后续投票的形态。所以做Hough前,我一般会先把坐标转换后的误差协方差算一下,做到心里有数。
2.2 从累加器到候选航迹:投票、峰值检测、反变换和拟合
标准Hough航迹起始的完整流程可以拆成五步:
- 确定处理滑窗,通常取3到5帧扫描数据。
- 把窗口内所有量测点投进参数空间累加器。
- 扫描累加器,找出超过门限η的峰值。
- 对每个峰值做反变换,得到数据空间中的直线方程。
- 在直线附近设定一个关联半径,把窗口内落在半径内的点归入对应候选航迹,并处理重复关联。
第5步特别容易漏。Hough给出的是直线参数,不是航迹。你得回到原始点迹,计算每个点到这条直线的距离,把距离小于阈值的点收进来,再重新做一次最小二乘拟合。这个“先峰值后拟合”的两步走,比直接拿峰值参数当航迹干净得多。
门限η的物理意义很直接:一个峰值的高度表示“有多少个点踩在同一条直线参数上”。理论上,如果滑窗N帧、目标每帧大概率出现、检测概率为pd,那么单目标峰值的期望约为N*pd。比如5帧滑窗,pd=0.85,期望峰值就是4.25。门限取太高会漏弱目标,取太低就会冒出大量假航迹。我自己的经验是先让门限等于期望峰值的0.5到0.7倍,再根据蒙特卡罗仿真微调。这个思路比拍脑袋强很多。
2.3 标准Hough的软肋:杂波自由投票和批处理延迟
标准Hough的每个点都一视同仁,杂波点也会各自映射成正弦曲线,然后参与累加。单个杂波点要形成伪峰很难,但杂波密度上去以后情况就变了:滑窗内的杂波点总数可能是目标点数的几十倍,即使每个峰需要的投票数只要5~10,随机组合也很容易凑出来。目标检测概率不高的时候,目标真实峰会被压低,和杂波峰混在一起,更加难分辨。
另一个麻烦是批处理延迟。标准Hough必须等滑窗内所有帧的点迹全部到齐,才能一次投票和检测,所以航迹起始时间至少是一个滑窗长度。如果目标速度快、机动性强,等3帧它可能已经转弯了,直线假设很可能失效。
正是这些软肋,催生了两种改进方向:修正Hough引入目标运动约束,把“乱投票”变成“有序投票”;序列Hough改成逐帧递推,把“一次性批处理”变成“在线累积”。它们都不是推翻标准Hough,而是在标准Hough的骨架上加约束而已。
3. 修正Hough变换:把速度约束焊进投票规则
3.1 从“单点投票”到“满足速度约束的点对投票”
看了标准Hough的投票方式,很容易想到一个问题:既然杂波点自由投票会形成伪峰,能不能在投票前先审查一下“哪些点有可能来自同一个目标”?修正Hough做的就是这件事。
最常见的做法是:不再对每个量测点独立投票,而是取相邻两帧的量测点组成点对,先检查这两个点之间的距离是否落在目标合理速度范围内。只有满足速度约束的点对,才根据它们确定的直线参数去投票。这个改动非常有效:杂波点虽然随机,但如果要让两个不同帧的杂波点在位置上恰好构成“看起来像合理速度”的一对,概率就低了很多。
更重要的是,这个约束把目标运动学先验直接嵌入了检测过程。标准Hough的峰值回答的是“有没有足够多的点在某条直线上”,修正Hough的峰值回答的是“有没有足够多的、运动上彼此关联的点在同一条线上”。这两个问题的可靠性完全不同,后者天然滤掉了很多不可能的组合。
速度门限怎么设?假设雷达扫描周期是T,目标速度范围是[vmin, vmax],那么相邻帧点对的距离d应满足:vminT ≤ d ≤ vmaxT。考虑到量测噪声和轻微机动,实际工程里我会把上界放宽20%到30%。但注意,放宽太多会让约束趋于失效,性能会慢慢退回标准Hough。这个度要由你的真实场景决定,不要照抄论文数字。
3.2 点对投票的实现细节:速度门限、计算量和优化
修正Hough的流程可以拆成四步:
- 选择参与点对投票的帧组合,常见是相邻两帧,也可以扩展成间隔l帧。
- 遍历两个不同帧里的所有点对,计算它们之间的距离d和对应速度。
- 通过速度门限的点对,反算它们所在直线的(r, θ),累加投票。
- 峰值检测和后续处理与标准Hough相同。
两个点A(x1,y1)和B(x2,y2)确定一条直线,反算(r, θ)有个细节:先求向量AB的方向角 φ = atan2(dy, dx),法线角度就是 θ = φ + π/2,然后把θ归一到[0, π)区间,再代入任意一点求ρ。如果不做角度归一化,同一个直线参数可能被投到累加器的两个不同位置,峰值就被拆散了。
计算量是修正Hough最大的痛点。标准Hough对每个点投票,复杂度是O(NNθ);修正Hough对两个不同帧的所有点对都要检查,复杂度会涨到O(N1N2),当每帧点迹有几百个时,双层循环会慢到让人怀疑人生。我一般先在空间上做网格分块,或者先按最大速度范围把第一帧的每个点在第二帧中的可能位置圈出来,只搜索落在圆形邻域内的第二帧点。这个预筛能把点对候选数量降一个数量级。
3.3 修正Hough的收益与代价
用一组直观数字说明收益:如果杂波密度使每帧有100个点,标准Hough里这100个点全部自由投票,参数空间很容易凑出3~4个杂波点构成的伪峰。修正Hough中,只有满足速度约束的帧间点对才有资格投票。假设目标速度范围占可能出现的速度总范围的比例只有十分之一,那杂波点对的有效组合数大约会降为原来的十分之一,伪峰概率自然大幅下降。
代价也很明显。第一,如果目标最大速度设得太大,约束变松,性能就退化了;设得太小,目标稍微机动一下就会断航。第二,它依然需要攒够一个滑窗才能批处理,起始延迟依然存在。第三,在检测概率低的情况下,同一目标相邻两帧都被检测到的概率降低,点对投票数会明显变少,弱目标更容易漏。所以修正Hough适合检测概率较高、目标速度范围已知、杂波密度中等的场景,不是万灵药。
4. 序列Hough变换:帧来了就更新,不攒满也能起始
4.1 批处理的延迟问题,序列处理的在线需求
标准Hough和修正Hough都有个共同缺点:必须等一个滑窗全部到齐,才能开始投票和检测。这在很多实时处理场景里是不能接受的。现代雷达数据处理系统普遍是逐帧滚动运行的,每来一帧量测,后端就希望尽快更新目标列表,而不是傻等3帧才开始判断。
序列Hough变换的出发点,就是把“批处理”改成“递推”。它不需要一次性把所有帧点迹投完,而是在每一帧到达时,把该帧的量测点投到更新后的累加器里,然后立刻判断有没有超门限的峰。累加器每帧还会做一次“衰减”或者“滑窗删除”,让旧帧信息逐步退出,防止很久以前的点一直影响当前判决。这样航迹起始的响应速度会快很多,甚至可能在前几帧就给出候选。
4.2 衰减因子递推公式和滑窗的取舍
序列Hough最典型的递推形式是:
A(k) = α * A(k-1) + 当前帧所有点迹的投票
α是衰减因子,0 < α < 1。这个公式等价于给过去的投票做指数衰减:越老的帧在累加器里的权重越低。α越大,历史信息保留越久,峰值更稳定,但杂波残留也越久;α越小,当前帧权重越大,响应快,但单帧噪声影响大。
如果从指数衰减的角度看,α=0.9时,大约7帧前投票还剩下0.9^7≈0.48,即一半左右;α=0.95时,13帧前还能剩一半。这个“记忆长度”会影响多目标场景下的判别。我在仿真里一般从0.85起步调试,然后再根据目标点密度和杂波强度去调。
另一种做法是固定长度滑窗:每一帧的投票放入队列,窗口滑出时把旧帧对应的累加贡献减掉。滑窗在逻辑上更严谨,不会受到很久以前点的扰动,但实现时要维护“每个网格被哪些帧投过票”,内存和更新逻辑都复杂一些。衰减因子方式实现极简单,一行 A = alpha * A 就搞定了,工程上更受欢迎,我也更推荐先用衰减方式把流程跑通。
4.3 峰值摘除与多目标处理:序列Hough工程上的关键细节
序列Hough有个特别容易被忽略的工程动作:峰值摘除。当一个峰超过门限,确认了一条候选航迹后,必须把峰值周围一小片区域的累加值清零或者削弱。否则下一帧同一个目标继续投票,同一个峰又会再次触发,导致一条目标被重复确认成好几条航迹。清零半径一般取θ方向一个量化步长、ρ方向一个量化步长的3到5倍,具体看参数空间的量化粒度。
多目标场景下,累加器里会有多个峰。峰值搜索不能只找全局最大值,要按“局部极大值”来找:先找出所有大于门限的网格,再把邻近的候选合并成一个峰,保证一个目标只输出一条航迹。我习惯的做法是:把累加器当成二维图像,做一次局部极大值提取,再按峰值从高到低排序确认。
序列Hough还能进一步利用“局部投票”技巧。当前帧的量测点(x_k, y_k),根据目标最大速度vmax,可以推断它上一帧只能出现在半径R=vmax*T的圆内。于是投票时不必遍历整个半平面的θ,只投给那些与上帧点迹连线方向落在合理范围内的网格。这和修正Hough的速度门限异曲同工,但因为逐帧递推,计算量被压得更低,在线性也更好。
5. 三种算法横向对比:机制差异、仿真设计和评估指标
5.1 一张表看清三者的本质差别
| 对比维度 | 标准Hough | 修正Hough | 序列Hough |
|---|---|---|---|
| 投票单元 | 单个量测点 | 满足速度约束的帧间点对 | 当前帧量测点,可叠加速度约束 |
| 是否利用运动学先验 | 否 | 是,速度门限 | 是,递推/衰减等效时间约束 |
| 处理模式 | 批处理,攒满滑窗 | 批处理,攒满滑窗+点对 | 逐帧递推,在线处理 |
| 主要优点 | 实现简单,原理清楚 | 杂波下虚警明显下降 | 响应快,内存固定,支持在线 |
| 主要缺点 | 高杂波下伪峰多,起始延迟大 | 点对组合计算量高,依赖速度范围 | 衰减参数敏感性高,重复确认要小心 |
| 典型适用场景 | 低杂波、目标少、原理验证 | 杂波中等、目标速度范围已知 | 对起始时间敏感、需逐帧输出航迹 |
这张表是选型的第一参考。标准Hough适合先跑通流程、验证参数空间概念;修正Hough适合需要压虚警但不追求实时性的场景;序列Hough适合最终产品级实现,对存储和延迟更友好。
5.2 仿真场景里必须有的四个要素
很多入门者搭仿真时只生成一条干净直线就去跑Hough,这能验证代码,但完全验证不了算法质量。一份可信的航迹起始仿真场景至少要有四样东西:
- 目标真实运动轨迹:匀速直线,或者带轻微机动,给出每一帧的位置真值。
- 量测噪声:在真实位置上叠加高斯噪声,距离方差和方位方差要按雷达参数设置。
- 检测概率:并不是每帧都能检测到目标,一般设在0.7~0.9,让部分帧出现缺帧。
- 杂波:每帧在监视区域内随机生成Nm个点,Nm服从泊松分布,位置均匀分布。
生成完数据后,把所有点迹按帧号打包,喂给算法。不同的算法要用同一份数据跑,才有对比价值;否则杂波随机种子一变,结论就没法归因了。
5.3 三个指标和蒙特卡罗调参思路
衡量航迹起始算法,我通常只看三个指标:
- 正确航迹起始率:能正确起始的目标数除以目标总数。这个指标太低,说明漏警严重。
- 虚假航迹率:错误起始的航迹数除以总起始航迹数。这个指标太高,后端会被假航迹塞满。
- 平均起始帧数:目标出现后第几帧被确认。批处理算法一般受滑窗长度限制,序列Hough期望更早。
这三个指标互相制约。把门限调高,虚假航迹率会下降,但正确起始率也可能下降、起始帧数变晚。所以调参要做蒙特卡罗仿真,跑几十次随机杂波和噪声,画出“正确起始率-虚假航迹率”的权衡曲线,然后选曲线拐点附近的门限。只跑一次仿真就拍板,很容易被随机性骗了。
6. Matlab代码骨架:造数据、三种投票、一套流程
这一节给的是核心代码骨架,不是完整工程,但主要逻辑和正式代码一致。你把它补上数据加载和画图代码,就能支撑一轮蒙特卡罗仿真。
6.1 先造一份带杂波的点迹数据
% 参数设置 T = 1; % 雷达扫描周期 Nf = 5; % 总帧数 v = [20, 30]; % 目标速度X/Y分量, m/s x0 = [1000, 2000]; % 目标初始位置 sigma_r = 10; % 距离量测噪声标准差 sigma_beta = 0.01; % 方位量测噪声标准差, 弧度 lambda_false = 30; % 每帧平均杂波点数 Pd = 0.85; % 检测概率 rng(2024); scans = cell(Nf, 1); for k = 1:Nf xt = x0(1) + v(1) * (k-1) * T; yt = x0(2) + v(2) * (k-1) * T; meas = []; if rand < Pd rb = sqrt(xt^2 + yt^2) + sigma_r * randn; be = atan2(yt, xt) + sigma_beta * randn; [xtm, ytm] = pol2cart(be, rb); meas = [meas; xtm, ytm]; end Nfalse = poissrnd(lambda_false); xf = 2000 * rand(Nfalse, 1); yf = 2000 * rand(Nfalse, 1); meas = [meas; xf, yf]; scans{k}.meas = meas; end这里把量测转到了直角坐标,方便后面算直线参数。实际工程里也可以在极坐标下做Hough,但直角坐标更容易画图、更容易验证。pol2cart的输入输出顺序是pol2cart(angle, radius),返回[x, y],别把顺序搞反了。
6.2 标准Hough投票:先跑通最朴素版本
pMax = 3000; dTheta = deg2rad(1); thetaBin = round(pi / dTheta); rhoStep = 10; pBin = round(2 * pMax / rhoStep) + 1; A = zeros(pBin, thetaBin); for k = 1:Nf P = scans{k}.meas; for i = 1:size(P, 1) x = P(i, 1); y = P(i, 2); for t = 1:thetaBin th = (t - 1) * dTheta; p = x * cos(th) + y * sin(th); idx = round((p + pMax) / rhoStep) + 1; if idx >= 1 && idx <= pBin A(idx, t) = A(idx, t) + 1; end end end end thresh = 4; [maxVal, linIdx] = max(A(:)); [pIdx, tIdx] = ind2sub(size(A), linIdx);这个版本性能很差,但逻辑最透明。优化时把最内层循环改成向量运算:对一个点,一次性生成所有θ,再算p向量,一次累加到A那一列。正式仿真里我还会把A建成稀疏矩阵,因为监视区域大时大部分网格没有点迹,稀疏结构能省一大块内存。
6.3 修正Hough点对投票:速度门限版
dMin = 5; % 最小速度对应的帧间位移 dMax = 50; % 最大速度对应的帧间位移 A = zeros(pBin, thetaBin); for k = 1:Nf - 1 P1 = scans{k}.meas; P2 = scans{k + 1}.meas; for i = 1:size(P1, 1) for j = 1:size(P2, 1) dx = P2(j, 1) - P1(i, 1); dy = P2(j, 2) - P1(i, 2); d = sqrt(dx^2 + dy^2); if d >= dMin && d <= dMax th = atan2(dy, dx) + pi / 2; if th < 0, th = th + pi; end if th >= pi, th = th - pi; end p = P1(i, 1) * cos(th) + P1(i, 2) * sin(th); idx = round((p + pMax) / rhoStep) + 1; tIdx = round(th / dTheta) + 1; if idx >= 1 && idx <= pBin && tIdx >= 1 && tIdx <= thetaBin A(idx, tIdx) = A(idx, tIdx) + 1; end end end end end这里θ用的是两点连线方向的法方向,角度要归一到[0, π)。dMin和dMax的单位一定要和坐标单位保持一致,如果坐标是公里,那dMax就要换算。速度门限的物理含义不是约束两点“一定来自同一目标”,而是淘汰“不可能来自同一目标”的组合,所以上下限可以留噪声余量。
6.4 序列Hough递推更新:衰减加摘除
alpha = 0.9; A = zeros(pBin, thetaBin); startedTracks = []; for k = 1:Nf A = alpha * A; P = scans{k}.meas; for i = 1:size(P, 1) x = P(i, 1); y = P(i, 2); for t = 1:thetaBin th = (t - 1) * dTheta; p = x * cos(th) + y * sin(th); idx = round((p + pMax) / rhoStep) + 1; if idx >= 1 && idx <= pBin A(idx, t) = A(idx, t) + 1; end end end [rows, cols] = find(A > thresh); for q = 1:length(rows) r0 = rows(q); c0 = cols(q); A(max(1, r0 - 2):min(pBin, r0 + 2), ... max(1, c0 - 2):min(thetaBin, c0 + 2)) = 0; startedTracks = [startedTracks; r0, c0, k]; end end这个“清零”是序列Hough最容易出错的地方。清零半径太大,会连别的目标的峰一起消掉;太小,同一个峰下一帧又冒出来,重复航迹就会涌现。我把累加器当成图来看,确认清零后的效果。注意衰减因子和清零会共同影响“一个目标能被连续确认几次”,要做扎扎实实的统计,不能凭感觉。
6.5 主函数调用顺序:从数据到航迹输出
完整调用顺序大概是:先生成扫描数据,然后选择一种Hough算法得到累加器峰值,再做反变换和点迹关联,最后用最小二乘拟合目标航迹参数。下面这段是标准Hough的示意:
% 1. 参数区 thresh = 4; assocR = 30; % 关联半径, 米 % 2. 得到峰值后反变换 theta_i = (tIdx - 1) * dTheta; rho_i = (pIdx - 1) * rhoStep - pMax; % 3. 对所有帧的历史点迹计算到直线距离 candPoints = []; for k = 1:Nf P = scans{k}.meas; d = abs(P(:,1) * cos(theta_i) + P(:,2) * sin(theta_i) - rho_i); idxSel = find(d < assocR); candPoints = [candPoints; P(idxSel, :)]; end % 4. 最小二乘拟合直线,得到目标航迹参数 coef = polyfit(candPoints(:,1), candPoints(:,2), 1);关联半径assocR的取值不能比量测噪声大太多,否则会把杂波也收进来;太小又会丢目标点。一个相对稳的起点是3倍的距离量测标准差。最小二乘拟合相当于在Hough检测的基础上做了精化,输出给后续跟踪滤波器使用。
7. 仿真调参时踩过的坑和我的应对
7.1 参数归一化:别让坐标尺度扭曲投票结果
第一次做Hough航迹起始时,我发现累加器里的峰值总是集中在某些角度上,怎么调都像有系统偏差。后来排查发现,X和Y方向的量测范围差太多,一个方向坐标方差远大于另一个,法线式参数空间被“拉长”了,导致峰值向某些方向倾斜。解决方式很简单:在投票前把坐标先归一化,按监视区域最大距离缩放到[-1, 1],跑完再逆变换回实际坐标。这个操作不改变算法逻辑,但会显著改善峰值分布。
7.2 峰值搜索的局部极大值:否则一条航迹会变成好几条
批量仿真时经常出现一个目标输出两三条相近航迹的情况。原因前面提过:目标点迹在参数空间形成的不是单点,而是小山包,多个相邻网格同时超过门限,每个都被当成了独立峰。解决方法是峰值搜索时只保留局部极大值,或者确认一个峰就把邻域清零。我建议两个都做:先用局部极大值筛一遍,再在航迹确认时对峰值邻域做抑制,双保险。
7.3 门限不是拍脑袋:先理论期望,再蒙特卡罗扫描
门限是航迹起始里最敏感的参数。我的做法分两步:第一步按检测概率和滑窗长度算理论期望值,比如5帧滑窗、pd=0.85,期望峰值4.25,阈值先定2到3。第二步扫描阈值,跑50次蒙特卡罗仿真,画出正确起始率和虚假航迹率的权衡曲线,选曲线膝盖位置。拍脑袋调阈值很容易在A场景正常、B场景崩盘,所以尽量让数据替你做决定。
7.4 航迹起始之后还要做什么:拟合、滤波、级联
Hough给出的候选只是“航迹头”,不是最终跟踪结果。我习惯在确认后用提取到的点迹做一次最小二乘拟合,得到位置和速度初值,再交给α-β滤波器或者卡尔曼滤波器做持续平滑外推。还有一个实用技巧:把Hough检测到的直线参数作为滤波器状态初始化的一部分,收敛会快很多,也能减少起始阶段的振荡。
如果想把性能再往上推,可以把Hough和逻辑法级联:Hough先做粗检测,给出候选点迹集合,再用M/N逻辑对候选集合做细确认。这样既利用Hough的直线聚类能力,又利用逻辑法的组合判决,综合效果往往好于单一算法。我在一个模拟项目里试过这种级联,同样门限下虚假航迹率又降了约三分之一。
最后分享一个我自己的调试习惯:每次改完算法,先画三张图——原始点迹散点图、参数空间累加器热图、检测直线叠加图。只要三个图能对上,参数问题基本就暴露了。调参不是玄学,是“先跑通、再量化、再权衡”的过程。希望这套代码骨架和踩坑记录能帮你把标准Hough、修正Hough和序列Hough真正跑起来;如果你也在做航迹起始,很高兴和你交流各自的场景和参数设置。