跳转至

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 的解码公式为:

xyz_m = xyz_int16 * otherParams[0] / 32767

第四通道是语义/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 话题为:

/rflysim/sensor{SeqID}/Depth_Cloud

消息类型为 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 映射为:

linear_acceleration = [-acc[0],  acc[1], -acc[2]]
angular_velocity    = [ rate[0], -rate[1], -rate[2]]

默认发布 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 调用接口为:

vis.sendUpdateUEImaged(vs, windID=0, IP="")

注意函数名是 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。数据保存在:

ue.CamDataVect1

常用字段包括 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 节点时需要特别注意:

  1. TypeID 7 的 Python 点云数组固定为四列。UDP 和共享内存 ROS 路径都只 声明 x/y/z 三个字段并设置 point_step=12,但写入的仍是四列 float32 数据,导致 PointCloud2.data 的实际长度与消息布局不一致。 该版本不应将内置 TypeID 7 ROS 发布作为稳定协议使用。
  2. TypeID 20~23 的第四个 ROS 字段在 UDP 路径名为 seg,共享内存 路径名为 w。字段内容均来自未缩放的 Stencil 通道,订阅端不应将 w 理解为四元数分量。
  3. TypeID 8 的 UDP ROS 路径支持 /fisheye,但共享内存 ROS 消息构造 分支当前遗漏 TypeID 8。
  4. TypeID 5、10、30、31 可以由 Python 解码,但当前没有内置 ROS 发布 分支。启用全局 ROS 转发时,建议对这些类型自行发布消息,或避免让其进入 SDK 的通用 ROS 发布路径。
  5. 视觉 UDP 包头目前使用本机字节序;独立 IMU/Odom 已使用显式小端。 自定义跨平台实现应固定小端格式。

7. 相关文档

MAVROS 的进程启动、PX4 链路和多机命名空间属于飞控与 ROS 通信,不属于 视觉传感器线上协议,因此统一在 ROS 与 RflyRosStart 文档中说明。