Compare commits
2 Commits
05a8782eea
...
自动板机1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 681785f2f3 | |||
| 52301bcf8d |
@@ -109,7 +109,7 @@ private:
|
||||
const uint8_t &enemy_color; // 敌方颜色,引用外部变量,自动变化
|
||||
const uint8_t &is_anti_top; // 进入反陀螺,引用外部变量,自动变化
|
||||
State state; // 自瞄状态对象实例
|
||||
ArmorBox target_box, last_box; // 目标装甲板
|
||||
ArmorBoxes current_target_boxes; // 聚类后的同一车体装甲板
|
||||
int anti_switch_cnt; // 防止乱切目标计数器
|
||||
cv::Ptr<cv::Tracker> tracker; // tracker对象实例
|
||||
Classifier classifier; // CNN分类器对象实例,用于数字识别
|
||||
@@ -142,6 +142,10 @@ private:
|
||||
bool sendBoxPosition(uint16_t shoot_delay); // 发送装甲板位置
|
||||
bool shouldFire() const { return can_fire; } // 获取开火建议
|
||||
public:
|
||||
ArmorBox target_box, last_box; // 目标装甲板
|
||||
cv::Point2f target_area_center; // 目标大框像素中心
|
||||
double z_min_filtered = 0; // 目标深度极小值
|
||||
|
||||
void run(cv::Mat &src); // 自瞄主函数
|
||||
struct HistoryItem {
|
||||
cv::Point3f pos;
|
||||
|
||||
@@ -17,6 +17,7 @@ class Solver
|
||||
public:
|
||||
explicit Solver(const std::string & config_path);
|
||||
|
||||
const cv::Mat& get_camera_matrix() const { return camera_matrix_; }
|
||||
Eigen::Matrix3d R_gimbal2world() const;
|
||||
|
||||
void set_R_gimbal2world(const Eigen::Quaterniond & q);
|
||||
|
||||
@@ -21,40 +21,32 @@ public:
|
||||
* @return true 目标与预测弹着点重合,建议击发
|
||||
*/
|
||||
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;
|
||||
|
||||
cv::Point2f curr_center = armor_finder.target_box.getCenter();
|
||||
cv::Point2f last_center = armor_finder.last_box.getCenter();
|
||||
cv::Point2f area_center = armor_finder.target_area_center;
|
||||
|
||||
// 1. 获取目标当前的 3D 状态 (相对于相机)
|
||||
const auto &latest = armor_finder.history.back();
|
||||
double dist = latest.pos.z / 100.0; // cm -> m
|
||||
|
||||
// 2. 估计目标速度 (简单线性回归或两点差分)
|
||||
const auto &first = armor_finder.history.front();
|
||||
double dt = latest.time - first.time;
|
||||
if (dt <= 0) return false;
|
||||
|
||||
cv::Point3f velocity = (latest.pos - first.pos) * (1.0 / dt); // cm/s
|
||||
|
||||
// 3. 计算飞行时间 (单位: s)
|
||||
// 横向速度估计 (采用帧差分,工业相机一般 ~100 fps)
|
||||
double vx = curr_center.x - last_center.x;
|
||||
if (std::abs(vx) < 1.0) return false;
|
||||
|
||||
// 计算这块装甲板滑到大框中心所需的预测帧数
|
||||
double frames_to_pass = (area_center.x - curr_center.x) / vx;
|
||||
|
||||
// 若不是朝着中心滑动 (已偏离),不激发
|
||||
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_total = t_flight + SYSTEM_DELAY / 1000.0;
|
||||
|
||||
// 4. 预测目标未来的位置 (cm)
|
||||
cv::Point3f pos_pred = latest.pos + velocity * t_total;
|
||||
|
||||
// 5. 将预测位置映射回像素坐标 (用于判断重合)
|
||||
double x_pred_pixel = pos_pred.x * FOCUS_PIXAL / pos_pred.z + IMAGE_CENTER_X;
|
||||
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;
|
||||
double t_total = t_flight + SYSTEM_DELAY / 1000.0;
|
||||
|
||||
// 将总延迟时间换算为帧数偏移 (以 100FPS)
|
||||
double delay_frames = t_total * 100.0;
|
||||
|
||||
// 如果剩余到达中心时间 = 飞弹+机械延迟时间 左右,开火!
|
||||
return std::abs(frames_to_pass - delay_frames) < 3.0; // 容差 +/- 3帧
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -114,19 +114,40 @@ end:
|
||||
cv::waitKey(1);
|
||||
}
|
||||
|
||||
// 自动扳机位置预测逻辑:更新历史位置
|
||||
// 自动扳机位置预测与虚拟靶点重推逻辑
|
||||
if (target_box.rect != cv::Rect2d()) {
|
||||
auto_aim::Armor armor;
|
||||
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;
|
||||
armor.name = static_cast<auto_aim::ArmorName>(target_box.id);
|
||||
armor.points = target_box.points;
|
||||
|
||||
// 更新云台位姿 (使用 MCU 回传的 yaw/pitch,单位假设为度,需转为弧度)
|
||||
// 1. 遍历当前目标车的所有可见装甲板,寻找极小深度 Z_min
|
||||
double min_z = 99999.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;
|
||||
double current_time = frame_time / 1000.0;
|
||||
history.push_back({current_pos, current_time});
|
||||
|
||||
@@ -206,6 +206,26 @@ bool ArmorFinder::findArmorBox(const cv::Mat &src, ArmorBox &box) {
|
||||
} else { // 如果分类器不可用,则直接选取候选区中的第一个区域作为目标(往往会误识别)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,28 +51,9 @@ bool ArmorFinder::sendBoxPosition(uint16_t shoot_delay) {
|
||||
if (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
|
||||
sum_yaw += dx;
|
||||
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;
|
||||
|
||||
// 直接基于虚拟靶心 target_xyz (已包含 Z_min 和中心坐标转换) 算出绝对偏航角
|
||||
double yaw = atan2(target_xyz.x, target_xyz.z) * 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; // 米
|
||||
|
||||
// 弹道补偿使用 PnP 提供的 3D 坐标
|
||||
@@ -80,9 +61,14 @@ bool ArmorFinder::sendBoxPosition(uint16_t shoot_delay) {
|
||||
double y_target = -target_xyz.y / 100.0; // 垂直高度差
|
||||
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);
|
||||
|
||||
// 当满足预测开火条件时,通知电控 (借用 shoot_delay 或其他预留位)
|
||||
if (can_fire && shoot_delay == 0) {
|
||||
shoot_delay = 1; // 假定 1 为开火 flag,由电控在裁判系统中绘制绿框或自动激发
|
||||
}
|
||||
|
||||
return sendTarget(serial, yaw, pitch_comp, dist * 100.0, shoot_delay);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#ifndef _SETCONFIG_H_
|
||||
#define _SETCONFIG_H_
|
||||
|
||||
#ifndef WITH_CONFIG
|
||||
#define WITH_CONFIG
|
||||
#endif
|
||||
|
||||
#ifdef WITH_CONFIG
|
||||
#include <config/config.h>
|
||||
|
||||
Reference in New Issue
Block a user