Ros图像与Opencv图像的相互转换

转自:https://blog.csdn.net/qq_27050183/article/details/51141998

ros wiki 原文:http://wiki.ros.org/cv_bridge/Tutorials/UsingCvBridgeToConvertBetweenROSImagesAndOpenCVImages

Ros图像与Opencv图像的相互转换(C++)(译文*来自wiki)(ROS为indigo版本)

摘要:此教程通过将ROS图像转换为OpenCV图像讲解了使ROS与OpenCV相结合的方法。教程包含一个示例节点,可以用作自己的节点模板。
 

关键词:ROS图像,OpenCV图像,CVBrideg

教程等级:中等难度

1.综述

ROS有其自己的消息格式为sensor_msgs/Image的显示图像,但是许多开发者想结合OpenCV来显示处理图像。CvBridge是ROS的一个类,此类提供了ROS与opencv相结合的接口。CvBridge能够在cv_bridge包中的vision_opencv栈中找到。

在本教程中,你将学会通过CvBridge编写一个节点并以此将ros图像转化为opencv中的CV::Mat格式。同样的,你也会学会将opencv图像转化为ros图像。

2.ros图像信息转化为opencv图像

CVBridge定义了一个包含opencv图像的CvImage类型。CvImage包含额外的sensor_msgs/Image信息,因此我们可以将上述俩者进行相互转换。CvImage类代码如下:

 
  1. class CvImage

  2. {

  3. public:

  4. std_msgs::Header header;

  5. std::string encoding;

  6. cv::Mat image;

  7. };

  8.  
  9. typedef boost::shared_ptr<CvImage> CvImagePtr;

  10. typedef boost::shared_ptr<CvImage const> CvImageConstPtr;

当将ros的sensor_msgs/Image信息转化为CvImage时,CvBridge提供俩种不同的用例。

1.在我们要修改数据的地方。我们必须复制一份ros的信息数据。

2.如果我们不修改数据。我们可以安全地分享由ros消息所拥有的数据,而不用复制。

CvBridge为向CvImage转化提供如下函数:

 
  1. <span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">// Case 1: Always copy, returning a mutable CvImage

  2. CvImagePtr toCvCopy(const sensor_msgs::ImageConstPtr& source,

  3. const std::string& encoding = std::string());

  4. CvImagePtr toCvCopy(const sensor_msgs::Image& source,

  5. const std::string& encoding = std::string());

  6.  
  7. // Case 2: Share if possible, returning a const CvImage

  8. CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr& source,

  9. const std::string& encoding = std::string());

  10. CvImageConstPtr toCvShare(const sensor_msgs::Image& source,

  11. const boost::shared_ptr<void const>& tracked_object,

  12. const std::string& encoding = std::string());</span></span></span>

输入是图像的消息指针,以及一个可选的编码参数。编码是指定的cvimage。
toCvCopy从ROS信息中创建图像数据的副本,即使当源和目的地encodings匹配。然而,你可以自由修改返回的cvimage。

toCvShare将cv::Mat点返回在ROS的消息数据,避免复制,如果源和目的编码匹配。只要你保持一份返回的cvimage,ROS的消息数据将不会释放。如果编码不匹配时,它将分配一个新的缓冲区,执行转换。您不得修改返回的cvimage,因为它可能与ROS的图像信息共享数据,这反过来又可能与其他回调共享。

注意:对tocvshare二过载更方便,当你有一个指针指向其他信息类型。(例如:stereo_msgs/DisparityImage)包含sensor_msgs/Image你要去转换。

如果没有编码(或更确切地说,空字符串),目标图像编码将与图像信息编码相同。在这种情况下tocvshare保证不复制图像数据。图像编码可以是以下任何一个opencv图像编码:

8UC[1-4]
8SC[1-4]
16UC[1-4]
16SC[1-4]
32SC[1-4]
32FC[1-4]
64FC[1-4]

对于常见的图像编码,cvbridge会选择做颜色或像素深度转换是必要的。要使用此功能,指定编码为以下字符串之一:

mono8: CV_8UC1, grayscale image
mono16: CV_16UC1, 16-bit grayscale image
bgr8: CV_8UC3, color image with blue-green-red color order
rgb8: CV_8UC3, color image with red-green-blue color order
bgra8: CV_8UC4, BGR color image with an alpha channel
rgba8: CV_8UC4, RGB color image with an alpha channel

注意,mono8和bgr8是两图像编码预计大多数OpenCV函数。

最后,cvbridge识别Bayer模式编码为OpenCV型8uc1(8位无符号,一个通道)。它不会执行转换或从Bayer模式;在一个典型的ROS系统,这是由image_proc而做的。cvbridge识别以下Bayer编码:

bayer_rggb8
bayer_bggr8
bayer_gbrg8
bayer_grbg8

3OpenCV图像转换为ROS图像

将Cvimage图像转换为ros图像,使用toImageMsg()成员函数:

 
  1. <span style="font-size:18px;"><span style="font-size:18px;">class CvImage

  2. {

  3. sensor_msgs::ImagePtr toImageMsg() const;

  4.  
  5. // Overload mainly intended for aggregate messages that contain

  6. // a sensor_msgs::Image as a member.

  7. void toImageMsg(sensor_msgs::Image& ros_image) const;

  8. };</span></span>

如果是一个已经分配的cvimage,别忘了填写头文件和域。

4 一个ROS节点例子

这里是一个节点,监听ROS图像信息话题,将图像转换为cv::Mat并在其上画圆,用opencv显示图像。然后图像将重新在ROS上显示。

在你的package.xml 和 CMakeLists.xml (或者你用 catkin_create_pkg),添加如下依赖:

 
  1. <span style="font-size:18px;"><span style="font-size:18px;">sensor_msgs

  2. cv_bridge

  3. roscpp

  4. std_msgs

  5. image_transport</span></span>

创建一个image_converter.cpp文件在你的/src 文件夹并添加如下代码:

 
  1. <span style="font-size:18px;"><span style="font-size:18px;">#include <ros/ros.h>

  2. #include <image_transport/image_transport.h>

  3. #include <cv_bridge/cv_bridge.h>

  4. #include <sensor_msgs/image_encodings.h>

  5. #include <opencv2/imgproc/imgproc.hpp>

  6. #include <opencv2/highgui/highgui.hpp>

  7.  
  8. static const std::string OPENCV_WINDOW = "Image window";

  9.  
  10. class ImageConverter

  11. {

  12. ros::NodeHandle nh_;

  13. image_transport::ImageTransport it_;

  14. image_transport::Subscriber image_sub_;

  15. image_transport::Publisher image_pub_;

  16.  
  17. public:

  18. ImageConverter()

  19. : it_(nh_)

  20. {

  21. // Subscrive to input video feed and publish output video feed

  22. image_sub_ = it_.subscribe("/camera/image_raw", 1,

  23. &ImageConverter::imageCb, this);

  24. image_pub_ = it_.advertise("/image_converter/output_video", 1);

  25.  
  26. cv::namedWindow(OPENCV_WINDOW);

  27. }

  28.  
  29. ~ImageConverter()

  30. {

  31. cv::destroyWindow(OPENCV_WINDOW);

  32. }

  33.  
  34. void imageCb(const sensor_msgs::ImageConstPtr& msg)

  35. {

  36. cv_bridge::CvImagePtr cv_ptr;

  37. try

  38. {

  39. cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);

  40. }

  41. catch (cv_bridge::Exception& e)

  42. {

  43. ROS_ERROR("cv_bridge exception: %s", e.what());

  44. return;

  45. }

  46.  
  47. // Draw an example circle on the video stream

  48. if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60)

  49. cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0));

  50.  
  51. // Update GUI Window

  52. cv::imshow(OPENCV_WINDOW, cv_ptr->image);

  53. cv::waitKey(3);

  54.  
  55. // Output modified video stream

  56. image_pub_.publish(cv_ptr->toImageMsg());

  57. }

  58. };

  59.  
  60. int main(int argc, char** argv)

  61. {

  62. ros::init(argc, argv, "image_converter");

  63. ImageConverter ic;

  64. ros::spin();

  65. return 0;

  66. }</span></span>

让我们来解读上面的代码:

<span style="font-size:18px;">#include <image_transport/image_transport.h></span>

使用image_transport发布和订阅的图像在ROS允许你订阅压缩图像流。记住,将image_transport包含进你的 package.xml中。

 
  1. <span style="font-size:18px;">#include <cv_bridge/cv_bridge.h>

  2. #include <sensor_msgs/image_encodings.h></span>

包括cvbridge头以及一些有用的常量和函数图像编码相关。记住,将cv_bridge包含进 package.xml中。

 
  1. <span style="font-size:18px;">#include <opencv2/imgproc/imgproc.hpp>

  2. #include <opencv2/highgui/highgui.hpp></span>

包括头文件对于OpenCV图像处理和图形用户界面模块。记住,将opencv2包含进package.xml。

 
  1. <span style="font-size:18px;">ros::NodeHandle nh_;

  2. image_transport::ImageTransport it_;

  3. image_transport::Subscriber image_sub_;

  4. image_transport::Publisher image_pub_;

  5.  
  6. public:

  7. ImageConverter()

  8. : it_(nh_)

  9. {

  10. // Subscrive to input video feed and publish output video feed

  11. image_sub_ = it_.subscribe("/camera/image_raw", 1,

  12. &ImageConverter::imageCb, this);

  13. image_pub_ = it_.advertise("/image_converter/output_video", 1);</span>

用image_transport订阅一个图像主题“in”和发布一个图像主题“out”。

 
  1. <span style="font-size:18px;">cv::namedWindow(OPENCV_WINDOW);

  2. }

  3.  
  4.  
  5. ~ImageConverter()

  6. {

  7. cv::destroyWindow(OPENCV_WINDOW);

  8. }</span>

OpenCV HighGUI 在开启和关闭窗口时调用创建和销毁。

 
  1. <span style="font-size:18px;">void imageCb(const sensor_msgs::ImageConstPtr& msg)

  2. {

  3. cv_bridge::CvImagePtr cv_ptr;

  4. try

  5. {

  6. cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);

  7. }

  8. catch (cv_bridge::Exception& e)

  9. {

  10. ROS_ERROR("cv_bridge exception: %s", e.what());

  11. return;

  12. }</span>

在我们的用户的回调中,我们首先将ROS图像信息通过OpenCV转换到一个适合的工作中的cvimage 。因为我们要画的图像,我们需要一个可变的复制,所以我们使用tocvcopy()。

注意,OpenCV要求彩色图像使用BGR信道规则。

你应该把你的调用使用tocvcopy() / tocvshared()来捕获转换错误,因为这些函数不会检查你的数据的有效性。

 
  1. <span style="font-size:18px;">// Draw an example circle on the video stream

  2. if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60)

  3. cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0));

  4. // Update GUI Window</span>

画一个红色圆圈的图像,然后显示在显示窗口。

<span style="font-size:18px;">cv::waitKey(3);</span>

将cvimage转换到ROS的图像信息,并且发布在“out”的话题。

要运行这个节点,你需要一个图像流。运行一个摄像头或播放一个包文件来生成图像流。现在你可以运行这个节点“in”映射到运行的图像流的话题。

如果你已经成功地转换图像为opencv的格式,你会看到一个highgui窗口的名称为“image windows”,你的图像和圆会显示。

你可以看看你的节点是否在ros上正确发布图像,通过使用rostopic或通过使用image_view查看图像。

5图像数据共享实例

在上面的例子中,我们明确的复制了图像数据,但是共享(在可能的情况下)同样容易:

 
  1. <span style="font-size:18px;">namespace enc = sensor_msgs::image_encodings;

  2.  
  3. void imageCb(const sensor_msgs::ImageConstPtr& msg)

  4. {

  5. cv_bridge::CvImageConstPtr cv_ptr;

  6. try

  7. {

  8. cv_ptr = cv_bridge::toCvShare(msg, enc::BGR8);

  9. }

  10. catch (cv_bridge::Exception& e)

  11. {

  12. ROS_ERROR("cv_bridge exception: %s", e.what());

  13. return;

  14. }

  15.  
  16. // Process cv_ptr->image using OpenCV

  17. }</span>

如果传入的消息是“bgr8”编码,cv_ptr将别名数据没有复制。如果它有一个不同的但可转换编码,比如“mono8”,cvbridge将分配一个为cv_ptr分配新的缓冲区并执行转换。如果没有异常处理这只会是一行代码,但是如果一个畸形的(或不支持)传入消息编码会降低节点。例如,如果输入图像是从image_raw话题Bayer模式的相机,cvbridge将抛出一个异常,因为它(故意)不支持Bayer到颜色的自动转换。一个稍微复杂的例子:

 
  1. <span style="font-size:18px;">namespace enc = sensor_msgs::image_encodings;

  2.  
  3. void imageCb(const sensor_msgs::ImageConstPtr& msg)

  4. {

  5. cv_bridge::CvImageConstPtr cv_ptr;

  6. try

  7. {

  8. if (enc::isColor(msg->encoding))

  9. cv_ptr = cv_bridge::toCvShare(msg, enc::BGR8);

  10. else

  11. cv_ptr = cv_bridge::toCvShare(msg, enc::MONO8);

  12. }

  13. catch (cv_bridge::Exception& e)

  14. {

  15. ROS_ERROR("cv_bridge exception: %s", e.what());

  16. return;

  17. }

  18.  
  19. // Process cv_ptr->image using OpenCV

  20. }</span>

在这种情况下,我们要使用颜色,如果可用,否则返回到单色。如果输入图像是“bgr8”或“mono8”,我们避免复制数据。

猜你喜欢

转载自blog.csdn.net/haima1998/article/details/82078777