实验编号: 实验指导书 实验项目: 机器视觉 所属课程: 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 消息库和相关功能包的
依赖项。如下所示: 因此,在src/camera timer.cpp文件中我们要包含以下头文件: #include #include #include #include #include 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("camera_index",camera_index,DEFAULT_CAMERAINDEX ) -3-
- 3 - 依赖项。如下所示: 因此,在src/camera timer.cpp 文件中我们要包含以下头文件: #include #include #include #include #include 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( "camera_index", camera_index, DEFAULT_CAMERA_INDEX );
if not camera.isOpened()) ROS_ERROR STREAM("Failed to open camera device!"); ros::shutdown(); 3 nh.param("fps",fps,DEFAULT_FPS ) ros::Duration period ros::Duration(1.fps ) pub_image_raw it.advertise("image_raw",1 ) frame boost::make shared() 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( "fps", fps, DEFAULT_FPS ); ros::Duration period = ros::Duration( 1. / fps ); pub_image_raw = it.advertise( "image_raw", 1 ); frame = boost::make_shared(); 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 在构造函数中设置回调函数: 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 我们用它来获取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 在构造函数中设置回调函数: 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 我们用它来获取Camerainf o 消息。现在,在计时器中捕获图像的回调函数里,我们能 使用image transport : : CameraPublisher (不只是用于图像) 。代码如下所示:
camera >frame->image; if(not frame->image.empty()) frame->header.stamp ros::Time:now(); *camera_info camera_info_manager.getCameralnfo(); camera_info->header frame->header; camera_pub.publish(frame->tolmageMsg(),camera_info ) 3 这会在前面提到的再配置方法中所使用的互斥标记的制约下工作。现在,我们为第一版 驱动进行了各种参数配置,但在manager库中仍然要使用再配置方法(在摄像头加载过程中 已经调用过一次)获取摄像头信息,用于设定节点句柄、摄像头名称和camera infourl参 数。然后我们将图像/帧(RO S Image)和Camera Image消息都发布出去。 为了能使用驱动,运行以下命令: roslaunch chapter6_tutorials camera.launch view:=true 它将会使用params,/camera/webcam,yaml作为默认参数,其中设定了到目前为止的所 有动态再配置参数。 你能够使用rostopic list、rostopic hz/camera//image raw和imageview来检查摄 像头是否在工作。 我们使用了OS中所有可用资源来完成驱动程序,以便进行摄像头、图像与机器视觉等 工作。为了能讲解得更清晰,我们将会在下面的章节中分别解释每一项工作。 2.2使用cv_bridge进行Open CV和ROS图像处理 假设我们有一个OpenCV图像,那么它会是一个cv::Mat image。我们需要使用 CVbridge库将其转换为ROS Image消息并将其发布。我们可以选择分别使用CvShare或 CvCopy去共享图像或者复制图像。然而,如果可能的话,通过cv bridge在Cvimage?类中使 用OpenCV image宇段更容易。这正是我们做摄像头驱动时的做法,将其作为一个指针: cv_bridge:CvlmagePtr frame; 对于一个指针,我们应该按照这样的方法进行初始化: frame;boos t ma ke_sha red() 如果我们事先知道图像的编码方式: frame->encoding:sen sor_msgs image_encodings::BGRB: 最后让我们将OpenCV image设为指针,例如,从摄像头对其进行捕捉的时候: camera frame->i mage; 在这个指针中直接设置消息的时间戳也很常见: frame->header.stamp ros:Time:now(); 现在只需要将其发布。我们需要一个发布者来实现这个功能。这个实现者需要使用OS 中的image transport API。我们在下一小节中来介绍这些内容。 2.3发布单一图像。 我们可以使用ros::Publisher,但更好的办法是使用imagetransport发布者。它能 够发布单一图像或多个图像,并附带相应的摄像头信息。这和我们之前在摄像头驱动中的设 -6-
- 6 - camera >> frame->image; if( not frame->image.empty() ) { frame->header.stamp = ros::Time::now(); *camera_info = camera_info_manager.getCameraInfo(); camera_info->header = frame->header; camera_pub.publish( frame->toImageMsg(), camera_info ); } 这会在前面提到的再配置方法中所使用的互斥标记的制约下工作。现在,我们为第一版 驱动进行了各种参数配置,但在manager 库中仍然要使用再配置方法(在摄像头加载过程中 已经调用过一次)获取摄像头信息,用于设定节点句柄、摄像头名称和camera infourl 参 数。然后我们将图像/帧( RO S Image )和Camera Image 消息都发布出去。 为了能使用驱动,运行以下命令: roslaunch chapter6_tutorials camera.launch view:=true 它将会使用params/camera/webcam . yaml 作为默认参数,其中设定了到目前为止的所 有动态再配置参数。 你能够使用rostopic list 、rostopic hz /camera/image raw 和imageview 来检查摄 像头是否在工作。 我们使用了ROS 中所有可用资源来完成驱动程序,以便进行摄像头、图像与机器视觉等 工作。为了能讲解得更清晰,我们将会在下面的章节中分别解释每一项工作。 2.2 使用cv_ bridge 进行Open CV 和ROS 图像处理 假设我们有一个OpenCV 图像,那么它会是一个cv: : Mat image 。我们需要使用 CVbridge 库将其转换为ROS Image 消息并将其发布。我们可以选择分别使用CvShare 或 CvCopy 去共享图像或者复制图像。然而,如果可能的话,通过cv_bridge 在Cvimage类中使 用OpenCV image 宇段更容易。这正是我们做摄像头驱动时的做法,将其作为一个指针: cv_bridge::CvlmagePtr frame; 对于一个指针,我们应该按照这样的方法进行初始化: frame; boos t : :ma ke_sha red(); 如果我们事先知道图像的编码方式: frame->encoding; sen sor_msgs : :image_encodings::BGRB; 最后让我们将OpenCV image 设为指针,例如,从摄像头对其进行捕捉的时候: camera > frame-> i mage; 在这个指针中直接设置消息的时间戳也很常见: frame->header.stamp ; ros: :Time: :now(); 现在只需要将其发布。我们需要一个发布者来实现这个功能。这个实现者需要使用ROS 中的image transport API 。我们在下一小节中来介绍这些内容。 2.3发布单一图像。 我们可以使用ros: : Publisher,但更好的办法是使用imagetransport 发布者。它能 够发布单一图像或多个图像,并附带相应的摄像头信息。这和我们之前在摄像头驱动中的设
计是一样的。lmageTransport APl很强大的一点在于能够无缝地提供不同的传输格式。你 所发布的图像会出现在几个主题中,图像被分为基本图像、未压缩图像、压缩图形等多种类 型。所支持的传输类型的数量是由在ROS中安装的插件决定的,最常用的是compressed和 theora传输。你能够通过简单的rostopic cal1命令来查看它们。 在你的代码中,你需要使用节点句柄创建图像传输和发布者。在本示例中我们将会使用 一个简单的图像发布者。请检查最终的USB摄像头驱动中的CameraPublisher,看一下它的 使用方法: ros:NodeHandle nh; image transport ImageTransport it; image_transport:Publisher pub_image_raw; 节点句柄和图像传输通过以下代码构建(在类的属性构造函数中): nh(”~”), it (nh 然后,发布者是通过使用node的命名空间和一个image raw主题来实现的: pub_image_raw it advertise ("image_raw",1 )i 因此,前面章节中展示的frame属性现在就能够使用下面的代码声明进行发布了: pub_image_raw.publish(frame ->toimageMsg ()) 2.4在R0S中使用0 penCV 为了能在我们的节点中使用它,在你的manifest.xml文件中输入以下代码片段: 现在,你能够在你的代码中使用任何OpenCV Al凹的类、函数及其他代码段。如果你是一 个OpenCV的新手,那么你也可以简单地引用它的命名空间和CV,并学习一下OpenCV的自带 教程。需要说明的是本书并不是用于介绍OpenCV的,仅对ROS中的机器视觉 2.5显示摄像头输入的图像 这是通过image view功能包下的image view主题实现的: rosrun image view illllage v i ew image:=/camera/image raw 在这里最重要的是通过使用图像传输,我们能够选择不同的主题来查看图像,并在必要 时使用压缩格式。还有,在双目视觉中,我们使用rviz还能查看由差分图像生成的点云。 3.如何标定摄像头 大部分摄像头,尤其是广角镜头,会引人巨大的失真。我们可以从径向和切向对这种失 真进行建模,并使用标定算法计算模型的参数。这种摄像头标定算法同时还允许我们获得一 个带有镜头焦距和主点的标定矩阵,这就为我们提供了一个方法能根据所获取的图像以米为 单位测量实际环境。在双目视觉的情况下,它也能帮助我们得到深度信息,也就是,摄像头 获取的每个像素之间的距离,我们会在后面再去介绍这部分内容。而最终的结果就是我们获 取了真实环境的3D信息。 标定是通过使用一种己知的标定图案,并对图案的多种角度的视图进行辨识来实现的。 最典型的图案是棋盘,也有使用一列圆圈或者非对称的圆圈作为图案的,但要注意当视图 -7-
- 7 - 计是一样的。lmageTransport APl 很强大的一点在于能够无缝地提供不同的传输格式。你 所发布的图像会出现在几个主题中,图像被分为基本图像、未压缩图像、压缩图形等多种类 型。所支持的传输类型的数量是由在ROS 中安装的插件决定的,最常用的是compressed 和 theora 传输。你能够通过简单的rostopic call 命令来查看它们。 在你的代码中,你需要使用节点句柄创建图像传输和发布者。在本示例中我们将会使用 一个简单的图像发布者。请检查最终的USB 摄像头驱动中的CameraPublisher ,看一下它的 使用方法: ros: : NodeHandle nh; image_transport :ImageTransport it; image_transport: :Publisher pub_image_raw; 节点句柄和图像传输通过以下代码构建(在类的属性构造函数中): nh (”~”), it ( nh ) 然后,发布者是通过使用node 的命名空间和一个image raw 主题来实现的: pub_image_raw ; it . advertise (”image_raw”, l ) i 因此,前面章节中展示的frame 属性现在就能够使用下面的代码声明进行发布了: pub_image_raw.publish( frame ->toimageMsg () ) ; 2.4 在ROS 中使用OpenCV 为了能在我们的节点中使用它,在你的manifest.xml 文件中输入以下代码片段: 在CMakeLists . xml 文件中,只需要增加一行就能够编译我们的节点。也就是说,我 们不需要对Open CV 库再做任何修改。 在节点的. cpp 文件中,我们要包含所需的所有OpenCV 库。例如,我们使用以下声明 来包含highgui . hpp 文件: #include 现在,你能够在你的代码中使用任何OpenCV A凹的类、函数及其他代码段。如果你是一 个OpenCV 的新手,那么你也可以简单地引用它的命名空间和CV ,并学习一下OpenCV的自带 教程。需要说明的是本书并不是用于介绍OpenCV 的,仅对ROS 中的机器视觉 2.5 显示摄像头输入的图像 这是通过image view功能包下的image view 主题实现的: rosrun image_view i皿age_v i ew image:=/camera/image_raw 在这里最重要的是通过使用图像传输,我们能够选择不同的主题来查看图像,并在必要 时使用压缩格式。还有,在双目视觉中,我们使用rviz 还能查看由差分图像生成的点云。 3.如何标定摄像头 大部分摄像头,尤其是广角镜头,会引人巨大的失真。我们可以从径向和切向对这种失 真进行建模,并使用标定算法计算模型的参数。这种摄像头标定算法同时还允许我们获得一 个带有镜头焦距和主点的标定矩阵,这就为我们提供了一个方法能根据所获取的图像以米为 单位测量实际环境。在双目视觉的情况下,它也能帮助我们得到深度信息,也就是,摄像头 获取的每个像素之间的距离,我们会在后面再去介绍这部分内容。而最终的结果就是我们获 取了真实环境的3 D 信息。 标定是通过使用一种已知的标定图案,并对图案的多种角度的视图进行辨识来实现的。 最典型的图案是棋盘, 也有使用一列圆圈或者非对称的圆圈作为图案的,但要注意当视图
倾斜时,圆圈看起来就变成了椭圆。检测算法会获取棋盘上每个单元格的内角点,并用来估 计摄像头的内参数和外参数。简而言之,外参数是摄像头的姿态,或者换句话说,就是在摄 像头保持在某个固定位置时,图案相对于摄像头的位姿。我们最需要的是内参数,因为它是 不会发生改变的,这就能在摄像头在任何位姿时使用,能够测量图像的距离,修正图像的失 真,也就是纠正图像。 运行我们的摄像头驱动,就能够使用OS的标定工具完成摄像头标定。在此过程中,摄 像头驱动提供了Camerainf o消息和camera info set服务。这个服务允许设定标定结果保 存的文件路径。之后,我们每次使用摄像头时,标定信息就能够被图像管道加载。 在USB摄像头驱动的情况下,我们有强大的.launch文件来集成摄像头标定节点。独 立的启动文件。因此,要标定你的摄像头,请使用以下命令: roslauEch chapter6 tutorials callera.launch calibrate:=true 在下图中,你会看到一个和FireWire摄像头一样的GUI中的标定过程,也就是说我们 也要运行camera info set服务。 display .0 cale Size Skew CALIBRA SAVE COMM 上图展示的示例中已经获取了足够多的图案视图,这样标定(calibrate)按钮被允 许使用。因为这时己经可以求解标定模型了。下图展示了标定过程的结束,允许我们保存标 定数据并提交到摄像头配置文件中去,而且我们不需要做更多的设置。 4. 尝试ROS中图像的管道功能 ROS图像管道通过image proc功能包运行。它提供了各种用于从摄像机采集的源图像中获 取单色和彩色图像的转换功能。在使用FireWire摄像头的情况下,摄像头(在其图像传感 器中)很可能使用了拜耳(Bayer)模式进行图像编码。需要进行去拜耳化来获得彩色图像。 一旦你标定完摄像头,图像管道就会提取Camerainfo消息(其中包含了去拜耳化模式信息) 并修正你的图像。这里,修正意味着修复图像,这样就能用失真模型的参数来修正径向和切 -8-
- 8 - 倾斜时,圆圈看起来就变成了椭圆。检测算法会获取棋盘上每个单元格的内角点,并用来估 计摄像头的内参数和外参数。简而言之,外参数是摄像头的姿态,或者换句话说,就是在摄 像头保持在某个固定位置时,图案相对于摄像头的位姿。我们最需要的是内参数,因为它是 不会发生改变的,这就能在摄像头在任何位姿时使用,能够测量图像的距离,修正图像的失 真, 也就是纠正图像。 运行我们的摄像头驱动,就能够使用ROS 的标定工具完成摄像头标定。在此过程中,摄 像头驱动提供了Camerainf o 消息和camera info set 服务。这个服务允许设定标定结果保 存的文件路径。之后, 我们每次使用摄像头时,标定信息就能够被图像管道加载。 在USB 摄像头驱动的情况下,我们有强大的. launch 文件来集成摄像头标定节点。独 立的启动文件。因此,要标定你的摄像头,请使用以下命令: roslau且ch chapter6_ tutorials ca皿era.launch calibrate:=true 在下图中,你会看到一个和FireWire 摄像头一样的GUI 中的标定过程, 也就是说我们 也要运行camera info set 服务。 上图展示的示例中已经获取了足够多的图案视图,这样标定( calibrate )按钮被允 许使用。因为这时已经可以求解标定模型了。下图展示了标定过程的结束,允许我们保存标 定数据并提交到摄像头配置文件中去,而且我们不需要做更多的设置。 4. 尝试ROS中图像的管道功能 ROS 图像管道通过image proc 功能包运行。它提供了各种用于从摄像机采集的源图像中获 取单色和彩色图像的转换功能。在使用FireWire 摄像头的情况下,摄像头(在其图像传感 器中)很可能使用了拜耳( Bayer)模式进行图像编码。需要进行去拜耳化来获得彩色图像。 一旦你标定完摄像头,图像管道就会提取Camerainfo 消息(其中包含了去拜耳化模式信息) 并修正你的图像。这里,修正意味着修复图像,这样就能用失真模型的参数来修正径向和切
向的失真。 ROS图像管道的结果是,我们能够在命名空间内看到更多的关于摄像头的主题。在下图中, 你能看到主题image raw、image mono和image color分别显示了源图像、单色图像、彩 色图像: 3●@enrique@siank:- ⑧/camera/image..raw roscore http://siani...x /home/enrique/ros...x enrique@siani:- /camera/image rect/theora/parameter updates /camera/image_rect color /camera/image rect color/compressed camera/image_rect_color/compressed/parameter_descriptions camera/image rect color /compressed/parameter updates /camera/image_rect_color/compressedDepth /camera/image rect color/compressedDepth/parameter_descriptio /camera/image rect color/compressedDepth/parameter updates /camera/image rect color/theora camera/image_rect_color/theora/parameter_descriptions camera/image rect color/theora/parameter updates /camera1394 node/parameter descriptions camera1394_node/parameter_updates /diagnostics rosout /rosout agg enrique@siani:-$rosrun image_view image_view image:=/camer image raw enriquedsiani:-$rosrun image view image_view image:=/camer /image_mono enriquedsiant:-S rosrun image view image view image:=/camer /image raw ②/camera/image_mono roscore http://siani...x /home/enrique/ros...x enrique@siani:- /camera/image rect color/compressed /camera/image_rect_color/compressed/parameter_descriptions camera/image_rect_color/compressed/parameter_updates /camera/image rect color/compressedDepth camera/image_rect_color/compressedDepth/parameter descripti /camera/image_rect_color/compressedDepth/parameter_updates camera/image rect_color/theora /camera/image rect color/theora/parameter descriptions camera/image rect color/theora/parameter_updates /camera1394 node/parameter descriptions /camera1394_node/parameter_updates diagnostics /rosout /rosout_agg enriquedsiani:-rosrun image_view image_view image:=/camera /image_raw enriqueasiani:-$rosrun image view image_view image:=/camera /image mono enrique@siani:-s rosrun image_view image_view image:=/camer /image raw riquedsiant:-$rosrun image_view image_view image:=/camer Lmage mono -9-
- 9 - 向的失真。 ROS 图像管道的结果是,我们能够在命名空间内看到更多的关于摄像头的主题。在下图中, 你能看到主题image_raw 、image_mono 和image_color 分别显示了源图像、单色图像、彩 色图像:
3enrlque siani:- 多●/camera/Image_color roscore http/小siani..×home/enrique/ros.,×enrique@siani:- /camera/image_rect_color/compressed/parameter_updates /camera/image rect color/compressedDepth /camera/image_rect_color/compressedDepth/parameter descriptio /camera/image rect color/compressedDepth/parameter updates camera/image_rect_color/theora camera/tmage rect color/theora/parameter descripttons camera/image_rect_color/theora/parameter_updates camera1394 node/parameter_descriptions /camera1394 node/parameter updates diagnostics /rosout rosout_agg enrique@stant:-s rosrun tmage_vtew image_vtew tmage:=/camera /image_raw enrique@stani:-$rosrun image_view image_view image:=/camera image mono enrique@stant:-S rosrun image_view image_view image:=/camera 1mage raw nrique@siani:-s rosrun image_view image_view image:=/camer tmage mono enrique@siani:-$rosrun image_view image_view image:=/camer Lmage_color 在主题image_rect和image_rect_color中,提供的修正后的图像是单色和影色的。在下图 中,我们比较未标定过的失真源图像和修正后的图像。你能分别哪个是修正的,因为在下图 的图案中,只有修正后的才有直线。尤其是在远离图像中心(传感器的原点)的区域里: -10-
- 10 - 在主题image_rect 和image_rect_color 中,提供的修正后的图像是单色和影色的。在下图 中,我们比较未标定过的失真源图像和修正后的图像。你能分别哪个是修正的,因为在下图 的图案中,只有修正后的才有直线。尤其是在远离图像中心(传感器的原点)的区域里: