ROS 图像相关的命令与应用

本文翻译自 xx大学的机器人课程,以及个人使用的Ros进行图像处理时的一些问题总结:

1. ROS 中表示图像

1.1 查看图像

ROS中最基本的图像表示方式为sensor_mags/Image 类型,可以使用RVIZ对该topic所对应的图像画面进行查看,RVIZ中的Camera display功能;或者更简便的,可以使用模块image_view :

具体命令为: rosrun image_view image_view image:=topic_name

例如, 如果想要查看 Turtlebot‘s KInect sensor 捕捉的图像,可以使用命令:

rosrun image_view image_view image:=/camera/rgb/image_color compressed

rosrun image_view image_view image:=/camera/rgb/image_mono compressed

1.2. 转换图像 

大家可能已经知道,在ROS中订阅其他电脑上发出的某个topic,在传输的的时候是会占用一定的网络带宽的, 例如,在我的电脑上, 订阅  /camera/rgb/image color 话题,每秒钟会消耗掉2.5 megabytes;

之所以会出现如上的状况,是因为我们传输的都是未压缩的视频或图像数据。 当然,ROS也提供了对应的转换方法,可以将topic中的图像转换成对应的压缩格式,这种压缩方式基本只会降低图像或者视频的大小,而并不会损失掉一些图像的细节,同时,ROS将该模块都统一打包,因此我们并不需要关心具体的压缩或者解压过程。

关于image transport的最简单的例子如下:

#include 
#include 

void callback(const sensor_msgs::ImageConstPtr& msg) {
    ROS_INFO("Got image.");
}
int main(int argc, char **argv) {
    ros::init(argc, argv, "nodeName");
    ros::NodeHandle nh;
    image_transport::ImageTransport it(nh);
    image_transport::Subscriber sub = it.subscribe(topicName, queueSize, callback);
    ros::spin();
}

如果使用以上代码,必须进行对在package.xml 和CMakeLists.txt 中添加依赖库:image_transport.

注意,在上面的代码中我们使用的topicName依然是原先的topic内容,但是代码在执行的时候 会比之前的那种直接subscribe一个topic的方法多一层判断,会先调用参数∼image transport判断是否需要订阅压缩图像;

关于 ∼image transport的几种情况:

1. 如果对参数 ∼image transport不进行任何的说明或者是赋值,那么最后的结果 跟没使用这个模块是一样的效果, ROS最终会follow的topic依然是未压缩的图像;

2. 如果参数 ∼image transport设置成compressed的类型,每副图像都会以JPEG或者PNG的形式进行压缩,而后在callback之前,也就是接收到数据之后进行解压缩decompression;

3. 如果参数 ∼image transport设置成theora,视频流将会被压缩成为更小的格式,这种方式比compressed更高效一点,因为它会推算连续帧中的相似性。

使用Image_transport的另一个方法或者好处是,我们可以仅仅通过命令实现压缩或者改变压缩的方式,如下:


    
        
    
 

上面的讲解中Image_transport前的~表示这是一个node的private变量,因此使用时一般是加载node之间,作为参数出现。

 1.3 OpenCV

OpenCV是什么这里就不介绍了,ROS中其实已经整合了OpenCV,并且可以在ROS代码中进行使用;

1.3.1 编译使用opencv

如果要在ROS代码中使用Opencv,需要额外地包含OPencv的库,对应的包含方法如下:

find_package(OpenCV_REQUIRED)
target_link_libraries(your executable-name ${OpenCV_LIBS}) ​

同时还需要包含另一个包cv_bridge, 主要是用于在ROS图像与OpenCV图像之间做一个转换;

1.3.2 转换成cv::Mat的格式:

cv_bridge的一个好处是将sensor_msgs::Image类型的消息转换成为cv::Mat的形式;例如,我们可以在callback函数中进行如下设置:

#include 
#include 
...
cv_bridge::CvImagePtr cvImagePtr;
try {
    cvImagePtr = cv_bridge::toCvCopy(msg);
} catch (cv_bridge::Exception &e) {
    ROS_ERROR("cv_bridge exception: %s", e.what());
}

cvImagePtr 是cv_bridge的图像数据表达,我们可以直接使用cvImagePtr->image 来获得cv::Mat;

1.4  图像访问

一张图像其实就是一个二维数组,对cv::Mat 格式的图像,可以通过rows 和 cols 进行数据访问:

cv::Mat &Mat = cv::ImagePtr->image;
int width = mat.cols;
int height = mat.rows;  

除了检查图像的size,最主要的是我们需要对获取来的图像中的数值进行访问, OPencv中cv::Mat 可以用at进行单个像素的数值访问,cv::Mat中图像的存储可以有很多种形式,例如单通道的灰色图像以单字节的形式存储,而三通道的彩色图像,每个像素的位置存放的都是一个1*3的数组, 不仅如此,cv::Mat中还可以存储float或者double型的数据。

以Turtlebot 的/camera/rgb/image mono 为例,这是一个单通道Mono8的图像,每一个像素中存储的是一个无符号字符 unsigned char, 对这种类型数据的访问为:

cv::Mat &mat = cvImagePtr->image; ROS_INFO("image[%d,%d] = %d", i, j, (int) mat.at(i, j));

这样输出的数据中都是从0 到 255的数字,代表了不同的像素值, 0为black, 255 为white;

而话题 /camera/rgb/image color 传输的确实bgr8的图像,每个像素中存放的是三个无符号char,分别代表了red,green,blue,这种形式的图像一般通过cv::Vec3b 的形式进行访问;

cv::Mat &mat = cvImagePtr->image; 
cv::Vec3b pixel = mat.at(i,j); 
ROS_INFO("image[%d,%d] = (r=%d,g=%d,b=%d)", i, j, pixel[2], pixel[1], pixel[0]);  

1.5 图像显示 

opencv的另一个好处是可以很方便地显示图像,这里用一段代码举例:

#include   
cvNamedWindow("windowName", CV_WINDOW_AUTOSIZE);
cvStartWindowThread();
cv::imshow("windowName", mat);
cvDestroyWindow("windowName");

这样的显示方法 对比 直接在ROS中进行图像显示 的一个好处就是: 在图像显示之前,可以对图像进行编辑, 例如我们可以通过如下代码,将一副彩色图像中的某个像素值变成0 ,也就是三个通道的数值都变成黑色:

cv::Vec3b &pixel = mat.at(i,j);
pixel[0] = 0;
pixel[1] = 0;
pixel[2] = 0;

结束。

上面的教程相对是比较基础的,接下来是我自己的一些总结:

1. 首先是压缩图像部分,这部分 分为发布压缩图像订阅/查看压缩图像

发布:

将一个正常的图像topic转换成压缩图像的形式,这里用compressed形式进行举例,可以在launch文件中直接进行设置:

 

 也是同样放在一个node中作为参数出现;

订阅/显示:

如果要是查看一个压缩图像,上面说道可以用ros自己的模块image_view进行显示,但是如果要访问一个压缩图像,则需要在image_view后面进行参数添加:

rosrun image_view image_view image:=/camera/rgb/image_color  ∼image transport = compressed  

 使用:

上面从Ros的sensor_msg形式转换成opencv的cv::Mat 都是针对与原图像,这里主要说以下针对压缩图像的转换,具体代码如下:

  ros::NodeHandle nodeHandler;
  ros::Subscriber encoder_sub = nodeHandler.subscribe(
      "/odom", 10, &ImageGrabber::GrabOdom, &igb);
  ros::Subscriber image_sub = nodeHandler.subscribe(
      "/usb_cam_left/image_raw/compressed", 1, &ImageGrabber::GrabImage, &igb);
  ros::spin();

上面是main函数的部分,接下来是回调函数:

void ImageGrabber::GrabImage(const sensor_msgs::CompressedImageConstPtr &msgRGB) {
  cv::Mat image;
  try{
    image = cv::imdecode(cv::Mat(msgRGB->data),1);
  }
  catch(cv_bridge::Exception& e){
    ROS_ERROR("Could not convert to image! ");
  }
  
  cout << "get image data" << endl;
  if(!image.empty() && finishedCurTrack)
  {
    /* DO SOMETHING*/
  }
}

另外,如果是SLAM中,我们经常使用到双目,这是后就需要对双目图像进行压缩和转换,压缩部分与单目一样,但是在订阅的时候,因为SLAM双目跟踪的特殊性质,需要对双目采集的图像进行一个同步,具体如下:

    ros::NodeHandle nh;
    message_filters::Subscriber left_sub(nh, "/left_cam/image_raw/compressed", 1);
    message_filters::Subscriber right_sub(nh, "/right_cam/image_raw/compressed", 1);
    typedef message_filters::sync_policies::ApproximateTime sync_pol;
    message_filters::Synchronizer sync(sync_pol(10), left_sub,right_sub);
    sync.registerCallback(boost::bind(&ImageGrabber::GrabStereo,&igb,_1,_2));
    ros::spin();

这里需要注意在 message_filters中订阅的是sensor_msgs::CompressedImage类型的消息, 而在接下来的回调函数中使用的是指针类型的消息:

void ImageGrabber::GrabStereo(const sensor_msgs::CompressedImageConstPtr& msgLeft,const sensor_msgs::CompressedImageConstPtr& msgRight)
{
    cv::Mat left_img;
    try
    {
        left_img = cv::imdecode(cv::Mat(msgLeft->data),1);
    }
    catch (cv_bridge::Exception& e)
    {
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
    }

    cv::Mat right_img;
    try
    {
        right_img = cv::imdecode(cv::Mat(msgRight->data),1);
    }
    catch (cv_bridge::Exception& e)
    {
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
    }
    /*DO SOMETHING*/
}

 

你可能感兴趣的:(SLAM基础算法,ROS,图像,opencv,image_transport)