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

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类代码如下:
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”,我们避免复制数据。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值