Fix : 페이셜 노이즈 제거 기능 추가

This commit is contained in:
qsxft258@gmail.com 2026-02-01 12:19:34 +09:00
parent 1b5530240f
commit 76906a6380

View File

@ -31,6 +31,26 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
private string lastProcessedMessage = ""; // 이전 메시지 저장용 private string lastProcessedMessage = ""; // 이전 메시지 저장용
public int LOCAL_PORT = 49983; public int LOCAL_PORT = 49983;
// 데이터 필터링 설정
[Header("Data Filtering")]
[Tooltip("데이터 스무딩/필터링 활성화")]
public bool enableFiltering = true;
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩)")]
[Range(0f, 0.95f)]
public float smoothingFactor = 0.5f;
[Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")]
[Range(1f, 100f)]
public float maxBlendShapeDelta = 30f;
[Tooltip("프레임 간 최대 허용 회전 변화량 (도)")]
[Range(1f, 90f)]
public float maxRotationDelta = 25f;
// 필터링용 이전 값 저장
private Dictionary<string, float> prevBlendShapeValues = new Dictionary<string, float>();
private Dictionary<string, Vector3> prevBoneRotations = new Dictionary<string, Vector3>();
private Vector3 prevHeadPosition = Vector3.zero;
private bool hasFirstFrame = false;
// 성능 최적화를 위한 캐시 // 성능 최적화를 위한 캐시
private Dictionary<string, List<BlendShapeMapping>> blendShapeCache; private Dictionary<string, List<BlendShapeMapping>> blendShapeCache;
private readonly char[] splitEquals = new char[] { '=' }; private readonly char[] splitEquals = new char[] { '=' };
@ -238,9 +258,14 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
// 정규화된 이름으로 캐시 검색 // 정규화된 이름으로 캐시 검색
string normalizedName = NormalizeBlendShapeName(shapeName).ToLowerInvariant(); string normalizedName = NormalizeBlendShapeName(shapeName).ToLowerInvariant();
// 필터링 적용
if (enableFiltering)
{
weight = FilterBlendShapeValue(normalizedName, weight);
}
if (blendShapeCache.TryGetValue(normalizedName, out List<BlendShapeMapping> mappings)) if (blendShapeCache.TryGetValue(normalizedName, out List<BlendShapeMapping> mappings))
{ {
// 캐시에서 찾은 모든 매핑에 대해 weight 설정
foreach (var mapping in mappings) foreach (var mapping in mappings)
{ {
if (mapping.renderer != null) if (mapping.renderer != null)
@ -348,10 +373,18 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
y = -y; // Y축 회전 반전 y = -y; // Y축 회전 반전
} }
Vector3 rotation = new Vector3(x, y, z);
// 본 회전 필터링 적용
if (enableFiltering)
{
rotation = FilterBoneRotation(strArray2[0], rotation);
}
switch (strArray2[0]) switch (strArray2[0])
{ {
case "head" when headBone != null: case "head" when headBone != null:
headBone.localRotation = Quaternion.Euler(x, y, -z); headBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, -rotation.z);
if (headPositionObject != null && commaList.Length >= 6) if (headPositionObject != null && commaList.Length >= 6)
{ {
@ -361,19 +394,24 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
if (mirrorMode) if (mirrorMode)
{ {
posX = -posX; // X축 위치 반전 posX = -posX;
} }
headPositionObject.localPosition = new Vector3(posX, posY, posZ); Vector3 newPos = new Vector3(posX, posY, posZ);
if (enableFiltering)
{
newPos = FilterHeadPosition(newPos);
}
headPositionObject.localPosition = newPos;
} }
break; break;
case "rightEye" when rightEyeBone != null: case "rightEye" when rightEyeBone != null:
rightEyeBone.localRotation = Quaternion.Euler(x, y, z); rightEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z);
break; break;
case "leftEye" when leftEyeBone != null: case "leftEye" when leftEyeBone != null:
leftEyeBone.localRotation = Quaternion.Euler(x, y, z); leftEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z);
break; break;
} }
} }
@ -522,6 +560,82 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
} }
} }
/// <summary>
/// BlendShape 값 필터링: 스파이크 제거 + EMA 스무딩
/// </summary>
float FilterBlendShapeValue(string name, float rawValue)
{
if (prevBlendShapeValues.TryGetValue(name, out float prevValue))
{
float delta = Mathf.Abs(rawValue - prevValue);
// 스파이크 감지: 변화량이 임계값 초과 시 이전 값 유지
if (delta > maxBlendShapeDelta)
{
return prevValue;
}
// EMA 스무딩 적용
float smoothed = Mathf.Lerp(rawValue, prevValue, smoothingFactor);
prevBlendShapeValues[name] = smoothed;
return smoothed;
}
// 첫 프레임은 그대로 저장
prevBlendShapeValues[name] = rawValue;
return rawValue;
}
/// <summary>
/// 본 회전 필터링: 스파이크 제거 + EMA 스무딩
/// </summary>
Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation)
{
if (prevBoneRotations.TryGetValue(boneName, out Vector3 prevRot))
{
float delta = Vector3.Distance(rawRotation, prevRot);
// 스파이크 감지
if (delta > maxRotationDelta)
{
return prevRot;
}
// EMA 스무딩
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, smoothingFactor);
prevBoneRotations[boneName] = smoothed;
return smoothed;
}
prevBoneRotations[boneName] = rawRotation;
return rawRotation;
}
/// <summary>
/// 머리 위치 필터링
/// </summary>
Vector3 FilterHeadPosition(Vector3 rawPos)
{
if (hasFirstFrame)
{
float delta = Vector3.Distance(rawPos, prevHeadPosition);
// 위치 스파이크 감지 (0.1 단위 기준)
if (delta > maxRotationDelta * 0.01f)
{
return prevHeadPosition;
}
Vector3 smoothed = Vector3.Lerp(rawPos, prevHeadPosition, smoothingFactor);
prevHeadPosition = smoothed;
return smoothed;
}
hasFirstFrame = true;
prevHeadPosition = rawPos;
return rawPos;
}
private bool HasBlendShapes(SkinnedMeshRenderer skin) private bool HasBlendShapes(SkinnedMeshRenderer skin)
{ {
if (!skin.sharedMesh) if (!skin.sharedMesh)