本文参考在ROS中 opencv 发布和接收图像消息
2018-04-21 20:47:22 博瓦 阅读数 5859更多
分类专栏: ROS实例学习,记录自己的理解和所用知识,遇见的错误,解决方案及心得体会(斜体)。
首先进入到你的工作空间,比如我的工作空间是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/: 没有那个文件或目录
原因分析:
catkin_ws文件夹位于dev目录下,正确路径应为~/dev/catkin_ws。
解决方案:
方案1:路径变为
cd ~/dev/catkin_ws/
方案2:在home文件夹中新建catkin_ws工作空间,但是要删除原本dev下的catkin_ws工作空间,否则会产生干扰。本文采用此种方法。
在学习从摄像头获取视频之前,我们最好先学会如何发布一个图像消息,简单熟悉之后,再学习如何发布视频。
首先入功能包,创建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标准库头文件
#include //C++标准输入输出库
#include //opencv2标准头文件
#include //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
#include
#include
#include
首先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();
}
}
发布图片消息,使消息类型匹配的节点订阅该消息。
#include
#include
#include
#include
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
为了是上述两个节点编译成功,我们必须对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"
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
#include
#include
#include
#include
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节点中必须包含。
#include
image_transport 头文件用来在ROS系统的话题上发布和订阅图像消息
#include
opencv2标准头文件,调用opencv库
#include
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
#include
#include
#include
#include
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窗口。