How to manage a skeleton object¶
This tutorial will explain you the main functionalities to manage a skeleton object for:
- defining a skeleton;
- retrieving a skeleton;
- accessing single keypoints of a skeleton;
- dealing with pixels;
- additional functionalities.
Defining a skeleton¶
A skeleton can be defined as:
- unordered list of keypoints: the list defines the keypoints as pairs of points associated to the keypoint tag;
-
ordered list of keypoints: the list defines the keypoints according to the following order:
- 0:
shoulder_center
- 1:
head
- 2:
shoulder_left
- 3:
elbow_left
- 4:
hand_left
- 5:
shoulder_right
- 6:
elbow_right
- 7:
hand_right
- 8:
hip_center
- 9:
hip_left
- 10:
knee_left
- 11:
ankle_left
- 12:
foot_left
- 13:
hip_right
- 14:
knee_right
- 15:
ankle_right
- 16:
foot_right
- 0:
-
property-like structure: the available properties are the following:
- type: string containing skeleton's type ("assistive_rehab::SkeletonStd").
- tag: string containing skeleton's tag.
- transformation: 4 x 4 skeleton's roto-translation matrix.
- coronal: vector containing skeleton's coronal plane.
- sagittal: vector containing skeleton's sagittal plane.
- transverse: vector containing skeleton's transverse plane.
- skeleton: list containing keypoints with the following subproperties:
- tag: string containing keypoint's tag.
- status: string containing keypoint's status (updated or stale).
- position: vector containing keypoint's camera coordinates x,y,z.
- pixel: vector containing keypoint's image coordinates u,v.
- child: list containing keypoint's child, specified as position, status, tag.
An example is the following:
(coronal (-0.0171187190241832 0.168776145353523 -0.984581522687332)) (sagittal (0.999852451902468 0.0040979367701588 -0.0166817666585492)) (skeleton (((child (((pixel (143.0 18.0)) (position (-0.0958624515848492 -0.655793450456223 2.01140785217285)) (status updated) (tag head)) ((child (((child (((pixel (182.0 106.0)) (position (0.132475296984726 -0.0970469850595488 2.24838447570801)) (status updated) (tag handLeft)))) (pixel (179.0 77.0)) (position (0.126047049721491 -0.301864959631551 2.20449256896973)) (status updated) (tag elbowLeft)))) (pixel (173.0 40.0)) (position (0.0829495294433831 -0.549497736338085 2.17046546936035)) (status updated) (tag shoulderLeft)) ((child (((child (((pixel (117.0 102.0)) (position (-0.318757589652556 -0.129758383247854 2.30834770202637)) (status updated) (tag handRight)))) (pixel (124.0 75.0)) (position (-0.265819393982889 -0.319631634579399 2.22591972351074)) (status updated) (tag elbowRight)))) (pixel (132.0 42.0)) (position (-0.194277581569445 -0.550633963158095 2.17509078979492)) (status updated) (tag shoulderRight)) ((child (((child (((child (((child (((pixel (153.0 200.0)) (position (-0.0305329581785242 0.615750619982796 2.40194511413574)) (status updated) (tag footLeft)))) (pixel (156.0 187.0)) (position (-0.0161527965668162 0.538963623868779 2.52594947814941)) (status updated) (tag ankleLeft)))) (pixel (158.0 148.0)) (position (-0.00361483866030924 0.218990793550177 2.42797660827637)) (status updated) (tag kneeLeft)))) (pixel (164.0 105.0)) (position (0.0249104292284522 -0.102991968370345 2.2304573059082)) (status updated) (tag hipLeft)) ((child (((child (((child (((pixel (138.0 194.0)) (position (-0.170998545149134 0.592533010093213 2.49751472473145)) (status updated) (tag footRight)))) (pixel (144.0 182.0)) (position (-0.130142124436922 0.509498389750101 2.55434036254883)) (status updated) (tag ankleRight)))) (pixel (142.0 147.0)) (position (-0.133305882322194 0.209480672220591 2.39202308654785)) (status updated) (tag kneeRight)))) (pixel (138.0 103.0)) (position (-0.158599348153779 -0.109193487775799 2.21346664428711)) (status updated) (tag hipRight)))) (pixel (151.0 103.0)) (position (-0.0738603465810386 -0.102184160595443 2.20890045166016)) (status updated) (tag hipCenter)))) (pixel (153.0 41.0)) (position (-0.054339762871519 -0.540136057902244 2.13348770141602)) (status updated) (tag shoulderCenter)))) (tag "#7") (transformation (4 4 (1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0))) (transverse (0.0438836580749896 -0.984546958261919 -0.169533216601232)) (type "assistive_rehab::SkeletonStd")
From an unordered list of keypoints¶
The following code snippet creates a SkeletonStd
object from an unordered list of keypoints:
#include <yarp/sig/Vector.h>
#include "AssistiveRehab/skeleton.h"
int main()
{
assistive_rehab::SkeletonStd skeleton;
std::vector<std::pair<std::string,yarp::sig::Vector>> unordered;
{
yarp::sig::Vector p(3); p[0]=0.0; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::shoulder_center,p));
}
{
yarp::sig::Vector p(3); p[0]=0.0; p[1]=-0.1; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::head,p));
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::shoulder_left,p));
}
{
yarp::sig::Vector p(3); p[0]=0.2; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::elbow_left,p));
}
{
yarp::sig::Vector p(3); p[0]=0.3; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::hand_left,p));
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::shoulder_right,p));
}
{
yarp::sig::Vector p(3); p[0]=-0.2; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::elbow_right,p));
}
{
yarp::sig::Vector p(3); p[0]=-0.3; p[1]=0.0; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::hand_right,p));
}
{
yarp::sig::Vector p(3); p[0]=0.0; p[1]=0.1; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::hip_center,p));
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.1; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::hip_left,p));
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.2; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::knee_left,p));
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.1; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::hip_right,p));
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.2; p[2]=0.0;
unordered.push_back(std::make_pair(KeyPointTag::knee_right,p));
}
skeleton.setTag("unordered");
skeleton.update(unordered);
skeleton.print();
return EXIT_SUCCESS;
}
The result is:
tag = "unordered"
transformation =
-1.000 0.000 0.000 0.000
0.000 1.000 0.000 0.000
-0.000 0.000 -1.000 0.000
0.000 0.000 0.000 1.000
coronal = (-0.000 0.000 1.000)
sagittal = (-1.000 0.000 -0.000)
transverse = ( 0.000 -1.000 0.000)
keypoint["shoulderCenter"] = ( 0.000 0.000 0.000); pixel=( nan nan); status=updated; parent={}; child={"head" "shoulderLeft" "shoulderRight" "hipCenter" }
keypoint["head"] = ( 0.000 -0.100 0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={}
keypoint["shoulderLeft"] = (-0.100 0.000 -0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"elbowLeft" }
keypoint["elbowLeft"] = (-0.200 0.000 -0.000); pixel=( nan nan); status=updated; parent={"shoulderLeft" }; child={"handLeft" }
keypoint["handLeft"] = (-0.300 0.000 -0.000); pixel=( nan nan); status=updated; parent={"elbowLeft" }; child={}
keypoint["shoulderRight"] = ( 0.100 0.000 0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"elbowRight" }
keypoint["elbowRight"] = ( 0.200 0.000 0.000); pixel=( nan nan); status=updated; parent={"shoulderRight" }; child={"handRight" }
keypoint["handRight"] = ( 0.300 0.000 0.000); pixel=( nan nan); status=updated; parent={"elbowRight" }; child={}
keypoint["hipCenter"] = ( 0.000 0.100 0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"hipLeft" "hipRight" }
keypoint["hipLeft"] = (-0.100 0.100 -0.000); pixel=( nan nan); status=updated; parent={"hipCenter" }; child={"kneeLeft" }
keypoint["kneeLeft"] = (-0.100 0.200 -0.000); pixel=( nan nan); status=updated; parent={"hipLeft" }; child={"ankleLeft" }
keypoint["ankleLeft"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"kneeLeft" }; child={"footLeft" }
keypoint["footLeft"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"ankleLeft" }; child={}
keypoint["hipRight"] = ( 0.100 0.100 0.000); pixel=( nan nan); status=updated; parent={"hipCenter" }; child={"kneeRight" }
keypoint["kneeRight"] = ( 0.100 0.200 0.000); pixel=( nan nan); status=updated; parent={"hipRight" }; child={"ankleRight" }
keypoint["ankleRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"kneeRight" }; child={"footRight" }
keypoint["footRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"ankleRight" }; child={}
Note
Don't get worried about nan
in the pixel
fields: it's correct since we didn't set them up yet. Uninitialized points and/or pixels take nan
values.
From an ordered list of keypoints¶
The following code snippet creates a SkeletonStd
object from an ordered list of keypoints:
#include <yarp/sig/Vector.h>
#include "AssistiveRehab/skeleton.h"
int main()
{
assistive_rehab::SkeletonStd skeleton;
std::vector<yarp::sig::Vector> ordered;
{
yarp::sig::Vector p(3); p[0]=0.0; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.0; p[1]=-0.1; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.2; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.3; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=-0.2; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=-0.3; p[1]=0.0; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.1; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=0.1; p[1]=0.2; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.1; p[2]=0.0;
ordered.push_back(p);
}
{
yarp::sig::Vector p(3); p[0]=-0.1; p[1]=0.2; p[2]=0.0;
ordered.push_back(p);
}
skeleton.setTag("ordered");
skeleton.update(ordered);
skeleton.print();
return EXIT_SUCCESS;
}
The result is:
tag = "ordered"
transformation =
-1.000 0.000 0.000 0.000
0.000 1.000 0.000 0.000
-0.000 0.000 -1.000 0.000
0.000 0.000 0.000 1.000
coronal = (-0.000 0.000 0.707)
sagittal = (-1.000 0.000 -0.000)
transverse = ( 0.707 -0.707 0.000)
keypoint["shoulderCenter"] = ( 0.000 0.000 0.000); pixel=( nan nan); status=updated; parent={}; child={"head" "shoulderLeft" "shoulderRight" "hipCenter" }
keypoint["head"] = ( 0.000 -0.100 0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={}
keypoint["shoulderLeft"] = (-0.100 0.000 -0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"elbowLeft" }
keypoint["elbowLeft"] = (-0.200 0.000 -0.000); pixel=( nan nan); status=updated; parent={"shoulderLeft" }; child={"handLeft" }
keypoint["handLeft"] = (-0.300 0.000 -0.000); pixel=( nan nan); status=updated; parent={"elbowLeft" }; child={}
keypoint["shoulderRight"] = ( 0.100 0.000 0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"elbowRight" }
keypoint["elbowRight"] = ( 0.200 0.000 0.000); pixel=( nan nan); status=updated; parent={"shoulderRight" }; child={"handRight" }
keypoint["handRight"] = ( 0.300 0.000 0.000); pixel=( nan nan); status=updated; parent={"elbowRight" }; child={}
keypoint["hipCenter"] = (-0.100 0.100 -0.000); pixel=( nan nan); status=updated; parent={"shoulderCenter" }; child={"hipLeft" "hipRight" }
keypoint["hipLeft"] = (-0.100 0.200 -0.000); pixel=( nan nan); status=updated; parent={"hipCenter" }; child={"kneeLeft" }
keypoint["kneeLeft"] = ( 0.100 0.100 0.000); pixel=( nan nan); status=updated; parent={"hipLeft" }; child={"ankleLeft" }
keypoint["ankleLeft"] = ( 0.100 0.200 0.000); pixel=( nan nan); status=updated; parent={"kneeLeft" }; child={"footLeft" }
keypoint["footLeft"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"ankleLeft" }; child={}
keypoint["hipRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"hipCenter" }; child={"kneeRight" }
keypoint["kneeRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"hipRight" }; child={"ankleRight" }
keypoint["ankleRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"kneeRight" }; child={"footRight" }
keypoint["footRight"] = ( nan nan nan); pixel=( nan nan); status=stale; parent={"ankleRight" }; child={}
From a property-like structure¶
Assuming that the object skeleton1
has been previously defined using one of the instructions above, a new Skeleton
object can be defined as following:
yarp::os::Property prop=skeleton1.toProperty();
std::unique_ptr<assistive_rehab::Skeleton> skeleton2(assistive_rehab::skeleton_factory(prop));
skeleton2->setTag("properties");
skeleton2->print();
Retrieving a skeleton¶
A skeleton can be retrieved as a property-like structure from a yarp port:
assistive_rehab::SkeletonStd skeleton;
if(yarp::os::Bottle* b=opcPort.read(false))
{
yarp::os::Property prop;
prop.fromString(b->get(0).asList()->toString());
skeleton.update(assistive_rehab::skeleton_factory(prop)->toProperty());
}
or from the OPC as following:
assistive_rehab::SkeletonStd skeleton;
yarp::os::Bottle cmd,reply;
cmd.addVocab(yarp::os::Vocab::encode("ask"));
yarp::os::Bottle &content=cmd.addList().addList();
content.addString("skeleton");
opcPort.write(cmd,reply);
if(reply.size()>1)
{
if(reply.get(0).asVocab()==Vocab::encode("ack"))
{
if(yarp::os::Bottle *idField=reply.get(1).asList())
{
if(yarp::os::Bottle *idValues=idField->get(1).asList())
{
int id=idValues->get(0).asInt();
cmd.clear();
cmd.addVocab(Vocab::encode("get"));
yarp::os::Bottle &content=cmd.addList().addList();
yarp::os::Bottle replyProp;
content.addString("id");
content.addInt(id);
opcPort.write(cmd,replyProp);
if(replyProp.get(0).asVocab() == yarp::os::Vocab::encode("ack"))
{
if(yarp::os::Bottle *propField=replyProp.get(1).asList())
{
yarp::os::Property prop(propField->toString().c_str());
skeleton.update(assistive_rehab::skeleton_factory(prop)->toProperty());
}
}
}
}
}
}
Accessing single keypoints of a skeleton¶
If you want to access to a keypoint of the skeleton, you can use the operator[]
of the class Skeleton
, by passing as parameter the keypoint's tag.
For example, the following snippet allows you to get the shoulder_center
3D coordinates:
yarp::sig::Vector sc=skeleton[assistive_rehab::KeyPointTag::shoulder_center]->getPoint();
Dealing with pixels¶
Occasionally, it might be worth storing also the pixels alongside the points, which are used by the algorithm to reconstruct the 3D skeleton. This is particularly useful when the skeleton is employed to enable gaze tracking, for example. In this context, the 3D information of the keypoints needs normally to be transformed from the camera frame to the root frame of the robot. Instead, having the pixels would ease this process.
The updating methods described above do have their pixel-wise counterparts:
std::vector<std::pair<yarp::sig::Vector,yarp::sig::Vector>> ordered_withpixels;
yarp::sig::Vector p(3,0.1); yarp::sig::Vector px(2,10.0);
ordered_withpixels.push_back(std::make_pair(p,px));
skeleton.update_withpixels(ordered_withpixels);
std::vector<std::pair<std::string<std::pair<yarp::sig::Vector,yarp::sig::Vector>>> unordered_withpixels;
yarp::sig::Vector p(3,0.1); yarp::sig::Vector px(2,10.0);
unordered_withpixels.push_back(std::make_pair(assistive_rehab::KeyPointTag::shoulder_center,std::make_pair(p,px)));
skeleton.update_withpixels(unordered_withpixels);
yarp::sig::Vector pixel=skeleton[assistive_rehab::KeyPointTag::shoulder_center].getPixel();
Additional functionalities¶
Normalization¶
Some applications might require a normalized skeleton, to avoid having different results for different human physiques. The normalization provided in the library makes the length of the observed human segments always equal to 1 and can be applied as following:
skeleton.normalize();
Keypoints' reference system¶
Keypoints in the skeleton are defined with respect to the camera. To change the reference system, given a transformation matrix T, you can use the method setTransformation
, as following:
skeleton.setTransformation(T);
Tip
If you want to use the skeleton as reference system, you can create the transformation matrix T from the skeleton's planes:
yarp::sig::Vector coronal=skeleton.getCoronal();
yarp::sig::Vector sagittal=skeleton.getSagittal();
yarp::sig::Vector transverse=skeleton.getTransverse();
yarp::sig::Vector p=skeleton[assistive_rehab::KeyPointTag::shoulder_center]->getPoint();
yarp::sig::Matrix T1(4,4);
T1.setSubcol(coronal,0,0);
T1.setSubcol(sagittal,0,1);
T1.setSubcol(transverse,0,2);
T1.setSubcol(p,0,3);
T1(3,3)=1.0;
T=yarp::math::SE3inv(T1);
skeleton.setTransformation(T);