標準ボディモーションファイル形式

概要

Choreonoidでは ボディモデル の動作軌道を表現するデータとして「ボディモーション」があり、これを格納する「ボディモーションアイテム」は

等に用いられています。

本節では、このボディモーションを読み書きするための標準のファイル形式(標準ボディモーションファイル形式)について解説します。

基本仕様

標準ボディモーションファイルは、ベースの記述形式をYAMLとするテキストファイルとして記述されます。

ファイル名の拡張子については通常 ".seq" という拡張子を付与します。YAML形式なので ".yaml" または ".yml" としてもよいのですが、".seq" を用いることで他のYAMLファイルと区別しやすくなります。seqは "Sequence(シーケンス)"に由来するもので、通常「シーク」と読みます。

ボディモーションではモデル全体の動作を表現するため、

  • リンクの位置姿勢の軌道
  • 関節角軌道
  • その他軌道(ZMP軌道など)

といった複数の種類の軌道データから構成されるようになっています。

標準ボディモーション形式では、そのような複数の動作軌道データを、YAMLの階層構造を利用することでひとつのファイル内に格納します。各々の種類の動作軌道データを「コンポーネント」と呼びます。ファイル形式自体は、任意の種類の動作軌道コンポーネントを列挙することが可能となっています。

ボディモーションでは離散化された軌道を複数の時系列「フレーム」からなるデータとして記述します。フレームは時間軸上に一定の間隔で関連付けられます。この間隔を表す値として、一般的に

  • タイムステップ
  • フレームレート

の2種類の値があります。

タイムステップはフレーム間の時間間隔に対応し、フレームレートは単位時間あたりのフレーム数に対応します。これらは互いに逆数の関係となります。タイムステップの単位には通常「秒」を用います。対応するフレームレートの単位は「フレーム数/秒」となります。

標準ボディモーションファイルでは、フレームレートを用いてフレームの間隔を指定します。

なお、標準ボディモーションファイルでは、必ずしも一定間隔とは限らないフレームのデータも記述することが可能です。この場合、フレームレートによる指定は行わずに、各フレームに対応する時刻を全てのフレームに対して記述することになります。これについては後述します。

基本構造

標準ボディモーションファイルは以下のような構造で記述されます。

# トップノード
type: CompositeSeq
content: BodyMotion
formatVersion: 2
frameRate: 1000
numFrames: 7261
components:
  -
    # コンポーネント1
    type: MultiSE3Seq
    content: LinkPosition
    numParts: 1
    frameRate: 1000
    numFrames: 7261
    SE3Format: XYZQWQXQYQZ
    frames:
    # コンポーネント1のフレームデータ
      - フレーム1のデータ
      - フレーム2のデータ
               .
               .
               .
  -
    # コンポーネント2
    type: MultiValueSeq
    content: JointDisplacement
    numParts: 2
    frameRate: 1000
    numFrames: 7261
    frames:
    # コンポーネント2のフレームデータ
      - フレーム1のデータ
      - フレーム2のデータ
               .
               .
               .

#以下は説明のためのコメントですので通常は必要ありません。また、フレームのデータについてはここでは模式的な表現としており、実際の記述内容については以下で解説します。

YAML形式ですので、同じ階層にあるデータはインデントを合わせて記述します。上記の例では、各コンポーネントの記述部は、トップレベルからインデントを下げて記述し、インデントレベルも同一とすることが必要です。

トップノード

テキストのトップレベルには、以下のキーからなるマッピングのノードを記述します。

トップレベルノード
キー 内容
type CompositeSeqを指定
content BodyMotionを指定
formatVersion フォーマットのバージョン。2を指定
frameRate 動作全体でベースとなるフレームレート(フレーム数/秒)を指定
numFrames 動作全体のフレーム数を指定
components リスティング形式でコンポーネントを記述

typeとcontentには指定のものを記述します。

formatVersionは将来的に記述形式の変更を行った場合でも古い形式のデータを読めるようにするためのものです。本ドキュメントで解説している現在のバージョンは2なので、ここでは2を指定します。formatVersionを1とするか、formatVersionの記述がない場合は、古い形式のファイルとみなされます。なお、古い形式についてはここでは解説しません。

frameRate、numFramesについては、動作全体を対象とした値を記述します。実際には各コンポーネントで個別に指定することも可能なのですが、動作全体で統一することが望ましいですし、ここに書いておくと各コンポーネントでの記述を省略することができます。なお、numFramesに関しては情報として提示するためのもので、実際のフレーム数は各コンポーネントに実際に記述されているフレームデータの数となります。

components以下に実際の動作軌道データとなるコンポーネントを記述します。リスティングの形式で複数種類の動作軌道コンポーネントを記述可能です。

コンポーネントノード

ひとつのコンポーネントノードには、ひとつの種類の動作軌道データを記述します。各コンポーネントに共通で使用するキーは以下になります。

コンポーネントノードの共通部分
キー 内容
type 動作軌道のデータ型を文字列で指定
content データの用途を文字列で指定
numParts 1フレームの要素数。Multiタイプのデータ形式の場合に有効
frameRate フレームレート。省略するとトップノードで指定した値が使われる
numFrames フレーム数を指定
frames YAMLシーケンスとしてフレームデータを列挙

typeにはデータ型を、contentにはデータの用途をそれぞれ文字列で指定します。現在のところ、以下のtypeが利用可能です。

コンポーネントのtype
type データ型 contentの例
MultiValueSeq 複数の浮動少数点値からなるフレームの時系列データ JointDisplacement (関節変位軌道)
MultiSE3Seq 複数のSE(3)値(3次元空間中の位置・姿勢)からなるフレームの時系列データ LinkPosition (リンク位置姿勢軌道)
Vector3Seq 3次元ベクトル値ひとつからなるフレームの時系列データ ZMP (ゼロモーメントポイント軌道)

frameRateはトップノードで記述していればその値に合わせることが望ましいです。あるいは、トップノードでの記述があれば、各コンポーネントでは記述を省略することが可能で、その場合トップノードの値が適用されます。

numFramesについてもトップフレームの値との関係はframeRateと同様です。ただし実際のフレーム数はframes以下に記述されているフレームデータの数で決まります。numFramesはあくまで情報提示のために使用されます。

各type、contentの詳細について以下で解説します。

MultiValueSeq型

複数の浮動少数点値からなるフレームの時系列データです。1フレームあたり、複数のスカラ値で構成されると考えてもよいですし、多次元のベクトル値ひとつで構成されると考えても良いです。もちろん、どちらも同じことではあります。

この型の具体的な用途として、関節角軌道があります。その場合、contentには "JointDisplacement" を指定します。AngleではなくDisplacementとしているのは、関節によっては回転関節ではなく直動関節のものもあるからです。JointDisplacement (関節変位)であればどちらにも当てはまりますし、以下では関節角度も含めて「関節変位」という呼び方を用いることにします。

今のところボディモーションでサポートしているMultiValueSeq型のcontentはJointDisplacementのみですが、ファイル形式としてはどのようなcontentを格納しても結構です。Choreonoid上でボディモーションとして読み込む際にはJointDisplacment以外のコンポーネントは無視されますが、他のソフトウェアで他のcontentを使うようにしても一向に構いません。

MultiValuseSeq型は先頭に"Multi"が付いたタイプであり、この場合はコンポーネントノードの "numParts" が有効となります。そこに1フレームあたりの要素数(次元数)を指定する必要があります。関節変位軌道として用いる場合、関節の数をnumPartsに記述することになります。

frames以下には各フレームをひとつのYAMLシーケンスとして、その中にnumParts個分の数値を記述します。並べる順番は関節IDの順にします。回転関節の角度については単位をラジアンとし、直動関節の場合はメートルとします。

このコンポーネントの記述例を以下に示します。

  -
    type: MultiValueSeq
    content: JointDisplacement
    numParts: 2
    frameRate: 100
    numFrames: 100
    frames:
      - [ 0.0,  0.0  ]
      - [ 0.01, 0.01 ]
      - [ 0.01, 0.02 ]
      - [ 0.02, 0.03 ]
      - [ 0.02, 0.04 ]
              .
              .
              .

これは関節が2つある場合の例です。ここでは6番目のフレーム以降は省略していますが、実際にはnumFramesで指定されている100フレーム分が書かれることになります。

MultiSE3Seq型

複数のSE(3)値からなるフレームの時系列データです。SE(3)の値は3次元空間中の位置と姿勢(回転)を両方表現する値です。

この型の具体的な用途としては、リンクの位置姿勢の軌道があります。この場合 content には "LinkPosition" を指定します。

単一リンクのモデルでは、その動きを表現するのにこの型の軌道データが必要となります。また、複数のリンクからなるモデルについて、その関節の動きはJointDisplacmentデータで表現することができますが、モデル全体の動きを表現するためには、やはりルートリンクの位置姿勢の軌道が必要となります。このため、ボディモーションには通常ルートリンクの位置姿勢の軌道データが含まれることになります。

実際に1フレームに含めるリンクの数は、MultiValueSeq型と同様にnumPartsで指定します。並べ方は、リンクインデックスの順番(リンクツリーを深さ優先探索で辿った順番)となります。通常1番目の要素はルートリンクに対応します。

SE(3)は位置と姿勢をあわせて6次元の値になりますが、そのうち姿勢に対応する3次元分については、回転行列、クォータニオン、ロールピッチヨー等の様々な表現方法があります。また、それらの要素をどのように並べるかについても決めておく必要があります。MultiSE3Seq型のコンポーネントでは、これを"SE3Format"というキーで指定します。これに指定可能なシンボルを以下にまとめます。

SE3Formatのタイプ
シンボル 内容
XYZQWQXQYQZ 姿勢をクォータニオンで記述する。位置のX、Y、Zの後に、クォータニオンのW、X、Y、Zの値を並べるものとする
XYZQXQYQZQW XYZQWQXQYQZと同様に姿勢をクォータニオンで記述するが、クォータニオンの並べ方をX、Y、Z、Wの順とする
XYZRPY 姿勢をロールピッチヨー形式で記述する。位置のX、Y、 Zの後に、姿勢のR、P、 Y の値を並べるものとする

いずれの場合も、SE(3)の値ひとつ分はひとつのYAMLシーケンスとして記述されます。標準の形式は "XYZQWQXQYQZ" です。この形式で、例えば位置 (X, Y, Z) が (1, 2, 3) で姿勢のクォータニオン (W, X, Y, Z) が (1, 0, 0, 0) の値は

[ 1, 2, 3, 1, 0, 0, 0 ]

と記述されます。

そして、このようなSE(3)の値をさらにnumParts分のYAMLシーケンスとしてframes以下に並べていきます。

このコンポーネントの記述例を以下に示します。

 -
   type: MultiSE3Seq
   content: LinkPosition
   numParts: 1
   frameRate: 100
   numFrames: 100
   SE3Format: XYZQWQXQYQZ
   frames:
     - [ [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
                .
                .
                .

この例のように、numPartsが1の場合でも、各フレームのYAMLシーケンスは2重の入れ子の状態となります。numPartsが2以上の場合、各フレームの記述では、複数のSE(3)値を以下のように並べます。

- [ [ X1, Y1, Z1, QW1, QX1, QY1, QZ1 ], [ X2, Y2, Z2, QW2, QX2, QY2, QZ2 ], ... , [ Xn, Yn, Zn, QWn, QXn, QYn, QZn ] ]

※ ここでXn等のラベルはn番目のSE(3)値の各要素に対応する数値を表しています。また、「...」のところは実際には3番目からn-1番目のSE(3)値が入ります。

Vector3Seq型

単一の3次元ベクトル値からなるフレームの時系列データです。

この型の具体的な用途としては、重心軌道やゼロモーメントポイント(ZMP)軌道などが考えられます。

現在BodyMotionで正式にサポートされているのZMPで、この場合 content には "ZMP" を指定します。また、"isRootRelative" というキーに "true" を指定すると、座標系がルートリンクからの相対座標となります。このキーの指定がないか、falseが指定されている場合は、グローバル座標での記述となります。

この型ではフレームに含まれる値はひとつに限定されているので、numParts の指定は無効となります。

このコンポーネントの記述例を以下に示します。

 -
   type: Vector3Seq
   content: ZMP
   frameRate: 100
   numFrames: 100
   frames:
     - [ 0.0, 0.0,   0.0 ]
     - [ 0.0, 0.001, 0.0 ]
     - [ 0.0, 0.002, 0.0 ]
     - [ 0.0, 0.003, 0.0 ]
     - [ 0.0, 0.004, 0.0 ]
              .
              .
              .

リンク位置姿勢軌道と関節変位軌道の併用形態

モデルの動作の基本となるのは、リンク位置姿勢軌道(MultiSE3Seq型のLinkPositionデータ)と関節変位軌道(MultiValueSeq型のJointDisplacementデータ)です。

単一リンクからなる剛体モデルの場合、関節は存在しませんので、numPartsが1のリンク位置姿勢軌道のみがあればOKです。一方、複数のリンクからなり関節が存在するモデルの場合は、動作を表現するにあたって、リンク位置姿勢軌道と関節変位軌道を適切に組み合わせる必要があります。その形態としては以下が考えられます。

  1. 全リンク分のリンク位置姿勢軌道
  2. ルートリンクの位置姿勢軌道 + 全関節分の関節変位軌道
  3. 全リンク分のリンク位置姿勢軌道 + 全関節分の関節変位軌道

1については、動作の全てをリンクの位置姿勢データで表現する形態です。これによって、各リンクが剛体である場合の動作は完全に表現できることになります。

2については、ルートリンクのみリンクの位置姿勢を与え、それに加えて全関節分の関節変位も与えます。この場合、ルートリンク以外のリンクの位置姿勢は、関節変位を用いた順運動学計算によって求めることができます。ロボットの動作データの表現方法としては、これが標準的な形式です。メリットとしては、1と比べてデータ量がずっと少なくなります。リンクひとつの位置姿勢を表現するのには6次元のSE(3)値が必要なのに対し、関節ひとつは1次元の浮動小数点値で表現できるためです。また、ロボットでは関節変位を参照したいことも一般的であり、この形態ではそれを直接的に行うことができます。一方で、デメリットとしては、順運動学で求めたリンクの位置姿勢が、実際のロボットやシミュレーション結果の位置姿勢とずれることがあります。実機の場合リンクや関節の剛性が十分でない場合がありますし、シミュレーションでもそれを再現しようとしたり、関節拘束の計算方法によって多少のズレが出てきたりするからです。

3については、リンクの位置姿勢のズレもない上に、関節変位についても直接参照することができるというメリットがあります。ただしデータ量については上記の組み合わせの中で最大となってしまいます。

標準ボディモーションファイルはフォーマットとしては上記の組み合わせを全てサポートしていますので、それぞれの形態のメリット・デメリットを考慮しながら、用途に応じて適切な組み合わせでデータを記述するようにしてください。

不定間隔フレームデータの記述方法

Choreonoidのボディモーションは 基本仕様 でも述べたように、動作の各フレームは時間的に一定間隔で並べられるものとしています。

これに対して、動作データによっては、一定間隔で並べることが適切ではない場合もあります。例えばロボットの状態がログとして出力され、それを記録していくような場合に、ロボットからのログが一定間隔で出力されるとは限りません。ロボットのコンピュータにおける制御や通信等の処理状況によっては、ログの処理に時間をかけられないこともあり、結果としてログ出力の間隔がまちまちとなることは珍しいことではありません。あるいは、ロボットが静止している時間が多いような場合、静止している時も細かい時間間隔でデータを保持すると無駄が多くなってしまいます。

そのような場合は、フレームの間隔を必ずしも一定とはせず、各フレームに時刻のタイムスタンプをつけて対応することも一般的です。そのようなデータをここでは「不定間隔フレームデータ」もしくは「時刻指定付きフレームデータ」と呼ぶことにします。

標準ボディモーションファイルでは、実はこの形式のデータも記述可能です。

その場合、ノードにおいて

hasFrameTime: true

という記述を追加します。

これは他のパラメータと同様に、各コンポーネントのノードに記述してもよいですし、トップノードに記述してあれば、各コンポーネントにおいては記述を省略することも可能です。

そして、各フレームデータの先頭に、そのフレームの時刻に対応する数値を記述します。

例えば MultiValueSeq型 の例を不定間隔フレーム化すると、以下のようになります。

  -
    type: MultiValueSeq
    content: JointDisplacement
    numParts: 2
    numFrames: 100
    hasFrameTime: true
    frames:
      - [ 0.0, 0.0,  0.0  ]
      - [ 0.1, 0.01, 0.01 ]
      - [ 0.3, 0.01, 0.02 ]
      - [ 0.4, 0.02, 0.03 ]
      - [ 0.7, 2,    0.04 ]
              .
              .
              .

ここでframes以下に並べてある数値の先頭が時刻に対応します。ここでは 0.0, 0.1, 0.3, 0.4, 0.7 という時刻が設定されています。このように一定間隔でない時刻を指定してもOKです。ただし、各フレームの時刻はその前のフレームよりも大きい値を指定するようにしてください。時間が逆行するような記述方法については、本フォーマットとしてはサポート外とします。

結果として、各フレームに書かれている数値の数が3つとなっていますが、フレームの要素数はあくまでnumPartsに書かれている2となりますのでご注意下さい。

MultiSE3Seq型 で挙げた例を、上記と同じ時刻で不定間隔化すると以下のようになります。

 -
   type: MultiSE3Seq
   content: LinkPosition
   numParts: 1
   numFrames: 100
   hasFrameTime: true
   SE3Format: XYZQWQXQYQZ
   frames:
     - [ 0.0, [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ 0.1, [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ 0.3, [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ 0.4, [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
     - [ 0.7, [ -2, -0.5, 0.1, 1, 0, 0, 0 ] ]
                .
                .
                .

この場合、SE(3)の値自体がひとつのシーケンスとなっており、時刻の値はそのひとつ上位に書かれることになりますので、その点ご注意ください。

不定間隔フレームデータのインポート

繰り返しとなりますが、Choreonoidのボディモーションは 基本仕様 でも述べたように一定間隔フレームデータの設計となっており、不定間隔フレームデータを格納することはできません。また、不定間隔フレームデータに対応した他のデータ構造も現在のところChoreonoidには用意されていません。従って、不定間隔フレームデータをそのままChoreonoid上に読みこむことはできません。

ただし、不定間隔フレームデータのファイルを一定間隔フレームデータとしてインポートすることは可能です。この場合、読み込み元の各フレームはその時刻以降で最も近い読み込み先のフレームに当てはめられ、次の読み込み先フレームまでその値が維持されます。

注釈

インポートにおいてはフレーム間の補間なども行えばよりスムーズな動作軌道としてインポートできるかと思いますが、現在のところそのような機能はなく、元のデータの値を対応するフレームにセットするだけとなっています。

この機能について、まず読み込み先のフレームレートを決めておく必要があります。現在のところ、Choreonoidのインタフェースとしてはこれを設定する機能が無く、ボディモーションファイルに記述されたframeRateの値が使われるようになっています。ですので、トップノードのみで結構ですので、frameRateについて希望する値をファイルに記述しておくようにしてください。

インポート自体は、通常のボディモーションファイルの読み込みと同様に行うことができます。つまり、メインメニューの「ファイル」ー「読み込み」から「ボディモーション」を選択し、ファイルダイアログ上でインポートしたいファイルを選択します。そのファイルが不定間隔フレームデータであった場合は、自動的にインポートの処理となります。

記述スタイルについて

YAMLではブロックスタイル、フロースタイルの2種類の書き方があります。階層構造を記述する際に、ブロックスタイルではインデントを用いますし、フロースタイルでは波括弧 "{ }" (マッピング用)と角括弧 "[ ]" (シーケンス用)を用います。

本形式のファイルにおいては、どちらを用いても、あるいはどう組み合わせてもよいのですが、一般的には上の記述例のようにします。つまり、各フレームのデータの記述にはフロースタイルを用いることとし、それ以外の部分にはブロックスタイルを用います。このように記述すると見やすくまとまってよいかと思います。Choreonoid上からファイルを出力する場合は、このスタイルになります。

なお、YAMLはJSON形式のスーパーセットであり、全てをフロースタイルで記述すると、JSON形式のファイルとなります。動作データをJSON形式のファイルとして扱いたい場合は、そのようにしてください。(ただし、JSONではコメントも入れることが出来ないようですので、ご注意下さい。)

サンプルファイル

shareディレクトリ ( ディレクトリ構成 参照 ) の "motion/SR1" 以下にSR1モデル用の標準ボディモーションファイルがいくつかあります。これらは "SR1Walk.cnoid" や "SR1WalkinHouse.cnoid" といったサンプルにおいて、コントローラから読み込まれて動作パターンデータとして使われています。

シミュレーション結果の出力先 として生成されたボディモーションアイテムを保存することでも、標準ボディモーションファイルがどのようなものか確認することができます。生成されたボディモーションアイテムを選択状態とし、メインメニューの「ファイル」-「名前を付けて選択アイテムを保存」を実行すると、ファイル保存用のダイアログボックスが表示されますので、これを用いてファイルの保存を行ってください。