In this post, you will learn how to integrate the OpenCV library with a ROS2 C++ node. The example shown builds into a “hello-world” binary for ROS2 integration with OpenCV that publishes an image to the ROS network.
After going through this post, you would be able to use OpenCV to do things related to image processing and computer vision and make the results available to other ROS2 nodes. The example uses ROS2 Humble.
Step 1: Fire up a system with ROS2 installation
“Hey, do I have to install ros2 first?” Absolutely not! Just log in to The Construct to get access to virtual machines pre-installed with ROS.
Once logged in, click on My Rosjects, then Create a New Rosject, supply the information as shown in the image below, and click Create. Then RUN the rosject.
You might also want to try this on a local PC if you have ros2 installed. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ros2 installation.
Step 2: Verify that OpenCV is installed
All ROS installs include OpenCV, so verify whether OpenCV has been installed.
Open a web shell and run the following command:
pkg-config --modversion opencv4
You should get a version number similar to this:
4.5.4
If the above output is similar to what you see, you are set and ready, everything should work. Otherwise, please install OpenCV using the following command:
sudo apt install libopencv-dev python3-opencv
Step 3: Create a ROS2 C++ node integrating the OpenCV library
First, we need to create a package. We need the following dependencies for the package:
rclcpp – this is the ros2 C++ API we’ll use to create the ros2 node
std_msgs – needed for sending message header while sending the image
sensor_msgs – needed for sending the image itself
cv_bridge – converts from the OpenCV image format to the ros2 image format
image_transport – compresses the image for transport within the ros2 network
OpenCV – generates the image we want to send
Run the following command in the terminal you used in Step 2, to create the package:
Now go to the src folder of the package you just created and create the C++ file that will define the node:
cd ~/ros2_ws/src/my_opencv_demo/src
touch minimal_opencv_ros2_node.cpp
Open the Code Editor, locate the C++ file you just created, and paste the code indicated below. Explanations are given as comments within the code.
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "std_msgs/msg/header.hpp"
#include <chrono>
#include <cv_bridge/cv_bridge.h> // cv_bridge converts between ROS 2 image messages and OpenCV image representations.
#include <image_transport/image_transport.hpp> // Using image_transport allows us to publish and subscribe to compressed image streams in ROS2
#include <opencv2/opencv.hpp> // We include everything about OpenCV as we don't care much about compilation time at the moment.
using namespace std::chrono_literals;
class MinimalImagePublisher : public rclcpp::Node {
public:
MinimalImagePublisher() : Node("opencv_image_publisher"), count_(0) {
publisher_ =
this->create_publisher<sensor_msgs::msg::Image>("random_image", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalImagePublisher::timer_callback, this));
}
private:
void timer_callback() {
// Create a new 640x480 image
cv::Mat my_image(cv::Size(640, 480), CV_8UC3);
// Generate an image where each pixel is a random color
cv::randu(my_image, cv::Scalar(0, 0, 0), cv::Scalar(255, 255, 255));
// Write message to be sent. Member function toImageMsg() converts a CvImage
// into a ROS image message
msg_ = cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", my_image)
.toImageMsg();
// Publish the image to the topic defined in the publisher
publisher_->publish(*msg_.get());
RCLCPP_INFO(this->get_logger(), "Image %ld published", count_);
count_++;
}
rclcpp::TimerBase::SharedPtr timer_;
sensor_msgs::msg::Image::SharedPtr msg_;
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
// create a ros2 node
auto node = std::make_shared<MinimalImagePublisher>();
// process ros2 callbacks until receiving a SIGINT (ctrl-c)
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Finally, edit the package’s CMakeLists.txt (~/ros2_ws/src/my_opencv_demo/CMakeLists.txt) file to recognize the node. Copy the lines of code shown below and paste them before the invocation of ament_package():
The CMakeLists.txt file should now look like this:
cmake_minimum_required(VERSION 3.8)
project(my_opencv_demo)
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(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(cv_bridge REQUIRED)
find_package(image_transport REQUIRED)
find_package(OpenCV REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
add_executable(minimal_opencv_ros2_node src/minimal_opencv_ros2_node.cpp)
ament_target_dependencies(minimal_opencv_ros2_node rclcpp std_msgs sensor_msgs cv_bridge image_transport OpenCV)
install(TARGETS
minimal_opencv_ros2_node
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
Congratulations! You have created a ros2 C++ node integrating OpenCV. Now, we need to see if it works!
Step 4: Compile and test ROS2 C++ node integrating the OpenCV library
Generate the ros2 executable by compiling and sourcing the package:
cd ~/ros2_ws
colcon build --packages-select my_opencv_demo
source ~/ros2_ws/install/setup.bash
If you got to this point, great! Now we will run the node:
ros2 run my_opencv_demo minimal_opencv_ros2_node
You should see some output similar to this:
[INFO] [1677071986.446315963] [opencv_image_publisher]: Image 0 published
[INFO] [1677071986.941745471] [opencv_image_publisher]: Image 1 published
[INFO] [1677071987.442009334] [opencv_image_publisher]: Image 2 published
[INFO] [1677071987.941677164] [opencv_image_publisher]: Image 3 published
[INFO] [1677071988.441115565] [opencv_image_publisher]: Image 4 published
[INFO] [1677071988.940492910] [opencv_image_publisher]: Image 5 published
[INFO] [1677071989.441007118] [opencv_image_publisher]: Image 6 published
Now let’s run ros2 topic list to confirm the existence of the image topic. Leave the node running in the current terminal and run the following command in a new terminal:
source ~/ros2_ws/install/setup.bash
ros2 topic list
The output should inclide the /random_image topic:
/parameter_events
/random_image
/rosout
Finally lets us see what the image produced by OpenCV looks like. Run the following in the same terminal where you ran ros2 topic list. If you get the error, (image_view:8839): Gdk-ERROR **: 13:30:34.498: The program 'image_view' received an X Window System error, just run the command again.
ros2 run image_view image_view --ros-args --remap image:=/random_image
You should now see something like this pop up on your screen. Yahoo!
Great job! You have successfully created a ros2 C++ node integrating OpenCV!
Step 5: Extra: add text to the Image
Copy the lines of code shown below and paste them inside the timer callback function just before writing the message to be sent:
// Declare the text position
cv::Point text_position(15, 40);
// Declare the size and color of the font
int font_size = 1;
cv::Scalar font_color(255, 255, 255);
// Declare the font weight
int font_weight = 2;
// Put the text in the image
cv::putText(my_image, "ROS2 + OpenCV", text_position, cv::FONT_HERSHEY_COMPLEX, font_size, font_color, font_weight);
Stop the currently running node, recompile and source the package, and re-run the node. You should now see something like this:
Step 6: Check your learning
Do you understand how to create a ros2 C++ node integrating OpenCV? If you don’t know it yet, please go over the post again, more carefully this time.
(Extra) Step 7: Watch the video to understand how to integrate the OpenCV library with a ROS2 C++ node
Here you go:
If you want to learn more about ROS 2 and dive deeper into robotics, check out the Robotics Developer Masterclass, where you’ll master robotics development from scratch and get 100% job-ready to work at robotics companies.
Feedback
Did you like this post? Do you have any questions about how to integrate the OpenCV library with a ROS2 C++ node? Please leave a comment in the comments section below, so we can interact and learn from each other.
If you want to learn about other ROS2 topics, please let us know in the comments area and we will do a video or post about it.
In this post, you will learn how to introspect ROS 2 executables by identifying publishers and subscribers in the executables. This answers this question posted on ROS Answers.
“Hey, do I have to install ros2 first?” Absolutely not! We will be using The Construct to get access to virtual machines pre-installed with ROS.
Click here to copy the ROS2 TurtleBot3 sandbox packages. Once copied, click the red RUN button to launch the packages in a virtual machine. Please be patient while the environment loads.
PS: You will need to login or create an account to copy the packages.
You might also want to try this on a local PC if you have ros2 and some executables installed. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ros2 installation.
Step 2: Explore the source code of an ament_python package
You can know what topics are being used by an executable (that is, introspect it) without running it by checking its source code, looking for publishers and subscribers.
Now head over to the Code Editor to make to explore the source code of the packages you just copied.
First, we look at the turtlebot3_teleop package.
Let’s look for the executable file for this package. We can find that in the setup.py file.
Looking at the entry_points part of the file, and then the console_scripts, we find the executable file is in the turtlebot3_teleop/script/teleop_keyboard.py file. Let’s find the publishers and/subscribers for this executable.
So we see that the main executable is src/node_main.cpp. Let’s examine it.
turtlebot3_node/src/node_main.cpp
// Copyright 2019 ROBOTIS CO., LTD.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Darby Lim
#include <rcutils/cmdline_parser.h>
#include <rclcpp/rclcpp.hpp>
#include <chrono>
#include <memory>
#include <string>
#include "turtlebot3_node/diff_drive_controller.hpp"
#include "turtlebot3_node/turtlebot3.hpp"
void help_print()
{
printf("For turtlebot3 node : \n");
printf("turtlebot3_node [-i usb_port] [-h]\n");
printf("options:\n");
printf("-h : Print this help function.\n");
printf("-i usb_port: Connected USB port with OpenCR.");
}
int main(int argc, char * argv[])
{
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
if (rcutils_cli_option_exist(argv, argv + argc, "-h")) {
help_print();
return 0;
}
rclcpp::init(argc, argv);
std::string usb_port = "/dev/ttyACM0";
char * cli_options;
cli_options = rcutils_cli_get_option(argv, argv + argc, "-i");
if (nullptr != cli_options) {
usb_port = std::string(cli_options);
}
rclcpp::executors::SingleThreadedExecutor executor;
auto turtlebot3 = std::make_shared<robotis::turtlebot3::TurtleBot3>(usb_port);
auto diff_drive_controller =
std::make_shared<robotis::turtlebot3::DiffDriveController>(
turtlebot3->get_wheels()->separation,
turtlebot3->get_wheels()->radius);
executor.add_node(turtlebot3);
executor.add_node(diff_drive_controller);
executor.spin();
rclcpp::shutdown();
return 0;
}
There’s no reference to a publisher or subscriber in this file, but it references some other files. Let’s look at the turtlebot3_node/turtlebot3.cpp file referenced on line 25.
Success! We see the /cmd_vel is also referenced on line 313. Can you find other files linked to the main file and it’s linked files, where other topics are referenced?
Step 4: Check your learning
Do you understand how to introspect ROS 2 executables? If you don’t know it yet, please go over the post again, more carefully this time.
(Extra) Step 5: Watch the video to understand how to introspect ROS 2 executables
Here you go:
Feedback
Did you like this post? Do you have any questions about how to introspect ROS 2 executables? Please leave a comment in the comments section below, so we can interact and learn from each other.
If you want to learn about other ROS2 topics, please let us know in the comments area and we will do a video or post about it.
In this post, you will learn how to create and use your own ros2 C++ library. I’ll show you how to create the library and then use it in a demo package.
Step 1: Fire up a system with ROS2 installation
“Hey, do you mean I have to install ros2 first?” Absolutely not! Just log in to The Construct to get access to virtual machines pre-installed with ROS.
Once logged in, click on My Rosjects, then Create a New Rosject, supply the information as shown in the video and click Create. Then RUN the rosject.
You might also want to try this on a local PC if you have ros2 installed. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ros2 installation.
Step 2: Create a new package that contains the library’s source code (logic)
Open a web shell and run the following commands to create the package.
Now that your package is created, we need to create a header file for the library. Move inside the include/my_value_converter_library directory and create a header file named library_header.h.
cd ~/ros2_ws/src/my_value_converter_library/include/my_value_converter_library
touch library_header.h
Now head over to the Code Editor to make changes to the header file. Check the image below for how to open the Code Editor.
Locate the header file in the code editor: ros2_ws > src > my_value_converter_library > include > my_value_converter_library > library_header.h and paste in the following code.
Now we need to edit the CMakeLists.txt file of our package to recognize the source code we have just added. Open the file ros2_ws > src > my_value_converter_library > CMakeLists.txt and add the following lines before the ament_package() call.
# let the compiler search for headers in the include folder
include_directories(include)
# define a library target called my_value_converter_library
add_library(my_value_converter_library src/my_value_converter_library.cpp)
# this line to exports the library
ament_export_targets(my_value_converter_library HAS_LIBRARY_TARGET)
# install the include/my_cpp_library directory to the install/include/my_cpp_library
install(
DIRECTORY include/my_value_converter_library
DESTINATION include
)
install(
TARGETS my_value_converter_library
EXPORT my_value_converter_library
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
So much for all the hard work – now is the time to see if it works. Time to compile the code. In the same web shell, run the following commands:
cd ~/ros2_ws
colcon build
Success! We have now created our library. Next, we are going to use it!
PS: if your code did not compile correctly, please go over the instructions and ensure you have created the files in the exact locations specified.
Step 3: Create a new package that uses the library you just created
Create the new package in the open shell. Note that my_value_converter_library is passed as a dependency during the creation of the new package. This will simplify some of the work that needs to be done when modifying the CMakeLists.txt file.
Now verify that the dependencies have been properly added. If the build runs successfully, it is so. If not, please correct the errors before you proceed.
cd ~/ros2_ws
colcon build
If you got to this point, great! Now we will create the C++ source code that will use your library.
cd ~/ros2_ws/src/my_value_converter_node/src
touch my_value_converter_node.cpp
Now locate the my_value_converter_node.cpp file in the code editor and paste in the following source code.
#include <memory>
#include <string>
#include "my_value_converter_library/library_header.h"
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
class ConverterNode : public rclcpp::Node {
public:
explicit ConverterNode() : Node("converter_node") {
auto callback =
[this](const std_msgs::msg::Float32::SharedPtr input_msg) -> void {
RCLCPP_DEBUG(this->get_logger(), "I heard: [%f]", input_msg->data);
// Use the library
// Map an value from 0 - 1023 range to -1.0 to +1.0 range
double new_value = map_value(input_msg->data, 0, 1023, -1.0, 1.0);
output_msg_ = std::make_unique<std_msgs::msg::Float32>();
output_msg_->data = new_value;
RCLCPP_DEBUG(this->get_logger(), "Publishing: '%f'", output_msg_->data);
pub_->publish(std::move(output_msg_));
};
sub_ = create_subscription<std_msgs::msg::Float32>("output_topic", 10,
callback);
// Create a publisher
rclcpp::QoS qos(rclcpp::KeepLast(7));
pub_ = this->create_publisher<std_msgs::msg::Float32>("input_topic", qos);
}
private:
std::unique_ptr<std_msgs::msg::Float32> output_msg_;
rclcpp::Publisher<std_msgs::msg::Float32>::SharedPtr pub_;
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr sub_;
};
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
auto node = std::make_shared<ConverterNode>();
RCLCPP_INFO(node->get_logger(), "My value converter node started.");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
As you might have expected, we need to modify the CMakeLists.txt file for our new package. Locate the ros2_ws > src > my_value_converter_node > CMakeLists.txt in the code editor and paste in the following code before the ament_package() call.
# define the binary to be built and identify the source files with with which to build it
add_executable(main src/my_value_converter_node.cpp)
# tell CMake that the executable "main" depends on the library "my_value_converter_library"
ament_target_dependencies(main my_value_converter_library rclcpp std_msgs)
# install the executable in the lib folder to make it detectable through setup.bash
install(TARGETS
main
DESTINATION lib/${PROJECT_NAME}/
)
Now build and use your package!
cd ~/ros2_ws
colcon build
source install/setup.bash
ros2 run my_value_converter_node main
Step 4: Check your learning
Do you understand how to create a ros2 C++ library? If you don’t know it yet, please go over the post again, more carefully this time.
(Extra) Step 5: Watch the video to understand how to install a ros2 binary package
Here you go:
Feedback
Did you like this post? Do you have any questions about how to create a ros2 C++ library? Please leave a comment in the comments section below, so we can interact and learn from each other.
If you want to learn about other ROS2 topics, please let us know in the comments area and we will do a video or post about it.
In this post, you will learn how to install a ros2 binary package and make it available for use. This post was written in response to this question on ROS Answers.
Step 1: Fire up a system with ROS2 installation
“Hey, do you mean I have to install ros2 first?” Absolutely not! Just login to The Construct to get access to virtual machines pre-installed with ROS.
Once logged in, click on My Rosjects, then Create a New Rosject, supply the information as shown in the video and click Create. Then RUN the rosject.
You might also want to try this on a local PC if you have ros2 installed. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ros2 installation.
Step 2: Check the ros2 binaries currently installed
Open a web shell and run the following commands. You should see a long list of many binaries!
cd /opt/ros/foxy/share/
ls -al
ROS binaries are stored in /opt/ros/{distro}/share/ If you would like to check if some binary is already installed without going through the long list, use grep. In this case we want to check binaries related to joint_state.
Heck, if you were the user in this post, you wouldn’t need to install anything. “What would I do then?!” Read on to find out!
Step 3: Install the ros2 binary package
We will demo this with the same package the user was struggling with: ros-foxy-joint-state-publisher-gui. Since the package is already installed in our case, we need to uninstall it first, just to demonstrate. Run the following on the current shell:
The user got an error when they tried to launch the GUI binary. Let’s see what we get (fingers crossed).
ros2 run joint_state_publisher_gui joint_state_publisher_gui
You should get a GUI like the image below pop up, confirming that the binary is really there. Cheers!
Step 5: Check your learning
Do you understand how to install a ros2 binary package and make it available for use? The magic word here is source.
If you didn’t get the point above, please go over the post again, more carefully this time.
(Extra) Step 6: Watch the video to understand how to install a ros2 binary package
Here you go:
Feedback
Did you like this post? Do you have any questions about how to install a ros2 binary package? Please leave a comment on the comments section below, so we can interact and learn from each other.
If you want to learn about other ROS2 topics, please let us know in the comments area and we will do a video or post about it.