<think>根据提供的错误信息,主要问题在于类型不匹配和属性访问错误。以下是修复方案:
1. **类型转换错误**:`ProcessPoseResults` 方法中,`result.poseLandmarks[targetPlayerIndex]` 返回的是 `NormalizedLandmarks` 结构体,而 `CalculatePoseConfidence` 方法期望接收 `NormalizedLandmarks` 类型,但之前错误地使用了 `List<NormalizedLandmark>`。现在需要调整 `CalculatePoseConfidence` 方法的参数类型。
2. **空值检查错误**:在 `CalculatePoseConfidence` 方法中,不能使用 `== null` 来检查 `NormalizedLandmarks` 结构体(因为它是值类型)。应该检查其内部的 `landmarks` 列表是否为 null。
3. **属性访问错误**:`NormalizedLandmark` 结构体中的可见性属性是 `visibility`(小写),而不是 `Visibility`(大写)。因此需要将 `landmark.Visibility` 改为 `landmark.visibility`。
### 修复后的 PoseProcessor 代码
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Tasks.Vision.PoseLandmarker;
using Mediapipe.Unity.Sample.PoseLandmarkDetection;
using Mediapipe.Tasks.Components.Containers;
public class PoseProcessor : MonoBehaviour
{
[Header("组件引用")]
public PoseLandmarkerRunner poseRunner;
public KickDetection kickDetector;
[Header("检测设置")]
public int targetPlayerIndex = 0; // 默认追踪第一位检测到的玩家
public float minDetectionConfidence = 0.5f;
void Start()
{
// 获取场景中的PoseLandmarkerRunner
if (poseRunner == null)
{
poseRunner = FindObjectOfType<PoseLandmarkerRunner>();
}
// 初始化踢球检测器
if (kickDetector == null)
{
kickDetector = gameObject.AddComponent<KickDetection>();
}
// 订阅姿态检测事件
if (poseRunner != null)
{
poseRunner.OnPoseLandmarkerOutput += ProcessPoseResults;
}
else
{
Debug.LogError("PoseLandmarkerRunner not found in scene!");
}
}
private void ProcessPoseResults(PoseLandmarkerResult result)
{
// 使用正确的空值检查方式
if (result.Equals(default(PoseLandmarkerResult)) ||
result.poseLandmarks == null ||
result.poseLandmarks.Count == 0)
{
return;
}
// 获取目标玩家的关键点
var landmarks = result.poseLandmarks[targetPlayerIndex]; // 注意:这里已经是NormalizedLandmarks类型
// 计算姿态置信度(使用关键点的平均可见性)
float poseConfidence = CalculatePoseConfidence(landmarks);
// 检查置信度
if (poseConfidence < minDetectionConfidence)
{
return;
}
// 检测踢球动作
bool isKicking = kickDetector.IsKicking(landmarks); // 直接传递NormalizedLandmarks
if (isKicking)
{
// 触发踢球事件
Debug.Log("KickDetected");
Debug.Log($"踢球动作检测! 置信度: {poseConfidence:F2}");
// 使用事件系统
KickEventManager.TriggerKickEvent();
}
}
// 计算姿态置信度(基于关键点可见性) - 参数类型修正为NormalizedLandmarks
private float CalculatePoseConfidence(NormalizedLandmarks landmarks)
{
// 检查landmarks结构体中的landmarks列表是否为null或空
if (landmarks.landmarks == null || landmarks.landmarks.Count == 0)
return 0f;
float totalVisibility = 0f;
foreach (var landmark in landmarks.landmarks)
{
// 使用小写visibility属性
totalVisibility += landmark.visibility ?? 0f; // 如果visibility为null,则视为0
}
return totalVisibility / landmarks.landmarks.Count;
}
void OnDestroy()
{
// 取消订阅防止内存泄漏
if (poseRunner != null)
{
poseRunner.OnPoseLandmarkerOutput -= ProcessPoseResults;
}
}
}
```
### 修复后的 KickDetection 代码(适配 NormalizedLandmarks)
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Tasks.Components.Containers;
public class KickDetection : MonoBehaviour
{
[Header("检测参数")]
[Tooltip("腿部角度阈值(度)")]
public float legAngleThreshold = 45f;
[Tooltip("最小踢腿速度(m/s)")]
public float minKickSpeed = 2.0f;
[Tooltip("最低踢腿高度(标准化值)")]
public float minKickHeight = 0.3f;
[Tooltip("动作冷却时间(秒)")]
public float cooldownTime = 0.5f;
// BlazePose关键点索引
private const int LEFT_HIP = 23;
private const int RIGHT_HIP = 24;
private const int LEFT_KNEE = 25;
private const int RIGHT_KNEE = 26;
private const int LEFT_ANKLE = 27;
private const int RIGHT_ANKLE = 28;
private Vector3 prevRightAnklePos;
private Vector3 prevLeftAnklePos;
private float lastKickTime;
public bool IsKicking(NormalizedLandmarks landmarks)
{
// 检查landmarks结构体中的landmarks列表
if (landmarks.landmarks == null || landmarks.landmarks.Count < 29)
return false;
// 获取关键点列表
var landmarkList = landmarks.landmarks;
// 计算腿部角度
float leftLegAngle = CalculateLegAngle(
landmarkList[LEFT_HIP],
landmarkList[LEFT_KNEE],
landmarkList[LEFT_ANKLE]);
float rightLegAngle = CalculateLegAngle(
landmarkList[RIGHT_HIP],
landmarkList[RIGHT_KNEE],
landmarkList[RIGHT_ANKLE]);
// 计算脚踝速度
Vector3 currentRightAnklePos = LandmarkToVector(landmarkList[RIGHT_ANKLE]);
Vector3 currentLeftAnklePos = LandmarkToVector(landmarkList[LEFT_ANKLE]);
float rightAnkleSpeed = (prevRightAnklePos != Vector3.zero)
? Vector3.Distance(currentRightAnklePos, prevRightAnklePos) / Time.deltaTime
: 0f;
float leftAnkleSpeed = (prevLeftAnklePos != Vector3.zero)
? Vector3.Distance(currentLeftAnklePos, prevLeftAnklePos) / Time.deltaTime
: 0f;
prevRightAnklePos = currentRightAnklePos;
prevLeftAnklePos = currentLeftAnklePos;
// 检测脚踝高度
float rightAnkleHeight = landmarkList[RIGHT_ANKLE].y; // 注意:小写y
float leftAnkleHeight = landmarkList[LEFT_ANKLE].y;
// 踢球条件判断
bool isRightKick = rightLegAngle > legAngleThreshold &&
rightAnkleSpeed > minKickSpeed &&
rightAnkleHeight > minKickHeight;
bool isLeftKick = leftLegAngle > legAngleThreshold &&
leftAnkleSpeed > minKickSpeed &&
leftAnkleHeight > minKickHeight;
bool isKicking = (isRightKick || isLeftKick) &&
Time.time - lastKickTime > cooldownTime;
if (isKicking)
{
lastKickTime = Time.time;
string kickSide = isRightKick ? "右脚" : "左脚";
Debug.Log($"踢球动作检测! ({kickSide}) 角度:{(isRightKick ? rightLegAngle : leftLegAngle)} " +
$"速度:{(isRightKick ? rightAnkleSpeed : leftAnkleSpeed):F1}m/s " +
$"高度:{(isRightKick ? rightAnkleHeight : leftAnkleHeight):F2}");
}
return isKicking;
}
// 将NormalizedLandmark转换为Unity Vector3
private Vector3 LandmarkToVector(NormalizedLandmark landmark)
{
return new Vector3(landmark.x, landmark.y, landmark.z); // 注意:小写x, y, z
}
private float CalculateLegAngle(NormalizedLandmark hip,
NormalizedLandmark knee,
NormalizedLandmark ankle)
{
Vector3 hipPos = LandmarkToVector(hip);
Vector3 kneePos = LandmarkToVector(knee);
Vector3 anklePos = LandmarkToVector(ankle);
Vector3 thigh = kneePos - hipPos;
Vector3 shin = anklePos - kneePos;
return Vector3.Angle(thigh, shin);
}
// 初始化位置
void Start()
{
prevRightAnklePos = Vector3.zero;
prevLeftAnklePos = Vector3.zero;
}
}
```
### 修改点总结
1. **类型匹配**:
- 将 `CalculatePoseConfidence` 和 `IsKicking` 方法的参数类型从 `IList<NormalizedLandmark>` 或 `NormalizedLandmarks` 统一调整为 `NormalizedLandmarks` 结构体。
- 在 `ProcessPoseResults` 中,直接传递 `result.poseLandmarks[targetPlayerIndex]`(它是 `NormalizedLandmarks` 类型)给这两个方法。
2. **空值检查**:
- 在 `CalculatePoseConfidence` 方法中,检查 `landmarks.landmarks` 是否为 null 或空列表(而不是检查结构体本身,因为结构体不可能为 null)。
3. **属性访问**:
- 将 `landmark.Visibility` 改为 `landmark.visibility`(小写)。
- 将 `landmark.X`/`Y`/`Z` 改为 `landmark.x`/`y`/`z`(小写)。
4. **可见性处理**:
- 由于 `visibility` 是可为 null 的 float,在累加时使用 `??` 运算符提供默认值 0。
5. **坐标访问**:
- 在 `KickDetection` 中,访问关键点的坐标时使用小写属性(如 `landmark.x` 而不是 `landmark.X`)。
### 注意事项
- **结构体与空值**:`NormalizedLandmarks` 是值类型,不能与 null 比较。因此,在 `CalculatePoseConfidence` 方法中,我们检查其内部的 `landmarks` 列表。
- **属性大小写**:确保使用正确的属性大小写(根据提供的 `NormalizedLandmark` 定义,所有属性都是小写)。
§§相关问题§§
1. 如果某些关键点的可见性(visibility)为null,应如何处理才能避免计算错误?
2. 在多人场景中,如何动态选择目标玩家(targetPlayerIndex)以提高动作检测的准确性?
3. 如何优化关键点数据的处理以减少GC(垃圾回收)压力?
4. 当使用世界坐标(poseWorldLandmarks)时,踢球检测算法需要做哪些调整?
5. 如何设计一个校准阶段来自动调整踢球检测的阈值参数(如角度、速度、高度)?
</think>
### 修复后的完整代码
根据提供的 `NormalizedLandmarks` 定义,以下是修复后的完整代码:
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Tasks.Vision.PoseLandmarker;
using Mediapipe.Unity.Sample.PoseLandmarkDetection;
using Mediapipe.Tasks.Components.Containers;
public class PoseProcessor : MonoBehaviour
{
[Header("组件引用")]
public PoseLandmarkerRunner poseRunner;
public KickDetection kickDetector;
[Header("检测设置")]
public int targetPlayerIndex = 0; // 默认追踪第一位检测到的玩家
public float minDetectionConfidence = 0.5f;
void Start()
{
// 获取场景中的PoseLandmarkerRunner
if (poseRunner == null)
{
poseRunner = FindObjectOfType<PoseLandmarkerRunner>();
}
// 初始化踢球检测器
if (kickDetector == null)
{
kickDetector = gameObject.AddComponent<KickDetection>();
}
// 订阅姿态检测事件
if (poseRunner != null)
{
poseRunner.OnPoseLandmarkerOutput += ProcessPoseResults;
}
else
{
Debug.LogError("PoseLandmarkerRunner not found in scene!");
}
}
private void ProcessPoseResults(PoseLandmarkerResult result)
{
// 使用正确的空值检查方式
if (result.Equals(default(PoseLandmarkerResult)) ||
result.poseLandmarks == null ||
result.poseLandmarks.Count == 0)
{
return;
}
// 获取目标玩家的关键点(直接使用NormalizedLandmarks类型)
NormalizedLandmarks landmarks = result.poseLandmarks[targetPlayerIndex];
// 计算姿态置信度(使用关键点的平均可见性)
float poseConfidence = CalculatePoseConfidence(landmarks);
// 检查置信度
if (poseConfidence < minDetectionConfidence)
{
return;
}
// 检测踢球动作(直接传递NormalizedLandmarks)
bool isKicking = kickDetector.IsKicking(landmarks);
if (isKicking)
{
// 触发踢球事件
Debug.Log("KickDetected");
Debug.Log($"踢球动作检测! 置信度: {poseConfidence:F2}");
// 使用事件系统
KickEventManager.TriggerKickEvent();
}
}
// 计算姿态置信度(基于关键点可见性)
private float CalculatePoseConfidence(NormalizedLandmarks landmarks)
{
// 检查landmarks结构体中的landmarks列表
if (landmarks.landmarks == null || landmarks.landmarks.Count == 0)
return 0f;
float totalVisibility = 0f;
foreach (var landmark in landmarks.landmarks)
{
// 使用小写visibility属性(根据定义)
totalVisibility += landmark.visibility ?? 0f; // 处理null值
}
return totalVisibility / landmarks.landmarks.Count;
}
void OnDestroy()
{
// 取消订阅防止内存泄漏
if (poseRunner != null)
{
poseRunner.OnPoseLandmarkerOutput -= ProcessPoseResults;
}
}
}
// 简单的事件管理器实现
public static class KickEventManager
{
public delegate void KickAction();
public static event KickAction OnKickDetected;
public static void TriggerKickEvent()
{
OnKickDetected?.Invoke();
}
}
```
### 修复后的KickDetection.cs
```csharp
using UnityEngine;
using System.Collections.Generic;
using Mediapipe.Tasks.Components.Containers;
public class KickDetection : MonoBehaviour
{
[Header("检测参数")]
[Tooltip("腿部角度阈值(度)")]
public float legAngleThreshold = 45f;
[Tooltip("最小踢腿速度(m/s)")]
public float minKickSpeed = 2.0f;
[Tooltip("最低踢腿高度(标准化值)")]
public float minKickHeight = 0.3f;
[Tooltip("动作冷却时间(秒)")]
public float cooldownTime = 0.5f;
// BlazePose关键点索引
private const int LEFT_HIP = 23;
private const int RIGHT_HIP = 24;
private const int LEFT_KNEE = 25;
private const int RIGHT_KNEE = 26;
private const int LEFT_ANKLE = 27;
private const int RIGHT_ANKLE = 28;
private Vector3 prevRightAnklePos;
private Vector3 prevLeftAnklePos;
private float lastKickTime;
public bool IsKicking(NormalizedLandmarks landmarks)
{
// 检查landmarks结构体中的landmarks列表
if (landmarks.landmarks == null || landmarks.landmarks.Count < 29)
return false;
// 获取关键点列表
List<NormalizedLandmark> landmarkList = landmarks.landmarks;
// 计算腿部角度
float leftLegAngle = CalculateLegAngle(
landmarkList[LEFT_HIP],
landmarkList[LEFT_KNEE],
landmarkList[LEFT_ANKLE]);
float rightLegAngle = CalculateLegAngle(
landmarkList[RIGHT_HIP],
landmarkList[RIGHT_KNEE],
landmarkList[RIGHT_ANKLE]);
// 计算脚踝速度
Vector3 currentRightAnklePos = LandmarkToVector(landmarkList[RIGHT_ANKLE]);
Vector3 currentLeftAnklePos = LandmarkToVector(landmarkList[LEFT_ANKLE]);
float rightAnkleSpeed = (prevRightAnklePos != Vector3.zero)
? Vector3.Distance(currentRightAnklePos, prevRightAnklePos) / Time.deltaTime
: 0f;
float leftAnkleSpeed = (prevLeftAnklePos != Vector3.zero)
? Vector3.Distance(currentLeftAnklePos, prevLeftAnklePos) / Time.deltaTime
: 0f;
prevRightAnklePos = currentRightAnklePos;
prevLeftAnklePos = currentLeftAnklePos;
// 检测脚踝高度(使用小写属性名)
float rightAnkleHeight = landmarkList[RIGHT_ANKLE].y;
float leftAnkleHeight = landmarkList[LEFT_ANKLE].y;
// 踢球条件判断
bool isRightKick = rightLegAngle > legAngleThreshold &&
rightAnkleSpeed > minKickSpeed &&
rightAnkleHeight > minKickHeight;
bool isLeftKick = leftLegAngle > legAngleThreshold &&
leftAnkleSpeed > minKickSpeed &&
leftAnkleHeight > minKickHeight;
bool isKicking = (isRightKick || isLeftKick) &&
Time.time - lastKickTime > cooldownTime;
if (isKicking)
{
lastKickTime = Time.time;
string kickSide = isRightKick ? "右脚" : "左脚";
Debug.Log($"踢球动作检测! ({kickSide}) 角度:{(isRightKick ? rightLegAngle : leftLegAngle)} " +
$"速度:{(isRightKick ? rightAnkleSpeed : leftAnkleSpeed):F1}m/s " +
$"高度:{(isRightKick ? rightAnkleHeight : leftAnkleHeight):F2}");
}
return isKicking;
}
// 将NormalizedLandmark转换为Unity Vector3(使用小写属性名)
private Vector3 LandmarkToVector(NormalizedLandmark landmark)
{
return new Vector3(landmark.x, landmark.y, landmark.z);
}
private float CalculateLegAngle(NormalizedLandmark hip,
NormalizedLandmark knee,
NormalizedLandmark ankle)
{
Vector3 hipPos = LandmarkToVector(hip);
Vector3 kneePos = LandmarkToVector(knee);
Vector3 anklePos = LandmarkToVector(ankle);
Vector3 thigh = kneePos - hipPos;
Vector3 shin = anklePos - kneePos;
return Vector3.Angle(thigh, shin);
}
// 初始化位置
void Start()
{
prevRightAnklePos = Vector3.zero;
prevLeftAnklePos = Vector3.zero;
}
}
```
### 错误修复说明
1. **类型转换问题**:
```csharp
// 错误:尝试将List<NormalizedLandmark>转换为NormalizedLandmarks
// 修复:直接使用NormalizedLandmarks类型
NormalizedLandmarks landmarks = result.poseLandmarks[targetPlayerIndex];
```
2. **属性访问错误**:
```csharp
// 错误:使用大写的Visibility属性
// 修复:使用小写visibility(根据NormalizedLandmark定义)
totalVisibility += landmark.visibility ?? 0f; // 处理可能为null的情况
```
3. **空值检查错误**:
```csharp
// 错误:使用== null检查值类型
// 修复:检查结构体内部的landmarks列表
if (landmarks.landmarks == null || landmarks.landmarks.Count == 0)
return 0f;
```
4. **坐标访问错误**:
```csharp
// 错误:使用大写的X/Y/Z属性
// 修复:使用小写x/y/z(根据NormalizedLandmark定义)
return new Vector3(landmark.x, landmark.y, landmark.z);
```
5. **方法参数类型不匹配**:
```csharp
// 错误:传递List<NormalizedLandmark>给期望NormalizedLandmarks的方法
// 修复:直接传递NormalizedLandmarks结构体
bool isKicking = kickDetector.IsKicking(landmarks);
```
### 关键点说明
根据 `NormalizedLandmarks` 定义:
1. `NormalizedLandmarks` 是一个结构体,包含一个 `landmarks` 属性(`List<NormalizedLandmark>`)
2. `NormalizedLandmark` 包含小写属性:`x`, `y`, `z`, `visibility`
3. `visibility` 是可为空的 `float?` 类型
### 使用建议
1. **调试关键点数据**:
```csharp
private void DebugLandmarkInfo(NormalizedLandmarks landmarks)
{
if (landmarks.landmarks == null || landmarks.landmarks.Count == 0) return;
var firstLandmark = landmarks.landmarks[0];
Debug.Log($"第一个关键点: X={firstLandmark.x}, Y={firstLandmark.y}, " +
$"可见性={firstLandmark.visibility}");
}
```
2. **处理可见性为null的情况**:
```csharp
// 在计算置信度时处理null值
float visibilityValue = landmark.visibility ?? 0f;
```
3. **优化性能**:
```csharp
// 避免在每帧创建新列表
private List<NormalizedLandmark> tempLandmarkList = new List<NormalizedLandmark>();
public bool IsKicking(NormalizedLandmarks landmarks)
{
if (landmarks.landmarks == null) return false;
// 复用列表避免内存分配
tempLandmarkList.Clear();
tempLandmarkList.AddRange(landmarks.landmarks);
// 使用tempLandmarkList进行检测...
}
```
4. **添加安全检查**:
```csharp
private float CalculateLegAngle(NormalizedLandmark hip,
NormalizedLandmark knee,
NormalizedLandmark ankle)
{
// 添加空值检查
if (hip == null || knee == null || ankle == null) return 0f;
// 计算逻辑...
}
```
5. **处理多人场景**:
```csharp
private void ProcessPoseResults(PoseLandmarkerResult result)
{
if (result.poseLandmarks == null) return;
for (int i = 0; i < result.poseLandmarks.Count; i++)
{
var landmarks = result.poseLandmarks[i];
float confidence = CalculatePoseConfidence(landmarks);
if (confidence > minDetectionConfidence)
{
bool isKicking = kickDetector.IsKicking(landmarks);
// 处理每个玩家的动作...
}
}
}
```