4、Python环境与仿真框架搭建:安装必要的Python库、搭建基于Python的无人机仿真框架
好,咱们进入第四讲。说实话,很多同学觉得调LQR控制器是核心,环境搭建不过是跑几个pip命令,没什么技术含量。我以前也这么想,直到有一次在项目交付前三天,发现仿真环境和实飞环境对不上,差点把整个项目节奏打乱。嗯,从那以后,我每次都会花半天时间,认认真真把仿真框架搭好。
这一章,咱们就聊聊怎么搭一个靠谱的Python仿真环境。说白了,就是让你的代码跑得起来、跑得对、跑得稳。
4.1 安装必要的Python库
我个人习惯用Anaconda来管理Python环境,这样不同项目之间不会打架。你如果已经装了Python 3.8以上版本,直接用pip也行。
先列一下咱们需要的库:
| 库名 | 版本建议 | 用途 |
|---|---|---|
| numpy | ≥1.21 | 矩阵运算、数值计算 |
| scipy | ≥1.7 | 求解微分方程、插值、优化 |
| matplotlib | ≥3.4 | 数据可视化、轨迹绘制 |
| control | ≥0.9 | LQR求解、系统分析 |
安装命令很简单,打开终端,一行搞定:
pip install numpy scipy matplotlib control
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy scipy matplotlib control,速度会快很多。
安装完成后,可以跑个简单的测试脚本,看看库能不能正常导入:
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import control as ct
print("numpy版本:", np.__version__)
print("scipy版本:", sp.__version__)
print("control版本:", ct.__version__)
print("所有库导入成功!")
如果没报错,恭喜你,环境基础打好了。
4.2 仿真框架的整体结构
搭建仿真框架,说白了就是搭一个「虚拟试飞场」。我一般把它分成三个模块:
- 无人机模型模块:描述四旋翼的动力学和运动学
- 控制器模块:实现LQR控制律
- 仿真主循环模块:驱动整个仿真过程,记录数据
你想想看,这三个模块各司其职,就像工厂里的流水线。模型模块负责「被控对象」,控制器模块负责「决策」,主循环负责「调度」。
下面这张图,是我自己画的一个框架流程图,你可以参考一下:
4.3 搭建无人机模型模块
四旋翼的动力学模型,说白了就是牛顿第二定律加上欧拉方程。我这里用一个简化模型,只考虑位置和速度,忽略姿态动力学——因为咱们先聚焦在LQR的调参上。
模型的状态量我取为:
状态向量 x = [x, y, z, vx, vy, vz]^T
控制输入 u = [ax, ay, az]^T (加速度)
离散化后的状态方程:
x(k+1) = A * x(k) + B * u(k)
其中:
A = [[1, 0, 0, dt, 0, 0],
[0, 1, 0, 0, dt, 0],
[0, 0, 1, 0, 0, dt],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1]]
B = [[0.5*dt^2, 0, 0],
[0, 0.5*dt^2, 0],
[0, 0, 0.5*dt^2],
[dt, 0, 0],
[0, dt, 0],
[0, 0, dt]]
代码实现如下:
import numpy as np
class QuadrotorModel:
def __init__(self, dt=0.01):
self.dt = dt
self.A = np.array([
[1, 0, 0, dt, 0, 0],
[0, 1, 0, 0, dt, 0],
[0, 0, 1, 0, 0, dt],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1]
])
self.B = np.array([
[0.5*dt**2, 0, 0],
[0, 0.5*dt**2, 0],
[0, 0, 0.5*dt**2],
[dt, 0, 0],
[0, dt, 0],
[0, 0, dt]
])
def step(self, state, control):
"""执行一步仿真"""
return self.A @ state + self.B @ control
4.4 搭建LQR控制器模块
LQR控制器的核心,就是求解Riccati方程得到增益矩阵K。control库提供了现成的函数,咱们直接调用就行。
import control as ct
class LQRController:
def __init__(self, A, B, Q, R):
self.A = A
self.B = B
self.Q = Q
self.R = R
# 求解LQR增益
K, S, E = ct.lqr(A, B, Q, R)
self.K = np.array(K)
print("LQR增益矩阵K已求解完成,形状:", self.K.shape)
def compute_control(self, state, ref_state):
"""计算控制量 u = -K * (state - ref_state)"""
error = state - ref_state
u = -self.K @ error
return u
4.5 搭建仿真主循环
主循环就是让时间往前走,每一步都调用模型和控制器,记录数据。我一般这样写:
def run_simulation(model, controller, initial_state, ref_trajectory, steps):
"""
运行仿真
model: 无人机模型
controller: LQR控制器
initial_state: 初始状态
ref_trajectory: 参考轨迹,形状为 (steps, 6)
steps: 仿真步数
"""
state = initial_state.copy()
states = [state.copy()]
controls = []
for k in range(steps):
ref = ref_trajectory[k]
u = controller.compute_control(state, ref)
state = model.step(state, u)
states.append(state.copy())
controls.append(u.copy())
return np.array(states), np.array(controls)
你看,这个主循环其实很简单。但我在项目中遇到过一个问题:如果参考轨迹突然变化太大,控制器会输出很大的控制量,导致仿真发散。所以后来我加了一个限幅:
def compute_control_safe(self, state, ref_state, u_max=10.0):
error = state - ref_state
u = -self.K @ error
# 限幅
u = np.clip(u, -u_max, u_max)
return u
4.6 可视化与调试
仿真跑完了,得看看效果。我习惯用matplotlib画三张图:
- 三维轨迹图:看无人机实际轨迹和参考轨迹的贴合程度
- 位置误差图:看x、y、z三个方向的误差随时间变化
- 控制量图:看三个轴的控制加速度是否合理
代码示例:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def plot_results(states, ref_trajectory, controls):
fig = plt.figure(figsize=(15, 5))
# 三维轨迹
ax1 = fig.add_subplot(131, projection='3d')
ax1.plot(states[:, 0], states[:, 1], states[:, 2], 'b-', label='实际轨迹')
ax1.plot(ref_trajectory[:, 0], ref_trajectory[:, 1], ref_trajectory[:, 2], 'r--', label='参考轨迹')
ax1.set_xlabel('X (m)')
ax1.set_ylabel('Y (m)')
ax1.set_zlabel('Z (m)')
ax1.legend()
# 位置误差
ax2 = fig.add_subplot(132)
error = states[:-1] - ref_trajectory
ax2.plot(error[:, 0], label='X误差')
ax2.plot(error[:, 1], label='Y误差')
ax2.plot(error[:, 2], label='Z误差')
ax2.set_xlabel('时间步')
ax2.set_ylabel('误差 (m)')
ax2.legend()
ax2.grid(True)
# 控制量
ax3 = fig.add_subplot(133)
ax3.plot(controls[:, 0], label='ax')
ax3.plot(controls[:, 1], label='ay')
ax3.plot(controls[:, 2], label='az')
ax3.set_xlabel('时间步')
ax3.set_ylabel('加速度 (m/s²)')
ax3.legend()
ax3.grid(True)
plt.tight_layout()
plt.show()
4.7 完整仿真示例
把上面所有模块组合起来,跑一个完整的仿真:
# 参数设置
dt = 0.01
steps = 1000 # 仿真10秒
# 初始化模型和控制器
model = QuadrotorModel(dt=dt)
Q = np.diag([10, 10, 10, 1, 1, 1]) # 状态权重
R = np.diag([1, 1, 1]) # 控制权重
controller = LQRController(model.A, model.B, Q, R)
# 生成参考轨迹:螺旋上升
t = np.linspace(0, steps*dt, steps)
ref_trajectory = np.zeros((steps, 6))
ref_trajectory[:, 0] = 5 * np.cos(0.5 * t) # x
ref_trajectory[:, 1] = 5 * np.sin(0.5 * t) # y
ref_trajectory[:, 2] = 0.5 * t # z
# 初始状态
initial_state = np.array([5.0, 0.0, 0.0, 0.0, 0.0, 0.0])
# 运行仿真
states, controls = run_simulation(model, controller, initial_state, ref_trajectory, steps)
# 可视化
plot_results(states, ref_trajectory, controls)
嗯,框架搭好了,咱们就可以开始真正的调参之旅了。下一章我会聊聊Q和R矩阵的物理意义,以及怎么根据实际需求来设置它们。不过那是后话了,先把今天的代码跑通再说。