Files
radar_data_process/docs/track_init_readme.md
T
2026-09-18 11:09:57 +08:00

39 KiB
Raw Blame History

航迹起始(Track Init)详细逻辑说明

适用范围:data_process_class_dll/track_init.cpp / track_init.h(类 Track_Init),TWS 通道逻辑法航迹起始。 关联文件:kalman.cpp(滤波与统计距离)、coor_trans.cpp(坐标转换)、track_index_mangement.cpp(航迹号分配)、data_process.cpp(调用与参数)。 本文按当前源码实际行为梳理,行号均指当前版本文件行号;与 README.md 第 8.3 / 9.3 节存在差异之处,见第 12 节。


目录

  1. 模块定位与调用时机
  2. 数据结构与状态容器
  3. 处理流程总览
  4. 阶段一:输入装填与圈数计数
  5. 阶段二:点迹与临时航迹关联
  6. 阶段三:点迹与航迹头关联
  7. 阶段四:点迹消费与航迹头生成
  8. 阶段五:临时航迹转可靠航迹(起批)
  9. 阶段六:临时航迹消亡
  10. 起始条件汇总表
  11. 关键算法细节
  12. 与其他文档/实现的差异
  13. 参数与调参
  14. 输出字段映射
  15. 已知问题与风险点
  16. 代码索引

1. 模块定位与调用时机

1.1 在整体流程中的位置

航迹起始处于 TWS 处理链的末端:只有「当圈未被任何可靠航迹关联上」的剩余点迹才会进入起始流程。

flowchart LR
    A["data_preprocess<br/>点迹入 Data_buffer"] --> B["dot_coh(model=2)<br/>两帧滑窗凝聚"]
    B --> C["track_asso(model=1)<br/>IMM + EKF 点航关联<br/>已用点从 point_recv 删除"]
    C --> D["track_die<br/>可靠航迹消亡"]
    D --> E["<b>track_init</b><br/>逻辑法航迹起始"]
    E --> F["Trust_Track_Output<br/>(起批航迹 + 历史点)"]

调用点(data_process.cpp):

// L130-161,仅 model==1(TWS 满一圈)时执行
track_asso.track_asso_process(&point_recv, &trust_track, ...);   // 入口删除 Use_Flag==1 的点
track_die.track_die_process(&trust_track, ...);
track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
                                    Trust_Track_Output, Trust_track_num_Output, Work_Parameter);

1.2 调用频率 = 扫描圈

data_preprocess 在 Data_Input[0].Beam_index_aiz == 0 时返回 1(data_process.cpp L51),宿主据此调用 track_process(model=1)。 因此 track_init_process_logic() 每次调用对应 TWS 一圈,Temp_track::buff_round(已缓存圈数)正是以「调用次数」为单位累加的。

1.3 类接口

成员 可见性 作用
track_init_process_logic(...) public 起始主流程(唯一入口)
reset() public 清空内部点迹缓存 + 复位航迹号分配器
point_temp_track_asso(...) private 点迹 ↔ ≥2 点临时航迹关联
point_track_head_asso(...) private 点迹 ↔ 1 点航迹头关联
tmp_track_to_trust_track(...) private 收集 → 去重 → 起批 → 输出
tmp_track_die(...) private 临时航迹消亡
alpha_cal_track_init(...) private 三点折角(度)
track_init_prohibit(...) private 起批屏蔽区判断
track_index_mangement private 航迹号(1~500)分配器

temp_track(std::vector<std::vector<Temp_track>>)不是 Track_Init 的成员,而是 Data_Process::temp_track,由调用方传入并跨圈保持;track_clear_all() 中清空(data_process.cpp L233),Track_Init::reset() 只清自己的 point_process。


2. 数据结构与状态容器

2.1 Temp_track(临时航迹点,struct.h L94-121)

字段 含义 起始流程中的作用
X[4] 4 维状态 [x, vx, y, vy] EKF 一步预测的状态;由两点卡尔曼初始化填充
P[4][4] 状态协方差 同上的协方差
r / azi / vr / height / snr / RCS / Amp 该点的极坐标测量 初始化、输出、条件判据(azi 为弧度)
T / GNSS_time CPI 时间 / GNSS 时间(ms) ΔT 计算(除 1000 转秒)
d 该点关联时的马氏统计距离 去重阶段用于「保留更优者」
buff_round 已缓存圈数 生命周期与关联时机控制(见 4.2)
asso_flag 0 未关联 / 1 已关联(待删除)/ 2 去重淘汰 临时状态标记
pitch_num、speed_dim[129]、range_dim[8] 俯仰波位、十字星数据 纯透传
Temp_track_section_idx 临时航迹区号 相关赋值已被注释停用,当前不参与逻辑

⚠ 下标陷阱:Temp_track::X 是 4 维 [x, vx, y, vy](y 在 index 2),而 Trust_Track::X 是 6 维 [x, vx, ax, y, vy, ay](y 在 index 3)。两套结构混用下标必错。

2.2 容器语义

容器 语义
temp_track(外层 vector) 「临时航迹表」:外层每一项 = 一条临时航迹,下标是不稳定的位置号(擦除后会重排);内层 vector = 该航迹按时间正序的点序列
point_process(Track_Init 成员) 本次调用待处理的点迹工作副本
point_recv(Data_Process 成员) TWS 点迹文件:凝聚输出 → 被 track_asso 消费 → 剩余点进入 track_init → 被清空

2.3 输入与输出

参数 方向 说明
point_recv 输入/输出 输入:当圈未被可靠航迹关联的点迹;输出:被清空(track_init.cpp L118)
trust_track 输入/输出 可靠航迹表;起批成功的临时航迹 push_back 到此
temp_track 输入/输出 临时航迹表(跨圈状态)
Trust_Track_Output[MAX_TRACK_NUM][10] 输出 与 track_asso 共用同一数组与同一计数;起始流程在 track_asso 已写入的行之后追加新航迹的历史点
Trust_track_num_Output 输出 累计行数(track_asso 输出 N 行后,track_init 在其后继续追加)
Work_Parameter 输入 运行时雷达参数(RadarPara)

输出行数核算:track_asso 先为当圈全部可靠航迹写 N 行;track_die 随后删除 D 条(已写出的行与计数不变);起始流程每次起批前判 trust_track->size() < MAX_TRACK_INDEX(500),故最多再追加 499 − N + D 行,合计最多 499 + D 行。正常情况下 D 很小,远小于 500;但理论上当 D ≥ 2 且当圈起批数量恰好占满配额(需数百条同时起批)时,行号会超过第一维容量 500,存在越界写入风险(极端场景,见 15.14)。第二维 [j](j < L ≤ 10)始终安全。代价是可靠航迹数达 500 时不再允许新起批。


3. 处理流程总览

flowchart TD
    S["track_init_process_logic 入口"] --> A1["① 取出 point_recv → point_process"]
    A1 --> A2["② temp_track 全部点的 buff_round += 1"]
    A2 --> Q{"point_process 空?"}
    Q -- 是 --> DIE["⑥ tmp_track_die"]
    Q -- 否 --> B["③ point_temp_track_asso<br/>点 ×(≥2 点临时航迹)"]
    B --> C["④ point_track_head_asso<br/>点 ×(1 点航迹头)"]
    C --> E["⑤ 删除 Use_Flag==1 的点<br/>剩余点 → 新建航迹头(buff_round=1)"]
    E --> F["⑦ tmp_track_to_trust_track<br/>收集 → 去重 → 起批 → 输出"]
    F --> G["清空 point_process / point_recv"]
    G --> DIE
    DIE --> R["return 0"]

要点顺序(不可颠倒):

  1. buff_round 先自增,再做关联 —— 这正是「同一圈内不重复关联同一条临时航迹」的实现方式(见 4.2)。
  2. 先关联 ≥2 点临时航迹,再关联 1 点航迹头 —— 前者优先,但两者不互斥:同一圈可同时被两边关联(点迹可以既延续长航迹,又延续航迹头)。
  3. 关联结果统一用 Use_Flag 标记,最后一次性物理删除 —— 只有 Use_Flag==0 的剩余点才会变成新航迹头。
  4. 转可靠航迹在航迹头生成之后 —— 因此当圈新建的航迹头(1 点)不参与当圈起批。
  5. 消亡在最后执行 —— 且即使当圈无剩余点迹(跳过 ③~⑦)也照常执行。

4. 阶段一:输入装填与圈数计数

4.1 装填(track_init.cpp L36-38)

for (size_t i=0;i<(*point_recv).size();i++)
    point_process.push_back((*point_recv)[i]);      // 全量拷贝,不检查 Use_Flag
  • 上游 track_asso 已把关联上的点从 point_recv 中删除,所以这里进来的都是「未关联点」;函数内部不再用 Use_Flag 过滤输入(Use_Flag 只用于本次调用内部两阶段关联的标记)。
  • 点迹的 Range 已在凝聚阶段做过 [R_MIN=100, R_MAX=100000] m 量程过滤。

4.2 buff_round 自增(L40-47)

for (每一条临时航迹 j) (*temp_track)[i][j].buff_round += 1;

含义与后果(关键机制):

动作 效果
新建点(航迹头 / 新关联点) buff_round = 1
每经过一次 track_init 调用(一圈) 该航迹内所有点的 buff_round +1;关联判据只看末点
关联判据要求 末点.buff_round >= 2 等价于:上一圈加入的点才能被本圈延续 ⇒ 同一条临时航迹每圈最多吸收一个点(除分裂,见 5.5)
消亡判据 末点.buff_round > 2 等价于:连续 2 圈没有新点就消亡(允许中间空 1 圈,最大关联间隔 2 圈)

时间戳只在关联时用 ΔT = (T_point − T_末点)/1000 秒表示,不参与生命周期判断;生命周期完全由「圈数」驱动。这也意味着临时航迹的存活时间与扫描周期成正比,与航迹的绝对时间无关。

生命周期时间轴(以 scan k 为起点)

scan k    : 新点加入 → 末点 buff_round = 1        (本圈不能再关联)
scan k+1  : 自增 → 2 → 允许关联;若关联成功 → 新点 1;若失败 → 2 > 2 不成立,存活
scan k+2  : 自增 → 3 → 仍然允许关联;若成功 → 新点 1;若失败 → 3 > 2 成立,消亡

5. 阶段二:点迹与临时航迹关联

函数:point_temp_track_asso()(L145-304)。双重循环:外层点迹、内层临时航迹,无一对一竞争约束。

5.1 参与资格(L164)

if( (*temp_track)[j].size() > 1 && (*temp_track)[j][L-1].buff_round >= 2 )
  • 只处理 ≥2 点 的临时航迹(1 点的是航迹头,留给阶段三);
  • 末点必须来自历史圈次。

5.2 关联判据(L167-221)

设点迹量测(Z[3]):


Z=\begin{bmatrix} r_p \\ \theta_p \\ v_{r,p}\end{bmatrix},\qquad
\mathbf{X}_{k-1}=\text{末点状态}[x,v_x,y,v_y],\qquad
\Delta T=\frac{T_p-T_{\text{末点}}}{1000}\ \text{(s)}

前置硬门限(L191):|r_p − r_末点| >= 5 —— 距离差小于 5 m 的点直接不参与(连 d 都不算)。

统计距离(L194-195):调用 kalman::d_cal_track_init_EKF(Z, X, P, ΔT, prt, freq_ind) 得到 3 维量测的马氏距离平方 d(详见 11.2)。

折角(L199-205):取末点前一点 → 末点 → 新点,计算折角 α(度,详见 11.3)。

三条件同时成立(L221):


\boxed{\;d^2 \le \text{TRACK\_START\_THRESHOLD}^2=64
\quad\wedge\quad
\alpha < \text{ALPHA\_START}=60^\circ
\quad\wedge\quad
v_{r,p}\cdot v_{r,\text{末点}} > 0\;}

⚠ 由于 d_cal_track_init_EKF 返回的 d 本身就是马氏距离平方,而判据又写作 d*d <= 64(即 d <= 8),实际生效的门限是「马氏距离平方 ≤ 8」。3 自由度卡方分布 95% 分位为 7.815,因此该写法恰好给出约 95.4% 的置信门限(详见 11.2 与 15.6)。

5.3 记录关联(L223-231)

命中后向 asso_info 记录 (point_idx=i+1, track_idx=j+1, d),同时:

  • 末点.asso_flag = 1 → 标记原航迹待删除;
  • point_process[i].Use_Flag = 1 → 标记该点已被消费。

索引在整个扫描循环中保持稳定(循环期间不修改两个容器),因此后面的 1-based 索引回查是安全的。

5.4 生成新临时航迹(L237-281)

对每一条关联记录:复制原航迹的全部 L 个点(并把各点 asso_flag 清 0)、再追加关联上的新点,作为一条新的临时航迹 push 到 temp_track 末尾。

新点字段:极坐标测量、幅度、SNR、RCS、时间、俯仰波位、十字星数据全套透传;asso_flag=0、buff_round=1、d = asso_info[i].d; 状态与协方差由 两点卡尔曼初始化 得到(L268-279):

Z0 = (末点.r·cos(末点.azi), 末点.r·sin(末点.azi));   // 原航迹末点
Z1 = (点.Range·cos(点.Azimuth), 点.Range·sin(点.Azimuth));
T  = (点.CPI_Time − 末点.T)/1000.0;
Kalman.kalman_filter_init_2dots(Z0, Z1, T, X, P);   // 4 维 CV 初始化

5.5 航迹分裂(重要特性)

关联循环不做一对一分配:

  • 一个点若满足多条临时航迹的判据,会为每一条各自复制出一条新航迹 —— 一条母航迹可以派生多条子航迹;
  • 一条临时航迹若被多个点同时命中,也会各自复制成多条子航迹(每个子航迹只带一个点);
  • 因此同一圈内可能产生大量同源临时航迹,靠第 8.3 节的去重收敛。

5.6 删除被关联的原航迹(L283-303)

遍历 temp_track,凡 size()>=2 且 末点 asso_flag==1 者整体 erase(每次擦除后 Iter = begin() 重扫,O(n²) 但结果正确;1 点航迹不在此处理)。新生成的子航迹末点 asso_flag==0,不会被误删。


6. 阶段三:点迹与航迹头关联

函数:point_track_head_asso()(L306-445)。这是两点起始:航迹头 + 1 个点 → 2 点临时航迹。

6.1 参与资格(L325)

if((*temp_track)[j].size()==1 && (*temp_track)[j][0].buff_round >= 2)

只处理 1 点且该点来自历史圈次的航迹头。

6.2 关联判据(L328-372)

点迹与航迹头都转直角坐标后取平面距离:


x_p = r_p\cos\theta_p,\quad y_p=r_p\sin\theta_p;\qquad
x_h=\text{头.X}[0],\quad y_h=\text{头.X}[2]

dis=\sqrt{(x_p-x_h)^2+(y_p-y_h)^2},\qquad \Delta T=\frac{T_p-T_h}{1000}\ \text{(s)}

当前生效的判据(L368-372):


\boxed{\;
dis \le V_{max}\Delta T
\;\wedge\;
dis \ge V_{min}\Delta T
\;\wedge\;
v_{r,p}\cdot v_{r,h} > 0
\;\wedge\;
dis \ge |v_{r,p}|\,\Delta T
\;\wedge\;
dis \ge |v_{r,h}|\,\Delta T\;}

各条件的物理含义:

条件 含义
dis ≤ V_max·ΔT 平均速度不超过最大速度(V_max = Work_Parameter.V_MAX)
dis ≥ V_min·ΔT 平均速度不低于最小速度(滤除静止/慢速点)
v_r,p · v_r,h > 0 两点多普勒径向速度同号(同接近/同远离)
dis ≥ abs(v_r,p)·ΔT 位置变化不小于点迹自身多普勒在这段时间内的径向位移(多普勒-几何一致性)
dis ≥ abs(v_r,h)·ΔT 同上,用航迹头的多普勒约束

近/远程分支(L348-356)当前是死分支:work_mode==0 与 else 都取 vmax = Work_Parameter.V_MAX(注释"近程模式最大速度减小一点"未生效;原 V_MAX/5 写法仅保留在未启用的 track_init_direct_tracking.cpp 注释里)。

ΔT 语义:判据隐含要求 ΔT > 0(若 ΔT < 0,则 dis ≤ 负数 必假)。ΔT == 0 时仅在 dis == 0(同时间同位置的点)才可能成立,属退化情形。

与文档差异:源码中被注释停用的旧判据按距离分段收紧门限(r<1000 时半速、中远程 r<2000 时 1/2~1/3 速),见第 12 节。

6.3 命中后的处理(L374-427)

  • 记录 (point_idx, track_idx),标记 头.asso_flag=1、point_process[i].Use_Flag=1;
  • 逐条生成 2 点临时航迹:第 1 点 = 原航迹头(asso_flag 清 0),第 2 点 = 新点(全套测量透传,buff_round=1,asso_flag=0,d 未赋值,保持 0);
  • 同样调用 kalman_filter_init_2dots 得到 4 维状态/协方差(Z0 = 头点、Z1 = 新点,T = (点.CPI_Time − 头.T)/1000)。

6.4 删除被关联的航迹头(L429-443)

凡 [0].asso_flag==1 的 1 点航迹头整条 erase(同样每次重扫)。 注意:本阶段遍历的是全部 temp_track,其中由阶段二新建的 ≥2 点航迹因 size()!=1 不会被误处理。


7. 阶段四:点迹消费与航迹头生成

7.1 删除已消费点迹(L57-70)

if((*Iter).Use_Flag==1) { point_process.erase(Iter); Iter = point_process.begin(); }

物理删除所有被阶段二或阶段三消费的点(两阶段共用同一 Use_Flag)。

结果:一个点迹要么参与延续已有临时航迹/航迹头(可同时延续两者),要么成为新航迹头,二者互斥。这保证不会「用同一个点既延续又起新头」。

7.2 剩余点回写(L72-75,等效空操作)

std::vector<PointRecv>().swap((*point_recv));
for (...) (*point_recv).push_back(point_process[i]);   // 回写未被使用的点

紧接着 L117-118 又把 point_process 与 point_recv 双双清空,因此这段回写不产生任何对外效果(point_recv 最终必然为空),属历史残留代码。

7.3 剩余点转航迹头(L77-114)

对每个剩余点构造 1 点 Temp_track:

字段 取值
r / azi / height / Amp / vr / snr / RCS 点迹对应量(azi 为弧度)
pitch_num / speed_dim / range_dim 透传
X[0] = Range·cos(Azimuth)、X[1]=0、X[2] = Range·sin(Azimuth)、X[3]=0 位置赋值,速度置 0(注意 4 维布局)
T、GNSS_time 点迹时间
asso_flag = 0、buff_round = 1 生命周期起点
d 未赋值 → 0

8. 阶段五:临时航迹转可靠航迹(起批)

函数:tmp_track_to_trust_track()(L458-671),四步。

8.1 步骤 1:收集候选(L467-499)

int L = (*Iter).size();
double range = (*Iter)[L-1].r;
double azi   = (*Iter)[L-1].azi/PI*180;      // 转度,供屏蔽区比较

if( (L <= 10 && L >= Work_Parameter.track_start_point_num) && track_init_prohibit(range, azi, Work_Parameter)==0 )
    { 复制到 Track_to_start; 从 temp_track 中 erase; }
else
    Iter++;

入选条件:

  1. 长度:track_start_point_num ≤ L ≤ 10(track_start_point_num 由宿主经 track_process_parameters_initial 传入,接口层校验范围 3~9,data_process.cpp L208);
  2. 不在起批屏蔽区内(用末点的距离/方位判断,见 8.5)。

命中即从 temp_track 摘出(先复制后删除,Track_to_start 成为候选表)。

结果:一旦长度达到阈值且不落在屏蔽区,当圈即起批(没有"连续 M 圈满足才起批"的确认期)。若因屏蔽区被拦住,航迹会继续在 temp_track 中增长,直到离开屏蔽区(此时 L > track_start_point_num,见 8.3 的副作用)或达到 L=10 被判消亡。

8.2 步骤 2:同源去重(L504-550)

int L = Work_Parameter.track_start_point_num;      // 注意:此 L 是"起批点数",不是航迹长度
for (i: 0 .. size-2)  for (j: i+1 .. size-1) {
    if (两者都未被标记 asso_flag==2) {
        if (某条长度 < 3) continue;                 // 防御性判断(实际不会触发)
        if (前三点中存在一点 "T 与 r 完全相同") {     // 硬编码比较点 0/1/2
            if (Track_to_start[i][L-1].d < Track_to_start[j][L-1].d)  j 标记 asso_flag=2;
            else                                                      i 标记 asso_flag=2;
        }
    }
}
// 然后统一删除 [0].asso_flag==2 的航迹
  • 同源判定:两条候选航迹的前三个点中,只要有一个点的 T 与 r 完全相等(同一点迹被复制而来),就视为同源重复;
  • 择优准则:比较 [L-1].d(第 track_start_point_num 个点的关联统计距离),保留 d 更小者;
  • 标记位置:只写 [0].asso_flag = 2,最后统一擦除;
  • 去重仅看前三点,且无论航迹实际多长都用固定下标 0/1/2。

8.3 步骤 3:起批与构造可靠航迹(L552-667)

if (Track_to_start[i].size() < 3) continue;               // 防御
if (trust_track->size() < MAX_TRACK_INDEX) {              // 500 条上限
    int index = track_index_mangement.track_ind_get(trust_track);
    if (index > 0) { ... }
}

三点卡尔曼初始化(L563-571,注意使用的是 L-3, L-2, L-1,即最后三个点):

Z0 = 极坐标→直角(点[L-3]);  Z1 = (点[L-2]);  Z2 = (点[L-1]);
T1 = (点[L-2].T − 点[L-3].T)/1000.0;   T2 = (点[L-1].T − 点[L-2].T)/1000.0;
Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1, T2, X, P);   // 6 维 [x,vx,ax,y,vy,ay]

⚠ 不一致点:初始化用「最后三点」,而输出历史点(8.4)只写「前 L 个点」。当航迹长度 = track_start_point_num 时两者一致;当因屏蔽区滞留使长度 > 阈值时,输出的历史点里不包含用于初始化的那三个点。

Trust_Track 字段构造(L573-608):

字段 赋值
X[6] / P[6][6] 三点初始化结果
X1/X2/X3 = X、P1/P2/P3 = P 三个 IMM 子模型同状态、同协方差初始化
u[0..2] = 0.3333 模型概率均分
Track_Index track_ind_get() 分配的航迹号
T_track / GNSS_time / Height 取末点的对应值
Amplitude / snr_point / RCS / pitch_num / speed_dim / range_dim 末点透传
point_flag = 1 实点
Track_Mode = 0 TWS
Target_Type = UNCONF_TARGET(0) 未确认目标
Extrapolate_round = 0 外推计数清零
approach_flag = -1 接近标志复位
manual_delete_flag / manual_tracking_flag / associate_point_number / point_type = 0 各类标志复位

L606-608 有一处死代码:coor_trans::cart2polar(X[0], X[3], &r, &azi)(把 X[3]=vy 当 y 用),计算结果 r/azi 从未被使用。

航迹号分配(track_index_mangement.cpp):空表返回 1;否则从 lastest_index+1 向上找第一个未被占用的号(含 500),找不到再从 1 回绕;全满返回 0(不起批,该候选被静默丢弃)。

8.4 输出更新航迹(L612-664)

  • *Trust_track_num_Output += 1,在 track_asso 已写的行之后追加一行;
  • 对 j = 0 .. L-1(L = track_start_point_num)写入该航迹的历史点:Track_Index、Point_Sum = L,以及每个点的距离/方位/俯仰、速度、幅度/SNR/RCS、俯仰波位、十字星数据、航向角、时间、直角坐标、原始点信息等(完整映射见第 14 节);
  • 之后该航迹的后续更新由 track_asso(每次 Point_Sum=1)负责。

8.5 起批屏蔽区(L699-712)

for (i = 0; i < Work_Parameter.track_prohibite_area_num; i++)
    if (r < R_max_track_prohibited[i] && r > R_min_track_prohibited[i] &&
        azi < Azimuth_max_track_prohibited[i] && azi > Azimuth_min_track_prohibited[i])
        return 1;      // 位于屏蔽区,禁止起批
return 0;
  • 判据是距离-方位矩形区域(azi 已转成度),最多 30 个(RadarPara 数组长度;数量未做上限校验);
  • 采用严格不等号:恰好落在边界上的点不算屏蔽;
  • 判断用末点极坐标,不使用高度。

9. 阶段六:临时航迹消亡

函数:tmp_track_die()(L673-697):

if (n <= 0)                            erase;   // 空航迹
if ((*Iter)[n-1].buff_round > 2 || n >= 10)  erase;
消亡条件 含义
序列为空 防御性清理
末点 buff_round > 2 连续 2 圈没有新点(允许空 1 圈)
长度 n >= 10 达到上限仍未完成起批(例如长期滞留屏蔽区)

消亡不输出任何信息(与可靠航迹消亡不同,起批失败的临时航迹对宿主不可见)。


10. 起始条件汇总表

10.1 条件链(一条 3 点起批航迹的完整判据)

阶段 判据(源码位置)
0. 输入资格 该点未被 track_asso 关联(已从 point_recv 删除);凝聚后 100 m ≤ r ≤ 100000 m
1. 生成航迹头 未被阶段二/三消费(Use_Flag==0);创建时 buff_round=1(L77-114)
2. 航迹头 → 2 点(L325, L368-372) 航迹头 size==1 且 buff_round ≥ 2;dis ≤ V_MAX·ΔT;dis ≥ V_MIN·ΔT;dis ≥ abs(vr_p)·ΔT;dis ≥ abs(vr_h)·ΔT;vr_p·vr_h > 0(隐含 ΔT > 0)
3. 2 点 → 3 点及以上(L164, L191, L221) 临时航迹 size > 1 且末点 buff_round ≥ 2;abs(r_p − r_末点) ≥ 5 m;d ≤ 8(代码写 d² ≤ 64);α < 60°;vr_p·vr_末点 > 0
4. 转可靠航迹(L484, L556) track_start_point_num ≤ L ≤ 10;末点不在起批屏蔽区;同源去重后保留;trust_track.size() < 500 且成功分配航迹号
5. 消亡(L686) 末点.buff_round > 2(连续 2 圈无新点)或 长度 ≥ 10

10.2 门限汇总

判据 数值 来源 可调
最大速度 V_MAX 运行时 RadarPara.V_MAX ✅
最小速度 V_MIN 运行时 RadarPara.V_MIN ✅
距离差下限 abs(Δr) 5 m(硬编码) track_init.cpp L191 ❌
起批波门 d ≤ 8 TRACK_START_THRESHOLD=8 parameters.h L49/L98 编译期
起批折角 ALPHA_START=60° parameters.h L50/L99 编译期
起批点数 39(典型 34) RadarPara.track_start_point_num ✅
临时航迹长度上限 10(硬编码) L484 / L686 ❌
允许丢圈数 1 圈(buff_round > 2 消亡) L686 ❌
航迹头上限等待 2 圈 L686(1 点航迹同判据) ❌
起批屏蔽区 ≤30 个矩形 RadarPara.track_prohibite_area_* ✅
航迹数上限 500 MAX_TRACK_INDEX 编译期

11. 关键算法细节

11.1 两点卡尔曼初始化(kalman.cpp L18-58)

输入两个直角坐标量测 Z0, Z1 与时间差 T(秒):


X=\begin{bmatrix}x_1\\ (x_1-x_0)/T\\ y_1\\ (y_1-y_0)/T\end{bmatrix},
\qquad
P_{axis}=\begin{bmatrix}R & R/T\\ R/T & 2R/T^2\end{bmatrix}

其中 R 取 Z1 处的转换量测协方差(极坐标→直角坐标,见 README.md 9.1 节),x/y 两轴独立填充 P[4][4]。

风险:T → 0 时速度与协方差同时发散,函数无除零保护(对应 BUG-18);临时航迹的两点初始化 T 来自相邻两圈,实际由时间戳决定,若宿主给出重复时间戳会异常。

11.2 EKF 统计距离(kalman.cpp L615-693)

对 4 维状态做 CV 一步预测(F = [[1,T,0,0],[0,1,0,0],[0,0,1,T],[0,0,0,1]],过程噪声 Q = 0.03²·I,G=[T²/2, T]ᵀ):


\hat X_{k|k-1}=F X,\qquad P_{k|k-1}=FPF^{T}+GQG^{T}

非线性量测函数(距离-方位-径向速度):


h(\hat X)=\begin{bmatrix}\sqrt{x^2+y^2}\\ \operatorname{atan2}(y,x)\bmod 2\pi\\ -\dfrac{x v_x+y v_y}{\sqrt{x^2+y^2}}\end{bmatrix},
\qquad H=\left.\frac{\partial h}{\partial X}\right|_{\hat X_{k|k-1}}

量测噪声 R = diag(σ_r², σ_a², σ_v²) = diag(10², 0.02², 2²),新息协方差 S = H P Hᵀ + R,残差做两项修正:

  • 方位环绕:ν_θ = wrapToPi(θ_p − θ_pred),归一化到 [−π, π);
  • 多普勒盲速折叠:ν_v ← ν_v − round(ν_v / v_bind)·v_bind,其中

v_{bind}=\frac{150000}{f_{GHz}\cdot PRI_{\mu s}}\ \text{(m/s)},
\qquad f_{GHz}=9.2+0.02\cdot f_{ind}

(PRI ≤ 0 或 f ≤ 0 时返回安全值 1.0;调用方约定 PRI 单位 µs,不允许为 0。)

返回马氏距离平方:


d=\nu^{T}S^{-1}\nu,\qquad \nu=Z-h(\hat X_{k|k-1})

调用参数来源:prt = point.PRF_index(实际存的是 DataRev.PRI,单位 µs);freq_ind = point.Freq_index。

11.3 折角计算(L448-456)


\alpha=\arccos\!\left(\frac{\vec D_{12}\cdot\vec D_{23}}{\|\vec D_{12}\|\,\|\vec D_{23}\|}\right)\cdot\frac{180}{\pi}

其中 D12 = (末点 − 前一点)、D23 = (新点 − 末点),三点均取 Temp_track 的直角坐标(X[0] 为 x、X[2] 为 y)。α 越小表示越接近直线;ALPHA_START=60° 表示允许的航向偏折。

无 acos 定义域钳位:当两点重合(分母为 0)或浮点误差使比值略超 ±1 时结果为 NaN,NaN < 60 恒为 false → 表现为「该点无法关联」,不崩溃。


12. 与其他文档/实现的差异

12.1 与 README.md 第 8.3 / 9.3 节的差异(README 已滞后)

项 README 描述 当前源码
点-航迹头判据 按距离分段:近程 r≥1000 全速 / r<1000 半速;中远程 r≥2000 全速 / r<2000 1/2~1/3 速 该分支已整体注释停用,改为统一 V_MIN·ΔT ≤ dis ≤ V_MAX·ΔT 外加两条多普勒一致性条件 dis ≥ abs(vr_p)·ΔT、dis ≥ abs(vr_h)·ΔT(L361-372)
起批触发长度 长度满足 track_start_point_num ≤ L ≤ 10 一致
初始化用点 最近三点 一致
输出历史点 "将临时航迹的全部 L 个点写入" 实际只写前 track_start_point_num 个点(L614 的 L 是起批点数,不是航迹长度)
use_flag 过滤 "track_init 跳过 Use_Flag==1 的点" 上游 track_asso 已删除已用点,track_init 自身不再按 Use_Flag 过滤输入

12.2 与 track_init_direct_tracking.cpp(引导跟踪版,未启用)的差异

项 track_init.cpp(TWS,启用) track_init_direct_tracking.cpp(未启用)
入口 完整实现 track_init_process_logic 空实现(L11-23)
点-航迹头判据 统一门限 + 两条多普勒一致性条件 仍为分距离段门限(V_MAX/5 亦在其注释中)
起批长度 L ≥ track_start_point_num(长度可大于阈值) L == track_start_point_num(严格相等)
去重 有同源去重 无
屏蔽区 有 无
航迹号 track_ind_get() 分配 外部传入 track_ID
消亡阈值 buff_round > 2 buff_round > 1(更严格)
输出模式 Track_Mode = 0(TWS) Track_Mode = 1(TAS)

13. 参数与调参

13.1 编译期宏(parameters.h)

宏 相扫/机扫 含义 调大效果
TRACK_START_THRESHOLD 8 / 8 起批波门,实际生效为 d ≤ 8 更容易起批(虚警增多)
ALPHA_START 60 / 60 起批折角(度) 更容易起批(允许更急的转向)
SIGMA_R / SIGMA_A / SIGMA_V 10 / 0.02 / 2.0 EKF 量测噪声 影响 S 与 d,进而影响关联难度
MAX_TRACK_INDEX 500 航迹号/航迹数上限 —
R_MIN / R_MAX 100 / 100000 有效量程(凝聚阶段过滤) —
PI 3.1415926f 角度换算(float 精度) —

13.2 运行期参数(RadarPara)

字段 影响
track_start_point_num 起批点数(接口校验 3~9):调大 → 起批更慢、更抗虚警,但机动目标捕获更迟
V_MAX / V_MIN 头-点关联的速度区间;V_MIN 调大可滤除静止/慢速点
work_mode 当前对起始流程无实际影响(近/远程分支取值相同)
track_prohibite_area_num + 4×30 数组 起批屏蔽区(距离 m / 方位 度)
track_start_threshold 声明但未被实现读取(实际用宏 TRACK_START_THRESHOLD)

13.3 调参直觉

  • 虚警多、起批太容易:先降 TRACK_START_THRESHOLD(8 → 5 左右),再降 ALPHA_START;必要时提高 track_start_point_num。
  • 真实目标起批慢:提高 TRACK_START_THRESHOLD / ALPHA_START,或降低 track_start_point_num(最小 3)。
  • 静止/低速杂波起批:提高 V_MIN。
  • 某方位扇区/距离段禁止起批(如地物、干扰区):配置起批屏蔽区,比调门限更精准。

14. 输出字段映射

对每条新起批航迹的每个历史点 j = 0 .. track_start_point_num-1(L614-664):

输出字段 来源 备注
Track_Index 新分配的航迹号 同一行内所有点相同
Point_Sum = track_start_point_num 与 track_asso 输出的 Point_Sum=1 可作区分
Range / Azimuth 点的 r / azi(→度) 极坐标
Elevation asin(h/r)(度,钳位 ±1) BUG-27 修复后的定义域钳位
z 点的 height
Range_V sqrt(X[1]² + X[3]²) = sqrt(vx²+vy²) 速度模值(非径向速度)
Direction_Angle atan2(vy, vx)(→度, [0,360)) 航向角
x / v_x / y / v_y 点的 X[0..3] 注意 4 维布局
range_point / azi_point / elev_point / vr_point 点的原始测量 point_type=0
Amplitude / track_snr / track_rcs 点的 Amp / snr / RCS
pitch_num / speed_dim / range_dim 点透传
track_time T/1000(秒)
GNSS_time 点透传
Flag_Point 恒为 1(实点)
Track_Mode 恒为 0(TWS)
未赋值 Flag_Direction、x_Predict/y_Predict/z_Predict、Range_Predict/Azimuth_Predict/Elevation_Predict、Target_Type、prf_point、v_z DLL 不清零该数组,宿主读到的是其自身缓存的旧值/未初始化值

15. 已知问题与风险点

以下为阅读源码得到的观察,不代表已确认的缺陷,部分为有意保留的历史行为;修改前请结合实测数据评估。

  1. 硬编码 5 m 距离差门限(L191):|r_p − r_末点| < 5 的点永不参与关联。对切向飞行、远距离慢速目标可能造成漏关联(起批失败)。
  2. 初始化用点与输出历史点不一致(L563-565 vs L614):航迹长度大于 track_start_point_num 时(典型原因:滞留在起批屏蔽区),用于初始化的最后三点未出现在输出的历史点中。
  3. 去重只比较前三个点(L517-519 硬编码 0/1/2),且优度指标取 [track_start_point_num-1].d(长度大于阈值时非末点);若 track_start_point_num > 3,共享前三点但后续分叉的两条航迹会被误判为同源。
  4. 无一对一关联约束:一个点可同时为多条临时航迹续点(阶段二/三),产生航迹分裂,虚警场景下临时航迹表可能快速膨胀(靠去重与消亡收敛)。
  5. ΔT ≤ 0 无显式校验(BUG-33 遗留):头-点关联靠 dis ≤ V_MAX·ΔT 隐式排除负 ΔT;EKF 关联路径 d_cal_track_init_EKF(ΔT<0) 会得到无物理意义的结果,只是通常会被 d ≤ 8 挡住。
  6. 判据对马氏距离平方二次平方(L221):实际门限为 d ≤ 8(3 自由度约 95.4%)。若按字面 d² ≤ 64 理解会误判为 d ≤ 64(极宽松)。修改 TRACK_START_THRESHOLD 时务必记得这一层。
  7. 两点/三点初始化无 T > 0 保护(BUG-18):T 极小(重复/乱序时间戳)时速度、加速度与协方差会爆炸性放大。
  8. 死代码:L72-75 的 point_recv 回写随即被 L117-118 清空;L606-608 的 cart2polar 结果未使用;L348-356 的近/远程分支取值相同。
  9. acos 无定义域钳位(L452):浮点越界产生 NaN,表现为条件恒假(静默丢弃该关联),不报错也不崩溃。
  10. 航迹数饱和静默丢弃:trust_track.size() ≥ 500 或 track_ind_get() 返回 0 时,候选被直接跳过且该临时航迹已从 temp_track 摘除 → 起批机会永久丢失。
  11. 未赋值输出字段(第 14 节):宿主若直接使用 x_Predict、Flag_Direction、Target_Type 等字段,会读到非本次写入的数据。
  12. PI 为 float 常量(3.1415926f)参与双精度运算,角度换算存在约 1e-7 量级偏差,属既有实现风格。
  13. 精度/量纲提醒:Temp_track::vr 直接取点的 Velocity(多普勒)作为径向速度,EKF 关联时用于残差折叠(盲速 v_bind)与符号判据;若宿主的 Velocity 存在模糊,符号判据 vr_p·vr_末点 > 0 可能失效。
  14. 输出数组行号可能越界(极端场景):容量判据用 trust_track->size() < 500,而 Trust_Track_Output 的行计数已在 track_asso 阶段按「消亡前」的航迹数推进,中间的 track_die 删除 D 条后腾出的配额会让起始流程多写 D 行;当 D ≥ 2 且当圈起批数占满配额时,写入行号可超过 MAX_TRACK_NUM=500(详见 2.3)。建议把判据改为同时校验 *Trust_track_num_Output < MAX_TRACK_NUM 与 trust_track->size() < MAX_TRACK_INDEX。

16. 代码索引

16.1 track_init.cpp

行号 内容
L11-143 track_init_process_logic 主流程
L36-38 装填点迹
L40-47 buff_round 自增
L49 if(point_process.size()>0) 主分支
L52 / L55 两阶段关联调用
L57-70 删除已消费点迹
L72-75 点迹回写(等效空操作)
L77-114 剩余点 → 航迹头
L117-118 清空 point_process / point_recv
L121 转可靠航迹调用
L126 临时航迹消亡调用
L145-304 point_temp_track_asso
L164 ≥2 点 + buff_round≥2 资格
L191 abs(Δr) ≥ 5 m 门限
L194-195 EKF 统计距离 d
L199-205 折角 α
L221 三条件判据
L237-281 生成新临时航迹(两点初始化)
L283-303 删除被关联的原航迹
L306-445 point_track_head_asso
L325 1 点 + buff_round≥2 资格
L361-372 门限判据(含停用的旧分支注释)
L388-427 生成 2 点临时航迹
L429-443 删除被关联的航迹头
L448-456 alpha_cal_track_init
L458-671 tmp_track_to_trust_track
L484 起批长度 + 屏蔽区条件
L504-550 同源去重
L552-667 起批与输出
L563-571 三点卡尔曼初始化
L573-604 Trust_Track 构造
L612-664 输出历史点
L673-697 tmp_track_die
L699-712 track_init_prohibit
L714-723 构造 / reset()

16.2 依赖

文件 函数 用途
kalman.cpp L18-58 kalman_filter_init_2dots 两点(4 维)初始化
kalman.cpp L125-194 kalman_filter_init_3dots 三点(6 维)初始化
kalman.cpp L615-693 d_cal_track_init_EKF 起始关联统计距离
kalman.cpp L780-789 Bind_speed 盲速计算
coor_trans.cpp L8-20 polar2cart / cart2polar 坐标转换
track_index_mangement.cpp L14-77 track_ind_get 航迹号分配
data_process.cpp L51 data_preprocess 一圈完成 → model=1
data_process.cpp L154-159 track_process 起始流程调用点
data_process.cpp L204-222 参数初始化/修改 校验 track_start_point_num ∈ [3,9]