easy_handeye代码分析


关于easy_handeye官网的教程的翻译写在了印象笔记上面,这里是对于其代码的解释和流程说明。

handeye_calibration.py

https://github.com/IFL-CAMP/easy_handeye/blob/master/easy_handeye/src/easy_handeye/handeye_calibration.py

import os
import yaml
import rospy
from geometry_msgs.msg import Vector3, Quaternion, Transform, TransformStamped

class HandeyeCalibration(object):
    #用来存储手眼标定中的参数和变换
    DIRECTORY = os.path.expanduser('~/.ros/easy_handeye')
   #标定配置文件的默认存储位置
    def __init__(self,
                 eye_on_hand=False,#bool
                 robot_base_frame=None,#string
                 robot_effector_frame=None,#string
                 tracking_base_frame=None,#string
                 transformation=None):#((float, float, float), (float, float, float, float))
        """
        Creates a HandeyeCalibration object.
        :param eye_on_hand: if false, it is a eye-on-base calibration
        :param robot_base_frame: needed only for eye-on-base calibrations: robot base link tf name
        :param robot_effector_frame: needed only for eye-on-hand calibrations: robot tool tf name
        :param tracking_base_frame: tracking system tf name
        :param transformation: transformation between optical origin and base/tool robot frame as tf tuple
        :return: a HandeyeCalibration object
        :rtype: easy_handeye.handeye_calibration.HandeyeCalibration*
        """
        if transformation is None:
            transformation = ((0, 0, 0), (0, 0, 0, 1))
        self.eye_on_hand = eye_on_hand
        self.transformation = TransformStamped(transform=Transform(
            Vector3(*transformation[0]), Quaternion(*transformation[1])))
        if self.eye_on_hand:
            self.transformation.header.frame_id = robot_effector_frame
        else:
            self.transformation.header.frame_id = robot_base_frame
        self.transformation.child_frame_id = tracking_base_frame
        self.filename = HandeyeCalibration.DIRECTORY + '/' + rospy.get_namespace().rstrip('/').split('/')[-1] + '.yaml'

    def to_dict(self):
        """
        Returns a dictionary representing this calibration. 
        dictionary是python中的一种数据结构,每个字典元素都有键(key)和值(value)两个属性,键用于定义和标识字典元素,可以是字符串或者整数;值是字典元素对应的值。
        :return: a dictionary representing this calibration.
        :rtype: dict[string, string|dict[string,float]]
        """
        ret = {
            'eye_on_hand': self.eye_on_hand,
            'tracking_base_frame': self.transformation.child_frame_id,
            'transformation': {
                'x': self.transformation.transform.translation.x,
                'y': self.transformation.transform.translation.y,
                'z': self.transformation.transform.translation.z,
                'qx': self.transformation.transform.rotation.x,
                'qy': self.transformation.transform.rotation.y,
                'qz': self.transformation.transform.rotation.z,
                'qw': self.transformation.transform.rotation.w
            }
        }#定义为ret的字典
        if self.eye_on_hand:
            ret['robot_effector_frame'] = self.transformation.header.frame_id
        else:
            ret['robot_base_frame'] = self.transformation.header.frame_id
  	#直接在字典里添加元素 robot_effector_frame 或者 robot_base_frame
        return ret
	#所以这个函数的作用就是定义ret这个字典。
	
    def from_dict(self, in_dict):
        """
        Sets values parsed from a given dictionary.
        :param in_dict: input dictionary.
        :type in_dict: dict[string, string|dict[string,float]]
        :rtype: None
        """
        self.eye_on_hand = in_dict['eye_on_hand']
        self.transformation = TransformStamped(
            child_frame_id=in_dict['tracking_base_frame'],
            transform=Transform(
                Vector3(in_dict['transformation']['x'],
                        in_dict['transformation']['y'],
                        in_dict['transformation']['z']),
                Quaternion(in_dict['transformation']['qx'],
                           in_dict['transformation']['qy'],
                           in_dict['transformation']['qz'],
                           in_dict['transformation']['qw'])
            )
        )
        if self.eye_on_hand:
            self.transformation.header.frame_id = in_dict['robot_effector_frame']
        else:
            self.transformation.header.frame_id = in_dict['robot_base_frame']
	#感觉像是拷贝构造函数的意思....对ret进行重新赋值
	
    def to_yaml(self):
        """
        Returns a yaml string representing this calibration.
        :return: a yaml string
        :rtype: string
        """
        return yaml.dump(self.to_dict())

    def from_yaml(self, in_yaml):
        """
        Parses a yaml string and sets the contained values in this calibration.
        :param in_yaml: a yaml string
        :rtype: None
        """
        self.from_dict(yaml.load(in_yaml))

    def to_file(self):
        """
        Saves this calibration in a yaml file in the default path.
        The default path consists of the default directory and the namespace the node is running in.
        :rtype: None
        """
        if not os.path.exists(HandeyeCalibration.DIRECTORY):
            os.makedirs(HandeyeCalibration.DIRECTORY)

        with open(self.filename, 'w') as calib_file:
            calib_file.write(self.to_yaml())
	#with open() as的用法是在文件处理中获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。
	#函数的作用是在文件中写入参数
	
    def from_file(self):
        """
        Parses a yaml file in the default path and sets the contained values in this calibration.
        The default path consists of the default directory and the namespace the node is running in.
        :rtype: None
        """
        with open(self.filename) as calib_file:
            self.from_yaml(calib_file.read())
            #函数的作用是从文件中读入参数
         

    def from_parameters(self):
        """
        Stores this calibration as ROS parameters in the namespace of the current node.
        :rtype: None
        """
        calib_dict = {}
        root_params = ['eye_on_hand', 'tracking_base_frame']
        for rp in root_params:
            calib_dict[rp] = rospy.get_param(rp)
        if calib_dict['eye_on_hand']:
            calib_dict['robot_effector_frame'] = rospy.get_param('robot_effector_frame')
        else:
            calib_dict['robot_base_frame'] = rospy.get_param('robot_base_frame')
        transf_params = 'x', 'y', 'z', 'qx', 'qy', 'qz', 'qw'
        calib_dict['transformation'] = {}
        for tp in transf_params:
            calib_dict['transformation'][tp] = rospy.get_param('transformation/'+tp)
        self.from_dict(calib_dict)

    def to_parameters(self):
        """
        Fetches a calibration from ROS parameters in the namespace of the current node into this object.
        :rtype: None
        """
        calib_dict = self.to_dict()
        root_params = ['eye_on_hand', 'tracking_base_frame']
        if calib_dict['eye_on_hand']:
            root_params.append('robot_effector_frame')
        else:
            root_params.append('robot_base_frame')

        for rp in root_params:
            rospy.set_param(rp, calib_dict[rp])

        transf_params = 'x', 'y', 'z', 'qx', 'qy', 'qz', 'qw'

        for tp in transf_params:
	rospy.set_param('transformation/'+tp, calib_dict['transformation'][tp])

handeye_calibrator.py

https://github.com/IFL-CAMP/easy_handeye/blob/master/easy_handeye/src/easy_handeye/handeye_calibrator.py

import rospy
import tf
from tf import transformations as tfs
from geometry_msgs.msg import Vector3, Quaternion, Transform
from visp_hand2eye_calibration.msg import TransformArray
from visp_hand2eye_calibration.srv import compute_effector_camera_quick
from easy_handeye.handeye_calibration import HandeyeCalibration


class HandeyeCalibrator(object):
    """
    Connects tf and ViSP hand2eye to provide an interactive mean of calibration.
    """

    MIN_SAMPLES = 2  
    # TODO: correct? this is what is stated in the paper, but sounds strange
    #Minimum samples required for a successful calibration.

    def __init__(self):
        self.eye_on_hand = rospy.get_param('eye_on_hand', False)
        self.robot_effector_frame = rospy.get_param('robot_effector_frame', 'tool0')
        self.robot_base_frame = rospy.get_param('robot_base_frame', 'base_link')
        self.tracking_base_frame = rospy.get_param('tracking_base_frame', 'optical_origin')
        self.tracking_marker_frame = rospy.get_param('tracking_marker_frame', 'optical_target')
        self.listener = tf.TransformListener()
        self.broadcaster = tf.TransformBroadcaster()
        self.transformer = tf.TransformerROS()
        self.samples = []
        # calibration service
        rospy.wait_for_service('compute_effector_camera_quick')
        self.calibrate = rospy.ServiceProxy( 'compute_effector_camera_quick', compute_effector_camera_quick)
        

    def _wait_for_tf_init(self):
        """
        Waits until all needed frames are present in tf.
        :rtype: None
        """
        self.listener.waitForTransform(self.robot_base_frame, self.robot_effector_frame, rospy.Time(0), rospy.Duration(10))
        self.listener.waitForTransform(self.tracking_base_frame, self.tracking_marker_frame, rospy.Time(0), rospy.Duration(60))

    def _wait_for_transforms(self):
        """
        Waits until the needed transformations are recent in tf.
        :rtype: rospy.Time
        """
        now = rospy.Time.now()
        self.listener.waitForTransform(self.robot_base_frame, self.robot_effector_frame, now, rospy.Duration(10))
        self.listener.waitForTransform(self.tracking_base_frame, self.tracking_marker_frame, now, rospy.Duration(10))
        return now

    def _get_transforms(self, time=None):
        """
        Samples the transforms at the given time.
        :param time: sampling time (now if None)
        :type time: None|rospy.Time
        :rtype: dict[str, ((float, float, float), (float, float, float, float))]
        """
        if time is None:
            time = self._wait_for_transforms()

        # here we trick the library (it is actually made for eye_on_hand only). Trust me, I'm an engineer
        if self.eye_on_hand:
            rob = self.listener.lookupTransform(self.robot_base_frame, self.robot_effector_frame, time)
        else:
            rob = self.listener.lookupTransform(self.robot_effector_frame, self.robot_base_frame, time)
        opt = self.listener.lookupTransform(self.tracking_base_frame, self.tracking_marker_frame, time)
        return {'robot': rob, 'optical': opt}

    def take_sample(self):
        """
        Samples the transformations and appends the sample to the list.
        对转换进行采样并将样本附加到列表中。
        :rtype: None
        """
        rospy.loginfo("Taking a sample...")
        transforms = self._get_transforms()
        rospy.loginfo("Got a sample")
        self.samples.append(transforms)

    def remove_sample(self, index):
        """
        Removes a sample from the list.
        :type index: int
        :rtype: None
        """
        if 0 <= index < len(self.samples):
            del self.samples[index]

    @staticmethod
    def _tuple_to_msg_transform(tf_t):
        """
        Converts a tf tuple into a geometry_msgs/Transform message
        将tf元组转换为geometry_msgs / Transform消息
        :type tf_t: ((float, float, float), (float, float, float, float))
        :rtype: geometry_msgs.msg.Transform
        """
        transl = Vector3(*tf_t[0])
        rot = Quaternion(*tf_t[1])
        return Transform(transl, rot)

    def get_visp_samples(self):
        """
        Returns the sample list as a TransformArray.
        :rtype: visp_hand2eye_calibration.msg.TransformArray
        """
        hand_world_samples = TransformArray()
        #hand_world_samples.header.frame_id = self.robot_base_frame
        hand_world_samples.header.frame_id = self.tracking_base_frame
        # DONTFIXME: yes, I know, it should be like the line above.
        # thing is, otherwise the results of the calibration are wrong. don't ask me why

        camera_marker_samples = TransformArray()
        camera_marker_samples.header.frame_id = self.tracking_base_frame

        for s in self.samples:
            to = HandeyeCalibrator._tuple_to_msg_transform(s['optical'])
            camera_marker_samples.transforms.append(to)
            tr = HandeyeCalibrator._tuple_to_msg_transform(s['robot'])
            hand_world_samples.transforms.append(tr)

        return hand_world_samples, camera_marker_samples

    def compute_calibration(self):
        """
        Computes the calibration through the ViSP service and returns it.
        :rtype: easy_handeye.handeye_calibration.HandeyeCalibration
        """
        if len(self.samples) < HandeyeCalibrator.MIN_SAMPLES:
            rospy.logwarn("{} more samples needed! Not computing the calibration".format(
                HandeyeCalibrator.MIN_SAMPLES - len(self.samples)))
            return

        # Update data
        hand_world_samples, camera_marker_samples = self.get_visp_samples()

        if len(hand_world_samples.transforms) != len(camera_marker_samples.transforms):
            rospy.logerr("Different numbers of hand-world and camera-marker samples!")
            raise AssertionError

        rospy.loginfo("Computing from %g poses..." % len(self.samples))

        try:
            result = self.calibrate(camera_marker_samples, hand_world_samples)
            rospy.loginfo("Computed calibration: {}".format(str(result)))
            transl = result.effector_camera.translation
            rot = result.effector_camera.rotation
            result_tuple = ((transl.x, transl.y, transl.z),
                            (rot.x, rot.y, rot.z, rot.w))

            ret = HandeyeCalibration(self.eye_on_hand,
                                     self.robot_base_frame,
                                     self.robot_effector_frame,
                                     self.tracking_base_frame,
                                     result_tuple)

            return ret

        except rospy.ServiceException as ex:
            rospy.logerr("Calibration failed: " + str(ex))
return None

关于@staticmethod的说明
一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。
而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。 这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于命名空间的整洁。如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。

参考博客:https://blog.csdn.net/handsomekang/article/details/9615239

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值