名称: 动作记谱系统 描述: 设计用于编码、评分和生成编舞动作的系统,使用Laban记谱、计算几何和过程动画原则。 许可证: MIT
动作记谱系统
这个技能提供指导,用于创建系统以编码、分析和生成人类动作,适用于编舞、动画和动作分析。
核心能力
- 动作记谱: Labanotation、Benesh、Motif记谱
- 计算几何: 骨骼表示、关节角度
- 过程动画: 基于规则的动作生成
- 努力-形状分析: Laban运动分析(LMA)
- 时间结构: 节奏、分段、动态
动作记谱基础
动作的挑战
动作本质上是多维的:
- 3D空间路径
- 时间演化
- 身体部位协调
- 定性动态(努力)
- 关系上下文(其他身体、对象、空间)
主要记谱系统
| 系统 | 优势 | 应用场景 |
|---|---|---|
| Labanotation | 完整、精确 | 归档、重建 |
| Benesh | 紧凑、视觉化 | 芭蕾、治疗 |
| Motif | 抽象、可读 | 教学、分析 |
| 动作捕捉 | 精确坐标 | 动画、研究 |
Laban运动分析框架
身体组件
哪些身体部位在移动:
身体组织:
├── 核心-远端(从中心向外)
├── 头-尾(脊柱连接)
├── 上-下(水平分割)
├── 身体半侧(左-右)
└── 交叉侧(对角线连接)
身体部位层次:
中心(骨盆)
├── 躯干(脊柱、胸部)
│ ├── 头
│ ├── 肩膀
│ └── 手臂 → 肘部 → 手 → 手指
└── 臀部
└── 腿 → 膝盖 → 脚 → 脚趾
空间组件
身体移动的位置:
class KinesphereModel:
"""身体周围可达空间"""
DIMENSIONS = {
'垂直': {'上', '下'},
'水平': {'左', '右'},
'矢状': {'前', '后'}
}
LEVELS = ['低', '中', '高']
# 动球中的27个方向
DIRECTION_SYMBOLS = {
'放置_高': (0, 1, 0),
'放置_中': (0, 0, 0),
'放置_低': (0, -1, 0),
'前_高': (0, 1, 1),
'前_中': (0, 0, 1),
'前_低': (0, -1, 1),
# ... 所有27种组合
}
# 空间尺度
SCALES = {
'近': 0.3, # 接近身体中心
'中': 0.6, # 一般可达范围
'远': 1.0 # 完全伸展
}
努力组件
动作如何执行(定性动态):
class EffortFactors:
"""Laban努力品质"""
FACTORS = {
'重量': {
'轻': {'感觉': '轻盈', '值': -1},
'强': {'感觉': '有力', '值': 1}
},
'时间': {
'持续': {'感觉': '悠闲', '值': -1},
'快速': {'感觉': '紧急', '值': 1}
},
'空间': {
'间接': {'感觉': '灵活', '值': -1},
'直接': {'感觉': '专注', '值': 1}
},
'流动': {
'自由': {'感觉': '流畅', '值': -1},
'束缚': {'感觉': '控制', '值': 1}
}
}
# 基本努力动作(重量、时间、空间的组合)
ACTIONS = {
'拳击': {'重量': '强', '时间': '快速', '空间': '直接'},
'轻点': {'重量': '轻', '时间': '快速', '空间': '直接'},
'挥砍': {'重量': '强', '时间': '快速', '空间': '间接'},
'弹动': {'重量': '轻', '时间': '快速', '空间': '间接'},
'按压': {'重量': '强', '时间': '持续', '空间': '直接'},
'滑行': {'重量': '轻', '时间': '持续', '空间': '直接'},
'扭转': {'重量': '强', '时间': '持续', '空间': '间接'},
'漂浮': {'重量': '轻', '时间': '持续', '空间': '间接'}
}
形状组件
身体如何改变形式:
class ShapeQualities:
"""身体形状变化"""
MODES = {
'形状流': {
'描述': '内部塑形,自我导向',
'示例': ['呼吸', '增长/缩小']
},
'方向性': {
'描述': '与环境连接',
'子类型': ['放射状', '弧状']
},
'雕刻': {
'描述': '雕刻3D空间',
'关系': '与环境互动'
}
}
AFFINITIES = {
'上升': {'努力': '轻', '方向': '上'},
'下沉': {'努力': '强', '方向': '下'},
'展开': {'努力': '间接', '方向': '水平'},
'包围': {'努力': '直接', '方向': '内'},
'前进': {'努力': '持续', '方向': '前'},
'后退': {'努力': '快速', '方向': '后'}
}
计算动作表示
骨骼数据结构
class Skeleton:
"""层次化骨骼表示"""
def __init__(self):
self.joints = {
'骨盆': Joint('骨盆', parent=None),
'脊柱': Joint('脊柱', parent='骨盆'),
'胸部': Joint('胸部', parent='脊柱'),
'颈部': Joint('颈部', parent='胸部'),
'头': Joint('头', parent='颈部'),
'左肩': Joint('左肩', parent='胸部'),
'左肘': Joint('左肘', parent='左肩'),
'左腕': Joint('左腕', parent='左肘'),
'右肩': Joint('右肩', parent='胸部'),
# ... 等等
}
def get_world_position(self, joint_name):
"""从局部变换计算全局位置"""
joint = self.joints[joint_name]
position = joint.local_position
current = joint
while current.parent:
parent = self.joints[current.parent]
position = parent.rotation.apply(position) + parent.local_position
current = parent
return position
def compute_joint_angles(self):
"""提取关节角度用于分析"""
angles = {}
for name, joint in self.joints.items():
if joint.parent:
angles[name] = joint.rotation.as_euler('xyz')
return angles
class Joint:
"""骨骼层次中的单个关节"""
def __init__(self, name, parent=None):
self.name = name
self.parent = parent
self.local_position = np.array([0, 0, 0])
self.rotation = Rotation.identity()
self.constraints = {} # 关节限制
运动轨迹
class MotionTrajectory:
"""姿态的时间序列"""
def __init__(self, fps=30):
self.fps = fps
self.frames = [] # Skeleton状态列表
self.annotations = [] # 定性标记
def duration(self):
return len(self.frames) / self.fps
def get_velocity(self, joint_name, frame_idx):
"""计算瞬时速度"""
if frame_idx < 1:
return np.zeros(3)
pos_current = self.frames[frame_idx].get_world_position(joint_name)
pos_prev = self.frames[frame_idx - 1].get_world_position(joint_name)
return (pos_current - pos_prev) * self.fps
def extract_effort_features(self, joint_name, window=10):
"""从运动估计Laban努力品质"""
features = {
'重量': self._compute_acceleration_magnitude(joint_name, window),
'时间': self._compute_temporal_change_rate(joint_name, window),
'空间': self._compute_path_directness(joint_name, window),
'流动': self._compute_flow_continuity(joint_name, window)
}
return features
过程动作生成
基于规则的编舞
class ChoreographyGenerator:
"""从规则生成动作序列"""
def __init__(self):
self.vocabulary = self._load_movement_vocabulary()
self.grammar = self._load_grammar_rules()
def generate_phrase(self, theme, duration_beats=16):
"""生成编舞短语"""
# 从主题开始基于动机
motif = self._select_motif(theme)
# 通过短语发展
phrase = [motif]
current_beats = motif.duration_beats
while current_beats < duration_beats:
# 应用发展规则
if random.random() < 0.3:
# 带变奏重复
variation = self._vary_motif(phrase[-1])
phrase.append(variation)
elif random.random() < 0.5:
# 对比
contrast = self._generate_contrast(phrase[-1])
phrase.append(contrast)
else:
# 过渡
transition = self._smooth_transition(phrase[-1])
phrase.append(transition)
current_beats += phrase[-1].duration_beats
return phrase
def _vary_motif(self, motif):
"""创建动作变奏"""
variations = [
self._change_level, # 相同动作,不同水平
self._mirror, # 左右反转
self._change_size, # 更大或更小
self._change_tempo, # 更快或更慢
self._change_direction, # 面向不同方向
self._fragment, # 动作的部分
self._extend, # 添加到动作
]
return random.choice(variations)(motif)
努力驱动动画
class EffortAnimator:
"""生成具有指定努力品质的动作"""
def animate_action(self, skeleton, target_position, effort_state):
"""以指定努力移动身体部位"""
# 时间因子影响持续时间
if effort_state['time'] == '快速':
duration = 0.3
ease_type = '指数'
else: # 持续
duration = 1.2
ease_type = '正弦'
# 重量因子影响加速度
if effort_state['weight'] == '强':
acceleration_curve = self._strong_curve()
else: # 轻
acceleration_curve = self._light_curve()
# 空间因子影响路径
if effort_state['space'] == '直接':
path = self._linear_path(skeleton.current, target_position)
else: # 间接
path = self._curved_path(skeleton.current, target_position)
# 流动因子影响连续性
if effort_state['flow'] == '束缚':
path = self._add_micro_pauses(path)
# 自由流动默认平滑
return self._create_animation(
path=path,
duration=duration,
acceleration=acceleration_curve
)
空间模式生成
class FloorPatternGenerator:
"""生成空间路径"""
def generate_path(self, pattern_type, space_bounds, duration):
"""生成地板模式"""
patterns = {
'圆形': self._circular_path,
'螺旋': self._spiral_path,
'八字形': self._figure_eight,
'对角线交叉': self._diagonal_cross,
'锯齿形': self._zigzag_path,
'随机游走': self._random_walk
}
generator = patterns.get(pattern_type, self._random_walk)
return generator(space_bounds, duration)
def _spiral_path(self, bounds, duration, turns=3):
"""生成螺旋地板模式"""
center = bounds.center
max_radius = min(bounds.width, bounds.height) / 2
points = []
num_points = int(duration * 30) # 每拍30个点
for i in range(num_points):
t = i / num_points
angle = t * turns * 2 * np.pi
radius = max_radius * (1 - t) # 向内螺旋
x = center[0] + radius * np.cos(angle)
y = center[1] + radius * np.sin(angle)
points.append((x, y, t * duration))
return points
记谱输出
文本记谱格式
class MovementScore:
"""基于文本的动作乐谱"""
def to_notation(self, phrase):
"""将短语转换为可读记谱"""
score = []
for movement in phrase:
notation = {
'拍': movement.start_beat,
'持续时间': movement.duration_beats,
'身体': self._notate_body(movement),
'空间': self._notate_space(movement),
'努力': self._notate_effort(movement),
'描述': movement.description
}
score.append(notation)
return score
def _notate_effort(self, movement):
"""努力记谱符号"""
symbols = {
('强', '快速', '直接'): '⚡', # 拳击
('轻', '持续', '间接'): '☁️', # 漂浮
('强', '持续', '间接'): '🌀', # 扭转
# ... 等等
}
effort_tuple = (
movement.effort['重量'],
movement.effort['时间'],
movement.effort['空间']
)
return symbols.get(effort_tuple, '○')
视觉乐谱表示
时间 →
| 0 | 1 | 2 | 3 | 4 |
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ ◊ 头 │ │ ○ │ │ │
│ ═ 手臂 │ ═══════│════ │ ═════ │═════ │
│ ║ 躯干 │ ║ │ ║║ │ ║ │ ║║║ │
│ ‖ 腿 │ ‖‖ │ ‖ ‖ │ ‖‖ │ ‖ ‖ │
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ 水平: │ 高 │ 中 │ 低 │ 中 │
│ 努力: │ ⚡ │ ☁️ │ 🌀 │ ⚡ │
└─────────┴─────────┴─────────┴─────────┴─────────┘
最佳实践
编码原则
- 保留意图: 捕捉编舞者想要的,不仅仅是位置
- 分层信息: 结构 > 形状 > 动态
- 允许解释: 除非需要,不要过度指定
- 支持变奏: 启用受控即兴
分析指南
- 提取定量(位置)和定性(努力)特征
- 考虑文化和风格上下文
- 与从业者验证
参考
references/labanotation-symbols.md- Labanotation符号参考references/lma-glossary.md- Laban运动分析术语references/motion-capture-formats.md- 常见动作捕捉数据格式