Skip to content

refactor: ekf and debug tool#62

Merged
creeper5820 merged 4 commits into
mainfrom
refactor/ekf
Jun 21, 2026
Merged

refactor: ekf and debug tool#62
creeper5820 merged 4 commits into
mainfrom
refactor/ekf

Conversation

@creeper5820

@creeper5820 creeper5820 commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator
  • 添加了击打目标的可视化
  • 起草了一个前哨站 EKF 实现,目前没有任何引用,仍在开发中

变更概览

本PR为RMC自动瞄准系统引入了前哨站扩展卡尔曼滤波(EKF)实现和相关的瞄准点目标可视化功能。

核心新增功能

前哨站EKF模型(OutpostModel)

  • 新增 OutpostModel 类,实现5维状态的卡尔曼滤波预测和校正逻辑
  • 状态包含世界坐标系下的位置(x/y/z)、旋转角和旋转速度
  • 包含过程噪声矩阵和观测噪声矩阵的完整协方差传播机制
  • 支持基于装甲板的观测更新和角度归一化处理

瞄准点可视化

  • 在火力控制(FireControl)的计算结果中新增 aim_point 字段,用于存储瞄准目标点的三维坐标
  • 在自动瞄准(AutoAim)逻辑中添加瞄准点的2D投影和可视化渲染,根据是否允许射击用红/绿颜色标示瞄准点

坐标系变换与投影

  • PoseEstimator 中新增 make_point2d 方法,实现从里程计坐标系到相机坐标系的转换和二维投影
  • 新增通用重投影函数 reproject_point,基于相机内参和畸变参数进行投影计算

前哨站几何求解重构

  • 新增 OutpostSolution 类,将前哨站中心、位置和姿态的计算从装甲灯条解析中分离出来
  • 支持不同装甲板层级(上/中/下)之间的转换计算
  • 提供更清晰的数据流用于后续EKF观测更新

测试与调试工具

  • 新增 test_outpost_ekf.cpp 测试程序,用于验证EKF实现
  • 程序模拟前哨站圆周运动,注入高斯噪声和离群值进行观测
  • 基于ROS2发布可视化marker(真值、观测、估计值等)
  • 定期输出中心误差和角度误差用于评估滤波效果

代码质量改进

  • 更新 AGENTS.md 明确头文件引入规范(最小引入原则)
  • 统一EKF参数文件中的代码格式(括号初始化风格)
  • 增强CMake配置,新增测试目标和依赖库链接

影响范围

  • 新增公开API:OutpostModelOutpostSolutionreproject_pointPoseEstimator::make_point2d
  • 修改现有API:FireControl::Result 结构体新增 aim_point 字段
  • 实现当前为草稿状态,仍在活跃开发中

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@creeper5820, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 21 minutes and 30 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c81a9ed3-bb69-433d-bd79-1f314d51793e

📥 Commits

Reviewing files that changed from the base of the PR and between cf6da34 and bc16b1f.

📒 Files selected for processing (2)
  • src/utility/math/corners_optimizor.cpp
  • src/utility/math/reprojection.cpp

Walkthrough

新增前哨站 EKF 预测模型 OutpostModel(5 维卡尔曼,含预测/校正/状态接口),重构 OutpostSolution 几何求解,新增相机重投影工具函数 reproject_point,在 PoseEstimator 中添加 make_point2d,并将 aim_point 字段通过 FireControl::Result 传递至 AutoAim 可视化层;附带 EKF 测试程序。

Changes

前哨站 EKF 预测器与瞄准点可视化

层 / 文件 摘要
数学基础:重投影工具与 OutpostSolution 几何
src/utility/math/reprojection.hpp, src/utility/math/reprojection.cpp, src/utility/math/outpost.hpp, src/utility/math/outpost.cpp
新增 util::reproject_point(声明与 cv::projectPoints 实现);新增 OutpostSolution 类(ArmorLevel 枚举、Input/Result 结构体、solve() 方法);NeighborBarSolution::solve() 重构为调用 OutpostSolution,引入 details::get_transformget_level 辅助函数。
OutpostModel EKF 预测器
src/module/predictor/model/outpost.hpp, src/module/predictor/model/outpost.cpp, src/module/predictor/model/robot.hpp, src/module/predictor/outpost/ekf_parameter.hpp
新增 OutpostModel(Pimpl 封装),含 State 结构体;Impl 实现 5 维卡尔曼,含初始化、预测(状态+协方差传播)、校正(雅可比、卡尔曼增益、后验更新与协方差对称化)及对外方法;新增空的 RobotModel 占位类;ekf_parameter.hpp 做花括号格式微调。
PoseEstimator::make_point2d
src/kernel/pose_estimator.hpp, src/kernel/pose_estimator.cpp
PoseEstimator 新增 make_point2d 声明;Impl 内部实现 odom→相机坐标变换并调用 reproject_point;公有接口转发 pimpl;含 into_camera_link 内部变量排列微调。
aim_point 字段传递与可视化
src/kernel/fire_control.hpp, src/kernel/fire_control.cpp, src/kernel/auto_aim.cpp
FireControl::Result 新增 aim_point 字段;solve() 中赋值;auto_aim.cpp 改用结构化 feedforward 绑定,并在 make_point2d 成功时绘制颜色点标记。
EKF 测试程序与 CMake 构建
tool/cxx/CMakeLists.txt, tool/cxx/test_outpost_ekf.cpp
新增 test_outpost_ekf.cpp:ROS2 程序,模拟真值/噪声/离群观测,驱动 OutpostModel,发布 MarkerArray 并输出误差统计;CMakeLists.txt 注册构建目标,补充 hikcamerarunning.cpprclcpp 依赖。
AGENTS.md 头文件规范
AGENTS.md
SOP 新增"最小引入原则"条目:已被间接引入的类型无需再次显式包含对应头文件。

Sequence Diagram(s)

sequenceDiagram
    participant AutoAim
    participant FireControl
    participant PoseEstimator
    participant util_reproject_point as util::reproject_point

    AutoAim->>FireControl: solve(armors, ...)
    FireControl-->>AutoAim: Result{..., aim_point}

    AutoAim->>PoseEstimator: make_point2d(result->aim_point)
    PoseEstimator->>PoseEstimator: 变换 odom→相机坐标系
    PoseEstimator->>util_reproject_point: reproject_point(point_camera, camera)
    util_reproject_point->>util_reproject_point: cv::projectPoints()
    util_reproject_point-->>PoseEstimator: optional<Point2d>
    PoseEstimator-->>AutoAim: optional<Point2d>

    AutoAim->>AutoAim: 绘制彩色圆点标记(颜色取决于 shoot_permitted)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Alliance-Algorithm/rmcs_auto_aim_v2#43:大幅重构 PoseEstimator 的姿态/变换流水线(update_camera_transforminto_odom_link 等),与本 PR 新增的 make_point2d 所依赖的 PoseEstimator 内部结构直接相关。

Suggested labels

enhancement

Poem

🐇 小兔跳呀跳,前哨站旋转不停歇,
卡尔曼滤波来预测,五维状态稳如山。
重投影画出瞄准点,红绿光标闪屏间,
OutpostModel 新出炉,兔子为你鼓掌欢!✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.22% 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 PR title correctly summarizes the main changes: refactoring EKF implementation and adding debug tooling for visualization and testing.
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 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 and usage tips.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/kernel/fire_control.cpp (1)

118-165: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

用弹道解算后的目标点填充 aim_point

Line 165 输出的是 Line 104 在粗选阶段缓存的 aim_point_position;但返回的 yaw/pitch 来自 target_solution。若两次预测时间不同,可视化点会和实际解算姿态不一致。

建议修复
             last_selected_armor_id = solution->candidate.armor.id;
             target_solution        = std::move(*solution);
+            target_solution.candidate.armor.translation.copy_to(aim_point_position);
         }

这样射击评估和 Result::aim_point 都使用同一个解算结果目标点。

🤖 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/kernel/fire_control.cpp` around lines 118 - 165, The issue is that the
`aim_point_position` being returned in the Result on line 165 comes from the
rough selection phase cached value (from line 104), but the actual `yaw` and
`pitch` returned are derived from `target_solution` after trajectory planning
and offset correction. This causes inconsistency when visualization points don't
match the actual solved attitude. Fix this by calculating or deriving the
`aim_point` from the same `target_solution` that produces the final `yaw` and
`pitch` values, rather than using the earlier cached `aim_point_position` value,
ensuring both the shoot evaluation and Result use the same ballistic solution
target point.
🤖 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/kernel/pose_estimator.cpp`:
- Around line 279-287: The code is projecting points without validating their
visibility and validity, allowing points with z <= 0 (behind camera) or
non-finite coordinates to be reprojected, resulting in misleading debug markers.
Before calling util::reproject_point with point_cv, add a validation check to
ensure the z coordinate is positive and all coordinates are finite; if either
condition fails, return std::nullopt instead of attempting reprojection.

In `@src/module/predictor/model/outpost.cpp`:
- Around line 42-50: Remove the unsafe `reinterpret_cast` usage in the const and
non-const `state()` methods which directly reinterpret `posteriors_state` as
`State`. Replace these methods with explicit conversion functions that safely
map between `StateVector` (Eigen::Matrix<double, 5, 1>) and `State`. Create two
helper functions to handle the bidirectional conversion instead of using type
punning through reinterpret_cast, and apply the same fix to any other locations
(such as Line 122) where similar reinterpret_cast conversions occur between
these types.

In `@src/utility/math/reprojection.cpp`:
- Around line 25-41: The reproject_point function does not validate that the
input point has positive depth before attempting projection, which allows points
behind or at the camera (point_camera.z <= 0) to be processed and generate
misleading 2D projections. Add an early validation check at the beginning of the
reproject_point function that returns std::nullopt if point_camera.z is less
than or equal to zero. Additionally, ensure the file includes the <cmath> header
if it is not already present.

---

Outside diff comments:
In `@src/kernel/fire_control.cpp`:
- Around line 118-165: The issue is that the `aim_point_position` being returned
in the Result on line 165 comes from the rough selection phase cached value
(from line 104), but the actual `yaw` and `pitch` returned are derived from
`target_solution` after trajectory planning and offset correction. This causes
inconsistency when visualization points don't match the actual solved attitude.
Fix this by calculating or deriving the `aim_point` from the same
`target_solution` that produces the final `yaw` and `pitch` values, rather than
using the earlier cached `aim_point_position` value, ensuring both the shoot
evaluation and Result use the same ballistic solution target point.
🪄 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: 0624faef-aef9-474c-90f7-7415fe83f565

📥 Commits

Reviewing files that changed from the base of the PR and between 3f16234 and cf6da34.

📒 Files selected for processing (16)
  • AGENTS.md
  • src/kernel/auto_aim.cpp
  • src/kernel/fire_control.cpp
  • src/kernel/fire_control.hpp
  • src/kernel/pose_estimator.cpp
  • src/kernel/pose_estimator.hpp
  • src/module/predictor/model/outpost.cpp
  • src/module/predictor/model/outpost.hpp
  • src/module/predictor/model/robot.hpp
  • src/module/predictor/outpost/ekf_parameter.hpp
  • src/utility/math/outpost.cpp
  • src/utility/math/outpost.hpp
  • src/utility/math/reprojection.cpp
  • src/utility/math/reprojection.hpp
  • tool/cxx/CMakeLists.txt
  • tool/cxx/test_outpost_ekf.cpp

Comment on lines +279 to +287
// odom (ROS) -> camera (OpenCV)
const auto point_ros_odom = point_odom.make<Eigen::Vector3d>();
const auto point_ros_camera = Eigen::Vector3d { q.inverse() * (point_ros_odom - t) };

const auto point_opencv = ros2opencv_position(point_ros_camera);
const auto point_cv = Point3d { point_opencv };

// reproject
return util::reproject_point(point_cv, camera_feature);

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

在重投影前过滤相机后方/非法点

Line 281 之后直接投影会把 z <= 0 或非有限坐标也转成 2D 点,调试层会出现误导性的 aim_point 标记。建议在调用 util::reproject_point 前先做可见性与有限值检查,不满足时返回 std::nullopt

建议修复
     // odom (ROS) -> camera (OpenCV)
     const auto point_ros_odom   = point_odom.make<Eigen::Vector3d>();
     const auto point_ros_camera = Eigen::Vector3d { q.inverse() * (point_ros_odom - t) };
+    if (!point_ros_camera.allFinite() || point_ros_camera.z() <= 0.0) {
+        return std::nullopt;
+    }
 
     const auto point_opencv = ros2opencv_position(point_ros_camera);
     const auto point_cv     = Point3d { point_opencv };
🤖 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/kernel/pose_estimator.cpp` around lines 279 - 287, The code is projecting
points without validating their visibility and validity, allowing points with z
<= 0 (behind camera) or non-finite coordinates to be reprojected, resulting in
misleading debug markers. Before calling util::reproject_point with point_cv,
add a validation check to ensure the z coordinate is positive and all
coordinates are finite; if either condition fails, return std::nullopt instead
of attempting reprojection.

Comment on lines +42 to +50
static_assert(sizeof(State) == sizeof(posteriors_state));
auto state() noexcept -> State& {
auto* data_pointer = &posteriors_state;
return *reinterpret_cast<State*>(data_pointer);
}
auto state() const noexcept -> const State& {
const auto* data_pointer = &posteriors_state;
return *reinterpret_cast<const State*>(data_pointer);
}

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'reinterpret_cast<\s*(const\s+)?State(\s*[*&])|reinterpret_cast<\s*(const\s+)?StateVector(\s*[*&])' src/module/predictor/model/outpost.cpp
rg -nP 'static_assert\s*\(\s*sizeof\(State\)\s*==\s*sizeof\(posteriors_state\)\s*\)' src/module/predictor/model/outpost.cpp

Repository: Alliance-Algorithm/rmcs_auto_aim_v2

Length of output: 379


🏁 Script executed:

cat -n src/module/predictor/model/outpost.cpp | head -160

Repository: Alliance-Algorithm/rmcs_auto_aim_v2

Length of output: 7918


🏁 Script executed:

cat -n src/module/predictor/model/outpost.hpp

Repository: Alliance-Algorithm/rmcs_auto_aim_v2

Length of output: 867


移除 Lines 42-50 与 Line 122 的 reinterpret_cast 状态互转

直接重解释 StateEigen::Matrix<double, 5, 1> 违反了严格别名规则(strict aliasing),即使通过 static_assert(sizeof(...)) 检查,也无法保证对象布局和内存访问安全,属于未定义行为。建议改为显式转换函数处理 StateVectorState 之间的映射。

建议修复示例
-        static_assert(sizeof(State) == sizeof(posteriors_state));
-        auto state() noexcept -> State& {
-            auto* data_pointer = &posteriors_state;
-            return *reinterpret_cast<State*>(data_pointer);
-        }
-        auto state() const noexcept -> const State& {
-            const auto* data_pointer = &posteriors_state;
-            return *reinterpret_cast<const State*>(data_pointer);
-        }
+        static auto to_state(const StateVector& v) noexcept -> State {
+            return State {
+                .x = v[0],
+                .y = v[1],
+                .z = v[2],
+                .rotation_speed = v[3],
+                .rotation_angle = v[4],
+            };
+        }
+        static auto from_state(const State& s) noexcept -> StateVector {
+            auto v = StateVector { };
+            v << s.x, s.y, s.z, s.rotation_speed, s.rotation_angle;
+            return v;
+        }
@@
-        auto posterior_state = State { prior_state };
-        reinterpret_cast<StateVector&>(posterior_state).noalias() += kalman_gain * innovation;
+        auto posterior_vector = Context::from_state(prior_state);
+        posterior_vector.noalias() += kalman_gain * innovation;
+        auto posterior_state = Context::to_state(posterior_vector);
🤖 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/model/outpost.cpp` around lines 42 - 50, Remove the
unsafe `reinterpret_cast` usage in the const and non-const `state()` methods
which directly reinterpret `posteriors_state` as `State`. Replace these methods
with explicit conversion functions that safely map between `StateVector`
(Eigen::Matrix<double, 5, 1>) and `State`. Create two helper functions to handle
the bidirectional conversion instead of using type punning through
reinterpret_cast, and apply the same fix to any other locations (such as Line
122) where similar reinterpret_cast conversions occur between these types.

Comment on lines +25 to +41
auto reproject_point(const Point3d& point_camera, const util::CameraFeature& camera)
-> std::optional<Point2d> {

auto object_points = std::vector<cv::Point3f> {
cv::Point3f {
static_cast<float>(point_camera.x),
static_cast<float>(point_camera.y),
static_cast<float>(point_camera.z),
},
};
auto projected = std::vector<cv::Point2f> { };

cv::projectPoints(object_points, cv::Vec3d { 0.0, 0.0, 0.0 }, cv::Vec3d { 0.0, 0.0, 0.0 },
camera.intrinsic(), camera.distortion(), projected);

if (projected.empty()) return std::nullopt;
return Point2d { projected[0] };

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

投影前先拒绝非正深度点。

point_camera.z <= 0 表示目标不在相机前方;当前只检查 projected.empty(),会把相机后方/近平面的点继续交给可视化链路,生成误导性的 2D 点。

建议修复
 auto reproject_point(const Point3d& point_camera, const util::CameraFeature& camera)
     -> std::optional<Point2d> {
 
+    if (!std::isfinite(point_camera.x) || !std::isfinite(point_camera.y)
+        || !std::isfinite(point_camera.z) || point_camera.z <= 0.0) {
+        return std::nullopt;
+    }
+
     auto object_points = std::vector<cv::Point3f> {
         cv::Point3f {
             static_cast<float>(point_camera.x),
             static_cast<float>(point_camera.y),
             static_cast<float>(point_camera.z),
@@
 
     if (projected.empty()) return std::nullopt;
-    return Point2d { projected[0] };
+    if (!std::isfinite(projected.front().x) || !std::isfinite(projected.front().y)) {
+        return std::nullopt;
+    }
+    return Point2d { projected.front() };
 }

如果本文件尚未包含 <cmath>,需要同步补上。

🤖 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/utility/math/reprojection.cpp` around lines 25 - 41, The reproject_point
function does not validate that the input point has positive depth before
attempting projection, which allows points behind or at the camera
(point_camera.z <= 0) to be processed and generate misleading 2D projections.
Add an early validation check at the beginning of the reproject_point function
that returns std::nullopt if point_camera.z is less than or equal to zero.
Additionally, ensure the file includes the <cmath> header if it is not already
present.

@creeper5820 creeper5820 merged commit aed9851 into main Jun 21, 2026
2 of 3 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in RMCS Auto Aim V2 Jun 21, 2026
@creeper5820 creeper5820 deleted the refactor/ekf branch June 21, 2026 14:56
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.

1 participant