ROS2的编译
colcon 是 ROS2 编译的工具。ROS2 的工作空间与 ROS1 保持一样的目录结构:
<workspace>
├── build # 编译时自动生成,包含编译的中间文件
├── install # 编译时自动生成,包含编译的结果:可执行文件,库文件,message 头文件等
├── log # 编译时的日志,方便在编译失败时查找问题(ros2 新增)
└── src
├── ros_pkg1 # 功能包1
├── ros_pkg2
├── ros_pkg3
......
需要注意的是,ROS2在执行 colcon 命令编译时,需要在workspace
目录,因为编译时自动生成的build
,install
,log
等文件夹会与 colcon 所在目录保持同级。
colcon 编译
编译整个工作空间
colcon build
编译默认是选用 ninja
作为构建方式并且使用 8线程 进行代码的编译。编译的结果在install
文件夹中是以 package
为单位存放的。
编译工作空间,并建立软连接,可以让launch修改后不必重新编译,如果install的文件需要拷贝到另外的机器使用,不要使用这个参数,否则会找不到源文件(Python)。
colcon build --symlink-install
编译工作空间指定pkg
colcon build --symlink-install --packages-select packages1
编译工作空间多个pkg
colcon build --symlink-install --packages-select packages1 packages2 packages3
忽略某个包
编译除了指定忽略的pkg外的其他所有pkg,同样也可以在 package 目录下创建 COLCON_IGNORE 空文件以忽略编译。
colcon build --packages-ignore packages2
清除已有的编译缓存
如果不加 --cmake-clean-cache
参数,系统如果发现编译完成后生成三个文件夹install
, build
, log
中有相关包的信息,则会跳过这个包的重新编译。
colcon build --cmake-clean-cache
合并安装
colcon build --merge-install
与 catkin_make
功能一样,将编译结果合并安装,比如头文件都放在 install/include
目录下, 库文件都放在 install/lib
文件夹下。
rosdep
rosdep
是一种依赖管理工具,可以与包和外部库一起工作。它是用于识别和安装依赖项以构建或安装包的命令行实用程序。rosdep
不是一个独立的包管理器;它是一个元包管理器,使用其对系统和依赖项的了解来查找适合在特定平台上安装的包。实际的安装是使用系统包管理器完成的。
rosdep
在编译和部署时解决开发的软件包编译及运行依赖的问题,最常见的就是解决 xxxConfig.cmake not found
的问题。
通常在构建工作空间之前调用rosdep
,用于安装工作空间内包的依赖项。
rosdep init
初始化远程服务器的地址。
rosdep update
将 ros package 的依赖关系缓存在本地。
rosdep install --from-paths src --ignore-src src -y
通过查询每一个 ros_package 下的 package.xml
文件中的值来确定要下载的依赖包并进行安装。
工作空间中的package.xml
是 rosdep
查找依赖集合的文件。package.xml
中依赖项列表完整且正确非常重要。
<depend>
构建时和运行时都提供的依赖关系。对于C++,如果不确定,可以全部使用此标签。纯Python项目没有build阶段,改用 <exec_depend>
。
<build_depend>
仅在构建时使用特定的依赖项,而在运行时不需要,可以使用 <build_depend>
标签。
<exec_depend>
仅在运行时需要的依赖。
bloom
一个打包工具,可以将代码制作成 deb
或 rpm
包,然后安装到目标机器上进行测试和部署。
安装:
sudo apt-get install python3-bloom fakeroot
工作方式:
读取 ros package 中的
CMakeLists.txt
的install
确定安装位置。读取 ros package 中的
package.xml
文件确定版本号及依赖项。基于以上信息进行软件的打包。
使用参考:
cd <ros package 的目录下>
bloom-generate rosdebian --os-name <系统名字> --ros-distro <ros版本名字>
fakeroot debian/rules binary
ROS2 节点
下面是ROS Graph,ROS的Node可以向任意数量的Topic发布数据,同时可以订阅任意数量的Topic。
创建工作空间
如果是创建C++的工作空间
ros2 pkg create --build-type ament_cmake msg_demo --dependencies rclcpp example_interfaces
如果是创建Python的工作空间
ros2 pkg create --build-type ament_python msg_demo --dependencies rclcpp example_interfaces
其中--dependencies
可以在创建时指定,也可以手动在package.xml
添加。
当然我们也可以直接下载ROS官方的Demo...
git clone https://github.com/ros2/examples -b foxy
进入examples/rclcpp/topics
路径后,运行 colcon build
然后对构建的软件包运行测试,此时不需引用工作空间。 colcon test
会确保测试在正确的环境中运行,并且可以访问它们的依赖项:
colcon test
引用工作空间:
source install/setup.bash
或者
. install/setup.bash
分别在两个终端中运行:
ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
ros2 run examples_rclcpp_minimal_publisher publisher_member_function
图像的发布和订阅
Publisher
这是一个简单的静态图像发布节点(chatgpt写的)
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
class ImagePublisher : public rclcpp::Node
{
public:
ImagePublisher() : Node("image_publisher"), count_(0)
{
publisher_ = this->create_publisher<sensor_msgs::msg::Image>("image", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&ImagePublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = sensor_msgs::msg::Image();
cv::Mat image = cv::imread("/home/jetson/Documents/CV/ros2_demo/image_demo/hand-landmarks.png");
cv::putText(image, "Frame: " + std::to_string(count_),
cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);
cv_bridge::CvImage cv_image;
cv_image.header.stamp = this->get_clock()->now();
cv_image.header.frame_id = "camera";
cv_image.encoding = "bgr8";
cv_image.image = image;
cv_image.toImageMsg(message);
publisher_->publish(message);
count_++;
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ImagePublisher>());
rclcpp::shutdown();
return 0;
}
Subscriber
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cv_bridge/cv_bridge.h"
#include <opencv2/opencv.hpp>
class ImageSubscriberNode : public rclcpp::Node
{
public:
ImageSubscriberNode() : Node("image_subscriber")
{
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(
"image", 10,
std::bind(&ImageSubscriberNode::image_callback, this, std::placeholders::_1));
}
private:
void image_callback(const sensor_msgs::msg::Image::SharedPtr msg)
{
cv_bridge::CvImagePtr cv_ptr;
try
{
cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
}
catch (const cv_bridge::Exception& e)
{
RCLCPP_ERROR(this->get_logger(), "Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str());
return;
}
cv::imshow("Received Image", cv_ptr->image);
cv::waitKey(10);
}
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ImageSubscriberNode>());
rclcpp::shutdown();
return 0;
}
CMakeLists.txt
下面是图像发布和订阅的CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(image_demo)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(image_transport REQUIRED)
find_package(std_msgs REQUIRED) # 确保 std_msgs 被找到
find_package(cv_bridge REQUIRED) # 确保 cv_bridge 被找到
set(OpenCV_DIR "/home/nvidia/opencv-4.5.4/build")
find_package(OpenCV)
message(STATUS "OpenCV library status:")
message(STATUS " config: ${OpenCV_DIR}")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS} ${CUDA_INCLUDE_DIRS})
add_executable(image_publisher src/image_publisher.cpp)
add_executable(image_subscriber src/image_subscriber.cpp)
ament_target_dependencies(image_publisher
rclcpp
sensor_msgs
image_transport
cv_bridge
OpenCV
)
ament_target_dependencies(image_subscriber
rclcpp
sensor_msgs
image_transport
cv_bridge
OpenCV
)
install(TARGETS
image_publisher
image_subscriber
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
ROS2的ament_cmake
是基于CMake
改进而来的。现在来拆解一下这个CMakeLists
。
第一行指定里Cmake的最低版本,第二行是功能包名称,注意应与package.xml
中保持一致。
cmake_minimum_required(VERSION 3.5)
project(image_demo)
查找依赖项,添加非ROS2功能包的依赖项时,需要将头文件路径在include_directories
中写出。而依赖项为ROS2功能包时,无需此操作。
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(image_transport REQUIRED)
find_package(std_msgs REQUIRED) # 确保 std_msgs 被找到
find_package(cv_bridge REQUIRED) # 确保 cv_bridge 被找到
set(OpenCV_DIR "/home/nvidia/opencv-4.5.4/build")
find_package(OpenCV)
message(STATUS "OpenCV library status:")
message(STATUS " config: ${OpenCV_DIR}")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS} ${CUDA_INCLUDE_DIRS})
add_executable
用于构建执行文件。
add_executable(image_publisher src/image_publisher.cpp)
add_executable(image_subscriber src/image_subscriber.cpp)
ament_target_dependencies
是官方推荐的添加依赖项方式。使依赖项的库、头文件和自身依赖项被正确找到。若依赖项为ROS2功能包,最好使用ament_target_dependencies
。若功能包有多个库,将一并包含。此外,ament_target_dependencies
只能链接find_package()
找到的包,如果是自定义的库文件,仍需要使用target_link_libraries的方式链接。
ament_target_dependencies(image_publisher
rclcpp
sensor_msgs
image_transport
cv_bridge
OpenCV
)
ament_target_dependencies(image_subscriber
rclcpp
sensor_msgs
image_transport
cv_bridge
OpenCV
)
install
安装的是执行文件。安装路径是 image_demo/install/image_demo/lib/image_demo
install(TARGETS
image_publisher
image_subscriber
DESTINATION lib/${PROJECT_NAME})
下面是编译单元测试文件
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
项目安装通过ament_package()
完成,并且每个软件包必须执行一次这个调用。ament_package()
会安装package.xml
文件,用ament
索引注册该软件包,并安装CMake的配置文件,以便其他软件包可以用find_package()
找到该软件包。由于ament_package()
会从CMakeLists.txt文件中收集大量信息,因此它应该是CMakeLists.txt文件中的最后一个调用。
ament_package()
参考文章
[1] https://blog.csdn.net/u014603518/article/details/127717928
[2] https://www.ncnynl.com/archives/202407/6415.html
[3] http://fishros.org/doc/ros2/humble/Tutorials/Intermediate/Rosdep.html。。