Tutorial: How to get skeleton from kinect + OpenNI through ROS ?

This tutorial goes very quickly through the concepts and steps required to be able to acquire through ROS framework, a skeleton detected by the Microsoft Kinect device and the OpenNI driver and middleware.

Motivations

Getting a working OpenNI + nite installation sounds like a nightmare to more than a few people who have tried to do it on their own. Getting a working API binding it in your favorite language might also be a difficult quest.

For these reasons a lot of people use bundled versions of OpenNI in other frameworks, which generally means that other people have taken care of fixing the OpenNI + Nite installation process and maintained a working API.

While this might be seen as adding another (useless ?) layer on top of so many abstraction layers, it is on the other hand often saving you a lot of boring work.

Finally, and this is more for roboticists, using ROS as such a framework has many advantages amongst which include:

  • access to the device in any language which has ROS bindings,
  • immediate and effortless access to the device over network,
  • interoperability with other ROS applications, and benefits of the ROS visualization tools,
  • etc.

Getting everything installed

This tutorial assumes you have a Unix-like system working with ROS installed along with its openni_kinect package (we use the ROS bundled openni version).

  • General instructions on how to install ROS can be found on this dedicated web page.
  • Specific instruction for the openni_package can be found on http://www.ros.org/wiki/openni_kinect.
  • Finally for the example at the end of the tutorial we use a python2 installation and the rospy package (which is part of the ros_comm stack).

Packages or stacks can be either installed through distribution specific binaries or using rosdep (you will first need to setup your ROS environment, as detailed further).

Basic concept and general architecture

ROS code is organised into stacks and packages (a stack contains many packages).

In this tutorial we will mainly need the following concepts of ros, described in ROS documentation as:

  • Nodes: Nodes are processes that perform computation. ROS is designed to be modular at a fine-grained scale; a robot control system will usually comprise many nodes. For example, one node controls a laser range-finder, one node controls the wheel motors, one node performs localization, one node performs path planning, one Node provides a graphical view of the system, and so on. A ROS node is written with the use of a ROS client library, such as roscpp or rospy.
  • Messages: Nodes communicate with each other by passing messages. A message is simply a data structure, comprising typed fields. Standard primitive types (integer, floating point, boolean, etc.) are supported, as are arrays of primitive types. Messages can include arbitrarily nested structures and arrays (much like C structs).
  • Master: The ROS Master provides name registration and lookup to the rest of the Computation Graph. Without the Master, nodes would not be able to find each other, exchange messages, or invoke services.
  • Topic: Messages are routed via a transport system with publish / subscribe semantics. A node sends out a message by publishing it to a given topic. The topic is a name that is used to identify the content of the message. A node that is interested in a certain kind of data will subscribe to the appropriate topic. There may be multiple concurrent publishers and subscribers for a single topic, and a single node may publish and/or subscribe to multiple topics. In general, publishers and subscribers are not aware of each others’ existence. The idea is to decouple the production of information from its consumption. Logically, one can think of a topic as a strongly typed message bus. Each bus has a name, and anyone can connect to the bus to send or receive messages as long as they are the right type.

This means that we will need to have:

  • a ROS master running (roscore),
  • the openni_tracker node running, which will be publishing skeleton positions acquired by the kinect on a specific topic,
  • suscriber that will connect to the topic to get the skeleton positions and make them accessible to whatever we want to do with them.

Since publishers and suscribers need not be on the same computer, it is perfectly possible and transparent from that point to have the kinect plugged on one computer and access the skeleton positions from an other.

ROS comes with a set of command line tools; a useful resource to remember the associated commands is the ROS cheat sheet.

Getting things started

You will first need to configure ROS. This means setting up the environment variables so that the:

  • ROS executables are in your path,
  • ROS parts know which master they have to connect to.

This process is described in details in a dedicated ROS tutorial and summarized below.

Setting up a ROS environment requires:

  • a ROS workspace (e.g. ~/ros_workspace) which is a directory in which we are going to create our own simple ROS package,
  • loading a set of environment variables. You can either
    • have a dedicated file containing the following lines for that and run, or source it manually,
    • or directly put the lines in your ~/.bashrc (or your .profile or shell specific configuration file):
    source /opt/ros/electric/setup.bash
    export ROS_ROOT=/opt/ros/electric/ros
    export PATH=$ROS_ROOT/bin:$PATH
    export PYTHONPATH=$ROS_ROOT/core/roslib/src:$PYTHONPATH
    export ROS_PACKAGE_PATH=~/ros_workspace:/opt/ros/electric/stacks:$ROS_PACKAGE_PATH
    export ROS_MASTER_URI=http://localhost:11311

    (Of course replace ~/ros_workspace by your actual ROS workspace.)

All the following commands need to be run in a terminal where the ROS environment variables are set, so if you chose not to add the previous commands to your .bashrc or equivalent please take care of loading your ROS environment manually in each terminal you open.
Once your environment is set up correctly, you should be able to run the ROS master by typing:

roscore

This displays some information and does not release the terminal so you have to open a new terminal for the next command.

Then you might launch the openni_tracker node through the command:

rosrun openni_tracker openni_tracker

and if everything is OK, nothing should be output until someone is detected in front of the kinect and it outputs something similar to:

[ INFO] [1334336554.595585110]: New User 1

If the user tries to get calibrated by adopting the Psi pose. Useful output should inform you about the process.

Note that you will need to calibrate a user first in order to actually get some skeleton data from the kinect.

You may seek control by typing (again in a new terminal):

rostopic list

and get

/rosout
/rosout_agg
/tf

The /tf topic should only be there if a user is calibrated and will contain the skeleton data.

Actually the /tf comes from the time frame stack that provides the data type in which the skeleton poses are given.

At this point it is possible to visualize the data published on the /tf topic by using for example the rviz tool, launched by:

rosrun rviz rviz

Then select what is in the /t topic for display.

Writing a python suscriber

The last step is to write our own suscriber to actually access the skeleton data.

The example we give in this tutorial is written in python.

We first need to create a ROS package that will contain our code. For ROS to be able to later locate your code you will have to create this package in the ROS workspace we created earlier. This can be done through the roscreate-pkg command:

roscreate-pkg NAME rospy tf

where NAME is the name you want to give to the package, rospy and tf specify that our package depends on both these libraries.

This will generate a directory named after NAME.

Now accessing to the skeleton values from python requires the following steps:

  • import roslib and load the manifest.xml file generated by roscreate and containing information about our package and its dependencies:
    import roslib
    roslib.load_manifest('NAME')
  • import our dependencies (needs to be done after the manifest is loaded):
    import rospy
    import tf
  • init a node named ‘kinect_listener’ (you could chose any other name):
    rospy.init_node('kinect_listener', anonymous=True)
  • create a TransformListener node:
    listener = tf.TransformListener()
  • request the translation and a rotation describing any frame: frames are named as ‘/segment_userid’ where segment is the name of the segment and userid the id of the user. For example to get the frame (as a couple of a translation and a rotation) corresponding to the left knee of the first user:
    trans, rot = listener.lookupTransform('/openni_depth_frame', '/left_knee_1',
        rospy.Duration())

    Note that we have to provide the TransformListener with a time, to get the frame at this particular time. Providing current ROS time returns the last translation and rotation values for the frame.

This mechanism can be integrated in a simple Kinect class (that can be put in a python file in the package src directory), which provides easy access to all the frames of a given user through:

# Import the kinect class from your file
from yourfile import Kinect

# Init the Kinect object
kin = Kinect()

# Get values
for i in xrange(10):
    print i, kin.get_posture()

The code of the corresponding class can be found at https://gist.github.com/2414166.

Additional information

In this tutorial we only covered the steps required to get a working access to skeleton information. More in depth presentations of the ROS system can be found in the ROS wiki tutorials.

Share Button