Ros图像与Opencv图像的相互转换(C++)

时间:2024-05-19 12:08:13

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

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图像。

Ros图像与Opencv图像的相互转换(C++)

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>  
让我们来解读上面的代码:
  1. <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>  
画一个红色圆圈的图像,然后显示在显示窗口。
  1. <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”,我们避免复制数据。