在ROS中 opencv 发布和接收图像消息 学习总结

本文参考在ROS中 opencv 发布和接收图像消息
2018-04-21 20:47:22 博瓦 阅读数 5859更多
分类专栏: ROS
实例学习,记录自己的理解和所用知识,遇见的错误,解决方案及心得体会(斜体)。

1、创建相关功能包

首先进入到你的工作空间,比如我的工作空间是catkin_ws,然后进入src 目录下,使用catkin_create_pkg 命令创建你的功能包,以my_image_transport 功能包为例,注意功能包后面参数为该功能包的依赖项。

 1. $ cd catkin_ws/  	
 2. $ cd src/ 			
 3. $ catkin_create_pkg my_image_transport image_transport cv_bridge			

注释:

1.切换到catkin_ws工作目录下
2.切换到src路径下
3.创建ROS功能包:功能包名称 my_image_transport,依赖功能包image_transport cv_bridge

然后回到工作空间内编译该功能包

1. $ cd ..
2. $ catkin_make

注释:

1.返回上级目录
2.构建~/catkin_ws/src目录中所有的功能包

ps:如果不带任何参数的话,catkin_make 默认编译工作空间下的所有功能包,如果你不怕麻烦的话可以使用这个参数编译特定的功能包:

  catkin_make-DCATKIN_WHITELIST_PACKAGES="package1;package2"

或者使用

catkin_make --pkg package_name 

注释:

构建package_name功能包

同时,更新初始化文件

 $ source devel/setup.bash

注释

执行devel/setup.bash中的命令

所用知识:

1.cd命令
用于切换工作路径,格式为“cd [目录名称]”(注意中间空格)
常用cd命令:

cd 进入用户主目录
cd ~ 进入用户主目录
cd / 进入根目录
cd . 当前目录
cd - 返回到上一次所在目录
cd … 返回上级目录
cd …/… 返回上两级目录
cd !$ 把上个命令的参数作为cd 参数使用
cd / 进入根目录
cd . 当前目录

2.创建功能包
参考《ROS机器人编程》

4.9.1. 创建功能包
创建ROS功能包的命令如下。
$ catkin _ create _ pkg [功能包名称] [依赖功能包 1 ] [依赖功能包 n ] “catkin_create_pkg”命令在创建用户功能包时会生成catkin 构建系统所需的CMakeLists.txt和package.xml文件的包目录。

3.catkin_make
参考《linux就该这么学》P123

*catkin_make:基于catkin 构建系统的构建

bash catkin_make [选项]

catkin_make是构建用户创建的功能包或构建下载的功能包的命令。以下示例是构建 ~/catkin_ws/src目录中所有功能包的示例。
$ cd ~/catkin_ws && catkin_ make
如果要只构建一部分功能包,而不是全部功能包,请使用“–pkg
[包名]”选项来运行,如下所示:
$ catkin_make -- pkg user_ros_tutorials*

4.source
摘自百度:吴亮弟

命令用法:
source FileName
作用:在当前bash环境下读取并执行FileName中的命令 注:该命令通常用命令“.”来替代。
如:source /etc/profile 与 . /etc/profile是等效的。 注意:source命令与shell scripts的区别是, source在当前bash环境下执行命令,而scripts是启动一个子shell来执行命令。这样如果把设置环境变量(或alias等等)的命令写进scripts中,就只会影响子shell,无法改变当前的BASH,所以通过文件(命令列)设置环境变量时,要用source 命令。

问题:

1.根目录与主目录区别?
在这里插入图片描述
在这里插入图片描述在这里插入图片描述答:如图,dev文件夹位于home目录中,故在根目录中切换显示:没有那个文件或目录。
一、根目录是 /,是树状形式目录的根,只有一个,整个系统最重要的一个目录,因为不但所有的目录都是由根目录衍生出来的,同时根目录也与开机/还原/系统修复等动作有关,即相当于Windows的C盘。
二、主目录是用户的HOME目录,添加用户的时候指定的。对于不同用户,主目录不同。
例如:对于用户名为user的用户,缺省的HOME目录是/home/user,root用户例外,它的缺省HOME目录是/root。
在其他地方调用主目录使用~/

2.bash cd: catkin_ws/: 没有那个文件或目录
错误:bash cd: catkin_ws/: 没有那个文件或目录
原因分析:
catkin_ws文件夹位于dev目录下,正确路径应为~/dev/catkin_ws。

在这里插入图片描述
解决方案:
方案1:路径变为

cd ~/dev/catkin_ws/

方案2:在home文件夹中新建catkin_ws工作空间,但是要删除原本dev下的catkin_ws工作空间,否则会产生干扰。本文采用此种方法。

实践结果
在这里插入图片描述

2.在ROS中传递图像

在学习从摄像头获取视频之前,我们最好先学会如何发布一个图像消息,简单熟悉之后,再学习如何发布视频。

2.1、创建一个图像发布者程序

首先入功能包,创建src目录,在里面创建如下代码:

   1. $ cd src
   2. $ vi my_publisher.cpp

注释:

1.目录切换至src
2.使用vim文本编辑器创建 my_publisher.cpp文件

所用知识

vim文本编辑器,参考《linux就该这样学》4.1vim文本编辑器及
Linux vi/vim 的区别和用法

问题:

在这里插入图片描述
在这里插入图片描述
如上图 1所示,功能包my_image_transport中不存在src目录,所以需先创建该目录,如上图2所示,代码如下:先将目录切换至catkin_ws/src/my_image_transport/中,然后创建src目录,切换至catkin_ws/src/my_image_transport/src目录下

cd src/
cd my_image_transport/
mkdir src
cd src

另:

include 			 → 头文件目录
src 	             → 源代码目录
CMakeLists.txt 		 → 构建配置文件			
package.xml 		 → 功能包配置文件

如果你已经对ros中的各种命令比较熟悉,你可以采取更简单的命令

$ rosed my_image_transport my_publisher.cpp

注释:

编辑my_image_transport功能包中的my_publisher.cpp文件。

所用知识:

参考《ROS机器人编程》P96

5.2.3. rosed:ROS编辑命令

bash bash rosed [功能包名称] [文件名称]
该命令用于编辑功能包中的特定文件。运行时,它会用用户设置的编辑器打开文件。用于快速修改相对简单的内容。

ps: 如果上述命令不管用,那么在运行上述命令之前最好先运行下面的命令,每次遇到类似的问题都可以运行这条命令,屡试不爽!

$ source ~/catkin_ws/devel/setup.bash

注:

这个问题是因为需要使用source刷新环境,每次进行catkin_make之后,都要进行source。可能由于某种操作导致需要刷新环境,比如当加入了新的package编译完成后,也要进行source刷新环境,否则会出现找不到“package not found” 的问题。
解决方法为:

gedit ~/.bashrc

注释:打开.bashrc文件
然后在.bashrc文件最后一句加上下面指令:

source ~/catkin_ws/devel/setup.bash

在这里插入图片描述
这样在每次打开终端时,让系统自动刷新工作空间环境。在这个工作空间下的所有package都可以编译后就可以直接运行了,不用再source。注意此处的目录应为你工作空间内devel文件夹中的setup.bash文件。

下面是my_publisher.cpp 文件里的内容。

  #include <ros/ros.h> //ros标准库头文件
    #include <image_transport/image_transport.h> //C++标准输入输出库
    #include <opencv2/highgui/highgui.hpp> //opencv2标准头文件
    #include <cv_bridge/cv_bridge.h> //cv_bridge中包含CvBridge库
     
    int main(int argc, char** argv)
    {
      ros::init(argc, argv, "image_publisher");
      ros::NodeHandle nh;
      image_transport::ImageTransport it(nh);
      image_transport::Publisher pub = it.advertise("camera/image", 1);
     
      cv::Mat image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);
      sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();
     
      ros::Rate loop_rate(5);
      while (nh.ok()) {
        pub.publish(msg);
        ros::spinOnce();
        loop_rate.sleep();
      }
    }

我们来一步一步地分析一下这段代码。

   #include <ros/ros.h>
    #include <image_transport/image_transport.h>
    #include <opencv2/highgui/highgui.hpp>
    #include <cv_bridge/cv_bridge.h>

首先ros.h这个头文件是所有的ros节点中必须要包含的,下面三个分别是实现图像的发布和订阅,调用opencv库(当然前提是你在你的系统里已经安装了OpenCV),完成opencv图像格式转化为ROS图像格式所要用到的头文件。另外,在package.xml文件中我们也需要根据所需头文件配置相关的依赖。(下节我们会介绍到)

image_transport::ImageTransport it(nh);

用之前声明的节点句柄初始化it,其实这里的it和nh的功能基本一样,你可以向之前一样使用it来发布和订阅相消息。

image_transport::Publisher pub = it.advertise("camera/image", 1);

告诉节点管理器(roscore)我们要在camera/image话题上发布图像,这里第一个参数是话题的名称,第二个是缓冲区的大小(即消息队列的长度,在发布图像消息时消息队列的长度只能是1)。

cv::Mat image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);

根据运行时给定的参数(图像文件的路径)读取图像,这是opencv里面的函数,具体可以参考opencv的相关资料。

sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();

将opencv格式的图像转化为ROS所支持的消息类型,从而发布到相应的话题上。

   ros::Rate loop_rate(5);
      while (nh.ok()) {
        pub.publish(msg);
        ros::spinOnce();
        loop_rate.sleep();
      }
    }

发布图片消息,使消息类型匹配的节点订阅该消息。

2.2、创建订阅者节点

   #include <ros/ros.h>
    #include <image_transport/image_transport.h>
    #include <opencv2/highgui/highgui.hpp>
    #include <cv_bridge/cv_bridge.h>
     
    void imageCallback(const sensor_msgs::ImageConstPtr& msg)
    {
      try
      {
        cv::imshow("view", cv_bridge::toCvShare(msg, "bgr8")->image);
      }
      catch (cv_bridge::Exception& e)
      {
        ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str());
      }
    }
     
    int main(int argc, char **argv)
    {
      ros::init(argc, argv, "image_listener");
      ros::NodeHandle nh;
      cv::namedWindow("view");
      cv::startWindowThread();
      image_transport::ImageTransport it(nh);
      image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback);
      ros::spin();
      cv::destroyWindow("view");
    }

前面重复的部分这里不在赘述,我只介绍一下几段新的代码。

void imageCallback(const sensor_msgs::ImageConstPtr& msg)

这是一个回调函数,当有新的图像消息到达camera/image时,该函数就会被调用。

cv::imshow(“view”, cv_bridge::toCvShare(msg, “bgr8”)->image);

这段代码用于显示捕捉到的图像,其中

cv_bridge::toCvShare(msg, “bgr8”)->image

用于将ROS图像消息转化为Opencv支持的图像格式(采用BGR8编码方式)。

其实这部分代码的用法恰好与上一节中发布者节点中的

CvImage(std_msgs::Header(), “bgr8”, image).toImageMsg();

作用相反。

补充

同2.1,在src目录下创建订阅者程序:

cd src
vi my_subscriber.cpp

在这里插入图片描述

2.3、配置相关文件

为了是上述两个节点编译成功,我们必须对CMakeList.txt和package.xml做一些修改。首先在CMakeList.txt添加如下几行代码

   find_package(OpenCV)
    include_directories(include ${OpenCV_INCLUDE_DIRS})
    #build my_publisher and my_subscriber
    add_executable(my_publisher src/my_publisher.cpp)
    target_link_libraries(my_publisher ${catkin_LIBRARIES})
     
    add_executable(my_subscriber src/my_subscriber.cpp)
    target_link_libraries(my_subscriber ${catkin_LIBRARIES})

注释:

find_package(OpenCV)

find_package项是进行构建所需的组件包,这是让用户先创建依赖包的选项。

include_directories(include ${OpenCV_INCLUDE_DIRS})

include_directories设置包含目录。

 #build my_publisher and my_subscriber
add_executable(my_publisher src/my_publisher.cpp)

add_executable配置可执行文件。引用src/my_publisher.cpp文件生成my_publisher可执行文件。如果要创建两个以上的可执行文件,需追加add_executable项目。

 target_link_libraries(my_publisher ${catkin_LIBRARIES})

target_link_libraries是在创建特定的可执行文件之前将库和可执行文件进行链接的选项。

然后打开package.xml,在里面添加如下代码,

  <buildtool_depend>catkin</buildtool_depend> //描述构建系统的依赖关系,使用catkin构建系统。
  <build_depend>cv_bridge</build_depend>	//依赖功能包cv_bridge
  <build_depend>image_transport</build_depend>	//依赖功能包image_transport
  <build_depend>roscpp</build_depend>	//依赖功能包roscpp
  <build_depend>rospy</build_depend>	//依赖功能包rospy
  <build_depend>std_msgs</build_depend>		//依赖功能包std_msgs
  <build_export_depend>cv_bridge</build_export_depend>		//依赖ROS中未指定的元功能包cv_bridge
  <build_export_depend>image_transport</build_export_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>cv_bridge</exec_depend>
  <exec_depend>image_transport</exec_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>

配置好上述文件之后你就可以编译节点了!

   $ cd ~/catkin_ws
    $ catkin_make -DCATKIN_WHITHELIST_PACKAGES="my_image_transport"

问题:

在这里插入图片描述造成该问题的原因是:CMakeList.txt中

target_link_libraries(my_publisher ${catkin_LIBRARIES})

中没有加${OpenCV_LIBRARIES}库,应改为:

  target_link_libraries(my_publisher ${catkin_LIBRARIES} ${OpenCV_LIBRARIES})
target_link_libraries(my_subscriber ${catkin_LIBRARIES} ${OpenCV_LIBRARIES})

在这里插入图片描述
编译成功之后你就可以运行你写好的节点了。如果没有运行成功,就检查一下相关的配置文件或者节点代码中有无错误。在运行节点之前一定要先打开节点管理器。打开终端运行roscore,然后打开另一终端运行下述命令,

$ rosrun my_image_transport my_publisher /home/viki/Pictures/th.jpeg

上述命令有三个参数,分别是包名,节点名,所传递的图像的路径。如果不成功,就试试我之前提到的那个source命令。

再打开一个终端,输入rostopic list查看当前的主题清单。
下面运行订阅者节点,

$ rosrun my_image_transport my_subscriber

运行效果如下图所示,
下面我们看一下当前活动的节点,

$ rosnode list

为了便于理解各个节点之间的交互情况,我们可以运行一个辅助节点rqt_graph。

$ rosrun rqt_graph rqt_graph

效果如下图所示,

如果你想优雅的关闭每个节点,那么请使用rosnode kill命令。当然你使用ctrl+c或者直接关闭终端也可以关闭节点,但这种方法不免过于简单粗暴,而且并未完全的关闭节点,因为节点管理器那里仍然有记录。

现在我们已经学会了如何在ros网络里发布图像消息.现在我们同样可以用类似的方法发布视频消息,下面我们以从摄像头获取视频数据为例进行简单的讲解,当然你也可以读取视频文件再发布出去.

问题

在这里插入图片描述
原因:原代码有误。
改为:

my_publisher.cpp

  #include <ros/ros.h>
    #include <image_transport/image_transport.h>
    #include <opencv2/highgui/highgui.hpp> 
    #include <cv_bridge/cv_bridge.h>
    #include <opencv2/opencv.hpp>
    int main(int argc, char** argv)
    {
      ros::init(argc, argv, "image_publisher");
      ros::NodeHandle nh;
      image_transport::ImageTransport it(nh);
      image_transport::Publisher pub = it.advertise("camera/image", 1);
     
        cv::Mat image = cv::imread("/home/rosfun/catkin_ws/src/my_image_transport/src/one.jpg", CV_LOAD_IMAGE_COLOR);
        sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();

     
      ros::Rate loop_rate(5);
      while (nh.ok()) 
      {
        pub.publish(msg);
        ros::spinOnce();
        loop_rate.sleep();
      }
    }

注释:

#include <ros/ros.h>

ros标准库头文件,所有ros节点中必须包含。

 #include <image_transport/image_transport.h>

image_transport 头文件用来在ROS系统的话题上发布和订阅图像消息

#include <opencv2/highgui/highgui.hpp> 

opencv2标准头文件,调用opencv库

 #include <cv_bridge/cv_bridge.h>

cv_bridge中包含CvBrige库,是完成opencv图像格式转化为ROS图像格式所要用到的头文件

  int main(int argc, char** argv)

节点主函数

  ros::init(argc, argv, "image_publisher");

初始化节点名称

 ros::NodeHandle nh;

声明一个节点句柄来与ROS系统进行通信

image_transport::ImageTransport it(nh);

用之前声明的节点句柄初始化it,其实这里的it和nh的功能基本一样,你可以向之前一样使用it来发布和订阅相消息。

   image_transport::Publisher pub = it.advertise("camera/image", 1);

告诉节点管理器(roscore)我们要在camera/image话题上发布图像,发布者Publisher pub,第一个参数是话题的名称,第二个是缓冲区的大小(即消息队列的长度,在发布图像消息时消息队列的长度只能是1)。
NodeHandle::advertise()将会返回 image_transport::Publisher对象,该对象有两个作用,首先是它包括一个publish()方法可以在指定的话题上发布消息,其次,当超出范围之外的时候就会自动的处理。

  cv::Mat image = cv::imread("/home/rosfun/catkin_ws/src/my_image_transport/src/one.jpg", CV_LOAD_IMAGE_COLOR);

根据运行时给定的参数(图像文件的路径)读取图像,这是opencv里面的函数,具体可以参考opencv的相关资料。

sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();

将opencv格式的图像转化为ROS所支持的消息类型,从而发布到相应的话题上。

   ros::Rate loop_rate(5);	//设置循环频率为10hz。
      while (nh.ok()) 
      {
        pub.publish(msg);
        ros::spinOnce();	//ROS消息回调处理函数,调用后还可以继续执行之后的程序.
        loop_rate.sleep();	//休眠一下,使程序满足前面所设置的5hz的要求。 
      }

发布图片消息,使消息类型匹配的节点订阅该消息。

my_subscribe.cpp

    #include <ros/ros.h>
    #include <image_transport/image_transport.h>
    #include <opencv2/highgui/highgui.hpp> 
    #include <cv_bridge/cv_bridge.h>
    #include <opencv2/opencv.hpp>
	
    void imageCallback(const sensor_msgs::ImageConstPtr& msg)
    {

        cv::imshow("view", cv_bridge::toCvShare(msg, "bgr8")->image);
        cv::waitKey(30);
    
    }
     
    int main(int argc, char **argv)
    {
      ros::init(argc, argv, "image_listener");
      ros::NodeHandle nh;
      cv::namedWindow("view");
      cv::startWindowThread();
      image_transport::ImageTransport it(nh);
      image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback);
      ros::spin();
      cv::destroyWindow("view");
    }

注释:

void imageCallback(const sensor_msgs::ImageConstPtr& msg)

这是一个回调函数,当有新的图像消息到达camera/image时,该函数就会被调用。

cv::imshow("view", cv_bridge::toCvShare(msg, "bgr8")->image);

这段代码用于显示捕捉到的图像,其中

cv_bridge::toCvShare(msg, "bgr8")->image

用于将ROS图像消息转化为Opencv支持的图像格式(采用BGR8编码方式)。
其实这部分代码的用法恰好与上一节中发布者节点中的

CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();

作用相反。

  cv::waitKey(30);

它显示指定的图像,毫秒。

1.waitKey()与waitKey(0),都代表无限等待,waitKey函数的默认参数就是int delay = 0,故这俩形式本质是一样的。
2.waitKey(n),等待n毫秒后,关闭显示的窗口。

 cv::namedWindow("view");

新建一个显示窗口,可以指定窗口的类型。此处窗口名字view。

  cv::startWindowThread();

新开一个线程实时刷新图片显示。

  image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback);

告诉节点管理器(roscore)我们要在camera/image话题上订阅图像,订阅者Subscriber sub,第一个参数是话题的名称,第二个是缓冲区的大小。

ros::spin();

用于调用后台函数,等待接收消息。在接收到消息时执行后台函数 。调用后不会再返回 。

  cv::destroyWindow("view");

关闭view窗口。

最终结果:

在这里插入图片描述

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值