实验编号: 实验指导书 实验项目: 机器视觉 所属课程: ROS机器人操作系统基础与实战 课程代码: 面向专业: 机电专业 课程负责人: 朱笑笑 年月 日
实验编号: 实 验 指 导 书 实验项目: 机器视觉 所属课程: ROS 机器人操作系统基础与实战 课程代码: 面向专业: 机电专业 课程负责人: 朱笑笑 年 月 日
一、实验目的 机器视觉是机器人对环境感知的重要传感器,本课程让学生熟悉机器视觉在 ROS中的基本使用方法,了解Opencv库和ROS的协作方式。 二、 实验内容 1. USB摄像头的驱动安装和调用 2. 完成Ros Kinect的安装 3. 标定摄像头 4. 尝试ROS中图像的管道功能 5. 测试ar_track alvar包 三、 实验过程或其他示意图 1. USB摄像头的驱动安装和调用 本实验中使用笔记本上内置摄像头,利用ls/dev/video0可以查看摄像头的变化高。 值得一提的是有两个主要的选项能用于USB摄像头驱动。 1.1usb_cam。 安装Sudo apt--get install ros-kinetic-usb-cam 运行rosparam set usb_cam/pixel_format yuyv做一个参数设置 运行rosrun usb_cam usb_cam node:并使用image_view显示摄像头图像,你会看到类似 于下图所示。它本身就是USB摄像头的原始图像,而且本身就是彩色的。 ③/camera/image_raw Roslaunch chapter6 usb cam.launch view:=true 对于usb_cam.launch文件运行了rosrun gscam gscam,并且对摄像头的一些参数进 行配置。它还使用image_view显示了摄像头图像,如下图所示: 2创建USB摄像头驱动功能包 在manifest.xml文件中,我们必须设置Open CV、ROS Image消息库和相关功能包的 -2-
- 2 - 一、 实验目的 机器视觉是机器人对环境感知的重要传感器,本课程让学生熟悉机器视觉在 ROS中的基本使用方法,了解Opencv库和ROS的协作方式。 二、 实验内容 1. USB摄像头的驱动安装和调用 2. 完成Ros Kinect的安装 3. 标定摄像头 4. 尝试ROS中图像的管道功能 5. 测试ar_track_alvar包 三、 实验过程或其他示意图 1. USB摄像头的驱动安装和调用 本实验中使用笔记本上内置摄像头,利用ls /dev/video0可以查看摄像头的变化高。 值得一提的是有两个主要的选项能用于USB 摄像头驱动。 1.1 usb_cam。 安装 Sudo apt-get install ros-kinetic-usb-cam 运行rosparam set usb_cam/pixel_format yuyv 做一个参数设置 运行rosrun usb_cam usb_cam node并使用image_view 显示摄像头图像,你会看到类似 于下图所示。它本身就是USB 摄像头的原始图像,而且本身就是彩色的。 Roslaunch chapter6 usb_cam.launch view:=true 对于usb_cam. launch 文件运行了rosrun gscam gscam ,并且对摄像头的一些参数进 行配置。它还使用image_view 显示了摄像头图像, 如下图所示: 2 创建USB 摄像头驱动功能包 在manifest.xml 文件中,我们必须设置Open CV 、ROS Image 消息库和相关功能包的
依赖项。如下所示: <depend package="sensor_msgs"/> <depend package="opencv2"/> <depend package="cvbridge"/> <depend package="image_transport"/> 因此,在src/camera timer.cpp文件中我们要包含以下头文件: #include <ros/ros.h> #include <image_transport/image_transport.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <opencv2/highgui/highgui.hpp> image transport API允许我们使用多种传输格式发布图像,其中包括各种压缩图像格 式和在ROS系统中以插件形式无缝集成的各种编解码器,例如,Tbeora压缩。上面的 cv_bridge字段用于从Open CV图像¥1J ROS Image消息的转换,其中在有灰度/颜色转 换时还会用到sensor msgs进行图像编码。最后为了使用cv::VideoCapture还需要使用 OpenCV(opencv2)中的highgui API。 这里我们会对src/camera t工mer.cpp文件中的大部分代码进行解释,其中有一 个实现摄像头驱动的类。它的属性如下: ros:NodeHandle nh; image_transport:ImageTransport it; image_transport:Publisher pub_image_raw; cy::VideoCapture camera; cv:Mat image; cv_bridge:CvImagePtr frame; ros:Timertimer; int camera_index; int fps; 2.1使用lmageTransport API发布摄像头帧 像往常一样,我们需要节点句柄。然后我们需要一个用于以任何可能格式发送图像的 ImageTransport对象。在代码中,我们只需要使用发布者,但注意它必须是imagetransport 库的实现,而不是一般用于图像消息的ros::Publ isher。 然后我们使用Open CV来捕捉图像/帧。在捕捉帧的时候,我们直接使用cv_brigdet帧。 它是CvimagePtr类型的,通过它我们可以直接访问图像字段。 最后,我们有计时器和用于驱动程序工作的基本摄像头参数。这些参数包括摄像头的索 引名,也就是/dev/videoX设备的编号,例如,0代表/dev/video0。这个索引名被 传递给cv::VideoCapture o FPS用于设定摄像头(如果可能的话,因为有些摄像头不支持 这个参数)和计时器。这里我们使用了一个int值,但在最终版本的src/camera.cpp中它 也可能是一个double值。 这个驱动会使用用于安装和初始化节点、摄像头、计时器的类构造函数: nh.param<int>("camera_index",camera_index,DEFAULT_CAMERAINDEX ) -3-
- 3 - 依赖项。如下所示: <depend package="sensor_msgs"/> <depend package="opencv2"/> <depend package="cv_bridge"/> <depend package="image_transport"/> 因此,在src/camera timer.cpp 文件中我们要包含以下头文件: #include <ros/ros.h> #include <image_transport/image_transport.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <opencv2/highgui/highgui.hpp> image transport API 允许我们使用多种传输格式发布图像,其中包括各种压缩图像格 式和在ROS 系统中以插件形式无缝集成的各种编解码器,例如, Tbeora 压缩。上面的 cv_bridge 字段用于从Open CV 图像¥1J ROS Image 消息的转换,其中在有灰度/颜色转 换时还会用到sensor msgs 进行图像编码。最后为了使用cv : : VideoCapture 还需要使用 OpenCV(opencv2 )中的highgui API 。 这里我们会对src / camera t 工mer.cpp 文件中的大部分代码进行解释,其中有一 个实现摄像头驱动的类。它的属性如下: ros::NodeHandle nh; image_transport::ImageTransport it; image_transport::Publisher pub_image_raw; cv::VideoCapture camera; cv::Mat image; cv_bridge::CvImagePtr frame; ros::Timer timer; int camera_index; int fps; 2.1 使用lmageTransport API 发布摄像头帧 像往常一样,我们需要节点句柄。然后我们需要一个用于以任何可能格式发送图像的 ImageTransport 对象。在代码中,我们只需要使用发布者,但注意它必须是imagetransport 库的实现,而不是一般用于图像消息的ros : :Publ isher 。 然后我们使用Open CV 来捕捉图像/帧。在捕捉帧的时候,我们直接使用cv_brigde帧。 它是CvimagePtr 类型的,通过它我们可以直接访问图像字段。 最后,我们有计时器和用于驱动程序工作的基本摄像头参数。这些参数包括摄像头的索 引名,也就是/ dev/videoX 设备的编号,例如, 0 代表/ dev/videoO 。这个索引名被 传递给cv: :VideoCapture o FPS 用于设定摄像头(如果可能的话,因为有些摄像头不支持 这个参数)和计时器。这里我们使用了一个int 值,但在最终版本的src/camera.cpp 中它 也可能是一个double 值。 这个驱动会使用用于安装和初始化节点、摄像头、计时器的类构造函数: nh.param<int>( "camera_index", camera_index, DEFAULT_CAMERA_INDEX );
if not camera.isOpened()) ROS_ERROR STREAM("Failed to open camera device!"); ros::shutdown(); 3 nh.param<int>("fps",fps,DEFAULT_FPS ) ros::Duration period ros::Duration(1.fps ) pub_image_raw it.advertise("image_raw",1 ) frame boost::make shared<cv_bridge:Cvlmage >() frame->encoding sensor_msgs::image_encodings::BGR8; timer =nh.createTimer(period,&CameraDriver::capture,this ) 首先,我们要打开摄像头,如果无法打开则需要终止程序。注意,我们必须在属性构造 函数内完成这些工作: camera(cameraindex 这里camera工ndex是作为参数进行传递。 然后,我们读取fs参数并计算计时器周期。这些参数用于创建计时器并设定用于图像 捕捉的回调函数。我们使用ImageTransport API作为image_raw图像(源图像)的发布者, 并初始化frame变量。 下面代码是用于图像捕捉的回调函数读取和发布图像: camera >frame->image; if(not frame->image.empty()) frame->header.stamp ros::Time:now(); pub_image_raw.publish(frame->tolmageMsg()); 3 它捕捉图像,并检查帧是否被实际捕捉。在这种情况下,设定时间戳并发布己经被转换 成ROS Image的图像。 你可以使用以下代码启动这个节点: rosrun chapter6_tutorials camera_timer camera_index:=o fps:=15 我们将会以1SFPS的速度启动/dev/video(0摄像头。 然后你能使用image_view查看图像。和轮询的实现方式类似的是,你有一个.launch文 件,并能够使用以下命令运行: roslaunch chapter6_tutorials camera_polling.launch camera_index:=0 fps:=15 view:=true 现在,你能看到/camera,/image_.raw主题的图像。 -4-
- 4 - if ( not camera.isOpened() ) { ROS_ERROR_STREAM( "Failed to open camera device!" ); ros::shutdown(); } nh.param<int>( "fps", fps, DEFAULT_FPS ); ros::Duration period = ros::Duration( 1. / fps ); pub_image_raw = it.advertise( "image_raw", 1 ); frame = boost::make_shared< cv_bridge::CvImage >(); frame->encoding = sensor_msgs::image_encodings::BGR8; timer = nh.createTimer( period, &CameraDriver::capture, this ); 首先,我们要打开摄像头,如果无法打开则需要终止程序。注意,我们必须在属性构造 函数内完成这些工作: camera( camera_index ) 这里camera 工ndex 是作为参数进行传递。 然后,我们读取fps 参数并计算计时器周期。这些参数用于创建计时器并设定用于图像 捕捉的回调函数。我们使用lmageTransport API 作为image_raw 图像(源图像)的发布者, 并初始化frame 变量。 下面代码是用于图像捕捉的回调函数读取和发布图像: camera >> frame->image; if( not frame->image.empty() ) { frame->header.stamp = ros::Time::now(); pub_image_raw.publish( frame->toImageMsg() ); } 它捕捉图像,并检查帧是否被实际捕捉。在这种情况下,设定时间戳并发布已经被转换 成ROS Image 的图像。 你可以使用以下代码启动这个节点: rosrun chapter6_tutorials camera_timer camera_index:=0 fps:=15 我们将会以lSFPS 的速度启动/ dev/videoO 摄像头。 然后你能使用image_view 查看图像。和轮询的实现方式类似的是,你有一个.launch 文 件,并能够使用以下命令运行: roslaunch chapter6_tutorials camera_polling.launch camera_index:=0 fps:=15 view:=true 现在,你能看到/ camera/image_raw 主题的图像
在计时器的实现方式中,最终的实现中包含camera,launch文件,并提供更多的选项。 这些选项需要贯穿本章的全部内容。在最终的实现中,最大的改进是能够支持动态参数再配 置,也就是说,它能够提供包括摄像头标定在内的摄像头信息在线配置功能。我们会简要说 明这是如何实现的,并建议读者自行查看源代码来进一步学习完整的代码设计。 我们能够对摄像头参数的动态再配置提供支持。然而,大多数的USB摄像头并不支持 对部分参数的修改。我们所做的主要针对所有Open CV支持的参数,并在发生错误(或部分 参数失效)时向用户提示警告信息。配置文件是cfg/Camera.cfg。请查看文件以了解详细 内容。它支持这些参数: ·camera index用于选择/dev/videoX设备。 ·frame_width和frame_height用于提供图像的分辨率。 ·fps用于摄像头的FPS值。 ·fourcc表示FOURCC的定义格式,用于指定摄像头像素(查看http:/www.fourcc.org, 虽然这个标识符所指的格式通常是YUYV或者MJPG,但对于大多数USB摄像头它们无法使用 OpenCV进行改变)。 ·brightness、c。ntrast、saturation和hue这些变量设定摄像头的属性。在数字摄 像头中,它们通过软件修改.并在传感器的图像获取过程中进行调整或直接对最终的图像 进行调整。 ·gain设定了传感器模数转换(ADC)的增压。它会向图像中引人椒盐噪声,也会增加在 黑暗环境中图像的亮度。 ·exposure决定了图像的曝光,也就设定了图像的亮度。通常通过调整增压和快门速度 (在廉价摄像头中,这就是积分时间内进入传感器的光)进行调整。 ·frame id用于指定摄像头坐标系,而且在导航功能中这非常有用。 ·camera info url这是一个到摄像头信息的路径,所保存内容主要关于摄像头标定。 然后,在驱动中,我们通过以下语句使用动态再配置服务: #include <dynamic_reconfigure/server.h> 在构造函数中设置回调函数: server.setCallback(boost:bind(&CameraDriver:reconfig,this,_1,2)); 回调函数会再配置摄像头。我们甚至允许改变摄像头或停止当前使用的摄像头,然后使 用OpenCV的cv::VideoCapture类来配置摄像头属性。其中包括了部分前面提到的参数。 以frame width参数为例说明: newconfig.frame_width setProperty(camera, CV CAP PROP FRAME_WIDTH newconfig.frame_width ) 它依赖于前面提到的setProperty方法,它会调用cv::VideoCapture中的set方法, 并控制实例,在失败时发送ROS警告信息。 FPS在计时器中改变,而且在摄像头中通常不能像其他参数那样被修改。 最后,最需要注意的是,所有的再配置过程都受到一个锁定的互斥标记的制约,以避免 在获取图像的同时进行驱动的再配置。 为了能设定摄像头信息,ROS有一个camera_info_manager库,能够帮助我们完成这 些工作。简而言之,是使用: #include <camera_info_manager/camera_info_manager.h> 我们用它来获取Camerainf o消息。现在,在计时器中捕获图像的回调函数里,我们能 使用image transport::CameraPublisher(不只是用于图像)。代码如下所示: -5-
- 5 - 在计时器的实现方式中,最终的实现中包含camera . launch 文件,并提供更多的选项。 这些选项需要贯穿本章的全部内容。在最终的实现中,最大的改进是能够支持动态参数再配 置,也就是说,它能够提供包括摄像头标定在内的摄像头信息在线配置功能。我们会简要说 明这是如何实现的,并建议读者自行查看源代码来进一步学习完整的代码设计。 我们能够对摄像头参数的动态再配置提供支持。然而,大多数的US B 摄像头并不支持 对部分参数的修改。我们所做的主要针对所有Open CV 支持的参数,并在发生错误(或部分 参数失效)时向用户提示警告信息。配置文件是cfg/Camera.cfg 。请查看文件以了解详细 内容。它支持这些参数: • camera index 用于选择/ dev/videoX 设备。 • frame_width 和frame_height 用于提供图像的分辨率。 • fps 用于摄像头的FPS 值。 • fourcc 表示FOURCC 的定义格式,用于指定摄像头像素(查看http :/www.fourcc.org, 虽然这个标识符所指的格式通常是YUYV 或者MJPG ,但对于大多数USB 摄像头它们无法使用 OpenCV 进行改变) 。 • brightness 、c。ntrast 、saturation 和hue 这些变量设定摄像头的属性。在数字摄 像头中,它们通过软件修改. 并在传感器的图像获取过程中进行调整或直接对最终的图像 进行调整。 • gain 设定了传感器模数转换(ADC) 的增压。它会向图像中引人椒盐噪声,也会增加在 黑暗环境中图像的亮度。 • exposure 决定了图像的曝光,也就设定了图像的亮度。通常通过调整增压和快门速度 (在廉价摄像头中,这就是积分时间内进入传感器的光)进行调整。 • frame id 用于指定摄像头坐标系,而且在导航功能中这非常有用。 • camera info url 这是一个到摄像头信息的路径,所保存内容主要关于摄像头标定。 然后,在驱动中,我们通过以下语句使用动态再配置服务: #include <dynamic_reconfigure/server.h> 在构造函数中设置回调函数: server.setCallback( boost::bind( &CameraDriver::reconfig, this, _1, _2) ); 回调函数会再配置摄像头。我们甚至允许改变摄像头或停止当前使用的摄像头,然后使 用OpenCV 的cv: : VideoCapture 类来配置摄像头属性。其中包括了部分前面提到的参数。 以frame width 参数为例说明: newconfig.frame_width = setProperty( camera, CV_CAP_PROP_FRAME_WIDTH ,newconfig.frame_width ); 它依赖于前面提到的setProperty 方法,它会调用cv: : VideoCapture 中的set方法, 并控制实例,在失败时发送ROS 警告信息。 FPS 在计时器中改变,而且在摄像头中通常不能像其他参数那样被修改。 最后,最需要注意的是,所有的再配置过程都受到一个锁定的互斥标记的制约,以避免 在获取图像的同时进行驱动的再配置。 为了能设定摄像头信息, ROS 有一个camera_info_manager 库,能够帮助我们完成这 些工作。简而言之,是使用: #include <camera_info_manager/camera_info_manager.h> 我们用它来获取Camerainf o 消息。现在,在计时器中捕获图像的回调函数里,我们能 使用image transport : : CameraPublisher (不只是用于图像) 。代码如下所示: