Sunday, 31 May 2015

Editing BVH files

Motion capture is becoming quite accessible to the indie game developer, thanks to the Microsoft Kinect device. Although it didn't really take off as a dedicated game controller, in the same way that the Nintendo Wii of a few years back, it has proved very popular as a PC connected USB-device for all kinds of 3D applications.


There are two flavours of Xbox Kinect - the v1 (top) is the original device, originally released for the Xbox 360. The improved v2 version (bottom) was released for both the Xbox One and for PC as a development kit from Microsoft. Over time, Microsoft stopped producing the PC version and instead created an adapter to make the Xbox version compatible with any PC supporting a USB3 port.

Using either of these sensors, it's pretty easy to do motion capture with a half-decent PC and any one of a number of mo-cap applications. You can even do mo-cap directly in Unity with something like Cinema Mo Cap, which supports both types of Kinect device.


Although motion capture with a Kinect is (relatively) cheap - about £200 for the hardware and software, compared with £15,000 for a profession set-up - some of the captured motions leave a little bit to be desired, and almost always require some degree of "cleaning up" to remove rogue movements and the odd limb-twitch.

And this is where the cheap vs expensive approach really shows. Raw motion capture data is great for capturing key poses, but the problem with a lot of cheap mo-cap solutions is that they don't provide any editing tools - they simply expect you to use the motion capture data as provided.

This has two main drawbacks:

The first is that every now and again the mo-cap software mis-interprets the position of a limb or extremity (usually a foot or a finger) which creates strange glitches in the animation when played back.

The second is that a massive amount of extra data is captured that isn't really necessary. Most 3D packages provide "tweening" for animating a 3d character between two poses. BVH mo-cap files include all the inbetween movements as the subject moves from one pose to another.

If we had a way of capturing just the keyframes with the "important" poses in them (just before/after a major movement) we'd be able to eliminate both of these problems in one - we'd only need to keep the actual poses that add to our character movement, and in doing so, we could easily exclude any poses that contained glitches.

The problem with this approach is cost.
Mo-cap editing software is expensive.
And we're pretty cheap.
So we set about creating a simple mo-cap editor.

Now recreating a 3d animation from mo-cap data would be cool, but there's loads of this software around that does a far better job than we could do in a weekend. So we're not going to do that. We're going to use the fact the the BVH (mocap) data file format is pretty basic, to allow us to parse an animation file and extract only the data we're interested in.

For example, a BVH file consists of  a simple skeleton description, followed by a number of rotations for each "bone" or joint in the skeleton.

As we're looking at the Brekel motion capture software, we had a look at the sample data they provide from their Microsoft Kinect capture solution. The header of their BVH file looks something like this:

HIERARCHY
ROOT Hips
{
   OFFSET 0.000 77.138 0.000
   CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
   JOINT LeftHip
   {
      OFFSET 8.415 0.567 3.660
      CHANNELS 3 Zrotation Xrotation Yrotation
      JOINT LeftKnee
      {
         OFFSET 0.000 -39.733 0.000
         CHANNELS 3 Zrotation Xrotation Yrotation
         JOINT LeftAnkle
         {
            OFFSET 0.000 -37.972 0.000
            CHANNELS 3 Zrotation Xrotation Yrotation
            End Site
            {
               OFFSET 0.000 0.000 11.130
            }
         }
      }
   }
   JOINT RightHip
   {
      OFFSET -8.415 0.567 3.660
      CHANNELS 3 Zrotation Xrotation Yrotation
      JOINT RightKnee
      {
         OFFSET 0.000 -39.733 0.000
         CHANNELS 3 Zrotation Xrotation Yrotation
         JOINT RightAnkle
         {
            OFFSET 0.000 -37.972 0.000
            CHANNELS 3 Zrotation Xrotation Yrotation
            End Site
            {
               OFFSET 0.000 0.000 11.130
            }
         }
      }
   }
   JOINT Chest
   {
      OFFSET 0.000 32.175 0.000
      CHANNELS 3 Zrotation Xrotation Yrotation
      JOINT Chest2
      {
         OFFSET 0.000 23.529 0.000
         CHANNELS 3 Zrotation Xrotation Yrotation
         JOINT LeftCollar
         {
            OFFSET 1.862 -0.567 0.239
            CHANNELS 3 Zrotation Xrotation Yrotation
            JOINT LeftShoulder
            {
               OFFSET 17.792 0.000 0.000
               CHANNELS 3 Zrotation Xrotation Yrotation
               JOINT LeftElbow
               {
                  OFFSET 26.436 0.000 0.000
                  CHANNELS 3 Zrotation Xrotation Yrotation
                  JOINT LeftWrist
                  {
                     OFFSET 24.686 0.000 0.000
                     CHANNELS 3 Zrotation Xrotation Yrotation
                     JOINT LeftFinger0
                     {
                        OFFSET 4.877 -1.044 3.608
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT LeftFinger01
                        {
                           OFFSET 2.665 -0.000 -0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT LeftFinger02
                           {
                              OFFSET 2.543 -0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET 2.667 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT LeftFinger1
                     {
                        OFFSET 9.232 -0.301 2.144
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT LeftFinger11
                        {
                           OFFSET 4.225 0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT LeftFinger12
                           {
                              OFFSET 2.654 -0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET 1.958 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT LeftFinger2
                     {
                        OFFSET 8.920 0.000 -0.000
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT LeftFinger21
                        {
                           OFFSET 4.863 0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT LeftFinger22
                           {
                              OFFSET 2.765 0.000 -0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET 2.006 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT LeftFinger3
                     {
                        OFFSET 8.689 -0.126 -2.087
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT LeftFinger31
                        {
                           OFFSET 4.538 0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT LeftFinger32
                           {
                              OFFSET 2.305 0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET 1.923 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT LeftFinger4
                     {
                        OFFSET 8.391 -0.817 -3.758
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT LeftFinger41
                        {
                           OFFSET 3.044 -0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT LeftFinger42
                           {
                              OFFSET 1.975 0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET 1.667 0.000 0.000
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
         JOINT RightCollar
         {
            OFFSET -1.862 -0.567 0.239
            CHANNELS 3 Zrotation Xrotation Yrotation
            JOINT RightShoulder
            {
               OFFSET -17.792 0.000 0.000
               CHANNELS 3 Zrotation Xrotation Yrotation
               JOINT RightElbow
               {
                  OFFSET -26.436 0.000 0.000
                  CHANNELS 3 Zrotation Xrotation Yrotation
                  JOINT RightWrist
                  {
                     OFFSET -24.686 0.000 0.000
                     CHANNELS 3 Zrotation Xrotation Yrotation
                     JOINT RightFinger0
                     {
                        OFFSET -4.877 -1.044 3.608
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT RightFinger01
                        {
                           OFFSET -2.665 0.000 -0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT RightFinger02
                           {
                              OFFSET -2.543 0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET -2.667 -0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT RightFinger1
                     {
                        OFFSET -9.232 -0.301 2.144
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT RightFinger11
                        {
                           OFFSET -4.225 -0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT RightFinger12
                           {
                              OFFSET -2.654 -0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET -1.958 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT RightFinger2
                     {
                        OFFSET -8.920 -0.000 0.000
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT RightFinger21
                        {
                           OFFSET -4.863 0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT RightFinger22
                           {
                              OFFSET -2.765 -0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET -2.006 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT RightFinger3
                     {
                        OFFSET -8.689 -0.126 -2.087
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT RightFinger31
                        {
                           OFFSET -4.538 -0.000 0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT RightFinger32
                           {
                              OFFSET -2.305 -0.000 -0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET -1.923 0.000 0.000
                              }
                           }
                        }
                     }
                     JOINT RightFinger4
                     {
                        OFFSET -8.391 -0.817 -3.758
                        CHANNELS 3 Zrotation Xrotation Yrotation
                        JOINT RightFinger41
                        {
                           OFFSET -3.044 0.000 -0.000
                           CHANNELS 3 Zrotation Xrotation Yrotation
                           JOINT RightFinger42
                           {
                              OFFSET -1.975 0.000 0.000
                              CHANNELS 3 Zrotation Xrotation Yrotation
                              End Site
                              {
                                 OFFSET -1.667 0.000 0.000
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
         JOINT Neck
         {
            OFFSET 0.000 0.767 0.000
            CHANNELS 3 Zrotation Xrotation Yrotation
            JOINT Head
            {
               OFFSET 0.000 6.907 0.000
               CHANNELS 3 Zrotation Xrotation Yrotation
               End Site
               {
                  OFFSET 0.000 14.799 0.000
               }
            }
         }
      }
   }
}

And after all this junk, we start to see the actual animation data:

MOTION
Frames: 1222
Frame Time: 0.033333

-19.09096 86.72628 -193.07651 -0.17976 -3.92397 0.68769 11.14140 7.20556 -0.92078 -2.29540 5.11692 1.91521 -4.62996 20.50473 12.81783 -1.86685 12.98398 -2.19955 4.44833 -6.98297 -8.14759 1.86476 28.76261 -3.89672 -0.15640 2.07988 0.12122 -0.19043 2.47325 0.22838 -13.72436 1.22983 -4.97758 -56.87571 -0.90905 2.53451 4.08357 4.61863 -27.81969 90.82480 13.02917 -11.51783 -0.00002 -0.00001 0.00000 0.00000 -0.00000 0.00000 0.00000 0.00000 0.00003 -0.00000 0.00000 -0.00000 -0.00000 0.00000 0.00000 -0.00000 0.00001 -0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 20.66945 1.98194 5.24341 45.82977 -3.28564 -12.81110 -4.51496 4.06287 32.23806 -67.48892 16.51301 3.78246 40.90540 11.75460 -3.91033 0.00000 0.00000 -5.00000 0.00000 -0.00000 -60.00000 -2.00000 0.00000 0.00000 -2.00000 -0.00000 -0.00000 -2.00000 -0.00000 -0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 0.00007 -0.00009 0.00017 0.36586 5.18664 -1.03638

-18.93125 86.57366 -193.57823 -0.16391 -3.94172 0.83452 10.91408 7.45215 -0.60187 -1.98701 4.62638 2.93570 -4.33215 20.72713 11.94433 -2.08694 12.07617 -2.03624 4.64667 -5.82190 -8.21822 2.01918 28.58089 -4.24085 -0.15541 2.07988 0.18790 -0.18295 2.47323 0.31173 -13.54430 1.22964 -5.07436 -56.47004 -0.41186 2.11939 4.26038 4.77343 -29.40027 91.83288 13.19723 -10.56911 -0.00002 -0.00001 0.00000 0.00000 -0.00000 0.00000 0.00000 0.00000 0.00003 -0.00000 0.00000 -0.00000 -0.00000 0.00000 0.00000 -0.00000 0.00001 -0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 20.79433 2.03776 5.34991 45.90844 -3.74690 -12.70695 -4.24716 3.86554 32.13912 -66.30006 16.40318 4.01541 40.90540 11.75460 -3.91033 0.00000 0.00000 -5.00000 0.00000 -0.00000 -60.00000 -2.00000 0.00000 0.00000 -2.00000 -0.00000 -0.00000 -2.00000 -0.00000 -0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 0.00007 -0.00009 0.00017 0.38941 5.29672 -1.33426

-18.69029 86.44820 -194.06238 -0.13211 -3.93218 0.98746 10.64748 7.47083 -0.35351 -1.75983 4.40165 3.84590 -4.08096 20.97137 11.17286 -2.40106 11.17657 -1.92644 4.91138 -4.70405 -8.28357 2.14832 28.34713 -4.54350 -0.15157 2.07987 0.23891 -0.17385 2.47322 0.37546 -13.39292 1.21721 -5.08208 -56.20360 0.13947 1.70207 4.39204 4.79714 -30.64237 91.87553 13.25845 -8.73148 -0.00002 -0.00001 0.00000 0.00000 -0.00000 0.00000 0.00000 0.00000 0.00003 -0.00000 0.00000 -0.00000 -0.00000 0.00000 0.00000 -0.00000 0.00001 -0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 -0.00004 0.00000 0.00000 -0.00005 -0.00000 -0.00000 -0.00003 -0.00001 0.00000 20.85100 2.04922 5.36268 46.08860 -4.05784 -12.48223 -3.93563 3.70835 31.93921 -65.79578 16.03147 4.05839 40.90540 11.75460 -3.91033 0.00000 0.00000 -5.00000 0.00000 -0.00000 -60.00000 -2.00000 0.00000 0.00000 -2.00000 -0.00000 -0.00000 -2.00000 -0.00000 -0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 80.00001 -0.00001 0.00001 90.00000 -0.00000 0.00001 70.00000 0.00000 0.00000 0.00007 -0.00009 0.00017 0.28722 5.38471 -1.59374

The motion data follows the structure of the defined skeleton exactly. So in our example, the first root of the tree is the hips bone/joint. This is defined as having 6 "channels"- the X,Y,Z positions of the hips in real space, and the Z,X,Y rotation applied to the hips. The position of every bone in the skeleton after this is in relation to the "root" bone.

This means that the first six values on each line of the motion data define the position and rotation of the hips joint. The next entry in the skeleton is the left-hip joint. This is defined as having three rotational values. So the next three values in each line of the motion data (the sixth, seventh and eighth values) define the rotation of the left-hip joint. The next joint in the skeleton is the left-knee, defined with three channels, so the ninth, tenth and eleventh values in the motion data are the Z,X,Y rotation values for the left-knee joint.

And so the motion data continues, each set of three values relating to the next bone in the skeleton. To confirm that this is true, we can take any line of motion data and apply it the skeleton description in the BVH header file. Here is the first line of motion data, applied to the skeleton:

MOTION
Frames: 1222
Frame Time: 0.033333

ROOT Hips -19.09096 86.72628 -193.07651 -0.17976 -3.92397 0.68769
JOINT LeftHip 11.14140 7.20556 -0.92078
JOINT LeftKnee -2.29540 5.11692 1.91521
JOINT LeftAnkle -4.62996 20.50473 12.81783
JOINT RightHip -1.86685 12.98398 -2.19955
JOINT RightKnee 4.44833 -6.98297 -8.14759
JOINT RightAnkle 1.86476 28.76261 -3.89672
JOINT Chest -0.15640 2.07988 0.12122
JOINT Chest2 -0.19043 2.47325 0.22838
JOINT LeftCollar -13.72436 1.22983 -4.97758
JOINT LeftShoulder -56.87571 -0.90905 2.53451
JOINT LeftElbow 4.08357 4.61863 -27.81969
JOINT LeftWrist 90.82480 13.02917 -11.51783
JOINT LeftFinger0 -0.00002 -0.00001 0.00000
JOINT LeftFinger01 0.00000 -0.00000 0.00000
JOINT LeftFinger02 0.00000 0.00000 0.00003
JOINT LeftFinger1 -0.00000 0.00000 -0.00000
JOINT LeftFinger11 -0.00000 0.00000 0.00000
JOINT LeftFinger12 -0.00000 0.00001 -0.00000
JOINT LeftFinger2 -0.00004 0.00000 0.00000
JOINT LeftFinger21 -0.00005 -0.00000 -0.00000
JOINT LeftFinger22 -0.00003 -0.00001 0.00000
JOINT LeftFinger3 -0.00004 0.00000 0.00000
JOINT LeftFinger31 -0.00005 -0.00000 -0.00000
JOINT LeftFinger32 -0.00003 -0.00001 0.00000
JOINT LeftFinger4 -0.00004 0.00000 0.00000
JOINT LeftFinger41 -0.00005 -0.00000 -0.00000
JOINT LeftFinger42 -0.00003 -0.00001 0.00000
JOINT RightCollar 20.66945 1.98194 5.24341
JOINT RightShoulder 45.82977 -3.28564 -12.81110
JOINT RightElbow -4.51496 4.06287 32.23806
JOINT RightWrist -67.48892 16.51301 3.78246
JOINT RightFinger0 40.90540 11.75460 -3.91033
JOINT RightFinger01 0.00000 0.00000 -5.00000
JOINT RightFinger02 0.00000 -0.00000 -60.00000
JOINT RightFinger1 -2.00000 0.00000 0.00000
JOINT RightFinger11 -2.00000 -0.00000 -0.00000
JOINT RightFinger12 -2.00000 -0.00000 -0.00000
JOINT RightFinger2 80.00001 -0.00001 0.00001
JOINT RightFinger21 90.00000 -0.00000 0.00001
JOINT RightFinger22 70.00000 0.00000 0.00000
JOINT RightFinger3 80.00001 -0.00001 0.00001
JOINT RightFinger31 90.00000 -0.00000 0.00001
JOINT RightFinger32 70.00000 0.00000 0.00000
JOINT RightFinger4 80.00001 -0.00001 0.00001
JOINT RightFinger41 90.00000 -0.00000 0.00001
JOINT RightFinger42 70.00000 0.00000 0.00000
JOINT Neck 0.00007 -0.00009 0.00017
JOINT Head 0.36586 5.18664 -1.03638

With this information, it should be pretty trivial to create an interface (in VB6 of course!) that can read and re-create the BVH skeleton tree visually:


We've added in some tick boxes in the tree structure, to allow us to select which bones/joints we want to include (or remove, if necessary). By walking through the tree structure, we're able to re-create the BVH structure, amended with bones removed if required.


By ticking and unticking boxes in our editor, we're able to remove entire limbs and tree branches from the skeleton. In this example, we've removed the left forearm, hand and fingers, as well as the right ankle and foot.


Of course removing a bone (or bones) from the skeleton means we also need to split each line of the motion data, and re-create it to apply only to the bones selected in the interface. This is also a relatively trival exercise, but one that needs careful attention, since removing the wrong set of three values can vastly effect the rest of the animation.

Lastly, we added in a list of "keyframes" we want to capture into our editor. So instead of recreating the entire bvh file as captured, we can take only the frames we're interested in from the BVH data. This is done by simply reading through the original BVH motion data and if the line number (plus the offset from where the motion data starts) matches any one of the keyframes required, the motion data is parsed and re-applied to our modified skeleton, before being written to a second BVH file.

By using this approach, we've been able to create motion capture files with only the bones/joints we're interested in (for example, removing the lower limbs from an animation from which only the upper torso is only required) as well as only the key poses from the animation we need to recreate the animation in our Unity game. This results in much smaller files, with only a tiny amount of data in them (compared to the original animation files).

Of course a BVH file containing only the key poses we're interested in won't play back at the correct speed (since we've dropped about 98% of the captured data). But by capturing only the key poses, after importing into Unity, we can stretch the imported animation out, using the original animation to work out how many frames to leave between each key pose. And we still need to load the original data into some kind of BVH playback application (such as BVHViewer) in order to see the animation being played out on a 3d character. But using a bvh-viewing app allows us to select a frame with a pose we want to keep and then simply enter this frame number into our VB editor app - it's a bit clunky, but it works. And, as much as anything, doesn't cost a penny!


No comments:

Post a Comment