OpenSeeIKTarget is now has tooltips and can interpolate between frames, making motion much smoother. Another coordinate issue was fixed too. OpenSeeExpression can now record additional calibration data for fully recorded expressions.
This commit is contained in:
parent
0d0a30dedb
commit
750f9920e5
2 changed files with 110 additions and 19 deletions
|
|
@ -21,11 +21,15 @@ public class OpenSeeExpression : MonoBehaviour
|
|||
public int expressionStabilizer = 1;
|
||||
[Tooltip("Setting this to a value above 1 will record only every recordingSkip-th frame when capture calibration data. This can be useful with cameras that have a high framerate leading to only a short time for capturing varied expression information. Usually at least 20-30 seconds are required to capture a good variety of angles and expression variation, so this should be set accordingly when using the recording flag. At 1, no frames are skipped.")]
|
||||
public int recordingSkip = 2;
|
||||
[Tooltip("If allowOverRecording is enabled and expression data is recorded beyond 100%, this is used instead of recordingSkip to avoid quickly replacing the existing data.")]
|
||||
public int overRecordingSkip = 5;
|
||||
[Tooltip("This is the filename used for loading and saving expression data using the load and save flags.")]
|
||||
public string filename = "";
|
||||
[Header("Toggles")]
|
||||
[Tooltip("When enabled, calibration data will be collected from the given OpenSee component.")]
|
||||
public bool recording = false;
|
||||
[Tooltip("When enabled, you can keep recording past 100%. When doing so, random frames in the already collected data will be overwritten with new data. To prevent quickly replacing all the collected data, overRecordingSkip is used instead of recordingSkip, which should be set to a higher value.")]
|
||||
public bool allowOverRecording = false;
|
||||
[Tooltip("When enabled, the calibration data collected for the current expression is cleared and this flag is set back to false.")]
|
||||
public bool clear = false;
|
||||
[Tooltip("When enabled, a new prediction model is trained and this flag is set back to false. Only expressions for which percentRecorded is 100% will be included in training.")]
|
||||
|
|
@ -104,6 +108,7 @@ public class OpenSeeExpression : MonoBehaviour
|
|||
private int lastPrediction = -1;
|
||||
private int lastPredictionCount = 0;
|
||||
private int frameCount = 0;
|
||||
private System.Random rnd;
|
||||
|
||||
private void ResetInfo() {
|
||||
recording = false;
|
||||
|
|
@ -134,6 +139,7 @@ public class OpenSeeExpression : MonoBehaviour
|
|||
ResetInfo();
|
||||
expressions = new Dictionary<string, List<float[]>>();
|
||||
model = new SVMModel();
|
||||
rnd = new System.Random();
|
||||
}
|
||||
|
||||
private float[] GetData(OpenSee.OpenSeeData t) {
|
||||
|
|
@ -175,12 +181,16 @@ public class OpenSeeExpression : MonoBehaviour
|
|||
List<float[]> list = expressions[key];
|
||||
if (list.Count == maxSamples)
|
||||
keys.Add(key);
|
||||
else
|
||||
else {
|
||||
Debug.Log("[Training error] Skipping expression " + key + " due to lack of collected data.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int classes = keys.Count;
|
||||
if (classes < 2 || classes > 25)
|
||||
if (classes < 2 || classes > 25) {
|
||||
Debug.Log("[Training error] The number of expressions that can be used for training is " + classes + ", which is either below 2 or higher than 25.");
|
||||
return false;
|
||||
}
|
||||
keys.Sort();
|
||||
classLabels = keys.ToArray();
|
||||
int train_split = maxSamples * 3 / 4;
|
||||
|
|
@ -290,12 +300,19 @@ public class OpenSeeExpression : MonoBehaviour
|
|||
List<float[]> list = expressions[calibrationExpression];
|
||||
if (list.Count >= maxSamples) {
|
||||
percentRecorded = 100f;
|
||||
recording = false;
|
||||
return false;
|
||||
recording = allowOverRecording;
|
||||
if (!recording)
|
||||
return false;
|
||||
}
|
||||
if (frameCount % recordingSkip != 0)
|
||||
int skip = recordingSkip;
|
||||
if (percentRecorded >= 100f)
|
||||
skip = overRecordingSkip;
|
||||
if (frameCount % skip != 0)
|
||||
return true;
|
||||
list.Add(GetData(t));
|
||||
if (percentRecorded >= 100f) {
|
||||
list[rnd.Next(maxSamples)] = GetData(t);
|
||||
} else
|
||||
list.Add(GetData(t));
|
||||
percentRecorded = 100f * (float)list.Count/(float)maxSamples;
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,54 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace OpenSee {
|
||||
|
||||
public class OpenSeeIKTarget : MonoBehaviour
|
||||
{
|
||||
public OpenSee.OpenSee openSee;
|
||||
[Header("Settings")]
|
||||
[Tooltip("This is the source of face tracking data.")]
|
||||
public OpenSee openSee;
|
||||
[Tooltip("When set to true, the next rotation and translation values will be set as zero rotation and zero translation.")]
|
||||
public bool calibrate = true;
|
||||
[Tooltip("This sets which face id to look for in the OpenSee data.")]
|
||||
public int faceId = 0;
|
||||
[Tooltip("Often, the translation vector's scale is too high. Setting this somewhere between 0.05 to 0.3 seems stabilize things, but it also reduces the range of motion.")]
|
||||
public float translationScale = 0.3f;
|
||||
[Tooltip("This smoothed out the detected motion. Values can be between 0 (no smoothing) and 1 (no motion). The appropriate value probably depends on the camera's frame rate.")]
|
||||
public float smooth = 0.2f;
|
||||
double updated = 0.0f;
|
||||
Quaternion dR = Quaternion.Euler(0f, 0f, -90f);
|
||||
Vector3 dT = new Vector3(0f, 0f, 0f);
|
||||
[Tooltip("When enabled, tracking will lag by one video capture frame, but the captured motion information is interpolated between frames, making things look smoother and less jittery and jumpy.")]
|
||||
public bool interpolate = true;
|
||||
[Tooltip("When set to true, the transform will be updated in FixedUpdate, otherwise it will be updated in Update.")]
|
||||
public bool fixedUpdate = false;
|
||||
[Header("Information")]
|
||||
[Tooltip("This is the current rotation calibration value as euler angles.")]
|
||||
public Vector3 rotationOffset = new Vector3(0f, 0f, -90f);
|
||||
[Tooltip("This is the current translation calibration.")]
|
||||
public Vector3 translationOffset = new Vector3(0f, 0f, 0f);
|
||||
[Tooltip("This is the average number of interpolated frames per received tracking data.")]
|
||||
public float averageInterpolations = 0f;
|
||||
|
||||
private double updated = 0.0f;
|
||||
private Quaternion dR = Quaternion.Euler(0f, 0f, -90f);
|
||||
private Vector3 dT = new Vector3(0f, 0f, 0f);
|
||||
|
||||
private int interpolationCount = 1;
|
||||
private int interpolateState = 0;
|
||||
private float avgInterps = 1f;
|
||||
private Quaternion lastR;
|
||||
private Vector3 lastT;
|
||||
private Quaternion currentR;
|
||||
private Vector3 currentT;
|
||||
|
||||
void Interpolate() {
|
||||
if (!interpolate || interpolateState < 2)
|
||||
return;
|
||||
float t = Mathf.Clamp((float)interpolationCount / avgInterps, 0f, 0.985f);
|
||||
transform.localPosition = Vector3.Lerp(lastT, currentT, t);
|
||||
transform.localRotation = Quaternion.Lerp(lastR, currentR, t);
|
||||
interpolationCount++;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
void RunUpdate() {
|
||||
var openSeeData = openSee.trackingData;
|
||||
if (openSeeData == null || openSeeData.Length < 1)
|
||||
return;
|
||||
|
|
@ -28,22 +64,60 @@ public class OpenSeeIKTarget : MonoBehaviour
|
|||
if (openSeeData[idx].time > updated) {
|
||||
updated = openSeeData[idx].time;
|
||||
} else {
|
||||
Interpolate();
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion convertedQuaternion = new Quaternion(-openSeeData[idx].rawQuaternion.y, -openSeeData[idx].rawQuaternion.x, openSeeData[idx].rawQuaternion.z, openSeeData[idx].rawQuaternion.w);
|
||||
Vector3 t = openSeeData[idx].translation;
|
||||
t.x = -t.x;
|
||||
t.z = -t.z;
|
||||
|
||||
if (calibrate) {
|
||||
calibrate = false;
|
||||
dR = convertedQuaternion;
|
||||
dR = Quaternion.Inverse(dR);
|
||||
dT = openSeeData[idx].translation;
|
||||
dT = t;
|
||||
rotationOffset = new Vector3(dR.eulerAngles.x, dR.eulerAngles.y, dR.eulerAngles.z);
|
||||
translationOffset = new Vector3(dT.x, dT.y, dT.z);
|
||||
}
|
||||
|
||||
Vector3 v = openSeeData[idx].translation - dT;
|
||||
v.x = -v.x;
|
||||
v.z = -v.z;
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, v * translationScale, 1f - smooth);
|
||||
transform.localRotation = Quaternion.Lerp(transform.localRotation, convertedQuaternion * dR, 1f - smooth);
|
||||
|
||||
if (interpolateState > 1)
|
||||
avgInterps = Mathf.Lerp(avgInterps, (float)interpolationCount, 0.15f);
|
||||
interpolationCount = 0;
|
||||
averageInterpolations = avgInterps;
|
||||
|
||||
lastT = currentT;
|
||||
lastR = currentR;
|
||||
if (interpolateState > 0) {
|
||||
currentT = Vector3.Lerp(currentT, (t - dT) * translationScale, 1f - smooth);
|
||||
currentR = Quaternion.Lerp(transform.localRotation, convertedQuaternion * dR, 1f - smooth);
|
||||
} else {
|
||||
currentT = (t - dT) * translationScale;
|
||||
currentR = convertedQuaternion * dR;
|
||||
}
|
||||
if (interpolateState < 2)
|
||||
interpolateState++;
|
||||
|
||||
if (interpolate) {
|
||||
Interpolate();
|
||||
} else {
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, (t - dT) * translationScale, 1f - smooth);
|
||||
transform.localRotation = Quaternion.Lerp(transform.localRotation, convertedQuaternion * dR, 1f - smooth);
|
||||
}
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (fixedUpdate)
|
||||
RunUpdate();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!fixedUpdate)
|
||||
RunUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue