身体关键点+avatar处理

写一段代码,将mediapipe捕捉的人体关键点数据转换成unity中avatar支持的形式 以下是一个将MediaPipe人体关键点数据转换为Unity Avatar控制的示例代码方案,分为Python数据采集和C# Unity控制两部分:

# Python部分 - MediaPipe数据处理(示例)
import mediapipe as mp
import socket
import json

mp_pose = mp.solutions.pose

# Unity连接配置
HOST = '127.0.0.1'  # Unity所在IP
PORT = 65432        # 通信端口

# 关键点索引映射(MediaPipe Pose定义的33个关键点)
JOINT_INDICES = {
    "LEFT_HIP": 23,
    "RIGHT_HIP": 24,
    "SPINE": 11,  # 肩部中点近似为脊柱上端
    "LEFT_KNEE": 25,
    "RIGHT_KNEE": 26,
    "LEFT_SHOULDER": 11,
    "RIGHT_SHOULDER": 12,
    "NECK": 0,     # 鼻部近似为颈部
}

# 坐标系转换函数
def convert_coordinates(mediapipe_point):
    """将MediaPipe坐标系转换为Unity坐标系"""
    # MediaPipe: 右手坐标系,Y轴向下
    # Unity: 左手坐标系,Y轴向上,Z轴向前
    return {
        'x': mediapipe_point.x,
        'y': -mediapipe_point.y,  # 反转Y轴
        'z': -mediapipe_point.z   # 反转Z轴
    }

# 数据发送函数
def send_to_unity(sock, landmarks):
    joint_data = {}
    for joint_name, index in JOINT_INDICES.items():
        if landmarks[index].visibility < 0.5:  # 过滤低可见度关键点
            continue
        joint_data[joint_name] = convert_coordinates(landmarks[index])

    # 计算臀部中心点
    if "LEFT_HIP" in joint_data and "RIGHT_HIP" in joint_data:
        lh = joint_data["LEFT_HIP"]
        rh = joint_data["RIGHT_HIP"]
        joint_data["HIP_CENTER"] = {
            'x': (lh['x'] + rh['x']) / 2,
            'y': (lh['y'] + rh['y']) / 2,
            'z': (lh['z'] + rh['z']) / 2
        }

    sock.sendto(json.dumps(joint_data).encode(), (HOST, PORT))

# MediaPipe处理主循环
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    with mp_pose.Pose(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7) as pose:

        cap = cv2.VideoCapture(0)
        while cap.isOpened():
            success, image = cap.read()
            if not success:
                continue

            results = pose.process(image)
            if results.pose_landmarks:
                send_to_unity(s, results.pose_landmarks.landmark)
// C#部分 - Unity控制脚本
using UnityEngine;
using System.Net.Sockets;
using System.Text;
using System;

public class MotionReceiver : MonoBehaviour
{
    public Transform hipCenter;
    public Transform spine;
    public Transform leftLeg, rightLeg;
    public Transform leftArm, rightArm;
    public Transform neck;

    private UdpClient udpClient;
    private const int port = 65432;

    void Start()
    {
        udpClient = new UdpClient(port);
        udpClient.Client.ReceiveTimeout = 1000;
    }

    void Update()
    {
        try {
            IPEndPoint remoteEP = null;
            byte[] data = udpClient.Receive(ref remoteEP);
            string json = Encoding.UTF8.GetString(data);
            ProcessPoseData(JsonUtility.FromJson<PoseData>(json));
        } catch (Exception e) {
            Debug.LogWarning(e.ToString());
        }
    }

    void ProcessPoseData(PoseData data)
    {
        // 臀部位置控制
        if (data.HIP_CENTER != null)
        {
            Vector3 hipPos = new Vector3(
                data.HIP_CENTER.x * 2,  // 缩放系数根据实际调整
                data.HIP_CENTER.y * 2,
                data.HIP_CENTER.z * 2
            );
            hipCenter.localPosition = hipPos;
        }

        // 腿部旋转计算
        if (data.LEFT_HIP != null && data.LEFT_KNEE != null)
        {
            Vector3 hip = ToVector3(data.LEFT_HIP);
            Vector3 knee = ToVector3(data.LEFT_KNEE);
            leftLeg.rotation = Quaternion.LookRotation(knee - hip, Vector3.up);
        }

        // 脊柱旋转
        if (data.SPINE != null && data.NECK != null)
        {
            Vector3 spinePos = ToVector3(data.SPINE);
            Vector3 neckPos = ToVector3(data.NECK);
            spine.rotation = Quaternion.LookRotation(neckPos - spinePos, Vector3.up);
        }
    }

    Vector3 ToVector3(JointPoint point)
    {
        return new Vector3(point.x, point.y, point.z);
    }

    [System.Serializable]
    public class PoseData
    {
        public JointPoint HIP_CENTER;
        public JointPoint LEFT_HIP;
        public JointPoint RIGHT_HIP;
        public JointPoint LEFT_KNEE;
        // 其他需要的关键点...
    }

    [System.Serializable]
    public class JointPoint
    {
        public float x;
        public float y;
        public float z;
    }
}

关键实现细节说明:

  1. 坐标系转换:
  2. MediaPipe使用右手坐标系(Y轴向下)
  3. Unity使用左手坐标系(Y轴向上,Z轴向前)
  4. 转换公式:y = -mediapipe.y, z = -mediapipe.z
  5. 数据优化:
  6. 使用UDP协议实现低延迟传输
  7. 仅传输必要关键点数据
  8. 添加可见性过滤(visibility < 0.5)
  9. Unity端处理:
  10. 使用Quaternion.LookRotation计算骨骼朝向
  11. 根据实际场景调整位置缩放系数
  12. 建议的骨骼层级关系:
    Hips (root)
    ├─ LeftUpLeg
    ├─ RightUpLeg
    └─ Spine
    ├─ Neck
    ├─ LeftArm
    └─ RightArm
  13. 校准建议:
  14. 在Unity中创建校准场景
  15. 添加位置/旋转偏移量补偿
  16. 实现动态缩放系数调整

实际使用时需要: 1. 在Unity Avatar中正确设置Humanoid骨骼映射 2. 根据摄像头分辨率调整坐标缩放参数 3. 添加数据平滑滤波器(移动平均/Kalman滤波) 4. 实现断线重连机制 5. 添加调试可视化(显示原始关键点数据)

Q:unity中怎样直接控制avatar做动作 在Unity中直接控制Avatar动作主要通过操作骨骼的Transform来实现,以下是详细的实现方法和步骤:


1. 基础准备

1.1 配置Humanoid Avatar

  1. 导入模型后,在模型导入设置中:
  2. 选择 Rig 标签
  3. Animation Type: Humanoid
  4. 点击 Configure 检查骨骼映射
  5. 确保所有关键骨骼(如Hips、Spine、Legs等)正确映射

1.2 创建空物体控制器

public class AvatarController : MonoBehaviour
{
    // 通过Unity Inspector绑定骨骼
    public Transform hips;
    public Transform spine;
    public Transform leftUpperLeg;
    public Transform rightUpperLeg;
    public Transform neck;
    // 其他需要控制的骨骼...
}

2. 直接骨骼控制核心逻辑

2.1 位置控制(适用于根节点)

void UpdateHipsPosition(Vector3 newPosition)
{
    // 保持Y轴高度(根据需求调整)
    Vector3 adjustedPos = new Vector3(
        newPosition.x * positionScale,
        Mathf.Clamp(newPosition.y, minHeight, maxHeight),
        newPosition.z * positionScale
    );
    hips.localPosition = Vector3.Lerp(hips.localPosition, adjustedPos, 0.2f);
}

2.2 旋转控制(关节控制关键)

void UpdateLimbRotation(Transform upperJoint, Transform lowerJoint, Vector3 jointPos, Vector3 nextJointPos)
{
    // 计算骨骼方向
    Vector3 boneDirection = (nextJointPos - jointPos).normalized;

    // 计算旋转(使用LookRotation+轴调整)
    if(boneDirection != Vector3.zero)
    {
        Quaternion targetRot = Quaternion.LookRotation(boneDirection, Vector3.up);

        // 调整轴向(根据模型骨骼方向)
        targetRot *= Quaternion.Euler(90, 0, 0); // 示例修正

        upperJoint.rotation = Quaternion.Slerp(
            upperJoint.rotation,
            targetRot,
            rotationSmoothness * Time.deltaTime
        );
    }
}

2.3 脊柱链式控制

void UpdateSpine(Vector3 hipPos, Vector3 spinePos, Vector3 neckPos)
{
    // 臀部到脊柱的旋转
    UpdateLimbRotation(hips, spine, hipPos, spinePos);

    // 脊柱到脖子的旋转
    UpdateLimbRotation(spine, neck, spinePos, neckPos);
}

3. 完整数据驱动示例

public class MotionCaptureDriver : MonoBehaviour
{
    // 参数调整
    [Header(&quot;Settings&quot;)]
    [Range(0.1f, 2f)] public float positionScale = 1.5f;
    [Range(0.1f, 5f)] public float rotationSmoothness = 2f;

    void ProcessMotionData(PoseData data)
    {
        // 根节点位置
        UpdateHipsPosition(data.hipCenter);

        // 左腿
        UpdateLimbRotation(
            leftUpperLeg, 
            leftLowerLeg, 
            data.leftHip, 
            data.leftKnee
        );

        // 右腿
        UpdateLimbRotation(
            rightUpperLeg,
            rightLowerLeg,
            data.rightHip,
            data.rightKnee
        );

        // 脊柱链
        UpdateSpineChain(
            data.hipCenter,
            data.spine,
            data.neck
        );

        // 手臂控制(同理)
        // ...
    }
}

4. 高级控制技巧

4.1 逆向动力学(IK)控制

// 在Animator上添加IK控制
void OnAnimatorIK(int layerIndex)
{
    if(useFootIK)
    {
        // 左脚IK
        animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1);
        animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1);
        animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootTarget.position);
        animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootTarget.rotation);

        // 同样处理右脚...
    }
}

4.2 数据平滑处理

// 使用低通滤波器
Vector3 LowPassFilter(Vector3 newValue, Vector3 previousValue)
{
    return Vector3.Lerp(previousValue, newValue, 0.3f);
}

// 使用在关键数据上
smoothedHipPosition = LowPassFilter(rawHipPosition, smoothedHipPosition);

4.3 运动重定向校准

IEnumerator CalibrationRoutine()
{
    // 记录初始偏移
    Vector3 initialHipOffset = hips.position - rawData.hipCenter;

    // 等待3秒校准时间
    yield return new WaitForSeconds(3);

    // 应用永久性偏移
    permanentOffset = initialHipOffset;
}

5. 调试与优化

5.1 可视化调试

void OnDrawGizmos()
{
    // 绘制骨骼连线
    Gizmos.color = Color.red;
    Gizmos.DrawLine(hips.position, spine.position);
    Gizmos.DrawLine(spine.position, neck.position);

    // 绘制关键点
    if(showRawData)
    {
        Gizmos.color = Color.green;
        Gizmos.DrawSphere(rawHipPosition, 0.1f);
    }
}

5.2 性能优化

  1. 使用 Job System + Burst Compiler 处理数据
  2. 控制Update频率: csharp [SerializeField] int updateInterval = 2; // 每2帧更新一次 void Update() { if(Time.frameCount % updateInterval == 0) { UpdateAvatar(); } }

6. 常见问题解决

  1. 骨骼翻转问题
  2. LookRotation后添加轴向修正: csharp targetRotation *= Quaternion.Euler(90, 0, 0); // 根据实际模型调整
  3. 动作抖动csharp // 添加阈值过滤 if((newPosition - currentPosition).magnitude > 0.01f) { ApplyPosition(newPosition); }
  4. 比例不匹配csharp // 动态缩放计算 float CalculateScaleRatio(Vector3 realWorldShoulderWidth) { float modelWidth = Vector3.Distance(leftShoulder.position, rightShoulder.position); return modelWidth / realWorldShoulderWidth.magnitude; }

通过以上方法,可以实现从MediaPipe数据到Unity Avatar的精准动作控制。实际开发时需要根据具体模型比例和动作需求调整参数,建议配合Unity的Humanoid重定向功能实现更好的跨模型兼容性。

Q:将动作数据应用到ik系统,实现平滑自然的效果,有什么要点方法

将动作数据应用到IK系统实现平滑自然效果的要点与方法

在Unity中通过IK系统实现平滑自然的动作效果,需要结合数据预处理、IK参数调节、运动学协调等多个环节。以下是关键步骤与实施方法:


1. 选择合适的IK系统

  • Unity内置Animator IK
  • 适用于Humanoid Avatar,通过OnAnimatorIK回调控制。
  • 支持设置AvatarIKGoal(如手、脚)和AvatarIKHint(如肘、膝)的权重与目标位置。
  • 优点:集成方便,适合基础需求。
  • 缺点:功能较为基础,复杂动作需结合其他逻辑。
  • 第三方插件(如Final IK)
  • 提供更强大的IK解算器(如CCD、FABRIK)。
  • 支持全身IK、骨骼链约束等高级功能。
  • 适用场景:复杂动作(攀爬、抓取)或高精度需求。

2. 数据预处理:减少抖动与噪声

  • 低通滤波csharp Vector3 LowPassFilter(Vector3 rawData, Vector3 prevData, float smoothFactor = 0.2f) { return Vector3.Lerp(prevData, rawData, smoothFactor); }
  • 对关节位置和旋转进行平滑处理,避免数据突变。
  • 可见性过滤csharp if (landmark.visibility < 0.5f) return; // 忽略低置信度数据
  • 速度限制csharp Vector3 ClampVelocity(Vector3 target, Vector3 current, float maxSpeed) { Vector3 direction = target - current; if (direction.magnitude > maxSpeed) { return current + direction.normalized * maxSpeed; } return target; }

3. 坐标系与比例校准

  • 坐标系对齐
  • MediaPipe使用右手坐标系(Y轴向下),Unity为左手坐标系(Y轴向上,Z轴向前)。
  • 转换公式: csharp Vector3 ConvertToUnitySpace(Vector3 mediapipePoint) { return new Vector3( mediapipePoint.x, -mediapipePoint.y, // Y轴反转 -mediapipePoint.z // Z轴反转 ) * scaleFactor; // 根据场景调整缩放系数 }
  • 动态比例校准csharp // 校准步骤:用户摆出T-Pose时计算缩放系数 float CalculateScaleFactor(Vector3 realWorldShoulderWidth) { Vector3 modelShoulderWidth = rightShoulder.position - leftShoulder.position; return modelShoulderWidth.magnitude / realWorldShoulderWidth.magnitude; }

4. IK权重与过渡控制

  • 渐进式权重调整csharp float ikWeight = Mathf.Clamp01(Vector3.Distance(currentPos, targetPos) * 0.5f); animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, ikWeight); animator.SetIKPosition(AvatarIKGoal.LeftHand, targetPos);
  • 根据目标距离动态调整权重,避免动作突变。
  • 平滑插值csharp currentPos = Vector3.Lerp(currentPos, targetPos, 0.1f); currentRot = Quaternion.Slerp(currentRot, targetRot, 0.1f);

5. 身体协调与约束

  • 脊柱与头部跟随 void UpdateBodyOrientation(Vector3 headPosition) { // 身体朝向跟随头部 Vector3 lookDirection = headPosition - spine.position; spine.rotation = Quaternion.LookRotation(lookDirection, Vector3.up);</p> <p>// 头部微调 neck.rotation = Quaternion.Lerp(neck.rotation, targetHeadRot, 0.3f); }
  • 关节旋转约束csharp // 限制肘部弯曲角度(示例) float ClampElbowAngle(float angle) { return Mathf.Clamp(angle, 20f, 160f); // 限制在20°~160° }

6. 高级技巧:逆向动力学与正向动力学结合

  • 混合使用IK与FK
  • 使用IK控制手脚位置,FK控制躯干旋转。
  • 示例代码: csharp void UpdateArm(bool useIK) { if (useIK) { // IK模式:控制手部位置 animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position); } else { // FK模式:直接设置关节旋转 upperArm.rotation = CalculateArmRotation(); } }
  • 脚部地面适配csharp void AdjustFootToGround(Transform foot, Vector3 targetPos) { if (Physics.Raycast(targetPos + Vector3.up, Vector3.down, out RaycastHit hit, 2f)) { foot.position = hit.point + Vector3.up * 0.1f; // 保持轻微悬空 foot.rotation = Quaternion.FromToRotation(foot.up, hit.normal) * foot.rotation; } }

7. 调试与优化

  • 可视化调试csharp void OnDrawGizmos() { Gizmos.color = Color.blue; Gizmos.DrawSphere(leftHandTarget.position, 0.1f); Gizmos.DrawLine(leftShoulder.position, leftHandTarget.position); }
  • 性能优化
  • 降低更新频率csharp [SerializeField] int updateInterval = 2; // 每2帧更新一次 void Update() { if (Time.frameCount % updateInterval == 0) { UpdateIK(); } }
  • 使用Job System并行处理csharp // 定义IJobParallelFor处理多关节计算 public struct IKJob : IJobParallelFor { public NativeArray<Vector3> positions; public void Execute(int index) { // 并行计算每个关节的目标位置 } }

8. 常见问题解决

  • 动作抖动
  • 增加数据平滑系数,或添加移动平均滤波。
  • 限制最小移动距离(如位移小于0.01单位时不更新)。
  • 骨骼扭曲
  • 调整轴向修正参数: csharp targetRotation *= Quaternion.Euler(90, 0, 0); // 根据模型调整
  • 穿透问题
  • 添加碰撞检测,限制关节位置: csharp if (Physics.CheckSphere(targetPos, 0.1f)) { targetPos = FindNearestValidPosition(targetPos); }

总结

实现平滑自然的IK动作需要: 1. 数据清洗:滤波与校准消除噪声。 2. 权重控制:渐进式过渡避免突变。 3. 运动协调:结合IK与FK保持身体平衡。 4. 约束优化:限制关节范围与物理检测。 5. 高效计算:通过并行处理与频率控制优化性能。

通过逐步调试参数(如平滑系数、权重过渡速度)并配合可视化工具,最终可实现接近真实人体运动的自然效果。

此条目发表在Media Pipe分类目录。将固定链接加入收藏夹。