转载于:https://www.jianshu.com/p/9a5a0433f5e4
1. 唠唠叨叨
一直在寻求一个稳定博客发布平台,能够提供的模式多样,排版的样式能够多样化选择,同时博客发布要方便,同时博客平台本身要简洁:想来想去,感觉还是简书好,自己搭建博客平台太过于繁琐,采用现成框架的话,能够修改的样式也不多!更深层的抵触是认为博客的效果远没有视频教程传达的详细。
在这里许下宏愿,在以后的研究生涯中,我将每周发布一篇博客,并录制相应的视频;如果经过多次训练之后,我能够更加熟练的话,我也许会一周两篇,甚至更多,但是一周一篇是肯定会做到的!
话不多说,进入正题。最近要在ROS里面启动摄像头,并采集图像,将得到的图像发布给图像接收节点,整个逻辑十分的简单清晰。
节点1:充当图像发布节点,并启动相机、采集图像,并发布图像话题 /camera/image
节点2:充当图像订阅节点,功能为订阅图像话题,显示相机图像
ROS官方教程对此提供了相应的教程,相关链接列举如下:
1. Writing a Simple Image Publisher (C++)
2. Writing a Simple Image Subscriber (C++)
3. Running the Simple Image Publisher and Subscriber with Different Transports
这里介绍下本文的整体框架:在第二节介绍整个功能包的部署和使用;在第三节介绍图像发布节点;第四节介绍图像订阅节点;第五节介绍在使用官方源码包时遇到的一些问题和自己开发过程的一点困难。
本文的实验平台:
- Ubuntu16.0
- ROS kinetic
- TX2
2. 功能包部署
这一步是我个人比较纠结的一块,自己对于ROS的功能包创建不是很熟悉,对于CmakeLists.txt和package.xml文件的编写方式和相关依赖项不是很熟悉。
# 创建工作空间文件夹,并进入
$ mkdir -p ~/robot_ws/src
$ cd robot_ws/src
# 克隆代码
$ git clone https://github.com/zhouyumeng/camera_driver.git
# 返回上一层目录
$ cd ..
# 编译源码
$ catkin_make
# 加载环境变量
$ source devel/setup.bash
# 对于加载环境变量这一步操作,通常直接将环境变量直接写入~/.bashrc文件下
# 即在~/.bashrc文件下添加“ source ~/robot_ws/devel/setup.bash”
camera_driver文件夹下的整体文件部署如下:
.
├── CMakeLists.txt
├── package.xml
├── README.md
├── src
├── camera_node.cpp
└── cam_test_subscriber.cpp
下面分析下ROS功能包的CmakeLists.txt文件和package.xml文件。
# CmakeLists.txt文件分析
cmake_minimum_required(VERSION 3.5)
project(camera_driver)
SET(OpenCV_DIR /usr/local/share/OpenCV)
find_package(OpenCV REQUIRED)
find_package(catkin REQUIRED COMPONENTS
cv_bridge
image_transport
message_generation
message_runtime
roscpp
sensor_msgs
std_msgs
)
SET(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -std=c++11”)
catkin_package(CATKIN_DEPENDS cv_bridge image_transport message_generation roscpp sensor_msgs std_msgs
)
include_directories(
include
${catkin_INCLUDE_DIRS}
${OpenCV_INCLUDE_DIRS}
)
add_executable(camera_node src/camera_node.cpp)
add_dependencies(camera_node ${catkin_EXPORTED_TARGETS} KaTeX parse error: Expected '}', got 'EOF' at end of input: …ation">{</span>{PROJECT_NAME}_EXPORTED_TARGETS})
target_link_libraries(camera_node ${catkin_LIBRARIES} ${OpenCV_LIBS})
add_executable(cam_test_subscriber src/cam_test_subscriber.cpp)
add_dependencies(cam_test_subscriber ${catkin_EXPORTED_TARGETS} KaTeX parse error: Expected '}', got 'EOF' at end of input: …ation">{</span>{PROJECT_NAME}_EXPORTED_TARGETS})
target_link_libraries(cam_test_subscriber ${catkin_LIBRARIES} ${OpenCV_LIBS})
# package.xml文件分析
<?xml version="1.0"?>
<package format="2">
<name>camera_driver</name>
<version>0.0.0</version>
<description>The camera_driver package</description>
<maintainer email="nvidia@todo.todo">nvidia</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>cv_bridge</build_depend>
<build_depend>image_transport</build_depend>
<build_depend>message_generation</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>sensor_msgs</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>opencv2</build_depend>
<build_export_depend>cv_bridge</build_export_depend>
<build_export_depend>image_transport</build_export_depend>
<build_export_depend>message_generation</build_export_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>sensor_msgs</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>opencv2</exec_depend>
<exec_depend>cv_bridge</exec_depend>
<exec_depend>image_transport</exec_depend>
<exec_depend>message_runtime</exec_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>opencv2</exec_depend>
<export>
</export>
</package>
3. 图像发布节点:camera_node.cpp
我个人的项目比较特殊,特殊在相机平台特殊,需要借助V4L2驱动来开启和采集图片(目前项目尚在开发,以后项目开发完了,再进行介绍),导致相机的启动不能通过OpenCV的相关函数直接启动,为了简单期间,本文使用S-YUE晟悦的WX051摄像头,是一款USB摄像头,免驱即插即用。
//camera_node.cpp:位于功能包的src文件夹下
//重点在于将OpenCV和ROS联合使用,用OpenCV来开启摄像头,ROS自带的功能包进行图像传递
#include <ros/ros.h> //ros.h包含大部分通用的ROS头文件
#include <opencv2/opencv.hpp> //opencv.hppp包含大部分通用的OpenCV头文件
#include <opencv2/highgui.hpp> //opencvGUI组件
#include <opencv2/imgproc.hpp> //引入cvtColor()
#include <image_transport/image_transport.h> //image_transport实现图像传输
#include <cv_bridge/cv_bridge.h> //cv_bridge提供ROS对OpenCV格式的接口功能
using namespace cv;
using namespace std;
int main(int argc, char argv)
{
//ROS节点初始化,节点名:image_publisher
ros::init(argc, argv, “image_publisher”);
<span class="token comment">//创建一个节点句柄,方便节点资源使用和管理</span>
ros<span class="token operator">::</span>NodeHandle nh<span class="token punctuation">;</span>
<span class="token comment">//图像发送接口句柄,用于管理图像接口资源</span>
image_transport<span class="token operator">::</span>ImageTransport <span class="token function">it</span><span class="token punctuation">(</span>nh<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//创建一个图像信息发布器,发布名为“camera/image”的topic,消息类型为sensor_msgs::ImagePtr</span>
<span class="token comment">//发布图像消息队列的长度只能是1</span>
image_transport<span class="token operator">::</span>Publisher pub <span class="token operator">=</span> it<span class="token punctuation">.</span><span class="token function">advertise</span><span class="token punctuation">(</span><span class="token string">"camera/image"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//获取摄像机接口</span>
VideoCapture <span class="token function">cam</span><span class="token punctuation">(</span><span class="token string">"/dev/video0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//启动相机的端口号,一般默认相机为video0,可以通过`ls /dev/video*`来查看</span>
<span class="token comment">//检测接口是否打开</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cam<span class="token punctuation">.</span><span class="token function">isOpened</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"摄像头开启正常\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//声明图像帧</span>
Mat srcframe<span class="token punctuation">;</span>
<span class="token comment">//设置循环频率</span>
ros<span class="token operator">::</span>Rate <span class="token function">loop_rate</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//单位为Hz,10Hz不会让人察觉图像的停顿</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>nh<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token comment">//从摄像头读取图像</span>
cam <span class="token operator">>></span> srcframe<span class="token punctuation">;</span>
<span class="token comment">//将OpenCV的Mat转变为sensor_msgs</span>
sensor_msgs<span class="token operator">::</span>ImagePtr msg <span class="token operator">=</span> cv_bridge<span class="token operator">::</span><span class="token function">CvImage</span><span class="token punctuation">(</span>std_msgs<span class="token operator">::</span><span class="token function">Header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"bgr8"</span><span class="token punctuation">,</span> srcframe<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toImageMsg</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//发布封装完毕的消息</span>
pub<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//循环等待回调函数</span>
ros<span class="token operator">::</span><span class="token function">spinOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//按照循环频率延时</span>
loop_rate<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"发布信息\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
4.图像订阅节点:cam_test_subscriber.cpp
//如标题表明的那样,该节点只是为了测试图像的传递功能
//cam_test_subscriber.cpp:位于功能包的src文件夹下
//订阅图像话题,并显示图像
#include <ros/ros.h> //ros.h包含大部分通用的ROS头文件
#include <opencv2/highgui.hpp> //opencvGUI组件
#include <opencv2/opencv.hpp> //opencv.hppp包含大部分通用的OpenCV头文件
#include <image_transport/image_transport.h> //image_transport实现图像传输
#include <cv_bridge/cv_bridge.h> //cv_bridge提供ROS对OpenCV格式的接口功能
using namespace cv;
using namespace std;
//接收到订阅的消息后,会进入消息回调函数
void imageCallback(const sensor_msgs::ImageConstPtr &msg)
{
try
{
//显示图像
imshow(“view”, cv_bridge::toCvShare(msg, “bgr8”)->image);
<span class="token comment">//等待30ms</span>
<span class="token function">waitKey</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span>cv_bridge<span class="token operator">::</span>Exception <span class="token operator">&</span>e<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token function">ROS_ERROR</span><span class="token punctuation">(</span><span class="token string">"Could not convert from '%s' to 'bgr8'."</span><span class="token punctuation">,</span> msg<span class="token operator">-></span>encoding<span class="token punctuation">.</span><span class="token function">c_str</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
int main(int argc, char argv)
{
//初始化ROS节点,节点名:image_listener
ros::init(argc, argv, “image_shower”);
<span class="token comment">//创建节点句柄</span>
ros<span class="token operator">::</span>NodeHandle nh<span class="token punctuation">;</span>
<span class="token comment">//定义窗口</span>
<span class="token function">namedWindow</span><span class="token punctuation">(</span><span class="token string">"view"</span><span class="token punctuation">,</span> CV_WINDOW_NORMAL<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//图像消息句柄</span>
image_transport<span class="token operator">::</span>ImageTransport <span class="token function">it</span><span class="token punctuation">(</span>nh<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//创建一个Subscribe, 订阅名为“camera/image”的画图,注册回调函数imageCallback</span>
image_transport<span class="token operator">::</span>Subscriber sub <span class="token operator">=</span> it<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token string">"camera/image"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> imageCallback<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//循环等待回调函数</span>
ros<span class="token operator">::</span><span class="token function">spin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 销毁显示窗口</span>
cv<span class="token operator">::</span><span class="token function">destroyWindow</span><span class="token punctuation">(</span><span class="token string">"view"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
5. 我所遇到的那些别人也同样遇到过的奇葩们
-
- cv::startWindowThread()函数带来麻烦
对于cv::startWindowThread()
函数,官方文档并没有给出合理的解释,而且据Github上面的issues提示,该函数将会被移除,该函数的功能目前可以总结为“及时刷新窗口”,且该函数的优先级高于cv::waitKey()
。
常见用法如下:
cv::namedWindow("view");
cv::startWindowThread();
cv::imshow("view", img);
cv::destroyWindow("view");
在cv::imshow
之后无需搭配cv::waitKey(10)
来刷新图像。
在ROS的图像传递过程中,禁止将cv::startWindowThread()
和cv::waitKey(10)
混合使用,否则会出现线程锁报错。
建议用法:
在摄像头或者视频流数据的读取过程中,如果使用了函数cv::startWindowThread()
请勿使用cv::waitKey()
,如果是常见的图片显示的话,无需使用cv::startWindowThread()
函数。由于该函数将在不久移除,建议采用传统方式显示图像,可能在刷新速度上要慢一点,但是足够满足开发需求,即:
cv::namedWindow("view", CV_WINDOW_NORMAL);
cv::imshow("view", img);
cv::waitKey(10);
cv::destroyWindow("view");
-
- 在
sudo
权限下开启ROS节点
- 在
该问题是使用的相机平台导致的,在此将它记录下来。产生的原因是该相机平台涉及到GPIO总线的调用,而GPIO总线的调用需要在sudo
命令的超级权限下才能开启,我这里采用了一个比较简单,但是不怎么方便的方法,好在问题解决了,特此记录:
# 进入ROS功能包的/devel/lib/package/路径下
# 对相关节点产生的可执行文件执行下述三条指令,添加权限即可
chown root:root my_node
chmod a+rx my_node
chmod u+s my_node
-
- CmakeLists.txt文件中,有一个坑
我使用的OpenCV是自己编译的版本,所以需要这一步:
SET(OpenCV_DIR /usr/local/share/OpenCV)
可以直接注释掉,find_package(OpenCV REQUIRED)
会自动检索系统中ROS自带的OpenCV版本,如果实在不知道如何实现,可以参考官方项目包,祝你好运
-
- 相机的启动端口号和图像节点的发布频率设置
详情请见程序注释