RflySim 视觉与传感器通信协议¶
本文说明 VisionCaptureApi.py 当前实现的视觉传感器数据传输、ROS/ROS 2
转发、CopterSim IMU/Odom 接口和吊舱通信协议。
除特别说明外:
- 多字节数值按 RflySim 当前运行环境的小端格式解释。
SeqID是传感器编号,ROS 话题中的sensor{SeqID}使用该值。TypeID是传感器类型,不等同于SeqID。- ROS 转发由
VisionCaptureApi.py的模块级变量isEnableRosTrans控制, 必须在创建VisionCaptureApi实例前启用。例如先import VisionCaptureApi as vis_api,再设置vis_api.isEnableRosTrans = True。 - UE4 与 UE5 使用相同的 Python 接收接口。UE5 对部分点云使用 GPU
生成,但外部传输仍采用本文所述的
int16点坐标编码。
1. 数据传输¶
1.1 SendProtocol¶
VisionSensorReq.SendProtocol 和 VisionSensorReqNew.SendProtocol 都是由
8 个 uint16 组成的数组。
| 索引 | 含义 |
|---|---|
[0] |
传输模式 |
[1:5] |
目标 IPv4 地址的四个字节 |
[5] |
目标 UDP 端口 |
[6] |
UDP 分片有效载荷大小,通常不超过 60000 字节 |
[7] |
传感器专用功能位;点云中用于选择三通道或四通道线上格式 |
SendProtocol[0] 的取值如下。
| 值 | 传输方式 | 说明 |
|---|---|---|
| 0 | 共享内存 | 原始数据,本机低延迟传输;jsonLoad() 在 Linux 下会默认切换为 UDP |
| 1 | UDP | 图像通常使用 JPEG 等编码数据,点云和结构化传感器使用自定义二进制载荷 |
| 2 | UDP 原始图像 | 图像像素不压缩直传,仅适用于图像类传感器 |
| 3 | UDP PNG | 图像使用 PNG 无损压缩,仅适用于图像类传感器 |
TypeID 5、7、10、20~23、30、31 使用自定义二进制载荷,不应按普通图像 调用视频流模式解码。
1.2 UDP 分片包头¶
UDP 数据帧可能被拆分成多个包。当前接收端根据校验码识别两种包头。
24 字节包头¶
校验码为 1234567890,Python 格式为 4i1d。
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | checksum |
int32 | 固定为 1234567890 |
| 4 | packet_len |
int32 | 当前 UDP 包总长度,包含包头 |
| 8 | packet_seq |
int32 | 当前分片序号,从 0 开始 |
| 12 | packet_count |
int32 | 当前数据帧的分片总数 |
| 16 | timestamp |
float64 | RflySim3D/UE 生成该帧时的仿真时间 |
32 字节包头¶
校验码为 1234567893,Python 格式为 6i1d。
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | checksum |
int32 | 固定为 1234567893 |
| 4 | packet_len |
int32 | 当前 UDP 包总长度,包含包头 |
| 8 | packet_seq |
int32 | 当前分片序号,从 0 开始 |
| 12 | packet_count |
int32 | 当前数据帧的分片总数 |
| 16 | frame_id |
int32 | 数据帧编号 |
| 20 | reserved |
int32 | 保留字段 |
| 24 | timestamp |
float64 | RflySim3D/UE 生成该帧时的仿真时间 |
接收端使用时间戳区分并发数据帧,按照 packet_seq 拼接载荷。实现自定义
接收端时,应同时支持这两种包头。
字节序说明
当前 Python 代码对视觉 UDP 包头使用本机 struct 格式。在平台支持的
x86/x64 小端环境中,上述格式分别为 24 和 32 字节。跨架构实现时应显式
固定小端和字段宽度,不应依赖编译器结构体对齐。
1.3 共享内存头¶
共享内存数据不使用 UDP 分片包头。每个共享内存区域以 9 字节控制信息开头:
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | state |
uint8 | 写入、读取和完成状态 |
| 1 | timestamp |
float64 | 仿真时间戳 |
| 9 | payload |
bytes | 传感器载荷 |
共享内存名称为 RflySim3DImg_{SeqID}。图像为原始像素;点云和结构化
传感器继续使用各 TypeID 对应的载荷格式。
2. 传感器数据¶
2.1 TypeID 索引¶
| TypeID | 传感器 | Python 输出 |
|---|---|---|
| 1 | RGB 相机 | H×W×3 uint8,BGR |
| 2 | 深度相机 | H×W uint16 或 H×W×1 uint16 |
| 3 | 灰度相机 | H×W uint8 或 H×W×1 uint8 |
| 4 | 语义分割相机 | H×W×3 uint8,BGR |
| 5 | 激光测距 | DistanceSensor |
| 7 | 深度转点云 | 点云数组 |
| 8 | 鱼眼相机 | H×W×3 uint8,BGR |
| 9 | 吊舱相机 | H×W×3 uint8,BGR |
| 10 | 光流传感器 | OpticalFlowSensor |
| 20 | 载体/传感器坐标系 LiDAR | 点云数组 |
| 21 | 世界坐标系 LiDAR | 点云数组 |
| 22 | Livox 花瓣扫描 LiDAR | 点云数组 |
| 23 | Livox Mid-360 LiDAR | 点云数组 |
| 30 | 简易视觉目标检测 | 目标框列表 |
| 31 | 简易雷达目标检测 | 目标相对位置列表 |
| 40 | 红外灰度相机 | H×W uint8 |
| 41 | 红外彩色相机 | H×W×3 uint8,BGR |
2.2 图像及 ROS 话题¶
开启 ROS 转发后,图像类传感器发布 sensor_msgs/Image:
| TypeID | ROS 编码 | 话题 |
|---|---|---|
| 1 | bgr8 |
/rflysim/sensor{SeqID}/img_rgb |
| 2 | mono16 |
/rflysim/sensor{SeqID}/img_depth |
| 3 | mono8 |
/rflysim/sensor{SeqID}/img_gray |
| 4 | bgr8 |
/rflysim/sensor{SeqID}/img_Segmentation |
| 8 | bgr8 |
/rflysim/sensor{SeqID}/fisheye |
| 9 | bgr8 |
/rflysim/sensor{SeqID}/img_cine |
| 40 | mono8 |
/rflysim/sensor{SeqID}/img_Infrared_Gray |
| 41 | bgr8 |
/rflysim/sensor{SeqID}/img_Infrared |
ROS 话题区分大小写。消息的 frame_id 默认为 map,也可以通过
tf_cfg.yaml 中的 sensors_frame_id 配置。
2.3 点云公共载荷¶
TypeID 7、20、21、22、23 的 UDP 和共享内存载荷使用相同的点云主体。 以下偏移从传感器载荷起点计算,不包含 UDP 包头或共享内存的 9 字节控制头。
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | copter_id |
int32 | 传感器绑定的载体 ID |
| 4 | axis_type |
int32 | 位姿参考方式 |
| 8 | position[3] |
float32[3] | 位置 |
| 20 | euler[3] |
float32[3] | Roll、Pitch、Yaw |
| 32 | point_count |
int32 | 点数量 |
| 36 | points |
int16[] | 点云数据 |
axis_type 与传感器的 AxisMask 配置对应:
| 值 | 含义 |
|---|---|
| 0 | 使用绑定载体的绝对位姿 |
| 1 | 使用传感器的绝对位姿 |
| 2 | 使用传感器相对初始位置的位姿 |
点数据格式由 SendProtocol[7] 决定:
SendProtocol[7] |
线上格式 | 每点字节数 |
|---|---|---|
| 0 | int16 [x,y,z] |
6 |
| 大于 0 | int16 [x,y,z,stencil] |
8 |
XYZ 的解码公式为:
第四通道是语义/Stencil 编码,不参与距离缩放。当前 Python 接口为了保持
下游数组形状稳定,会将两种线上格式都转换为 N×4;三通道载荷的第四列
补 0。UDP 路径输出 float32,共享内存路径当前输出 float64。
2.4 LiDAR ROS 数据¶
TypeID 20~23 发布 sensor_msgs/PointCloud2:
| TypeID | 话题 |
|---|---|
| 20 | /rflysim/sensor{SeqID}/vehicle_lidar |
| 21 | /rflysim/sensor{SeqID}/global_lidar |
| 22 | /rflysim/sensor{SeqID}/livox_lidar |
| 23 | /rflysim/sensor{SeqID}/mid360_lidar |
当前 UDP 发布路径使用四个 FLOAT32 字段 x/y/z/seg,偏移分别为
0、4、8、12,point_step=16。第四字段保存上面的 Stencil 值;三通道
载荷对应的第四字段为 0。
2.5 深度转点云¶
TypeID 7 使用 点云公共载荷,而不是逐点
float32 [x,y,z]。
ROS 话题为:
消息类型为 sensor_msgs/PointCloud2。当前 ROS 消息的布局声明与实际数据
长度不一致,详见当前实现限制。
2.6 激光测距¶
TypeID 5 可以单独使用,也可以作为吊舱测距功能的一部分。独立测距传感器 载荷为 56 字节:
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | distance |
float32 | 射线起点到命中点的距离 |
| 4 | copter_id |
int32 | 绑定载体 ID |
| 8 | ray_start[3] |
float32[3] | 射线起点 |
| 20 | angle_euler[3] |
float32[3] | 传感器欧拉角 |
| 32 | impact_point[3] |
float32[3] | 命中点 |
| 44 | box_origin[3] |
float32[3] | 命中对象包围盒中心 |
VisionCaptureApi 将结果保存为 self.Img[idx] 中的 DistanceSensor
对象。当前版本没有为 TypeID 5 创建
/rflysim/sensor{SeqID}/distance ROS 发布器。
2.7 光流¶
TypeID 10 的 UDP 载荷为 34 字节:
| 偏移 | 字段 | 类型 |
|---|---|---|
| 0 | time_usec |
uint64 |
| 8 | sensor_id |
uint8 |
| 9 | flow_x |
int16 |
| 11 | flow_y |
int16 |
| 13 | flow_comp_m_x |
float32 |
| 17 | flow_comp_m_y |
float32 |
| 21 | quality |
uint8 |
| 22 | ground_distance |
float32 |
| 26 | flow_rate_x |
float32 |
| 30 | flow_rate_y |
float32 |
接收结果保存在 VisionCaptureApi.OpticalFlowSensor。当前 SDK 没有为该
数据创建 ROS 发布器,共享内存接收函数也没有 TypeID 10 的专用分支。
2.8 简易目标传感器¶
TypeID 30 输出视锥内目标的二维检测框:
int32 target_count
repeat target_count times:
int32 copter_id
float32 credibility
float32 min_x
float32 min_y
float32 max_x
float32 max_y
TypeID 31 输出全向检测目标的相对位置:
int32 target_count
repeat target_count times:
int32 copter_id
float32 relative_x
float32 relative_y
float32 relative_z
otherParams[0] 是最大检测距离,otherParams[1] 是最大目标数量。
两类结果均通过 self.Img[idx] 获取,当前没有内置 ROS 消息发布。
3. CopterSim IMU 与里程计¶
3.1 请求协议¶
VisionCaptureApi 通过 SensorReqCopterSim 向 CopterSim 请求数据。
请求结构为 36 字节、小端 <4H4B6f:
| 字段 | 类型 | 说明 |
|---|---|---|
checksum |
uint16 | 请求校验码 |
sensor_type |
uint16 | 0=IMU,1=Odom |
update_freq |
uint16 | 更新频率,当前接口限制为 1~1000 Hz |
port |
uint16 | 返回 UDP 端口 |
ip[4] |
uint8[4] | 返回 IPv4 地址 |
params[6] |
float32[6] | 保留参数 |
请求发送到 30100 + (copterID - 1) * 2。默认返回端口为
31000 + copterID - 1,IMU 与 Odom 可以复用同一个接收线程。
3.2 IMU¶
调用 sendImuReqCopterSim() 请求 IMU。返回报文为 40 字节、小端
<iid6f:
| 字段 | 类型 | 说明 |
|---|---|---|
checksum |
int32 | 固定为 1234567898 |
sequence |
int32 | 消息序号 |
simulation_time |
float64 | 仿真时间 |
acceleration[3] |
float32[3] | 原始加速度 |
angular_rate[3] |
float32[3] | 原始角速度 |
当前 ROS 映射为:
默认发布 sensor_msgs/Imu 到 /rflysim/imu;可以通过
tf_cfg.yaml 的 imu_topic_name 和 imu_frame_id 修改。接口不提供
姿态解算,消息中的姿态四元数为 0,并将姿态协方差首项设置为 -1 表示未知。
3.3 独立 Odom¶
调用 sendOdomReqCopterSim() 请求独立 Odom。也可以使用
sendOdomReqClient() 只发送请求,并自行管理接收端。返回报文为
88 字节、小端 <IIdIHH3d4f3f3f:
| 字段 | 类型 | 说明 |
|---|---|---|
checksum |
uint32 | 固定为 1234567888 |
sequence |
uint32 | 消息序号 |
simulation_time |
float64 | 仿真时间 |
copter_id |
uint32 | 载体 ID |
frame_type |
uint16 | 当前有效值 1,表示世界 NED、机体 FRD |
flags |
uint16 | 数据有效位和不连续标志 |
position[3] |
float64[3] | NED 世界位置 |
quaternion_wxyz[4] |
float32[4] | NED 到 FRD 的姿态四元数 |
linear_velocity[3] |
float32[3] | NED 线速度 |
angular_velocity[3] |
float32[3] | FRD 角速度 |
flags 定义如下:
| 位 | 含义 |
|---|---|
1 << 0 |
位置有效 |
1 << 1 |
姿态有效 |
1 << 2 |
线速度有效 |
1 << 3 |
角速度有效 |
1 << 5 |
数据不连续,需要重建局部坐标原点 |
数据按 copter_id 缓存在 odomDataByCopter,通过
getOdomData(copterID) 获取。
开启 ROS 转发后,接口将 NED/FRD 转换为 ROS 使用的 NWU/FLU,并发布:
| 话题 | header.frame_id |
child_frame_id |
|---|---|---|
/rflysim/uav{copterID}/global/odom |
map |
base_link{copterID} |
/rflysim/uav{copterID}/local/odom |
odom{copterID} |
base_link{copterID} |
局部 Odom 以首次有效样本为原点。收到不连续标志后,接口会清除该飞机的 初始变换并在下一帧重新建立局部坐标系。
3.4 点云携带的位姿¶
TypeID 7、20~23 的点云载荷仍携带 copter_id、axis_type 和
position/euler,用于让点云与位姿共享同一时间戳。该兼容路径主要提供
位姿,速度字段保持默认值。
当同一 copter_id 已收到独立 Odom 时,SDK 使用独立 Odom 发布器,不再
重复发布点云携带的 Odom。
4. 吊舱 UDP 协议¶
4.1 控制请求¶
吊舱使用 TypeID 9 和 VisionSensorReqNew。结构体为 148 字节,
Python 格式 2H1I14H28f:
struct VisionSensorReqNew {
uint16 checksum; // 控制请求为 12345
uint16 seq_id;
uint32 bitmask;
uint16 type_id;
uint16 target_copter;
uint16 target_mount_type;
uint16 data_width;
uint16 data_height;
uint16 data_check_freq;
uint16 send_protocol[8];
float camera_fov;
float sensor_pos_xyz[3];
float eular_or_quat;
float sensor_ang_eular[3];
float sensor_ang_quat[4];
float other_params[16];
};
控制请求通过 UDP 发送到 20010 + windID。Python 调用接口为:
注意函数名是 sendUpdateUEImaged。不带末尾 d 的
sendUpdateUEImage 只接受旧版 VisionSensorReq。
4.2 bitmask¶
多个控制项可以按位或组合:
| bit | 有效字段 | 说明 |
|---|---|---|
1 << 1 |
CameraFOV |
设置视场角 |
1 << 2 |
SensorAngEular/Quat |
设置目标姿态 |
1 << 3 |
EularOrQuat、SensorAngQuat |
使用四元数姿态模式 |
1 << 4 |
otherParams[0] |
设置焦距,单位 mm |
1 << 5 |
otherParams[1] |
大于 0 时回中 |
1 << 6 |
otherParams[2:4] |
Pitch、Yaw 角速度,单位 deg/s |
1 << 7 |
otherParams[4] |
光学变倍 |
1 << 8 |
otherParams[5] |
按 CopterID 或指定位置跟踪目标 |
1 << 9 |
otherParams[6] |
开关吊舱激光测距 |
1 << 10 |
otherParams[7] |
0=RGB,1=红外彩色,2=红外灰度 |
1 << 11 |
otherParams[8:11] |
AI 全目标框选或像素点选 |
AI 模式中,otherParams[8]=1 表示框选视野内目标;
otherParams[8]=2 表示使用 otherParams[9]、[10] 中的像素坐标
选择目标。
4.3 状态接收¶
UE4CtrlAPI 在 UDP 端口 20006 接收吊舱状态。返回数据同样使用
2H1I14H28f,长度 148 字节,返回校验码为 12346。数据保存在:
常用字段包括 CameraFOV、SensorAngEular、SensorAngQuat 和
otherParams。当前吊舱例程从 otherParams[7] 读取测距结果。不同
RflySim3D/UE5 版本可能扩展 otherParams,使用前应同时核对对应版本例程。
5. RosTrans 吊舱话题¶
RosTrans 当前使用 rflysim_msgs/GimbalCtrl 和
rflysim_msgs/GimbalStatus,不再使用旧文档中的
CameraCtrl/CameraAICtrl/CaramerStatus/CameraParams 消息。
5.1 吊舱控制¶
| 项目 | 值 |
|---|---|
| 消息类型 | rflysim_msgs/GimbalCtrl |
| 话题 | /onboard/gimbal/common/control |
| 路由方式 | 接收方根据 target_id 过滤 |
ctrl_type 的主要取值:
| 值 | 功能 |
|---|---|
| 4 | 角速度控制或回中 |
| 8 | 光学变倍 |
| 120 | 连续激光开关 |
| 121 | 单次激光测距 |
| 122 | 像素坐标跟踪 |
| 123 | 停止跟踪或退出 GPS 凝视 |
| 124 | GPS 坐标凝视 |
| 125 | 夜间模式 |
消息同时提供 yaw_speed_dps、pitch_speed_dps、pixel_x/y、
laser_on、GPS 凝视坐标和 target_id 等对应字段。
5.2 吊舱状态¶
| 项目 | 值 |
|---|---|
| 消息类型 | rflysim_msgs/GimbalStatus |
| 话题 | /onboard/gimbal/state/status |
| 路由字段 | source_id |
状态消息包含:
- 相机内参
fx/fy/cx/cy和畸变参数; - 飞机到吊舱基座的 FRD 安装外参;
- 吊舱到相机、NED 到相机的姿态及有效位;
laser_range_valid和laser_range_m。
直接 UDP 吊舱接口与 RosTrans ROS 接口是两套入口。前者使用
VisionSensorReqNew 和端口 20010/20006,后者使用上述 ROS 自定义消息,
不应混用消息结构。
6. 当前实现限制¶
以下项目是当前 VisionCaptureApi.py 的实际限制。开发自定义接收端或 ROS
节点时需要特别注意:
- TypeID 7 的 Python 点云数组固定为四列。UDP 和共享内存 ROS 路径都只
声明
x/y/z三个字段并设置point_step=12,但写入的仍是四列float32数据,导致PointCloud2.data的实际长度与消息布局不一致。 该版本不应将内置 TypeID 7 ROS 发布作为稳定协议使用。 - TypeID 20~23 的第四个 ROS 字段在 UDP 路径名为
seg,共享内存 路径名为w。字段内容均来自未缩放的 Stencil 通道,订阅端不应将w理解为四元数分量。 - TypeID 8 的 UDP ROS 路径支持
/fisheye,但共享内存 ROS 消息构造 分支当前遗漏 TypeID 8。 - TypeID 5、10、30、31 可以由 Python 解码,但当前没有内置 ROS 发布 分支。启用全局 ROS 转发时,建议对这些类型自行发布消息,或避免让其进入 SDK 的通用 ROS 发布路径。
- 视觉 UDP 包头目前使用本机字节序;独立 IMU/Odom 已使用显式小端。 自定义跨平台实现应固定小端格式。
7. 相关文档¶
MAVROS 的进程启动、PX4 链路和多机命名空间属于飞控与 ROS 通信,不属于
视觉传感器线上协议,因此统一在 ROS 与 RflyRosStart 文档中说明。