Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
Tutorial: Eye-to-hand IBVS with Panda 7-dof robot from Franka Emika

1. Introduction

This tutorial explains how to achieve an eye-to-hand image-based visual servoing (IBVS) with Franka Emika's Panda 7-dof robot, with a fixed Realsense camera mounted on a tripod and observing the robot's end-effector, to which we've attached an Apriltag.

Panda robot in eye-to-hand configuration.

The proposed image-based visual servoing will consider the four 2D image point visual features corresponding to the corners of the Apritag.

Let us note:

  • $ {^e}{\bf J}_e $ the robot Jacobian in the end-effector frame relative to the end-effector frame
  • $ {^c}{\bf V}_e $ the 6-by-6 velocity twist matrix corresponding to the $ {^c}{\bf M}_e $ camera to end-effector frames transformation that allows to transform the velocity from point e in end-effector frame to point c in camera frame
  • $ {^c}{\bf V}_f $ the 6-by-6 velocity twist matrix corresponding to the $ {^c}{\bf M}_f $ camera to robot reference frames transformation that allows to transform the velocity from point f in robot reference frame to point c in camera frame
  • $ {^f}{\bf V}_e $ the 6-by-6 velocity twist matrix corresponding to the $ {^f}{\bf M}_e $ robot reference to robot end-effector frames transformation that allows to transform the velocity from point e in robot end-effector frame to point f in robot reference frame
  • $ {\bf s}(t) $ current image point visual features vector
  • $ {\bf s}^{*} $ desired current image point visual features vector
  • $ {\widehat {\bf L}}_{s} $ the interaction matrix corresponding to image point visual features that can be the current one updated at each iteration of the loop $ {\bf s}(t) $, or the desired one $ {\bf s}^{*} $
  • $ \lambda $ is the gain of the controller
  • $ {\dot {\bf q}} $ the joint velocities to apply tho the robot to achieve the eye-to-hand visual servoing.

In this tutorial we will consider two different eye-to-hand control laws:

  • The first one called "L_cVe_eJe" implemented in servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe.cpp where joint velocities are computed thanks to:

    \‍[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{e} {^c}{\bf V}_e {^e}{\bf J}_e} \right)^{+} {\bf e}\‍]

    Here to build $ {^c}{\bf V}_e $ there is the need to compute $ {^c}{\bf M}_e = {^c}{\bf M}_o \; {^o}{\bf M}_e$ where $ {^c}{\bf M}_o $ is the pose of the object in the camera frame and $ {^o}{\bf M}_e $ constant transformation is obtained by extrinsic calibration.
  • The second one called "L_cVf_fVe_eJe" implemented in servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe.cpp where joint velocities are computed thanks to:

    \‍[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{s=s(t)} {^c}{\bf V}_f {^f}{\bf V}_e {^e}{\bf J}_e} \right)^{+} ({\bf s}(t) - {\bf s}^*)\‍]

    This control law is recommended since it doesn't need to compute the pose of the object $ {^c}{\bf M}_o $. Here we will build $ {^c}{\bf V}_f $ thanks to constant $ {^c}{\bf M}_f $ transformation obtained by extrinsic calibration, while $ {^f}{\bf V}_e $ is here build thanks to $ {^f}{\bf M}_e $ given by the robot SDK.
  • The third one called "Ldes_cVf_fVe_eJe" implemented in servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe.cpp where joint velocities are computed thanks to:

    \‍[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{s=s^{*}} {^c}{\bf V}_f {^f}{\bf V}_e {^e}{\bf J}_e} \right)^{+} ({\bf s}(t) - {\bf s}^*)\‍]

    Unlike previous control laws, this control law doesn't need to compute the pose of the object $ {^c}{\bf M}_o $. Here we will build $ {^c}{\bf V}_f $ thanks to constant $ {^c}{\bf M}_f $ transformation obtained by extrinsic calibration, while $ {^f}{\bf V}_e $ is here build thanks to $ {^f}{\bf M}_e $ given by the robot odometry.

We suppose here that you have:

  • a Panda robot in its research version from Franka Emika that will be controlled throw vpRobotFranka class.
  • an Intel Realsense D345 camera mounted on a tripod. Images will be acquired thanks to vpRealSense2 class. Note that this tutorial should also work with any other Intel Realsense camera.
  • an Apriltag 36h11 attached to the robot end-effector. If needed follow 2. Print an AprilTag marker section.

2. Prerequisites

2.1. Create a folder to host data

The binaries corresponding to servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe.cpp and servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe.cpp are build and available in "${VISP_WS}/visp-build/example/servo-franka/" folder. Inside this folder we recommand to create a folder (let say "data-calib") in which we will record data camera intrinsic and extrinsic calibration

$ cd ${VISP_WS}/visp-build/example/servo-franka/
$ mkdir data-calib

2.2. Calibrate camera intrinsics

Even though the Realsense camera can retrieve intrinsic factory parameters, we recommend calibrating your camera to obtain more accurate results. To this end follow Tutorial: Camera intrinsic calibration.

To resume:

  • Print an OpenCV chessboard OpenCV_Chessboard.pdf
  • Acquire images of the chessboard
    $ cd ${VISP_WS}/visp-build/example/servo-franka/data-calib
    $ ../../../tutorial/grabber/tutorial-grabber-realsense --seqname chessboard-%02d.jpg --record 1
  • In "${VISP_WS}/visp-build/example/servo-franka/data-calib/" folder create calib-chessboard.cfg file with the following content
    # Number of inner corners per a item row and column. (square, circle)
    BoardSize_Width: 9
    BoardSize_Height: 6
    # The size of a square in meters
    Square_Size: 0.025
    # The type of pattern used for camera calibration.
    # One of: CHESSBOARD or CIRCLES_GRID
    Calibrate_Pattern: CHESSBOARD
    # The input image sequence to use for calibration
    Input: chessboard-%02d.jpg
    # Tempo in seconds between two images. If > 10 wait a click to continue
    Tempo: 1
  • In this file update the "Square_Size" parameter set by default to 0.025 m to the size of your printed chessboard
  • Run the intrinsic calibration app
    $ cd ${VISP_WS}/visp-build/example/servo-franka/data-calib/
    $ ../../../apps/calibration/intrinsic/visp-calibrate-camera calib-chessboard.cfg --output franka_camera_visp.xml
  • Results are saved in "${VISP_WS}/visp-build/example/servo-franka/data-calib/franka_camera_visp.xml"

2.3. Calibrate camera extrinsics

To achieve an eye-to-hand visual servoing depending on the visual-servoing scheme, there is the need to know the transformation between the robot reference and the camera frames or the transformation between the robot end-effector and the object (in our case the Apriltag) frames.

The Tutorial: Camera eye-to-hand extrinsic calibration explains how to estimate both transformations. Follow the steps corresponding to our use case 4.1. Panda robot + Realsense. To resume:

  • The first step is to aquire images of your object, the Apriltag, for different robot end-effector positions. Usually we acquire between 6 and 10 images. By default the robot controller IP is 192.168.1.1. If your Franka has an other IP (let say 192.168.30.10) use "--ip" option like:
    $ cd ${VISP_WS}/visp-build/example/servo-franka/data-calib/
    $ ../../../apps/calibration/hand-eye/visp-acquire-franka-calib-data --ip 192.168.30.10 --output-folder .
  • It records the following outputs in "${VISP_WS}/visp-build/example/servo-franka/data-calib/" folder:
    • franka_camera.xml: XML file that contains the intrinsic camera parameters extracted from camera firmware
    • couples of franka_image-<number>.png + franka_pose_rPe-<number>.txt with number starting from 1. franka_pose_rPe-<number>.yaml is the pose of the end-effector expressed in the robot reference frame $^r{\bf M}_e$, while franka_image-<number>.png is the image captured at the corresponding robot position.
  • Given the camera intrinsic parameters estimated previously and the set of images, compute now the tag poses running (adapt the tag size parameter to your use case):
    $ cd ${VISP_WS}/visp-build/example/servo-franka/data-calib/
    $ ../../../apps/calibration/hand-eye/visp-compute-apriltag-poses \
    --tag-size 0.048 \
    --input franka_image-%d.png \
    --intrinsic franka_camera_visp.xml \
    --output franka_pose_cPo_%d.yaml
  • Finally estimate the extrinsic parameters, running:
    $ cd ${VISP_WS}/visp-build/example/servo-franka/data-calib/
    $ ../../../apps/calibration/hand-eye/visp-compute-eye-to-hand-calibration \
    --data-path . \
    --rPe franka_pose_rPe_%d.yaml \
    --cPo franka_pose_cPo_%d.yaml \
    --output-rPc franka_rPc.yaml \
    --output-ePo franka_ePo.yaml
  • Extrinsic parameters will be saved in "${VISP_WS}/visp-build/example/servo-franka/data-calib/" folder where you will find
    • franka_rPc.yaml file that contains the transformation between the robot reference and the camera frames
    • franka_ePo.yaml file that contains the transformation between the end-effector and the apriltag frames.

2.4. Learn desired visual features

To ensure that the visual servo loop converges to a desired position within the robot's joint limits, the desired visual features are learned. More precisely, for each tag corner we will save $(x, y, Z)$. Since we are using a 0.048 m large Apriltag we will run servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe binary corresponding to servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe.cpp source code.

Note
You can also use the servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe binary corresponding to servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe source code with the same command line options.
Warning
In our case, we are using an Apriltag 0.048 m wide. Adapt the value of parameter ”--tag-size” to the dimensions of your tag.
$ cd ${VISP_WS}/visp-build/example/servo-franka/
$ ./servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe \
--learn-desired-features \
--intrinsic data-calib/franka_camera_visp.xml \
--camera-name Camera \
--tag-size 0.048
  • It will produce the following printings
    Parameters:
    Apriltag
    Size [m] : 0.048
    Z aligned : false
    Camera intrinsics
    Factory parameters : no
    Param file name [.xml]: data-calib/franka_camera.xml
    Camera name : Camera
    Found camera with name: "Camera"
    Camera parameters used to compute the pose:
    Camera parameters for perspective projection with distortion:
    px = 601.0670294 py = 602.4186662
    u0 = 321.3921431 v0 = 236.8025954
    kud = 0.05663697965
    kdu = -0.05584121348
  • Move the robot arm to a desired position in which the Apriltag is visible like the following
  • Save the corresponding visual features with a left-click in the image, and then use a right-click to quit the app.
    Desired visual features saved in: learned_desired_features.txt
  • The content of the "learned_desired_features.txt" file looks like the following in which we have the $(x, y, Z)$ values for each tag corner.
    $ cat learned_desired_features.txt
    0.243153 -0.152012 0.155126
    -0.0636572 -0.146852 0.158983
    -0.0669343 0.158418 0.153581
    0.250862 0.161218 0.149723

2.5. Summary of prerequisite data

At this point in "${VISP_WS}/visp-build/example/servo-franka/" folder you should have the following data files

  • The learned desired position of the tag
    $ cd ${VISP_WS}/visp-build/example/servo-franka
    $ ls
    ...
    learned_desired_features.txt
  • The intrinsic and extrinsic parameters:
    $ ls data-calib
    ...
    franka_camera_visp.xml franka_ePo.yaml franka_rPc.yaml

3. Eye-to-hand IBVS

3.1 Using Lcur_cVe_eJe controller

  • You are now ready to start the eye-in-hand image-based visual servoing using "L_cVe_eJe" controller implemented in servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe.cpp
    $ cd ${VISP_WS}/visp-build/example/servo-franka/
    $ ./servoFrankaIBVS-EyeToHand-Lcur_cVe_eJe \
    --ip 192.168.30.10 \
    --intrinsic data-calib/franka_camera_visp.xml \
    --eMo data-calib/franka_ePo.yaml \
    --camera-name Camera \
    --tag-size 0.048 \
    --no-convergence-threshold
  • Visual servoing will start and converge to the desired features like in the following video:

  • Here you can notice the straight line trajectories of the image points.
  • The following video shows the corresponding trajectory of the robot:

Code explanation

  • If you take a look to the source code, you will see that we create a desired feature point vector p_d updated by reading the values $ (x^*, y^*, Z^*) $ of the four points from "learned_desired_features.txt" file
    std::vector<vpFeaturePoint> p_d; // Desired visual features
    // Sanity options check
    if (desired_features_filename.empty() || (!vpIoTools::checkFilename(desired_features_filename))) {
    std::cout << "Cannot start eye-to-hand visual-servoing. Desired features are not available." << std::endl;
    std::cout << "use --learn-desired-features flag to learn the desired features." << std::endl;
    return EXIT_FAILURE;
    }
    else {
    if (!read_desired_features(desired_features_filename, p_d)) {
    std::cout << "Error: Unable to read desired features from: " << desired_features_filename << std::endl;
    return EXIT_FAILURE;
    }
    }
  • Then we create the servo task by adding the four current and desired visual feature points
    // Create current visual features
    std::vector<vpFeaturePoint> p(4); // We use 4 points
    // Add the 4 visual feature points
    for (size_t i = 0; i < p.size(); ++i) {
    task.addFeature(p[i], p_d[i]);
    }
  • Next we set the servo task type
  • Indicating that the current inteaction matrix will be used
    task.setInteractionMatrixType(vpServo::CURRENT);
  • Thus in the servo while loop the interaction matrix will be computed at each iteration of the servo loop. To this end we need to update $ (x, y, Z) $ for each of the 4 points. For updating $ Z $ coordinate of each point there is the need to compute the pose $ {^c}{\bf M}_o $ of the object.
    bool ret = detector.detect(I, opt_tag_size, cam, c_M_o_vec);
    Here the tag pose is available in c_M_o_vec[0] as an homogeneous matrix
    vpHomogeneousMatrix c_M_o = c_M_o_vec[0];
  • Updating current visual features p[i] is done with
    // Get tag corners
    std::vector<vpImagePoint> corners = detector.getPolygon(0);
    // Update visual features
    for (size_t i = 0; i < corners.size(); ++i) {
    // Update the point feature from the tag corners location
    vpFeatureBuilder::create(p[i], cam, corners[i]);
    // Set the feature Z coordinate from the pose
    point[i].changeFrame(c_M_o, c_P);
    p[i].set_Z(c_P[2]); // Update Z value of each visual feature thanks to the tag pose
    }
  • This pose is here also used to compute $ {^c}{\bf M}_e = {^c}{\bf M}_o \; {^e}{\bf M}{_o}{^{-1}} $ where $ {^e}{\bf M}_o $ constant homogeneous matrix is given by extrinsic calibration and read thanks to "--eMo" command line option.
    c_M_e = c_M_o * e_M_o.inverse();
  • The Jacobian $ {^e}{\bf J}_e $ is updated with
    vpMatrix e_J_e;
    robot.get_eJe(e_J_e);
    task.set_eJe(e_J_e);
  • We can now compute the control law corresponding to the joint velocities q_dot that we have to apply to the robot
    qdot = task.computeControlLaw();

3.2 Using Lcur_cVf_fVe_eJe controller

  • To use rather "Lcur_cVf_fVe_eJe" controller implemented in servoFrankaIBVS-EyeToHand-Lcur_cVf_fVe_eJe, you may run:
    $ cd ${VISP_WS}/visp-build/example/servo-franka/
    $ ./servoFrankaIBVS-EyeToHand-Lcur_cVf_fVe_eJe \
    --ip 192.168.30.10 \
    --intrinsic data-calib/franka_camera_visp.xml \
    --rMc data-calib/franka_rPc.yaml \
    --camera-name Camera \
    --tag-size 0.048 \
    --no-convergence-threshold
  • As in the previous section, we use the current interaction matrix computed at each iteration of the servo loop.
    task.setInteractionMatrixType(vpServo::CURRENT);
    @ CURRENT
    Definition vpServo.h:217
  • That's why we need to update $ (x, y, Z) $ for each of the 4 feature points. For updating $ Z $ coordinate there is the need to compute the pose $ {^c}{\bf M}_o $ of the object.
  • Visual servoing will start and converge to the desired features like in the following video:

  • Here you can also notice the straight line trajectories of the image points.

3.3 Using Ldes_cVf_fVe_eJe controller

  • To use rather "Ldes_cVf_fVe_eJe" controller implemented in servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe, you may run:
    $ cd ${VISP_WS}/visp-build/example/servo-franka/
    $ ./servoFrankaIBVS-EyeToHand-Ldes_cVf_fVe_eJe \
    --ip 192.168.30.10 \
    --intrinsic data-calib/franka_camera_visp.xml \
    --rMc data-calib/franka_rPc.yaml \
    --camera-name Camera \
    --tag-size 0.048 \
    --no-convergence-threshold
  • As explained in 1. Introduction, with this controller you don't need to estimate the pose $ {^c}{\bf M}_o $ of the object. That's why in the source code we will use the interaction matrix at the desired position by calling:
    task.setInteractionMatrixType(vpServo::DESIRED);
    @ DESIRED
    Definition vpServo.h:223
  • Visual servoing will start and converge to the desired features like in the following video. As the interaction matrix is computed with the desired visual features, you may notice that the trajectories of the points in the image are no longer straight lines.

  • Note that with this controller, there's no guarantee that the 4 points used as visual features will remain visible in the image.