转载于: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文件。
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">{{ 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">{{ PROJECT_NAME}_EXPORTED_TARGETS})
target_link_libraries(cam_test_subscriber ${ catkin_LIBRARIES} ${ OpenCV_LIBS})
# package.xml文件分析
<package format="2">
<name>camera_drivername>
<version>0.0.0version>
<description>The camera_driver packagedescription>
<maintainer email="[email protected]">nvidiamaintainer>
<license>TODOlicense>
<buildtool_depend>catkinbuildtool_depend>
<build_depend>cv_bridgebuild_depend>
<build_depend>image_transportbuild_depend>
<build_depend>message_generationbuild_depend>
<build_depend>roscppbuild_depend>
<build_depend>sensor_msgsbuild_depend>
<build_depend>std_msgsbuild_depend>
<build_depend>opencv2build_depend>
<build_export_depend>cv_bridgebuild_export_depend>
<build_export_depend>image_transportbuild_export_depend>
<build_export_depend>message_generationbuild_export_depend>
<build_export_depend>roscppbuild_export_depend>
<build_export_depend>sensor_msgsbuild_export_depend>
<build_export_depend>std_msgsbuild_export_depend>
<exec_depend>opencv2exec_depend>
<exec_depend>cv_bridgeexec_depend>
<exec_depend>image_transportexec_depend>
<exec_depend>message_runtimeexec_depend>
<exec_depend>roscppexec_depend>
<exec_depend>sensor_msgsexec_depend>
<exec_depend>std_msgsexec_depend>
<exec_depend>opencv2exec_depend>
<export>
export>
package>
3. 图像发布节点:camera_node.cpp
我个人的项目比较特殊,特殊在相机平台特殊,需要借助V4L2驱动来开启和采集图片(目前项目尚在开发,以后项目开发完了,再进行介绍),导致相机的启动不能通过OpenCV的相关函数直接启动,为了简单期间,本文使用S-YUE晟悦的WX051摄像头,是一款USB摄像头,免驱即插即用。
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
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);
VideoCapture cam("/dev/video0");
if (!cam.isOpened())
{
exit(0);
}
printf("摄像头开启正常\n");
Mat srcframe;
ros::Rate loop_rate(10);
while (nh.ok())
{
cam >> srcframe;
sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", srcframe).toImageMsg();
pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
printf("发布信息\n");
}
}
4.图像订阅节点:cam_test_subscriber.cpp
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
void imageCallback(const sensor_msgs::ImageConstPtr &msg)
{
try
{
imshow(“view”, cv_bridge::toCvShare(msg, “bgr8”)->image);
waitKey(5);
}
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_shower”);
ros::NodeHandle nh;
namedWindow("view", CV_WINDOW_NORMAL);
image_transport::ImageTransport it(nh);
image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback);
ros::spin();
cv::destroyWindow("view");
}
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");
该问题是使用的相机平台导致的,在此将它记录下来。产生的原因是该相机平台涉及到GPIO总线的调用,而GPIO总线的调用需要在sudo
命令的超级权限下才能开启,我这里采用了一个比较简单,但是不怎么方便的方法,好在问题解决了,特此记录:
# 进入ROS功能包的/devel/lib/package/路径下
# 对相关节点产生的可执行文件执行下述三条指令,添加权限即可
chown root:root my_node
chmod a+rx my_node
chmod u+s my_node
我使用的OpenCV是自己编译的版本,所以需要这一步:
SET(OpenCV_DIR /usr/local/share/OpenCV)
可以直接注释掉,find_package(OpenCV REQUIRED)
会自动检索系统中ROS自带的OpenCV版本,如果实在不知道如何实现,可以参考官方项目包,祝你好运
详情请见程序注释