Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 681785f2f3 | |||
| 52301bcf8d |
@@ -109,7 +109,7 @@ private:
|
|||||||
const uint8_t &enemy_color; // 敌方颜色,引用外部变量,自动变化
|
const uint8_t &enemy_color; // 敌方颜色,引用外部变量,自动变化
|
||||||
const uint8_t &is_anti_top; // 进入反陀螺,引用外部变量,自动变化
|
const uint8_t &is_anti_top; // 进入反陀螺,引用外部变量,自动变化
|
||||||
State state; // 自瞄状态对象实例
|
State state; // 自瞄状态对象实例
|
||||||
ArmorBox target_box, last_box; // 目标装甲板
|
ArmorBoxes current_target_boxes; // 聚类后的同一车体装甲板
|
||||||
int anti_switch_cnt; // 防止乱切目标计数器
|
int anti_switch_cnt; // 防止乱切目标计数器
|
||||||
cv::Ptr<cv::Tracker> tracker; // tracker对象实例
|
cv::Ptr<cv::Tracker> tracker; // tracker对象实例
|
||||||
Classifier classifier; // CNN分类器对象实例,用于数字识别
|
Classifier classifier; // CNN分类器对象实例,用于数字识别
|
||||||
@@ -142,6 +142,10 @@ private:
|
|||||||
bool sendBoxPosition(uint16_t shoot_delay); // 发送装甲板位置
|
bool sendBoxPosition(uint16_t shoot_delay); // 发送装甲板位置
|
||||||
bool shouldFire() const { return can_fire; } // 获取开火建议
|
bool shouldFire() const { return can_fire; } // 获取开火建议
|
||||||
public:
|
public:
|
||||||
|
ArmorBox target_box, last_box; // 目标装甲板
|
||||||
|
cv::Point2f target_area_center; // 目标大框像素中心
|
||||||
|
double z_min_filtered = 0; // 目标深度极小值
|
||||||
|
|
||||||
void run(cv::Mat &src); // 自瞄主函数
|
void run(cv::Mat &src); // 自瞄主函数
|
||||||
struct HistoryItem {
|
struct HistoryItem {
|
||||||
cv::Point3f pos;
|
cv::Point3f pos;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class Solver
|
|||||||
public:
|
public:
|
||||||
explicit Solver(const std::string & config_path);
|
explicit Solver(const std::string & config_path);
|
||||||
|
|
||||||
|
const cv::Mat& get_camera_matrix() const { return camera_matrix_; }
|
||||||
Eigen::Matrix3d R_gimbal2world() const;
|
Eigen::Matrix3d R_gimbal2world() const;
|
||||||
|
|
||||||
void set_R_gimbal2world(const Eigen::Quaterniond & q);
|
void set_R_gimbal2world(const Eigen::Quaterniond & q);
|
||||||
|
|||||||
@@ -21,40 +21,32 @@ public:
|
|||||||
* @return true 目标与预测弹着点重合,建议击发
|
* @return true 目标与预测弹着点重合,建议击发
|
||||||
*/
|
*/
|
||||||
static bool should_fire(const ArmorFinder &armor_finder, double v0, double gimbal_yaw, double gimbal_pitch, double threshold_deg = -1.0) {
|
static bool should_fire(const ArmorFinder &armor_finder, double v0, double gimbal_yaw, double gimbal_pitch, double threshold_deg = -1.0) {
|
||||||
if (armor_finder.history.size() < 3) return false; // 轨迹点不足,不预测
|
if (armor_finder.last_box.rect == cv::Rect2d()) return false;
|
||||||
|
|
||||||
// 1. 获取目标当前的 3D 状态 (相对于相机)
|
cv::Point2f curr_center = armor_finder.target_box.getCenter();
|
||||||
const auto &latest = armor_finder.history.back();
|
cv::Point2f last_center = armor_finder.last_box.getCenter();
|
||||||
double dist = latest.pos.z / 100.0; // cm -> m
|
cv::Point2f area_center = armor_finder.target_area_center;
|
||||||
|
|
||||||
// 2. 估计目标速度 (简单线性回归或两点差分)
|
// 横向速度估计 (采用帧差分,工业相机一般 ~100 fps)
|
||||||
const auto &first = armor_finder.history.front();
|
double vx = curr_center.x - last_center.x;
|
||||||
double dt = latest.time - first.time;
|
if (std::abs(vx) < 1.0) return false;
|
||||||
if (dt <= 0) return false;
|
|
||||||
|
|
||||||
cv::Point3f velocity = (latest.pos - first.pos) * (1.0 / dt); // cm/s
|
// 计算这块装甲板滑到大框中心所需的预测帧数
|
||||||
|
double frames_to_pass = (area_center.x - curr_center.x) / vx;
|
||||||
|
|
||||||
// 3. 计算飞行时间 (单位: s)
|
// 若不是朝着中心滑动 (已偏离),不激发
|
||||||
|
if (frames_to_pass < 0) return false;
|
||||||
|
|
||||||
|
// 弹道时间预判
|
||||||
|
double dist = armor_finder.z_min_filtered / 100.0; // cm -> m
|
||||||
double t_flight = BallisticSolver::get_flight_time(dist, -gimbal_pitch, v0, BALLISTIC_K);
|
double t_flight = BallisticSolver::get_flight_time(dist, -gimbal_pitch, v0, BALLISTIC_K);
|
||||||
double t_total = t_flight + SYSTEM_DELAY / 1000.0;
|
double t_total = t_flight + SYSTEM_DELAY / 1000.0;
|
||||||
|
|
||||||
// 4. 预测目标未来的位置 (cm)
|
// 将总延迟时间换算为帧数偏移 (以 100FPS)
|
||||||
cv::Point3f pos_pred = latest.pos + velocity * t_total;
|
double delay_frames = t_total * 100.0;
|
||||||
|
|
||||||
// 5. 将预测位置映射回像素坐标 (用于判断重合)
|
// 如果剩余到达中心时间 = 飞弹+机械延迟时间 左右,开火!
|
||||||
double x_pred_pixel = pos_pred.x * FOCUS_PIXAL / pos_pred.z + IMAGE_CENTER_X;
|
return std::abs(frames_to_pass - delay_frames) < 3.0; // 容差 +/- 3帧
|
||||||
double y_pred_pixel = pos_pred.y * FOCUS_PIXAL / pos_pred.z + IMAGE_CENTER_Y;
|
|
||||||
|
|
||||||
// 6. 推断弹着点偏移 (像素级)
|
|
||||||
// 理想打击点应该就在相机中心 (自瞄已经对准补差后的位置)
|
|
||||||
// 但是我们需要判断当前云台的 "指向误差" 是否足够小
|
|
||||||
double dist_to_center = sqrt(pow(x_pred_pixel - IMAGE_CENTER_X, 2) + pow(y_pred_pixel - IMAGE_CENTER_Y, 2));
|
|
||||||
|
|
||||||
// 7. 动态阈值判定 (依据目标距离和装甲板大小)
|
|
||||||
// 距离越远,允许的像素误差越小
|
|
||||||
double threshold = (threshold_deg > 0) ? (threshold_deg * FOCUS_PIXAL * PI / 180.0 / 100.0) : (200.0 / (dist + 0.001));
|
|
||||||
|
|
||||||
return dist_to_center < threshold;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -114,19 +114,40 @@ end:
|
|||||||
cv::waitKey(1);
|
cv::waitKey(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动扳机位置预测逻辑:更新历史位置
|
// 自动扳机位置预测与虚拟靶点重推逻辑
|
||||||
if (target_box.rect != cv::Rect2d()) {
|
if (target_box.rect != cv::Rect2d()) {
|
||||||
auto_aim::Armor armor;
|
// 1. 遍历当前目标车的所有可见装甲板,寻找极小深度 Z_min
|
||||||
armor.type = (target_box.id == B1 || target_box.id == R1 || target_box.id == B2 || target_box.id == R2 || target_box.id == B7 || target_box.id == R7 || target_box.id == B8 || target_box.id == R8) ? auto_aim::ArmorType::big : auto_aim::ArmorType::small;
|
double min_z = 99999.0;
|
||||||
armor.name = static_cast<auto_aim::ArmorName>(target_box.id);
|
|
||||||
armor.points = target_box.points;
|
|
||||||
|
|
||||||
// 更新云台位姿 (使用 MCU 回传的 yaw/pitch,单位假设为度,需转为弧度)
|
|
||||||
solver.set_R_gimbal2world(mcu_data.curr_yaw * CV_PI / 180.0, mcu_data.curr_pitch * CV_PI / 180.0);
|
solver.set_R_gimbal2world(mcu_data.curr_yaw * CV_PI / 180.0, mcu_data.curr_pitch * CV_PI / 180.0);
|
||||||
|
for (auto &b : current_target_boxes) {
|
||||||
|
auto_aim::Armor arm;
|
||||||
|
arm.type = (b.id == B1 || b.id == R1 || b.id == B2 || b.id == R2 || b.id == B7 || b.id == R7 || b.id == B8 || b.id == R8) ? auto_aim::ArmorType::big : auto_aim::ArmorType::small;
|
||||||
|
arm.name = static_cast<auto_aim::ArmorName>(b.id);
|
||||||
|
arm.points = b.points;
|
||||||
|
solver.solve(arm);
|
||||||
|
double z_cm = arm.xyz_in_gimbal.z() * 100.0;
|
||||||
|
if (z_cm < min_z) min_z = z_cm;
|
||||||
|
}
|
||||||
|
|
||||||
solver.solve(armor);
|
// 平滑滤波获取稳定的 z_min
|
||||||
|
if (z_min_filtered <= 0.1 || min_z < z_min_filtered) {
|
||||||
|
z_min_filtered = min_z; // 激进跟进最小值锁定
|
||||||
|
} else {
|
||||||
|
z_min_filtered = z_min_filtered * 0.7 + min_z * 0.3; // 缓慢恢复
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 将 2D 大框中心逆推为 3D 虚拟靶点
|
||||||
|
const cv::Mat& cam_mat = solver.get_camera_matrix();
|
||||||
|
double fx = cam_mat.at<double>(0, 0);
|
||||||
|
double cx = cam_mat.at<double>(0, 2);
|
||||||
|
double fy = cam_mat.at<double>(1, 1);
|
||||||
|
double cy = cam_mat.at<double>(1, 2);
|
||||||
|
|
||||||
|
double X_cam = (target_area_center.x - cx) * z_min_filtered / fx;
|
||||||
|
double Y_cam = (target_area_center.y - cy) * z_min_filtered / fy;
|
||||||
|
|
||||||
|
target_xyz = cv::Point3f(X_cam, Y_cam, z_min_filtered);
|
||||||
|
|
||||||
target_xyz = cv::Point3f(armor.xyz_in_gimbal.x() * 100.0, armor.xyz_in_gimbal.y() * 100.0, armor.xyz_in_gimbal.z() * 100.0); // 转换为cm
|
|
||||||
cv::Point3f current_pos = target_xyz;
|
cv::Point3f current_pos = target_xyz;
|
||||||
double current_time = frame_time / 1000.0;
|
double current_time = frame_time / 1000.0;
|
||||||
history.push_back({current_pos, current_time});
|
history.push_back({current_pos, current_time});
|
||||||
|
|||||||
@@ -206,6 +206,26 @@ bool ArmorFinder::findArmorBox(const cv::Mat &src, ArmorBox &box) {
|
|||||||
} else { // 如果分类器不可用,则直接选取候选区中的第一个区域作为目标(往往会误识别)
|
} else { // 如果分类器不可用,则直接选取候选区中的第一个区域作为目标(往往会误识别)
|
||||||
box = armor_boxes[0];
|
box = armor_boxes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 反小陀螺:同类装甲板大框聚类 ---
|
||||||
|
if (box.rect != cv::Rect2d(0, 0, 0, 0)) {
|
||||||
|
current_target_boxes.clear();
|
||||||
|
double min_x = 9999, max_x = -9999, min_y = 9999, max_y = -9999;
|
||||||
|
for (const auto &one_box : armor_boxes) {
|
||||||
|
// 将同分类 ID 的装甲板聚类 (即使未分类也可根据需求退化处理)
|
||||||
|
if (one_box.id == box.id) {
|
||||||
|
current_target_boxes.push_back(one_box);
|
||||||
|
min_x = fmin(min_x, one_box.rect.x);
|
||||||
|
max_x = fmax(max_x, one_box.rect.x + one_box.rect.width);
|
||||||
|
min_y = fmin(min_y, one_box.rect.y);
|
||||||
|
max_y = fmax(max_y, one_box.rect.y + one_box.rect.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!current_target_boxes.empty()) {
|
||||||
|
target_area_center = cv::Point2f(min_x + (max_x - min_x) / 2.0, min_y + (max_y - min_y) / 2.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,28 +51,9 @@ bool ArmorFinder::sendBoxPosition(uint16_t shoot_delay) {
|
|||||||
if (shoot_delay) {
|
if (shoot_delay) {
|
||||||
LOGM(STR_CTR(WORD_BLUE, "next box %dms"), shoot_delay);
|
LOGM(STR_CTR(WORD_BLUE, "next box %dms"), shoot_delay);
|
||||||
}
|
}
|
||||||
auto rect = target_box.rect;
|
|
||||||
double dx = rect.x + rect.width / 2 - IMAGE_CENTER_X;
|
|
||||||
double dy = rect.y + rect.height / 2 - IMAGE_CENTER_Y;
|
|
||||||
|
|
||||||
// PID
|
// 直接基于虚拟靶心 target_xyz (已包含 Z_min 和中心坐标转换) 算出绝对偏航角
|
||||||
sum_yaw += dx;
|
double yaw = atan2(target_xyz.x, target_xyz.z) * 180 / PI;
|
||||||
sum_pitch += dy;
|
|
||||||
float yaw_I_component = YAW_AIM_KI * sum_yaw;
|
|
||||||
float pitch_I_component = PITCH_AIM_KI * sum_pitch;
|
|
||||||
|
|
||||||
double tmp_yaw = dx;
|
|
||||||
double tmp_pitch = dy;
|
|
||||||
dx = YAW_AIM_KP * dx + YAW_AIM_KI * sum_yaw +
|
|
||||||
YAW_AIM_KD * (dx - last_yaw);
|
|
||||||
dy = PITCH_AIM_KP * dy + PITCH_AIM_KI * sum_pitch +
|
|
||||||
PITCH_AIM_KD * (dy - last_pitch);
|
|
||||||
|
|
||||||
last_yaw = tmp_yaw;
|
|
||||||
last_pitch = tmp_pitch;
|
|
||||||
//
|
|
||||||
|
|
||||||
double yaw = atan(dx / FOCUS_PIXAL) * 180 / PI;
|
|
||||||
double dist = sqrt(target_xyz.x * target_xyz.x + target_xyz.y * target_xyz.y + target_xyz.z * target_xyz.z) / 100.0; // 米
|
double dist = sqrt(target_xyz.x * target_xyz.x + target_xyz.y * target_xyz.y + target_xyz.z * target_xyz.z) / 100.0; // 米
|
||||||
|
|
||||||
// 弹道补偿使用 PnP 提供的 3D 坐标
|
// 弹道补偿使用 PnP 提供的 3D 坐标
|
||||||
@@ -80,9 +61,14 @@ bool ArmorFinder::sendBoxPosition(uint16_t shoot_delay) {
|
|||||||
double y_target = -target_xyz.y / 100.0; // 垂直高度差
|
double y_target = -target_xyz.y / 100.0; // 垂直高度差
|
||||||
double pitch_comp = BallisticSolver::get_pitch(x_target, y_target, MUZZLE_VELOCITY, BALLISTIC_K);
|
double pitch_comp = BallisticSolver::get_pitch(x_target, y_target, MUZZLE_VELOCITY, BALLISTIC_K);
|
||||||
|
|
||||||
// 计算是否满足开火条件 (例如残差小于 1.5 度)
|
// 计算是否满足开火条件 (容差 1.5 度,可根据需要调节)
|
||||||
can_fire = AutoTrigger::should_fire(*this, MUZZLE_VELOCITY, yaw, pitch_comp, 1.5);
|
can_fire = AutoTrigger::should_fire(*this, MUZZLE_VELOCITY, yaw, pitch_comp, 1.5);
|
||||||
|
|
||||||
|
// 当满足预测开火条件时,通知电控 (借用 shoot_delay 或其他预留位)
|
||||||
|
if (can_fire && shoot_delay == 0) {
|
||||||
|
shoot_delay = 1; // 假定 1 为开火 flag,由电控在裁判系统中绘制绿框或自动激发
|
||||||
|
}
|
||||||
|
|
||||||
return sendTarget(serial, yaw, pitch_comp, dist * 100.0, shoot_delay);
|
return sendTarget(serial, yaw, pitch_comp, dist * 100.0, shoot_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
#ifndef _SETCONFIG_H_
|
#ifndef _SETCONFIG_H_
|
||||||
#define _SETCONFIG_H_
|
#define _SETCONFIG_H_
|
||||||
|
|
||||||
|
#ifndef WITH_CONFIG
|
||||||
#define WITH_CONFIG
|
#define WITH_CONFIG
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_CONFIG
|
#ifdef WITH_CONFIG
|
||||||
#include <config/config.h>
|
#include <config/config.h>
|
||||||
|
|||||||
Reference in New Issue
Block a user