summary6-利用相机中的数据提取进一步的环境信息 [Python]
本课程结束时,您将能够: 使用一系列ROS工具来查询ROS网络上的摄像头图像主题,并查看流式传输给他们的图像。 使用计算机视觉库OpenCV和ROS,获取相机图像并实时处理。 利用过滤过程隔离图像中感兴趣的对象。 利用这些过程产生的信息来控制机器人的位置,开发目标检测节点。 以摄像头数据为反馈信号,通过比例控制实现线路跟踪行为。 在ROS使用摄像头和图像
我们可以使用许多工具来检查机器人ROS中间拍摄的实时图像。就像所有的机器人数据一样,这些流程都发布在主题上,所以我们首先需要确定这些主题。 在新的终端实例(终端2)中运行rostopic list查看系统当前活动
[TERMINAL 2] $ rostopic list | grep /camera
我们真实机器人上的摄像头数据仍然与模拟中使用的相同ROS在主题中发布新闻格式,使我们在模拟中开发的节点在时机成熟时能够直接转移到真实的机器人上。 上述相机主题列表中的第一项显示,我们在这里有深入的信息。与真正的机器人非常相似,我们正在使用的模拟版本也有一个摄像头模块,可以确定深度信息简单地捕捉图像。但请记住,从第三周开始,我们还有一个非常强大的激光雷达传感器,可以为我们提供这些信息,所以我们不会真正使用相机的深度功能。 我们真正感兴趣的是相机拍摄RGB因此,本周我们将使用的关键主题是:/camera/rgb/image_raw
我们可以通过各种方式(实时)查看流式图像传输到上述主题,现在我们将讨论几种方法。 一种方法是使用RViz:
student@WSL-ROS(2201b):~$ rostopic info /camera/rgb/image_raw Type: sensor_msgs/Image Publishers: * /gazebo (http://localhost:32811/) Subscribers: None student@WSL-ROS(2201b):~$ rosmsg info sensor_msgs/Image std_msgs/Header header uint32 seq time stamp string frame_id uint32 height uint32 width string encoding uint8 is_bigendian uint32 step uint8[] data
这是一个很好的工具,可以让我们轻松查看和发布ROS网络上任何摄像头主题的图像。另一个有用的特点是捕获这些图像并将其作为(.jpg文件)保存到文件系统 单击窗口左上角的下拉框,选择要显示的图像主题。camera/rgb/image_raw(如果尚未选中)。
[TERMINAL 2] $ rosrun rqt_image_view rqt_image_view [TERMINAL 3] $ roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch ``````bash [TERMINAL 2] $ rosrun rqt_image_view rqt_image_view [TERMINAL 3] $ roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch
OpenCV实时图像分析是一个成熟而强大的计算机视觉库,因此对机器人应用非常有用。这个库是跨平台的,有一个Python API(cv我们将在这个实验中使用它来完成我们自己的计算机视觉任务。虽然我们可以直接使用它Python(通过API)使用OpenCV,但是,库不能直接解释和发布ROS本机图像格式采用摄像头主题图像消息,需要使用接口。该接口称为CvBridge,是一个ROS包,用于处理ROS和OpenCV图像格式之间的转换。因此,在开发中ROS当节点执行计算机视觉相关任务时,我们需要同时使用这两个库(OpenCV和CvBridge)。
我们经常希望机器人执行的一项常见工作是物体检测。我们将解释如何通过颜色过滤来检测机器人现在应该看到的彩色柱子。在这个练习中,你将学习如何使用它OpenCV捕获图像,过滤图像并进行其他分析,以确认我们可能感兴趣的功能的存在和位置。
第一步 首先在catkin_ws/src在目录中创建一个名字week6_vision并将新包rospy、cv_bridge、sensor_MSG和geometry_MSG作为依赖项。 然后在软件包上运行catkin build,然后重新为你的环境提供资源(就像你前几周做的那样)。 刚创建的包src在文件夹中创建一个名称object_detection的新Python文件。 在此复制代码,保存文件,然后查看代码解释程序,以了解节点的工作模式和运行过程中应发生的情况。 使用rosrun运行节点。 确保关闭弹出窗口!该节点应捕获图像并显示在弹出窗口中。节点将等待您关闭弹出窗口,然后执行其他操作(例如将图像保存到文件系统中)! 通过阅读解释程序,您应该知道节点刚刚获得图像并将其保存到文件系统的位置。导航到文件系统的位置并使用它eog查看映像。 在检测运行对象时,您可能会从终端输出中注意到什么。py节点是指机器人摄像头1080x1920像素本机大小捕捉图像(您应该已经使用过以前rostopic echo查询/camera/rgb/image_raw/width and/height了解到这一点)。在一个图像中,有200多万像素(确切地说,每个图像有2073600像素),每个像素都有蓝色、绿色和红色值,所以图像文件中有很多数据!运行对象检测时,图像文件的大小(以字节为单位)实际上已打印到终端。py节点。。。它有多大? 因此,对于机器人来说,处理这样大小的图像是一项艰巨的任务:我们做的任何分析都会非常缓慢,我们捕获的任何原始图像都会占用相当大的存储空间。下一步是将图像切割到更容易管理的大小,以减少这种影响。 第二步 我们将检测修改对象。现在将节点复制到: 以原始大小捕获新图像 把它剪下来,集中在一个特别感兴趣的领域 保存两个图像(切割后的图像应该比原始图像小很多)。 在你的目标探索中。py该行节点定位:
#!/usr/bin/env python3 import rospy from pathlib import Path import cv2
from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image
node_name = "object_detection_node"
rospy.init_node(node_name)
print(f"Launched the '{
node_name}' node. Currently waiting for an image...")
rate = rospy.Rate(5)
base_image_path = Path("/home/student/myrosdata/week6_images")
base_image_path.mkdir(parents=True, exist_ok=True)
cvbridge_interface = CvBridge()
waiting_for_image = True
def show_and_save_image(img, img_name):
full_image_path = base_image_path.joinpath(f"{
img_name}.jpg")
cv2.imshow(img_name, img)
cv2.waitKey(0)
cv2.imwrite(str(full_image_path), img)
print(f"Saved an image to '{
full_image_path}'\n"
f"image dims = {
img.shape[0]}x{
img.shape[1]}px\n"
f"file size = {
full_image_path.stat().st_size} bytes")
def camera_cb(img_data):
global waiting_for_image
try:
cv_img = cvbridge_interface.imgmsg_to_cv2(img_data, desired_encoding="bgr8")
except CvBridgeError as e:
print(e)
if waiting_for_image == True:
height, width, channels = cv_img.shape
print(f"Obtained an image of height {
height}px and width {
width}px.")
show_and_save_image(cv_img, img_name = "step1_original")
crop_width = width - 400
crop_height = 400
crop_y0 = int((width / 2) - (crop_width / 2))
crop_z0 = int((height / 2) - (crop_height / 2))
cropped_img = cv_img[crop_z0:crop_z0+crop_height, crop_y0:crop_y0+crop_width]
show_and_save_image(cropped_img, img_name = "step2_cropping")
hsv_img = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2HSV)
lower_threshold = (115, 225, 100)
upper_threshold = (130, 255, 255)
img_mask = cv2.inRange(hsv_img, lower_threshold, upper_threshold)
show_and_save_image(img_mask, img_name = "step3_image_mask")
filtered_img = cv2.bitwise_and(cropped_img, cropped_img, mask = img_mask)
show_and_save_image(filtered_img, img_name = "step4_filtered_image")
waiting_for_image = False
rospy.Subscriber("/camera/rgb/image_raw", Image, camera_cb)
while waiting_for_image:
rate.sleep()
cv2.destroyAllWindows()
第三步 如上所述,图像本质上是一系列像素,每个像素都有一个蓝色、绿色和红色值,代表实际的图像颜色。从我们刚刚获得并裁剪的原始图像中,我们希望去除除我们希望机器人检测到的柱子相关颜色以外的任何颜色。因此,我们需要对像素应用一个过滤器,我们最终将使用它来丢弃与彩色柱无关的任何像素数据,同时保留与彩色柱无关的数据。 这个过程叫做掩蔽,为了达到这个目的,我们需要设置一些颜色阈值。在标准的蓝绿红(BGR)或红绿蓝(RGB)颜色空间中,这可能很难做到,您可以在本文的RealPython中看到一个很好的例子。通用域名格式。我们将应用本文中讨论的一些步骤,将裁剪后的图像转换为色调饱和度值(HSV)颜色空间,这使颜色掩蔽的过程更容易一些。 首先,分析裁剪图像的色调和饱和度值。为此,运行位于com2009_examples包中的以下ROS节点,将剪切图像的路径作为附加参数提供:
[TERMINAL 2] $ rosrun com2009_examples image_colours.py /home/student/myrosdata/week6_images/step2_cropping.jpg
节点应生成散点图,说明图像中每个像素的色调和饱和度值。图中的每个数据点代表一个图像像素,每个数据点的颜色与RGB值匹配: 你应该从图像中看到,所有与我们想要检测的彩色柱相关的像素都聚集在一起。我们可以使用这些信息来指定一系列色调和饱和度值,这些值可以用来遮掩我们的图像:过滤掉任何超出这个范围的颜色,从而使我们能够隔离柱子。像素也有一个值(或“亮度”),该值未在该图中显示。根据经验,亮度值在100到255之间的范围通常工作得很好。
lower_threshold = (115, 225, 100)
upper_threshold = (130, 255, 255)
节点应生成散点图,说明图像中每个像素的色调和饱和度值。图中的每个数据点代表一个图像像素,每个数据点的颜色与RGB值匹配
你现在已经成功地在你的机器人的视野内隔离了一个感兴趣的物体,但也许我们想让我们的机器人朝它移动,或者——相反地——让我们的机器人在它周围导航,避免撞到它!因此,我们还需要知道物体相对于机器人视点的位置,我们可以利用图像矩来实现这一点。 我们刚才所做的工作使我们获得了所谓的色块。OpenCV还内置了一些工具,让我们能够计算像这样的一团颜色的质心,让我们能够确定感兴趣的对象在图像中的确切位置(以像素为单位)。这是使用图像矩原理完成的:基本上是与图像相关的统计参数,告诉我们像素集合(即我们刚刚分离的颜色团)是如何分布在其中的。你可以在这里阅读更多关于图像时刻的信息,从中我们可以了解到,通过考虑我们之前通过阈值化获得的图像遮罩的一些关键时刻,可以获得色块的中心坐标: 注:我们在这里将水平方向称为y轴,垂直方向称为z轴,以匹配我们之前用于定义机器人主轴的术语。 不过,我们真的不需要太过担心这些时刻的起源。OpenCV有一个内置的矩()函数,我们可以使用该函数从图像遮罩(例如我们之前生成的图像遮罩)中获取此信息: m=cv2。瞬间(img_面具) 因此,使用这个,我们可以非常简单地获得水滴质心的y和z坐标: cy=m[‘m10’]/(m[‘m00’]+1e-5) cz=m[‘m01’]/(m[‘m00’]+1e-5) 注意,我们在M00矩上加了一个非常小的数字,以确保上述方程中的除数永远不会为零,从而确保我们永远不会被任何“除以零”的错误所困扰。
同样,有一个内置的OpenCV工具,我们可以使用它在图像上添加一个圆,以说明机器人视点内的质心位置:cv2。圆圈()。这就是我们制作红色圆圈的方法,您可以在上图中看到。您可以在一个完整的对象检测示例中看到这是如何实现的。上一个练习中的py节点。 在我们的例子中,我们实际上无法改变机器人在z轴上的位置,因此cz质心分量对于我们的导航目的可能没有那么重要。然而,我们可能希望使用质心坐标cy来了解特征在机器人视野中的水平位置,并使用该信息转向它(或远离它,取决于我们试图实现的目标)。我们现在将更详细地了解这一点。
colour_search.py 基本上,该节点的功能如下: 机器人在从相机获取图像的同时打开现场(通过订阅/camera/rgb/image_raw主题)。 获取摄像头图像并进行裁剪,然后对裁剪后的图像应用阈值,以便在模拟环境中检测蓝柱。 如果机器人看不到一根蓝色的柱子,那么它会很快在原地转动。 一旦检测到,代表支柱的蓝色斑点的质心将被计算,以获得其在机器人视点中的当前位置。 蓝色的柱子一出现,机器人就开始转得更慢。 一旦机器人确定支柱位于其正前方,它就会停止转动,使用蓝色斑点质心的cy分量来确定这一点。 然后,机器人等待一段时间,然后再次开始转动。 整个过程不断重复,直到它再次找到蓝柱。
首先,您可能想看看当前正在执行的裁剪,以磨练机器人视点的特定区域。。。这看起来是最理想的吗?可以改进吗? 接下来,应用HSV阈值来检测线条颜色,这些阈值看起来正确吗?使用您在本课程前面使用的方法,确定要应用的一些适当HSV阈值。 节点使用比例控制器。本质上,我们指定了一个比例增益(kp),并将该增益乘以位置误差以获得角速度。如果此计算输出的角速度过大,或大于我们的机器人可以达到的最大角速度,会发生什么情况?因此,请考虑对角度计算输出进行一些错误检查,以确保在发布到/CMDVLE主题之前,任何角速度命令都保持在一些合理的范围内。 PID增益需要调整,以便适合特定系统。对比例增益参数(kp)进行调整,直到获得合理的响应,并且机器人有效地跟随线路。 角速度是通过考虑色块质心的y坐标来确定的。