Final IK详细实现一——数据通信

为了实现使用MediaPipe实时姿势特征点数据驱动Final IK以达到动作捕捉效果,可以按照以下步骤进行:

步骤概览

  1. 使用MediaPipe获取姿势数据:通过Python脚本捕获摄像头数据,检测姿势关键点。
  2. 数据传输:通过UDP将关键点坐标实时发送至Unity。
  3. Unity接收与处理数据:解析数据并转换为Unity的3D坐标。
  4. 驱动Final IK:将处理后的数据映射到角色骨骼,利用Final IK实现自然运动。

详细步骤与代码示例

1. MediaPipe姿势检测(Python端)

安装所需库:

pip install mediapipe opencv-python

编写Python脚本捕获摄像头数据并发送关键点:

import cv2
import mediapipe as mp
import socket
import json

mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.5)

# UDP配置
UDP_IP = "127.0.0.1"  # Unity所在机器的IP
UDP_PORT = 8051
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

cap = cv2.VideoCapture(0)  # 摄像头设备号

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        continue

    # 转换为RGB并检测姿势
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = pose.process(image)

    if results.pose_landmarks:
        # 提取所有33个关键点的归一化坐标
        landmarks = []
        for landmark in results.pose_landmarks.landmark:
            landmarks.extend([landmark.x, landmark.y, landmark.z])

        # 发送JSON格式数据
        sock.sendto(json.dumps(landmarks).encode(), (UDP_IP, UDP_PORT))

    # 显示画面(可选)
    cv2.imshow('MediaPipe Pose', cv2.flip(frame, 1))
    if cv2.waitKey(5) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()

2. Unity接收数据(C#脚本)

创建UDPReceiver.cs脚本,接收并解析数据:

using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

public class UDPReceiver : MonoBehaviour
{
    public int port = 8051;
    private Thread receiveThread;
    private UdpClient udpClient;
    private bool isReceiving = false;

    // 存储解析后的关键点坐标(33个点,每个点有x,y,z)
    public Vector3[] receivedLandmarks = new Vector3[33];

    void Start()
    {
        StartReceiving();
    }

    void StartReceiving()
    {
        receiveThread = new Thread(new ThreadStart(ReceiveData));
        receiveThread.IsBackground = true;
        isReceiving = true;
        receiveThread.Start();
    }

    private void ReceiveData()
    {
        udpClient = new UdpClient(port);
        while (isReceiving)
        {
            try
            {
                IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, port);
                byte[] data = udpClient.Receive(ref remoteEndPoint);
                string jsonString = System.Text.Encoding.UTF8.GetString(data);
                ParseLandmarks(jsonString);
            }
            catch (Exception e)
            {
                Debug.LogError(e.ToString());
            }
        }
    }

    void ParseLandmarks(string jsonData)
    {
        List<float> values = new List<float>(JsonUtility.FromJson<float[]>(jsonData));
        for (int i = 0; i < 33; i++)
        {
            int index = i * 3;
            float x = values[index];
            float y = 1 - values[index + 1]; // 反转Y轴适配Unity
            float z = values[index + 2];
            receivedLandmarks[i] = new Vector3(x, y, z) * 10; // 缩放坐标(根据场景调整)
        }
    }

    void OnDisable()
    {
        isReceiving = false;
        udpClient.Close();
        receiveThread.Abort();
    }
}

3. 坐标映射与Final IK驱动

在Unity中设置角色模型和Final IK组件:

  1. 角色准备

    • 导入带骨骼的角色模型。
    • 为需要驱动的身体部位(如手、脚)添加Final IK的LimbIK组件,并设置对应的IK目标(空物体)。
  2. 脚本映射关键点
    • 创建IKController.cs脚本,将接收到的关键点数据映射到IK目标:
using UnityEngine;
using RootMotion.FinalIK;

public class IKController : MonoBehaviour
{
    public UDPReceiver udpReceiver;

    // Final IK组件
    public LimbIK leftArmIK;
    public LimbIK rightArmIK;
    public LimbIK leftLegIK;
    public LimbIK rightLegIK;

    // IK目标物体
    public Transform leftHandTarget;
    public Transform rightHandTarget;
    public Transform leftFootTarget;
    public Transform rightFootTarget;

    void Update()
    {
        if (udpReceiver.receivedLandmarks == null) return;

        // 左手腕(MediaPipe索引15)
        leftHandTarget.position = udpReceiver.receivedLandmarks[15];
        // 右手腕(索引16)
        rightHandTarget.position = udpReceiver.receivedLandmarks[16];
        // 左脚踝(索引27)
        leftFootTarget.position = udpReceiver.receivedLandmarks[27];
        // 右脚踝(索引28)
        rightFootTarget.position = udpReceiver.receivedLandmarks[28];

        // 更新IK计算
        leftArmIK.solver.Update();
        rightArmIK.solver.Update();
        leftLegIK.solver.Update();
        rightLegIK.solver.Update();
    }
}
  1. 场景配置
    • UDPReceiver脚本挂载到任意物体(如Camera),并设置port与Python端一致。
    • IKController脚本挂载到角色,并拖拽对应IK组件和Target物体到Inspector面板。

优化与注意事项

  • 坐标系调整:根据角色大小调整缩放系数(如上述代码中的*10),可能需要通过实验确定最佳值。
  • 数据平滑:添加滤波(如指数平滑)减少抖动:
    // 在ParseLandmarks方法中添加滤波
    Vector3 filteredPosition = Vector3.Lerp(receivedLandmarks[i], newPosition, 0.1f);
  • 根骨骼控制:使用髋部关键点(索引23、24)控制角色整体位置,使角色移动。
  • 性能优化:确保Python和Unity的帧率匹配,避免数据堆积。

通过以上步骤,即可实现用MediaPipe实时驱动Final IK的动作捕捉效果。根据实际需求调整关键点映射和IK参数,可获得更自然的动作效果。

此条目发表在Final IK应用分类目录。将固定链接加入收藏夹。