8.3 应用开发、编译与示例
本节主要解答与在RDK平台上安装/使用第三方库、编译应用程序、运行官方示例以及相关问题。
如需交叉编译部署,请参考交叉编译环境部署
Q1: 第三方库在RDK上的安装/交叉编译和使用方法是怎样的?
A:
- 板端直接安装: 如果第三方库提供了适用于ARM架构的预编译包(例如
.deb
文件),或者可以通过包管理器(如apt
)直接安装,那么可以在RDK板卡上直接进行安装。对于Python库,如果Pypi上有对应的arm64 wheels包,也可以直接pip install
。 - 交叉编译: 如果第三方库需要从源码编译,推荐在PC开发主机上进行交叉编译,然后将编译产物部署到RDK板卡上。
- 环境部署: 详细的交叉编译环境搭建步骤,请参考地平线开发者社区 的教程:交叉编译环境部署
- 编译步骤: 通常需要配置CMake Toolchain文件,指定交叉编译器、目标系统Sysroot等。
Q2: 在编译大型程序(如C++项目、ROS功能包)的过程中,如果系统提示编译进程被“kill”或出现内存不足相关的错误日志,应该如何解决?
A: 编译大型项目时,如果物理内存不足,Linux系统的OOM (Out Of Memory) killer机制可能会杀死消耗内存最多的进程(通常是编译器进程如cc1plus
、ld
等),导致编译失败。
解决方法: 增加系统的交换空间 (Swap)。Swap是硬盘上的一块区域,当物理内存不足时,系统可以将部分不常用的内存数据暂时存放到Swap中,从而释放物理内存供当前任务使用。虽然Swap比物理内存慢,但可以有效防止因瞬时内存不足导致的编译失败。
增加Swap空间的步骤示例 (创建一个1GB的Swap文件):
# 1. (可选)创建一个目录用于存放Swap文件,或者直接在根目录创建
sudo mkdir -p /swapfile_custom_dir
cd /swapfile_custom_dir
# 2. 使用dd命令创建一个指定大小的空文件 (bs=1M表示块大小为1MB, count=1024表示1024个块,即1GB)
sudo dd if=/dev/zero of=swap bs=1M count=1024
# 3. 设置正确的文件权限 (只有root用户可读写)
sudo chmod 0600 swap
# 4. 将该文件格式化为Swap分区
sudo mkswap -f swap
# 5. 启用Swap分区
sudo swapon swap
# 6. 验证Swap空间是否已启用 (会显示Swap总量和已用量)
free -h
swapon --show
使其开机自动挂载 (可选但推荐):
编辑 /etc/fstab
文件,在末尾添加一行(假设您的swap文件路径是/swapfile_custom_dir/swap
):
/swapfile_custom_dir/swap none swap sw 0 0
参考教程: Swap使用教程
Q3: 如何运行GC4633 MIPI摄像头的示例程序?
A: 地平线官方通常会提供基于常见MIPI摄像头(如F37、GC4663)的AI算法示例(例如FCOS目标检测)。这些示例一般会自动检测连接的摄像头型号并进行算法推理。
运行步骤示例 (以 /app/ai_inference/03_mipi_camera_sample
为例):
- 确保GC4663(或其他兼容的MIPI摄像头)已正确连接到RDK板卡的MIPI CSI接口,并且板卡已上电。
- 通过SSH或串口登录到板卡系统。
- 进入示例程序所在的目录:
cd /app/ai_inference/03_mipi_camera_sample
# 注意:具体路径可能因RDK系统版本和镜像内容而略有不同。 - 使用
sudo
权限运行Python示例脚本:sudo python3 mipi_camera.py
- 如果示例设计为通过HDMI输出,请确保RDK板卡的HDMI接口已连接到显示器。运行后,您应该能在显示器上看到摄像头捕捉的实时画面以及AI算法处理后的结果(例如检测框、分类标签等)。
Q4: 使用rqt_image_view
查看RDK通过ROS发布的RGB888 RAW图像时,感觉非常卡顿,甚至无法接收图像,是什么原因?
A: 这个问题通常与ROS2中间件DDS的配置以及网络传输效率有关,特别是当传输未压缩的大尺寸原始图像数据时。
- 原因分析:
- 默认的FastDDS在UDP协议层可能没有实现有效的MTU(最大传输单元)分片。当发布的图像数据包大小超过网络路径上的MTU时,IP层会进行分片。
- 大量的IP分片对许多常见的路由器、交换机或网卡来说处理负担较重,可能导致无法有效缓冲所有分片。
- 在UDP传输中,如果任何一个IP分片丢失,整个UDP包(即整个图像帧)通常就会被丢弃,或者需要等待重传(如果上层有相关机制,但ROS图像话题通常不保证可靠传输),这会导致严重的卡顿或图像丢失。这种情况有时被称为“IP fragmentation attack”的类似表现,即大量分片导致网络拥堵和丢包。
- 解决方法:
- 更换DDS实现: 尝试将ROS2的RMW (ROS Middleware) 实现从默认的
rmw_fastrtps_cpp
切换到rmw_cyclonedds_cpp
。CycloneDDS在处理大数据包和网络分片方面有时表现更优 。 在终端执行以下命令来切换DDS(仅对当前终端会话有效,或可加入到.bashrc
):然后重新启动您的ROS节点。export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
- 降低传输数据量:
- 发送压缩格式图像: 考虑在RDK板卡端将原始图像(如RGB888)压缩为JPEG或PNG等格式后再通过ROS话题发布。这会显著减小每帧图像的数据量。您可以在PC端订阅压缩图像话题,并由
rqt_image_view
(或自定义节点)进行解压显示。 - 降低分辨率或帧率: 如果应用允许,适当降低发布图像的分辨率或帧率也能有效减少网络负担。
- 发送压缩格式图像: 考虑在RDK板卡端将原始图像(如RGB888)压缩为JPEG或PNG等格式后再通过ROS话题发布。这会显著减小每帧图像的数据量。您可以在PC端订阅压缩图像话题,并由
- 更换DDS实现: 尝试将ROS2的RMW (ROS Middleware) 实现从默认的
Q5: 地平线提供的Linux镜像(特指经过最小裁剪的系统,非完整Ubuntu Desktop/Server)是否支持在板卡端直接进行编译操作?
A: 地平线为RDK提供的部分Linux镜像,特别是那些为嵌入式部署而经过最小化裁剪的rootfs(根文件系统),可能不包含完整的编译工具链(如GCC, G++, make, CMake等)和开发所需的头文件、库文件。
- 结论: 这类最小化Linux镜像通常无法支持或不适合在板卡端直接进行复杂的源码编译工作。
- 推荐做法: 对于需要在RDK上运行的应用程序,推荐采用交叉编译的方式。即在PC开发主机(如Ubuntu PC)上配置好针对RDK目标平台的交叉编译环境,在PC上完成编译后,再将生成的可执行文件和相关依赖部署到RDK板卡上运行。
Q6: 在地平线提供的最小化Linux镜像上如何运行官方手册中提供的示例(这些示例通常以Ubuntu系统环境为例)?
A: 官方手册中的示例(尤其是TROS/ROS相关的示例)通常是在功能更完整的Ubuntu系统环境下演示的。要在最小化的Linux镜像(可能没有预装Python解释器或完整的ROS环境)上运行这些示例(特别是C++编写的ROS节点),需要做一些调整:
-
Ubuntu系统与Linux镜像启动示例的差异:
- 环境配置:
- Ubuntu系统: 通常使用
source /opt/tros/setup.bash
(或对应ROS版本的setup.bash) 来配置TROS/ROS环境,这个脚本会设置大量的环境变量(如PATH
,LD_LIBRARY_PATH
,AMENT_PREFIX_PATH
等)。 - Linux镜像: 可能需要手动设置关键的环境变量,特别是
LD_LIBRARY_PATH
以确保程序能找到所需的共享库。日志路径ROS_LOG_DIR
也可能需要手动指定到一个可写的位置。
- Ubuntu系统: 通常使用
- 配置文件拷贝: 无论哪种系统,运行示例前通常都需要将示例依赖的配置文件(如模型配置、参数文件等)从TROS/ROS安装路径下拷贝到当前工作目录或指定路径。
- 启动方式:
- Ubuntu系统: 常使用
ros2 run <package_name> <executable_name>
或ros2 launch <package_name> <launch_file_name>
来启动节点或启动文件。 - Linux镜像: 由于可能没有完整的
ros2
命令行工具或launch系统,通常需要直接运行编译好的C++可执行程序,并通过命令行参数的方式传递原本在launch文件中设置的参数。
- Ubuntu系统: 常使用
- 环境配置:
-
将launch脚本内容转换为Linux镜像上的直接执行命令(以一个C++的
dnn_node_example
为例):-
分析Ubuntu上的启动命令:
# Ubuntu: 配置tros.b环境
source /opt/tros/setup.bash
# Ubuntu: 从tros.b的安装路径中拷贝出运行示例需要的配置文件。config中为example使用的模型,回灌使用的本地图片
cp -r /opt/tros/${TROS_DISTRO}/lib/dnn_node_example/config/ .
# Ubuntu: 使用本地jpg格式图片进行回灌预测,并存储渲染后的图片
ros2 launch dnn_node_example dnn_node_example_feedback.launch.py -
找到launch脚本并分析其内容:
- 查找launch脚本路径:
# find /opt/tros/ -name dnn_node_example_feedback.launch.py
/opt/tros/share/dnn_node_example/launch/dnn_node_example_feedback.launch.py - 查看launch脚本内容 (Python launch file):
# dnn_node_example_feedback.launch.py (主要内容节选)
def generate_launch_description():
config_file_launch_arg = DeclareLaunchArgument(
"dnn_example_config_file", default_value=TextSubstitution(text="config/fcosworkconfig.json")
)
img_file_launch_arg = DeclareLaunchArgument(
"dnn_example_image", default_value=TextSubstitution(text="config/test.jpg")
)
# 拷贝config中文件
dnn_node_example_path = os.path.join(
get_package_prefix('dnn_node_example'),
"lib/dnn_node_example")
# print("dnn_node_example_path is ", dnn_node_example_path) # 这行通常在launch中不直接打印
# cp_cmd = "cp -r " + dnn_node_example_path + "/config ."
# print("cp_cmd is ", cp_cmd) # 这行通常在launch中不直接打印
# os.system(cp_cmd) # launch文件通常不直接执行shell命令拷贝,而是依赖ament_cmake的install规则
return LaunchDescription([
config_file_launch_arg,
img_file_launch_arg,
Node(
package='dnn_node_example',
executable='example', # 可执行文件名
output='screen',
parameters=[ # 传递给可执行程序的参数
{"feed_type": 0},
{"config_file": LaunchConfiguration('dnn_example_config_file')},
{"image": LaunchConfiguration('dnn_example_image')},
{"image_type": 0},
{"dump_render_img": 1}
],
arguments=['--ros-args', '--log-level', 'info']
)
])
从launch脚本中,我们可以知道它启动了
dnn_node_example
包中的名为example
的可执行文件,并传递了一系列参数。 - 查找launch脚本路径:
-
找到可执行程序路径: 在TROS安装路径下查找该可执行文件:
# find /opt/tros/ -name example -executable -type f
# (更精确的查找方式可能是基于package名)
# 通常位于 /opt/tros/${TROS_DISTRO}/lib/<package_name>/<executable_name>
# 示例路径: /opt/tros/humble/lib/dnn_node_example/example(假设
TROS_DISTRO
环境变量在Linux镜像上未设置,您需要知道实际的发行版名称,如humble
或foxy
) -
在Linux镜像上构造启动命令:
- 配置环境:
# 假设TROS库文件位于/opt/tros/humble/lib (具体路径需确认)
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/tros/humble/lib/
# 指定一个可写的日志目录
export ROS_LOG_DIR=/userdata/ # 或者 /tmp/roslogs/
mkdir -p $ROS_LOG_DIR - 拷贝配置文件: (与Ubuntu上类似)
# 例如,对于humble版本:
cp -r /opt/tros/humble/lib/dnn_node_example/config/ . - 直接运行可执行程序并传递参数:
ROS2节点参数通常通过
--ros-args -p <param_name>:=<param_value>
的形式传递。/opt/tros/humble/lib/dnn_node_example/example \
--ros-args \
-p feed_type:=0 \
-p config_file:="config/fcosworkconfig.json" \
-p image:="config/test.jpg" \
-p image_type:=0 \
-p dump_render_img:=1 \
--log-level info
- 配置环境:
- 完整的Linux镜像上运行示例脚本可能如下:
#!/bin/bash
# 1. 配置环境
# 根据实际TROS版本和安装路径调整
TROS_DISTRO_NAME="humble" # 或者 "foxy" 等
TROS_INSTALL_LIB_DIR="/opt/tros/${TROS_DISTRO_NAME}/lib"
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${TROS_INSTALL_LIB_DIR}
# 如果有其他依赖的库路径,也需要加入
# export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${TROS_INSTALL_LIB_DIR}/aarch64-linux-gnu # 示例
export ROS_LOG_DIR=/userdata/ros_logs_$(date +%s)
mkdir -p $ROS_LOG_DIR
echo "ROS logs will be stored in $ROS_LOG_DIR"
# 2. 准备工作目录和配置文件
WORK_DIR="/tmp/dnn_example_run_$(date +%s)" # 使用时间戳避免冲突
mkdir -p $WORK_DIR
cd $WORK_DIR
echo "Working directory: $(pwd)"
CONFIG_SOURCE_DIR="${TROS_INSTALL_LIB_DIR}/dnn_node_example/config"
if [ -d "$CONFIG_SOURCE_DIR" ]; then
echo "Copying config files from $CONFIG_SOURCE_DIR to $(pwd)/config"
mkdir -p config
cp -r $CONFIG_SOURCE_DIR/* ./config/
else
echo "Error: Config source directory $CONFIG_SOURCE_DIR not found."
exit 1
fi
# 3. 运行可执行程序
EXECUTABLE_PATH="${TROS_INSTALL_LIB_DIR}/dnn_node_example/example"
if [ ! -f "$EXECUTABLE_PATH" ]; then
echo "Error: Executable $EXECUTABLE_PATH not found."
exit 1
fi
echo "Starting DNN example..."
$EXECUTABLE_PATH \
--ros-args \
-p feed_type:=0 \
-p config_file:="config/fcosworkconfig.json" \
-p image:="config/test.jpg" \
-p image_type:=0 \
-p dump_render_img:=1 \
--log-level info
echo "DNN example finished. Check $WORK_DIR for output and $ROS_LOG_DIR for logs."
提示- 除了使用环境变量
ROS_LOG_DIR
设置log路径外,还可以通过启动参数--ros-args --disable-external-lib-logs
禁止node输出log到文件,使日志直接打印到控制台。 例如:$EXECUTABLE_PATH --ros-args --disable-external-lib-logs \
-p feed_type:=0 -p image_type:=0 -p dump_render_img:=1 - 详细的ROS2日志说明可以参考:ROS2官方文档 - About Logging
-
Q7: 如何快速查找ROS/TROS中launch启动脚本文件的具体路径?
A: 当您知道一个launch脚本的文件名(例如 dnn_node_example.launch.py
),但需要修改它或查看其内容时,可以在RDK板卡的TROS安装路径(通常是 /opt/tros/
)下使用 find
命令来查找。
查找示例:
# 查找名为 dnn_node_example.launch.py 的文件
find /opt/tros/ -name dnn_node_example.launch.py
Q8: 交叉编译TogetheROS.Bot (tros.b) 完整源码时速度很慢,有什么方法可以加速吗?
A: 完整编译tros.b的所有package确实需要较长时间(例如,在8核CPU、32GB内存的PC上可能需要20分钟左右)。以下是两种加速编译的方法:
-
使用最小化编译脚本:
- 地平线提供的tros.b编译脚本中,通常除了
all_build.sh
(完整编译)之外,还会提供一个minimal_build.sh
(最小化编译)的选项。 - 最小化编译通常会跳过编译算法示例(examples)和测试用例(tests)等非核心功能包,从而显著减少编译时间。
- 使用方法: 在您进行交叉编译配置的步骤中,将原本调用
./robot_dev_config/all_build.sh
的命令替换为调用./robot_dev_config/minimal_build.sh
。
- 地平线提供的tros.b编译脚本中,通常除了
-
手动忽略不需要编译的package:
- Colcon(ROS2的构建工具)支持通过在特定package的源码目录下放置一个名为
COLCON_IGNORE
的空文件来忽略该package的编译。 - 步骤:
- 首先,确定您不需要哪些package。这些package的源码通常是在编译前通过
.repos
文件(例如robot_dev_config/ros2_release.repos
- 首先,确定您不需要哪些package。这些package的源码通常是在编译前通过
- Colcon(ROS2的构建工具)支持通过在特定package的源码目录下放置一个名为