技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
blender-motion-capture【数据与研究】Automate motion capture and tracking workflows in Blender with Python. Use when the user wants to import BVH or FBX mocap data, retarget motion to armatures, track camera or object motion from video, solve camera motion, clean up motion capture data, or script any tracking pipeline in Blender.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Import, process, and retarget motion capture data in Blender using Python. Work with BVH/FBX mocap files, track camera and object motion from video footage, solve 3D camera paths, and clean up animation data — all scriptable from the terminal.
import bpy
bpy.ops.import_anim.bvh(
filepath="/path/to/mocap.bvh",
target='ARMATURE',
global_scale=1.0,
frame_start=1,
use_fps_scale=False,
rotate_mode='NATIVE',
axis_forward='-Z', axis_up='Y'
)
armature = bpy.context.active_object
action = armature.animation_data.action
print(f"Imported: {armature.name}, Bones: {len(armature.data.bones)}, Frames: {action.frame_range}")
bpy.ops.import_scene.fbx(
filepath="/path/to/mocap.fbx",
use_anim=True,
ignore_leaf_bones=True,
automatic_bone_orientation=True,
primary_bone_axis='Y', secondary_bone_axis='X'
)
from mathutils import Matrix
def retarget_motion(source_armature, target_armature, bone_mapping):
"""Retarget animation using a bone name mapping: {target_bone: source_bone}"""
source_action = source_armature.animation_data.action
frame_start, frame_end = int(source_action.frame_range[0]), int(source_action.frame_range[1])
if not target_armature.animation_data:
target_armature.animation_data_create()
new_action = bpy.data.actions.new(f"{source_action.name}_retarget")
target_armature.animation_data.action = new_action
for frame in range(frame_start, frame_end + 1):
bpy.context.scene.frame_set(frame)
for tgt_name, src_name in bone_mapping.items():
src = source_armature.pose.bones.get(src_name)
tgt = target_armature.pose.bones.get(tgt_name)
if not src or not tgt:
continue
tgt.rotation_quaternion = src.rotation_quaternion
tgt.keyframe_insert(data_path="rotation_quaternion", frame=frame)
# Copy location for root bone only
if src_name == list(bone_mapping.values())[0]:
tgt.location = src.location
tgt.keyframe_insert(data_path="location", frame=frame)
# Example Mixamo → Rigify mapping
mapping = {
"spine": "mixamorig:Hips", "spine.001": "mixamorig:Spine",
"spine.004": "mixamorig:Neck", "spine.006": "mixamorig:Head",
"upper_arm.L": "mixamorig:LeftArm", "forearm.L": "mixamorig:LeftForeArm",
"upper_arm.R": "mixamorig:RightArm", "forearm.R": "mixamorig:RightForeArm",
"thigh.L": "mixamorig:LeftUpLeg", "shin.L": "mixamorig:LeftLeg",
"thigh.R": "mixamorig:RightUpLeg", "shin.R": "mixamorig:RightLeg",
}
def decimate_fcurve(fcurve, factor=0.5):
"""Remove keyframes to reduce data while keeping shape."""
points = fcurve.keyframe_points
total = len(points)
keep_every = max(1, int(1.0 / factor))
remove_indices = [i for i in range(total) if i % keep_every != 0 and i != 0 and i != total - 1]
for i in reversed(remove_indices):
points.remove(points[i])
armature = bpy.context.active_object
action = armature.animation_data.action
for fcurve in action.fcurves:
decimate_fcurve(fcurve, factor=0.5)
fcurve.update()
# Load footage
clip = bpy.data.movieclips.load("/path/to/footage.mp4")
scene = bpy.context.scene
scene.active_clip = clip
# Configure tracking
tracking = clip.tracking
settings = tracking.settings
settings.default_pattern_size = 21
settings.default_search_size = 71
settings.default_motion_model = 'AFFINE'
# Camera settings for solving
camera = tracking.camera
camera.sensor_width = 36.0
camera.focal_length = 50.0
# Solve camera motion
bpy.ops.clip.solve_camera()
solve_error = tracking.reconstruction.average_error
print(f"Solve error: {solve_error:.4f} px ({'Good' if solve_error < 0.5 else 'Needs refinement'})")
# Set up scene from solved data
bpy.ops.clip.setup_tracking_scene()
obj = bpy.data.objects["MyObject"]
constraint = obj.constraints.new(type='FOLLOW_TRACK')
constraint.clip = clip
constraint.track = tracking.tracks["Marker_01"]
constraint.use_3d_position = True
constraint.camera = scene.camera
# Bake constraint to keyframes
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.nla.bake(
frame_start=1, frame_end=clip.frame_duration,
only_selected=True, visual_keying=True,
clear_constraints=True, bake_types={'OBJECT'}
)
# Export as BVH
bpy.ops.export_anim.bvh(
filepath="/tmp/output_mocap.bvh",
frame_start=int(action.frame_range[0]),
frame_end=int(action.frame_range[1]),
rotate_mode='NATIVE'
)
# Export as FBX with baked animation
bpy.ops.export_scene.fbx(
filepath="/tmp/output_anim.fbx",
use_selection=True, bake_anim=True,
bake_anim_use_all_bones=True, add_leaf_bones=False
)
User request: "Import all BVH files from a folder, list bone counts and frame ranges"
import bpy, glob, os
for filepath in sorted(glob.glob("/path/to/mocap_library/*.bvh")):
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
bpy.ops.import_anim.bvh(filepath=filepath, target='ARMATURE', global_scale=0.01, frame_start=1)
arm = bpy.context.active_object
if arm and arm.animation_data:
action = arm.animation_data.action
duration = (action.frame_range[1] - action.frame_range[0]) / bpy.context.scene.render.fps
print(f"{os.path.basename(filepath)}: {len(arm.data.bones)} bones, {duration:.1f}s")
Run: blender --background --python scan_mocap.py
User request: "Import a BVH file, apply it to my rigged character, and render a preview"
import bpy
bpy.ops.wm.open_mainfile(filepath="/path/to/character.blend")
char_armature = bpy.data.objects["Armature"]
bpy.ops.import_anim.bvh(filepath="/path/to/walk_cycle.bvh", target='ARMATURE', global_scale=0.01)
mocap_armature = bpy.context.active_object
mocap_action = mocap_armature.animation_data.action
# Transfer action (works when bone names match)
if not char_armature.animation_data:
char_armature.animation_data_create()
char_armature.animation_data.action = mocap_action
# Remove temp armature, set frame range, add camera, render
bpy.data.objects.remove(mocap_armature)
scene = bpy.context.scene
scene.frame_start, scene.frame_end = int(mocap_action.frame_range[0]), int(mocap_action.frame_range[1])
scene.render.filepath = "/tmp/mocap_preview/frame_"
bpy.ops.render.render(animation=True)
global_scale=0.01 for cm-based files.bpy.ops.nla.bake() to convert constraints to keyframes for export.bake_anim=True in FBX to flatten NLA strips and constraints.