译:ROS图像和OpenCV图像相互转换

ROS图像和OpenCV图像相互转换

描述: 本文将描述如何使用cv_bridge来将ROS图像转换为OpenCV图像,以及OpenCV转为ROS图像。

1. 概念

在ROS中是以自己的sensor_msgs/Image格式对图像进行处理的,但开发者可能会希望以OpenCV的格式对图像进行处理。CvBridge就是一个提供ROS和OpenCV间接口的ROS库,它可以在vision_opencv Stack的cv_bridge包中找到。

在本文中,你将学习如何编写一个利用CvBridge将ROS图像格式转换为OpenCV中的cv::Mat格式。还将学习如何将OpenCV图像转换为ROS格式以在ROS中发布。

这里写图片描述

1.1 从C-Tutle或更早的版本代码迁移

关于OpenCV,在ROS Diamondback中有很大的api变化,虽然后向兼容维护了一阵,但从hydro开始有些已经被移除了,如sensor_msgs/CvBridge。关于迁移的问题见链接

2. 转换ROS图像到OpenCV图像

CvBridge中定义了一个包含OpenCV图像及其编码、ROS header的CvImage类型。CvImage中包含了sensor_msgs/Image中的所有信息,因此我们可以在这两者间相互转换。CvImage的class如下:

namespace cv_bridge {

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;

}

当从sensor_msgs/Image消息转换到CvImage时,CvBridge认为有两种不同的用例:

  1. 我们想修改数据,需要拷贝一份数据。
  2. 我们不想修改数据,但需要使用CvImage格式,所以可以通过const指针共享该数据。

CvBridge中提供了以下函数来转换到CvImage:

// 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());

函数的输入是一个图像指针,以及一个可选的编码参数用于规定目标CvImage的编码。

toCvCopy创建一个图像数据的拷贝,你可以对返回的CvImage进行修改。

toCvShare将会返回一个指向ROS消息的cv::Mat const指针防止修改,只要你有返回的CvImage指针的拷贝,ROS消息就不会被释放。如果编码不匹配,ROS将分配一个新的buffer并执行转换,但你还是不能对其进行修改。

注:当你有一个包含sensor_msgs/Image的其他消息类型的指针时,使用toCvShare的第二种重载方法将更为方便。

如果没有给定编码信息,目标图像的编码将与源图像一样,在这种情况下toCvShare能保证不会对数据进行拷贝。图像编码可以是一下任意一种OpenCV支持的图像编码:

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

对于某些常用的编码,CvBridge提供了可选的color或pixel depth的转换,要想使用这个特性,需要指定以下编码格式:

  • 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也可以识别OpenCV中8UC1类型的Bayer pattern编码,CvBridge将不会对Bayer pattern进行转换,一般是由image_proc进行转换的。CvBridge可以识别一下以下集中Bayer编码:

  • bayer_rggb8
  • bayer_bggr8
  • bayer_gbrg8
  • bayer_grbg8

3. 将OpenCV图像转换为ROS图像消息

要转换CvImage为ROS图像消息,可以使用toImageMsg()成员方法:

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;
};

如果CvImage是你自己创建的,不要忘了填充header和编码字段。对于自己创建CvImage的例子,可以参考发布图像教程

4. 一个ROS节点例子

这里展示一个监听ROS图像消息话题的节点,并将该图像转换为cv::Mat格式,然后使用OpenCV在图像上画一个圆并进行显示。最后该图像将在ROS中重新发布。

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

sensor_msgs
cv_bridge
roscpp
std_msgs
image_transport

在src文件夹中创建image_converter.cpp文件,内容如下:

#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_)
  {
    // Subscribe 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;
}

对代码进行分解:

#include <image_transport/image_transport.h>

在ROS中使用image_transport发布订阅图像可以让你订阅压缩后的图像流,记得在package.xml中include image_transport。

#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>

include CvBridge的头文件以及image encodings(包含了很多有用的常量和函数),记得在package.xml中include cv_bridge。

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

includeOpenCV的图像处理和GUI模块头文件,记得在package.xml中include opencv2。

  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);

使用image_transport订阅发布图像话题。

    cv::namedWindow(OPENCV_WINDOW);
  }

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

在初始化和析构时调用OpenCV HighGUI来创建及销毁窗口。

  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;
    }

在我们的回调函数中,首先将ROS图像消息转换为了CvImage以在OpenCV中使用。因为我们需要在图像中画圆,所以需要一个图像的拷贝,应使用toCvCopy()。sensor_msgs::image_encodings::BGR8是”bgr8”字符串常量。

调用toCvCopy()/toCvShared()时需要捕获异常,因为这些函数不会校验数据的有效性。

    // 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());

将CvImage转为ROS图像消息并进行发布。

运行这个节点你需要一个图像流,启动一个摄像头或者回放一个bag文件来生成图像流。现在你可以将”in”topic重新映射remaaping到真实的图像流话题。

如果你成功地转换为OpenCV图像,你将在创建的窗口中看到添加圆圈之后的图像。

你可以通过rostopic或image_view查看图像来确认节点是否正确地发布了图像。

5. 共享图像数据的例子

在上节中我们创建了图像的拷贝,但共享图像也很容易:

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
}

如果输入图像的编码是”bgr8”,cv_ptr将会是图像数据的一个别名而非拷贝。如果输入图像不是”bgr8”编码但是可转换为”bgr8”编码(如”mono8”),CvBridge将会为cv_ptr分配一个新的buffer并执行转换。如果没有异常捕获语句的话一行代码就能共享图像了,但可能输入图像的编码无法转换为目标编码从而导致节点崩溃。例如,当输入图像是从一个Bayer pattern摄像机的image_raw话题接收的,CvBridge将会抛出异常,因为不支持Bayer到color的自动转换。

一个稍微更复杂的例子:

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
}

在这个例子中,如果可以的话我们将使用color的编码,不行的话就是用monochrome类型的编码,如果输入图像是”bgr8”或”mono8”编码,将不会进行拷贝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章