☰
PX4 中的 TensorFlow Lite Micro (TFLM) 神经网络集成指南
2026/10/2 1:38:20 网站建设 项目流程
  • 嵌入式
  • 物联网
  • 机器人
  • 自动驾驶
  • 智能硬件

【免费下载链接】PX4-Autopilot

PX4 Autopilot Software

项目地址:https://gitcode.com/gh_mirrors/px/PX4-Autopilot
点击查看免费下载

本篇技术指南深入讲解 PX4 如何将 TensorFlow Lite Micro(TFLM)推理库集成进mc_nn_control多旋翼神经网络控制模块:从.tflite模型格式与 C++ 数组转换、xxd转换命令,到 Resolver 算子注册、MicroInterpreter 初始化、输入/输出张量填充与推理调用的完整代码链路。读完本文,你将掌握如何把自行训练的网络替换进 PX4 固件并理解其底层推理机制。

TFLM 与 mc_nn_control 模块概览

PX4 的 MC Neural Networks Control 模块(mc_nn_control,参见 modules_controller)集成了一个基于 TensorFlow Lite Micro (TFLM) 推理库的神经网络。TFLM 是专为嵌入式设备设计的成熟推理库,因此在 PX4 上是一个合适的选择。

mc_nn_control是一个端到端的神经网络控制系统:它订阅 uORB 消息获取状态量,用神经网络计算控制输出,再直接发布到执行机构话题。该示例模块替换了整个控制器结构以及控制分配器,其模块级数据流如下:

模块从位置设定点出发,最终直接输出四路电机指令,绕过了传统的姿态/速率控制器与控制分配链路。神经网络部分的工作全部围绕 TFLM 展开:网络以 C++ 数组形式内嵌在固件中,由 TFLM 的解释器在飞控上完成前向推理。

TFLM 神经网络格式

TFLM 使用其自有的 tflite 格式 作为网络载体。但许多微控制器没有原生文件系统支持,无法在运行时读取.tflite文件,因此通常需要将一个 tflite 文件转换为 C++ 源文件和头文件,直接编译进固件。

mc_nn_control正是采用这种做法:TFLite 神经网络在代码中由control_net.cpp和control_net.hpp两个文件表示。其中:

  • control_net.hpp声明网络数据与大小:
constexpr unsigned int control_net_tflite_size = 15088; extern const unsigned char control_net_tflite[];
  • control_net.cpp以 16 字节对齐的字节数组存放完整的 tflite 模型内容:
alignas(16) const unsigned char control_net_tflite[] = { 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, ...

从control_net.cpp的字节流中可以辨认出serving_default、output_0、args_0以及CONVERSION_METADATA、min_runtime_version("2.20.0-dev20250323")、schema 版本 "2.0.0" 等 TFLite 模型内部结构,印证了这是一个标准的 flatbuffer 序列化 tflite 模型。该模型约 15 KB,相比典型视觉模型小得多,适合直接内嵌进飞控闪存。

获取 tflite 格式的网络

生成.tflite格式网络有很多在线资源。本文示例中的网络是在开源 Aerial Gym Simulator 中训练的;Aerial Gym 自带教程,既支持控制任务也支持基于视觉的导航任务的强化学习(RL)。项目在resources/conversion文件夹中提供了PyTorch -> TFLM的转换代码。

用你自己的神经网络更新 mc_nn_control

如果你训练了自己的网络(例如针对你自己的机架做了系统辨识,详见 mc_neural_network_control 的 "Training your own Network" 一节),可以按以下步骤将其替换进mc_nn_control。

首先,在 Ubuntu 终端中用xxd将.tflite网络转换成一个.cc文件:

xxd -i converted_model.tflite > model_data.cc

xxd -i会生成一个 C 数组(数组名取自文件名)以及对应的长度宏,例如unsigned int converted_model_tflite_len。

然后修改control_net.hpp和control_net.cpp,纳入model_data.cc中的数据:

  • 取.cc文件末尾的网络大小,替换control_net.hpp中的control_net_tflite_size(当前为15088)。
  • 取.cc文件中模型数组的数据,替换control_net.cpp中control_net_tflite[]数组的内容。

完成后重新编译 PX4(mc_nn_control需要与固件一起重新构建),即可运行你自己的网络。

代码详解:control_net.cpp 中的集成代码

本节对照mc_nn_control.cpp中的真实实现,逐段解释 TFLM 集成代码。

算子注册与 Resolver

推理前首先要创建 Resolver,并注册运行神经网络推理所需的算子。这一步在mc_nn_control.cpp顶部的匿名命名空间中完成:

// This number should be the number of operations in the model, like tanh and fully connected using NNControlOpResolver = tflite::MicroMutableOpResolver<3>; TfLiteStatus RegisterOps(NNControlOpResolver &op_resolver) { // Add the operations to you need to the op_resolver TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected()); TF_LITE_ENSURE_STATUS(op_resolver.AddRelu()); TF_LITE_ENSURE_STATUS(op_resolver.AddAdd()); return kTfLiteOk; }

MicroMutableOpResolver<3>尖括号中的数字3代表运行推理所需注册的算子数量。TFLM 提供了相当多受支持的算子(完整列表见micro_mutable_op_resolver.h),但最先进/复杂的算子不在其中——这是嵌入式推理库在功能与代码体积之间的取舍。

在控制示例中,网络是全连接结构,因此使用AddFullyConnected()注册全连接算子;激活函数是 ReLU,所以调用AddRelu();每个神经元上还有偏置项,因此用AddAdd()注册加法算子。如果你的网络包含其他算子(如卷积、池化、tanh),需要在这里相应增加数量并调用对应的注册函数,例如把MicroMutableOpResolver<3>改成更大的数字。

Interpreter(解释器)

在InitializeNetwork()函数中,首先设置从源文件和头文件加载的模型,然后搭建解释器:

int MulticopterNeuralNetworkControl::InitializeNetwork() { // Initialize the neural network const tflite::Model *control_model = ::tflite::GetModel(control_net_tflite); // Set up the interpreter static NNControlOpResolver resolver; if (RegisterOps(resolver) != kTfLiteOk) { PX4_ERR("Failed to register ops"); return -1; } constexpr int kTensorArenaSize = 10 * 1024; static uint8_t tensor_arena[kTensorArenaSize]; _interpreter = new tflite::MicroInterpreter(control_model, resolver, tensor_arena, kTensorArenaSize); if (_interpreter == nullptr) { PX4_ERR("interpreter alloc failed"); return -1; } // Allocate memory for the model's tensors TfLiteStatus allocate_status = _interpreter->AllocateTensors(); ... _input_tensor = _interpreter->input(0); ... return PX4_OK; }

这段代码取自 TFLM 官方文档并有详细说明。关键点是:

  • ::tflite::GetModel(control_net_tflite)直接从内嵌的字节数组解析 flatbuffer 模型,无需文件系统。
  • MicroInterpreter使用静态分配的tensor_arena(张量竞技场,此处为10 * 1024字节)作为模型运行时的内存池,这是 TFLM 在裸机/RTOS 上不依赖动态堆内存的核心机制。若模型变大,需要相应增大该数组。
  • AllocateTensors()为模型的张量分配内存;失败时返回错误并清理解释器。
  • 最终_control_interpreter被设置好,后续通过Invoke()成员函数运行推理;_input_tensor也从_control_interpreter->input(0)取得,用于后续填充输入数据。

输入(Inputs)

_input_tensor在PopulateInputTensor()函数中被填充。其原理是通过->data.f成员数组按索引写入网络要求的输入值。

控制网络使用一个15 元素的一维输入数组,顺序如下(在 mc_neural_network_control 中有详细说明):

  • [0..2]局部位置误差(目标位置 - 当前位置),3 个值
  • [3..8]三维旋转矩阵的前两行,6 个值
  • [9..11]线速度,3 个值
  • [12..14]角速度,3 个值

PopulateInputTensor()从 uORB 话题(vehicle_local_position、vehicle_attitude、vehicle_angular_velocity、trajectory_setpoint)收集数据,并在函数内完成坐标变换:PX4 使用 NED 坐标系,而训练该网络的 Aerial Gym Simulator 使用 ENU 坐标系,因此函数中构造了两个旋转矩阵frame_transf与frame_transf_2,把所有输入从 NED 表示变换到 ENU 表示:

// Creates a 15 element input tensor for the neural network [pos_err(3), lin_vel(3), att(6), ang_vel(3)] ... matrix::Vector3f position_local = matrix::Vector3f(_position.x, _position.y, _position.z); position_local = frame_transf * frame_transf_2 * position_local; ... _input_tensor->data.f[0] = trajectory_setpoint_local(0) - position_local(0); _input_tensor->data.f[1] = trajectory_setpoint_local(1) - position_local(1); _input_tensor->data.f[2] = trajectory_setpoint_local(2) - position_local(2); _input_tensor->data.f[3] = _attitude_local_mat(0, 0); ... _input_tensor->data.f[12] = angular_vel_local(0); _input_tensor->data.f[13] = angular_vel_local(1); _input_tensor->data.f[14] = angular_vel_local(2);

ENU 与 NED 只是旋转表示方式不同(平移差异仅为便于在同一图中显示两者):

如果要换成你自己的网络,只需按照训练时定义的输入约定,在此函数中提供相同的输入即可。输入完全可以按需改变——训练时用什么输入,PX4 里就提供什么输入。

输出(Outputs)

输出的处理方式与输入类似。正确设置输入后,调用Invoke()函数执行推理:

TfLiteStatus invoke_status = _interpreter->Invoke(); ... _output_tensor = _interpreter->output(0); if (_output_tensor == nullptr) { PX4_ERR("Output tensor is null"); return; } // Convert the output tensor to actuator values RescaleActions(); PublishOutput(_output_tensor->data.f);

推理结果通过_control_interpreter->output(0)获取,同样从输出张量的->data.f数组读出。

该控制网络的输出是4 个值,即四个电机的推力。由于 PX4 期望归一化的电机指令而 Aerial Gym Simulator 使用物理量,RescaleActions()函数负责把网络输出转换为电机指令:

  • 将网络输出限制在[-1, 1]区间;
  • 使用参数MC_NN_THRST_COEF(除以 100000)将归一化输出换算为转速 rps(sqrt关系,体现推力正比于转速平方);
  • 再结合MC_NN_MAX_RPM与MC_NN_MIN_RPM把 rpm 映射回归一化的[-1, 1]电机指令区间(a = 0.8的二次整形用于匹配电机的非线性响应)。

最终由PublishOutput(float *command_actions)把 4 路电机指令发布到 ActuatorMotors 话题:

actuator_motors.control[0] = PX4_ISFINITE(command_actions[0]) ? command_actions[0] : NAN; actuator_motors.control[1] = PX4_ISFINITE(command_actions[1]) ? command_actions[1] : NAN; actuator_motors.control[2] = PX4_ISFINITE(command_actions[2]) ? command_actions[2] : NAN; actuator_motors.control[3] = PX4_ISFINITE(command_actions[3]) ? command_actions[3] : NAN; // 其余 control[4..11] 置为 -NAN(未使用)

如果神经网络控制模式反应过激或不够灵敏,可以调节 MC_NN_THRST_COEF 参数(减小以获得更大推力)。

构建集成:把 TFLM 与 mc_nn_control 编入固件

mc_nn_control的构建依赖在src/modules/mc_nn_control/CMakeLists.txt中声明:

px4_add_module( MODULE mc_nn_control MAIN mc_nn_control SRCS mc_nn_control.cpp mc_nn_control.hpp control_net.cpp control_net.hpp MODULE_CONFIG mc_nn_control_params.yaml DEPENDS tensorflow_lite_micro px4_work_queue mathlib ) target_link_libraries(mc_nn_control PRIVATE tensorflow_lite_micro)

TFLM 库本身由src/lib/tensorflow_lite_micro/CMakeLists.txt提供:它通过 git 子模块引入 tflite_micro 源码(px4_add_git_submodule(TARGET git_tflite-micro PATH tflite_micro)),收集tensorflow/lite/micro下的内核与框架源码编译为tensorflow_lite_micro库,且仅在CONFIG_LIB_TFLM=y时生效;其第三方依赖(flatbuffers、ruy、gemmlowp、CMSIS 等)在构建时无需联网下载。mc_nn_control.hpp中对应的头文件包含为:

#include <tflite_micro/tensorflow/lite/micro/micro_mutable_op_resolver.h> #include <tflite_micro/tensorflow/lite/micro/micro_interpreter.h> #include <tflite_micro/tensorflow/lite/schema/schema_generated.h>

在板级配置中启用

模块与 TFLM 库通过 Kconfig 控制,对应配置在src/modules/mc_nn_control/Kconfig与src/lib/tensorflow_lite_micro/Kconfig中定义。要把它加入某个板卡配置,在对应default.px4board文件中加入两行:

CONFIG_LIB_TFLM=y CONFIG_MODULES_MC_NN_CONTROL=y

官方已在多种配置上测试过,可直接本地构建,例如:

make px4_sitl_neural
make px4_fmu-v6c_neural
make mro_pixracerpro_neural

需要注意:该模块要求Ubuntu 24.04 或更新版本(不支持 Ubuntu 22.04)。mc_nn_control模块大约占用 50 KB 闪存,而许多default.px4board配置的闪存已接近占满。要为神经网络控制模块腾出空间,可以移除其他模块(如 FW、rover、VTOL、UUV)的包含语句。

自动启动与参数

模块通过参数MC_NN_EN控制是否开机自动启动——ROMFS/px4fmu_common/init.d/rc.mc_apps启动脚本会检查该参数:

if param compare -s MC_NN_EN 1 then mc_nn_control start fi

相关参数在src/modules/mc_nn_control/mc_nn_control_params.yaml中定义:

参数类型默认值范围说明
MC_NN_ENboolean1-为 true 时神经网络控制在开机时自动启动
MC_NN_MAX_RPMint32220000 ~ 80000用于神经网络输出归一化的电机最大 RPM
MC_NN_MIN_RPMint3210000 ~ 80000用于神经网络输出归一化的电机最小 RPM
MC_NN_THRST_COEFfloat1.20.0 ~ 5.0电机推力系数,用于归一化网络输出(实际使用时除以 100000)
MC_NN_MANL_CTRLboolean1-是否允许用手动控制设定轨迹设定点(修改后需重启)

运行时的推理与调试观测

模块作为一个 WorkItem 挂载在nav_and_controllers工作队列上,以角速度话题vehicle_angular_velocity的更新作为触发源(见_angular_velocity_sub{this, ORB_ID(vehicle_angular_velocity)})。每次触发时,Run()完成:更新订阅 →PopulateInputTensor()填充输入 →_interpreter->Invoke()推理 →RescaleActions()归一化 →PublishOutput()发布actuator_motors。

为了观测推理性能,模块发布了 NeuralControl 调试话题,记录 15 维观测向量、4 维网络输出以及控制器总耗时与推理耗时:

uint64 timestamp # [us] Time since system start float32[15] observation # Observation vector (pos error (3), att (6d), lin vel (3), ang vel (3)) float32[4] network_output # Output from neural network int32 controller_time # [us] Time spent from input to output int32 inference_time # [us] Time spent for NN inference

这些消息归属于日志的debug类别,要在飞行日志中保存它们,需要把debug加入SDLOG_PROFILE参数。

小结

TFLM 在 PX4 中的集成是一条清晰、可复用的链路:训练出.tflite模型 → 用xxd -i转换为 C 数组 → 替换control_net.hpp/control_net.cpp→ 按需调整 Resolver 算子注册数量与类型 → 修改PopulateInputTensor()的输入约定 → 重新编译固件。理解 Resolver、MicroInterpreter、张量竞技场与输入/输出张量这四个核心概念后,你就能把这一端到端神经网络控制方案迁移到自己的机架、任务乃至其他控制器替换场景中。

  • 嵌入式
  • 物联网
  • 机器人
  • 自动驾驶
  • 智能硬件

【免费下载链接】PX4-Autopilot

PX4 Autopilot Software

项目地址:https://gitcode.com/gh_mirrors/px/PX4-Autopilot
点击查看免费下载
上一篇:如何用awesome-python-webapp快速搭建你的第一个Python网站
下一篇:protoc-gen-gorm 开源项目教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询