ROS 总结报告
文章目录
警告
首先,本文大部分内容为某位ROS大佬书籍内容的另一种体现(忘了名字了,但肯定有ROS这个单词)。
其次,本文为本人于大学期间学习 ROS 后的课程总结报告,如你是广东某与石油相关的学校的、甚至是某个李老师的学生,请不要直接复制本文,在校期间我跟他很熟。
小车建模
小车本体建模
基础知识
ROS 的建模没有可视化的界面1 ,它是根据机器人的模型描述文件进行建模的。
从根本来说,ROS 能够识别的模型文件只有 urdf 文件,这种文件是一种 xml 文件,里面的标签在 ROS 中都有一定的规定,我们现在接触的几个根标签为 link
、 joint
、 robot
和 gazebo
标签,文件内必须要有前 3 个标签的描述才能正确显示机器人,最后一个标签属于可选项,假如需要在 gazebo 仿真中正确显示和运行,则必须要添加对应的 gazebo
标签。
使用 urdf 文件进行建模:
-
使用
link
标签描述出机器人的各个部分的形状和大小。比如这个机器人需要两个扁平的长方体作为底板和平台、需要四根柱子从底板撑住平台或需要两个圆柱体作为车轮,这每一个零件都需要使用 urdf 文件的
link
标签进行描述,这个标签描述的长方体、圆柱体、球体或自己通过其他 3D 建模软件生成的 stl 文件是我们能够直接看到的机器人的部分,是模型最直接的表现。 -
使用
joint
根据从属结构连接link
描述的机器人的各个部分。建立好的所有
link
之间有一个问题,那就是它们都是相互独立的,相当于用积木拼好了机器人的形状,但是只要用手一推,这个积木就能立刻散开。为了让这些积木能够粘在一起,需要使用joint
标签连接这些部件。 连接时,一个joint
标签只能同时连接两个部分,而且他们之间会有一个从属关系,连接时还可以设置子物体相对于父物体的旋转、连接类型等属性,假如两个物体是相对不移动不旋转的话,直接使用fixed
连接属性即可。 -
使用
gazebo
标签描述机器人模型在 Gazebo 中仿真的参数(仅在仿真时需要)gazebo
标签描述的是机器人在 Gazebo 仿真时的参数,包括机器人材料、属性和插件等。其中,在仿真时比较重要的是插件属性,假如需要在 Gazebo 仿真中使用具体的插件进行仿真,那么这部分就需要正确调用对应的插件,配置需要正确。 -
用
robot
标签包含所有的link
、joint
、gazebo
标签。robot
标签是这些标签的顶层标签,有这个标签的 urdf 文件才会被认为是一个合法的 urdf 文件,否则在启动 rviz 或 Gazebo 时会有报错。
其实使用 urdf 文件进行建模时有一个不太好的地方,那就是每一个 link
和 joint
标签都需要写完整,而且在后期需要更改的时候也很麻烦。解决这个问题的办法就是使用 xacro 文件编程,这种文件可以说是描述 urdf 文件的一种文件,在里面可以像编程一样设置变量和使用变量,这样,就可以编写一个模板,然后直接多次调用模板,而且还能使用变量来使得后期的更改变得更简单。至于为什么说从根本来说 urdf 是 ROS 能够识别的模型描述文件,那是因为 ROS 解析 xacro 文件时会把 xacro 文件转换成 urdf 文件之后再解析。
萝卜机器人建模
萝卜机器人的结构关系如下图:
机器人总体分为 3 部分,分别是机器人本体框架、双目摄像机、轮子。其中机器人本体框架的 base_link
部分使用的是 3D 建模软件导出的 stl 文件,这能够减少很多 link
和 joint
描述,减少错误;双目摄像头使用的是模板编程和控制变量进行编程的,所有的参数都可以快速调整;轮子分为主动轮和从动轮,主动轮由两个轮子组成作为后轮,使用差速控制插件进行控制,从动轮模仿万向轮的设计,这样能够避免差速转向时带来的前轮的阻力。
机器人 3D 模型外观如下:
黄色部分为机器人本体 base_link
,下方两套白色圆柱体为主动轮,两套红色长方体加黑色球体为从动轮,第二层两个黑色长方体加红色长方体为双目摄像头。
添加差速轮控制仿真插件
添加仿真插件只需在机器人模型的 urdf 文件中添加一个 gazebo
标签并配置对应的信息即可:
<gazebo>
<plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
<rosDebugLevel>Debug</rosDebugLevel>
<publishWheelTf>true</publishWheelTf>
<robotNameSpace>/</robotNameSpace>
<publishTf>1</publishTf>
<publishWheelJointState>true</publishWheelJointState>
<alwaysOn>true</alwaysOn>
<updateRate>100.0</updateRate>
<legacyMode>true</legacyMode>
<leftJoint>left_motor_wheel_joint</leftJoint>
<rightJoint>right_motor_wheel_joint</rightJoint>
<wheelSeparation>${0.105*2+motor_length+motor_wheel_length}</wheelSeparation>
<wheelDiameter>0.066</wheelDiameter>
<broadcastTF>1</broadcastTF>
<wheelTorque>30</wheelTorque>
<wheelAcceleration>1.8</wheelAcceleration>
<commandTopic>cmd_vel</commandTopic>
<odometryFrame>odom</odometryFrame>
<odometryTopic>odom</odometryTopic>
<robotBaseFrame>base_footprint</robotBaseFrame>
</plugin>
</gazebo>
这里需要配置一个名为 “differential_drive_controller” 的插件,使用的是 libgazebo_ros_diff_drive.so
文件,然后在里面传递这个插件需要的参数,需要留意的是这里需要设置左右主动轮的 joint
,用到的是前面配置的主动轮的 joint
。
添加摄像头仿真插件
初探摄像头仿真插件
摄像头插件也只需要添加一个 gazebo
标签并设置对应的插件和参数就可以了。
不过由于摄像头的特殊性,需要多设置一个 sensor
标签指定摄像机的位置和朝向,而且需要设置的参数比较多,而这里设置的一些参数还会影响到后面双目测距的部分,主要是 width
、height
、format
、distortionK1
、distortionK2
、distortionK3
、distortionT1
和 distortionT2
这几个参数。后面5个建议设置为一个比较小的值(最好是0),它们设置的是相机图像的扭曲参数,这里就用最好的参数;宽度和高度建议填一个比较小的值,比如:640*480 ,设置小一点的好处是数据量比较小,双目测距和物体识别都能运行得更快。
<gazebo reference="${prefix}_camera_link">
<sensor name="${prefix}_camera_node" type="camera">
<update_rate>${camera_update_rate}</update_rate>
<camera name="${prefix}_head">
<horizontal_fov>${camera_horizontal_fov}</horizontal_fov>
<image>
<width>${camera_image_width}</width>
<height>${camera_image_height}</height>
<format>${camera_image_format}</format>
</image>
<clip>
<near>${camera_clip_near}</near>
<far>${camera_clip_far}</far>
</clip>
<noise>
<type>${camera_noise_type}</type>
<mean>${camera_noise_mean}</mean>
<stddev>${camera_noise_stddev}</stddev>
</noise>
</camera>
<plugin name="gazebo_camera" filename="libgazebo_ros_camera.so">
<alwaysOn>true</alwaysOn>
<updateRate>0</updateRate>
<cameraName>/camera/${prefix}_camera</cameraName>
<imageTopicName>${prefix}_image_raw</imageTopicName>
<cameraInfoTopicName>${prefix}_camera_info</cameraInfoTopicName>
<frameName>${prefix}_camera_link</frameName>
<hackBaseline>${camera_plugin_hackBaseline}</hackBaseline>
<distortionK1>${camera_plugin_disK1}</distortionK1>
<distortionK2>${camera_plugin_disK2}</distortionK2>
<distortionK3>${camera_plugin_disK3}</distortionK3>
<distortionT1>${camera_plugin_disT1}</distortionT1>
<distortionT2>${camera_plugin_disT2}</distortionT2>
</plugin>
</sensor>
</gazebo>
发布两个摄像头话题
从上面的代码中可以看到,plugin
标签内的 imageTopicName
我使用的是通过参数传值,因为是双目摄像头,需要两个摄像头2,这里就需要发布两个话题,而发布话题不能使用相同的名字,所以这里就要传参。
这里只需要调用上面的摄像头模板即可发布不同的摄像头:
<!-- camera, left and right -->
<usb_camera parent="camera_holder" prefix="left" translateY="1"/>
<usb_camera parent="camera_holder" prefix="right" translateY="-1"/>
这里传入的 translateY
的值其实是用来设置摄像头的位置是处于左边还是右边的,而且只能传 1 和 -1 的值。
3D 视觉
摄像头标定
标定是为了获取摄像机图像的一些参数,这些参数就是前面说到的 distortionK1
、distortionK2
、distortionK3
、distortionT1
和 distortionT2
这几个参数,假如使用的是真实的摄像机,那么标定这一步必不可少,但是使用仿真的话,就可以直接获取到这些参数,方便了不少。
真实的摄像机的标定会很麻烦,而且受制与设备的缺陷,出图的效果不会好到哪里,也就会导致双目测距出现比较大的误差,
#### ROS 获取仿真摄像头的固定属性数据
可以在写完两个摄像头的模型文件后,直接通过获取发布的相机话题的信息,即可获取到相机的基本属性:
rostopic echo 你发布的相机的话题名称
这里的相机的话题的名称就是前面的插件发布的相机的话题名称。
这样就可以得到类似下面的数据了:
header:
seq: 19
stamp:
secs: 156
nsecs: 170000000
frame_id: "left_camera_link"
height: 480
width: 640
distortion_model: "plumb_bob"
D: [0.0, 0.0, 0.0, 0.0, 0.0]
K: [381.36246688113556, 0.0, 320.5, 0.0, 381.36246688113556, 240.5, 0.0, 0.0, 1.0]
R: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
P: [381.36246688113556, 0.0, 320.5, -26.69537268167949, 0.0, 381.36246688113556, 240.5, 0.0, 0.0, 0.0, 1.0, 0.0]
binning_x: 0
binning_y: 0
roi:
x_offset: 0
y_offset: 0
height: 0
width: 0
do_rectify: False
是不是很轻松!!!
获取到了数据之后,需要把他们对应地写到相机配置信息文件中:
#!/usr/bin/env python
# coding: utf-8
# filename: camera_configs.py
import cv2
import numpy as np
left_camera_matrix = np.array([[381.36246688113556, 0.0, 320.5],
[0.0, 381.36246688113556, 240.5],
[0.0, 0.0, 1.0]])
left_distortion = np.array([[0., 0., 0., 0., 0.]])
right_camera_matrix = left_camera_matrix
right_distortion = left_distortion
om = np.array([0., 0., 0.]) # 旋转关系向量
R = cv2.Rodrigues(om)[0] # 使用Rodrigues变换将om变换为R
T = np.array([-50., 0., -0.]) # 平移关系向量
size = (640, 480) # 图像尺寸 (width, height)
# 进行立体更正
R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify(left_camera_matrix, left_distortion,
right_camera_matrix, right_distortion, size, R,
T)
# 计算更正map
left_map1, left_map2 = cv2.initUndistortRectifyMap(left_camera_matrix, left_distortion, R1, P1, size, cv2.CV_16SC2)
right_map1, right_map2 = cv2.initUndistortRectifyMap(right_camera_matrix, right_distortion, R2, P2, size, cv2.CV_16SC2)
这个文件会在深度图生成的主函数中被包含和使用。
深度图生成
深度图的生成可以直接使用 OpenCV 提供的一些接口和函数进行操作。
这里启动机器人的 Gazebo 仿真,暂停仿真,然后打开 rqt_image_view
拍下当前两个摄像机各自的图像,保存为 left.png
和 right.png
,然后使用以下的代码进行测试:
#!/usr/bin/env python
# coding: utf-8
import numpy as np
import cv2
import camera_configs
cv2.namedWindow("left")
cv2.namedWindow("right")
cv2.namedWindow("depth")
cv2.moveWindow("left", 0, 0)
cv2.moveWindow("right", 600, 0)
cv2.createTrackbar("num", "depth", 0, 30, lambda x: None)
cv2.createTrackbar("blockSize", "depth", 5, 255, lambda x: None)
# 添加点击事件,打印当前点的距离
def callbackFunc(e, x, y, f, p):
if e == cv2.EVENT_LBUTTONDOWN:
print(threeD[y][x])
cv2.setMouseCallback("depth", callbackFunc, None)
while True:
imgL = cv2.imread("left.png")
imgR = cv2.imread("right.png")
# 根据更正map对图片进行重构
img1_rectified = cv2.remap(imgL, camera_configs.left_map1, camera_configs.left_map2, cv2.INTER_LINEAR)
img2_rectified = cv2.remap(imgR, camera_configs.right_map1, camera_configs.right_map2, cv2.INTER_LINEAR)
# 将图片置为灰度图,为StereoBM作准备
imgL = cv2.cvtColor(img1_rectified, cv2.COLOR_BGR2GRAY)
imgR = cv2.cvtColor(img2_rectified, cv2.COLOR_BGR2GRAY)
# 两个trackbar用来调节不同的参数查看效果
num = cv2.getTrackbarPos("num", "depth")
blockSize = cv2.getTrackbarPos("blockSize", "depth")
if blockSize % 2 == 0:
blockSize += 1
if blockSize < 5:
blockSize = 5
# 根据Block Maching方法生成差异图(opencv里也提供了SGBM/Semi-Global Block Matching算法,有兴趣可以试试)
stereo = cv2.StereoBM_create(numDisparities=16*num, blockSize=blockSize)
disparity = stereo.compute(imgL, imgR)
disp = cv2.normalize(disparity, disparity, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# 将图片扩展至3d空间中,其z方向的值则为当前的距离
threeD = cv2.reprojectImageTo3D(disparity.astype(np.float32)/16., camera_configs.Q)
cv2.imshow("left", img1_rectified)
cv2.imshow("right", img2_rectified)
cv2.imshow("depth", disp)
key = cv2.waitKey(1)
if key == ord("q"):
break
elif key == ord("s"):
cv2.imwrite("./BM_left.jpg", imgL)
cv2.imwrite("./BM_right.jpg", imgR)
cv2.imwrite("./BM_depth.jpg", disp)
elif key == ord("c"):
leftFirst = not leftFirst
cv2.destroyAllWindows()
这里测试的原因是需要找到一个符合 ROS 仿真中深度图生成的参数 blockSize
和 num
,这两个参数能够调整深度图生成的效果,去除其中一些噪点。
深度图生成效果图:
左相机黑白图
右相机黑白图
生成的深度图
这里选择的参数为:
这个参数仅仅作为参考值,在真实仿真的过程中还需要根据仿真的效果调整这两个参数,一般来说调整的应该是 blockSize
参数,num
参数调整比较少。
与摄像头话题进行通信
获取话题数据
深度图生成程序测试无误后,需要把图片的获取方式切换到从相机发布的话题中获取数据。
这里打算写一个接口用于连接相机话题和深度图生成器:
import rospy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
bridge = CvBridge()
def image_callback(message):
frame = bridge.imgmsg_to_cv2(message, "bgr8")
cv2.namedWindow("Frame", cv2.WINDOW_NORMAL)
cv2.imshow("Frame", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
cv2.destroyAllWindows()
def camera_listener():
rospy.Subscriber("你的相机的话题名字", Image, imge_callback)
rospy.spin()
rospy.init_node('随便起一个名字就行', anonymous=True)
camera_listener()
这里看着很简单,实际存在一个特别大的问题,那就是因为 rospy.spin()
这一句是阻塞型的语句,所以只能在同一个进程中获取到一个相机的图像,另一个相机就不能获取了,所以这也就不能达成生成深度图的条件了。
其实这个问题使用 C++ 进行编程可以很容易解决,因为 ros 的 C++ 函数提供了 spinOnce()
函数,这会在获取了一张图片的数据之后直接跳转到下一条指令继续执行,但由于没有现成的 C++ 的深度图生成代码,所以这并不能行得通。
通过查找资料,终于找到了解决办法:
使用 rospy.wait_for_message()
函数。这个函数跟 C++ 中提供的函数类似,能够在获取一次数据之后停下来,执行下一条语句,而不是一直在这获取相同话题的数据。但是,这个函数也有一个不好的地方,那就是获取到的数据的类型并不是 sensor_msgs.msg.Image
,而是 rospy.Message
类型,这就很头疼了,所以需要一个数据转换的步骤来解决这个问题。
转换数据
这里需要把获取到的数据转换为 OpenCV 可以直接显示的图像数据,才能输入到深度图生成函数中生成深度图。
在官网上找文档,没有找到可以直接转换的方法,在查看代码的说明文档时也找不到具体的转换函数,有点麻烦了。
然后其实可以直接把这个数据转换为 str
类型,于是输出它的前面200个字符看看:
header:
seq: 0
stamp:
secs: 2
nsecs: 960000000
frame_id: "left_camera_link"
height: 480
width: 640
encoding: "rgb8"
is_bigendian: 0
step: 1920
data: [178, 178, 178, 176, 176, 176, 175,
虽然很明显这就是 yaml 数据,但是不知道是因为我的电脑内存太小还是什么原因,在使用 yaml 模块对这个字符串生成 yaml 对象时,电脑卡住不能继续运行了,使用这个方法似乎并不可行。
不过,既然截取前面200个字符这么容易,直接截取这部分的内容就可以,而且数据都是在 data
字段之后,而且这个字段是最后一个字段。这里不再出现电脑卡住的问题了。
获取到这个字符串后,可以通过 ", "
字符串来分割成为一个列表,然后使用 map 函数转换为 int 类型的数据,再通过 numpy 来转换为合适的结构即可:
#!/usr/bin/env python
# coding: utf-8
import rospy
import cv2
import numpy
topicName = "/camera/left_camera/left_image_raw"
if __name__ == "__main__":
rospy.init_node("getImageTest", anonymous=True)
frame = rospy.wait_for_message(topicName, Image, timeout=None)
print(type(frame))
# 获取 frame 的字符串类型的数据
frameStr = str(frame)
# 找到 "data: [" 字符串的位置,后面就是字符串列表数据
keyStr = "data: ["
# 截断字符串,取 keyStr 后面、最后的 "]" 之前的字符串
dataStr = frameStr[frameStr.find(keyStr) + len(keyStr): -2]
# 根据 ", " 分割字符串,直接获得一个列表
data = dataStr.split(", ")
# 对列表中的每一个元素转化为 int 类型,python2 转化后还是一个列表,
# python3 则需要使用 list 强制转换一下
data = map(int, data)
# 把列表转换为 numpy 数组
dataNpArr = numpy.array(data)
width = 1280 # 图像的宽度
height = 720 # 图像的高度
channel = 3 # 图像的通道数
# shape is (height, width, channel)
# 转换为正确的图片的格式
dataNpArr = dataNpArr.reshape((height, width, channel))
# 转换为正确的数据类型
dataNpArr = dataNpArr.astype(numpy.uint8)
# 显示图像看一下
cv2.namedWindow("frame", cv2.WINDOW_NORMAL)
cv2.imshow("frame", dataNpArr)
if cv2.waitKey(0) & 0xFF == ord('q'):
cv2.destroyAllWindows()
深度图部分的所有代码已上传到 Gitee :这里
机械臂控制
机械臂建模
使用以下命令创建一个新的机器人描述功能包:
catkin_create_pkg marm_description urdf xacro # 创建一个名字叫做 marm_description 的包
然后在里面创建自己的机械臂的urdf描述文件和launch文件,以启用rviz查看模型。
因为时间关系,这里使用书上的机械臂。
下面是机械臂的结构关系图:
下面是机械臂的外观图:
使用 MoveIt 配置机械臂
使用 MoveIt! Setup Assistant 工具生成配置文件。
使用以下命令启动 MoveIt Setup Assistant:
rosrun moveit_setup_assistant moveit_setup_assistant
假如出现如下的错误:
[rospack] Error: package 'moveit_setup_assistant' not found
则表明没有安装 moveit 的包,可以运行以下命令安装:
sudo apt-get install ros-melodic-moveit # 这里我使用的是 melodic 发行版的 ros,假如不是该发行版的话,改成对应的发行版名称即可
假如安装成功后运行 Setup Assistant 时出现以下错误:
[ERROR] [1608193144.717929566]: [registerPublisher] Failed to contact master at [localhost:11311]. Retrying...
可以在另一个终端先运行
roscore
命令启动 ros 内核后再重新启动 MoveIt 工具
运行成功后会出现以下界面:
加载机械臂 URDF 模型
接下来加载 URDF 模型,这里新建配置功能包,选择 “Create New MoveIt Configuration Package” 菜单,并在下面点击 “Browse” 加载自己的机械臂模型,这里加载我的机械臂模型:
然后点击 “Load Files” 按钮即可,如下图:
这里右边黑色的窗口本来应该要显示模型的,但由于这个版本的 MoveIt 有 bug,所以没有显示,但不影响接下来的操作(其实在后面的 Robot Poses 会有显示的)
配置自碰撞矩阵
接下来点击左边导航栏的 “Self-Collisions” 设置自碰撞矩阵,如下图:
点击 “Generate Collision Matrix” 按钮即可生成碰撞矩阵,如下图:
配置虚拟关节
虚拟关节主要用来描述机器人在 world 坐标系下的位置。如果机器人是移动的,则虚拟关节可以与移动基座关联,但这里设计的机械臂是固定不动的,所以无需设置虚拟关节。
创建规划组
点击左边导航栏的 “Planning Groups” 创建规划组。这一步可以将机器人的多个组成部分(link, joint)集成到一个组中,运动规划器会针对一组 link 或 joint 完成规划任务。这里创建两个组:一个组包含机械臂本体,另一个组包含前端夹爪。
点击下图中的 “Add Group” 按钮:
填写如下信息:
然后点击 “Add Kin. Chain” 按钮设置运动学计算需要包含的 link。这里先选中 “base_link” 然后点击下方 “Base Link” 处的 “Choose Selected” 按钮,如下图:
然后点击 “base_link” 左边的三角形,一步一步展开所有的分支,直到最后的 “grasping_frame” 显示出来为止,然后选中 “grasping_frame” ,再点击 “Tip Link” 处的 “Choose Selected” 按钮,如下图:
然后点击 “Save” 按钮保存即可。
保存后可以看到如下图所示的画面:
然后再点击 “Add Group” 添加新的规划组,填入如下数据:
点击 “Add Links” 按钮添加 link 信息,然后选中需要添加 link 再点击中间哪个 “>” 箭头的按钮即可加入到右边的列表中,如下图:
添加完毕后点击 “Save” 保存设置。
然后选中 “gripper” 规划组下的 “Joints”,点击 “Edit Selected” 以再次编辑规划组的信息,如下图:
然后为 gripper 规划组添加 joint 信息,选中需要添加的 joint,像上面添加 link 一样把他们加到右边的列表中并保存,如下图:
保存后能看到如下图的信息:
定义机器人位姿
接下来点击左边导航栏的 “Robot Poses” 定义机器人的位姿,这里可以设置一些自定义的位姿,定义这些位姿之后可以在使用 MoveIt! 的 API 编程时直接通过调用这些名称来调用这些位姿,点击 “Add Pose” 按钮添加位姿,如下图:
这里设置一个初始位姿,“Pose Name” 可以填写为 “home”,如下图:
点击保存按钮之后可以看到如下图的信息:
可以再次点击 “Add Pose” 添加新的位姿信息,这里可以设置一个叫做 “forward” 的位姿,这里可以拖动滑块条将右侧显示的机器人模型控制到希望的位姿,如下图所示:
再次点击保存即可保存当前位姿,可以看到如下信息:
这样就设置完机械臂的位姿信息了。
配置终端夹爪
接下来点击左边导航栏的 “End Effectors” 按钮配置终端夹爪的界面,点击 “Add End Effector” 按钮添加一个终端配置,如下图:
填入如下图信息,按下保存按钮即可:
保存后可以看到如下信息:
配置无用关节
机器人上的某些关节可能在规划、控制过程中使用不到,可以先生命出来。这里的机械臂机器人没有类似的 joint,所以这一步不需要配置。
设置作者信息
这一步可以设置作者的信息,点击左侧导航栏的 “Author Information”,如下图所示,根据情况进行填写即可:
生成配置文件
这里可以根据前面的配置自动生成配置功能包中的所有文件。点击导航栏的 "Configuration Files"按钮即可进入配置页面。
点击 “Brose” 按钮,选择一个存储配置功能包的路径(这个路径必须要存在),Setup Assistant 会自动将所有的配置文件打包成一个ROS功能包,一般命名为 “RobotName_moveit_config”,这里命名为 “marm_moveit_config”。然后点击 “Generate Package” 按钮即可,如下图:
其中要注意的是,上图中的路径,用蓝色方框框起来的部分的路径必须要存在,否则将不能生成,绿色椭圆框起来的部分为你要起的包的名字
点击生成按钮后可能会弹出如下图所示的信息框,这是因为我们刚才并没有设置虚拟关节,这里直接点击 “OK” 按钮即可:
生成成功后能看到如下图信息:
最后点击 “Exit Setup Assistant” 退出即可。
这里就完成了 MoveIt 的配置了,生成的文件用于通过 MoveIt 对机械臂进行控制。
使用 MoveIt 控制机械臂
通过代码控制机械臂运动
通过 MoveIt 的配置之后,可以得到机械臂的一些固定的属性,其中包括碰撞参数、控制组和末端夹爪等,这些参数都是跟随整个机械臂的,只要机械臂结构没有改变,就可以利用这些属性和 MoveIt 工具对机械臂进行控制。
工作空间规划
工作空间规划中,通过机器人终端的三维坐标位置和姿态给定机械臂的目标位姿,在运动规划时使用逆向运动学规划求解各轴位置。
这里直接看下效果(视频效果更好):
这里出现了部分重影,为仿真效果
这里到达了目标的位姿
笛卡尔运动规划
前面的工作空间规划中,机械臂只是简单地根据目标位姿反向计算出运动的计划,使用笛卡尔运动规划可以让机械臂运动到目标位姿的过程中经过规定的若干个点,从而限制机械臂的运动路径,这在实际的操作中有着很大的用处。
这里看下效果:
避障规划
笛卡尔运动规划能够实现限制运动路径,但假如在避障时也使用这种方式的话也会很麻烦,这里实现的是通过判断物体的碰撞参数规划出机械臂的运动路径,从而绕过物体到达指定位置。
这里看效果图:
先让机械臂放置与两个物体之间
机械臂在运动时绕过左边的物体
最终到达左边物体的左边
通过代码控制机械臂抓取和放置
通过ROS提供的 pick 和 place 接口可以在设定机器人抓取位姿和放置位姿的情况下利用机械臂的反向运动学和避障规划求解出机械臂抓取和放置过程中的位姿变换路径。
这里看效果图:
抓取前控制机械臂到达目标位姿
抓取到物体