1. 程式人生 > >ROS影象和OpenCV影象之間的轉換(C ++)

ROS影象和OpenCV影象之間的轉換(C ++)

1.概念

ROS以自己的sensor_msgs / Image訊息格式傳遞影象,但許多使用者希望將影象與OpenCV結合使用。

CvBridge是一個ROS庫,提供ROS和OpenCV之間的介面。

在本教程中,您將學習如何編寫使用CvBridge將ROS影象轉換為OpenCV cv :: Mat格式的節點。

您還將學習如何將OpenCV影象轉換為ROS格式,並以通過ROS釋出。

cvbridge4.png

 1.1 從C-Turtle或更早版本編寫的程式碼遷移

關於OpenCV有一個主要的改變是ROS Diamondback,其向後相容性已經維持了一段時間,但在較新的發行版(hydro)中被刪除,比如sensor_msgs / CvBridge。檢視

the design decision.。此外this QA是有幫助的。

2. 將ROS影象訊息轉換為OpenCV影象

CvBridge定義了一個包含OpenCV影象的CvImage型別,its encoding and a ROS header

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;

}

將ROS sensor_msgs / Image訊息轉換為CvImage時,CvBridge會識別兩個不同的用例:

  1. 我們想要就地修改資料。我們必須複製ROS訊息資料。
  2. 我們不會修改資料。我們可以安全地共享ROS訊息所擁有的資料,而不是複製。

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

輸入是影象訊息指標,以及可選的編碼引數。編碼 refers to目標CvImage。

即使源和目標編碼匹配,toCvCopy也會從ROS訊息建立影象資料的副本。不過,您可以自由修改返回的CvImage。

如果源和目標編碼匹配,toCvShare將在ROS訊息資料上指向返回的CV::Mat,避免複製。只要您還擁有返回的CvImage的副本,就不會釋放ROS訊息資料。

如果編碼不匹配,它將分配一個新緩衝區並執行轉換。您不能修改返回的CvImage,因為它可能與ROS影象訊息共享資料,而ROS影象訊息又可能與其他回撥共享。

注意:如果您指向包含要轉換的sensor_msgs / Image的其他訊息型別(例如stereo_msgs / DisparityImage),則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可根據需要選擇進行顏色或畫素深度轉換。要使用此功能,請將編碼指定為以下字串之一:

  • mono8:CV_8UC1,灰度影象

  • mono16:CV_16UC1,16位灰度影象

  • bgr8:CV_8UC3,彩色影象,藍綠紅色順序

  • rgb8:CV_8UC3,帶有紅綠藍顏色順序的彩色影象

  • bgra8:CV_8UC4,帶有alpha通道的BGR彩色影象

  • rgba8:CV_8UC4,帶有alpha通道的RGB彩色影象

請注意,mono8和bgr8是大多數OpenCV函式所期望的兩種影象編碼。

最後,CvBridge會將Bayer pattern encodings識別為具有OpenCV型別8UC1(8位無符號,一個通道)。它不會與Bayer pattern進行轉換; 在典型的ROS系統中,這是由image_proc完成的。CvBridge認可以下Bayer pattern

  • 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是您自己分配的,don't forget to fill in the header and encoding fields

有關自己分配一個的示例,請參閱釋出影象教程

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

我們分解上面的節點:

#include <image_transport/image_transport.h>

使用image_transport在ROS中釋出和訂閱影象,並允許您訂閱壓縮影象流。

請記住在package.xml中包含image_transport。

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

包括CvBridge的標頭檔案,以及與影象編碼相關的有用常量和函式。請記住在package.xml中包含cv_bridge。

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

包括OpenCV影象處理和GUI模組的標頭檔案。請記住在package.xml中包含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);

訂閱影象主題“in”並使用image_transport釋出影象主題“out” 。

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

在我們的訂閱者回調中,我們首先將ROS影象訊息轉換為適合使用OpenCV 的CvImage。由於我們要繪製圖像,我們需要一個可變的副本,因此我們使用toCvCopy()。sensor_msgs :: image_encodings :: BGR8只是“bgr8”的常量,但不太容易出現錯別字。

請注意,OpenCV期望彩色影象使用BGR通道順序。

您應始終將呼叫包裝到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::waitKey(3);

將CvImage轉換為ROS影象訊息並將其釋出到“out”主題上。

要執行節點,您需要一個影象流。執行相機或播放包檔案以生成影象流。

現在,您可以執行此節點,將“in” 重新對映到實際的影象流主題。

如果您已成功將影象轉換為OpenCV格式,您將看到一個名為“影象視窗”的HighGui視窗,並顯示您的影象+圓圈。

您可以使用rostopic或使用image_view檢視影象,檢視您的節點是否通過ROS正確釋出影象。

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將對其資料進行引用而不進行復制。如果有不同,但可以進行轉換編碼,比如“MONO8”,CvBridge將分配一個新的緩衝區cv_ptr並執行轉換。如果沒有異常處理,這隻會是一行程式碼,但是具有格式錯誤(或不支援)編碼的傳入訊息會導致節點崩潰。例如,如果傳入影象來自Bayer pattern攝像機的image_raw主題,則CvBridge將丟擲異常,因為它(故意)不支援Bayer-to-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
}

在這種情況下,我們想要使用顏色那麼就用,否則回到單色。如果傳入的影象是“bgr8”或“mono8”,我們避免複製資料。