Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ build-rmcs --packages-up-to rmcs_auto_aim_v2
cmake -B build
cmake --build build -j
```


2. 规范

- 项目头文件遵循最小引入原则,如果某个类型通过头文件间接引入了,那就不需要再引入该头文件
18 changes: 13 additions & 5 deletions src/kernel/auto_aim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,26 @@ struct AutoAim::Impl {
cmd.pitch = result->pitch;
cmd.robot_center = result->center_position;

if (result->feedforward.has_value()) {
cmd.yaw_rate = result->feedforward->yaw_rate;
cmd.pitch_rate = result->feedforward->pitch_rate;
cmd.yaw_acc = result->feedforward->yaw_acc;
cmd.pitch_acc = result->feedforward->pitch_acc;
if (auto feedforward = result->feedforward) {
cmd.yaw_rate = feedforward->yaw_rate;
cmd.pitch_rate = feedforward->pitch_rate;
cmd.yaw_acc = feedforward->yaw_acc;
cmd.pitch_acc = feedforward->pitch_acc;
}

auto impact_armors = snapshot->predicted_armors(result->impact_time);
visual.publish(impact_armors, "impact_robot");
visual.update_aiming_direction(cmd.yaw, cmd.pitch);
visual.update_mpc_plan(cmd.yaw, cmd.pitch, cmd.yaw_rate, cmd.pitch_rate,
cmd.yaw_acc, cmd.pitch_acc);

if (auto aim_2d = estimator.make_point2d(result->aim_point)) {
visual.draw_later(Canvas::Point {
.origin = aim_2d->make<cv::Point2i>(),
.radius = 5,
.color = result->shoot_permitted ? kRed : kGreen,
});
}
}
}
visual.draw_later(
Expand Down
1 change: 1 addition & 0 deletions src/kernel/fire_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ struct FireControl::Impl {
.feedforward = feedforward,
.shoot_permitted = shoot_permitted,
.center_position = Point3d { center_position },
.aim_point = Point3d { aim_point_position },
.impact_time = target_solution.impact_time,
};
}
Expand Down
1 change: 1 addition & 0 deletions src/kernel/fire_control.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class FireControl {
std::optional<Feedforward> feedforward;
bool shoot_permitted;
Point3d center_position;
Point3d aim_point;
TimePoint impact_time;
};

Expand Down
23 changes: 21 additions & 2 deletions src/kernel/pose_estimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ struct PoseEstimator::Impl {

auto transformed = armor;

auto position = Eigen::Vector3d {};
auto position = Eigen::Vector3d { };
transformed.translation.copy_to(position);
transformed.translation = camera_orientation.inverse() * (position - camera_translation);

auto quat = Eigen::Quaterniond {};
auto quat = Eigen::Quaterniond { };
transformed.orientation.copy_to(quat);
transformed.orientation = camera_orientation.inverse() * quat;

Expand Down Expand Up @@ -271,6 +271,21 @@ struct PoseEstimator::Impl {
}
return armor3ds;
}

auto make_point2d(const Point3d& point_odom) const -> std::optional<Point2d> {
const auto t = camera_feature.translation.make<Eigen::Vector3d>();
const auto q = camera_feature.orientation.make<Eigen::Quaterniond>();

// 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);
Comment on lines +279 to +287

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.

}
};

auto PoseEstimator::initialize(const YAML::Node& yaml) noexcept
Expand Down Expand Up @@ -298,6 +313,10 @@ auto PoseEstimator::update_camera_transform(const Transform& transform) -> void
return pimpl->update_camera_transform(transform);
}

auto PoseEstimator::make_point2d(const Point3d& point_odom) const -> std::optional<Point2d> {
return pimpl->make_point2d(point_odom);
}

PoseEstimator::PoseEstimator() noexcept
: pimpl { std::make_unique<Impl>() } { }

Expand Down
2 changes: 2 additions & 0 deletions src/kernel/pose_estimator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class PoseEstimator {
auto estimate_armor(const std::vector<Armor2d>&) const -> Armor3ds;
auto estimate_armor(const std::vector<Armor2d>&, Image&) const -> Armor3ds;

auto make_point2d(const Point3d& point_odom) const -> std::optional<Point2d>;

auto addition() -> const Addition&;
};

Expand Down
191 changes: 191 additions & 0 deletions src/module/predictor/model/outpost.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include "outpost.hpp"

#include "utility/math/angle.hpp"
#include "utility/robot/constant.hpp"

#include <eigen3/Eigen/Geometry>

using namespace rmcs;

struct OutpostModel::Impl {
using StateVector = Eigen::Matrix<double, 5, 1>;
using Covariance = Eigen::Matrix<double, 5, 5>;
using ProcessNoise = Eigen::Matrix<double, 5, 5>;
using ObservationNoise = Eigen::Matrix<double, 4, 4>;
using ObservationVector = Eigen::Matrix<double, 4, 1>;
using ObservationJacobian = Eigen::Matrix<double, 4, 5>;
using KalmanGain = Eigen::Matrix<double, 5, 4>;

static auto observation_jacobian() {
auto jacobian = ObservationJacobian { };
// clang-format off
jacobian <<
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 0, 1;
// clang-format on
return jacobian;
}

struct Context {
StateVector posteriors_state = StateVector::Zero();
Covariance posteriors_covariance = Covariance::Identity();
ProcessNoise noise_process = ProcessNoise::Zero();
ObservationNoise noise_observation = ObservationNoise::Zero();

Context() noexcept {
noise_process.diagonal() << 1e-4, 1e-4, 1e-4, 1e-2, 1e-2;
noise_observation.diagonal() << 0.09, 0.09, 0.09, 9e-2;
}

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);
}
Comment on lines +42 to +50

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.


auto update_center(const Eigen::Vector3d& point) noexcept {
state().x = point.x();
state().y = point.y();
state().z = point.z();
}
} context;

auto initialize_from(const Armor3d& armor) noexcept -> void {
constexpr auto pitch = kPredictedOutpostArmorPitch;
constexpr auto radius = kOutpostRadius;

const auto translation = armor.translation.make<Eigen::Vector3d>();
const auto orientation = armor.orientation.make<Eigen::Quaterniond>();

const auto backward = Eigen::Vector3d { orientation * Eigen::Vector3d::UnitX() };
const auto armor_to_center = Eigen::Vector3d {
Eigen::AngleAxisd { -pitch, orientation * Eigen::Vector3d::UnitY() } * backward
};
const auto center = Eigen::Vector3d { translation + radius * armor_to_center };

context.update_center(center);
context.state().rotation_speed = 0.0;
context.state().rotation_angle =
util::normalize_angle(std::atan2(-armor_to_center.y(), -armor_to_center.x()));

auto initial_covariance_diagonal = Eigen::Matrix<double, 5, 1> { };
initial_covariance_diagonal << 1.0, 1.0, 1.0, 64.0, 0.4;

context.posteriors_covariance = initial_covariance_diagonal.asDiagonal();
}

static auto predict_state(double dt, const State& last) -> State {
auto next = State { last };

next.rotation_angle = util::normalize_angle(last.rotation_angle + last.rotation_speed * dt);
return next;
}

auto predict_covariance(double dt, const Covariance& covariance) const -> Covariance {
auto jacobian = Covariance { Covariance::Identity() };
jacobian(4, 3) = dt;
return jacobian * covariance * jacobian.transpose() + context.noise_process;
}

static auto measure_innovation(const ObservationVector& observation, const State& prior_state)
-> ObservationVector {
auto predicted_observation = ObservationVector { };
predicted_observation << prior_state.x, prior_state.y, prior_state.z,
prior_state.rotation_angle;

auto innovation = ObservationVector { observation - predicted_observation };
innovation.coeffRef(3) = util::normalize_angle(innovation.coeff(3));
return innovation;
}

auto calculate_kalman_gain(const Covariance& prior_covariance) const -> KalmanGain {
const auto jacobian = observation_jacobian();

const auto innovation_covariance =
jacobian * prior_covariance * jacobian.transpose() + context.noise_observation;

return prior_covariance * jacobian.transpose()
* innovation_covariance.ldlt().solve(ObservationNoise::Identity());
}

auto a_posteriori_update(const State& prior_state, const Covariance& prior_covariance,
const KalmanGain& kalman_gain, const ObservationVector& innovation,
const ObservationJacobian& jacobian) const -> std::pair<State, Covariance> {

auto posterior_state = State { prior_state };
reinterpret_cast<StateVector&>(posterior_state).noalias() += kalman_gain * innovation;
posterior_state.rotation_angle = util::normalize_angle(posterior_state.rotation_angle);

const auto complement = Covariance::Identity() - kalman_gain * jacobian;
auto posterior_covariance = Covariance { };
posterior_covariance.noalias() = complement * prior_covariance * complement.transpose();
posterior_covariance +=
(kalman_gain * context.noise_observation * kalman_gain.transpose()).eval();
posterior_covariance = 0.5 * (posterior_covariance + posterior_covariance.transpose());

return { posterior_state, posterior_covariance };
}

auto predict(double dt) noexcept -> void {
const auto prior_state = predict_state(dt, context.state());
const auto prior_covariance = predict_covariance(dt, context.posteriors_covariance);

context.state() = prior_state;
context.posteriors_covariance = prior_covariance;
}

auto correct(const Armor3d& armor) noexcept -> void {
{ // 切板检测
}

constexpr auto pitch = kPredictedOutpostArmorPitch;

const auto orientation = armor.orientation.make<Eigen::Quaterniond>();
const auto translation = armor.translation.make<Eigen::Vector3d>();

const auto backward = Eigen::Vector3d { orientation * Eigen::Vector3d::UnitX() };
const auto armor_to_center = Eigen::Vector3d {
Eigen::AngleAxisd { -pitch, orientation * Eigen::Vector3d::UnitY() } * backward
};

const auto center = Eigen::Vector3d { translation + kOutpostRadius * armor_to_center };

const auto top_armor_yaw =
util::normalize_angle(std::atan2(-armor_to_center.y(), -armor_to_center.x()));

auto observation = ObservationVector { };
observation << center.x(), center.y(), center.z(), top_armor_yaw;

const auto prior_state = context.state();
const auto prior_covariance = context.posteriors_covariance;

const auto innovation = measure_innovation(observation, prior_state);
const auto kalman_gain = calculate_kalman_gain(prior_covariance);
const auto jacobian = observation_jacobian();

const auto [posterior_state, posterior_covariance] =
a_posteriori_update(prior_state, prior_covariance, kalman_gain, innovation, jacobian);

context.state() = posterior_state;
context.posteriors_covariance = posterior_covariance;
}
};

OutpostModel::OutpostModel(const Armor3d& armor) noexcept
: pimpl { std::make_unique<Impl>() } {
pimpl->initialize_from(armor);
}

OutpostModel::~OutpostModel() noexcept = default;

auto OutpostModel::predict(double dt) noexcept -> void { pimpl->predict(dt); }

auto OutpostModel::correct(const Armor3d& armor) noexcept -> void { pimpl->correct(armor); }

auto OutpostModel::state() noexcept -> State { return pimpl->context.state(); }
30 changes: 30 additions & 0 deletions src/module/predictor/model/outpost.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "utility/pimpl.hpp"
#include "utility/robot/armor.hpp"

namespace rmcs {

class OutpostModel {
RMCS_PIMPL_DEFINITION(OutpostModel)

public:
struct State {
// 前哨站旋转中心在世界坐标系下的位置
double x;
double y;
double z;

// 以参考装甲板朝向为基准的角速度与 yaw 角
double rotation_speed;
double rotation_angle;
};

explicit OutpostModel(const Armor3d& armor) noexcept;

auto predict(double dt) noexcept -> void;
auto correct(const Armor3d& armor) noexcept -> void;
auto state() noexcept -> State;
};

}
7 changes: 7 additions & 0 deletions src/module/predictor/model/robot.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

namespace rmcs {

class RobotModel { };

}
Loading
Loading