ROS2与C++入门教程-进程内(intra_process)图像处理演示
ROS2与C++入门教程-进程内(intra_process)图像处理演示
说明:
- 介绍ros2下实现进程内(intra_process)话题发布和订阅
- 在同一进程内的不同节点,可以通过共享指针方式实现内容读取,减少消息的拷贝开销
- 对于图像之类数据量比较大的节点间处理的效率和性能将大大提高
- 本演示实现图像处理,启动相机,订阅图像消息并处理
步骤:
- 新建一个包cpp_intra_process_image
cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake cpp_intra_process_image
- 进入include目录,新建文件common.hpp
cd ~/dev_ws/src/cpp_intra_process_image/include/cpp_intra_process_image
touch common.hpp
- 内容如下:
#ifndef IMAGE_PIPELINE__COMMON_HPP_
#define IMAGE_PIPELINE__COMMON_HPP_
#ifdef _WIN32
#include <process.h>
#define GETPID _getpid
#else
#include <unistd.h>
#define GETPID getpid
#endif
#include <chrono>
#include <string>
#include "opencv2/opencv.hpp"
#include "builtin_interfaces/msg/time.hpp"
int
encoding2mat_type(const std::string & encoding)
{
if (encoding == "mono8") {
return CV_8UC1;
} else if (encoding == "bgr8") {
return CV_8UC3;
} else if (encoding == "mono16") {
return CV_16SC1;
} else if (encoding == "rgba8") {
return CV_8UC4;
}
throw std::runtime_error("Unsupported mat type");
}
std::string
mat_type2encoding(int mat_type)
{
switch (mat_type) {
case CV_8UC1:
return "mono8";
case CV_8UC3:
return "bgr8";
case CV_16SC1:
return "mono16";
case CV_8UC4:
return "rgba8";
default:
throw std::runtime_error("Unsupported encoding type");
}
}
void set_now(builtin_interfaces::msg::Time & time)
{
std::chrono::nanoseconds now = std::chrono::high_resolution_clock::now().time_since_epoch();
if (now <= std::chrono::nanoseconds(0)) {
time.sec = time.nanosec = 0;
} else {
time.sec = static_cast<builtin_interfaces::msg::Time::_sec_type>(now.count() / 1000000000);
time.nanosec = now.count() % 1000000000;
}
}
void
draw_on_image(cv::Mat & image, const std::string & text, int height)
{
cv::Mat c_mat = image;
cv::putText(
c_mat,
text.c_str(),
cv::Point(10, height),
cv::FONT_HERSHEY_SIMPLEX,
0.3,
cv::Scalar(0, 255, 0));
}
#endif // IMAGE_PIPELINE__COMMON_HPP_
- 进入include目录,新建文件camera_node.hpp
cd ~/dev_ws/src/cpp_intra_process_image/include/cpp_intra_process_image
touch camera_node.hpp
- 内容如下:
#ifndef IMAGE_PIPELINE__CAMERA_NODE_HPP_
#define IMAGE_PIPELINE__CAMERA_NODE_HPP_
#include <chrono>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include "opencv2/highgui/highgui.hpp"
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cpp_intra_process_image/common.hpp"
/// Node which captures images from a camera using OpenCV and publishes them.
/// Images are annotated with this process's id as well as the message's ptr.
class CameraNode final : public rclcpp::Node
{
public:
/// \brief Construct a new CameraNode object for capturing video
/// \param output The output topic name to use
/// \param node_name The node name to use
/// \param watermark Whether to add a watermark to the image before publishing
/// \param device Which camera device to use
/// \param width What video width to capture at
/// \param height What video height to capture at
explicit CameraNode(
const std::string & output, const std::string & node_name = "camera_node",
bool watermark = true, int device = 0, int width = 800, int height = 600)
: Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true)),
canceled_(false), watermark_(watermark)
{
// Initialize OpenCV
cap_.open(device);
// TODO(jacobperron): Remove pre-compiler check when we drop support for Xenial
#if CV_MAJOR_VERSION < 3
cap_.set(CV_CAP_PROP_FRAME_WIDTH, static_cast<double>(width));
cap_.set(CV_CAP_PROP_FRAME_HEIGHT, static_cast<double>(height));
#else
cap_.set(cv::CAP_PROP_FRAME_WIDTH, static_cast<double>(width));
cap_.set(cv::CAP_PROP_FRAME_HEIGHT, static_cast<double>(height));
#endif
if (!cap_.isOpened()) {
throw std::runtime_error("Could not open video stream!");
}
// Create a publisher on the output topic.
pub_ = this->create_publisher<sensor_msgs::msg::Image>(output, rclcpp::SensorDataQoS());
// Create the camera reading loop.
thread_ = std::thread(std::bind(&CameraNode::loop, this));
}
~CameraNode()
{
// Make sure to join the thread on shutdown.
canceled_.store(true);
if (thread_.joinable()) {
thread_.join();
}
}
/// \brief Capture and publish data until the program is closed
void loop()
{
// While running...
while (rclcpp::ok() && !canceled_.load()) {
// Capture a frame from OpenCV.
cap_ >> frame_;
if (frame_.empty()) {
continue;
}
// Create a new unique_ptr to an Image message for storage.
sensor_msgs::msg::Image::UniquePtr msg(new sensor_msgs::msg::Image());
if (watermark_) {
std::stringstream ss;
// Put this process's id and the msg's pointer address on the image.
ss << "pid: " << GETPID() << ", ptr: " << msg.get();
draw_on_image(frame_, ss.str(), 20);
}
// Pack the OpenCV image into the ROS image.
set_now(msg->header.stamp);
msg->header.frame_id = "camera_frame";
msg->height = frame_.rows;
msg->width = frame_.cols;
msg->encoding = mat_type2encoding(frame_.type());
msg->is_bigendian = false;
msg->step = static_cast<sensor_msgs::msg::Image::_step_type>(frame_.step);
msg->data.assign(frame_.datastart, frame_.dataend);
pub_->publish(std::move(msg)); // Publish.
}
}
private:
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr pub_;
std::thread thread_;
std::atomic<bool> canceled_;
/// whether or not to add a watermark to the image showing process id and
/// pointer location
bool watermark_;
cv::VideoCapture cap_;
cv::Mat frame_;
};
#endif // IMAGE_PIPELINE__CAMERA_NODE_HPP_
- 进入include目录,新建文件image_view_node.hpp
cd ~/dev_ws/src/cpp_intra_process_image/include/cpp_intra_process_image
touch image_view_node.hpp
- 内容如下:
#ifndef IMAGE_PIPELINE__IMAGE_VIEW_NODE_HPP_
#define IMAGE_PIPELINE__IMAGE_VIEW_NODE_HPP_
#include <sstream>
#include <string>
#include "opencv2/highgui/highgui.hpp"
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cpp_intra_process_image/common.hpp"
/// Node which receives sensor_msgs/Image messages and renders them using OpenCV.
class ImageViewNode final : public rclcpp::Node
{
public:
/// \brief Construct a new ImageViewNode for visualizing image data
/// \param input The topic name to subscribe to
/// \param node_name The node name to use
/// \param watermark Whether to add a watermark to the image before displaying
explicit ImageViewNode(
const std::string & input, const std::string & node_name = "image_view_node",
bool watermark = true)
: Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true))
{
// Create a subscription on the input topic.
sub_ = this->create_subscription<sensor_msgs::msg::Image>(
input,
rclcpp::SensorDataQoS(),
[node_name, watermark](sensor_msgs::msg::Image::ConstSharedPtr msg) {
// Create a cv::Mat from the image message (without copying).
cv::Mat cv_mat(
msg->height, msg->width,
encoding2mat_type(msg->encoding),
const_cast<unsigned char *>(msg->data.data()));
if (watermark) {
// Annotate with the pid and pointer address.
std::stringstream ss;
ss << "pid: " << GETPID() << ", ptr: " << msg.get();
draw_on_image(cv_mat, ss.str(), 60);
}
// Show the image.
cv::Mat c_mat = cv_mat;
cv::imshow(node_name.c_str(), c_mat);
char key = cv::waitKey(1); // Look for key presses.
if (key == 27 /* ESC */ || key == 'q') {
rclcpp::shutdown();
}
if (key == ' ') { // If <space> then pause until another <space>.
key = '\0';
while (key != ' ') {
key = cv::waitKey(1);
if (key == 27 /* ESC */ || key == 'q') {
rclcpp::shutdown();
}
if (!rclcpp::ok()) {
break;
}
}
}
});
}
private:
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
cv::VideoCapture cap_;
cv::Mat frame_;
};
#endif // IMAGE_PIPELINE__IMAGE_VIEW_NODE_HPP_
- 进入include目录,新建文件watermark_node.hpp
cd ~/dev_ws/src/cpp_intra_process_image/include/cpp_intra_process_image
touch watermark_node.hpp
- 内容如下:
#ifndef IMAGE_PIPELINE__WATERMARK_NODE_HPP_
#define IMAGE_PIPELINE__WATERMARK_NODE_HPP_
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "opencv2/opencv.hpp"
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cpp_intra_process_image/common.hpp"
/// Node that receives an image, adds some text as a watermark, and publishes it again.
class WatermarkNode final : public rclcpp::Node
{
public:
/// \brief Construct a WatermarkNode that accepts an image, adds a watermark, and republishes it
/// \param input The name of the topic to subscribe to
/// \param output The topic to publish watermarked images to
/// \param text The text to add to the image
/// \param node_name The node name to use
explicit WatermarkNode(
const std::string & input, const std::string & output, const std::string & text,
const std::string & node_name = "watermark_node")
: Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true))
{
rclcpp::SensorDataQoS qos;
// Create a publisher on the input topic.
pub_ = this->create_publisher<sensor_msgs::msg::Image>(output, qos);
std::weak_ptr<std::remove_pointer<decltype(pub_.get())>::type> captured_pub = pub_;
// Create a subscription on the output topic.
sub_ = this->create_subscription<sensor_msgs::msg::Image>(
input,
qos,
[captured_pub, text](sensor_msgs::msg::Image::UniquePtr msg) {
auto pub_ptr = captured_pub.lock();
if (!pub_ptr) {
return;
}
// Create a cv::Mat from the image message (without copying).
cv::Mat cv_mat(
msg->height, msg->width,
encoding2mat_type(msg->encoding),
msg->data.data());
// Annotate the image with the pid, pointer address, and the watermark text.
std::stringstream ss;
ss << "pid: " << GETPID() << ", ptr: " << msg.get() << " " << text;
draw_on_image(cv_mat, ss.str(), 40);
pub_ptr->publish(std::move(msg)); // Publish it along.
});
}
private:
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr pub_;
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
cv::VideoCapture cap_;
cv::Mat frame_;
};
#endif // IMAGE_PIPELINE__WATERMARK_NODE_HPP_
- 进入src目录,新建文件camera_node.cpp
cd ~/dev_ws/src/cpp_intra_process_image/src
touch camera_node.cpp
- 内容如下:
#include <memory>
#include "cpp_intra_process_image/camera_node.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<CameraNode> camera_node = nullptr;
try {
camera_node = std::make_shared<CameraNode>("image");
} catch (const std::exception & e) {
fprintf(stderr, "%s Exiting..\n", e.what());
return 1;
}
rclcpp::spin(camera_node);
rclcpp::shutdown();
return 0;
}
- 进入src目录,新建文件image_view_node.cpp
cd ~/dev_ws/src/cpp_intra_process_image/src
touch image_view_node.cpp
- 内容如下:
#include <memory>
#include "cpp_intra_process_image/image_view_node.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto image_view_node = std::make_shared<ImageViewNode>("watermarked_image");
rclcpp::spin(image_view_node);
rclcpp::shutdown();
return 0;
}
- 进入src目录,新建文件watermark_node.cpp
cd ~/dev_ws/src/cpp_intra_process_image/src
touch watermark_node.cpp
- 内容如下:
#include <memory>
#include "cpp_intra_process_image/watermark_node.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto watermark_node =
std::make_shared<WatermarkNode>("image", "watermarked_image", "Hello world!");
rclcpp::spin(watermark_node);
rclcpp::shutdown();
return 0;
}
- 进入src目录,新建文件two_image_view.cpp
cd ~/dev_ws/src/cpp_intra_process_image/src
touch two_image_view.cpp
- 内容如下:
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "cpp_intra_process_image/camera_node.hpp"
#include "cpp_intra_process_image/image_view_node.hpp"
#include "cpp_intra_process_image/watermark_node.hpp"
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor executor;
// Connect the nodes as a pipeline: camera_node -> watermark_node -> image_view_node
// And the extra image view as a fork: \-> image_view_node2
std::shared_ptr<CameraNode> camera_node = nullptr;
try {
camera_node = std::make_shared<CameraNode>("image");
} catch (const std::exception & e) {
fprintf(stderr, "%s Exiting..\n", e.what());
return 1;
}
auto watermark_node =
std::make_shared<WatermarkNode>("image", "watermarked_image", "Hello world!");
auto image_view_node = std::make_shared<ImageViewNode>("watermarked_image");
auto image_view_node2 = std::make_shared<ImageViewNode>("watermarked_image", "image_view_node2");
executor.add_node(camera_node);
executor.add_node(watermark_node);
executor.add_node(image_view_node);
executor.add_node(image_view_node2);
executor.spin();
rclcpp::shutdown();
return 0;
}
- 进入src目录,新建文件all_in_one.cpp
cd ~/dev_ws/src/cpp_intra_process_image/src
touch all_in_one.cpp
- 内容如下:
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "cpp_intra_process_image/camera_node.hpp"
#include "cpp_intra_process_image/image_view_node.hpp"
#include "cpp_intra_process_image/watermark_node.hpp"
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor executor;
// Connect the nodes as a pipeline: camera_node -> watermark_node -> image_view_node
std::shared_ptr<CameraNode> camera_node = nullptr;
try {
camera_node = std::make_shared<CameraNode>("image");
} catch (const std::exception & e) {
fprintf(stderr, "%s Exiting ..\n", e.what());
return 1;
}
auto watermark_node =
std::make_shared<WatermarkNode>("image", "watermarked_image", "Hello world!");
auto image_view_node = std::make_shared<ImageViewNode>("watermarked_image");
executor.add_node(camera_node);
executor.add_node(watermark_node);
executor.add_node(image_view_node);
executor.spin();
rclcpp::shutdown();
return 0;
}
- 编译package.xml
- 在<buildtool_depend>ament_cmake</buildtool_depend>后增加
<depend>libopencv-dev</depend>
<depend>rclcpp</depend>
<depend>sensor_msgs</depend>
<depend>std_msgs</depend>
- 编译CMakelist.txt
- 在find_package(ament_cmake REQUIRED)后增加
find_package(builtin_interfaces REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rmw REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc videoio)
- 生成执行文件
include_directories(include)
# A single program with one of each of the image pipeline demo nodes.
add_executable(all_in_one
src/all_in_one.cpp)
target_link_libraries(all_in_one
rclcpp::rclcpp
${builtin_interfaces_TARGETS}
${sensor_msgs_TARGETS}
opencv_core
opencv_highgui)
# A single program with one of each of the image pipeline demo nodes, but two image views.
add_executable(two_image_view
src/two_image_view.cpp)
target_link_libraries(two_image_view
rclcpp::rclcpp
${builtin_interfaces_TARGETS}
${sensor_msgs_TARGETS}
opencv_core
opencv_highgui)
# A stand alone node which produces images from a camera using OpenCV.
add_executable(camera_node
src/camera_node.cpp)
target_link_libraries(camera_node
rclcpp::rclcpp
${builtin_interfaces_TARGETS}
${sensor_msgs_TARGETS}
opencv_core
opencv_highgui
opencv_imgproc)
# A stand alone node which adds some text to an image using OpenCV before passing it along.
add_executable(watermark_node
src/watermark_node.cpp)
target_link_libraries(watermark_node
rclcpp::rclcpp
${builtin_interfaces_TARGETS}
${sensor_msgs_TARGETS}
opencv_core
opencv_videoio)
# A stand alone node which consumes images and displays them using OpenCV.
add_executable(image_view_node
src/image_view_node.cpp)
target_link_libraries(image_view_node
rclcpp::rclcpp
${builtin_interfaces_TARGETS}
${sensor_msgs_TARGETS}
opencv_core
opencv_highgui)
- 安装相关库文件和执行文件
install(TARGETS
all_in_one
two_image_view
camera_node
watermark_node
image_view_node
DESTINATION lib/${PROJECT_NAME})
- 编译包
colcon build --symlink-install --packages-select cpp_intra_process_image
- 加载工作空间
. install/setup.bash
- 测试1:
ros2 run cpp_intra_process_image all_in_one
- 测试2:
ros2 run cpp_intra_process_image two_image_view
参考:
获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号