Skip to content

refactor: outpost ekf - #63

Merged
creeper5820 merged 8 commits into
mainfrom
refactor/ekf
Jun 24, 2026
Merged

refactor: outpost ekf#63
creeper5820 merged 8 commits into
mainfrom
refactor/ekf

Conversation

@creeper5820

@creeper5820 creeper5820 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

概述

完全重构了前哨站的 EKF,结合先前的前哨站专属距离优化,大幅度提升了收敛速度和稳定程度,同时具有强大的自纠正能力

实机效果

实测距离在 7m 内能做到 90% 左右的命中率,这个距离下弹道的 yaw 散布为半块装甲板,pitch 散布为 1/3 块装甲板

8m.mp4

概述

本次提交对 outpost EKF 进行了整体重构,并顺带统一了预测/快照接口的时间推进方式,移除了旧的初始化路径与部分过时的 outpost 布局实现。

主要变更

  • 重构 OutpostModel
    • 滤波状态改为直接基于矩阵状态向量实现
    • 新增配置结构 Config
    • 新增 full() / current() 等接口
    • 增强装甲板切换检测与观测补偿
  • OutpostRobotState / RegularRobotState / RobotState
    • 预测接口从绝对时间改为 dt
    • 快照接口改为显式传入时间戳
    • 移除 initialize(...) 初始化入口
    • 统一由时间差驱动模型预测
  • OutpostSnapshot
    • 构造与内部运动外推逻辑改为基于 OutpostModel::State 和装甲集合
  • 预测与火控链路:
    • TargetMotion.center_position 改为 Point3d
    • 相关计算改用新的点类型与时间推进方式
  • 资源与配置:
    • 删除废弃的 armor_layout.hpp
    • 调整 config.yaml 中 outpost/火控参数
    • 更新 README.md 中对外 Topic 说明
  • 测试工具:
    • 更新 outpost EKF 仿真与可视化,支持多装甲板与切板检测场景

结果

整体上,这次修改将 outpost 目标跟踪从“单装甲、旧初始化路径”升级为“多装甲、自校正更强、时间推进更统一”的实现,并同步清理了相关接口与文档。

creeper5820 and others added 8 commits June 22, 2026 15:41
- remove explicit initialize from robot state models
- switch regular/outpost prediction APIs to dt-based updates
- lazy-initialize models from first observation update
- keep snapshot timestamps managed by RobotState
- replace tracker outpost state internals with OutpostModel adapter
- snapshot OutpostModel state and predicted armors
- use Point3d for TargetMotion center position
- adapt fire-control consumers to Point3d motion centers
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

代码概览

本次 PR 对前哨站目标预测的 EKF 实现进行全面重构,将内部状态改为 StateVector 驱动并引入切板检测;同步将整个预测器层的 predict/get_snapshot 接口从绝对时间戳改为增量时间差/外部时间戳;删除 initialize() 公开入口;并更新 TargetMotion::center_position 类型为 Point3d,适配下游火控模块。

变更内容

前哨站 EKF 与预测器时间 API 重构

层 / 文件 摘要
OutpostModel 新接口与 Config 契约
src/module/predictor/model/outpost.hpp, src/utility/math/outpost.hpp, src/utility/math/outpost.cpp
新增 OutpostModel::Config 结构体(过程噪声、观测噪声、plate_switch_yaw_min)及 configure()/full()/current() 方法声明;新增 outpost_relative_height 工具函数。
OutpostModel EKF 内部重构
src/module/predictor/model/outpost.cpp
Impl::Context 改为 StateVector 存储,重写 HeightBufferranges::fill/NaN 感知),新增 measure_innovation/calculate_kalman_gain/a_posteriori_updatecorrect() 中切板检测引入 rotation_sign/pending_sign/sign_evidence_count 序列判定与 delta_yaw/delta_height 补偿。
TargetMotion::center_position 类型变更
src/module/predictor/snapshot.hpp, src/module/predictor/regular/snapshot.cpp
center_positionEigen::Vector3d 改为 Point3d,移除对 Eigen/Core 的直接依赖,同步更新 RegularSnapshot::motion_of
OutpostSnapshot 改为基于 OutpostModel::State
src/module/predictor/outpost/snapshot.hpp, src/module/predictor/outpost/snapshot.cpp
构造函数从 EKF::XVec/OutpostArmorLayout/angular_velocity 改为接受 OutpostModel::State + vector<Armor3d> + TimePointpredicted_armors 改为对现有装甲施加 Z 轴旋转外推;删除 armor_layout.hpp/ekf_parameter.hpp 依赖。
预测器层 predict(dt)/get_snapshot(stamp) 接口变更
src/module/predictor/robot_state.hpp, src/module/predictor/robot_state.cpp, src/module/predictor/regular/robot_state.hpp, src/module/predictor/regular/robot_state.cpp, src/module/predictor/outpost/robot_state.hpp, src/module/predictor/outpost/robot_state.cpp
predict 从绝对 TimePoint 改为 double dtget_snapshot 从无参改为携带 TimePoint stamp;移除 initialize() 公开入口;RobotState 新增首次时间戳初始化分支并用 delta_time 计算 dt
Decider 初始化逻辑调整
src/module/tracker/decider.cpp
新建 RobotState 分支从"立即 initialize + 记录 last_seen_times"改为"仅调用 predict(t)",last_seen_times 写入后移至融合成功后。
火控模块适配
src/kernel/fire_control.cpp, src/module/fire_control/armor_selector.cpp, src/module/fire_control/target_solver.cpp, src/kernel/auto_aim.cpp
FireControl::solve 显式构造 target_center 并用其 norm() 计算飞行时间;target_solver 改用 make<Eigen::Vector3d>()armor_selector 改为引用绑定 center.y/center.xtarget_solution 改为拷贝赋值。
test_outpost_ekf 仿真工具更新
tool/cxx/test_outpost_ekf.cpp
引入 kOffsetTable 三装甲板候选真值,改为通过 model->full() 获取 estimated_armors,添加 plate_switched 检测与 [PLATE_SWITCH] 输出,统一噪声为 k* 常量。
配置参数与文档更新
config/config.yaml, README.md
yaw_offset/pitch_offset 归零,outpost_coming_angle 从 60 改为 40;README 输出接口表补充角速度/角加速度输出条目。

时序图

sequenceDiagram
  participant Decider
  participant RobotState
  participant OutpostRobotState
  participant OutpostModel
  participant OutpostSnapshot

  Decider->>RobotState: predict(TimePoint t)
  RobotState->>RobotState: delta_time(t, time_stamp) → dt
  RobotState->>OutpostRobotState: predict(dt)
  OutpostRobotState->>OutpostModel: predict(dt)

  Decider->>RobotState: update(armors)
  RobotState->>OutpostRobotState: update(armors)
  OutpostRobotState->>OutpostModel: correct(armor)
  OutpostModel->>OutpostModel: 切板检测 + delta_yaw/delta_height 补偿
  OutpostModel->>OutpostModel: measure_innovation → calculate_kalman_gain → a_posteriori_update

  Decider->>RobotState: get_snapshot(t)
  RobotState->>OutpostRobotState: get_snapshot(t)
  OutpostRobotState->>OutpostModel: full() → vector~Armor3d~
  OutpostRobotState->>OutpostModel: state() → State
  OutpostRobotState->>OutpostSnapshot: OutpostSnapshot(State, armors, stamp)
  OutpostSnapshot-->>Decider: Snapshot
Loading

预估代码审查工作量

🎯 5 (Critical) | ⏱️ ~120 分钟

相关 PR

  • Alliance-Algorithm/rmcs_auto_aim_v2#62:与本 PR 在 src/module/predictor/model/outpost.{hpp,cpp}tool/cxx/test_outpost_ekf.cpp 的 OutpostModel 预测/校正及可视化逻辑上直接重叠。
  • Alliance-Algorithm/rmcs_auto_aim_v2#61:同属 outpost robot state 合并后的火控/预测重构改动,在 ArmorSelector::Impl::select 等同函数/同模块层面存在直接代码层关联。
  • Alliance-Algorithm/rmcs_auto_aim_v2#40:本 PR 的预测器时间处理重构(RobotState 切换为 time_stamp/dt 与带时间戳的 get_snapshot)与该 PR 的 TimePoint 统一化改动直接相关。

建议标签

enhancement

🐇 EKF 换新装,StateVector 起舞扬,
切板轻探知,delta_yaw 妙飞翔。
predict 随 dt 漫步,snapshot 携 stamp 归航,
前哨守阵地,兔子跳代码春光!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题简洁且准确概括了本次对 outpost EKF 的大规模重构。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ekf

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/module/predictor/model/outpost.cpp`:
- Around line 444-453: `OutpostModel::full()` currently assumes
`pimpl->at(0..2)` is already mapped to “highest to lowest,” but `abs_ref_index`
is still provisional until all three plates are observed, so early snapshots can
violate the `full()` contract and mislead callers. Update `OutpostModel` to
track whether the absolute plate order is resolved (for example via a dedicated
readiness state tied to `abs_ref_index`/`rel_see_index`), and prevent `full()`
from exposing ordered output until that state is true; alternatively, initialize
`abs_ref_index` from a reliable prior so the ordering is correct from the first
frame. Keep `current()` consistent with the same resolution state.
- Around line 335-344: The index updates in outpost.cpp are using two different
directions: `height_buff.go_next()` follows `go_sign`, but `rel_see_index` is
still advanced with the raw `sign`, which can desynchronize the board state when
`rotation_sign` is already locked. Update the `rel_see_index` adjustment in the
same block to use the same confirmed direction variable as
`height_buff.go_next()` (the `go_sign` logic), so all cut-sheet indices advance
consistently and the downstream `delta_yaw`/`delta_height` compensation stays
aligned.

In `@src/module/predictor/outpost/robot_state.cpp`:
- Around line 21-30: `OutpostRobotState::update` is only using `armors.front()`,
which drops same-frame observations and makes EKF correction depend on detection
order. Update the method to process the full `std::span<Armor3d const>` from
`Decider`, applying all valid armors in the batch when initializing or
correcting the `OutpostModel`, rather than only the first element.

In `@src/module/predictor/robot_state.cpp`:
- Around line 34-38: In `RobotState::predict`, the `time_stamp` is updated
before validating whether `dt` is usable, which can cause the parent timestamp
to move backward on out-of-order input and lead to over-prediction. Reorder the
logic so the `dt` check happens first, and only assign `time_stamp = t` when
`dt` is valid and the prediction will actually proceed; keep the existing
`std::visit` model prediction path unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eee57bcd-0b1d-419f-9787-0f5ff65a8b1c

📥 Commits

Reviewing files that changed from the base of the PR and between aed9851 and 6d95e40.

📒 Files selected for processing (24)
  • README.md
  • config/config.yaml
  • src/kernel/auto_aim.cpp
  • src/kernel/fire_control.cpp
  • src/module/fire_control/armor_selector.cpp
  • src/module/fire_control/target_solver.cpp
  • src/module/predictor/model/outpost.cpp
  • src/module/predictor/model/outpost.hpp
  • src/module/predictor/outpost/armor_layout.hpp
  • src/module/predictor/outpost/ekf_parameter.hpp
  • src/module/predictor/outpost/robot_state.cpp
  • src/module/predictor/outpost/robot_state.hpp
  • src/module/predictor/outpost/snapshot.cpp
  • src/module/predictor/outpost/snapshot.hpp
  • src/module/predictor/regular/robot_state.cpp
  • src/module/predictor/regular/robot_state.hpp
  • src/module/predictor/regular/snapshot.cpp
  • src/module/predictor/robot_state.cpp
  • src/module/predictor/robot_state.hpp
  • src/module/predictor/snapshot.hpp
  • src/module/tracker/decider.cpp
  • src/utility/math/outpost.cpp
  • src/utility/math/outpost.hpp
  • tool/cxx/test_outpost_ekf.cpp
💤 Files with no reviewable changes (4)
  • src/module/predictor/outpost/ekf_parameter.hpp
  • src/module/predictor/outpost/armor_layout.hpp
  • README.md
  • src/module/predictor/robot_state.hpp

Comment thread src/module/predictor/model/outpost.cpp
Comment thread src/module/predictor/model/outpost.cpp
Comment on lines 21 to 30
auto update(std::span<Armor3d const> armors) -> bool {
if (armors.empty()) return false;

if (!initialized) {
initialize(armors.front(), time_stamp);
++update_count;
return true;
if (!model) {
model = std::make_unique<OutpostModel>(armors.front());
} else {
model->correct(armors.front());
}

auto match = select_best_match(armors);
if (!match.has_value()) return false;

if (!apply_match(*match)) return false;

++update_count;
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

不要丢弃同帧的其余前哨站观测。

Decider 会把同一 DeviceId 的装甲板作为 span 传入;这里只用 armors.front() 会让 EKF 修正依赖检测顺序,并忽略同帧的额外有效观测。

建议修改
         if (armors.empty()) return false;
 
+        auto next = armors.begin();
         if (!model) {
-            model = std::make_unique<OutpostModel>(armors.front());
-        } else {
-            model->correct(armors.front());
+            model = std::make_unique<OutpostModel>(*next++);
+        }
+
+        for (; next != armors.end(); ++next) {
+            model->correct(*next);
         }
 
         return true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto update(std::span<Armor3d const> armors) -> bool {
if (armors.empty()) return false;
if (!initialized) {
initialize(armors.front(), time_stamp);
++update_count;
return true;
if (!model) {
model = std::make_unique<OutpostModel>(armors.front());
} else {
model->correct(armors.front());
}
auto match = select_best_match(armors);
if (!match.has_value()) return false;
if (!apply_match(*match)) return false;
++update_count;
return true;
auto update(std::span<Armor3d const> armors) -> bool {
if (armors.empty()) return false;
auto next = armors.begin();
if (!model) {
model = std::make_unique<OutpostModel>(*next++);
}
for (; next != armors.end(); ++next) {
model->correct(*next);
}
return true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/module/predictor/outpost/robot_state.cpp` around lines 21 - 30,
`OutpostRobotState::update` is only using `armors.front()`, which drops
same-frame observations and makes EKF correction depend on detection order.
Update the method to process the full `std::span<Armor3d const>` from `Decider`,
applying all valid armors in the batch when initializing or correcting the
`OutpostModel`, rather than only the first element.

Comment on lines +34 to +38
const auto dt = util::delta_time(t, *time_stamp).count();
time_stamp = t;

if (!state) return;
std::visit([t](auto& model) { model.predict(t); }, *state);
std::visit([dt](auto& model) { model.predict(dt); }, *state);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

先校验 dt 再推进 time_stamp

Line 35 会在子模型忽略 dt <= 0 之前更新父级时间戳;如果输入时间戳乱序,下一帧会从回退后的时间重新计算 dt,导致过度预测。

建议修改
         const auto dt = util::delta_time(t, *time_stamp).count();
+        if (!(dt > 0.0)) return;
+
         time_stamp    = t;
 
         if (!state) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const auto dt = util::delta_time(t, *time_stamp).count();
time_stamp = t;
if (!state) return;
std::visit([t](auto& model) { model.predict(t); }, *state);
std::visit([dt](auto& model) { model.predict(dt); }, *state);
const auto dt = util::delta_time(t, *time_stamp).count();
if (!(dt > 0.0)) return;
time_stamp = t;
if (!state) return;
std::visit([dt](auto& model) { model.predict(dt); }, *state);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/module/predictor/robot_state.cpp` around lines 34 - 38, In
`RobotState::predict`, the `time_stamp` is updated before validating whether
`dt` is usable, which can cause the parent timestamp to move backward on
out-of-order input and lead to over-prediction. Reorder the logic so the `dt`
check happens first, and only assign `time_stamp = t` when `dt` is valid and the
prediction will actually proceed; keep the existing `std::visit` model
prediction path unchanged.

@creeper5820
creeper5820 merged commit 9760bf5 into main Jun 24, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in RMCS Auto Aim V2 Jun 24, 2026
@creeper5820
creeper5820 deleted the refactor/ekf branch June 24, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants