ROS学习笔记(九):MoveIt!与机械臂控制

news2025/1/14 1:02:21

ROS学习笔记(九):MoveIt!与机械臂控制

  • MoveIt!简介
  • MoveIt!系统架构
  • MoveIt!编程与机械臂控制
    • 关节空间规划
    • 工作空间规划
    • 笛卡尔运动规划
    • 避障规划
  • Pick and Place示例

MoveIt!简介

MoveIt!是在2012年在PR2与多个机械臂功能包的基础上集成得到的ROS软件包,它为开发者提供了一个易于使用的集成化开发平台,由一系列移动操作的功能包组成,包括运动规划、操作控制、3D感知、运动学、控制与导航算法等,且提供友好的GUI,可以广泛应用于工业、商业、研发和其他领域。

使用MoveIt!实现机械臂控制的四个步骤:

  1. 组装:在控制之前需要有机器人,可以是真实的机械臂,也可以是仿真的机械臂,但都要创建完整的机器人URDF模型。
  2. 配置:使用MoveIt!控制机械臂之前,需要根据机器人的URDF模型,再使用Setup Assistant工具完成自碰撞矩阵、规划组、终端夹具等配置,配置完成后生成一个ROS功能包。
  3. 驱动:使用ArbotiX或者ros_control功能包中的控制器插件,实现对机械臂关节的驱动。插件的使用方法一般分为两步:首先创建插件的YAML配置文件,然后通过launch文件启动插件并加载配置参数。
  4. 控制:MoveIt!提供了C++、Python、rviz插件等接口,可以实现机器人关节空间和工作空间下的运动规划,规划过程中会综合考虑场景信息,并实现自主避障的优化控制。

关于MoveIt!安装与使用的更多内容可见教程。

MoveIt!系统架构

move_group是MoveIt!的核心节点,可以综合其他独立的功能组件作为用户提供ROS中的动作指令和服务。move_group本身并不具备丰富的功能,主要完成各功能包、插件的集成。它通过话题(Topic)的方式接收机器人发布的传感器信息、关节状态信息,以及机器人的TF坐标变换;它通过服务(Service)的方式运动规划器和运动学求解器通信;它还通过动作(Action)的方式向机器人控制器发送规划结果,并接受机器人执行情况的反馈;另外,还需要ROS参数服务器提供机器人的运动学参数,这些参数可根据机器人的URDF模型生成(SRDF和配置文件)。
在这里插入图片描述

MoveIt!编程与机械臂控制

MoveIt!的move_group提供了丰富的C++和Python的编程API,可以帮助我们完成更多运动控制的相关功能,接下来主要以Python API为例,介绍MoveIt!的编程方法。

关节空间规划

关节空间运动是以关节角度为控制量的机器人运动。机械臂关节空间的规划不需要考虑机器人终端的姿态。
使用如下命令实现MArm关节空间下的运动测试:

roslaunch marm_planning arm_planning.launch
rosrun marm_planning moveit_fk_demo.py

marm_planning/scripts/moveit_fk_demo.py的源码如下:

import rospy, sys
import moveit_commander

class MoveItFkDemo:
    def __init__(self):
        # 初始化move_group的API
        moveit_commander.roscpp_initialize(sys.argv)

        # 初始化ROS节点
        rospy.init_node('moveit_fk_demo', anonymous=True)
 
        # 初始化需要使用move group控制的机械臂中的arm group
        arm = moveit_commander.MoveGroupCommander('manipulator')
        
        # 设置机械臂运动的允许误差值
        arm.set_goal_joint_tolerance(0.001)

        # 设置允许的最大速度和加速度
        arm.set_max_acceleration_scaling_factor(0.5)
        arm.set_max_velocity_scaling_factor(0.5)
        
        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(1)
         
        # 设置机械臂的目标位置,使用六轴的位置数据进行描述(单位:弧度)
        joint_positions = [0.391410, -0.676384, -0.376217, 0.0, 1.052834, 0.454125]
        arm.set_joint_value_target(joint_positions)
                 
        # 控制机械臂完成运动
        arm.go()
        rospy.sleep(1)

        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(1)
        
        # 关闭并退出moveit
        moveit_commander.roscpp_shutdown()
        moveit_commander.os._exit(0)

if __name__ == "__main__":
    try:
        MoveItFkDemo()
    except rospy.ROSInterruptException:
        pass

工作空间规划

工作空间规划是通过机器人终端的三维坐标位置和姿态给定,在运动规划时使用逆向运动学求解各轴位置。
使用如下命令实现MArm工作空间下的运动测试:

roslaunch marm_planning arm_planning.launch
rosrun marm_planning moveit_ik_demo.py

marm_planning/scripts/moveit_ik_demo.py的源码如下:

import rospy, sys
import moveit_commander
from geometry_msgs.msg import PoseStamped, Pose


class MoveItIkDemo:
    def __init__(self):
        # 初始化move_group的API
        moveit_commander.roscpp_initialize(sys.argv)
        
        # 初始化ROS节点
        rospy.init_node('moveit_ik_demo')
                
        # 初始化需要使用move group控制的机械臂中的arm group
        arm = moveit_commander.MoveGroupCommander('manipulator')
                
        # 获取终端link的名称
        end_effector_link = arm.get_end_effector_link()
                        
        # 设置目标位置所使用的参考坐标系
        reference_frame = 'base_link'
        arm.set_pose_reference_frame(reference_frame)
                
        # 当运动规划失败后,允许重新规划
        arm.allow_replanning(True)
        
        # 设置位置(单位:米)和姿态(单位:弧度)的允许误差
        arm.set_goal_position_tolerance(0.001)
        arm.set_goal_orientation_tolerance(0.001)
       
        # 设置允许的最大速度和加速度
        arm.set_max_acceleration_scaling_factor(0.5)
        arm.set_max_velocity_scaling_factor(0.5)

        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(1)
               
        # 设置机械臂工作空间中的目标位姿,位置使用x、y、z坐标描述,
        # 姿态使用四元数描述,基于base_link坐标系
        target_pose = PoseStamped()
        target_pose.header.frame_id = reference_frame
        target_pose.header.stamp = rospy.Time.now()     
        target_pose.pose.position.x = 0.2593
        target_pose.pose.position.y = 0.0636
        target_pose.pose.position.z = 0.1787
        target_pose.pose.orientation.x = 0.911822
        target_pose.pose.orientation.y = -0.0269758
        target_pose.pose.orientation.z = 0.285694
        target_pose.pose.orientation.w = -0.293653
        
        # 设置机器臂当前的状态作为运动初始状态
        arm.set_start_state_to_current_state()
        
        # 设置机械臂终端运动的目标位姿
        arm.set_pose_target(target_pose, end_effector_link)
        
        # 规划运动路径
        traj = arm.plan()
        
        # 按照规划的运动路径控制机械臂运动
        arm.execute(traj)
        rospy.sleep(1)

        # 控制机械臂终端向右移动5cm
        arm.shift_pose_target(1, -0.05, end_effector_link)
        arm.go()
        rospy.sleep(1)
  
        # 控制机械臂终端反向旋转90度
        arm.shift_pose_target(3, -1.57, end_effector_link)
        arm.go()
        rospy.sleep(1)
           
        # 控制机械臂回到初始化位置
        arm.set_named_target('home')
        arm.go()

        # 关闭并退出moveit
        moveit_commander.roscpp_shutdown()
        moveit_commander.os._exit(0)

if __name__ == "__main__":
    MoveItIkDemo()

笛卡尔运动规划

工作空间的运动规划并没有对机器人终端轨迹有任何约束,目标位姿给定后,可以通过运动学反解获得关节空间下的各轴位置(即角度),接下来的规划和运动仍然在关节空间下完成。笛卡尔运动规划不仅考虑机械臂的起始位姿,还考虑了运动过程中的位姿约束。
使用如下命令实现MArm笛卡尔运动规划测试:

roslaunch marm_planning arm_planning_with_trail.launch
# 带路径约束的运动规划
rosrun marm_planning moveit_cartesian_demo.py _cartesian:=True
# 不带路径约束的运动规划
rosrun marm_planning moveit_cartesian_demo.py _cartesian:=False

marm_planning/scripts/moveit_cartesian_demo.py的源码如下:

import rospy, sys
import moveit_commander
from moveit_commander import MoveGroupCommander
from geometry_msgs.msg import Pose
from copy import deepcopy

class MoveItCartesianDemo:
    def __init__(self):
        # 初始化move_group的API
        moveit_commander.roscpp_initialize(sys.argv)

        # 初始化ROS节点
        rospy.init_node('moveit_cartesian_demo', anonymous=True)
                        
        # 初始化需要使用move group控制的机械臂中的arm group
        arm = MoveGroupCommander('manipulator')
        
        # 当运动规划失败后,允许重新规划
        arm.allow_replanning(True)
        
        # 设置目标位置所使用的参考坐标系
        arm.set_pose_reference_frame('base_link')
                
        # 设置位置(单位:米)和姿态(单位:弧度)的允许误差
        arm.set_goal_position_tolerance(0.001)
        arm.set_goal_orientation_tolerance(0.001)
        
        # 设置允许的最大速度和加速度
        arm.set_max_acceleration_scaling_factor(0.5)
        arm.set_max_velocity_scaling_factor(0.5)
        
        # 获取终端link的名称
        end_effector_link = arm.get_end_effector_link()

        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(1)
                                               
        # 获取当前位姿数据最为机械臂运动的起始位姿
        start_pose = arm.get_current_pose(end_effector_link).pose

        print start_pose
                
        # 初始化路点列表
        waypoints = []
                
        # 将初始位姿加入路点列表
        waypoints.append(start_pose)
            
        # 设置路点数据,并加入路点列表
        wpose = deepcopy(start_pose)
        wpose.position.z -= 0.2
        waypoints.append(deepcopy(wpose))

        wpose.position.x += 0.1
        waypoints.append(deepcopy(wpose))
        
        wpose.position.y += 0.1
        waypoints.append(deepcopy(wpose))

        fraction = 0.0   #路径规划覆盖率
        maxtries = 100   #最大尝试规划次数
        attempts = 0     #已经尝试规划次数
        
        # 设置机器臂当前的状态作为运动初始状态
        arm.set_start_state_to_current_state()
 
        # 尝试规划一条笛卡尔空间下的路径,依次通过所有路点
        while fraction < 1.0 and attempts < maxtries:
            (plan, fraction) = arm.compute_cartesian_path (
                                    waypoints,   # waypoint poses,路点列表
                                    0.01,        # eef_step,终端步进值
                                    0.0,         # jump_threshold,跳跃阈值
                                    True)        # avoid_collisions,避障规划
            
            # 尝试次数累加
            attempts += 1
            
            # 打印运动规划进程
            if attempts % 10 == 0:
                rospy.loginfo("Still trying after " + str(attempts) + " attempts...")
                     
        # 如果路径规划成功(覆盖率100%),则开始控制机械臂运动
        if fraction == 1.0:
            rospy.loginfo("Path computed successfully. Moving the arm.")
            arm.execute(plan)
            rospy.loginfo("Path execution complete.")
        # 如果路径规划失败,则打印失败信息
        else:
            rospy.loginfo("Path planning failed with only " + str(fraction) + " success after " + str(maxtries) + " attempts.")  

        rospy.sleep(1)

        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(1)
        
        # 关闭并退出moveit
        moveit_commander.roscpp_shutdown()
        moveit_commander.os._exit(0)

if __name__ == "__main__":
    try:
        MoveItCartesianDemo()
    except rospy.ROSInterruptException:
        pass

避障规划

机器人在工作空间内运动规划过程中存在障碍物时,需要进行避障规划。

使用如下命令实现MArm避障规划测试:

roslaunch marm_planning arm_planning.launch
rosrun marm_planning moveit_obstacles_demo.py

marm_planning/scripts/moveit_obstacles_demo.py的源码如下:

import rospy, sys
import moveit_commander
from moveit_commander import MoveGroupCommander, PlanningSceneInterface
from moveit_msgs.msg import  PlanningScene, ObjectColor
from geometry_msgs.msg import PoseStamped, Pose

class MoveItObstaclesDemo:
    def __init__(self):
        # 初始化move_group的API
        moveit_commander.roscpp_initialize(sys.argv)
        
        # 初始化ROS节点
        rospy.init_node('moveit_obstacles_demo')
        
        # 初始化场景对象
        scene = PlanningSceneInterface()
        
        # 创建一个发布场景变化信息的发布者
        self.scene_pub = rospy.Publisher('planning_scene', PlanningScene, queue_size=5)
        
        # 创建一个存储物体颜色的字典对象
        self.colors = dict()
        
        # 等待场景准备就绪
        rospy.sleep(1)
                        
        # 初始化需要使用move group控制的机械臂中的arm group
        arm = MoveGroupCommander('arm')
        
        # 获取终端link的名称
        end_effector_link = arm.get_end_effector_link()
        
        # 设置位置(单位:米)和姿态(单位:弧度)的允许误差
        arm.set_goal_position_tolerance(0.01)
        arm.set_goal_orientation_tolerance(0.05)
       
        # 当运动规划失败后,允许重新规划
        arm.allow_replanning(True)
        
        # 设置目标位置所使用的参考坐标系
        reference_frame = 'base_link'
        arm.set_pose_reference_frame(reference_frame)
        
        # 设置每次运动规划的时间限制:5s
        arm.set_planning_time(5)
        
        # 设置场景物体的名称
        table_id = 'table'
        box1_id = 'box1'
        box2_id = 'box2'
        
        # 移除场景中之前运行残留的物体
        scene.remove_world_object(table_id)
        scene.remove_world_object(box1_id)
        scene.remove_world_object(box2_id)    
        rospy.sleep(1)
        
        # 控制机械臂先回到初始化位置
        arm.set_named_target('home')
        arm.go()
        rospy.sleep(2)
        
        # 设置桌面的高度
        table_ground = 0.25
        
        # 设置table、box1和box2的三维尺寸
        table_size = [0.2, 0.7, 0.01]
        box1_size = [0.1, 0.05, 0.05]
        box2_size = [0.05, 0.05, 0.15]
        
        # 将三个物体加入场景当中
        table_pose = PoseStamped()
        table_pose.header.frame_id = reference_frame
        table_pose.pose.position.x = 0.26
        table_pose.pose.position.y = 0.0
        table_pose.pose.position.z = table_ground + table_size[2] / 2.0
        table_pose.pose.orientation.w = 1.0
        scene.add_box(table_id, table_pose, table_size)
        
        box1_pose = PoseStamped()
        box1_pose.header.frame_id = reference_frame
        box1_pose.pose.position.x = 0.21
        box1_pose.pose.position.y = -0.1
        box1_pose.pose.position.z = table_ground + table_size[2] + box1_size[2] / 2.0
        box1_pose.pose.orientation.w = 1.0   
        scene.add_box(box1_id, box1_pose, box1_size)
        
        box2_pose = PoseStamped()
        box2_pose.header.frame_id = reference_frame
        box2_pose.pose.position.x = 0.19
        box2_pose.pose.position.y = 0.15
        box2_pose.pose.position.z = table_ground + table_size[2] + box2_size[2] / 2.0
        box2_pose.pose.orientation.w = 1.0   
        scene.add_box(box2_id, box2_pose, box2_size)
        
        # 将桌子设置成红色,两个box设置成橙色
        self.setColor(table_id, 0.8, 0, 0, 1.0)
        self.setColor(box1_id, 0.8, 0.4, 0, 1.0)
        self.setColor(box2_id, 0.8, 0.4, 0, 1.0)
        
        # 将场景中的颜色设置发布
        self.sendColors()    
        
        # 设置机械臂的运动目标位置,位于桌面之上两个盒子之间
        target_pose = PoseStamped()
        target_pose.header.frame_id = reference_frame
        target_pose.pose.position.x = 0.2
        target_pose.pose.position.y = 0.0
        target_pose.pose.position.z = table_pose.pose.position.z + table_size[2] + 0.05
        target_pose.pose.orientation.w = 1.0
        
        # 控制机械臂运动到目标位置
        arm.set_pose_target(target_pose, end_effector_link)
        arm.go()
        rospy.sleep(2)

        # 设置机械臂的运动目标位置,进行避障规划
        target_pose2 = PoseStamped()
        target_pose2.header.frame_id = reference_frame
        target_pose2.pose.position.x = 0.2
        target_pose2.pose.position.y = -0.25
        target_pose2.pose.position.z = table_pose.pose.position.z + table_size[2] + 0.05
        target_pose2.pose.orientation.w = 1.0
        
        # 控制机械臂运动到目标位置
        arm.set_pose_target(target_pose2, end_effector_link)
        arm.go()
        rospy.sleep(2)
        
        # 控制机械臂回到初始化位置
        arm.set_named_target('home')
        arm.go()
        
        # 关闭并退出moveit
        moveit_commander.roscpp_shutdown()
        moveit_commander.os._exit(0)
        
    # 设置场景物体的颜色
    def setColor(self, name, r, g, b, a = 0.9):
        # 初始化moveit颜色对象
        color = ObjectColor()
        
        # 设置颜色值
        color.id = name
        color.color.r = r
        color.color.g = g
        color.color.b = b
        color.color.a = a
        
        # 更新颜色字典
        self.colors[name] = color

    # 将颜色设置发送并应用到moveit场景当中
    def sendColors(self):
        # 初始化规划场景对象
        p = PlanningScene()

        # 需要设置规划场景是否有差异     
        p.is_diff = True
        
        # 从颜色字典中取出颜色设置
        for color in self.colors.values():
            p.object_colors.append(color)
        
        # 发布场景物体颜色设置
        self.scene_pub.publish(p)

if __name__ == "__main__":
    try:
        MoveItObstaclesDemo()
    except KeyboardInterrupt:
        raise

Pick and Place示例

此节演示了一个简单的机器人pick and place应用,这个应用就是让机器人用夹爪将工作空间内的某个物体夹起来,然后将该物体放到目标位置。类似的功能在机器人的实际生产应用中使用非常广泛,例如码垛、搬运、挑拣等工作。

在这个应用例程中,假设已知物体的位置,MoveIt!需要控制机器人去抓取物体并放置到指定位置。

使用如下命令实现pick and place例程:

roslaunch marm_planning arm_planning.launch
rosrun marm_planning moveit_pick_and_place_demo.py

创建抓取的目标物体
创建一个用来抓取的目标物体,并将其设置颜色,代码如下:

target_size = [0.02,0.01,0.12]
target_pose = PoseStamped() 
target_pose.header.frame_id = REFERENCE_FRAME
target_pose.pose.position.x = 0.32
target_pose.pose.position.y = 0.0
target_pose.pose.position.z = table_ground + table_size[2] + target_size[2]/2.0
target_pose.pose.orientation.w =1.0

scene.add_box(target_id, target_pose, target_size)

self.setColor(target_id, 0.9, 0.9, 0, 1.0)

设置目标物体的放置位置
创建一个place,并准备放置目标物体的位置,代码如下:

place_pose = PoseStamped()
place_pose.header.frame_id = REFERENCE_FRAME
place_pose.pose.position.x = 0.32
place_pose.pose.position.y = -0.2
place_pose.pose.position.z = table_ground + table_size[2] + target_size[2]/2.0
place_pose.pose.orientation.w = 1.0

生成抓取姿态
生成抓取姿态的代码如下:

grasp_pose = target_pose
grasps = self.make_grasps(grasp_pose, [target_id]) 
for grasp in grasps: 
	self.gripper_pose_pub.publish(grasp.grasp_pose)
	rospy.sleep(0.2)

在抓去之前,需要确认目标位姿是否存在正确的抓取姿态。在pick时,目标物体的抓取位置就是物体摆放的位置,然后使用make_grasps()函数生成抓取姿态的列表,并将抓取姿态的消息发布,显示在rviz中,代码如下:

def make_grasps(self, initial_pose_stamped, allowed_touch_objects):
	# 初始化抓取姿态对象 
	g = Grasp()
	# 创建夹爪张开、闭合的姿态 
	g.pre_grasp_posture = self.make_gripper_posture(GRIPPER_OPEN)
	g.grasp_posture = self.make_gripper_posture(GRIPPER_CLOSED)
	# 设置期望的夹爪靠近、撤离目标的参数
	g.pre_grasp_approach = self.make_gripper_translation(0.01, 0.1, [1.0, 0.0, 0.0])
	g.post_grasp_retreat = self.make_gripper_translation(0.1, 0.15, [0.0, -1.0, 1.0])
	# 设置抓取姿态
	g.grasp_pose = initial_pose_stamped 
	# 需要尝试改变姿态的数据列表
	pitch_vals = [0, 0.1, -0.1, 0.2, -0.2, 0.3, -0.3]
	yaw_vals = [0]
	# 抓取姿态的列表
	grasps = []
	# 改变姿态,生成抓取动作 
	for y in yaw_vals:
		for p in pitch_vals:
			# 欧拉角到四元数的转换
			q = quaternion _ from _ euler (0, p , y )
			# 设置抓取的姿态
			g.grasp_pose.pose.orientation.x = q[0]
			g.grasp_pose.pose.orientation.y = q[1]
			g.grasp_pose.pose.orientation.z = q[2]
			g.grasp_pose.pose.orientation.w = q[3]
			# 设置抓取的唯一 id 号
			g.id = str(len(grasps))
			# 设置允许接触的物体
			g.allowed_touch_objects = allowed_touch_objects
			# 将本次规划的抓取放入抓取列表中
			grasps.append(deepcopy(g))
	# 返回抓取列表
	return grasps

make_grasps()函数通过pitch角度的变化得到不同的抓取姿态。
pick
设置pick时的代码如下:

while result != MoveItErrorCodes.SUCCESS and n_attempts < max_pick_attempts:
	n_attempts += 1
	rospy.loginfo("Pick attempt: " + str(n_attempts))
	result = arm.pick(target_id, grasps)
	rospy.sleep(0.2)

接下来机器人就可以尝试 pick 目标物体了。针对不同的抓取姿态,如果无法求解运动学逆解或者规划轨迹会发生碰撞,pick 的运动规划就会出错,因此例程设置重新尝试规划的次数。如果规划成功,则 pick()会控制机器人按照规划轨迹运动。

place
如果 pick 阶段的运动控制没有问题,那我们的工作已经完成了一半。接下来的任务是 place ,将目标物体放置到指定位置,代码如下:

if result == MoveItErrorCodes.SUCCESS:
	result = None
	n_attempts = 0
	# 生成放置姿态 
	places = self.make_places(place_pose)
	# 重复尝试放置,直到成功或者最多尝试次数
	while result != MoveItErrorCodes.SUCCESS and n _ attempts < max_place_attempts:
		n _ attempts += 1
		rospy.loginfo("Placeattempt: " + str(n_attempts))
		for place in places:
			result = arm.place(target_id, place)
			if result == MoveItErrorCodes.SUCCESS:
				break
		rospy.sleep(0.2)

place 与 pick 的原理是一致的,同样需要使用 make_places() 生成一个可能的夹爪放置姿态列表。然后根据这些可能的放置姿态尝试规划 place 操作的轨迹,规划成功后就可以控制机器人运动了,代码如下:

def make_places(self, init_pose)
	# 初始化放置抓取物体的位置
	place = PoseStamped()
	# 设置放置抓取物体的位置
	place = init_pose
	# 定义 x 方向上用于尝试放置物体的偏移参数
	x_vals = [0, 0.005, 0.01, 0.015, -0.005, -0.01, -0.0151]
	# 定义 y 方向上用于尝试放置物体的偏移参数
	y_vals = [0, 0.005, 0.01, 0.015, -0.005, -0.01, -0.015]
	pitch_vals = [0]
	# 定义用于尝试放置物体的偏航角参数
	yaw_vals = [0]
	# 定义放置物体的姿态列表
	places = []
	# 生成每个角度和偏移方向上的抓取姿态
	for y in yaw_vals:
		for p in pitch_vals:
			for y in y_vals:
				for x in x_vals:
					place.pose.position.x = init_pose.pose.position.x + x
					place.pose.position.y = init_pose.pose.position.y + y
					# 欧拉角到四元数的转换
					q = quaternion_from_euler(0, p, y)
					# 欧拉角到四元数的转换
					place.pose.orientation.x = q[0] 
					place.pose.orientation.y = q[1] 
					place.pose.orientation.z = q[2] 
					place.pose.orientation.w = q[3]
					# 将该放置姿态加入列表
					places.append(deepcopy(place))
	# 返回放置物体的姿态列表
	return places

make_places() 和 make_grasps() 的实现原理相同,都是通过设定的方向偏移、旋转列表生成多个可能的终端姿态

通过以上例程,我们就实现了一个简单的 pick and place 应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/557873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一图看懂!RK3568与RK3399怎么选?

▎简介 RK3568和RK3399都是Rockchip公司的处理器&#xff0c;具有不同的特点和适用场景。以下是它们的主要区别和应用场景。 ▎RK3568 RK3568是新一代的高性能处理器&#xff0c;采用了22nm工艺&#xff0c;具有更高的性能和更低的功耗。它支持4K视频解码和编码&#xff0c;支持…

某程序员辞职后,接6份兼职,月入3w+

对于程序员来说&#xff0c;35岁真的是很关键。 如果成为架构师或者是成为管理方面的人才&#xff0c;还是不用担心失业。要是你30多岁还在一线写代码&#xff0c;那被裁的可能性很大。即使你现在没有失业&#xff0c;也说明你能力很一般。 最近在职场论坛上看到这样一个帖子…

互联网广告丨行业知识储备

文章状态&#xff1a;持续更新中 更新时间&#xff1a;2023.05.22 本文不同于专业咨询机构输出的专业行业调研报告&#xff0c;仅作为产品经理对互联网广告行业的一些基础知识储备。文章会以产品经理的角度&#xff0c;从行业概述、行业目标与愿景、行业生态、行业的发展、行业…

数仓中指标-标签,维度-度量,自然键-代理键等各名词深度解析

作为一个数据人&#xff0c;是不是经常被各种名词围绕&#xff0c;是不是对其中很多概念认知模糊。有些词虽然只有一字之差&#xff0c;但是它们意思完全不同&#xff0c;今天我们就来了解下数仓建设及数据分析时常见的一些概念含义及它们之间的关系。 本文首发于公众号【五分钟…

LiveNVR视频平台接收无人机等移动终端RTMP推流后转成GB28181协议输出级联到GB28181视频平台的操作说明...

1、需求介绍 目前很多移动终端设备(如无人机等)只支持RTMP推流输出&#xff0c;不支持GB28181协议。但是又有需要通过GB28181协议接入到视频平台的需求。比如有些大疆无人机产品不能直接注册国标平台&#xff0c;只能rtmp推流。那么&#xff0c;项目中如果将无人机的rtmp的推流…

Stablediffusion模型diffusesr格式和ckpt格式相互转换

参考资料&#xff1a; diffusers的源码 [github] 因为小博客可能看的人很少&#xff0c;所以我写的啰嗦一点&#xff0c;想直接看如何互相转换的朋友可以直接转到文末的代码段。 当你在学习Stablediffusion这个开源的t2i模型时&#xff0c;不可避免地会碰到两种模型权重的存储格…

在rk3568移植rtl8723du,配置成wifi ap模式

1、在路径添加rtl8723du模块代码 kernel/drivers/net/wireless/rockchip_wlan 添加rtl8723du 2、修改Makefile 修改对应的路径 修改交叉编译的工具的路径和内核路径 3、修改rockchip_wlan目录下的Makefile 添加这个 obj-$(CONFIG_RTL8723DU) rtl8723du/ 4、修改rockchip_w…

淘宝按关键字搜索淘宝商品 API 参数及返回值说明 翻页展示 含调用示例

淘宝关键字搜索接口&#xff0c;是复原我们在淘宝购物时&#xff0c;在搜索栏内输入关键字&#xff0c;即可获取到相关商品列表&#xff0c;商品信息齐全&#xff0c;支持翻页展示。同时&#xff0c;传入参数sort可按价格排序&#xff0c;也可筛选响应价格段的商品。商品信息是…

关于【Stable-Diffusion WEBUI】基础模型对应VAE的问题

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;什么是VAE&#xff08;二&#xff09;模型嵌入VAE了么&#xff08;三&#xff09;我们能做什么&#xff08;3.1&#xff09;准备常见的VAE&#xff08;3.2&#xff09;下载模型对应的VAE&#xff08;3.3&…

小航编程题库GoC南海区小学四年级模拟测试题(含题库教师账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 填空题8.0分 删除编辑 答案:100 第1题画一条高度为100&#xff0c;粗为5的竖线。 //程序名:直线 //作者: int main() { pen.size(5)…

Python 面向对象高级--继承,方法重写,权限,类成员,实例成员

1.继承入门 class 子类名(父类名): 面向对象中的继承: 指的是多个类之间的所属关系&#xff0c;即子类默认继承父类的所有属性和方法. 面向对象中继承的作用: 提高代码的复用率, 减少重复代码的书写. class Animal():def __init__(self,name,age):self.name nameself.age a…

聚焦珠宝产业数字化变革 世界珠宝数字化发展论坛在厦门举办

家庭周报厦门讯 5月18日&#xff0c;世界珠宝数字化发展论坛在厦门举办。本活动由北京北大宝石鉴定中心作为指导单位&#xff0c;中国广告主协会品牌建设与营销专业委员会、世界珠宝数字化发展论坛组委会主办&#xff0c;北京真心红珠宝有限公司承办。这是在数字中国建设整体布…

S20330-SRS步进电机最简单的驱动方法

​ S20330-SRS步进电机最简单的驱动方法 步进电机最简单的驱动方法&#xff0c;了解四轴步进电机驱动器原理 四轴步进电机驱动器原理-简介四轴步进电机驱动器&#xff0c;其实就是一种将电脉冲转化为角位移的执行机构。首先步进驱动器会接收到一个脉冲信号&#xff0c;然后它按…

小航编程题库2022年NOC决赛图形化(小高组)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:A 第1题运行下面的程序&#xff0c;最终“我的变量”的值是多少&#xff1f; A、5B、10C、25D、30 答案…

排序算法——冒泡排序详解及优化

冒泡排序 排序的稳定性冒泡排序优化后的冒泡排序冒泡排序的复杂度 排序的稳定性 对于一个排序算法&#xff0c;假设两个相同的元素Ai和Aj 在排序前这两个元素满足条件i<j&#xff0c;即Ai在Aj之前 在排序后Ai仍在Aj之前&#xff0c;则称为排序算法为稳定排序 否则称这个算法…

【Linux】多进程实现并发服务器

多进程实现并发服务器 1.此时来一个客户端请求时 创建cfd1 属于是阻塞状态 如果再有一个客户端就没办法提取了 2.所以要fork一个子进程&#xff0c;此时关闭父进程的cfd1&#xff0c;关闭子进程的lfd;不影响其它客户端请求连接 具体流程&#xff1a; 创建套接字 绑定 监听 …

DNDC模型二:减排潜力模拟

查看原文>>>生态系统模型&#xff1a;DNDC、CMIP6、GEE林业、无人机遥感、InVEST、Noah-MP、ArcGIS Pro、APSIM模型等 由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。国家领导人在多次重要…

ZooKeeper 用的好好地,Kafka 为什么要抛弃 ZooKeeper?

ZooKeeper 的作用 ZooKeeper 是一个开源的分布式协调服务框架&#xff0c;你也可以认为它是一个可以保证一致性的分布式(小量)存储系统。特别适合存储一些公共的配置信息、集群的一些元数据等等。 它有持久节点和临时节点&#xff0c;而临时节点这个玩意再配合 Watcher 机制就…

升级到PyTorch 2.0的技巧总结

PyTorch 2.0 发布也有一段时间了&#xff0c;大家是不是已经开始用了呢&#xff1f; PyTorch 2.0 通过引入 torch.compile&#xff0c;可以显着提高训练和推理速度。 与 eagerly 模式相反&#xff0c;编译 API 将模型转换为中间计算图&#xff08;FX graph&#xff09;&#xf…

App外包开发上线Google Play流程

完成App开发后需要在各大应用市场上线&#xff0c;国内的应用市场比较多&#xff0c;各自的规则也不相同&#xff0c;上线审核也比较复杂&#xff1b;国外上线主要是Google Play市场&#xff0c;它更重视隐私的保护&#xff0c;必须严格按照规范来保护个人隐私&#xff0c;因此…