Skip to content

jutanke/ndms

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Normalized Directional Motion Similarity

Implementation of Normalized Directional Motion Similarity (NDMS) introduced in

@inproceedings{tanke2021intention,
  title={Intention-based Long-Term Human Motion Anticipation},
  author={Tanke, Julian and Zaveri, Chintan and Gall, Juergen},
  booktitle={International Conference on 3D Vision},
  year={2021},
}

If this library is useful to you please cite Intention-based Long-Term Human Motion Anticipation.

Install

pip install git+https://github.com/jutanke/ndms.git

Usage

from ndms.database import Data, Database


class SampleData(Data):
    """
    Storage of the data sequences that populate the evaluation Database.
    The sequences may have different lengths but must all have the same
    data dimenion.
    ! IMPORTANT ! Atm we only support 3D data, meaning that the dimension
    must be divisible by 3, meaning that each sequence should be 
      [n_frames x dim * 3]
    """
    def __init__(self, data):
        """
        :param data: list of sequences of variable length
        """
        self.data = data
        
    def __getitem__(self, index: int):
        return self.data[index]

    def __len__(self):
        return len(self.data)


test_sequences = SampleData(data=...)

kernel_size = 3  # nbr of frames for a motion word
db = Database(data=test_sequences, kernel_size=kernel_size)

framewise_ndms, _ = db.rolling_query(seq)


# you can define a transform function that is called for each motion word
# to for example normalize the data:
def smpl_transform(motion_word):
   """
   :param {kernel_size x dim*3}
   """
   return motion_word


db = Database(data=data, kernel_size=kernel_size, transform_data_fn=transform)

NDMS can be used for SMPL-skeletons as follows:

import numpy as np
import numpy.linalg as la
from ndms.database import Data, Database
from einops import rearrange


def apply_rotation_to_seq(seq, R):
    """
    :param seq: {n_frames x 18 x 3}
    :param R: {3x3}
    """
    R = np.expand_dims(R, axis=0)
    return np.ascontiguousarray(seq @ R)


def apply_normalization_to_seq(seq, mu, R):
    """
    :param seq: {n_frames x J x 3}
    :param mu: {3}
    :param R: {3x3}
    """
    is_flat = False
    if len(seq.shape) == 2:
        is_flat = True
        seq = rearrange(seq, "t (j d) -> t j d", d=3)

    if len(seq.shape) != 3 or seq.shape[2] != 3:
        raise ValueError(f"weird shape: {seq.shape}")

    mu = np.expand_dims(np.expand_dims(np.squeeze(mu), axis=0), axis=0)
    seq = seq - mu
    seq_rot = apply_rotation_to_seq(seq, R)

    if is_flat:
        seq_rot = rearrange(seq, "t j d -> t (j d)")
    return seq_rot


def get_normalization(left3d, right3d):
    """
    Get rotation + translation to center and face along the x-axis
    """
    mu = (left3d + right3d) / 2
    mu[2] = 0
    left2d = left3d[:2]
    right2d = right3d[:2]
    y = right2d - left2d
    y = y / (la.norm(y) + 0.00000001)
    angle = np.arctan2(y[1], y[0])
    R = rot.rot3d(0, 0, angle)
    return mu, R


class SMPLSkeletonData(Data):
    """
    Storage of the data sequences that populate the evaluation Database.
    The sequences may have different lengths but must all have the same
    data dimenion.
    ! IMPORTANT ! Atm we only support 3D data, meaning that the dimension
    must be divisible by 3, meaning that each sequence should be 
      [n_frames x 24 * 3]
    """
    def __init__(self, data):
        """
        :param data: list of sequences of variable length
        """
        self.data = data

    def n_dim(self):
      return 16 * 3
        
    def __getitem__(self, index: int):
        return self.data[index]

    def __len__(self):
        return len(self.data)


# you can define a transform function that is called for each motion word
# to for example normalize the data:
def transform(motion_word):
   """
   :param {kernel_size x dim*3}
   """
   jids_for_ndms = np.array([0, 1, 2, 4, 5, 7, 8, 10, 11, 16, 17, 18, 19, 20, 21, 15], dtype=np.int64)
   motion_word = rearrange(motion_word, "t (j d) -> t j d", d=3)
   assert motion_word.shape[1] == 45 or motion_word.shape[1] == 24
   motion_word = np.copy(motion_word[:, :, [0, 2, 1]])  # we want the gravitational axis to be z, not y!
   left = motion_word[0, 1]  # left hip joint
   right = motion_word[0, 2]  # right hip joint
   mu, R = get_normalization(left, right)
   motion_word = apply_normalization_to_seq(motion_word, mu, R)
   return np.ascontiguousarray(
    rearrange(motion_word[:, jids_for_ndms], "t j d -> t (j d)")
   )
   return motion_word

# the data should be a list of SMPl skeletons [(n_frames x 45/24 x 3)] where the
# floor plane normal is the y-axis - if this is not the case you will have to
# transform your sequences such that the y-axis is the floor normal! This is important
# for porperly normalize the motion words during inference!
# The different motion sequences do not have to be of equal length!
test_sequences = SMPLSkeletonData(data=...)

# we suggest the kernel size to represent ~1/3second
kernel_size = 8  # nbr of frames for a motion word
db = Database(data=test_sequences, kernel_size=kernel_size, transform_data_fn=transform)

# seq is [n_frames x 24/45 x 3] - n_frames can be any length!
framewise_ndms, _ = db.rolling_query(seq)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages