rm_vision/tasks/auto_aim/target.cpp
2025-12-15 02:33:20 +08:00

322 lines
10 KiB
C++

#include "target.hpp"
#include <numeric>
#include "tools/logger.hpp"
#include "tools/math_tools.hpp"
namespace auto_aim
{
Target::Target(
const Armor & armor, std::chrono::steady_clock::time_point t, double radius, int armor_num,
Eigen::VectorXd P0_dig)
: name(armor.name),
armor_type(armor.type),
jumped(false),
last_id(0),
update_count_(0),
armor_num_(armor_num),
t_(t),
is_switch_(false),
is_converged_(false),
switch_count_(0)
{
auto r = radius;
priority = armor.priority;
const Eigen::VectorXd & xyz = armor.xyz_in_world;
const Eigen::VectorXd & ypr = armor.ypr_in_world;
// 旋转中心的坐标
auto center_x = xyz[0] + r * std::cos(ypr[0]);
auto center_y = xyz[1] + r * std::sin(ypr[0]);
auto center_z = xyz[2];
// x vx y vy z vz a w r l h
// a: angle
// w: angular velocity
// l: r2 - r1
// h: z2 - z1
Eigen::VectorXd x0{{center_x, 0, center_y, 0, center_z, 0, ypr[0], 0, r, 0, 0}}; //初始化预测量
Eigen::MatrixXd P0 = P0_dig.asDiagonal();
// 防止夹角求和出现异常值
auto x_add = [](const Eigen::VectorXd & a, const Eigen::VectorXd & b) -> Eigen::VectorXd {
Eigen::VectorXd c = a + b;
c[6] = tools::limit_rad(c[6]);
return c;
};
ekf_ = tools::ExtendedKalmanFilter(x0, P0, x_add); //初始化滤波器(预测量、预测量协方差)
}
Target::Target(double x, double vyaw, double radius, double h) : armor_num_(4)
{
Eigen::VectorXd x0{{x, 0, 0, 0, 0, 0, 0, vyaw, radius, 0, h}};
Eigen::VectorXd P0_dig{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
Eigen::MatrixXd P0 = P0_dig.asDiagonal();
// 防止夹角求和出现异常值
auto x_add = [](const Eigen::VectorXd & a, const Eigen::VectorXd & b) -> Eigen::VectorXd {
Eigen::VectorXd c = a + b;
c[6] = tools::limit_rad(c[6]);
return c;
};
ekf_ = tools::ExtendedKalmanFilter(x0, P0, x_add); //初始化滤波器(预测量、预测量协方差)
}
void Target::predict(std::chrono::steady_clock::time_point t)
{
auto dt = tools::delta_time(t, t_);
predict(dt);
t_ = t;
}
void Target::predict(double dt)
{
// 状态转移矩阵
// clang-format off
Eigen::MatrixXd F{
{1, dt, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, dt, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, dt, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 1, dt, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
};
// clang-format on
// Piecewise White Noise Model
// https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/07-Kalman-Filter-Math.ipynb
double v1, v2;
if (name == ArmorName::outpost) {
v1 = 10; // 前哨站加速度方差
v2 = 0.1; // 前哨站角加速度方差
} else {
v1 = 100; // 加速度方差
v2 = 400; // 角加速度方差
}
auto a = dt * dt * dt * dt / 4;
auto b = dt * dt * dt / 2;
auto c = dt * dt;
// 预测过程噪声偏差的方差
// clang-format off
Eigen::MatrixXd Q{
{a * v1, b * v1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{b * v1, c * v1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, a * v1, b * v1, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, b * v1, c * v1, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, a * v1, b * v1, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, b * v1, c * v1, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, a * v2, b * v2, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, b * v2, c * v2, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
// clang-format on
// 防止夹角求和出现异常值
auto f = [&](const Eigen::VectorXd & x) -> Eigen::VectorXd {
Eigen::VectorXd x_prior = F * x;
x_prior[6] = tools::limit_rad(x_prior[6]);
return x_prior;
};
// 前哨站转速特判
if (this->convergened() && this->name == ArmorName::outpost && std::abs(this->ekf_.x[7]) > 2)
this->ekf_.x[7] = this->ekf_.x[7] > 0 ? 2.51 : -2.51;
ekf_.predict(F, Q, f);
}
void Target::update(const Armor & armor)
{
// 装甲板匹配
int id;
auto min_angle_error = 1e10;
const std::vector<Eigen::Vector4d> & xyza_list = armor_xyza_list();
std::vector<std::pair<Eigen::Vector4d, int>> xyza_i_list;
for (int i = 0; i < armor_num_; i++) {
xyza_i_list.push_back({xyza_list[i], i});
}
std::sort(
xyza_i_list.begin(), xyza_i_list.end(),
[](const std::pair<Eigen::Vector4d, int> & a, const std::pair<Eigen::Vector4d, int> & b) {
Eigen::Vector3d ypd1 = tools::xyz2ypd(a.first.head(3));
Eigen::Vector3d ypd2 = tools::xyz2ypd(b.first.head(3));
return ypd1[2] < ypd2[2];
});
// 取前3个distance最小的装甲板
for (int i = 0; i < 3; i++) {
const auto & xyza = xyza_i_list[i].first;
Eigen::Vector3d ypd = tools::xyz2ypd(xyza.head(3));
auto angle_error = std::abs(tools::limit_rad(armor.ypr_in_world[0] - xyza[3])) +
std::abs(tools::limit_rad(armor.ypd_in_world[0] - ypd[0]));
if (std::abs(angle_error) < std::abs(min_angle_error)) {
id = xyza_i_list[i].second;
min_angle_error = angle_error;
}
}
if (id != 0) jumped = true;
if (id != last_id) {
is_switch_ = true;
} else {
is_switch_ = false;
}
if (is_switch_) switch_count_++;
last_id = id;
update_count_++;
update_ypda(armor, id);
}
void Target::update_ypda(const Armor & armor, int id)
{
//观测jacobi
Eigen::MatrixXd H = h_jacobian(ekf_.x, id);
// Eigen::VectorXd R_dig{{4e-3, 4e-3, 1, 9e-2}};
auto center_yaw = std::atan2(armor.xyz_in_world[1], armor.xyz_in_world[0]);
auto delta_angle = tools::limit_rad(armor.ypr_in_world[0] - center_yaw);
Eigen::VectorXd R_dig{
{4e-3, 4e-3, log(std::abs(delta_angle) + 1) + 1,
log(std::abs(armor.ypd_in_world[2]) + 1) / 200 + 9e-2}};
//测量过程噪声偏差的方差
Eigen::MatrixXd R = R_dig.asDiagonal();
// 定义非线性转换函数h: x -> z
auto h = [&](const Eigen::VectorXd & x) -> Eigen::Vector4d {
Eigen::VectorXd xyz = h_armor_xyz(x, id);
Eigen::VectorXd ypd = tools::xyz2ypd(xyz);
auto angle = tools::limit_rad(x[6] + id * 2 * CV_PI / armor_num_);
return {ypd[0], ypd[1], ypd[2], angle};
};
// 防止夹角求差出现异常值
auto z_subtract = [](const Eigen::VectorXd & a, const Eigen::VectorXd & b) -> Eigen::VectorXd {
Eigen::VectorXd c = a - b;
c[0] = tools::limit_rad(c[0]);
c[1] = tools::limit_rad(c[1]);
c[3] = tools::limit_rad(c[3]);
return c;
};
const Eigen::VectorXd & ypd = armor.ypd_in_world;
const Eigen::VectorXd & ypr = armor.ypr_in_world;
Eigen::VectorXd z{{ypd[0], ypd[1], ypd[2], ypr[0]}}; //获得观测量
ekf_.update(z, H, R, h, z_subtract);
}
Eigen::VectorXd Target::ekf_x() const { return ekf_.x; }
const tools::ExtendedKalmanFilter & Target::ekf() const { return ekf_; }
std::vector<Eigen::Vector4d> Target::armor_xyza_list() const
{
std::vector<Eigen::Vector4d> _armor_xyza_list;
for (int i = 0; i < armor_num_; i++) {
auto angle = tools::limit_rad(ekf_.x[6] + i * 2 * CV_PI / armor_num_);
Eigen::Vector3d xyz = h_armor_xyz(ekf_.x, i);
_armor_xyza_list.push_back({xyz[0], xyz[1], xyz[2], angle});
}
return _armor_xyza_list;
}
bool Target::diverged() const
{
auto r_ok = ekf_.x[8] > 0.05 && ekf_.x[8] < 0.5;
auto l_ok = ekf_.x[8] + ekf_.x[9] > 0.05 && ekf_.x[8] + ekf_.x[9] < 0.5;
if (r_ok && l_ok) return false;
tools::logger()->debug("[Target] r={:.3f}, l={:.3f}", ekf_.x[8], ekf_.x[9]);
return true;
}
bool Target::convergened()
{
if (this->name != ArmorName::outpost && update_count_ > 3 && !this->diverged()) {
is_converged_ = true;
}
//前哨站特殊判断
if (this->name == ArmorName::outpost && update_count_ > 10 && !this->diverged()) {
is_converged_ = true;
}
return is_converged_;
}
// 计算出装甲板中心的坐标(考虑长短轴)
Eigen::Vector3d Target::h_armor_xyz(const Eigen::VectorXd & x, int id) const
{
auto angle = tools::limit_rad(x[6] + id * 2 * CV_PI / armor_num_);
auto use_l_h = (armor_num_ == 4) && (id == 1 || id == 3);
auto r = (use_l_h) ? x[8] + x[9] : x[8];
auto armor_x = x[0] - r * std::cos(angle);
auto armor_y = x[2] - r * std::sin(angle);
auto armor_z = (use_l_h) ? x[4] + x[10] : x[4];
return {armor_x, armor_y, armor_z};
}
Eigen::MatrixXd Target::h_jacobian(const Eigen::VectorXd & x, int id) const
{
auto angle = tools::limit_rad(x[6] + id * 2 * CV_PI / armor_num_);
auto use_l_h = (armor_num_ == 4) && (id == 1 || id == 3);
auto r = (use_l_h) ? x[8] + x[9] : x[8];
auto dx_da = r * std::sin(angle);
auto dy_da = -r * std::cos(angle);
auto dx_dr = -std::cos(angle);
auto dy_dr = -std::sin(angle);
auto dx_dl = (use_l_h) ? -std::cos(angle) : 0.0;
auto dy_dl = (use_l_h) ? -std::sin(angle) : 0.0;
auto dz_dh = (use_l_h) ? 1.0 : 0.0;
// clang-format off
Eigen::MatrixXd H_armor_xyza{
{1, 0, 0, 0, 0, 0, dx_da, 0, dx_dr, dx_dl, 0},
{0, 0, 1, 0, 0, 0, dy_da, 0, dy_dr, dy_dl, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, dz_dh},
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}
};
// clang-format on
Eigen::VectorXd armor_xyz = h_armor_xyz(x, id);
Eigen::MatrixXd H_armor_ypd = tools::xyz2ypd_jacobian(armor_xyz);
// clang-format off
Eigen::MatrixXd H_armor_ypda{
{H_armor_ypd(0, 0), H_armor_ypd(0, 1), H_armor_ypd(0, 2), 0},
{H_armor_ypd(1, 0), H_armor_ypd(1, 1), H_armor_ypd(1, 2), 0},
{H_armor_ypd(2, 0), H_armor_ypd(2, 1), H_armor_ypd(2, 2), 0},
{ 0, 0, 0, 1}
};
// clang-format on
return H_armor_ypda * H_armor_xyza;
}
bool Target::checkinit() { return isinit; }
} // namespace auto_aim