无人机集群固定阵型轨迹生成模型
版本:v2.0
1. 数学模型
1.1 主要变量定义
领航机状态
- $ P_L(t) = \begin{bmatrix} x_L(t) \\ y_L(t) \end{bmatrix} $:领航机在全局坐标下的位置(可以是离散时间点序列或连续函数)。
- $ \theta(t) $:领航机的航向角。
旋转矩阵
$$ R(\theta(t)) = \begin{bmatrix} \cos \theta(t) & -\sin \theta(t) \\ \sin \theta(t) & \cos \theta(t) \end{bmatrix}. $$
为将局部偏移转换为全局坐标,定义旋转矩阵:局部偏移向量
$$ \Delta_i = \begin{bmatrix} \Delta_{i,x} \\ \Delta_{i,y} \end{bmatrix}. $$
对于每个跟随 UAV $i$,在领航机局部坐标系中预先设计一个相对偏移向量:对于线型阵型,可定义:
$$ \Delta_i = \begin{bmatrix} 0 \\ i \times d \end{bmatrix} \quad \text{或} \quad \Delta_i = \begin{bmatrix} i \times d_x \\ i \times d_y \end{bmatrix}, $$其中 $d$(或 $d_x, d_y$)为预设间隔,正负号决定侧边。
对于楔形阵型(左右对称):
$$ \Delta_{i,\text{左}} = \begin{bmatrix} i\,d_x \\ i\,d_y \end{bmatrix}, \quad \Delta_{i,\text{右}} = \begin{bmatrix} i\,d_x \\ -i\,d_y \end{bmatrix}, \quad i=1,2,\ldots $$
跟随 UAV 全局期望位置
$$ P_i(t) = P_L(t) + R\big(\theta(t)\big) \Delta_i. $$
根据领航机的位置与航向,以及局部偏移,得到 UAV $i$ 的全局期望位置:
1.2 动态约束扩展(可选)
如果需要考虑 UAV 的运动学与动力学限制,可引入 UAV 的状态变量和动力学模型。以简单的平面运动模型为例:
- UAV $i$ 状态:$ X_i = \begin{bmatrix} x_i,\, y_i,\, \theta_i,\, v_i \end{bmatrix}^\top $
- 动力学模型: $$ \begin{cases} \dot{x}_i = v_i \cos \theta_i, \\ \dot{y}_i = v_i \sin \theta_i, \\ \dot{\theta}_i = u_i, \end{cases} $$ 其中 $u_i$ 为控制输入(转向角速率)。设计合适的跟踪控制器,使得 UAV $i$ 能够跟踪参考轨迹 $P_i(t)$ 并满足转弯半径、加速度等约束。
2. 模型实现流程
2.1 模型框架
输入数据
- 领航机轨迹 $ P_L(t) $ 和航向 $ \theta(t) $。
- 阵型参数(类型、无人机数量、间距参数等)。
局部偏移生成
根据阵型参数,为每个 UAV $i$ 计算局部偏移 $ \Delta_i $。
例如:- 线型阵型:$\Delta_i = \begin{bmatrix} 0 \\ i \times d \end{bmatrix}$
- 楔形阵型:根据左右分支分别计算 $\Delta_{i,\text{左}}$ 与 $\Delta_{i,\text{右}}$。
全局坐标转换
$$ P_i(t) = P_L(t) + R\big(\theta(t)\big) \Delta_i. $$
对于每个 UAV $i$ 在每个时刻 $t$,计算全局期望位置:轨迹平滑与动态可行性处理
- 对直接计算出的离散轨迹进行平滑处理(如样条插值、Bézier 曲线拟合等)。
- 根据 UAV 的运动学和动力学约束,通过轨迹优化或闭环控制器进行修正与跟踪。
输出
生成所有 UAV 的期望轨迹,用于下层路径跟踪或控制器执行。
2.2 伪代码示例
下面的伪代码描述了整体流程,便于模型理解与实现:
# 假设已获得领航机轨迹数据 leader_traj: list of (x, y, theta) 对
# formation_params 包含阵型类型和相关间隔参数
def compute_rotation_matrix(theta):
return [[cos(theta), -sin(theta)],
[sin(theta), cos(theta)]]
def formation_offset(i, formation_params, branch='center'):
"""
根据阵型参数计算第 i 架 UAV 的局部偏移。
branch 参数用于楔形阵型:'left' 或 'right' 表示左右分支,
'center' 可用于线型或单边阵型。
"""
if formation_params['type'] == 'line':
# 示例:所有 UAV 沿一侧,间隔 d
d = formation_params['d']
return [0, i * d]
elif formation_params['type'] == 'wedge':
d_x = formation_params['d_x']
d_y = formation_params['d_y']
if branch == 'left':
return [i * d_x, i * d_y]
elif branch == 'right':
return [i * d_x, -i * d_y]
# 可扩展其他阵型
return [0, 0]
def smooth_trajectory(traj):
# 根据需求对轨迹进行平滑处理
# 例如使用样条曲线拟合
return traj # 此处为占位函数
# 主函数:生成所有 UAV 的期望轨迹
def generate_formation_trajectories(leader_traj, formation_params, num_followers):
formation_trajs = {i: [] for i in range(1, num_followers+1)}
for (x_L, y_L, theta) in leader_traj:
# 计算旋转矩阵
R = compute_rotation_matrix(theta)
# 遍历所有 UAV
for i in range(1, num_followers+1):
# 根据阵型类型确定局部偏移,此处以线型为例,
# 若为楔形阵型,则需要区分左右分支
Delta = formation_offset(i, formation_params, branch='center')
# 全局坐标转换:P_follower = P_leader + R * Delta
# 这里假设 R 与 Delta 均为列表或数组,进行矩阵向量乘法
P_follower = [
x_L + R[0][0] * Delta[0] + R[0][1] * Delta[1],
y_L + R[1][0] * Delta[0] + R[1][1] * Delta[1]
]
formation_trajs[i].append(P_follower)
# 对每个 UAV 的轨迹进行平滑处理
for i in formation_trajs:
formation_trajs[i] = smooth_trajectory(formation_trajs[i])
return formation_trajs
# 示例调用
leader_traj = [
# (x, y, theta) 序列,实际数据由传感器或规划模块提供
(0, 0, 0),
(1, 0, 0),
(2, 0, 0),
# ... 更多数据
]
formation_params = {'type': 'line', 'd': 5.0} # 线型阵型,每架 UAV 间隔 5 单位
num_followers = 3
formation_trajectories = generate_formation_trajectories(leader_traj, formation_params, num_followers)3. 模型说明
模型通用性
该模型统一采用“领航机轨迹 + 局部偏移”思想,不论阵型如何变化(线型、楔形、甚至更复杂的阵型),只需要在局部坐标系中设计合适的 $\Delta_i$ 即可。扩展性
该模型可以方便地扩展至考虑 UAV 的运动学约束、路径平滑处理、实时反馈控制等问题,使得生成的期望轨迹既具备几何队形要求,又满足 UAV 的动态性能。实现细节
程序员在实际实现时,可根据具体平台和需求替换平滑算法、矩阵运算库、控制器设计部分等,同时可结合实际采样时间离散化处理。
4. C++代码实现参考
4.1 ChatGPT o3-mini-high
下面给出 C++ 代码示例,将上述数学模型封装为一个类,便于集成到已有的无人机集群控制系统中。代码中定义了基本数据结构(位置、姿态、阵型参数等)、旋转操作、局部偏移计算以及生成全局期望轨迹的流程。同时,针对不同阵型(线型与楔形)给出不同的局部偏移计算方法。可以根据需要进一步扩展平滑处理或加入 UAV 的动态约束。
下面是完整示例代码:
#include <iostream>
#include <vector>
#include <cmath>
#include <string>
// -------------------------- 数据结构定义 --------------------------
/// 用于表示平面位置点
struct Point {
double x;
double y;
};
/// 表示无人机的位姿(位置和航向角)
struct Pose {
double x;
double y;
double theta; // 航向角(弧度制)
};
/// 枚举不同的阵型类型
enum class FormationType {
LINE, // 线型阵型
WEDGE // 楔形阵型
};
/// 阵型参数定义
struct FormationParameters {
FormationType type;
// 线型阵型参数:两架 UAV 间的固定间隔(沿局部 y 轴方向)
double d;
// 楔形阵型参数:沿航向方向间隔 d_x 及横向间隔 d_y
double d_x;
double d_y;
};
// -------------------------- FormationGenerator 类 --------------------------
/**
* @brief 生成跟随 UAV 的期望轨迹
*
* 利用领航机轨迹和阵型参数,在每个时刻计算每架跟随 UAV 的全局期望位置。
*/
class FormationGenerator {
public:
/**
* @brief 构造函数
* @param leaderTrajectory 领航机轨迹(每个时刻的位姿)
* @param formationParams 阵型参数
* @param numFollowers 跟随 UAV 数量
*/
FormationGenerator(const std::vector<Pose>& leaderTrajectory,
const FormationParameters& formationParams,
int numFollowers)
: leaderTrajectory_(leaderTrajectory),
formationParams_(formationParams),
numFollowers_(numFollowers) {}
/**
* @brief 生成所有跟随 UAV 的轨迹
* @return 每个 UAV 的轨迹,外层 vector 下标对应 UAV 序号(从 0 开始),内层 vector 为轨迹点序列
*/
std::vector<std::vector<Point>> generate();
private:
std::vector<Pose> leaderTrajectory_;
FormationParameters formationParams_;
int numFollowers_;
/**
* @brief 对二维向量 (dx, dy) 根据角度 theta 进行旋转
* @param dx 原向量 x 分量
* @param dy 原向量 y 分量
* @param theta 旋转角度(弧度制)
* @return 旋转后的向量
*/
Point rotatePoint(double dx, double dy, double theta) {
Point rotated;
rotated.x = dx * std::cos(theta) - dy * std::sin(theta);
rotated.y = dx * std::sin(theta) + dy * std::cos(theta);
return rotated;
}
/**
* @brief 根据阵型参数计算局部偏移
*
* 对于线型阵型,所有 UAV 沿固定侧边(这里假设在局部 y 正方向)排列;
* 对于楔形阵型,则根据左右分支分别计算偏移。
*
* @param followerIndex 当前 UAV 的编号(从 1 开始,线型阵型时直接使用;楔形阵型时用来计算排名)
* @param branch 对于楔形阵型,“left”表示左侧分支,“right”表示右侧分支;线型阵型时可以忽略此参数
* @return 局部坐标系中的偏移向量
*/
Point computeLocalOffset(int followerIndex, const std::string& branch = "center") {
Point offset{0.0, 0.0};
if (formationParams_.type == FormationType::LINE) {
// 线型阵型:假设所有 UAV 沿局部 y 方向正侧边排列
offset.x = 0.0;
offset.y = followerIndex * formationParams_.d;
} else if (formationParams_.type == FormationType::WEDGE) {
// 楔形阵型:分别计算左右分支的局部偏移
if (branch == "left") {
offset.x = followerIndex * formationParams_.d_x;
offset.y = followerIndex * formationParams_.d_y;
} else if (branch == "right") {
offset.x = followerIndex * formationParams_.d_x;
offset.y = -followerIndex * formationParams_.d_y;
}
}
return offset;
}
/**
* @brief 对生成的轨迹进行平滑处理
*
* 目前为占位函数,可根据需要替换为样条、Bézier 曲线等算法
*
* @param traj 原始轨迹
* @return 平滑后的轨迹
*/
std::vector<Point> smoothTrajectory(const std::vector<Point>& traj) {
// 此处直接返回原始轨迹,可根据实际需求实现平滑算法
return traj;
}
};
// -------------------------- FormationGenerator 方法实现 --------------------------
std::vector<std::vector<Point>> FormationGenerator::generate() {
// 初始化每架跟随 UAV 的轨迹(编号从 0 到 numFollowers_-1)
std::vector<std::vector<Point>> formationTrajectories(numFollowers_);
// 对领航机轨迹中的每个时刻,计算所有跟随 UAV 的期望位置
for (const auto& leaderPose : leaderTrajectory_) {
// 对于每架 UAV
for (int i = 0; i < numFollowers_; ++i) {
Point localOffset;
if (formationParams_.type == FormationType::WEDGE) {
// 对于楔形阵型,常采用左右对称的方式
// 例如:将 UAV 分为左右两支,按照索引交替分配
// 此处计算时,用 (i/2 + 1) 作为该分支中的排名
int rank = i / 2 + 1;
std::string branch = (i % 2 == 0) ? "left" : "right";
localOffset = computeLocalOffset(rank, branch);
} else {
// 对于线型阵型,直接使用 UAV 编号(从 1 开始)
localOffset = computeLocalOffset(i + 1, "center");
}
// 将局部偏移转换到全局坐标
Point rotated = rotatePoint(localOffset.x, localOffset.y, leaderPose.theta);
Point globalPos;
globalPos.x = leaderPose.x + rotated.x;
globalPos.y = leaderPose.y + rotated.y;
formationTrajectories[i].push_back(globalPos);
}
}
// 对每架 UAV 的轨迹进行平滑处理(如有需要)
for (auto& traj : formationTrajectories) {
traj = smoothTrajectory(traj);
}
return formationTrajectories;
}
// -------------------------- 示例主函数 --------------------------
int main() {
// 示例:构造领航机轨迹数据(假设每个时刻的位姿信息)
std::vector<Pose> leaderTrajectory = {
{0.0, 0.0, 0.0},
{10.0, 0.0, 0.0},
{20.0, 0.0, 0.0},
{30.0, 0.0, M_PI / 6}, // 转弯示例,航向变为 30 度
{40.0, 5.0, M_PI / 4}
// ... 可根据实际情况扩展数据
};
// 示例1:线型阵型,间隔 5.0 个单位
FormationParameters lineParams;
lineParams.type = FormationType::LINE;
lineParams.d = 5.0;
// 例如,3 架跟随 UAV
int numFollowers = 3;
FormationGenerator lineFormationGenerator(leaderTrajectory, lineParams, numFollowers);
auto lineTrajectories = lineFormationGenerator.generate();
std::cout << "线型阵型生成的跟随 UAV 轨迹:" << std::endl;
for (int i = 0; i < numFollowers; ++i) {
std::cout << "UAV " << i + 1 << " 轨迹:" << std::endl;
for (const auto& pt : lineTrajectories[i]) {
std::cout << "(" << pt.x << ", " << pt.y << ") ";
}
std::cout << std::endl;
}
// 示例2:楔形阵型,设置 d_x = 3.0, d_y = 2.0
FormationParameters wedgeParams;
wedgeParams.type = FormationType::WEDGE;
wedgeParams.d_x = 3.0;
wedgeParams.d_y = 2.0;
FormationGenerator wedgeFormationGenerator(leaderTrajectory, wedgeParams, numFollowers);
auto wedgeTrajectories = wedgeFormationGenerator.generate();
std::cout << "\n楔形阵型生成的跟随 UAV 轨迹:" << std::endl;
for (int i = 0; i < numFollowers; ++i) {
std::cout << "UAV " << i + 1 << " 轨迹:" << std::endl;
for (const auto& pt : wedgeTrajectories[i]) {
std::cout << "(" << pt.x << ", " << pt.y << ") ";
}
std::cout << std::endl;
}
return 0;
}代码说明
数据结构定义
Point与Pose分别用于表示二维点和带航向角的位姿。FormationParameters中包含了两种阵型的相关参数(线型仅用d;楔形则需要d_x和d_y)。
FormationGenerator 类
- 构造时输入领航机轨迹、阵型参数和跟随 UAV 数量。
- 在
generate()方法中,遍历每个时刻的领航机位姿,然后对每架跟随 UAV 根据不同阵型计算局部偏移,通过旋转转换到全局坐标系,最终生成每架 UAV 的轨迹。
阵型处理
- 线型阵型直接按照 UAV 编号(从 1 开始)在局部 y 方向设置偏移;
- 楔形阵型则采用左右分支(通过交替分配)的方式,使用
(i/2+1)作为分支内的排名计算偏移。
扩展与集成
- 平滑函数
smoothTrajectory目前为占位函数,可根据具体需求替换为样条或 Bézier 曲线等算法; - 该模块化代码设计便于嵌入到已有的无人机集群控制模型中,并可根据需求进一步扩展(如加入 UAV 的运动学约束、实时控制等)。
- 平滑函数
以上代码可方便地将生成队形轨迹的模块与其它控制、规划模块进行集成,从而实现多架 UAV 协同飞行的功能。
4.2 Claude 3.5 Sonnet
以下为完整的C++代码实现:
// formation_control.hpp
#pragma once
#include <vector>
#include <map>
#include <cmath>
#include <memory>
#include <Eigen/Dense>
namespace uav {
// 基础数据结构
struct Point2D {
double x;
double y;
Point2D(double x = 0, double y = 0) : x(x), y(y) {}
Point2D operator+(const Point2D& other) const {
return Point2D(x + other.x, y + other.y);
}
};
struct UAVState {
Point2D position;
double heading; // theta
double velocity;
UAVState(const Point2D& pos = Point2D(),
double h = 0,
double v = 0)
: position(pos), heading(h), velocity(v) {}
};
// 阵型类型枚举
enum class FormationType {
LINE,
WEDGE,
// 可扩展其他阵型
};
// 阵型参数基类
class FormationParams {
public:
virtual ~FormationParams() = default;
};
// 线型阵型参数
class LineFormationParams : public FormationParams {
public:
double spacing; // UAV间距
explicit LineFormationParams(double d) : spacing(d) {}
};
// 楔形阵型参数
class WedgeFormationParams : public FormationParams {
public:
double dx; // 纵向间距
double dy; // 横向间距
WedgeFormationParams(double dx, double dy)
: dx(dx), dy(dy) {}
};
// 旋转矩阵计算类
class RotationMatrix {
public:
static Eigen::Matrix2d compute(double theta) {
Eigen::Matrix2d R;
R << std::cos(theta), -std::sin(theta),
std::sin(theta), std::cos(theta);
return R;
}
};
// 局部偏移计算类
class FormationOffset {
public:
static Point2D compute(int uavIndex,
const FormationType& type,
const std::shared_ptr<FormationParams>& params,
const std::string& branch = "center") {
switch (type) {
case FormationType::LINE:
return computeLineOffset(uavIndex,
std::static_pointer_cast<LineFormationParams>(params));
case FormationType::WEDGE:
return computeWedgeOffset(uavIndex,
std::static_pointer_cast<WedgeFormationParams>(params),
branch);
default:
return Point2D(0, 0);
}
}
private:
static Point2D computeLineOffset(
int uavIndex,
const std::shared_ptr<LineFormationParams>& params) {
return Point2D(0, uavIndex * params->spacing);
}
static Point2D computeWedgeOffset(
int uavIndex,
const std::shared_ptr<WedgeFormationParams>& params,
const std::string& branch) {
if (branch == "left") {
return Point2D(uavIndex * params->dx, uavIndex * params->dy);
} else if (branch == "right") {
return Point2D(uavIndex * params->dx, -uavIndex * params->dy);
}
return Point2D(0, 0);
}
};
// 轨迹平滑器接口
class TrajectorySmoothing {
public:
virtual std::vector<UAVState> smooth(
const std::vector<UAVState>& rawTrajectory) = 0;
virtual ~TrajectorySmoothing() = default;
};
// 简单的轨迹平滑器实现(可扩展)
class SimpleSmoothing : public TrajectorySmoothing {
public:
std::vector<UAVState> smooth(
const std::vector<UAVState>& rawTrajectory) override {
// 这里可以实现具体的平滑算法,如样条插值
return rawTrajectory; // 当前仅返回原轨迹
}
};
// 队形控制器主类
class FormationController {
public:
FormationController(
FormationType type,
std::shared_ptr<FormationParams> params,
std::shared_ptr<TrajectorySmoothing> smoother)
: formationType(type)
, formationParams(params)
, trajectorySmoother(smoother) {}
std::map<int, std::vector<UAVState>> generateFormationTrajectories(
const std::vector<UAVState>& leaderTrajectory,
int numFollowers) {
std::map<int, std::vector<UAVState>> formationTrajs;
// 为每个跟随者初始化轨迹容器
for (int i = 1; i <= numFollowers; ++i) {
formationTrajs[i] = std::vector<UAVState>();
}
// 计算每个时间点的队形位置
for (const auto& leaderState : leaderTrajectory) {
Eigen::Matrix2d R = RotationMatrix::compute(leaderState.heading);
for (int i = 1; i <= numFollowers; ++i) {
// 计算局部偏移
Point2D localOffset = FormationOffset::compute(
i, formationType, formationParams);
// 转换到全局坐标
Eigen::Vector2d localOffsetVec(localOffset.x, localOffset.y);
Eigen::Vector2d globalOffset = R * localOffsetVec;
// 计算跟随者位置
Point2D followerPos(
leaderState.position.x + globalOffset(0),
leaderState.position.y + globalOffset(1)
);
// 创建跟随者状态并添加到轨迹中
UAVState followerState(
followerPos,
leaderState.heading,
leaderState.velocity
);
formationTrajs[i].push_back(followerState);
}
}
// 对所有轨迹进行平滑处理
for (auto& [uavId, trajectory] : formationTrajs) {
trajectory = trajectorySmoother->smooth(trajectory);
}
return formationTrajs;
}
private:
FormationType formationType;
std::shared_ptr<FormationParams> formationParams;
std::shared_ptr<TrajectorySmoothing> trajectorySmoother;
};
} // namespace uav
代码说明
模块化设计:
- 使用命名空间
uav隔离所有相关类和结构 - 每个主要组件都被设计为独立的类
- 使用继承和多态支持不同的阵型参数和平滑算法
- 使用命名空间
关键组件:
Point2D和UAVState:基础数据结构FormationType和FormationParams:阵型配置RotationMatrix:旋转矩阵计算FormationOffset:局部偏移计算TrajectorySmoothing:轨迹平滑接口FormationController:主控制器类
扩展性考虑:
- 可以轻松添加新的阵型类型
- 可以实现不同的轨迹平滑算法
- 使用智能指针管理对象生命周期
使用示例:
using namespace uav;
// 创建线型阵型参数
auto lineParams = std::make_shared<LineFormationParams>(5.0); // 5.0米间距
// 创建轨迹平滑器
auto smoother = std::make_shared<SimpleSmoothing>();
// 创建队形控制器
FormationController controller(
FormationType::LINE,
lineParams,
smoother
);
// 准备领航机轨迹
std::vector<UAVState> leaderTrajectory = {
UAVState(Point2D(0, 0), 0, 20), // 位置(0,0),航向0,速度20
UAVState(Point2D(10, 0), M_PI/4, 20),
// ... 更多轨迹点
};
// 生成编队轨迹
int numFollowers = 3;
auto formationTrajectories = controller.generateFormationTrajectories(
leaderTrajectory,
numFollowers
);要将这个代码集成到现有系统中,你需要:
- 将代码放入适当的头文件和源文件中
- 确保你的项目中包含了Eigen库
- 根据需要实现具体的轨迹平滑算法
- 根据实际需求扩展其他阵型类型
这个实现保持了原始数学模型的结构,同时提供了良好的面向对象封装和扩展性。你可以根据具体需求进行进一步的定制和优化。
附录1. 轨迹跟踪实现参考示例
下面按步进仿真方式,使轨迹跟踪模型能够实时跟踪生成的期望轨迹。核心思想是:在每个仿真时间步内,根据当前 UAV 状态与期望轨迹(由上一个模块生成)的参考点计算误差,然后利用控制器(例如 PID、纯跟踪算法 Pure Pursuit、MPC 等)计算控制指令,再利用 UAV 的运动学或动力学模型对状态进行更新。下面给出一个简单示例,说明如何实现这一流程。
1. 按步进仿真的基本流程
初始化
- 定义仿真参数:总仿真时间 $T$ 和步长 $dt$;
- 初始化 UAV 当前状态(位置、航向角、速度等);
- 导入或生成参考轨迹(生成的队形轨迹),通常为一系列带时间标签的轨迹点。
主仿真循环
在每个时间步 $t$ 内,执行以下步骤:- (a)查找参考点
根据当前仿真时间 $t$ 从参考轨迹中提取或插值得到期望状态(位置、航向角等)。 - (b)计算误差
对比 UAV 当前状态和参考状态,计算位置误差、航向误差等。 - (c)控制器计算
将误差输入控制器,生成控制命令(例如航向角速率、油门指令或其他控制变量)。 - (d)状态更新
根据 UAV 的运动学/动力学模型(例如简单的 kinematic 模型或更复杂的动力学模型),使用离散积分方法(如欧拉积分或 Runge-Kutta 方法)更新 UAV 的状态。 - (e)记录数据
保存 UAV 当前状态、控制命令以及误差信息,便于后续分析和可视化。
- (a)查找参考点
仿真结束
当仿真时间达到预设时间后结束,并输出仿真数据或轨迹图以便评估跟踪效果。
2. ChatGPT o3-mini-high 示例代码(C++)
下面给出一个简化的 C++ 示例代码,该代码展示了如何在仿真主循环中让一个简单的 PID 跟踪控制器跟踪参考轨迹。注意:代码中的 UAV 动力学模型采用简单的平面运动 kinematic 模型,实际应用中可能需要更复杂的模型。
#include <iostream>
#include <vector>
#include <cmath>
// -------------------- 数据结构定义 --------------------
// UAV 状态(位置、航向角、速度)
struct UAVState {
double x;
double y;
double theta; // 航向角(弧度)
double v; // 速度(假设保持恒定或可控制)
};
// 期望轨迹点(含时间标签)
struct TrajectoryPoint {
double t; // 时间
double x;
double y;
double theta; // 期望航向角(可选)
};
// -------------------- PID 控制器 --------------------
class PIDController {
public:
PIDController(double kp, double ki, double kd)
: kp_(kp), ki_(ki), kd_(kd), prevError_(0.0), integral_(0.0) {}
// 输入误差,输出控制命令(例如:航向角速率或角度修正)
double compute(double error, double dt) {
integral_ += error * dt;
double derivative = (error - prevError_) / dt;
prevError_ = error;
return kp_ * error + ki_ * integral_ + kd_ * derivative;
}
private:
double kp_, ki_, kd_;
double prevError_;
double integral_;
};
// -------------------- 辅助函数 --------------------
// 将角度差归一化到 [-pi, pi]
double normalizeAngle(double angle) {
while (angle > M_PI)
angle -= 2 * M_PI;
while (angle < -M_PI)
angle += 2 * M_PI;
return angle;
}
// 根据当前 UAV 状态和期望轨迹点,计算航向角误差
double computeHeadingError(const UAVState& current, const TrajectoryPoint& desired) {
// 计算从当前点指向参考点的角度
double desiredTheta = std::atan2(desired.y - current.y, desired.x - current.x);
double error = normalizeAngle(desiredTheta - current.theta);
return error;
}
// 简单的 UAV 状态更新函数(基于 kinematic 模型)
UAVState updateState(const UAVState& current, double controlCommand, double dt) {
UAVState next = current;
// 假设 controlCommand 为航向角速率控制输入
next.theta += controlCommand * dt;
next.theta = normalizeAngle(next.theta);
next.x += current.v * std::cos(next.theta) * dt;
next.y += current.v * std::sin(next.theta) * dt;
return next;
}
// -------------------- 主仿真程序 --------------------
int main() {
// 1. 初始化仿真参数
double simulationTime = 10.0; // 总仿真时间(秒)
double dt = 0.1; // 时间步长(秒)
double currentTime = 0.0;
// 2. 初始化 UAV 状态
UAVState uav = {0.0, 0.0, 0.0, 5.0}; // 初始位置 (0,0), 航向 0, 速度 5
// 3. 定义参考轨迹(生成的轨迹)——示例数据
std::vector<TrajectoryPoint> refTrajectory = {
{0.0, 0.0, 0.0, 0.0},
{2.0, 2.0, 2.0, 0.0},
{4.0, 4.0, 4.0, 0.0},
{6.0, 6.0, 6.0, 0.0},
{8.0, 8.0, 8.0, 0.0},
{10.0, 10.0, 10.0, 0.0}
};
// 4. 初始化 PID 控制器参数(调试时根据需要调整)
PIDController pid(1.0, 0.0, 0.1);
// 5. 仿真主循环(按步进运行)
while (currentTime <= simulationTime) {
// 5.1 根据当前时间获取参考轨迹点
// 此处简单选取离当前时间最近的点(可改为线性插值)
TrajectoryPoint desired = refTrajectory.back();
for (const auto& point : refTrajectory) {
if (point.t >= currentTime) {
desired = point;
break;
}
}
// 5.2 计算跟踪误差(这里以航向角误差为例)
double headingError = computeHeadingError(uav, desired);
// 5.3 控制器计算,输出控制命令(例如航向角速率)
double controlCommand = pid.compute(headingError, dt);
// 5.4 更新 UAV 状态(状态更新函数可以根据 UAV 动力学进行扩展)
uav = updateState(uav, controlCommand, dt);
// 5.5 输出当前状态与参考信息,便于观察跟踪效果
std::cout << "Time: " << currentTime
<< " | UAV: (" << uav.x << ", " << uav.y << ", " << uav.theta << ")"
<< " | Ref: (" << desired.x << ", " << desired.y << ")"
<< " | Heading Error: " << headingError
<< std::endl;
// 5.6 更新时间
currentTime += dt;
}
return 0;
}代码说明
数据结构
UAVState定义了无人机当前的状态信息;TrajectoryPoint包含参考轨迹点和对应的时间标签(实际应用中可以更精细,如包含速度、加速度等)。
控制器部分
- 使用一个简单的 PID 控制器计算航向角误差,从而生成控制指令。
- 这里的误差采用当前 UAV 指向参考点的期望航向角与实际航向角的差值。
状态更新
updateState函数基于简单的平面运动 kinematic 模型,利用欧拉积分方法更新状态。- 在实际应用中,可根据 UAV 的动力学特性采用更精确的积分方法。
仿真循环
- 在每个时间步中,从参考轨迹中找到当前时间对应的期望状态(这里采用最简单的方法,实际可采用插值);
- 计算误差、得到控制命令,再更新状态。整个过程按 dt 进行迭代,完成仿真。
实际集成注意事项
轨迹插值
如果生成的参考轨迹时间间隔较大,建议采用线性或样条插值,获得更平滑的期望状态。控制器选择
根据 UAV 的动力学和任务需求,可以选择更为复杂的控制器(如 MPC、纯跟踪算法 Pure Pursuit、Backstepping 等)。模型匹配
跟踪模型应与 UAV 实际动态特性相匹配,在仿真中可能需要考虑加速度、侧向动力学等因素。实时性
当将该模型集成到实际无人机控制系统时,要确保控制器计算和状态更新能够满足实时要求。
通过以上步骤和示例代码,可以在仿真程序中,使轨迹跟踪模型实时跟踪生成的参考轨迹,从而验证和优化 UAV 队形飞行控制策略。
3. Claude 3.5 Sonnet 示例代码(C++)
以下为Claude 3.5 Sonnet生成的的轨迹跟踪控制器C++代码,用于仿真环境中跟踪上述生成的编队轨迹。
// trajectory_tracking.hpp
#pragma once
#include <vector>
#include <cmath>
#include <Eigen/Dense>
#include "formation_control.hpp" // 引用之前的编队控制代码
namespace uav {
// UAV动力学模型参数
struct UAVDynamicsParams {
double max_velocity; // 最大速度
double min_velocity; // 最小速度
double max_acceleration; // 最大加速度
double max_yaw_rate; // 最大偏航角速率
double control_frequency; // 控制频率 (Hz)
UAVDynamicsParams(
double maxV = 30.0,
double minV = 15.0,
double maxA = 5.0,
double maxYaw = 0.5,
double freq = 50.0
) : max_velocity(maxV),
min_velocity(minV),
max_acceleration(maxA),
max_yaw_rate(maxYaw),
control_frequency(freq) {}
};
// 轨迹跟踪控制器
class TrajectoryTracker {
public:
TrajectoryTracker(const UAVDynamicsParams& params)
: dynamics_params(params)
, dt(1.0 / params.control_frequency)
, current_index(0) {
// PID控制器增益
pid_gains = {
2.0, // Kp for heading
0.1, // Ki for heading
0.5, // Kd for heading
1.0, // Kp for velocity
0.1, // Ki for velocity
0.2 // Kd for velocity
};
}
// 设置目标轨迹
void setTrajectory(const std::vector<UAVState>& trajectory) {
reference_trajectory = trajectory;
current_index = 0;
resetPIDErrors();
}
// 更新UAV状态(单步仿真)
UAVState updateState(const UAVState& current_state) {
if (current_index >= reference_trajectory.size()) {
return current_state; // 轨迹已完成
}
// 获取参考状态
const UAVState& reference = reference_trajectory[current_index];
// 计算误差
double dx = reference.position.x - current_state.position.x;
double dy = reference.position.y - current_state.position.y;
double distance_error = std::sqrt(dx * dx + dy * dy);
// 计算期望航向角
double desired_heading = std::atan2(dy, dx);
double heading_error = normalizeAngle(desired_heading - current_state.heading);
// 更新PID误差
updatePIDErrors(heading_error, distance_error);
// 计算控制输入
double yaw_rate = computeHeadingControl();
double velocity = computeVelocityControl(distance_error);
// 应用动力学约束
yaw_rate = std::clamp(yaw_rate,
-dynamics_params.max_yaw_rate,
dynamics_params.max_yaw_rate);
velocity = std::clamp(velocity,
dynamics_params.min_velocity,
dynamics_params.max_velocity);
// 更新状态
UAVState new_state;
new_state.heading = normalizeAngle(
current_state.heading + yaw_rate * dt);
new_state.velocity = velocity;
new_state.position.x = current_state.position.x +
velocity * std::cos(new_state.heading) * dt;
new_state.position.y = current_state.position.y +
velocity * std::sin(new_state.heading) * dt;
// 检查是否需要切换到下一个轨迹点
if (distance_error < 1.0) { // 1.0米阈值
current_index++;
}
return new_state;
}
private:
UAVDynamicsParams dynamics_params;
std::vector<UAVState> reference_trajectory;
std::vector<double> pid_gains; // [Kp_h, Ki_h, Kd_h, Kp_v, Ki_v, Kd_v]
double dt;
size_t current_index;
// PID控制器状态
struct {
double heading_error_sum = 0;
double heading_error_prev = 0;
double velocity_error_sum = 0;
double velocity_error_prev = 0;
} pid_state;
void resetPIDErrors() {
pid_state = {};
}
void updatePIDErrors(double heading_error, double distance_error) {
pid_state.heading_error_sum += heading_error * dt;
pid_state.velocity_error_sum += distance_error * dt;
}
double computeHeadingControl() {
double p_term = pid_gains[0] * normalizeAngle(
reference_trajectory[current_index].heading -
pid_state.heading_error_prev);
double i_term = pid_gains[1] * pid_state.heading_error_sum;
double d_term = pid_gains[2] * (normalizeAngle(
reference_trajectory[current_index].heading -
pid_state.heading_error_prev) / dt);
return p_term + i_term + d_term;
}
double computeVelocityControl(double distance_error) {
double p_term = pid_gains[3] * distance_error;
double i_term = pid_gains[4] * pid_state.velocity_error_sum;
double d_term = pid_gains[5] *
(distance_error - pid_state.velocity_error_prev) / dt;
return reference_trajectory[current_index].velocity +
p_term + i_term + d_term;
}
// 将角度归一化到[-π, π]范围
static double normalizeAngle(double angle) {
while (angle > M_PI) angle -= 2 * M_PI;
while (angle < -M_PI) angle += 2 * M_PI;
return angle;
}
};
// 仿真管理器
class SimulationManager {
public:
SimulationManager(
const UAVDynamicsParams& params,
const std::vector<UAVState>& leader_trajectory,
int num_followers)
: dynamics_params(params)
, num_uavs(num_followers + 1) // 包括领航机
{
// 创建编队控制器
auto formation_params = std::make_shared<LineFormationParams>(5.0);
auto smoother = std::make_shared<SimpleSmoothing>();
formation_controller = std::make_unique<FormationController>(
FormationType::LINE,
formation_params,
smoother
);
// 生成编队轨迹
auto formation_trajectories =
formation_controller->generateFormationTrajectories(
leader_trajectory,
num_followers
);
// 为每个UAV创建轨迹跟踪器
trackers.push_back(std::make_unique<TrajectoryTracker>(params));
trackers[0]->setTrajectory(leader_trajectory);
for (int i = 1; i <= num_followers; ++i) {
trackers.push_back(std::make_unique<TrajectoryTracker>(params));
trackers[i]->setTrajectory(formation_trajectories[i]);
}
// 初始化UAV状态
current_states.resize(num_uavs);
for (int i = 0; i < num_uavs; ++i) {
if (i == 0) {
current_states[i] = leader_trajectory[0];
} else {
current_states[i] = formation_trajectories[i][0];
}
}
}
// 单步仿真
void stepSimulation() {
for (int i = 0; i < num_uavs; ++i) {
current_states[i] = trackers[i]->updateState(current_states[i]);
}
}
// 获取当前所有UAV状态
const std::vector<UAVState>& getCurrentStates() const {
return current_states;
}
private:
UAVDynamicsParams dynamics_params;
int num_uavs;
std::unique_ptr<FormationController> formation_controller;
std::vector<std::unique_ptr<TrajectoryTracker>> trackers;
std::vector<UAVState> current_states;
};
} // namespace uav
代码说明
这个实现提供了完整的轨迹跟踪功能。主要包含以下组件:
TrajectoryTracker类:
- 实现PID控制的轨迹跟踪器
- 考虑了无人机动力学约束
- 提供航向角和速度控制
- 支持实时状态更新
SimulationManager类:
- 管理整个仿真过程
- 集成编队控制和轨迹跟踪
- 提供统一的仿真步进接口
使用示例
using namespace uav;
// 创建动力学参数
UAVDynamicsParams params(
30.0, // 最大速度 (m/s)
15.0, // 最小速度 (m/s)
5.0, // 最大加速度 (m/s^2)
0.5, // 最大偏航角速率 (rad/s)
50.0 // 控制频率 (Hz)
);
// 创建领航机轨迹
std::vector<UAVState> leader_trajectory = {
UAVState(Point2D(0, 0), 0, 20),
UAVState(Point2D(100, 0), 0, 20),
UAVState(Point2D(200, 50), M_PI/4, 20),
// ... 更多轨迹点
};
// 创建仿真管理器
SimulationManager sim(params, leader_trajectory, 3); // 3个跟随者
// 仿真主循环
double sim_time = 0.0;
double sim_duration = 100.0; // 仿真100秒
while (sim_time < sim_duration) {
// 更新仿真
sim.stepSimulation();
// 获取当前状态
const auto& states = sim.getCurrentStates();
// 可以在这里添加可视化或数据记录代码
for (const auto& state : states) {
std::cout << "Position: (" << state.position.x << ", "
<< state.position.y << ")" << std::endl;
}
sim_time += 1.0 / params.control_frequency;
}该实现的特点:
模块化设计:
- 轨迹跟踪和仿真管理分离
- 便于集成到更大的系统中
完整的控制流程:
- 包含PID控制器
- 考虑动力学约束
- 支持连续轨迹跟踪
可扩展性:
- 可以轻松添加新的控制算法
- 支持不同的编队形式
- 可以集成其他动力学模型
仿真友好:
- 支持步进式仿真
- 便于数据记录和可视化
- 支持多机协同仿真
要根据具体需求使用这个代码,你可能需要:
- 调整PID控制器参数
- 添加具体的可视化代码
- 根据实际无人机性能修改动力学参数
- 添加数据记录和分析功能
附录2. 阵型过渡过程中需注意的问题
在阵型变换过程中,要实现高效过渡并尽量避免碰撞,通常需要从规划、协调、以及实时调整等多个层面考虑。
在阵型变换过程中,为了高效实现过渡并尽量避免碰撞,建议采取以下综合策略:
- 最优任务分配:合理分配每架 UAV 的目标位置,避免路径交叉。
- 平滑、时间参数化的轨迹规划:利用曲线规划方法生成连续且满足 UAV 动力学要求的轨迹,并在时间上合理错开 UAV 的通过时刻。
- 碰撞约束与局部避碰:在全局规划中融入碰撞约束,并设置局部避碰机制应对意外情况。
- 分布式协调与实时反馈:通过 UAV 间信息共享和实时控制,确保在过渡过程中各 UAV 能够协同调整,维持安全距离。
采用上述方法可以有效降低阵型变换过程中 UAV 路径交叉和碰撞的风险,同时兼顾变换效率,适应动态变化的任务需求。
1. 任务分配与目标匹配
目标分配优化
在变换开始前,可以采用最优匹配算法(如匈牙利算法)为每架 UAV 分配目标位置,确保总行程最短且避免交叉路径。这样可以减少 UAV 间路径交叉和冲突的可能性。匈牙利分配算法C++代码实现
考虑队形间相对顺序
保持 UAV 在队形内的相对顺序,减少路径交叉。例如,可以固定部分 UAV 的相对位置,仅让少数 UAV 调整位置,从而降低整体碰撞风险。在阵型变换开始前,为每架UAV(无人机)分配最优的目标位置是确保变换过程高效、安全的关键一步。可以采用**匈牙利算法(Hungarian Algorithm)**或类似的分配算法来解决这个问题,以实现总航程最短。这种最优分配本身就能显著减少路径交叉的可能性。
核心步骤:最优匹配与分配
最优匹配的目标是找到一个分配方案,使得所有无人机从初始位置到目标位置的“成本”之和最小。在这里,“成本”通常指飞行距离。
1. 构建成本矩阵 (Cost Matrix)
首先,需要构建一个成本矩阵 $C$。如果有 $N$ 架无人机和 $N$ 个目标槽位,这个矩阵将是 $N\times N$ 的:
- 矩阵的行 $i$ 代表第 $i$ 架无人机。
- 矩阵的列 $j$ 代表第 $j$ 个目标槽位。
- 矩阵中的元素 $C_{ij}$ 是第 $i$ 架无人机从其当前位置 $p_i^{\text{init}}$ 飞到第 $j$ 个目标位置 $p_j^{\text{goal}}$ 的欧几里得距离。 $C_{ij} = | p_i^{\text{init}} - p_j^{\text{goal}} |$
2. 应用匈牙利算法
匈牙利算法是一种用于解决分配问题(Assignment Problem)的组合优化算法。将上述成本矩阵作为输入,该算法能够高效地找到一个最优的“指派”方案,即为每一行(无人机)选择唯一的一列(目标位置),使得所选元素(距离)之和最小。
- 输入:$N\times N$ 的成本矩阵。
- 输出:一个最优的匹配对集合,例如
{(UAV_1, Goal_3), (UAV_2, Goal_1), ...},它明确了每架无人机应该飞往哪个具体的目标槽位。 - 保证:该算法找到的分配方案确保了所有无人机移动的总距离之和是全局最小的。
3. 分配结果
算法运行完毕后,每架无人机就得到了一个唯一且最优的目标位置。这个分配方案是后续轨迹规划的输入。
为何最优分配能减少路径交叉?
虽然最小化总距离并不直接等同于“无碰撞”,但它在很大程度上减少了路径交叉和冲突的概率,原因如下:
- 避免“长距离穿越”:该算法倾向于将无人机分配给离它最近的可用目标位置。这自然地避免了无人机需要长距离飞越整个编队去抵达一个遥远的目标点的情况。长距离穿越是导致路径交叉和冲突最主要的原因之一。
- 维持邻近关系:如果初始阵型和目标阵型在空间上具有一定的相似性(例如,从一个紧凑的队形变为另一个紧凑的队形),算法会倾向于保持无人机之间的相对顺序。原来在编队左侧的无人机,很大概率会被分配到目标阵型中同样位于左侧的位置。这种“局部性”分配使得无人机群的移动更有序,减少了混乱的交叉移动。
补充策略:确保安全无冲突
尽管匈牙利算法能从宏观上优化路径,但在复杂的阵型变换或密集的编队中,仍需结合其他策略来确保万无一失:
- 优先级分配 (Priority Assignment):对于必须穿越中心区域的少数无人机,可以为它们设置不同的飞行高度层或分配不同的通行优先级和时间窗口,以在时间和空间上错开它们的路径。
- 协同路径规划 (Cooperative Pathfinding):在完成目标分配后,可以使用像**基于冲突的搜索(Conflict-Based Search, CBS)或安全间隔梯形速度剖面(Safe Interval-based Trapezoidal Velocity Profiles)**等算法,对所有无人机的轨迹进行统一规划。这些算法可以检测并解决潜在的路径冲突点。
- 动态避障:为每架无人机配备一个实时的、局部的避障系统(如人工势场法或速度障碍法),作为最后一道防线。当有意外的冲突风险时,该系统可以微调无人机的轨迹以避免碰撞。
总结
在阵型变换前,采用最优匹配算法的流程如下:
- 构建成本矩阵:计算每架无人机到每个可能目标位置的距离。
- 运行匈牙利算法:求解该成本矩阵,获得总行程最短的最优分配方案。
- 分配目标:根据算法结果,为每架无人机指派其唯一的目标位置。
- 结合补充策略:将此最优分配作为输入,进行后续的协同轨迹规划和动态避障,以完全消除潜在的路径冲突。
通过这种“全局最优分配 + 局部路径协调”的组合策略,可以最大限度地确保阵型变换过程的平顺、高效与安全。
2. 过渡轨迹生成
平滑轨迹规划
利用样条曲线、Bézier 曲线或者多项式轨迹规划方法,在初始和目标位置之间生成连续、光滑的过渡轨迹。平滑轨迹有助于满足 UAV 的动力学约束(如最小转弯半径)并减少急剧变换。时间参数化
为每个 UAV 的过渡轨迹增加时间标记,使得 UAV 在不同时间段通过各自的过渡区域。通过时间同步控制,协调各 UAV 的速度和加速度,降低在同一区域同时出现的风险。分段规划与缓冲区域
可以将过渡过程划分为多个阶段,每个阶段先完成局部队形调整,且在关键转折点设置缓冲区域,给 UAV 足够的时间和空间避开临近 UAV。
3. 碰撞避免策略
优化中加入碰撞约束
在轨迹规划的优化问题中,引入 UAV 间最小安全距离作为约束条件。利用诸如序列二次规划(SQP)、模型预测控制(MPC)等方法,将碰撞约束融入全局轨迹规划中,生成满足动态约束且避免碰撞的轨迹。局部避碰机制
在 UAV 实际飞行中,可实时监测 UAV 间距离,当检测到潜在碰撞风险时,触发局部避碰策略(如小幅偏离、减速或暂停),保证 UAV 在短时间内不进入危险区域。
这种局部避碰可以基于传感器反馈和简单的控制规则实现,也可以采用基于人工势场的方法进行动态调整。分布式协调规划
每架 UAV 根据自身状态和邻近 UAV 的信息进行局部轨迹调整,并通过通信协调形成一致的全局变换。分布式方法能提高鲁棒性和响应速度,特别适用于大规模 UAV 队形。
4. 在线调整与反馈控制
实时反馈机制
在 UAV 跟踪预定过渡轨迹时,利用实时状态反馈对轨迹进行动态修正。结合预测模型,当检测到轨迹偏离或潜在碰撞时,及时调整控制命令以恢复安全状态。速度与加速度调控
根据过渡区域内 UAV 密度及环境风险,适时降低速度或调整加速度,为轨迹变换留出更多冗余时间,降低因速度过快导致的碰撞风险。
附录3. 阵型变换过程中保证跟踪前方的轨迹点
通过在轨迹规划阶段设计正向偏置,并在 UAV 跟踪控制中采用前瞻参考点选择算法(结合距离和几何判断),可以有效保证阵型过渡过程中每架固定翼 UAV 的期望轨迹点始终位于前进方向前,从而满足固定翼 UAV 的飞行动态要求,并降低因目标点选择不当引起的跟踪误差或碰撞风险。
下面给出的实施方案,旨在确保在阵型过渡过程中,每架固定翼 UAV 的期望轨迹点始终位于其前进方向前,而不是后方。方案分为以下几个步骤:
1 总体思路
轨迹规划时的正向设计
- 生成平滑过渡轨迹:在生成从初始(横排)到目标(楔形)状态的平滑轨迹时,采用局部坐标系,其中 x 轴代表 UAV 前进方向,保证轨迹点在局部坐标系中 x 坐标单调递增。
- 预留前向偏置:在规划过程中,对每个 UAV 的轨迹施加正向偏置,确保轨迹整体延伸于 UAV 前方。
前瞻(Lookahead)参考点选择
- 前瞻距离设定:定义一个合适的前瞻距离 $ L $(例如 10~20 米),保证 UAV 跟踪时总是选择距离当前位置大于 $ L $ 的参考点。
- 几何判断:在实时跟踪时,利用当前 UAV 状态计算其前进方向(通常通过当前航向角得到前向单位向量 $\vec{v}=(\cos\theta,\sin\theta)$),然后从预先规划好的轨迹中选取满足:
- 距离当前 UAV 位置大于 $ L $,以及
- 向量 $ \vec{w}= $(候选点位置 $-$ UAV 当前位置)的点积 $ \vec{v}\cdot\vec{w} > 0 $(确保候选点在 UAV 前方)
的轨迹点作为参考点。
实时检测与调整
- 动态监控:在每个仿真时间步内,实时检测当前参考点是否满足“在前方”条件;
- 调整策略:若发现参考点因 UAV 状态变化而落入后方,则重新从轨迹中搜索合适的前瞻参考点,或对轨迹做局部插值修正。
2. 具体实施步骤
2.1 轨迹规划阶段
规划平滑曲线
利用样条曲线、Bézier 曲线或多项式插值技术,在 UAV 的局部坐标系(x 为前进方向)中生成从初始点到目标点的平滑轨迹,确保每个轨迹点的 x 分量单调增加。这样在全局转换后,轨迹自然呈现出沿 UAV 前进方向延伸的特性。全局坐标转换
$$ P_{\text{global}} = P_{\text{UAV}} + R(\theta) \cdot P_{\text{local}}, $$
在每个时刻,将局部轨迹点转换为全局坐标:其中 $R(\theta)$ 为旋转矩阵,将局部 x 轴对齐到 UAV 当前航向。
2.2 跟踪阶段(实时选择参考点)
在仿真或实际飞行中,每个 UAV 按如下步骤进行实时参考点选择:
获取 UAV 当前状态
包括位置 $P$ 和航向角 $\theta$。计算 UAV 前进方向向量
$$ \vec{v} = (\cos\theta,\, \sin\theta). $$
设:沿轨迹搜索候选点
遍历预先生成的轨迹点集合,对每个候选点 $ Q $:计算向量差:
$$ \vec{w} = Q - P. $$计算距离 $ d = \|\vec{w}\| $ 和点积 $ \vec{v}\cdot\vec{w} $。
如果满足:
- $ d \ge L $(满足前瞻距离),
- 且 $ \vec{v}\cdot\vec{w} > 0 $(确保候选点在 UAV 前方),
则将 $ Q $ 作为合适的参考点。
选择最优候选点
若有多个满足条件的点,可选距离刚好大于 $ L $ 的第一个点,或者通过插值方式获得一个平滑的目标参考点。实时反馈与控制
参考点确定后,将其作为期望位置传给跟踪控制器,进行误差计算和控制指令生成。若检测到当前参考点不再满足条件(例如 UAV 误差较大导致参考点在后方),则立即重新选择或动态更新。
3. 示例实现(C++伪代码)
下面给出一个简单的 C++ 伪代码,说明如何从预先生成的轨迹中选取前方参考点:
#include <iostream>
#include <vector>
#include <cmath>
// 定义二维点结构体
struct Point {
double x;
double y;
};
// 定义 UAV 状态结构体
struct UAVState {
double x;
double y;
double theta; // 航向角(弧度)
};
// 计算两点之间欧氏距离
double distance(const Point& p1, const Point& p2) {
return std::sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y));
}
// 选择参考点函数:从轨迹中选择一个既满足前瞻距离又在前方的点
Point selectReferencePoint(const UAVState& current, const std::vector<Point>& trajectory, double lookaheadDistance) {
// UAV 当前位置
Point currentPos{ current.x, current.y };
// 计算 UAV 的前向单位向量
Point forward{ std::cos(current.theta), std::sin(current.theta) };
// 遍历轨迹点,选择第一个满足条件的点
for (const auto& candidate : trajectory) {
// 计算候选点相对于当前的位置差
Point diff { candidate.x - currentPos.x, candidate.y - currentPos.y };
double d = distance(candidate, currentPos);
// 计算点积,判断候选点是否在前方
double dot = forward.x * diff.x + forward.y * diff.y;
if (d >= lookaheadDistance && dot > 0) {
// 找到合适的参考点
return candidate;
}
}
// 如果没有找到合适的点,返回轨迹末尾点(或根据实际需求进行其他处理)
return trajectory.back();
}
// 示例主函数:演示在每个时间步如何选择参考点
int main() {
// 假设已生成的平滑过渡轨迹(全局坐标),可以通过规划模块获得
std::vector<Point> trajectory = {
{10, 5}, {20, 10}, {30, 15}, {40, 20}, {50, 25}
};
// 仿真参数
double lookaheadDistance = 10.0; // 前瞻距离
// 模拟 UAV 初始状态
UAVState uav = {0, 0, 0}; // 初始位置 (0,0),航向 0 弧度(正向 x 轴)
// 仿真循环(这里只做简单模拟)
for (int step = 0; step < 10; ++step) {
// 假设 UAV 状态由控制器更新,这里做简单线性前进模拟
uav.x += 5; // 每步前进 5 单位
// 例如保持航向不变,实际可通过控制器调整
// 选择参考点
Point refPoint = selectReferencePoint(uav, trajectory, lookaheadDistance);
// 输出当前状态和参考点信息
std::cout << "Step " << step << " | UAV Position: (" << uav.x << ", " << uav.y << ")"
<< " | Reference Point: (" << refPoint.x << ", " << refPoint.y << ")\n";
// 此处调用控制器:计算误差、生成控制命令,并更新 UAV 状态
// ...
}
return 0;
}代码说明
- 轨迹生成模块:应保证规划出的轨迹在 UAV 局部坐标系中 x 轴单调递增,这样全局转换后,大部分轨迹点自然在 UAV 前方。
- 前瞻策略:在
selectReferencePoint函数中,通过距离和点积判断确保选择的点位于 UAV 前方且满足前瞻距离要求。 - 实时更新:在仿真循环或飞行控制系统中,每个控制周期都调用参考点选择函数,确保 UAV 始终获取合适的期望轨迹点。
附录4. 移动平均滤波器
移动平均滤波器(Moving Average Filter, MA Filter)是一种简单而常用的数字滤波方法,其主要原理在于对连续的数值序列进行局部平均,从而减少数据中的随机噪声和高频成分,起到了低通滤波和信号平滑的作用,保留信号的低频趋势。它简单高效,易于实现,适用于实时数据处理,但需要注意信号延迟和可能的过度平滑问题。
1. 基本原理
数据平滑:
$$ y(n) = \frac{1}{N}\sum_{k=-M}^{M} x(n+k) \quad \text{其中} \quad N=2M+1, $$
移动平均滤波器通过计算当前数据点及其相邻数据点的平均值,平滑掉数据中的短期波动。例如,给定一个离散时间序列 $ \{x(n)\} $,如果采用窗口大小为 $ N $(一般为奇数)的移动平均滤波器,则滤波后的输出 $ y(n) $ 通常定义为:也可以采用只考虑过去的 $ N $ 个数据点的形式:
$$ y(n) = \frac{1}{N}\sum_{k=0}^{N-1} x(n-k). $$这种方法通过对局部窗口内的多个数据求平均,从而抑制随机噪声和突变,使得输出信号更加平滑和连续。
低通滤波:
移动平均滤波器实际上是一种低通滤波器,其频率响应类似于一个 sinc 函数。低频成分(信号的主要趋势)能够较好地保留,而高频成分(噪声或快速波动)则被衰减掉。这是因为相邻数据中的高频成分在平均过程中相互抵消,从而达到平滑效果。
2. 数学模型和频率响应
数学模型:
$$ h(k) = \frac{1}{N}, \quad k = -M, \ldots, M. $$
以对称窗口为例,假设滤波器的冲激响应为:则滤波器的输出为:
$$ y(n) = \sum_{k=-M}^{M} h(k)x(n+k) = \frac{1}{N}\sum_{k=-M}^{M} x(n+k). $$这是一个有限脉冲响应(FIR)滤波器,其所有系数均为常数 $1/N$。
频率响应:
$$ H(e^{j\omega}) = \frac{1}{N}\sum_{k=-M}^{M} e^{-j\omega k} = \frac{1}{N} \cdot \frac{\sin\left(\frac{N\omega}{2}\right)}{\sin\left(\frac{\omega}{2}\right)} e^{-j\omega M}. $$
对该 FIR 滤波器做离散时间傅里叶变换(DTFT),可以得到其频率响应 $ H(e^{j\omega}) $:从中可以看出,其幅度响应呈现出类似 sinc 函数的形状,对低频信号通过较多,而对高频信号则起到衰减作用。
3. windowSize 的选取
在移动平均滤波器中,windowSize(窗口大小)表示在计算每个滤波输出时所包含的连续数据点个数。也就是说,对于一个离散信号,滤波器会在当前位置周围选取一个窗口内的多个样本,然后计算这些样本的平均值作为当前输出值。
windowSize 的含义
平滑程度:
窗口越大,滤波器会整合更多数据点,从而使得输出信号更加平滑,能够更有效地抑制高频噪声。但同时,过大的窗口可能会使得信号细节丢失,导致响应滞后。延迟效应:
由于滤波器需要利用窗口内的数据计算平均值,当窗口较大时,会引入更明显的延迟。这在实时控制中可能是一个需要考虑的问题。频率特性:
移动平均滤波器本质上是一个低通滤波器。窗口大小越大,其低通特性越明显,即能够更强地抑制高频成分;窗口较小时,截止频率较高,滤波效果相对较弱。
windowSize 的取值参考
根据信号特性和噪声水平:
- 如果原始数据噪声较大,可以选择较大的 windowSize 以获得更平滑的输出。
- 如果数据变化较快、需要捕捉细节,则应选用较小的 windowSize。
与采样率有关:
- 采样率较高时,窗口中包含的数据点更多,通常可以适当增大 windowSize 来平滑噪声,但需要注意不要过大以免引起过多延迟。
- 采样率较低时,windowSize 一般取较小值,避免数据过度平均导致信号失真。
经验值建议:
- 对于简单应用,常见的取值为 3、5、7 或 9 等奇数。奇数的好处在于能保证窗口中心对称,减少相位失真。
- 实际应用中,可以先尝试 windowSize = 3 或 5,然后观察滤波后的信号平滑效果与响应速度是否满足需求,再进行调整。
仿真和实验调试:
- 最终的 windowSize 需要结合具体应用进行调试,可以通过仿真或实验获得最佳平衡点。例如,在无人机轨迹平滑中,如果目标是去除噪声并保持足够的响应速度,可以从 windowSize = 3 开始测试,然后根据平滑效果和系统响应延迟逐步调整到合适的值。
总结
- windowSize 表示计算平均值时所包含的数据点数,决定了滤波器的平滑程度与延迟效应。
- 取值需要在信号平滑与动态响应之间取得平衡:
- 较大 windowSize → 平滑效果好、延迟增大、细节可能丢失;
- 较小 windowSize → 平滑效果弱、细节保留、噪声可能未完全滤除。
- 常用经验值为 3、5、7 等,并建议结合采样率和实际系统需求,通过仿真和实验确定最佳值。
根据以上参考和实际场景进行调整,可以获得既满足噪声抑制需求又不会过多引入延迟的滤波效果。
4. 优点与缺点
优点:
- 实现简单:算法只需要对固定窗口内的数据求和并除以窗口长度,计算量小,易于实现。
- 线性相位特性:移动平均滤波器属于对称 FIR 滤波器,因此具有线性相位特性,不会引入相位失真,保持信号波形的形状。
- 实时性好:适合于实时数据处理和在线平滑。
缺点:
- 信号延迟:由于滤波器需要利用未来或过去的数据进行平均,会产生一定的延迟,可能导致响应滞后。
- 频率选择性较差:移动平均滤波器的截止特性较为宽松,不能精确分离某个频段内的信号和噪声,对某些应用场合可能不够理想。
- 平滑过度:如果窗口尺寸选得过大,可能会导致信号细节丢失,过度平滑。
5. 应用场景
移动平均滤波器广泛应用于:
- 信号噪声抑制(例如传感器数据、股票价格平滑)
- 图像处理中的降噪
- 数值数据平滑,提取数据的总体趋势
在无人机轨迹规划中,利用移动平均滤波器对离散规划的轨迹点进行平滑处理,可以消除因离散采样产生的不连续性和抖动,使得轨迹更加平滑且符合固定翼 UAV 的飞行动态要求。
6. C++ 代码示例
下面给出一个完整的 C++ 示例,该示例在之前轨迹生成和前瞻参考点选择的基础上,增加了一个简单的轨迹平滑算法(利用移动平均滤波器)对原始轨迹进行平滑处理,确保生成的轨迹平滑且连续。平滑后的轨迹再用于实时选择前方参考点,保证 UAV 始终跟踪前方轨迹点。
以下代码包含三个部分:
- 数据结构定义及辅助函数(距离计算);
- 平滑算法函数
smoothTrajectory(采用移动平均滤波); - 前瞻参考点选择函数
selectReferencePoint与仿真主循环示例。
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
// ---------------------- 数据结构定义 ----------------------
/// 表示二维点
struct Point {
double x;
double y;
};
/// 表示 UAV 状态(位置及航向角)
struct UAVState {
double x;
double y;
double theta; // 航向角(弧度制)
};
// ---------------------- 辅助函数 ----------------------
/// 计算两点之间的欧氏距离
double distance(const Point& p1, const Point& p2) {
return std::sqrt((p1.x - p2.x) * (p1.x - p2.x) +
(p1.y - p2.y) * (p1.y - p2.y));
}
// ---------------------- 轨迹平滑算法 ----------------------
/*
使用简单的移动平均滤波器对原始轨迹进行平滑处理。
参数 windowSize 为滤波窗口大小(建议取奇数),窗口内所有点的均值作为当前平滑后的点。
*/
std::vector<Point> smoothTrajectory(const std::vector<Point>& trajectory, int windowSize = 5) {
std::vector<Point> smoothed;
int n = trajectory.size();
if (n == 0) return smoothed;
smoothed.resize(n);
int halfWindow = windowSize / 2;
for (int i = 0; i < n; ++i) {
double sumX = 0.0, sumY = 0.0;
int count = 0;
// 计算窗口内点的均值
int start = std::max(0, i - halfWindow);
int end = std::min(n - 1, i + halfWindow);
for (int j = start; j <= end; j++) {
sumX += trajectory[j].x;
sumY += trajectory[j].y;
count++;
}
smoothed[i].x = sumX / count;
smoothed[i].y = sumY / count;
}
return smoothed;
}
// ---------------------- 前瞻参考点选择函数 ----------------------
/*
从给定轨迹中选择一个满足前瞻距离且位于 UAV 当前航向前的点作为参考点。
条件:候选点与 UAV 之间的距离大于 lookaheadDistance,
且候选点相对于 UAV 当前位置在前进方向上(点积大于零)。
*/
Point selectReferencePoint(const UAVState& current, const std::vector<Point>& trajectory, double lookaheadDistance) {
// UAV 当前的位置
Point currentPos { current.x, current.y };
// 计算 UAV 前进方向(单位向量)
Point forward { std::cos(current.theta), std::sin(current.theta) };
// 遍历轨迹点,选择第一个满足条件的候选点
for (const auto& candidate : trajectory) {
Point diff { candidate.x - currentPos.x, candidate.y - currentPos.y };
double d = distance(candidate, currentPos);
double dot = forward.x * diff.x + forward.y * diff.y;
if (d >= lookaheadDistance && dot > 0) {
return candidate;
}
}
// 若未找到合适点,则返回轨迹最后一个点(或根据实际情况进行其他处理)
return trajectory.back();
}
// ---------------------- 主函数 ----------------------
int main() {
// 原始轨迹(例如从横排到楔形阵型转换过程中的规划轨迹,单位为全局坐标)
std::vector<Point> rawTrajectory = {
{10, 5}, {20, 10}, {30, 15}, {40, 20}, {50, 25},
{60, 30}, {70, 35}, {80, 40}, {90, 45}, {100, 50}
};
// 对原始轨迹进行平滑处理(窗口大小可根据实际需要调整)
std::vector<Point> trajectory = smoothTrajectory(rawTrajectory, 3);
// 仿真中设定的前瞻距离
double lookaheadDistance = 10.0;
// 初始化 UAV 状态:初始位置 (0, 0),航向 0 弧度(正向 x 轴)
UAVState uav {0, 0, 0};
// 简单仿真循环:模拟 UAV 沿 x 方向前进,并选择合适的参考点
for (int step = 0; step < 10; ++step) {
// 模拟 UAV 前进:每个时间步沿 x 轴前进 5 个单位(实际系统中由控制器更新状态)
uav.x += 5;
// 选择当前时刻的参考点
Point refPoint = selectReferencePoint(uav, trajectory, lookaheadDistance);
// 输出当前 UAV 状态和选取的参考点
std::cout << "Step " << step << " | UAV Position: (" << uav.x << ", " << uav.y << ")"
<< " | Reference Point: (" << refPoint.x << ", " << refPoint.y << ")\n";
// 此处可调用跟踪控制器,将 refPoint 作为目标进行误差计算和控制指令生成
}
return 0;
}代码说明
轨迹平滑部分
- 函数
smoothTrajectory对输入的原始轨迹进行移动平均平滑,消除因离散采样产生的噪声和不连续性。 - 通过调整
windowSize参数可以控制平滑程度(窗口越大,平滑效果越明显,但会使轨迹滞后)。
- 函数
前瞻参考点选择
- 函数
selectReferencePoint利用 UAV 当前状态和航向,结合平滑后的轨迹数据,选择距离当前 UAV 位置大于lookaheadDistance且位于前进方向上的第一个轨迹点作为目标点。
- 函数
集成与实时控制
- 在主函数中,先对规划的原始轨迹进行平滑处理,再在仿真循环中不断选取参考点并输出(实际系统中应结合 UAV 的状态更新与控制器)。
通过这种方法,不仅能获得平滑连续的过渡轨迹,还能确保在实时跟踪中 UAV 始终参考前方的目标轨迹点,满足固定翼 UAV 的飞行动态要求,并降低碰撞风险。