Ros图像与Opencv图像的相互转换(C++)(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依赖如何安装 ros使用opencv_ros中的opencv依赖如何安装




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


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

class CvImage
{
public:
  std_msgs::Header header;
  std::string encoding;
  cv::Mat image;
};

typedef boost::shared_ptr<CvImage> CvImagePtr;
typedef boost::shared_ptr<CvImage const> CvImageConstPtr;


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

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


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



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


<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">// Case 1: Always copy, returning a mutable CvImage
CvImagePtr toCvCopy(const sensor_msgs::ImageConstPtr& source,
                    const std::string& encoding = std::string());
CvImagePtr toCvCopy(const sensor_msgs::Image& source,
                    const std::string& encoding = std::string());

// Case 2: Share if possible, returning a const CvImage
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr& source,
                          const std::string& encoding = std::string());
CvImageConstPtr toCvShare(const sensor_msgs::Image& source,
                          const boost::shared_ptr<void const>& tracked_object,
                          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()成员函数:


<span style="font-size:18px;"><span style="font-size:18px;">class CvImage
{
  sensor_msgs::ImagePtr toImageMsg() const;

  // Overload mainly intended for aggregate messages that contain
  // a sensor_msgs::Image as a member.
  void toImageMsg(sensor_msgs::Image& ros_image) const;
};</span></span>


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




4 一个ROS节点例子


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


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


<span style="font-size:18px;"><span style="font-size:18px;">sensor_msgs
cv_bridge
roscpp
std_msgs
image_transport</span></span>


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

<span style="font-size:18px;"><span style="font-size:18px;">#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

static const std::string OPENCV_WINDOW = "Image window";

class ImageConverter
{
  ros::NodeHandle nh_;
  image_transport::ImageTransport it_;
  image_transport::Subscriber image_sub_;
  image_transport::Publisher image_pub_;
  
public:
  ImageConverter()
    : it_(nh_)
  {
    // Subscrive to input video feed and publish output video feed
    image_sub_ = it_.subscribe("/camera/image_raw", 1, 
      &ImageConverter::imageCb, this);
    image_pub_ = it_.advertise("/image_converter/output_video", 1);

    cv::namedWindow(OPENCV_WINDOW);
  }

  ~ImageConverter()
  {
    cv::destroyWindow(OPENCV_WINDOW);
  }

  void imageCb(const sensor_msgs::ImageConstPtr& msg)
  {
    cv_bridge::CvImagePtr cv_ptr;
    try
    {
      cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
    }
    catch (cv_bridge::Exception& e)
    {
      ROS_ERROR("cv_bridge exception: %s", e.what());
      return;
    }

    // Draw an example circle on the video stream
    if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60)
      cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0));

    // Update GUI Window
    cv::imshow(OPENCV_WINDOW, cv_ptr->image);
    cv::waitKey(3);
    
    // Output modified video stream
    image_pub_.publish(cv_ptr->toImageMsg());
  }
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "image_converter");
  ImageConverter ic;
  ros::spin();
  return 0;
}</span></span>


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

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


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




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


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




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


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



<span style="font-size:18px;">ros::NodeHandle nh_;
  image_transport::ImageTransport it_;
  image_transport::Subscriber image_sub_;
  image_transport::Publisher image_pub_;
  
public:
  ImageConverter()
    : it_(nh_)
  {
    // Subscrive to input video feed and publish output video feed
    image_sub_ = it_.subscribe("/camera/image_raw", 1, 
      &ImageConverter::imageCb, this);
    image_pub_ = it_.advertise("/image_converter/output_video", 1);</span>


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



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


  ~ImageConverter()
  {
    cv::destroyWindow(OPENCV_WINDOW);
  }</span>


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



<span style="font-size:18px;">void imageCb(const sensor_msgs::ImageConstPtr& msg)
  {
    cv_bridge::CvImagePtr cv_ptr;
    try
    {
      cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
    }
    catch (cv_bridge::Exception& e)
    {
      ROS_ERROR("cv_bridge exception: %s", e.what());
      return;
    }</span>


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

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


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




<span style="font-size:18px;">// Draw an example circle on the video stream
    if (cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60)
      cv::circle(cv_ptr->image, cv::Point(50, 50), 10, CV_RGB(255,0,0));
// 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图像数据共享实例


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


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

void imageCb(const sensor_msgs::ImageConstPtr& msg)
{
  cv_bridge::CvImageConstPtr cv_ptr;
  try
  {
    cv_ptr = cv_bridge::toCvShare(msg, enc::BGR8);
  }
  catch (cv_bridge::Exception& e)
  {
    ROS_ERROR("cv_bridge exception: %s", e.what());
    return;
  }

  // Process cv_ptr->image using OpenCV
}</span>


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

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

void imageCb(const sensor_msgs::ImageConstPtr& msg)
{
  cv_bridge::CvImageConstPtr cv_ptr;
  try
  {
    if (enc::isColor(msg->encoding))
      cv_ptr = cv_bridge::toCvShare(msg, enc::BGR8);
    else
      cv_ptr = cv_bridge::toCvShare(msg, enc::MONO8);
  }
  catch (cv_bridge::Exception& e)
  {
    ROS_ERROR("cv_bridge exception: %s", e.what());
    return;
  }

  // Process cv_ptr->image using OpenCV
}</span>


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