First version.

This commit is contained in:
Emiliana 2020-01-01 13:55:01 +01:00
commit 114ab8d44e
24 changed files with 22859 additions and 0 deletions

170
Unity/OpenSee.cs Normal file
View file

@ -0,0 +1,170 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
namespace OpenSee {
public class OpenSee : MonoBehaviour {
[Header("UDP server settings")]
[Tooltip("To listen for remote connections, change this to 0.0.0.0 or your actual IP on the desired interface")]
public string listenAddress = "127.0.0.1";
[Tooltip("This is the port the server will listen for tracking packets on")]
public int listenPort = 11573;
private const int nPoints = 66;
private const int packetFrameSize = 8 + 1 + 4 + 4 * 4 + 3 * 4 + 3 * 4 + 4 * 66 + 4 * 2 * 66 + 4 * 3 * 66;
[Header("Tracking data")]
[Tooltip("This is an informational property that tells you how many packets have been received")]
public int receivedPackets = 0;
[Tooltip("This contains the actual tracking data")]
public OpenSeeData[] trackingData = null;
[System.Serializable]
public class OpenSeeData {
[Tooltip("The time this tracking data was captured at.")]
public double time;
[Tooltip("This field tells you if 3D points have been successfully estimated from the 2D points. If this is false, do not rely on pose or 3D data.")]
public bool got3DPoints;
[Tooltip("This field contains the error for fitting the original 3D points. It shouldn't matter much, but it it is very high, something is probably wrong")]
public float fit3DError;
[Tooltip("This is the rotation vector for the 3D points to turn into the estimated face pose.")]
public Vector3 rotation;
[Tooltip("This is the translation vector for the 3D points to turn into the estimated face pose.")]
public Vector3 translation;
[Tooltip("This is the raw rotation quaternion calculated from the OpenCV rotation matrix. It does not match Unity's coordinate system, but it still might be useful.")]
public Quaternion rawQuaternion;
[Tooltip("This is the raw rotation euler angles calculated by OpenCV from the rotation matrix. It does not match Unity's coordinate system, but it still might be useful.")]
public Vector3 rawEuler;
[Tooltip("This field tells you how certain the tracker is.")]
public float[] confidence;
[Tooltip("These are the detected face landmarks in image coordinates.")]
public Vector2[] points;
[Tooltip("These are 3D points estimated from the 2D points. The should be rotation and translation compensated.")]
public Vector3[] points3D;
public OpenSeeData() {
confidence = new float[nPoints];
points = new Vector2[nPoints];
points3D = new Vector3[nPoints];
}
private float readFloat(byte[] b, ref int o) {
float v = System.BitConverter.ToSingle(b, o);
o += 4;
return v;
}
private Quaternion readQuaternion(byte[] b, ref int o) {
float x = readFloat(b, ref o);
float y = readFloat(b, ref o);
float z = readFloat(b, ref o);
float w = readFloat(b, ref o);
Quaternion q = new Quaternion(x, y, z, w);
return q;
}
private Vector3 readVector3(byte[] b, ref int o) {
Vector3 v = new Vector3(readFloat(b, ref o), -readFloat(b, ref o), readFloat(b, ref o));
return v;
}
private Vector2 readVector2(byte[] b, ref int o) {
Vector2 v = new Vector2(readFloat(b, ref o), readFloat(b, ref o));
return v;
}
public void readFromPacket(byte[] b, int o) {
time = System.BitConverter.ToDouble(b, o);
o += 8;
byte got3D = b[o];
o++;
got3DPoints = false;
if (got3D != 0)
got3DPoints = true;
fit3DError = readFloat(b, ref o);
rawQuaternion = readQuaternion(b, ref o);
Quaternion convertedQuaternion = new Quaternion(-rawQuaternion.x, rawQuaternion.y, -rawQuaternion.z, rawQuaternion.w);
rawEuler = readVector3(b, ref o);
rotation = rawEuler;
rotation.z = (rotation.z + 90) % 360;
rotation.x = -(rotation.x + 180) % 360;
float x = readFloat(b, ref o);
float y = readFloat(b, ref o);
float z = readFloat(b, ref o);
translation = new Vector3(y, -x, z);
for (int i = 0; i < nPoints; i++) {
confidence[i] = readFloat(b, ref o);
}
for (int i = 0; i < nPoints; i++) {
points[i] = readVector2(b, ref o);
}
for (int i = 0; i < nPoints; i++) {
points3D[i] = readVector3(b, ref o);
}
}
}
private Socket socket;
private byte[] buffer;
private Thread receiveThread = null;
private volatile bool stopReception = false;
void performReception() {
EndPoint senderRemote = new IPEndPoint(IPAddress.Any, 0);
while (!stopReception) {
try {
int receivedBytes = socket.ReceiveFrom(buffer, SocketFlags.None, ref senderRemote);
if (receivedBytes < 1 || receivedBytes % packetFrameSize != 0) {
continue;
}
receivedPackets++;
int i = 0;
OpenSeeData[] newData = new OpenSeeData[receivedBytes / packetFrameSize];
for (int offset = 0; offset < receivedBytes; offset += packetFrameSize) {
newData[i] = new OpenSeeData();
newData[i].readFromPacket(buffer, offset);
i++;
}
trackingData = newData;
} catch { }
}
}
void Start () {
buffer = new byte[65535];
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress ip;
IPAddress.TryParse(listenAddress, out ip);
socket.Bind(new IPEndPoint(ip, listenPort));
socket.ReceiveTimeout = 15;
receiveThread = new Thread(() => performReception());
receiveThread.Start();
}
void Update () {
}
void OnApplicationQuit() {
if (receiveThread != null) {
stopReception = true;
receiveThread.Join();
stopReception = false;
}
}
}
}

View file

@ -0,0 +1,150 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
// From: https://stackoverflow.com/a/9164742
// Is this better? https://stackoverflow.com/a/36648201
namespace OpenSee
{
public class Win32 {
[DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
}
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(IntPtr a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
private IntPtr handle;
private bool disposed;
public Job()
{
handle = CreateJobObject(IntPtr.Zero, null);
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
Marshal.FreeHGlobal(extendedInfoPtr);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) { }
Close();
disposed = true;
}
public void Close()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr processHandle)
{
return AssignProcessToJobObject(handle, processHandle);
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public UInt32 LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public Int32 bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion
}

283
Unity/OpenSeeLauncher.cs Normal file
View file

@ -0,0 +1,283 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
namespace OpenSee {
public class OpenSeeLauncher : MonoBehaviour {
[Header("Settings")]
[Tooltip("If this is left empty, it will default to the OpenSee component on the same game object.")]
public OpenSee openSeeTarget;
[Tooltip("The numeric index of the camera as in the list returned by the ListCameras function.")]
public int cameraIndex = -1;
[Tooltip("The path to a video file that should be used as input for the tracker instead of a camera")]
public string videoPath = "";
[Tooltip("Should facetracker.exe be started automatically with the scene?")]
public bool autoStart = false;
[Tooltip("If this is enabled, available cameras are automatically printed to the debug console.")]
public bool autoListCameras = false;
[Tooltip("If this is enabled, no output from the tracker will be printed to the debug console. This cannot be changed while the tracker is running.")]
public bool dontPrint = true;
[Tooltip("The path to \"facetracker.exe\".")]
public string exePath = "facetracker.exe";
[Tooltip("The path, where the .onnx model files can be found")]
public string modelPath = "models\\";
[Tooltip("Additional options that should be passed to the face tracker.")]
public string[] commandlineOptions = new string[] { "--silent", "1", "--max-threads", "2", "--model", "1" };
[Header("Runtime information")]
[Tooltip("This field shows if the tracker is currently alive.")]
public bool trackerAlive = false;
private string ip;
private Process trackerProcess = null;
private StringBuilder trackerSB = null;
private bool dontPrintNow = false;
private Job job = null;
private bool CheckSetup(bool requireTarget) {
if (openSeeTarget == null)
openSeeTarget = GetComponent<OpenSee>();
if (openSeeTarget == null) {
UnityEngine.Debug.LogError("No openSeeTarget is set.");
return false;
}
IPAddress address;
if (!File.Exists(exePath)) {
UnityEngine.Debug.LogError("Facetracker executable cannot be found.");
return false;
}
if (!Directory.Exists(modelPath)) {
UnityEngine.Debug.LogError("Model directory cannot be found.");
return false;
}
if (requireTarget) {
ip = openSeeTarget.listenAddress;
if (!IPAddress.TryParse(ip, out address)) {
UnityEngine.Debug.LogError("No valid IP address was given in the OpenSee component.");
return false;
}
if (openSeeTarget.listenPort < 1 || openSeeTarget.listenPort > 65535) {
UnityEngine.Debug.LogError("No valid port was given in the OpenSee component.");
return false;
}
if (cameraIndex >= 0 && videoPath != "") {
UnityEngine.Debug.LogError("Entering both a camera index and a video filename is not valid.");
return false;
}
if (cameraIndex < 0 && !File.Exists(videoPath)) {
if (videoPath != "") {
UnityEngine.Debug.LogError("The given video file does not exist.");
} else {
UnityEngine.Debug.LogError("No camera or video file was given.");
}
return false;
}
}
if (job == null) {
job = new Job();
}
return true;
}
public string[] ListCameras() {
if (!CheckSetup(false))
return null;
StringBuilder stringBuilder;
ProcessStartInfo processStartInfo;
Process process;
stringBuilder = new StringBuilder();
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.UseShellExecute = false;
processStartInfo.FileName = exePath;
processStartInfo.Arguments = "--list-cameras 2";
process = new Process();
process.StartInfo = processStartInfo;
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) {
stringBuilder.Append(e.Data);
stringBuilder.Append("\n");
});
process.Start();
job.AddProcess(process.Handle);
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
string cameraInfo = stringBuilder.ToString();
string[] cameras = cameraInfo.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
return cameras;
}
// From: http://csharptest.net/529/how-to-correctly-escape-command-line-arguments-in-c/index.html
private static string EscapeArguments(params string[] args)
{
StringBuilder arguments = new StringBuilder();
Regex invalidChar = new Regex("[\x00\x0a\x0d]");// these can not be escaped
Regex needsQuotes = new Regex(@"\s|""");// contains whitespace or two quote characters
Regex escapeQuote = new Regex(@"(\\*)(""|$)");// one or more '\' followed with a quote or end of string
for (int carg = 0; args != null && carg < args.Length; carg++)
{
if (args[carg] == null) { throw new ArgumentNullException("args[" + carg + "]"); }
if (invalidChar.IsMatch(args[carg])) { throw new ArgumentOutOfRangeException("args[" + carg + "]"); }
if (args[carg] == String.Empty) { arguments.Append("\"\""); }
else if (!needsQuotes.IsMatch(args[carg])) { arguments.Append(args[carg]); }
else
{
arguments.Append('"');
arguments.Append(escapeQuote.Replace(args[carg], m => m.Groups[1].Value + m.Groups[1].Value + (m.Groups[2].Value == "\"" ? "\\\"" : "")));
arguments.Append('"');
}
if (carg + 1 < args.Length)
arguments.Append(' ');
}
return arguments.ToString();
}
public bool StartTracker() {
if (!CheckSetup(true))
return false;
ProcessStartInfo processStartInfo;
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.UseShellExecute = false;
processStartInfo.FileName = exePath;
List<string> arguments = new List<string>();
arguments.Add("--ip");
arguments.Add(ip);
arguments.Add("--port");
arguments.Add(openSeeTarget.listenPort.ToString());
arguments.Add("--model-dir");
arguments.Add(modelPath);
arguments.Add("--capture");
if (videoPath != "" && File.Exists(videoPath))
arguments.Add(videoPath);
else
arguments.Add(cameraIndex.ToString());
foreach (string argument in commandlineOptions) {
arguments.Add(argument);
}
string argumentString = EscapeArguments(arguments.ToArray());
processStartInfo.Arguments = argumentString;
if (trackerProcess != null && !trackerProcess.HasExited) {
trackerProcess.CloseMainWindow();
trackerProcess.Close();
if (!dontPrintNow) {
try {
trackerProcess.CancelOutputRead();
trackerProcess.CancelErrorRead();
} catch {}
}
}
trackerSB = new StringBuilder();
trackerProcess = new Process();
trackerProcess.StartInfo = processStartInfo;
dontPrintNow = dontPrint;
if (!dontPrintNow) {
trackerProcess.EnableRaisingEvents = true;
trackerProcess.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) {
trackerSB.Append(e.Data);
trackerSB.Append("\n");
});
trackerProcess.ErrorDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) {
trackerSB.Append(e.Data);
trackerSB.Append("\n");
});
}
trackerProcess.Start();
job.AddProcess(trackerProcess.Handle);
if (!dontPrintNow) {
trackerProcess.BeginOutputReadLine();
trackerProcess.BeginErrorReadLine();
}
return !trackerProcess.HasExited;
}
public void StopTracker() {
if (trackerProcess != null && !trackerProcess.HasExited) {
trackerProcess.CloseMainWindow();
trackerProcess.Close();
if (!dontPrintNow) {
try {
trackerProcess.CancelOutputRead();
trackerProcess.CancelErrorRead();
} catch {}
}
}
trackerProcess = null;
}
public void Start() {
if (autoStart)
StartTracker();
if (autoListCameras) {
string[] cameras = ListCameras();
if (cameras == null || cameras.Length < 1) {
UnityEngine.Debug.Log("No cameras found.");
return;
}
UnityEngine.Debug.Log("Cameras:");
for (int i = 0; i < cameras.Length; i++) {
UnityEngine.Debug.Log(i + " = " + cameras[i]);
}
}
}
public void Update() {
if (trackerProcess != null)
trackerAlive = !trackerProcess.HasExited;
else {
trackerAlive = false;
return;
}
if (dontPrintNow || trackerSB == null)
return;
int len = trackerSB.Length;
if (len > 0) {
string output = trackerSB.ToString();
UnityEngine.Debug.Log("Tracker: " + output);
trackerSB.Clear();
}
}
private void CleanJob() {
if (job != null) {
job.Dispose();
job = null;
}
}
public void OnDestroy() {
StopTracker();
CleanJob();
}
public void OnApplicationQuit() {
StopTracker();
CleanJob();
}
}
}

109
Unity/OpenSeeShowPoints.cs Normal file
View file

@ -0,0 +1,109 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace OpenSee {
public class OpenSeeShowPoints : MonoBehaviour {
public OpenSee openSee = null;
public bool show3DPoints = true;
public bool applyTranslation = false;
public bool applyRotation = false;
private OpenSee.OpenSeeData[] openSeeData;
private GameObject[] gameObjects;
private GameObject centerBall;
private double updated = 0.0;
void Start () {
if (openSee == null) {
openSee = GetComponent<OpenSee>();
}
gameObjects = new GameObject[66];
for (int i = 0; i < 66; i++) {
gameObjects[i] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
gameObjects[i].name = "Point " + (i + 1);
gameObjects[i].transform.SetParent(transform);
gameObjects[i].transform.localScale = new Vector3(0.025f, 0.025f, 0.025f);
}
centerBall = GameObject.CreatePrimitive(PrimitiveType.Sphere);
centerBall.name = "Center";
centerBall.transform.SetParent(transform);
centerBall.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
void Update () {
if (!openSee)
return;
openSeeData = openSee.trackingData;
if (openSeeData == null || openSeeData.Length < 1)
return;
if (openSeeData[0].time > updated) {
updated = openSeeData[0].time;
} else {
return;
}
if (show3DPoints) {
centerBall.gameObject.SetActive(false);
for (int i = 0; i < 66; i++) {
if (openSeeData[0].got3DPoints) {
Renderer renderer = gameObjects[i].GetComponent<Renderer>();
renderer.material.SetColor("_Color", Color.Lerp(Color.red, Color.green, openSeeData[0].confidence[i]));
gameObjects[i].transform.localPosition = openSeeData[0].points3D[i];
} else {
Renderer renderer = gameObjects[i].GetComponent<Renderer>();
renderer.material.SetColor("_Color", Color.cyan);
}
}
if (applyTranslation) {
transform.position = openSeeData[0].translation;
}
if (applyRotation) {
transform.eulerAngles = openSeeData[0].rotation;
}
} else {
centerBall.gameObject.SetActive(false);
Vector3 center = new Vector3(0.0f, 0.0f, 0.0f);
float minX = 10000.0f;
float minY = 10000.0f;
float maxX = -1.0f;
float maxY = -1.0f;
for (int i = 0; i < 66; i++) {
float x = openSeeData[0].points[i].x;
float y = -openSeeData[0].points[i].y;
if (minX > x) {
minX = x;
}
if (minY > y) {
minY = y;
}
if (maxX < x) {
maxX = x;
}
if (maxY < y) {
maxY = y;
}
center += new Vector3(x, y, 0.0f);
}
center = center / 66;
center = center - new Vector3(minX, minY, 0.0f);
center.x = center.x / (maxX - minX);
center.y = center.y / (maxX - minX);
center.z = 0.5f;
centerBall.transform.localPosition = center;
for (int i = 0; i < 66; i++) {
Renderer renderer = gameObjects[i].GetComponent<Renderer>();
renderer.material.SetColor("_Color", Color.Lerp(Color.red, Color.green, openSeeData[0].confidence[i]));
float x = openSeeData[0].points[i].x;
float y = -openSeeData[0].points[i].y;
Vector3 position = new Vector3(x, y, 0.0f);
position = position - new Vector3(minX, minY, 0.0f);
position.x = position.x / (maxX - minX);
position.y = position.y / (maxX - minX);
gameObjects[i].transform.localPosition = position;
}
}
}
}
}