Compare commits
100 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6da0ba68df | |||
|
|
85aa70fc67 | ||
|
|
d4af2410d4 | ||
|
|
9e61fcf1ea | ||
|
|
17d954338e | ||
|
|
f7ef854afa | ||
|
|
66e8d91b52 | ||
|
|
dee5bb7606 | ||
|
|
e6e24efd20 | ||
|
|
de37867e1f | ||
|
|
2b3225f599 | ||
|
|
ccbcae05c0 | ||
|
|
5d3977934b | ||
|
|
9e07f6e199 | ||
|
|
d242009e0b | ||
|
|
609a6290bc | ||
|
|
96a9a2549a | ||
|
|
c31c891959 | ||
|
|
75b2fc6196 | ||
|
|
eeac9295c6 | ||
|
|
ca61100f5b | ||
|
|
89e104004b | ||
|
|
f13ae8a1ec | ||
|
|
6f24efc4f5 | ||
|
|
bcd11597e5 | ||
|
|
259052fe97 | ||
|
|
951068c39e | ||
|
|
91a556f692 | ||
|
|
624141fa6a | ||
|
|
7df42c6ca4 | ||
|
|
40119c1797 | ||
|
|
670df51c0d | ||
|
|
822e9e7753 | ||
|
|
be48eadbc6 | ||
|
|
0443549277 | ||
|
|
022a0520fa | ||
|
|
d6062c9664 | ||
|
|
1f3a01976b | ||
|
|
1825b2e67e | ||
|
|
1f8b83feef | ||
|
|
5936b487a7 | ||
|
|
dc65454cb4 | ||
|
|
c0fd813844 | ||
|
|
6f0ef1afb9 | ||
|
|
90e32e5229 | ||
|
|
8c07f06f54 | ||
|
|
ae1213a298 | ||
|
|
0bad9d9e9e | ||
|
|
018785fb06 | ||
|
|
da3eaadfb9 | ||
|
|
0204d8a4a7 | ||
|
|
9aff345720 | ||
|
|
baff2c0256 | ||
|
|
bbbd5e4ab6 | ||
|
|
06406f642d | ||
|
|
a12ad223b7 | ||
|
|
f2f4ec0a5f | ||
|
|
8cba4b33bd | ||
|
|
2a5b5d7f7f | ||
|
|
85ce0d5c3f | ||
|
|
2eec12225b | ||
|
|
50b33bcc48 | ||
|
|
d89e8dacda | ||
|
|
e49cbdb9c0 | ||
|
|
8a7a7961d0 | ||
|
|
1264cc255c | ||
|
|
44d97a967c | ||
|
|
c5ff6a8fe1 | ||
|
|
24f26d049f | ||
|
|
84c81f6938 | ||
|
|
22384e341a | ||
|
|
8b72102583 | ||
|
|
9a6eb20d83 | ||
|
|
323d707849 | ||
|
|
98781a780a | ||
|
|
be86991e7a | ||
|
|
aed11bf4bd | ||
|
|
e1c8c61e3a | ||
|
|
aecd2f9fbe | ||
|
|
94c06480a1 | ||
|
|
e273b11333 | ||
|
|
91a3425930 | ||
|
|
940ad56811 | ||
|
|
7d923f8176 | ||
|
|
07fd56fef8 | ||
|
|
529b77ef31 | ||
|
|
8458e11752 | ||
|
|
a6cd1dfa6c | ||
|
|
971f81b030 | ||
|
|
4ba81cb147 | ||
|
|
0a45f64a6f | ||
|
|
5fd0251b37 | ||
|
|
4dd306816e | ||
|
|
bc74658196 | ||
|
|
a0ccd9a162 | ||
|
|
8459f8bd97 | ||
|
|
177831419d | ||
|
|
0eeb9ddc20 | ||
|
|
e2ef42fffd | ||
|
|
53be9c2bf9 |
28 changed files with 1251 additions and 301 deletions
26
.github/actions/upload-dated-artifact/action.yaml
vendored
Normal file
26
.github/actions/upload-dated-artifact/action.yaml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
name: Upload dated artifact
|
||||||
|
description: Uploads an artifact with a datetime
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
name:
|
||||||
|
description: The name of the artifact
|
||||||
|
required: true
|
||||||
|
path:
|
||||||
|
description: The path to the file/folder to upload
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
# https://stackoverflow.com/questions/60942067/get-current-date-and-time-in-github-workflows
|
||||||
|
- name: Get current datetime
|
||||||
|
id: date
|
||||||
|
shell: bash
|
||||||
|
run: echo "::set-output name=date::$(date '+%Y-%m-%d_%H-%M-%S')"
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.name }}_${{ steps.date.outputs.date }}
|
||||||
|
path: ${{ inputs.path }}
|
||||||
|
if-no-files-found: error
|
||||||
87
.github/workflows/create-binaries.yaml
vendored
Normal file
87
.github/workflows/create-binaries.yaml
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
name: Package using PyInstaller
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
env:
|
||||||
|
dist-path: dist/OpenSeeFace
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Pull OpenSeeFace
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Create binary
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
choco install vcredist140
|
||||||
|
|
||||||
|
xcopy c:\Windows\System32\msvcp140.dll .
|
||||||
|
xcopy c:\Windows\System32\vcomp140.dll .
|
||||||
|
xcopy c:\Windows\System32\concrt140.dll .
|
||||||
|
xcopy c:\Windows\System32\vccorlib140.dll .
|
||||||
|
|
||||||
|
make_exe.bat
|
||||||
|
|
||||||
|
- name: Move files for uploading
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Prevents the parent dist/ directory from being included
|
||||||
|
mv dist/facetracker ${{ env.dist-path }}
|
||||||
|
cp -r models ${{ env.dist-path }}
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: ./.github/actions/upload-dated-artifact
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: dist # Add the parent directory to prevent a tarbomb
|
||||||
|
|
||||||
|
# Files must be packed into a tar in order for file permissions to be preserved
|
||||||
|
build-unix:
|
||||||
|
env:
|
||||||
|
artifact-path: OpenSeeFace
|
||||||
|
tar-path: OpenSeeFace.tar
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
- name: mac
|
||||||
|
os: macos-latest
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Pull OpenSeeFace
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Create binary
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
chmod +x make_exe.sh
|
||||||
|
./make_exe.sh
|
||||||
|
|
||||||
|
- name: Move files for uploading
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Prevents the parent dist/ directory from being included
|
||||||
|
mv dist/facetracker ${{ env.artifact-path }}
|
||||||
|
cp -r models ${{ env.artifact-path }}
|
||||||
|
|
||||||
|
- name: Fix permissions from PyInstaller
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
find ${{ env.artifact-path }} -type f -exec chmod -x {} +
|
||||||
|
chmod +x ${{ env.artifact-path }}/facetracker
|
||||||
|
|
||||||
|
- name: Pack files into tar
|
||||||
|
shell: bash
|
||||||
|
run: tar -cvf ${{ env.tar-path }} ${{ env.artifact-path }}
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: ./.github/actions/upload-dated-artifact
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
path: ${{ env.tar-path }}
|
||||||
|
|
@ -30,7 +30,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
public OpenSeeIKTarget openSeeIKTarget;
|
public OpenSeeIKTarget openSeeIKTarget;
|
||||||
[Tooltip("This is the target VRM avatar's blend shape proxy.")]
|
[Tooltip("This is the target VRM avatar's blend shape proxy.")]
|
||||||
public VRMBlendShapeProxy vrmBlendShapeProxy;
|
public VRMBlendShapeProxy vrmBlendShapeProxy;
|
||||||
[Tooltip("When this is enabled, no blendshapes are applied.")]
|
[Tooltip("When this is enabled, no blendshapes are set.")]
|
||||||
public bool skipApply = false;
|
public bool skipApply = false;
|
||||||
[Tooltip("When this is enabled, no gaze tracking (bone or blendshape) is applied.")]
|
[Tooltip("When this is enabled, no gaze tracking (bone or blendshape) is applied.")]
|
||||||
public bool skipApplyEyes = false;
|
public bool skipApplyEyes = false;
|
||||||
|
|
@ -38,8 +38,12 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
public bool skipApplyJaw = false;
|
public bool skipApplyJaw = false;
|
||||||
[Tooltip("When this is enabled in addition to skipApply, expressions from hotkeys or expression detection are still applied.")]
|
[Tooltip("When this is enabled in addition to skipApply, expressions from hotkeys or expression detection are still applied.")]
|
||||||
public bool stillApplyExpressions = false;
|
public bool stillApplyExpressions = false;
|
||||||
|
[Tooltip("This factor is multiplied to all blendshape weights relating to camera tracking, as well as gaze and jaw tracking.")]
|
||||||
|
public float blendShapeWeight = 1f;
|
||||||
[Tooltip("This needs to be enabled when tracking with the fast 30 point model.")]
|
[Tooltip("This needs to be enabled when tracking with the fast 30 point model.")]
|
||||||
public bool only30Points = false;
|
public bool only30Points = false;
|
||||||
|
[Tooltip("When enabled, if a model has perfect sync blendshapes, they will be used for more detailed tracking. (Experimental!)")]
|
||||||
|
public bool useCameraPerfectSync = false;
|
||||||
//[Tooltip("When set to true, expression and audio data will be processed in FixedUpdate, otherwise it will be processed in Update.")]
|
//[Tooltip("When set to true, expression and audio data will be processed in FixedUpdate, otherwise it will be processed in Update.")]
|
||||||
//public bool fixedUpdate = true;
|
//public bool fixedUpdate = true;
|
||||||
[Tooltip("When enabled, the avatar will blink automatically. Otherwise, blinks will be applied according to the face tracking's blink detection, allowing it to wink. Even for tracked eye blinks, the eye blink setting of the current expression is used.")]
|
[Tooltip("When enabled, the avatar will blink automatically. Otherwise, blinks will be applied according to the face tracking's blink detection, allowing it to wink. Even for tracked eye blinks, the eye blink setting of the current expression is used.")]
|
||||||
|
|
@ -64,6 +68,8 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
public bool linkBlinks = true;
|
public bool linkBlinks = true;
|
||||||
[Tooltip("When enabled, it becomes possible to wink even with link blinks enabled.")]
|
[Tooltip("When enabled, it becomes possible to wink even with link blinks enabled.")]
|
||||||
public bool allowWinking = false;
|
public bool allowWinking = false;
|
||||||
|
[Tooltip("If allowWinking is enabled, if the eye blink values are within this distance, the blink is linked.")]
|
||||||
|
public float smartWinkThreshold = 0.85f;
|
||||||
[Tooltip("When enabled, the avatar's eye will move according to the face tracker's gaze tracking.")]
|
[Tooltip("When enabled, the avatar's eye will move according to the face tracker's gaze tracking.")]
|
||||||
public bool gazeTracking = true;
|
public bool gazeTracking = true;
|
||||||
[Tooltip("This is the right eye bone. When either eye bone is not set, the VRM look direction blendshapes are used instead.")]
|
[Tooltip("This is the right eye bone. When either eye bone is not set, the VRM look direction blendshapes are used instead.")]
|
||||||
|
|
@ -116,6 +122,8 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
public bool hybridLipSync = false;
|
public bool hybridLipSync = false;
|
||||||
[Tooltip("When enabled, viseme size will not depend on the OVR Lip Sync weight.")]
|
[Tooltip("When enabled, viseme size will not depend on the OVR Lip Sync weight.")]
|
||||||
public bool maximizeLipSync = false;
|
public bool maximizeLipSync = false;
|
||||||
|
[Tooltip("When enabled, mouth blendshapes are normalized if they add up to a value above 1.")]
|
||||||
|
public bool visemeNormalization = true;
|
||||||
[Tooltip("When enabled and the animator of the avatar has a float parameter called JawMovement, this parameter will be set to values from 0 to 1, corresponding to a closed to open mouth. This can be used for animating jaw bones.")]
|
[Tooltip("When enabled and the animator of the avatar has a float parameter called JawMovement, this parameter will be set to values from 0 to 1, corresponding to a closed to open mouth. This can be used for animating jaw bones.")]
|
||||||
public bool animateJawBone = false;
|
public bool animateJawBone = false;
|
||||||
[Tooltip("This should be set to the included mecanim animation, which sets the jaw bone to closed on the first frame and to open on the second frame.")]
|
[Tooltip("This should be set to the included mecanim animation, which sets the jaw bone to closed on the first frame and to open on the second frame.")]
|
||||||
|
|
@ -163,6 +171,9 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
[Tooltip("This shows the current audio volume.")]
|
[Tooltip("This shows the current audio volume.")]
|
||||||
public float audioVolume = 0f;
|
public float audioVolume = 0f;
|
||||||
|
|
||||||
|
[HideInInspector]
|
||||||
|
public OpenSee.OpenSee.OpenSeeData openSeeData { get; private set; } = null;
|
||||||
|
|
||||||
#if WINDOWS_BUILD
|
#if WINDOWS_BUILD
|
||||||
private OVRLipSync.Frame visemeData = new OVRLipSync.Frame();
|
private OVRLipSync.Frame visemeData = new OVRLipSync.Frame();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -182,7 +193,6 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
private BlendShapeKey[] visemePresetMap;
|
private BlendShapeKey[] visemePresetMap;
|
||||||
private Dictionary<string, OpenSeeVRMExpression> expressionMap;
|
private Dictionary<string, OpenSeeVRMExpression> expressionMap;
|
||||||
private OpenSeeVRMExpression currentExpression = null;
|
private OpenSeeVRMExpression currentExpression = null;
|
||||||
private OpenSee.OpenSee.OpenSeeData openSeeData = null;
|
|
||||||
private OpenSeeVRMExpression lastExpression = null;
|
private OpenSeeVRMExpression lastExpression = null;
|
||||||
|
|
||||||
private bool haveFullVisemeSet = false;
|
private bool haveFullVisemeSet = false;
|
||||||
|
|
@ -228,7 +238,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
private float currentExpressionEyebrowWeight;
|
private float currentExpressionEyebrowWeight;
|
||||||
private float currentExpressionVisemeFactor;
|
private float currentExpressionVisemeFactor;
|
||||||
|
|
||||||
private OpenSeeBlendShapeProxy proxy = new OpenSeeBlendShapeProxy();
|
private OpenSeeBlendShapeProxy proxy = null;
|
||||||
private Animator animator;
|
private Animator animator;
|
||||||
private bool haveJawParameter;
|
private bool haveJawParameter;
|
||||||
private Transform jawBone;
|
private Transform jawBone;
|
||||||
|
|
@ -252,6 +262,9 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
private TimeInterpolate browInterpolate;
|
private TimeInterpolate browInterpolate;
|
||||||
private BlendShapeKey[] browClips;
|
private BlendShapeKey[] browClips;
|
||||||
|
|
||||||
|
private bool trackMouth = false;
|
||||||
|
private double lastPerfectSync = -1;
|
||||||
|
|
||||||
private float lastAudioTime = -1f;
|
private float lastAudioTime = -1f;
|
||||||
|
|
||||||
private class TimeInterpolate {
|
private class TimeInterpolate {
|
||||||
|
|
@ -285,20 +298,68 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
|
|
||||||
// This class is used to ensure max() based instead of add() based blendshape accumulation and to allow setting animator parameters from blendshape values
|
// This class is used to ensure max() based instead of add() based blendshape accumulation and to allow setting animator parameters from blendshape values
|
||||||
private class OpenSeeBlendShapeProxy {
|
private class OpenSeeBlendShapeProxy {
|
||||||
private HashSet<string> browsBlendShapes = new HashSet<string>() { "BROWS UP", "BROWS DOWN", "BROWINNERUP", "BROWDOWNLEFT", "BROWDOWNRIGHT", "BROWOUTERUPLEFT", "BROWOUTERUPRIGHT" };
|
private HashSet<string> browsBlendShapes = new HashSet<string>() { "BROWS UP", "BROWS DOWN", "BROWINNERUP", "BROWDOWNLEFT", "BROWDOWNRIGHT", "BROWOUTERUPLEFT", "BROWOUTERUPRIGHT", "NOSESNEERLEFT", "NOSESNEERRIGHT" };
|
||||||
private HashSet<string> eyesBlendShapes = new HashSet<string>() { "BLINK", "BLINK_L", "BLINK_R", "EYELOOKUPLEFT", "EYELOOKUPRIGHT", "EYELOOKDOWNLEFT", "EYELOOKDOWNRIGHT", "EYELOOKINLEFT", "EYELOOKINRIGHT", "EYELOOKOUTLEFT", "EYELOOKOUTRIGHT", "EYEBLINKLEFT", "EYEBLINKRIGHT", "EYESQUINTRIGHT", "EYESQUINTLEFT", "EYEWIDELEFT", "EYEWIDERIGHT" };
|
private HashSet<string> eyesBlendShapes = new HashSet<string>() { "BLINK", "BLINK_L", "BLINK_R", "EYELOOKUPLEFT", "EYELOOKUPRIGHT", "EYELOOKDOWNLEFT", "EYELOOKDOWNRIGHT", "EYELOOKINLEFT", "EYELOOKINRIGHT", "EYELOOKOUTLEFT", "EYELOOKOUTRIGHT", "EYEBLINKLEFT", "EYEBLINKRIGHT", "EYESQUINTRIGHT", "EYESQUINTLEFT", "EYEWIDELEFT", "EYEWIDERIGHT", "NOSESNEERLEFT", "NOSESNEERRIGHT", "CHEEKSQUINTLEFT", "CHEEKSQUINTRIGHT" };
|
||||||
private HashSet<string> mouthBlendShapes = new HashSet<string>() { "A", "I", "U", "E", "O", "SIL", "CH", "DD", "FF", "KK", "NN", "PP", "RR", "SS", "TH", "JAWOPEN", "JAWFORWARD", "JAWLEFT", "JAWRIGHT", "MOUTHFUNNEL", "MOUTHPUCKER", "MOUTHLEFT", "MOUTHRIGHT", "MOUTHROLLUPPER", "MOUTHROLLLOWER", "MOUTHSHRUGUPPER", "MOUTHSHRUGLOWER", "MOUTHCLOSE", "MOUTHSMILELEFT", "MOUTHSMILERIGHT", "MOUTHFROWNLEFT", "MOUTHFROWNRIGHT", "MOUTHDIMPLELEFT", "MOUTHDIMPLERIGHT", "MOUTHUPPERUPLEFT", "MOUTHUPPERUPRIGHT", "MOUTHLOWERDOWNLEFT", "MOUTHLOWERDOWNRIGHT", "MOUTHPRESSLEFT", "MOUTHPRESSRIGHT", "MOUTHSTRETCHLEFT", "MOUTHSTRETCHRIGHT", "TONGUEOUT" };
|
private HashSet<string> mouthBlendShapes = new HashSet<string>() { "A", "I", "U", "E", "O", "SIL", "CH", "DD", "FF", "KK", "NN", "PP", "RR", "SS", "TH", "JAWOPEN", "JAWFORWARD", "JAWLEFT", "JAWRIGHT", "MOUTHFUNNEL", "MOUTHPUCKER", "MOUTHLEFT", "MOUTHRIGHT", "MOUTHROLLUPPER", "MOUTHROLLLOWER", "MOUTHSHRUGUPPER", "MOUTHSHRUGLOWER", "MOUTHCLOSE", "MOUTHSMILELEFT", "MOUTHSMILERIGHT", "MOUTHFROWNLEFT", "MOUTHFROWNRIGHT", "MOUTHDIMPLELEFT", "MOUTHDIMPLERIGHT", "MOUTHUPPERUPLEFT", "MOUTHUPPERUPRIGHT", "MOUTHLOWERDOWNLEFT", "MOUTHLOWERDOWNRIGHT", "MOUTHPRESSLEFT", "MOUTHPRESSRIGHT", "MOUTHSTRETCHLEFT", "MOUTHSTRETCHRIGHT", "TONGUEOUT", "CHEEKSQUINTLEFT", "CHEEKSQUINTRIGHT" };
|
||||||
private bool clearPSBrows = false;
|
private bool clearPSBrows = false;
|
||||||
private bool clearPSEyes = false;
|
private bool clearPSEyes = false;
|
||||||
private bool clearPSMouth = false;
|
private bool clearPSMouth = false;
|
||||||
private Dictionary<BlendShapeKey, float> clearKeys = new Dictionary<BlendShapeKey, float>();
|
private Dictionary<BlendShapeKey, float> clearKeys = new Dictionary<BlendShapeKey, float>();
|
||||||
|
|
||||||
|
private bool perfectSync = false;
|
||||||
|
private HashSet<string> visemeBlendShapes = new HashSet<string>() { "A", "I", "U", "E", "O", "SIL", "CH", "DD", "FF", "KK", "NN", "PP", "RR", "SS", "TH" };
|
||||||
|
private string[] perfectSyncNames = new string[] { "BROWINNERUP", "BROWDOWNLEFT", "BROWDOWNRIGHT", "BROWOUTERUPLEFT", "BROWOUTERUPRIGHT", "EYELOOKUPLEFT", "EYELOOKUPRIGHT", "EYELOOKDOWNLEFT", "EYELOOKDOWNRIGHT", "EYELOOKINLEFT", "EYELOOKINRIGHT", "EYELOOKOUTLEFT", "EYELOOKOUTRIGHT", "EYEBLINKLEFT", "EYEBLINKRIGHT", "EYESQUINTRIGHT", "EYESQUINTLEFT", "EYEWIDELEFT", "EYEWIDERIGHT", "CHEEKPUFF", "CHEEKSQUINTLEFT", "CHEEKSQUINTRIGHT", "NOSESNEERLEFT", "NOSESNEERRIGHT", "JAWOPEN", "JAWFORWARD", "JAWLEFT", "JAWRIGHT", "MOUTHFUNNEL", "MOUTHPUCKER", "MOUTHLEFT", "MOUTHRIGHT", "MOUTHROLLUPPER", "MOUTHROLLLOWER", "MOUTHSHRUGUPPER", "MOUTHSHRUGLOWER", "MOUTHCLOSE", "MOUTHSMILELEFT", "MOUTHSMILERIGHT", "MOUTHFROWNLEFT", "MOUTHFROWNRIGHT", "MOUTHDIMPLELEFT", "MOUTHDIMPLERIGHT", "MOUTHUPPERUPLEFT", "MOUTHUPPERUPRIGHT", "MOUTHLOWERDOWNLEFT", "MOUTHLOWERDOWNRIGHT", "MOUTHPRESSLEFT", "MOUTHPRESSRIGHT", "MOUTHSTRETCHLEFT", "MOUTHSTRETCHRIGHT", "TONGUEOUT" };
|
||||||
|
private InterpolatedMap<string, InterpolatedFloat, float> perfectSyncMap = new InterpolatedMap<string, InterpolatedFloat, float>();
|
||||||
|
private Dictionary<string, BlendShapeKey> clipMap = new Dictionary<string, BlendShapeKey>();
|
||||||
|
|
||||||
private VRMBlendShapeProxy proxy = null;
|
private VRMBlendShapeProxy proxy = null;
|
||||||
private Animator animator = null;
|
private Animator animator = null;
|
||||||
private RuntimeAnimatorController animatorController = null;
|
private RuntimeAnimatorController animatorController = null;
|
||||||
|
private Dictionary<BlendShapeKey, float> clearMap = new Dictionary<BlendShapeKey, float>();
|
||||||
private Dictionary<BlendShapeKey, float> values = new Dictionary<BlendShapeKey, float>();
|
private Dictionary<BlendShapeKey, float> values = new Dictionary<BlendShapeKey, float>();
|
||||||
|
private Dictionary<BlendShapeKey, string> clipNames = new Dictionary<BlendShapeKey, string>();
|
||||||
private Dictionary<string, Tuple<string, AnimatorControllerParameterType>> parameters = new Dictionary<string, Tuple<string, AnimatorControllerParameterType>>();
|
private Dictionary<string, Tuple<string, AnimatorControllerParameterType>> parameters = new Dictionary<string, Tuple<string, AnimatorControllerParameterType>>();
|
||||||
private bool skip = false;
|
private bool interpolatedPerfectSync = false;
|
||||||
|
private float defaultWeight = 1f;
|
||||||
|
private OpenSeeVRMDriver vrmDriver = null;
|
||||||
|
|
||||||
|
public OpenSeeBlendShapeProxy(OpenSeeVRMDriver vrmDriver) {
|
||||||
|
this.vrmDriver = vrmDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolatePerfectSync() {
|
||||||
|
if (!HasPerfectSync() || interpolatedPerfectSync)
|
||||||
|
return;
|
||||||
|
foreach (var name in perfectSyncNames) {
|
||||||
|
if (!perfectSyncMap.Check(name))
|
||||||
|
continue;
|
||||||
|
float weight = perfectSyncMap.Get(name);
|
||||||
|
AccumulateValue(name, weight, 1f);
|
||||||
|
}
|
||||||
|
interpolatedPerfectSync = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearPerfectSync() {
|
||||||
|
perfectSyncMap.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPerfectSync(string name, float weight, double nowT, float factor) {
|
||||||
|
perfectSyncMap.Store(name.ToUpper(), Mathf.Clamp(weight * factor, 0f, 1f), nowT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPerfectSync(string name, float weight, double nowT) {
|
||||||
|
SetPerfectSync(name, weight, nowT, defaultWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasPerfectSync() {
|
||||||
|
if (proxy == null || animator == null)
|
||||||
|
return false;
|
||||||
|
return perfectSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetWeight(float v) {
|
||||||
|
defaultWeight = v;
|
||||||
|
}
|
||||||
|
|
||||||
public void DisableFaceParts(bool brows, bool eyes, bool mouth) {
|
public void DisableFaceParts(bool brows, bool eyes, bool mouth) {
|
||||||
clearPSBrows = brows;
|
clearPSBrows = brows;
|
||||||
|
|
@ -307,7 +368,14 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetFloat(BlendShapeKey key, float weight) {
|
private void SetFloat(BlendShapeKey key, float weight) {
|
||||||
string name = key.Name.ToUpper();
|
SetFloat(key, weight, defaultWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetFloat(BlendShapeKey key, float weight, float factor) {
|
||||||
|
if (!clipNames.ContainsKey(key))
|
||||||
|
return;
|
||||||
|
string name = clipNames[key];
|
||||||
|
weight *= factor;
|
||||||
if (animator != null && parameters.ContainsKey(name)) {
|
if (animator != null && parameters.ContainsKey(name)) {
|
||||||
var parameter = parameters[name];
|
var parameter = parameters[name];
|
||||||
if (parameter.Item2 == AnimatorControllerParameterType.Float)
|
if (parameter.Item2 == AnimatorControllerParameterType.Float)
|
||||||
|
|
@ -333,70 +401,123 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSkip(bool skip) {
|
|
||||||
this.skip = skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() {
|
public void Clear() {
|
||||||
CheckAnimatorController();
|
CheckAnimatorController();
|
||||||
if (proxy != null && !skip) {
|
interpolatedPerfectSync = false;
|
||||||
|
if (proxy != null) {
|
||||||
foreach (var pair in proxy.GetValues()) {
|
foreach (var pair in proxy.GetValues()) {
|
||||||
proxy.ImmediatelySetValue(pair.Key, 0f);
|
|
||||||
SetFloat(pair.Key, 0f);
|
SetFloat(pair.Key, 0f);
|
||||||
}
|
}
|
||||||
foreach (var pair in values) {
|
foreach (var pair in values) {
|
||||||
proxy.ImmediatelySetValue(pair.Key, 0f);
|
|
||||||
SetFloat(pair.Key, 0f);
|
SetFloat(pair.Key, 0f);
|
||||||
}
|
}
|
||||||
|
proxy.SetValues(clearMap);
|
||||||
}
|
}
|
||||||
values.Clear();
|
values.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NormalizeVisemes() {
|
||||||
|
float total = 0f;
|
||||||
|
foreach (var pair in values) {
|
||||||
|
string name = pair.Key.Name.ToUpper();
|
||||||
|
if (visemeBlendShapes.Contains(name))
|
||||||
|
total += pair.Value;
|
||||||
|
}
|
||||||
|
if (total > 1f) {
|
||||||
|
var keys = new List<BlendShapeKey>(values.Keys);
|
||||||
|
foreach (var key in keys) {
|
||||||
|
string name = key.Name.ToUpper();
|
||||||
|
if (visemeBlendShapes.Contains(name))
|
||||||
|
values[key] = values[key] / total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Apply() {
|
public void Apply() {
|
||||||
if (skip || proxy == null)
|
if (proxy == null)
|
||||||
return;
|
return;
|
||||||
CheckAnimatorController();
|
CheckAnimatorController();
|
||||||
|
if (perfectSync)
|
||||||
|
InterpolatePerfectSync();
|
||||||
|
if (vrmDriver != null && vrmDriver.visemeNormalization)
|
||||||
|
NormalizeVisemes();
|
||||||
if (clearPSBrows || clearPSEyes || clearPSMouth) {
|
if (clearPSBrows || clearPSEyes || clearPSMouth) {
|
||||||
bool found = false;
|
|
||||||
clearKeys.Clear();
|
clearKeys.Clear();
|
||||||
foreach (var pair in proxy.GetValues()) {
|
foreach (var pair in proxy.GetValues()) {
|
||||||
string name = pair.Key.Name.ToUpper();
|
string name = pair.Key.Name.ToUpper();
|
||||||
if (clearPSBrows && browsBlendShapes.Contains(name)) {
|
if (clearPSBrows && browsBlendShapes.Contains(name)) {
|
||||||
clearKeys[pair.Key] = 0f;
|
clearKeys[pair.Key] = 0f;
|
||||||
found = true;
|
values[pair.Key] = 0f;
|
||||||
} else if (clearPSEyes && eyesBlendShapes.Contains(name)) {
|
} else if (clearPSEyes && eyesBlendShapes.Contains(name)) {
|
||||||
clearKeys[pair.Key] = 0f;
|
clearKeys[pair.Key] = 0f;
|
||||||
found = true;
|
values[pair.Key] = 0f;
|
||||||
} else if (clearPSMouth && mouthBlendShapes.Contains(name)) {
|
} else if (clearPSMouth && mouthBlendShapes.Contains(name)) {
|
||||||
clearKeys[pair.Key] = 0f;
|
clearKeys[pair.Key] = 0f;
|
||||||
found = true;
|
values[pair.Key] = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (found)
|
|
||||||
proxy.SetValues(clearKeys);
|
|
||||||
clearPSBrows = false;
|
clearPSBrows = false;
|
||||||
clearPSEyes = false;
|
clearPSEyes = false;
|
||||||
clearPSMouth = false;
|
clearPSMouth = false;
|
||||||
}
|
}
|
||||||
|
proxy.SetValues(values);
|
||||||
foreach (var pair in values) {
|
foreach (var pair in values) {
|
||||||
proxy.AccumulateValue(pair.Key, pair.Value);
|
SetFloat(pair.Key, pair.Value, 1f);
|
||||||
SetFloat(pair.Key, pair.Value);
|
|
||||||
}
|
}
|
||||||
proxy.Apply();
|
proxy.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AccumulateValue(BlendShapeKey key, float weight) {
|
public void AccumulateValue(BlendShapeKey key, float weight, float factor) {
|
||||||
if (skip)
|
|
||||||
return;
|
|
||||||
if (!values.ContainsKey(key))
|
if (!values.ContainsKey(key))
|
||||||
values.Add(key, weight);
|
values.Add(key, weight * factor);
|
||||||
else
|
else
|
||||||
values[key] = Mathf.Max(values[key], weight);
|
values[key] = Mathf.Max(values[key], weight * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AccumulateValue(BlendShapeKey key, float weight) {
|
||||||
|
AccumulateValue(key, weight, defaultWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AccumulateValue(string key, float weight, float factor) {
|
||||||
|
if (!clipMap.ContainsKey(key.ToUpper()))
|
||||||
|
return;
|
||||||
|
AccumulateValue(clipMap[key.ToUpper()], weight, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AccumulateValue(string key, float weight) {
|
||||||
|
AccumulateValue(key, weight, defaultWeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAvatar(VRMBlendShapeProxy vrmBlendShapeProxy, Animator animator) {
|
public void UpdateAvatar(VRMBlendShapeProxy vrmBlendShapeProxy, Animator animator) {
|
||||||
proxy = vrmBlendShapeProxy;
|
proxy = vrmBlendShapeProxy;
|
||||||
this.animator = animator;
|
this.animator = animator;
|
||||||
|
|
||||||
|
perfectSync = false;
|
||||||
|
if (proxy == null)
|
||||||
|
return;
|
||||||
|
HashSet<string> perfectSyncSet = new HashSet<string>(perfectSyncNames);
|
||||||
|
clipMap.Clear();
|
||||||
|
clipNames.Clear();
|
||||||
|
clearMap.Clear();
|
||||||
|
perfectSyncMap.Clear();
|
||||||
|
//perfectSyncMap.SetSmoothing(0.5f);
|
||||||
|
foreach (BlendShapeClip clip in vrmBlendShapeProxy.BlendShapeAvatar.Clips) {
|
||||||
|
if (clip.Preset == BlendShapePreset.Unknown && clip.BlendShapeName != null) {
|
||||||
|
string name = clip.BlendShapeName.ToUpper();
|
||||||
|
clipMap.Add(name, BlendShapeKey.CreateUnknown(clip.BlendShapeName));
|
||||||
|
clipNames.Add(BlendShapeKey.CreateUnknown(clip.BlendShapeName), name);
|
||||||
|
clearMap.Add(BlendShapeKey.CreateUnknown(clip.BlendShapeName), 0f);
|
||||||
|
if (perfectSyncSet.Contains(name)) {
|
||||||
|
perfectSyncSet.Remove(name);
|
||||||
|
}
|
||||||
|
} else if (clip.Preset != BlendShapePreset.Unknown) {
|
||||||
|
string name = clip.BlendShapeName.ToUpper();
|
||||||
|
clipNames.Add(BlendShapeKey.CreateFromPreset(clip.Preset), name);
|
||||||
|
clearMap.Add(BlendShapeKey.CreateFromPreset(clip.Preset), 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (perfectSyncSet.Count < 1)
|
||||||
|
perfectSync = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,6 +609,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
|
|
||||||
bool ApplyVisemes() {
|
bool ApplyVisemes() {
|
||||||
#if WINDOWS_BUILD
|
#if WINDOWS_BUILD
|
||||||
|
trackMouth = false;
|
||||||
if (vrmBlendShapeProxy == null || catsData == null)
|
if (vrmBlendShapeProxy == null || catsData == null)
|
||||||
return true;
|
return true;
|
||||||
float expressionFactor = 1f;
|
float expressionFactor = 1f;
|
||||||
|
|
@ -541,9 +663,9 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
float result = lastVisemeValues[i] * visemeFactor * expressionFactor;
|
float result = lastVisemeValues[i] * visemeFactor * expressionFactor;
|
||||||
if (result > 0f && i < lastIdx) {
|
if (result > 0f && i < lastIdx) {
|
||||||
if (haveFullVisemeSet)
|
if (haveFullVisemeSet)
|
||||||
proxy.AccumulateValue(fullVisemePresets[i], result);
|
proxy.AccumulateValue(fullVisemePresets[i], result, 1f);
|
||||||
else
|
else
|
||||||
proxy.AccumulateValue(visemePresetMap[i], result);
|
proxy.AccumulateValue(visemePresetMap[i], result, 1f);
|
||||||
}
|
}
|
||||||
if (i == lastIdx) {
|
if (i == lastIdx) {
|
||||||
if (animateJawBone)
|
if (animateJawBone)
|
||||||
|
|
@ -596,6 +718,8 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
if (currentExpression != null && !currentExpressionEnableVisemes)
|
if (currentExpression != null && !currentExpressionEnableVisemes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
trackMouth = !fadeOnly;
|
||||||
|
|
||||||
float expressionFactor = 1f;
|
float expressionFactor = 1f;
|
||||||
if (currentExpression != null)
|
if (currentExpression != null)
|
||||||
expressionFactor = currentExpressionVisemeFactor;
|
expressionFactor = currentExpressionVisemeFactor;
|
||||||
|
|
@ -604,10 +728,11 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
float interpolated = Mathf.Lerp(lastMouthStates[i], currentMouthStates[i], t);
|
float interpolated = Mathf.Lerp(lastMouthStates[i], currentMouthStates[i], t);
|
||||||
float result = interpolated * expressionFactor * visemeFactor;
|
float result = interpolated * expressionFactor * visemeFactor;
|
||||||
if (i < 5 && result > 0f)
|
if (i < 5 && result > 0f)
|
||||||
proxy.AccumulateValue(visemePresetMap[i], result);
|
if (!useCameraPerfectSync || !proxy.HasPerfectSync())
|
||||||
|
proxy.AccumulateValue(visemePresetMap[i], result);
|
||||||
if (i == 5) {
|
if (i == 5) {
|
||||||
if (animateJawBone)
|
if (animateJawBone)
|
||||||
SetJaw(result * 0.99999f);
|
SetJaw(result * 0.99999f * blendShapeWeight);
|
||||||
else
|
else
|
||||||
SetJaw(0f);
|
SetJaw(0f);
|
||||||
}
|
}
|
||||||
|
|
@ -683,6 +808,127 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
ApplyMouthShape(false);
|
ApplyMouthShape(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdatePerfectSync() {
|
||||||
|
if (openSeeData == null || lastPerfectSync >= openSeeData.time)
|
||||||
|
return;
|
||||||
|
lastPerfectSync = openSeeData.time;
|
||||||
|
if (useCameraPerfectSync && proxy.HasPerfectSync()) {
|
||||||
|
float upDownStrength = (openSeeData.features.EyebrowUpDownLeft + openSeeData.features.EyebrowUpDownRight) / 2f;
|
||||||
|
if (upDownStrength > 0f)
|
||||||
|
proxy.SetPerfectSync("BROWINNERUP", upDownStrength * 0.8f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("BROWINNERUP", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.EyebrowUpDownLeft < 0.2f) {
|
||||||
|
proxy.SetPerfectSync("BROWDOWNLEFT", -openSeeData.features.EyebrowUpDownLeft * 0.5f, lastPerfectSync);
|
||||||
|
if (openSeeData.features.EyeLeft < 0.1f && openSeeData.features.EyeLeft > -0.6f) {
|
||||||
|
proxy.SetPerfectSync("EYESQUINTLEFT", -openSeeData.features.EyeLeft, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYEBLINKLEFT", 0, lastPerfectSync);
|
||||||
|
} else if (openSeeData.features.EyeLeft <= -0.6f) {
|
||||||
|
proxy.SetPerfectSync("EYEBLINKLEFT", -openSeeData.features.EyeLeft * 1.5f, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYESQUINTLEFT", 0, lastPerfectSync);
|
||||||
|
} else {
|
||||||
|
proxy.SetPerfectSync("EYESQUINTLEFT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYEBLINKLEFT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxy.SetPerfectSync("BROWDOWNLEFT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYESQUINTLEFT", 0, lastPerfectSync);
|
||||||
|
if (openSeeData.features.EyeLeft <= -0.3f)
|
||||||
|
proxy.SetPerfectSync("EYEBLINKLEFT", -openSeeData.features.EyeLeft * 1.5f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("EYEBLINKLEFT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openSeeData.features.EyebrowUpDownRight < 0.2f) {
|
||||||
|
proxy.SetPerfectSync("BROWDOWNRIGHT", -openSeeData.features.EyebrowUpDownRight * 0.5f, lastPerfectSync);
|
||||||
|
if (openSeeData.features.EyeRight < 0.1f && openSeeData.features.EyeRight > -0.6f) {
|
||||||
|
proxy.SetPerfectSync("EYESQUINTRIGHT", -openSeeData.features.EyeRight, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYEBLINKRIGHT", 0, lastPerfectSync);
|
||||||
|
} else if (openSeeData.features.EyeRight <= -0.6f) {
|
||||||
|
proxy.SetPerfectSync("EYEBLINKRIGHT", -openSeeData.features.EyeRight * 1.5f, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYESQUINTRIGHT", 0, lastPerfectSync);
|
||||||
|
} else {
|
||||||
|
proxy.SetPerfectSync("EYESQUINTRIGHT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYEBLINKRIGHT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxy.SetPerfectSync("BROWDOWNRIGHT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("EYESQUINTRIGHT", 0, lastPerfectSync);
|
||||||
|
if (openSeeData.features.EyeRight <= -0.3f)
|
||||||
|
proxy.SetPerfectSync("EYEBLINKRIGHT", -openSeeData.features.EyeRight * 1.5f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("EYEBLINKRIGHT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openSeeData.features.EyebrowSteepnessLeft < 0.2f)
|
||||||
|
proxy.SetPerfectSync("BROWOUTERUPLEFT", -openSeeData.features.EyebrowSteepnessLeft, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("BROWOUTERUPLEFT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.EyebrowSteepnessRight < 0.2f)
|
||||||
|
proxy.SetPerfectSync("BROWOUTERUPRIGHT", -openSeeData.features.EyebrowSteepnessRight, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("BROWOUTERUPRIGHT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.EyeLeft > 0.5f)
|
||||||
|
proxy.SetPerfectSync("EYEWIDELEFT", openSeeData.features.EyeLeft * 0.7f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("EYEWIDELEFT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.EyeRight > 0.5f)
|
||||||
|
proxy.SetPerfectSync("EYEWIDERIGHT", openSeeData.features.EyeRight * 0.7f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("EYEWIDERIGHT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (trackMouth) {
|
||||||
|
if (openSeeData.features.MouthWide < 0.5f)
|
||||||
|
proxy.SetPerfectSync("MOUTHPUCKER", -openSeeData.features.MouthWide * 0.3f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("MOUTHPUCKER", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.MouthOpen > 0.0f) {
|
||||||
|
proxy.SetPerfectSync("JAWOPEN", openSeeData.features.MouthOpen, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("MOUTHCLOSE", 0, lastPerfectSync);
|
||||||
|
} else if (openSeeData.features.MouthOpen < -0.0f) {
|
||||||
|
proxy.SetPerfectSync("MOUTHCLOSE", openSeeData.features.MouthOpen, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("JAWOPEN", 0, lastPerfectSync);
|
||||||
|
} else {
|
||||||
|
proxy.SetPerfectSync("JAWOPEN", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("MOUTHCLOSE", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openSeeData.features.MouthCornerInOutLeft > 0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHLEFT", openSeeData.features.MouthCornerInOutLeft * 0.5f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("MOUTHLEFT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.MouthCornerInOutRight > 0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHRIGHT", openSeeData.features.MouthCornerInOutRight * 0.5f, lastPerfectSync);
|
||||||
|
else
|
||||||
|
proxy.SetPerfectSync("MOUTHRIGHT", 0, lastPerfectSync);
|
||||||
|
|
||||||
|
if (openSeeData.features.MouthCornerUpDownLeft > 0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHSMILELEFT", openSeeData.features.MouthCornerUpDownLeft * 0.5f, lastPerfectSync);
|
||||||
|
else if (openSeeData.features.MouthCornerUpDownLeft < -0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHFROWNLEFT", -openSeeData.features.MouthCornerUpDownLeft, lastPerfectSync);
|
||||||
|
else {
|
||||||
|
proxy.SetPerfectSync("MOUTHSMILELEFT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("MOUTHFROWNLEFT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openSeeData.features.MouthCornerUpDownRight > 0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHSMILERIGHT", openSeeData.features.MouthCornerUpDownRight * 0.5f, lastPerfectSync);
|
||||||
|
else if (openSeeData.features.MouthCornerUpDownLeft < -0.3f)
|
||||||
|
proxy.SetPerfectSync("MOUTHFROWNRIGHT", -openSeeData.features.MouthCornerUpDownRight, lastPerfectSync);
|
||||||
|
else {
|
||||||
|
proxy.SetPerfectSync("MOUTHSMILERIGHT", 0, lastPerfectSync);
|
||||||
|
proxy.SetPerfectSync("MOUTHFROWNRIGHT", 0, lastPerfectSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateBrows() {
|
void UpdateBrows() {
|
||||||
if ((browClips == null && (faceMesh == null || faceType < 0)) || openSeeData == null) {
|
if ((browClips == null && (faceMesh == null || faceType < 0)) || openSeeData == null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -695,21 +941,23 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
if (currentExpression != null)
|
if (currentExpression != null)
|
||||||
strength *= currentExpressionEyebrowWeight;
|
strength *= currentExpressionEyebrowWeight;
|
||||||
|
|
||||||
if (!skipApply && browClips == null) {
|
if (!useCameraPerfectSync || !proxy.HasPerfectSync()) {
|
||||||
if (browUpIndex > -1 && strength > 0f)
|
if (!skipApply && browClips == null) {
|
||||||
faceMesh.SetBlendShapeWeight(browUpIndex, strength * Mathf.Lerp(lastBrowStates[0], currentBrowStates[0], t));
|
if (browUpIndex > -1 && strength > 0f)
|
||||||
if (browDownIndex > -1 && strength > 0f)
|
faceMesh.SetBlendShapeWeight(browUpIndex, strength * Mathf.Lerp(lastBrowStates[0], currentBrowStates[0], t));
|
||||||
faceMesh.SetBlendShapeWeight(browDownIndex, strength * Mathf.Lerp(lastBrowStates[1], currentBrowStates[1], t));
|
if (browDownIndex > -1 && strength > 0f)
|
||||||
if (browAngryIndex > -1 && strength > 0f)
|
faceMesh.SetBlendShapeWeight(browDownIndex, strength * Mathf.Lerp(lastBrowStates[1], currentBrowStates[1], t));
|
||||||
faceMesh.SetBlendShapeWeight(browAngryIndex, strength * Mathf.Lerp(lastBrowStates[2], currentBrowStates[2], t));
|
if (browAngryIndex > -1 && strength > 0f)
|
||||||
if (browSorrowIndex > -1 && strength > 0f)
|
faceMesh.SetBlendShapeWeight(browAngryIndex, strength * Mathf.Lerp(lastBrowStates[2], currentBrowStates[2], t));
|
||||||
faceMesh.SetBlendShapeWeight(browSorrowIndex, strength * Mathf.Lerp(lastBrowStates[3], currentBrowStates[3], t));
|
if (browSorrowIndex > -1 && strength > 0f)
|
||||||
}
|
faceMesh.SetBlendShapeWeight(browSorrowIndex, strength * Mathf.Lerp(lastBrowStates[3], currentBrowStates[3], t));
|
||||||
if (browClips != null && strength > 0f) {
|
}
|
||||||
if (lastBrowUpDown > 0f)
|
if (browClips != null && strength > 0f) {
|
||||||
proxy.AccumulateValue(browClips[0], strength * Mathf.Lerp(lastBrowStates[0], currentBrowStates[0], t));
|
if (lastBrowUpDown > 0f)
|
||||||
else if (lastBrowUpDown < 0f)
|
proxy.AccumulateValue(browClips[0], strength * Mathf.Lerp(lastBrowStates[0], currentBrowStates[0], t));
|
||||||
proxy.AccumulateValue(browClips[1], strength * Mathf.Lerp(lastBrowStates[1], currentBrowStates[1], t));
|
else if (lastBrowUpDown < 0f)
|
||||||
|
proxy.AccumulateValue(browClips[1], strength * Mathf.Lerp(lastBrowStates[1], currentBrowStates[1], t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastBrows < openSeeData.time) {
|
if (lastBrows < openSeeData.time) {
|
||||||
|
|
@ -1199,7 +1447,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
foreach (var expression in expressionMap.Values) {
|
foreach (var expression in expressionMap.Values) {
|
||||||
float weight = expression.GetWeight(baseTransitionTime);
|
float weight = expression.GetWeight(baseTransitionTime);
|
||||||
if (weight > 0f) {
|
if (weight > 0f) {
|
||||||
proxy.AccumulateValue(expression.blendShapeKey, weight);
|
proxy.AccumulateValue(expression.blendShapeKey, weight, 1f);
|
||||||
|
|
||||||
// Accumulate limits on expressions
|
// Accumulate limits on expressions
|
||||||
currentExpressionEnableBlinking = currentExpressionEnableBlinking && expression.enableBlinking;
|
currentExpressionEnableBlinking = currentExpressionEnableBlinking && expression.enableBlinking;
|
||||||
|
|
@ -1237,7 +1485,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateGaze() {
|
void UpdateGaze() {
|
||||||
if (openSeeData == null || vrmBlendShapeProxy == null || openSeeIKTarget == null)
|
if (openSeeData == null || vrmBlendShapeProxy == null || openSeeIKTarget == null || skipApplyEyes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
float lookUpDown = currentLookUpDown;
|
float lookUpDown = currentLookUpDown;
|
||||||
|
|
@ -1273,7 +1521,7 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
if (leftEye != null)
|
if (leftEye != null)
|
||||||
leftEye.localRotation = Quaternion.identity;
|
leftEye.localRotation = Quaternion.identity;
|
||||||
if (gazeTracking) {
|
if (gazeTracking) {
|
||||||
Quaternion rotation = Quaternion.AngleAxis(-gazeFactor.x * gazeStrength * lookUpDown, Vector3.right) * Quaternion.AngleAxis(-gazeFactor.y * gazeStrength * lookLeftRight, Vector3.up);
|
Quaternion rotation = Quaternion.Slerp(Quaternion.identity, Quaternion.AngleAxis(-gazeFactor.x * gazeStrength * lookUpDown, Vector3.right) * Quaternion.AngleAxis(-gazeFactor.y * gazeStrength * lookLeftRight, Vector3.up), blendShapeWeight);
|
||||||
if (rightEye != null)
|
if (rightEye != null)
|
||||||
rightEye.localRotation = rotation;
|
rightEye.localRotation = rotation;
|
||||||
if (leftEye != null)
|
if (leftEye != null)
|
||||||
|
|
@ -1305,7 +1553,8 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
if (autoBlink || only30Points) {
|
if (autoBlink || only30Points) {
|
||||||
if (currentExpressionEnableBlinking) {
|
if (currentExpressionEnableBlinking) {
|
||||||
float blink = eyeBlinker.Blink();
|
float blink = eyeBlinker.Blink();
|
||||||
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), blink);
|
if (!useCameraPerfectSync || ! proxy.HasPerfectSync())
|
||||||
|
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), blink, 1);
|
||||||
}
|
}
|
||||||
} else if (openSeeExpression != null && openSeeExpression.openSee != null && openSeeData != null) {
|
} else if (openSeeExpression != null && openSeeExpression.openSee != null && openSeeData != null) {
|
||||||
if (!currentExpressionEnableBlinking)
|
if (!currentExpressionEnableBlinking)
|
||||||
|
|
@ -1323,7 +1572,8 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
if (!(wasLookingDown > 0f))
|
if (!(wasLookingDown > 0f))
|
||||||
eyeBlinker.Blink();
|
eyeBlinker.Blink();
|
||||||
float blink = eyeBlinker.Blink();
|
float blink = eyeBlinker.Blink();
|
||||||
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), blink);
|
if (!useCameraPerfectSync || ! proxy.HasPerfectSync())
|
||||||
|
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), blink);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1340,12 +1590,14 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
right = tmp;
|
right = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkBlinks && !allowWinking) {
|
if (!useCameraPerfectSync || !proxy.HasPerfectSync()) {
|
||||||
float v = Mathf.Max(left, right);
|
if (linkBlinks && !allowWinking) {
|
||||||
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), v);
|
float v = Mathf.Max(left, right);
|
||||||
} else {
|
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), v);
|
||||||
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_R), right);
|
} else {
|
||||||
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_L), left);
|
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_R), right);
|
||||||
|
proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink_L), left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openSeeData != null && lastBlink < openSeeData.time) {
|
if (openSeeData != null && lastBlink < openSeeData.time) {
|
||||||
|
|
@ -1389,8 +1641,10 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
right = left;
|
right = left;
|
||||||
|
|
||||||
if (linkBlinks) {
|
if (linkBlinks) {
|
||||||
if (Mathf.Abs(right - left) < 0.95 || !allowWinking) {
|
if (Mathf.Abs(right - left) < smartWinkThreshold || !allowWinking) {
|
||||||
float v = Mathf.Max(left, right);
|
float v = Mathf.Max(left, right);
|
||||||
|
if (v < 0.3f)
|
||||||
|
v = Mathf.Min(left, right);
|
||||||
left = v;
|
left = v;
|
||||||
right = v;
|
right = v;
|
||||||
}
|
}
|
||||||
|
|
@ -1700,11 +1954,27 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
|
|
||||||
void Start() {
|
void Start() {
|
||||||
InitExpressionMap();
|
InitExpressionMap();
|
||||||
|
proxy = new OpenSeeBlendShapeProxy(this);
|
||||||
humanBodyBones = (HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones));
|
humanBodyBones = (HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones));
|
||||||
humanBodyBoneTransforms = new Transform[humanBodyBones.Length];
|
humanBodyBoneTransforms = new Transform[humanBodyBones.Length];
|
||||||
humanBodyBoneRotations = new Quaternion[humanBodyBones.Length];
|
humanBodyBoneRotations = new Quaternion[humanBodyBones.Length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetProxyValues(Dictionary<BlendShapeKey, float> dict) {
|
||||||
|
foreach (var kv in dict) {
|
||||||
|
proxy.AccumulateValue(kv.Key, kv.Value, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetExternalPerfectSync(Dictionary<string, float> blendshapes, float time) {
|
||||||
|
if (blendshapes == null) {
|
||||||
|
proxy.ClearPerfectSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (var entry in blendshapes)
|
||||||
|
proxy.SetPerfectSync(entry.Key, entry.Value, time, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
void RunUpdates() {
|
void RunUpdates() {
|
||||||
if (openSeeExpression != null && openSeeExpression.openSee != null) {
|
if (openSeeExpression != null && openSeeExpression.openSee != null) {
|
||||||
openSeeData = openSeeExpression.openSee.GetOpenSeeData(openSeeExpression.faceId);
|
openSeeData = openSeeExpression.openSee.GetOpenSeeData(openSeeExpression.faceId);
|
||||||
|
|
@ -1715,45 +1985,45 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
InitializeLipSync();
|
InitializeLipSync();
|
||||||
initializeLipSync = false;
|
initializeLipSync = false;
|
||||||
}
|
}
|
||||||
proxy.SetSkip(skipApply);
|
proxy.SetWeight(blendShapeWeight);
|
||||||
proxy.Clear();
|
proxy.Clear();
|
||||||
FindFaceMesh();
|
FindFaceMesh();
|
||||||
if (!skipApply)
|
if (!skipApply)
|
||||||
UpdateExpression();
|
UpdateExpression();
|
||||||
BlinkEyes();
|
if (!skipApply)
|
||||||
|
BlinkEyes();
|
||||||
|
bool doMouthTracking = true;
|
||||||
#if WINDOWS_BUILD
|
#if WINDOWS_BUILD
|
||||||
ReadAudio();
|
ReadAudio();
|
||||||
bool doMouthTracking = true;
|
|
||||||
if (lipSync) {
|
if (lipSync) {
|
||||||
doMouthTracking = ApplyVisemes();
|
if (!skipApply)
|
||||||
|
doMouthTracking = ApplyVisemes();
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
bool doMouthTracking = true;
|
|
||||||
#endif
|
#endif
|
||||||
if (doMouthTracking)
|
if (doMouthTracking && !skipApply)
|
||||||
ApplyMouthShape();
|
ApplyMouthShape();
|
||||||
if (vrmBlendShapeProxy != null && browClips == null)
|
if (!skipApply)
|
||||||
|
UpdatePerfectSync();
|
||||||
|
if (!skipApply && vrmBlendShapeProxy != null && browClips == null)
|
||||||
proxy.Apply();
|
proxy.Apply();
|
||||||
UpdateBrows();
|
if (!skipApply)
|
||||||
if (vrmBlendShapeProxy != null && browClips != null)
|
UpdateBrows();
|
||||||
|
if (!skipApply && vrmBlendShapeProxy != null && browClips != null)
|
||||||
proxy.Apply();
|
proxy.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LateUpdate() {
|
void LateUpdate() {
|
||||||
|
if (!skipApplyJaw && jawBone != null && jawBoneAnimation != null && !haveJawParameter) {
|
||||||
|
jawBone.localRotation = jawRotation;
|
||||||
|
}
|
||||||
if (!skipApplyEyes && gazeTracking && !only30Points) {
|
if (!skipApplyEyes && gazeTracking && !only30Points) {
|
||||||
UpdateGaze();
|
UpdateGaze();
|
||||||
proxy.Apply();
|
|
||||||
}
|
|
||||||
if (!skipApplyJaw && jawBone != null && jawBoneAnimation != null) {
|
|
||||||
jawBone.localRotation = jawRotation;
|
|
||||||
}
|
}
|
||||||
// If blendshapes are received over VMC, apply expressions afterwards if they should be applied
|
// If blendshapes are received over VMC, apply expressions afterwards if they should be applied
|
||||||
if (skipApply && stillApplyExpressions) {
|
if (skipApply && stillApplyExpressions) {
|
||||||
proxy.SetSkip(false);
|
|
||||||
UpdateExpression();
|
UpdateExpression();
|
||||||
proxy.Apply();
|
|
||||||
proxy.SetSkip(skipApply);
|
|
||||||
}
|
}
|
||||||
|
proxy.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate() {
|
void FixedUpdate() {
|
||||||
|
|
@ -1796,4 +2066,17 @@ public class OpenSeeVRMDriver : MonoBehaviour {
|
||||||
public void SetAnimateJawBone(bool value) {
|
public void SetAnimateJawBone(bool value) {
|
||||||
animateJawBone = value;
|
animateJawBone = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetSmartWinkThreshold(float v) {
|
||||||
|
smartWinkThreshold = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGazeSmoothing(float v) {
|
||||||
|
gazeSmoothing = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVisemeNormalization(bool v) {
|
||||||
|
visemeNormalization = v;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,6 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
// UniJSON is part of UniVRM (https://github.com/vrm-c/UniVRM)
|
|
||||||
using UniJSON;
|
|
||||||
|
|
||||||
namespace OpenSee {
|
namespace OpenSee {
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
|
@ -61,14 +58,24 @@ public class OpenSeeWebcam {
|
||||||
|
|
||||||
private List<Tuple<OpenSeeWebcamCapability, int>> splitCaps = null;
|
private List<Tuple<OpenSeeWebcamCapability, int>> splitCaps = null;
|
||||||
|
|
||||||
|
bool ge(bool a, bool b) {
|
||||||
|
return a || (!a && !b);
|
||||||
|
}
|
||||||
|
|
||||||
private int CompareCaps(OpenSeeWebcamCapability a, OpenSeeWebcamCapability b) {
|
private int CompareCaps(OpenSeeWebcamCapability a, OpenSeeWebcamCapability b) {
|
||||||
if (a.rating < b.rating)
|
int aRes = a.minCX * a.minCY;
|
||||||
|
int bRes = b.minCX * b.minCY;
|
||||||
|
bool aGoodFps = a.minInterval <= 670000;
|
||||||
|
bool bGoodFps = a.minInterval <= 670000;
|
||||||
|
bool aGoodRes = aRes >= 850000 && aRes <= 2200000;
|
||||||
|
bool bGoodRes = bRes >= 850000 && bRes <= 2200000;
|
||||||
|
if (a.rating < b.rating && ge(aGoodFps, bGoodFps) && ge(aGoodRes, bGoodRes))
|
||||||
return -1;
|
return -1;
|
||||||
if (a.rating > b.rating)
|
if (a.rating > b.rating && ge(bGoodFps, aGoodFps) && ge(bGoodRes, aGoodRes))
|
||||||
return 1;
|
return 1;
|
||||||
if (a.minCX * a.minCY > b.minCX * b.minCY)
|
if (aRes > bRes && ge(aGoodFps, bGoodFps) && ge(aGoodRes, bGoodRes))
|
||||||
return -1;
|
return -1;
|
||||||
if (a.minCX * a.minCY < b.minCX * b.minCY)
|
if (aRes < bRes && ge(bGoodFps, aGoodFps) && ge(bGoodRes, aGoodRes))
|
||||||
return 1;
|
return 1;
|
||||||
if (a.minInterval < b.minInterval)
|
if (a.minInterval < b.minInterval)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -178,6 +185,11 @@ public class OpenSeeWebcamInfo : MonoBehaviour {
|
||||||
private static extern void bm_get_json_x86([Out] StringBuilder namebuffer, int bufferlength);
|
private static extern void bm_get_json_x86([Out] StringBuilder namebuffer, int bufferlength);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class OpenSeeWebcamList {
|
||||||
|
public List<OpenSeeWebcam> list;
|
||||||
|
}
|
||||||
|
|
||||||
public List<OpenSeeWebcam> cameras;
|
public List<OpenSeeWebcam> cameras;
|
||||||
public bool dumpJson = false;
|
public bool dumpJson = false;
|
||||||
|
|
||||||
|
|
@ -232,15 +244,11 @@ public class OpenSeeWebcamInfo : MonoBehaviour {
|
||||||
if (includeBlackMagic)
|
if (includeBlackMagic)
|
||||||
UnityEngine.Debug.Log("Blackmagic JSON: " + bmJsonData);
|
UnityEngine.Debug.Log("Blackmagic JSON: " + bmJsonData);
|
||||||
}
|
}
|
||||||
List<OpenSeeWebcam> details = new List<OpenSeeWebcam>();
|
List<OpenSeeWebcam> details = JsonUtility.FromJson<OpenSeeWebcamList>("{\"list\":" + jsonData + "}").list;
|
||||||
var parsed = JsonParser.Parse(jsonData);
|
|
||||||
parsed.Deserialize(ref details);
|
|
||||||
foreach (var cam in details)
|
foreach (var cam in details)
|
||||||
cam.type = OpenSeeWebcamType.DirectShow;
|
cam.type = OpenSeeWebcamType.DirectShow;
|
||||||
if (includeBlackMagic) {
|
if (includeBlackMagic) {
|
||||||
List<OpenSeeWebcam> bmDetails = new List<OpenSeeWebcam>();
|
List<OpenSeeWebcam> bmDetails = JsonUtility.FromJson<OpenSeeWebcamList>("{\"list\":" + bmJsonData + "}").list;
|
||||||
parsed = JsonParser.Parse(bmJsonData);
|
|
||||||
parsed.Deserialize(ref bmDetails);
|
|
||||||
foreach (var cam in bmDetails)
|
foreach (var cam in bmDetails)
|
||||||
cam.type = OpenSeeWebcamType.Blackmagic;
|
cam.type = OpenSeeWebcamType.Blackmagic;
|
||||||
details.AddRange(bmDetails);
|
details.AddRange(bmDetails);
|
||||||
|
|
@ -258,13 +266,5 @@ public class OpenSeeWebcamInfo : MonoBehaviour {
|
||||||
foreach (var camera in cameras)
|
foreach (var camera in cameras)
|
||||||
camera.GetPrettyCapabilities();
|
camera.GetPrettyCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AOTCall() {
|
|
||||||
GenericDeserializer<JsonValue, OpenSeeWebcamCapability[]>.GenericArrayDeserializer<OpenSeeWebcamCapability>(default(ListTreeNode<JsonValue>));
|
|
||||||
JsonObjectValidator.GenericDeserializer<JsonValue, OpenSeeWebcamCapability>.DeserializeField<System.Int32>(default(JsonSchema), default(ListTreeNode<JsonValue>));
|
|
||||||
JsonObjectValidator.GenericDeserializer<JsonValue, OpenSeeWebcamCapability>.DeserializeField<OpenSeeWebcamFormat>(default(JsonSchema), default(ListTreeNode<JsonValue>));
|
|
||||||
GenericDeserializer<JsonValue, OpenSeeWebcam[]>.GenericArrayDeserializer<OpenSeeWebcam>(default(ListTreeNode<JsonValue>));
|
|
||||||
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
37
README.md
37
README.md
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
**Note**: This is a tracking library, **not** a stand-alone avatar puppeteering program. I'm also working on [such](https://www.vseeface.icu/) an application, which is currently in a public beta.
|
**Note**: This is a tracking library, **not** a stand-alone avatar puppeteering program. I'm also working on [VSeeFace](https://www.vseeface.icu/), which allows animating [VRM](https://vrm.dev/en/how_to_make_vrm/) and [VSFAvatar](https://www.youtube.com/watch?v=jhQ8DF87I5I) 3D models by using OpenSeeFace tracking. [VTube Studio](https://denchisoft.com/) uses OpenSeeFace for webcam based tracking to animate Live2D models. A renderer for the Godot engine can be found [here](https://github.com/virtual-puppet-project/vpuppr).
|
||||||
|
|
||||||
This project implements a facial landmark detection model based on MobileNetV3.
|
This project implements a facial landmark detection model based on MobileNetV3.
|
||||||
|
|
||||||
|
|
@ -12,8 +12,18 @@ If anyone is curious, the name is a silly pun on the open seas and seeing faces.
|
||||||
|
|
||||||
An up to date sample video can be found [here](https://www.youtube.com/watch?v=AaNap_ud_3I&vq=hd1080), showing the default tracking model's performance under different noise and light levels.
|
An up to date sample video can be found [here](https://www.youtube.com/watch?v=AaNap_ud_3I&vq=hd1080), showing the default tracking model's performance under different noise and light levels.
|
||||||
|
|
||||||
|
# Tracking quality
|
||||||
|
|
||||||
|
Since the landmarks used by OpenSeeFace are a bit different from those used by other approaches (they are close to iBUG 68, with two less points in the mouth corners and quasi-3D face contours instead of face contours that follow the visible outline) it is hard to numerically compare its accuracy to that of other approaches found commonly in scientific literature. The tracking performance is also more optimized for making landmarks that are useful for animating an avatar than for exactly fitting the face image. For example, as long as the eye landmarks show whether the eyes are opened or closed, even if their location is somewhat off, they can still be useful for this purpose.
|
||||||
|
|
||||||
|
From general observation, OpenSeeFace performs well in adverse conditions (low light, high noise, low resolution) and keeps tracking faces through a very wide range of head poses with relatively high stability of landmark positions. Compared to MediaPipe, OpenSeeFace landmarks remain more stable in challenging conditions and it accurately represents a wider range of mouth poses. However, tracking of the eye region can be less accurate.
|
||||||
|
|
||||||
|
I ran OpenSeeFace on a sample clip from the video presentation for [3D Face Reconstruction with Dense Landmarks](https://microsoft.github.io/DenseLandmarks/) by Wood et al. to compare it to MediaPipe and their approach. You can watch the result [here](https://www.vseeface.icu/assets/media/OSFMediaPipe3DFR.mp4).
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
|
A sample Unity project for VRM based avatar animation can be found [here](https://github.com/emilianavt/OpenSeeFaceSample).
|
||||||
|
|
||||||
The face tracking itself is done by the `facetracker.py` Python 3.7 script. It is a commandline program, so you should start it manually from cmd or write a batch file to start it. If you downloaded a release and are on Windows, you can run the `facetracker.exe` inside the `Binary` folder without having Python installed. You can also use the `run.bat` inside the `Binary` folder for a basic demonstration of the tracker.
|
The face tracking itself is done by the `facetracker.py` Python 3.7 script. It is a commandline program, so you should start it manually from cmd or write a batch file to start it. If you downloaded a release and are on Windows, you can run the `facetracker.exe` inside the `Binary` folder without having Python installed. You can also use the `run.bat` inside the `Binary` folder for a basic demonstration of the tracker.
|
||||||
|
|
||||||
The script will perform the tracking on webcam input or video file and send the tracking data over UDP. This design also allows tracking to be done on a separate PC from the one who uses the tracking information. This can be useful to enhance performance and to avoid accidentially revealing camera footage.
|
The script will perform the tracking on webcam input or video file and send the tracking data over UDP. This design also allows tracking to be done on a separate PC from the one who uses the tracking information. This can be useful to enhance performance and to avoid accidentially revealing camera footage.
|
||||||
|
|
@ -28,6 +38,8 @@ A simple demonstration can be achieved by creating a new scene in Unity, adding
|
||||||
|
|
||||||
python facetracker.py --visualize 3 --pnp-points 1 --max-threads 4 -c video.mp4
|
python facetracker.py --visualize 3 --pnp-points 1 --max-threads 4 -c video.mp4
|
||||||
|
|
||||||
|
__Note__: If dependencies were installed using [poetry](https://python-poetry.org/), the commands have to be executed from a `poetry shell` or have to be prefixed with `poetry run`.
|
||||||
|
|
||||||
This way the tracking script will output its own tracking visualization while also demonstrating the transmission of tracking data to Unity.
|
This way the tracking script will output its own tracking visualization while also demonstrating the transmission of tracking data to Unity.
|
||||||
|
|
||||||
The included `OpenSeeLauncher` component allows starting the face tracker program from Unity. It is designed to work with the pyinstaller created executable distributed in the binary release bundles. It provides three public API functions:
|
The included `OpenSeeLauncher` component allows starting the face tracker program from Unity. It is designed to work with the pyinstaller created executable distributed in the binary release bundles. It provides three public API functions:
|
||||||
|
|
@ -89,7 +101,7 @@ Four pretrained face landmark models are included. Using the `--model` switch, i
|
||||||
|
|
||||||
FPS measurements are from running on one core of my CPU.
|
FPS measurements are from running on one core of my CPU.
|
||||||
|
|
||||||
Pytorch weights for use with `model.py` can be found [here](https://mega.nz/file/vvYXlYQT#h7FpEg4tmOCJNxjpsDEw0JomJIkVGKwrt4OUV0RNDDU).
|
Pytorch weights for use with `model.py` can be found [here](https://mega.nz/file/vvYXlYQT#h7FpEg4tmOCJNxjpsDEw0JomJIkVGKwrt4OUV0RNDDU). Some unoptimized ONNX models can be found [here](https://github.com/emilianavt/OpenSeeFace/issues/48).
|
||||||
|
|
||||||
# Results
|
# Results
|
||||||
|
|
||||||
|
|
@ -117,9 +129,8 @@ When distributing it, you should also distribute the `Licenses` folder along wit
|
||||||
|
|
||||||
The release builds contain a custom build of ONNX Runtime without telemetry.
|
The release builds contain a custom build of ONNX Runtime without telemetry.
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies (Python 3.6 - 3.9)
|
||||||
|
|
||||||
* Python 3.7
|
|
||||||
* ONNX Runtime
|
* ONNX Runtime
|
||||||
* OpenCV
|
* OpenCV
|
||||||
* Pillow
|
* Pillow
|
||||||
|
|
@ -129,6 +140,22 @@ The required libraries can be installed using pip:
|
||||||
|
|
||||||
pip install onnxruntime opencv-python pillow numpy
|
pip install onnxruntime opencv-python pillow numpy
|
||||||
|
|
||||||
|
Alternatively poetry can be used to
|
||||||
|
install all dependencies for this project in a separate virtual env:
|
||||||
|
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
* onnxruntime
|
||||||
|
* OpenCV
|
||||||
|
* Pillow
|
||||||
|
* Numpy
|
||||||
|
|
||||||
|
The required libraries can be installed using pip:
|
||||||
|
|
||||||
|
pip install onnxruntime opencv-python pillow numpy
|
||||||
|
|
||||||
# References
|
# References
|
||||||
|
|
||||||
## Training dataset
|
## Training dataset
|
||||||
|
|
@ -154,6 +181,8 @@ Additional training has been done on the WFLW dataset after reducing it to 66 po
|
||||||
|
|
||||||
For the training the gaze and blink detection model, the [MPIIGaze](https://www.mpi-inf.mpg.de/departments/computer-vision-and-machine-learning/research/gaze-based-human-computer-interaction/appearance-based-gaze-estimation-in-the-wild/) dataset was used. Additionally, around 125000 synthetic eyes generated with [UnityEyes](https://www.cl.cam.ac.uk/research/rainbow/projects/unityeyes/) were used during training.
|
For the training the gaze and blink detection model, the [MPIIGaze](https://www.mpi-inf.mpg.de/departments/computer-vision-and-machine-learning/research/gaze-based-human-computer-interaction/appearance-based-gaze-estimation-in-the-wild/) dataset was used. Additionally, around 125000 synthetic eyes generated with [UnityEyes](https://www.cl.cam.ac.uk/research/rainbow/projects/unityeyes/) were used during training.
|
||||||
|
|
||||||
|
It should be noted that additional custom data was also used during the training process and that the reference landmarks from the original datasets have been modified in certain ways to address various issues. It is likely not possible to reproduce these models with just the original LS3D-W and WFLW datasets, however the additional data is not redistributable.
|
||||||
|
|
||||||
The heatmap regression based face detection model was trained on random 224x224 crops from the WIDER FACE dataset.
|
The heatmap regression based face detection model was trained on random 224x224 crops from the WIDER FACE dataset.
|
||||||
|
|
||||||
@inproceedings{yang2016wider,
|
@inproceedings{yang2016wider,
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ public class OpenSee : MonoBehaviour {
|
||||||
public float rightEyeOpen;
|
public float rightEyeOpen;
|
||||||
[Tooltip("This field tells you how likely it is that the left eye is open.")]
|
[Tooltip("This field tells you how likely it is that the left eye is open.")]
|
||||||
public float leftEyeOpen;
|
public float leftEyeOpen;
|
||||||
[Tooltip("This field tells you how likely it is that the right eye is open.")]
|
[Tooltip("This field contains the rotation of the right eyeball.")]
|
||||||
public Quaternion rightGaze;
|
public Quaternion rightGaze;
|
||||||
[Tooltip("This field tells you how likely it is that the left eye is open.")]
|
[Tooltip("This field contains the rotation of the left eyeball.")]
|
||||||
public Quaternion leftGaze;
|
public Quaternion leftGaze;
|
||||||
[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.")]
|
[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;
|
public bool got3DPoints;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
public OpenSee openSee;
|
public OpenSee openSee;
|
||||||
[Tooltip("When this is set to another game object, its kinematic interpolation component will get updated from values of this.")]
|
[Tooltip("When this is set to another game object, its kinematic interpolation component will get updated from values of this.")]
|
||||||
public OpenSeeKinematicInterpolation kinematicInterpolation;
|
public OpenSeeKinematicInterpolation kinematicInterpolation;
|
||||||
|
[Tooltip("When set, the this object will follow the specified transform instead and automatically recalibrate based on it.")]
|
||||||
|
public Transform externalTarget = null;
|
||||||
[Tooltip("This can be used to skip the first few tracking frames.")]
|
[Tooltip("This can be used to skip the first few tracking frames.")]
|
||||||
public int skipFirst = 5;
|
public int skipFirst = 5;
|
||||||
[Tooltip("When set to true, the next rotation and translation values will be set as zero rotation and zero translation.")]
|
[Tooltip("When set to true, the next rotation and translation values will be set as zero rotation and zero translation.")]
|
||||||
public bool calibrate = true;
|
public bool calibrate = true;
|
||||||
[Tooltip("This sets which face id to look for in the OpenSee data.")]
|
[Tooltip("This sets which face id to look for in the OpenSee data.")]
|
||||||
public int faceId = 0;
|
public int faceId = 0;
|
||||||
|
[Tooltip("When enabled, the IK target will not move up and down.")]
|
||||||
|
public bool preventVerticalMotion = false;
|
||||||
[Tooltip("When this is enabled, rotations and movement are mirrored.")]
|
[Tooltip("When this is enabled, rotations and movement are mirrored.")]
|
||||||
public bool mirrorMotion = false;
|
public bool mirrorMotion = false;
|
||||||
[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.")]
|
[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.")]
|
||||||
|
|
@ -23,8 +27,6 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
[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.")]
|
[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.")]
|
||||||
[Range(0, 1)]
|
[Range(0, 1)]
|
||||||
public float smooth = 0.2f;
|
public float smooth = 0.2f;
|
||||||
[Tooltip("When set to true, a Kalman filter is used for smoothing.")]
|
|
||||||
public bool kalman = false;
|
|
||||||
[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.")]
|
[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;
|
public bool interpolate = true;
|
||||||
[Tooltip("When set to true, the transform will be updated in FixedUpdate, otherwise it will be updated in Update.")]
|
[Tooltip("When set to true, the transform will be updated in FixedUpdate, otherwise it will be updated in Update.")]
|
||||||
|
|
@ -85,17 +87,7 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
private bool skippedPosition = false;
|
private bool skippedPosition = false;
|
||||||
private float skipStartTimePosition = -1f;
|
private float skipStartTimePosition = -1f;
|
||||||
|
|
||||||
// Kalman
|
private Transform lastExternalTarget = null;
|
||||||
private float frameTime = 0f;
|
|
||||||
private bool kalmanInit = false;
|
|
||||||
private float kalmanSmooth = -1f;
|
|
||||||
private KF kalmanPVX;
|
|
||||||
private KF kalmanPVY;
|
|
||||||
private KF kalmanPVZ;
|
|
||||||
private KF kalmanRVX;
|
|
||||||
private KF kalmanRVY;
|
|
||||||
private KF kalmanRVZ;
|
|
||||||
private Vector3 kalmanLastRot = Vector3.zero;
|
|
||||||
|
|
||||||
public void Calibrate() {
|
public void Calibrate() {
|
||||||
calibrate = true;
|
calibrate = true;
|
||||||
|
|
@ -119,30 +111,11 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 FilterPos(Vector3 a, Vector3 b) {
|
Vector3 FilterPos(Vector3 a, Vector3 b) {
|
||||||
if (!kalman || frameTime > 1f)
|
return Vector3.Lerp(a, b, 1f - smooth);
|
||||||
return Vector3.Lerp(a, b, 1f - smooth);
|
|
||||||
else {
|
|
||||||
Vector3 v = (b - a) / frameTime;
|
|
||||||
v.x = kalmanPVX.Update(v.x);
|
|
||||||
v.y = kalmanPVY.Update(v.y);
|
|
||||||
v.z = kalmanPVZ.Update(v.z);
|
|
||||||
return a + v * frameTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Quaternion FilterRot(Quaternion a, Quaternion b) {
|
Quaternion FilterRot(Quaternion a, Quaternion b) {
|
||||||
if (!kalman || frameTime > 1f)
|
return Quaternion.Lerp(a, b, 1f - smooth);
|
||||||
return Quaternion.Lerp(a, b, 1f - smooth);
|
|
||||||
else {
|
|
||||||
Vector3 rotA = PrettyEuler(kalmanLastRot, a.eulerAngles);
|
|
||||||
Vector3 rotB = PrettyEuler(rotA, b.eulerAngles);
|
|
||||||
kalmanLastRot = rotA;
|
|
||||||
Vector3 v = Mathf.Deg2Rad * (rotB - rotA) / frameTime;
|
|
||||||
v.x = kalmanRVX.Update(v.x);
|
|
||||||
v.y = kalmanRVY.Update(v.y);
|
|
||||||
v.z = kalmanRVZ.Update(v.z);
|
|
||||||
return Quaternion.Euler(rotA + Mathf.Rad2Deg * v * frameTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpolate() {
|
void Interpolate() {
|
||||||
|
|
@ -166,44 +139,34 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
|
|
||||||
void RunUpdate() {
|
void RunUpdate() {
|
||||||
var openSeeData = openSee.GetOpenSeeData(faceId);
|
var openSeeData = openSee.GetOpenSeeData(faceId);
|
||||||
if (openSeeData == null || openSeeData.fit3DError > openSee.maxFit3DError)
|
Quaternion convertedQuaternion;
|
||||||
return;
|
Vector3 t;
|
||||||
if (openSeeData.time > updated) {
|
Vector3 convertedTranslation;
|
||||||
frameTime = (float)(openSeeData.time - updated);
|
|
||||||
updated = openSeeData.time;
|
if (externalTarget == null) {
|
||||||
trackingFrames++;
|
if (openSeeData == null || openSeeData.fit3DError > openSee.maxFit3DError)
|
||||||
if (trackingFrames < skipFirst)
|
|
||||||
return;
|
return;
|
||||||
|
if (openSeeData.time > updated) {
|
||||||
|
updated = openSeeData.time;
|
||||||
|
trackingFrames++;
|
||||||
|
if (trackingFrames < skipFirst)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Interpolate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedQuaternion = new Quaternion(-openSeeData.rawQuaternion.y, -openSeeData.rawQuaternion.x, openSeeData.rawQuaternion.z, openSeeData.rawQuaternion.w);
|
||||||
|
t = openSeeData.translation;
|
||||||
|
t.x = -t.x;
|
||||||
|
t.z = -t.z;
|
||||||
|
convertedTranslation = new Vector3(t.x, t.y, t.z);
|
||||||
} else {
|
} else {
|
||||||
Interpolate();
|
convertedQuaternion = externalTarget.localRotation;
|
||||||
return;
|
convertedTranslation = externalTarget.localPosition;
|
||||||
|
t = convertedTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kalmanInit) {
|
|
||||||
kalmanPVX = new KF(smooth);
|
|
||||||
kalmanPVY = new KF(smooth);
|
|
||||||
kalmanPVZ = new KF(smooth);
|
|
||||||
kalmanRVX = new KF(smooth);
|
|
||||||
kalmanRVY = new KF(smooth);
|
|
||||||
kalmanRVZ = new KF(smooth);
|
|
||||||
kalmanInit = true;
|
|
||||||
}
|
|
||||||
if (smooth != kalmanSmooth) {
|
|
||||||
kalmanPVX.SetSmoothness(smooth);
|
|
||||||
kalmanPVY.SetSmoothness(smooth);
|
|
||||||
kalmanPVZ.SetSmoothness(smooth);
|
|
||||||
kalmanRVX.SetSmoothness(smooth);
|
|
||||||
kalmanRVY.SetSmoothness(smooth);
|
|
||||||
kalmanRVZ.SetSmoothness(smooth);
|
|
||||||
kalmanSmooth = smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quaternion convertedQuaternion = new Quaternion(-openSeeData.rawQuaternion.y, -openSeeData.rawQuaternion.x, openSeeData.rawQuaternion.z, openSeeData.rawQuaternion.w);
|
|
||||||
Vector3 t = openSeeData.translation;
|
|
||||||
t.x = -t.x;
|
|
||||||
t.z = -t.z;
|
|
||||||
Vector3 convertedTranslation = new Vector3(t.x, t.y, t.z);
|
|
||||||
|
|
||||||
// Check for angular outliers
|
// Check for angular outliers
|
||||||
if (gotAccepted) {
|
if (gotAccepted) {
|
||||||
float angularDifference = Quaternion.Angle(lastAccepted, convertedQuaternion);
|
float angularDifference = Quaternion.Angle(lastAccepted, convertedQuaternion);
|
||||||
|
|
@ -268,6 +231,9 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calibrate = calibrate || (externalTarget != lastExternalTarget);
|
||||||
|
lastExternalTarget = externalTarget;
|
||||||
|
|
||||||
if (calibrate) {
|
if (calibrate) {
|
||||||
dR = convertedQuaternion;
|
dR = convertedQuaternion;
|
||||||
dR = Quaternion.Inverse(dR);
|
dR = Quaternion.Inverse(dR);
|
||||||
|
|
@ -302,7 +268,7 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
lastT = transform.localPosition;
|
lastT = transform.localPosition;
|
||||||
lastR = transform.localRotation;
|
lastR = transform.localRotation;
|
||||||
if (interpolateState > 0) {
|
if (interpolateState > 0) {
|
||||||
currentT = FilterPos(currentT, (t - dT) * translationScale);
|
currentT = FilterPos(transform.localPosition, (t - dT) * translationScale);
|
||||||
currentR = FilterRot(transform.localRotation, convertedQuaternion * dR);
|
currentR = FilterRot(transform.localRotation, convertedQuaternion * dR);
|
||||||
} else {
|
} else {
|
||||||
currentT = (t - dT) * translationScale;
|
currentT = (t - dT) * translationScale;
|
||||||
|
|
@ -317,6 +283,14 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
transform.localPosition = FilterPos(transform.localPosition, (t - dT) * translationScale);
|
transform.localPosition = FilterPos(transform.localPosition, (t - dT) * translationScale);
|
||||||
transform.localRotation = FilterRot(transform.localRotation, convertedQuaternion * dR);
|
transform.localRotation = FilterRot(transform.localRotation, convertedQuaternion * dR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preventVerticalMotion) {
|
||||||
|
currentT.y = 0f;
|
||||||
|
Vector3 pos = transform.localPosition;
|
||||||
|
pos.y = 0f;
|
||||||
|
transform.localPosition = pos;
|
||||||
|
}
|
||||||
|
|
||||||
if (kinematicInterpolation != null && kinematicInterpolation.gameObject != gameObject) {
|
if (kinematicInterpolation != null && kinematicInterpolation.gameObject != gameObject) {
|
||||||
if (interpolate)
|
if (interpolate)
|
||||||
kinematicInterpolation.UpdateKI(updated, currentT, currentR);
|
kinematicInterpolation.UpdateKI(updated, currentT, currentR);
|
||||||
|
|
@ -337,6 +311,10 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetPreventVerticalMotion(bool v) {
|
||||||
|
preventVerticalMotion = v;
|
||||||
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
{
|
{
|
||||||
if (fixedUpdate)
|
if (fixedUpdate)
|
||||||
|
|
@ -348,34 +326,6 @@ public class OpenSeeIKTarget : MonoBehaviour
|
||||||
if (!fixedUpdate)
|
if (!fixedUpdate)
|
||||||
RunUpdate();
|
RunUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
class KF {
|
|
||||||
private float R, P, Q, x;
|
|
||||||
|
|
||||||
public KF(float smooth) {
|
|
||||||
P = 1f;
|
|
||||||
x = 0f;
|
|
||||||
SetSmoothness(smooth);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSmoothness(float smooth) {
|
|
||||||
P = 1f;
|
|
||||||
Q = 0.3f;// * (Mathf.Exp(3f * smooth) + 1f);
|
|
||||||
R = 0.000001f + Mathf.Exp(smooth * 5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float Update(float input) {
|
|
||||||
//x = x * A;
|
|
||||||
//P = A * P * A;
|
|
||||||
|
|
||||||
float K = P / (P + R);
|
|
||||||
x = x + K * (input - x);
|
|
||||||
//Debug.Log("P = " + P + " 1-K = " + (1f - K));
|
|
||||||
P = (1f - K) * P + Q;
|
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
defaultReferences: []
|
defaultReferences: []
|
||||||
executionOrder: -1
|
executionOrder: -5
|
||||||
icon: {instanceID: 0}
|
icon: {instanceID: 0}
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
|
|
|
||||||
232
Unity/OpenSeeInterpolation.cs
Normal file
232
Unity/OpenSeeInterpolation.cs
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace OpenSee {
|
||||||
|
public class InterpolatedMap<T, V, U> where V : InterpolatedValue<U>, new() {
|
||||||
|
private Dictionary<T, V> map = new Dictionary<T, V>();
|
||||||
|
private float clamp = 1f;
|
||||||
|
private float smoothing = 0f;
|
||||||
|
private bool gotDefault = false;
|
||||||
|
private U defaultValue;
|
||||||
|
|
||||||
|
public void Clear() {
|
||||||
|
map.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSmoothing(float smoothing) {
|
||||||
|
this.smoothing = smoothing;
|
||||||
|
foreach (var entry in map) {
|
||||||
|
if (entry.Value != null)
|
||||||
|
entry.Value.SetSmoothing(smoothing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetClamp(float clamp) {
|
||||||
|
this.clamp = clamp;
|
||||||
|
foreach (var entry in map) {
|
||||||
|
if (entry.Value != null)
|
||||||
|
entry.Value.SetClamp(clamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prepare(T key) {
|
||||||
|
if (!map.ContainsKey(key)) {
|
||||||
|
map.Add(key, new V());
|
||||||
|
map[key].SetSmoothing(smoothing);
|
||||||
|
map[key].SetClamp(clamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(T key, U v) {
|
||||||
|
Prepare(key);
|
||||||
|
map[key].SetValue(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Store(T key, U v) {
|
||||||
|
Prepare(key);
|
||||||
|
map[key].UpdateValue(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Store(T key, U v, double nowT) {
|
||||||
|
if (!map.ContainsKey(key)) {
|
||||||
|
map.Add(key, new V());
|
||||||
|
map[key].SetSmoothing(smoothing);
|
||||||
|
map[key].SetClamp(clamp);
|
||||||
|
}
|
||||||
|
map[key].UpdateValue(v, nowT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public U GetDefault() {
|
||||||
|
if (gotDefault)
|
||||||
|
return defaultValue;
|
||||||
|
V tmp = new V();
|
||||||
|
defaultValue = tmp.GetDefault();
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Check(T key) {
|
||||||
|
return map.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public U Get(T key) {
|
||||||
|
if (!map.ContainsKey(key))
|
||||||
|
return GetDefault();
|
||||||
|
return map[key].Interpolate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public class InterpolatedValue<V> {
|
||||||
|
private float interpolateT = 0f;
|
||||||
|
private float updateT = 0f;
|
||||||
|
private double lastT = 0f;
|
||||||
|
private double currentT = 0f;
|
||||||
|
private float clamp = 1f;
|
||||||
|
private float smoothing = 0f;
|
||||||
|
private int gotData = 0;
|
||||||
|
|
||||||
|
private bool haveDefault = false;
|
||||||
|
private V defaultValue;
|
||||||
|
|
||||||
|
private bool valueSet = false;
|
||||||
|
private V dataValue;
|
||||||
|
private V dataValueLast;
|
||||||
|
|
||||||
|
abstract public V ValueLerpUnclamped(V a, V b, float t);
|
||||||
|
abstract public V GetBuiltinDefault();
|
||||||
|
|
||||||
|
public void SetDefault(V defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
haveDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetDefault() {
|
||||||
|
haveDefault = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V GetDefault() {
|
||||||
|
if (haveDefault)
|
||||||
|
return defaultValue;
|
||||||
|
return GetBuiltinDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSmoothing(float smoothing) {
|
||||||
|
this.smoothing = smoothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetClamp(float clamp) {
|
||||||
|
this.clamp = clamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(V v) {
|
||||||
|
dataValueLast = v;
|
||||||
|
dataValue = v;
|
||||||
|
valueSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InterpolatedValue(float smoothing = 0f, float clamp = 1f) {
|
||||||
|
this.smoothing = smoothing;
|
||||||
|
this.clamp = clamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateValue(V v) {
|
||||||
|
UpdateValue(v, Time.realtimeSinceStartup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateValue(V v, double nowT) {
|
||||||
|
if (nowT < 0f)
|
||||||
|
return;
|
||||||
|
lock (this) {
|
||||||
|
if (nowT < currentT || gotData == 0) {
|
||||||
|
// Reinitialize
|
||||||
|
gotData = 1;
|
||||||
|
updateT = Time.time;
|
||||||
|
lastT = 0f;
|
||||||
|
currentT = nowT;
|
||||||
|
dataValueLast = GetDefault();
|
||||||
|
dataValue = ValueLerpUnclamped(v, dataValueLast, smoothing);
|
||||||
|
} else if (nowT == currentT) {
|
||||||
|
// Change value
|
||||||
|
dataValue = ValueLerpUnclamped(v, dataValueLast, smoothing);
|
||||||
|
} else {
|
||||||
|
// Regular operation
|
||||||
|
updateT = Time.time;
|
||||||
|
lastT = currentT;
|
||||||
|
currentT = nowT;
|
||||||
|
if (gotData < 2)
|
||||||
|
gotData++;
|
||||||
|
dataValueLast = dataValue;
|
||||||
|
dataValue = ValueLerpUnclamped(v, dataValueLast, smoothing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public V Interpolate() {
|
||||||
|
lock (this) {
|
||||||
|
interpolateT = Time.time;
|
||||||
|
if (gotData < 2) {
|
||||||
|
if (valueSet)
|
||||||
|
return dataValue;
|
||||||
|
else
|
||||||
|
return GetDefault();
|
||||||
|
}
|
||||||
|
float t = Mathf.Min((interpolateT - updateT) / (float)(currentT - lastT), clamp);
|
||||||
|
return ValueLerpUnclamped(dataValueLast, dataValue, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedColor : InterpolatedValue<Color> {
|
||||||
|
public override Color ValueLerpUnclamped(Color a, Color b, float t) {
|
||||||
|
return Color.LerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override Color GetBuiltinDefault() {
|
||||||
|
return Color.clear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedQuaternion : InterpolatedValue<Quaternion> {
|
||||||
|
public override Quaternion ValueLerpUnclamped(Quaternion a, Quaternion b, float t) {
|
||||||
|
return Quaternion.SlerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override Quaternion GetBuiltinDefault() {
|
||||||
|
return Quaternion.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedVector2 : InterpolatedValue<Vector2> {
|
||||||
|
public override Vector2 ValueLerpUnclamped(Vector2 a, Vector2 b, float t) {
|
||||||
|
return Vector2.LerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override Vector2 GetBuiltinDefault() {
|
||||||
|
return Vector2.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedVector3 : InterpolatedValue<Vector3> {
|
||||||
|
public override Vector3 ValueLerpUnclamped(Vector3 a, Vector3 b, float t) {
|
||||||
|
return Vector3.LerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override Vector3 GetBuiltinDefault() {
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedVector4 : InterpolatedValue<Vector4> {
|
||||||
|
public override Vector4 ValueLerpUnclamped(Vector4 a, Vector4 b, float t) {
|
||||||
|
return Vector4.LerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override Vector4 GetBuiltinDefault() {
|
||||||
|
return Vector4.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InterpolatedFloat : InterpolatedValue<float> {
|
||||||
|
public override float ValueLerpUnclamped(float a, float b, float t) {
|
||||||
|
return Mathf.LerpUnclamped(a, b, t);
|
||||||
|
}
|
||||||
|
public override float GetBuiltinDefault() {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Unity/OpenSeeInterpolation.cs.meta
Normal file
11
Unity/OpenSeeInterpolation.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d61e11f1b6900a7419383be1377f191b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -7,6 +7,8 @@ namespace OpenSee {
|
||||||
|
|
||||||
public class OpenSeeKinematicInterpolation : MonoBehaviour
|
public class OpenSeeKinematicInterpolation : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
public float interpolateClamp = 1f;
|
||||||
|
|
||||||
private Vector3 lastPos;
|
private Vector3 lastPos;
|
||||||
private Vector3 lastPosV;
|
private Vector3 lastPosV;
|
||||||
private double lastT;
|
private double lastT;
|
||||||
|
|
@ -29,15 +31,19 @@ public class OpenSeeKinematicInterpolation : MonoBehaviour
|
||||||
|
|
||||||
private double updateT;
|
private double updateT;
|
||||||
private double interpolateT;
|
private double interpolateT;
|
||||||
|
private int ready = 0;
|
||||||
|
|
||||||
public void UpdateKI(double newT, Vector3 newPos, Quaternion newRot) {
|
public void UpdateKI(double newT, Vector3 newPos, Quaternion newRot) {
|
||||||
Interpolate();
|
Interpolate();
|
||||||
|
if (newT - currentT > 3f)
|
||||||
|
ready = 0;
|
||||||
|
|
||||||
currentPos = transform.localPosition;
|
currentPos = transform.localPosition;
|
||||||
currentRot = OpenSeeIKTarget.PrettyEuler(currentRot, transform.localRotation.eulerAngles);
|
currentRot = OpenSeeIKTarget.PrettyEuler(currentRot, transform.localRotation.eulerAngles);
|
||||||
float dTNow = (float)(interpolateT - updateT);
|
float dTNow = (float)(interpolateT - updateT);
|
||||||
if (interpolateT - updateT > 0) {
|
if (interpolateT - updateT > 0 || ready < 1) {
|
||||||
currentPosV = (currentPos - lastPos) / dTNow;
|
currentPosV = (currentPos - lastPos) / dT;
|
||||||
currentRotV = (currentRot - lastRot) / dTNow;
|
currentRotV = (currentRot - lastRot) / dT;
|
||||||
} else {
|
} else {
|
||||||
currentPosV = Vector3.zero;
|
currentPosV = Vector3.zero;
|
||||||
currentRotV = Vector3.zero;
|
currentRotV = Vector3.zero;
|
||||||
|
|
@ -64,6 +70,8 @@ public class OpenSeeKinematicInterpolation : MonoBehaviour
|
||||||
coeffRotB = ((6f*(currentRotV * dT + dT * lastRotV - 2f * currentRot + 2f * lastRot)) / dTPow3)/6f;
|
coeffRotB = ((6f*(currentRotV * dT + dT * lastRotV - 2f * currentRot + 2f * lastRot)) / dTPow3)/6f;
|
||||||
|
|
||||||
updateT = Time.time;
|
updateT = Time.time;
|
||||||
|
if (ready <= 2)
|
||||||
|
ready++;
|
||||||
}
|
}
|
||||||
|
|
||||||
float InterpolateValue(float dTNow, float dTNowPow2, float dTNowPow3, float a, float b, float x1, float x2, float v1, float v2) {
|
float InterpolateValue(float dTNow, float dTNowPow2, float dTNowPow3, float a, float b, float x1, float x2, float v1, float v2) {
|
||||||
|
|
@ -72,12 +80,12 @@ public class OpenSeeKinematicInterpolation : MonoBehaviour
|
||||||
|
|
||||||
void Interpolate() {
|
void Interpolate() {
|
||||||
interpolateT = Time.time;
|
interpolateT = Time.time;
|
||||||
if (interpolateT == updateT)
|
if (interpolateT == updateT || ready <= 2)
|
||||||
return;
|
return;
|
||||||
float dTNow = (float)(interpolateT - updateT);
|
float dTNow = (float)(interpolateT - updateT);
|
||||||
if (dTNow >= dT) {
|
if (dTNow >= dT) {
|
||||||
transform.localPosition = Vector3.LerpUnclamped(lastPos, currentPos, Mathf.Min((float)(dTNow / dT), 2f));
|
transform.localPosition = Vector3.LerpUnclamped(lastPos, currentPos, Mathf.Min((float)(dTNow / dT), interpolateClamp));
|
||||||
transform.localEulerAngles = Vector3.LerpUnclamped(lastRot, currentRot, Mathf.Min((float)(dTNow / dT), 2f));
|
transform.localEulerAngles = Vector3.LerpUnclamped(lastRot, currentRot, Mathf.Min((float)(dTNow / dT), interpolateClamp));
|
||||||
} else {
|
} else {
|
||||||
float dTNowPow2 = Mathf.Pow(dTNow, 2);
|
float dTNowPow2 = Mathf.Pow(dTNow, 2);
|
||||||
float dTNowPow3 = Mathf.Pow(dTNow, 3);
|
float dTNowPow3 = Mathf.Pow(dTNow, 3);
|
||||||
|
|
@ -92,7 +100,7 @@ public class OpenSeeKinematicInterpolation : MonoBehaviour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LateUpdate() {
|
public void Update() {
|
||||||
Interpolate();
|
Interpolate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
defaultReferences: []
|
defaultReferences: []
|
||||||
executionOrder: 0
|
executionOrder: -1
|
||||||
icon: {instanceID: 0}
|
icon: {instanceID: 0}
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ public class OpenSeeLauncher : MonoBehaviour {
|
||||||
[Tooltip("The path, where the .onnx model files can be found")]
|
[Tooltip("The path, where the .onnx model files can be found")]
|
||||||
public string modelPath = "models\\";
|
public string modelPath = "models\\";
|
||||||
[Tooltip("Additional options that should be passed to the face tracker.")]
|
[Tooltip("Additional options that should be passed to the face tracker.")]
|
||||||
public List<string> commandlineOptions = new List<string>(new string[] { "--silent", "1", "--max-threads", "4" });
|
public List<string> commandlineOptions = new List<string>(new string[] { "--silent", "1", "--max-threads", "1" });
|
||||||
[Tooltip("This string will be appended at the end of the tracker options without quoting.")]
|
[Tooltip("This string will be appended at the end of the tracker options without quoting.")]
|
||||||
public string extraOptions = "";
|
public string extraOptions = "";
|
||||||
[Tooltip("If enabled, the tracker commandline option string will be logged with Debug.Log.")]
|
[Tooltip("If enabled, the tracker commandline option string will be logged with Debug.Log.")]
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -77,7 +77,7 @@ class DShowCapture():
|
||||||
bm_lib.stop_capture_single.argtypes = []
|
bm_lib.stop_capture_single.argtypes = []
|
||||||
bm_lib.get_json_length.argtypes = []
|
bm_lib.get_json_length.argtypes = []
|
||||||
bm_lib.get_json.argtypes = [c_char_p, c_int]
|
bm_lib.get_json.argtypes = [c_char_p, c_int]
|
||||||
if not bm_options is None:
|
if bm_options is not None:
|
||||||
bm_lib.set_options.argtypes = [c_char_p]
|
bm_lib.set_options.argtypes = [c_char_p]
|
||||||
bm_lib.set_options(bm_options.encode())
|
bm_lib.set_options(bm_options.encode())
|
||||||
self.lib = lib
|
self.lib = lib
|
||||||
|
|
@ -93,7 +93,7 @@ class DShowCapture():
|
||||||
self.type = None
|
self.type = None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self.buffer is None:
|
if self.buffer is not None:
|
||||||
del self.buffer
|
del self.buffer
|
||||||
del self.name_buffer
|
del self.name_buffer
|
||||||
self.destroy_capture()
|
self.destroy_capture()
|
||||||
|
|
@ -104,7 +104,7 @@ class DShowCapture():
|
||||||
|
|
||||||
def get_device(self, device_number):
|
def get_device(self, device_number):
|
||||||
self.lib.get_device(self.cap, device_number, self.name_buffer, 255)
|
self.lib.get_device(self.cap, device_number, self.name_buffer, 255)
|
||||||
name_str = str(self.name_buffer.value.decode('utf8'))
|
name_str = str(self.name_buffer.value.decode('utf8', 'surrogateescape'))
|
||||||
return name_str
|
return name_str
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
|
|
@ -113,7 +113,7 @@ class DShowCapture():
|
||||||
json_length = self.lib.get_json_length(self.cap);
|
json_length = self.lib.get_json_length(self.cap);
|
||||||
json_buffer = create_string_buffer(json_length)
|
json_buffer = create_string_buffer(json_length)
|
||||||
self.lib.get_json(self.cap, json_buffer, json_length);
|
self.lib.get_json(self.cap, json_buffer, json_length);
|
||||||
json_text = str(json_buffer.value.decode('utf8'))
|
json_text = str(json_buffer.value.decode('utf8', 'surrogateescape'))
|
||||||
self.info = json.loads(json_text)
|
self.info = json.loads(json_text)
|
||||||
for cam in self.info:
|
for cam in self.info:
|
||||||
cam["type"] = "DirectShow"
|
cam["type"] = "DirectShow"
|
||||||
|
|
@ -124,7 +124,7 @@ class DShowCapture():
|
||||||
json_length = self.bm_lib.get_json_length();
|
json_length = self.bm_lib.get_json_length();
|
||||||
json_buffer = create_string_buffer(json_length)
|
json_buffer = create_string_buffer(json_length)
|
||||||
self.bm_lib.get_json(json_buffer, json_length);
|
self.bm_lib.get_json(json_buffer, json_length);
|
||||||
json_text = str(json_buffer.value.decode('utf8'))
|
json_text = str(json_buffer.value.decode('utf8', 'surrogateescape'))
|
||||||
bm_info = json.loads(json_text)
|
bm_info = json.loads(json_text)
|
||||||
dshow_len = len(self.info)
|
dshow_len = len(self.info)
|
||||||
for cam in bm_info:
|
for cam in bm_info:
|
||||||
|
|
@ -329,7 +329,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
img = cap.get_frame(1000)
|
img = cap.get_frame(1000)
|
||||||
if not img is None:
|
if img is not None:
|
||||||
cv2.imshow("DShowCapture", img)
|
cv2.imshow("DShowCapture", img)
|
||||||
if cv2.waitKey(1) & 0xFF == ord('q'):
|
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -13,13 +13,16 @@ if os.name == 'nt':
|
||||||
parser.add_argument("-a", "--list-dcaps", type=int, help="Set this to -1 to list all cameras and their available capabilities, set this to a camera id to list that camera's capabilities", default=None)
|
parser.add_argument("-a", "--list-dcaps", type=int, help="Set this to -1 to list all cameras and their available capabilities, set this to a camera id to list that camera's capabilities", default=None)
|
||||||
parser.add_argument("-W", "--width", type=int, help="Set camera and raw RGB width", default=640)
|
parser.add_argument("-W", "--width", type=int, help="Set camera and raw RGB width", default=640)
|
||||||
parser.add_argument("-H", "--height", type=int, help="Set camera and raw RGB height", default=360)
|
parser.add_argument("-H", "--height", type=int, help="Set camera and raw RGB height", default=360)
|
||||||
parser.add_argument("-F", "--fps", type=int, help="Set camera frames per second", default=24)
|
parser.add_argument("-D", "--dcap", type=int, help="Set which device capability line to use or -1 to use the default camera settings (FPS still need to be set separately)", default=None)
|
||||||
parser.add_argument("-D", "--dcap", type=int, help="Set which device capability line to use or -1 to use the default camera settings", default=None)
|
|
||||||
parser.add_argument("-B", "--blackmagic", type=int, help="When set to 1, special support for Blackmagic devices is enabled", default=0)
|
parser.add_argument("-B", "--blackmagic", type=int, help="When set to 1, special support for Blackmagic devices is enabled", default=0)
|
||||||
else:
|
else:
|
||||||
parser.add_argument("-W", "--width", type=int, help="Set raw RGB width", default=640)
|
parser.add_argument("-W", "--width", type=int, help="Set raw RGB width", default=640)
|
||||||
parser.add_argument("-H", "--height", type=int, help="Set raw RGB height", default=360)
|
parser.add_argument("-H", "--height", type=int, help="Set raw RGB height", default=360)
|
||||||
|
if sys.platform == 'linux':
|
||||||
|
parser.add_argument("--dformat", type=str, help="Set which device format to use (MJPG, YUYV, RGB3, ...)", default=None)
|
||||||
|
parser.add_argument("-F", "--fps", type=int, help="Set camera frames per second", default=24)
|
||||||
parser.add_argument("-c", "--capture", help="Set camera ID (0, 1...) or video file", default="0")
|
parser.add_argument("-c", "--capture", help="Set camera ID (0, 1...) or video file", default="0")
|
||||||
|
parser.add_argument("-M", "--mirror-input", action="store_true", help="Process a mirror image of the input video")
|
||||||
parser.add_argument("-m", "--max-threads", type=int, help="Set the maximum number of threads", default=1)
|
parser.add_argument("-m", "--max-threads", type=int, help="Set the maximum number of threads", default=1)
|
||||||
parser.add_argument("-t", "--threshold", type=float, help="Set minimum confidence threshold for face tracking", default=None)
|
parser.add_argument("-t", "--threshold", type=float, help="Set minimum confidence threshold for face tracking", default=None)
|
||||||
parser.add_argument("-d", "--detection-threshold", type=float, help="Set minimum confidence threshold for face detection", default=0.6)
|
parser.add_argument("-d", "--detection-threshold", type=float, help="Set minimum confidence threshold for face detection", default=0.6)
|
||||||
|
|
@ -39,9 +42,9 @@ parser.add_argument("--video-fps", type=float, help="This sets the frame rate of
|
||||||
parser.add_argument("--raw-rgb", type=int, help="When this is set, raw RGB frames of the size given with \"-W\" and \"-H\" are read from standard input instead of reading a video", default=0)
|
parser.add_argument("--raw-rgb", type=int, help="When this is set, raw RGB frames of the size given with \"-W\" and \"-H\" are read from standard input instead of reading a video", default=0)
|
||||||
parser.add_argument("--log-data", help="You can set a filename to which tracking data will be logged here", default="")
|
parser.add_argument("--log-data", help="You can set a filename to which tracking data will be logged here", default="")
|
||||||
parser.add_argument("--log-output", help="You can set a filename to console output will be logged here", default="")
|
parser.add_argument("--log-output", help="You can set a filename to console output will be logged here", default="")
|
||||||
parser.add_argument("--model", type=int, help="This can be used to select the tracking model. Higher numbers are models with better tracking quality, but slower speed. Models 1 and 0 tend to be too rigid for expression and blink detection. Model -2 is roughly equivalent to model 1, but faster. Model -3 is between models 0 and -1.", default=3, choices=[-3, -2, -1, 0, 1, 2, 3])
|
parser.add_argument("--model", type=int, help="This can be used to select the tracking model. Higher numbers are models with better tracking quality, but slower speed, except for model 4, which is wink optimized. Models 1 and 0 tend to be too rigid for expression and blink detection. Model -2 is roughly equivalent to model 1, but faster. Model -3 is between models 0 and -1.", default=3, choices=[-3, -2, -1, 0, 1, 2, 3, 4])
|
||||||
parser.add_argument("--model-dir", help="This can be used to specify the path to the directory containing the .onnx model files", default=None)
|
parser.add_argument("--model-dir", help="This can be used to specify the path to the directory containing the .onnx model files", default=None)
|
||||||
parser.add_argument("--gaze-tracking", type=int, help="When set to 1, experimental blink detection and gaze tracking are enabled, which makes things slightly slower", default=1)
|
parser.add_argument("--gaze-tracking", type=int, help="When set to 1, gaze tracking is enabled, which makes things slightly slower", default=1)
|
||||||
parser.add_argument("--face-id-offset", type=int, help="When set, this offset is added to all face ids, which can be useful for mixing tracking data from multiple network sources", default=0)
|
parser.add_argument("--face-id-offset", type=int, help="When set, this offset is added to all face ids, which can be useful for mixing tracking data from multiple network sources", default=0)
|
||||||
parser.add_argument("--repeat-video", type=int, help="When set to 1 and a video file was specified with -c, the tracker will loop the video until interrupted", default=0)
|
parser.add_argument("--repeat-video", type=int, help="When set to 1 and a video file was specified with -c, the tracker will loop the video until interrupted", default=0)
|
||||||
parser.add_argument("--dump-points", type=str, help="When set to a filename, the current face 3D points are made symmetric and dumped to the given file when quitting the visualization with the \"q\" key", default="")
|
parser.add_argument("--dump-points", type=str, help="When set to a filename, the current face 3D points are made symmetric and dumped to the given file when quitting the visualization with the \"q\" key", default="")
|
||||||
|
|
@ -59,12 +62,12 @@ class OutputLog(object):
|
||||||
self.fh = fh
|
self.fh = fh
|
||||||
self.output = output
|
self.output = output
|
||||||
def write(self, buf):
|
def write(self, buf):
|
||||||
if not self.fh is None:
|
if self.fh is not None:
|
||||||
self.fh.write(buf)
|
self.fh.write(buf)
|
||||||
self.output.write(buf)
|
self.output.write(buf)
|
||||||
self.flush()
|
self.flush()
|
||||||
def flush(self):
|
def flush(self):
|
||||||
if not self.fh is None:
|
if self.fh is not None:
|
||||||
self.fh.flush()
|
self.fh.flush()
|
||||||
self.output.flush()
|
self.output.flush()
|
||||||
output_logfile = None
|
output_logfile = None
|
||||||
|
|
@ -77,19 +80,19 @@ if os.name == 'nt':
|
||||||
import dshowcapture
|
import dshowcapture
|
||||||
if args.blackmagic == 1:
|
if args.blackmagic == 1:
|
||||||
dshowcapture.set_bm_enabled(True)
|
dshowcapture.set_bm_enabled(True)
|
||||||
if not args.blackmagic_options is None:
|
if args.blackmagic_options is not None:
|
||||||
dshowcapture.set_options(args.blackmagic_options)
|
dshowcapture.set_options(args.blackmagic_options)
|
||||||
if not args.priority is None:
|
if args.priority is not None:
|
||||||
import psutil
|
import psutil
|
||||||
classes = [psutil.IDLE_PRIORITY_CLASS, psutil.BELOW_NORMAL_PRIORITY_CLASS, psutil.NORMAL_PRIORITY_CLASS, psutil.ABOVE_NORMAL_PRIORITY_CLASS, psutil.HIGH_PRIORITY_CLASS, psutil.REALTIME_PRIORITY_CLASS]
|
classes = [psutil.IDLE_PRIORITY_CLASS, psutil.BELOW_NORMAL_PRIORITY_CLASS, psutil.NORMAL_PRIORITY_CLASS, psutil.ABOVE_NORMAL_PRIORITY_CLASS, psutil.HIGH_PRIORITY_CLASS, psutil.REALTIME_PRIORITY_CLASS]
|
||||||
p = psutil.Process(os.getpid())
|
p = psutil.Process(os.getpid())
|
||||||
p.nice(classes[args.priority])
|
p.nice(classes[args.priority])
|
||||||
|
|
||||||
if os.name == 'nt' and (args.list_cameras > 0 or not args.list_dcaps is None):
|
if os.name == 'nt' and (args.list_cameras > 0 or args.list_dcaps is not None):
|
||||||
cap = dshowcapture.DShowCapture()
|
cap = dshowcapture.DShowCapture()
|
||||||
info = cap.get_info()
|
info = cap.get_info()
|
||||||
unit = 10000000.;
|
unit = 10000000.;
|
||||||
if not args.list_dcaps is None:
|
if args.list_dcaps is not None:
|
||||||
formats = {0: "Any", 1: "Unknown", 100: "ARGB", 101: "XRGB", 200: "I420", 201: "NV12", 202: "YV12", 203: "Y800", 300: "YVYU", 301: "YUY2", 302: "UYVY", 303: "HDYC (Unsupported)", 400: "MJPEG", 401: "H264" }
|
formats = {0: "Any", 1: "Unknown", 100: "ARGB", 101: "XRGB", 200: "I420", 201: "NV12", 202: "YV12", 203: "Y800", 300: "YVYU", 301: "YUY2", 302: "UYVY", 303: "HDYC (Unsupported)", 400: "MJPEG", 401: "H264" }
|
||||||
for cam in info:
|
for cam in info:
|
||||||
if args.list_dcaps == -1:
|
if args.list_dcaps == -1:
|
||||||
|
|
@ -134,8 +137,8 @@ if args.benchmark > 0:
|
||||||
model_base_path = get_model_base_path(args.model_dir)
|
model_base_path = get_model_base_path(args.model_dir)
|
||||||
im = cv2.imread(os.path.join(model_base_path, "benchmark.bin"), cv2.IMREAD_COLOR)
|
im = cv2.imread(os.path.join(model_base_path, "benchmark.bin"), cv2.IMREAD_COLOR)
|
||||||
results = []
|
results = []
|
||||||
for model_type in [3, 2, 1, 0, -1, -2]:
|
for model_type in [3, 2, 1, 0, -1, -2, -3]:
|
||||||
tracker = Tracker(224, 224, threshold=0.1, max_threads=args.max_threads, max_faces=1, discard_after=0, scan_every=0, silent=True, model_type=model_type, model_dir=args.model_dir, no_gaze=(model_type < 0), detection_threshold=0.1, use_retinaface=0, max_feature_updates=900, static_model=True if args.no_3d_adapt == 1 else False)
|
tracker = Tracker(224, 224, threshold=0.1, max_threads=args.max_threads, max_faces=1, discard_after=0, scan_every=0, silent=True, model_type=model_type, model_dir=args.model_dir, no_gaze=(model_type == -1), detection_threshold=0.1, use_retinaface=0, max_feature_updates=900, static_model=True if args.no_3d_adapt == 1 else False)
|
||||||
tracker.detected = 1
|
tracker.detected = 1
|
||||||
tracker.faces = [(0, 0, 224, 224)]
|
tracker.faces = [(0, 0, 224, 224)]
|
||||||
total = 0.0
|
total = 0.0
|
||||||
|
|
@ -152,18 +155,19 @@ target_port = args.port
|
||||||
if args.faces >= 40:
|
if args.faces >= 40:
|
||||||
print("Transmission of tracking data over network is not supported with 40 or more faces.")
|
print("Transmission of tracking data over network is not supported with 40 or more faces.")
|
||||||
|
|
||||||
fps = 0
|
fps = args.fps
|
||||||
dcap = None
|
dcap = None
|
||||||
use_dshowcapture_flag = False
|
use_dshowcapture_flag = False
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
fps = args.fps
|
|
||||||
dcap = args.dcap
|
dcap = args.dcap
|
||||||
use_dshowcapture_flag = True if args.use_dshowcapture == 1 else False
|
use_dshowcapture_flag = True if args.use_dshowcapture == 1 else False
|
||||||
input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag, dcap=dcap)
|
input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag, dcap=dcap)
|
||||||
if args.dcap == -1 and type(input_reader) == DShowCaptureReader:
|
if args.dcap == -1 and type(input_reader) == DShowCaptureReader:
|
||||||
fps = min(fps, input_reader.device.get_fps())
|
fps = min(fps, input_reader.device.get_fps())
|
||||||
|
elif sys.platform == 'linux' and args.dformat:
|
||||||
|
input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, dcap=args.dformat)
|
||||||
else:
|
else:
|
||||||
input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag)
|
input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps)
|
||||||
if type(input_reader.reader) == VideoReader:
|
if type(input_reader.reader) == VideoReader:
|
||||||
fps = 0
|
fps = 0
|
||||||
|
|
||||||
|
|
@ -184,9 +188,9 @@ features = ["eye_l", "eye_r", "eyebrow_steepness_l", "eyebrow_updown_l", "eyebro
|
||||||
if args.log_data != "":
|
if args.log_data != "":
|
||||||
log = open(args.log_data, "w")
|
log = open(args.log_data, "w")
|
||||||
log.write("Frame,Time,Width,Height,FPS,Face,FaceID,RightOpen,LeftOpen,AverageConfidence,Success3D,PnPError,RotationQuat.X,RotationQuat.Y,RotationQuat.Z,RotationQuat.W,Euler.X,Euler.Y,Euler.Z,RVec.X,RVec.Y,RVec.Z,TVec.X,TVec.Y,TVec.Z")
|
log.write("Frame,Time,Width,Height,FPS,Face,FaceID,RightOpen,LeftOpen,AverageConfidence,Success3D,PnPError,RotationQuat.X,RotationQuat.Y,RotationQuat.Z,RotationQuat.W,Euler.X,Euler.Y,Euler.Z,RVec.X,RVec.Y,RVec.Z,TVec.X,TVec.Y,TVec.Z")
|
||||||
for i in range(66):
|
for i in range(68):
|
||||||
log.write(f",Landmark[{i}].X,Landmark[{i}].Y,Landmark[{i}].Confidence")
|
log.write(f",Landmark[{i}].X,Landmark[{i}].Y,Landmark[{i}].Confidence")
|
||||||
for i in range(66):
|
for i in range(70):
|
||||||
log.write(f",Point3D[{i}].X,Point3D[{i}].Y,Point3D[{i}].Z")
|
log.write(f",Point3D[{i}].X,Point3D[{i}].Y,Point3D[{i}].Z")
|
||||||
for feature in features:
|
for feature in features:
|
||||||
log.write(f",{feature}")
|
log.write(f",{feature}")
|
||||||
|
|
@ -219,6 +223,8 @@ try:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ret, frame = input_reader.read()
|
ret, frame = input_reader.read()
|
||||||
|
if ret and args.mirror_input:
|
||||||
|
frame = cv2.flip(frame, 1)
|
||||||
if not ret:
|
if not ret:
|
||||||
if repeat:
|
if repeat:
|
||||||
if need_reinit == 0:
|
if need_reinit == 0:
|
||||||
|
|
@ -245,8 +251,8 @@ try:
|
||||||
first = False
|
first = False
|
||||||
height, width, channels = frame.shape
|
height, width, channels = frame.shape
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
tracker = Tracker(width, height, threshold=args.threshold, max_threads=args.max_threads, max_faces=args.faces, discard_after=args.discard_after, scan_every=args.scan_every, silent=False if args.silent == 0 else True, model_type=args.model, model_dir=args.model_dir, no_gaze=False if args.gaze_tracking != 0 and args.model >= 0 else True, detection_threshold=args.detection_threshold, use_retinaface=args.scan_retinaface, max_feature_updates=args.max_feature_updates, static_model=True if args.no_3d_adapt == 1 else False, try_hard=args.try_hard == 1)
|
tracker = Tracker(width, height, threshold=args.threshold, max_threads=args.max_threads, max_faces=args.faces, discard_after=args.discard_after, scan_every=args.scan_every, silent=False if args.silent == 0 else True, model_type=args.model, model_dir=args.model_dir, no_gaze=False if args.gaze_tracking != 0 and args.model != -1 else True, detection_threshold=args.detection_threshold, use_retinaface=args.scan_retinaface, max_feature_updates=args.max_feature_updates, static_model=True if args.no_3d_adapt == 1 else False, try_hard=args.try_hard == 1)
|
||||||
if not args.video_out is None:
|
if args.video_out is not None:
|
||||||
out = cv2.VideoWriter(args.video_out, cv2.VideoWriter_fourcc('F','F','V','1'), args.video_fps, (width * args.video_scale, height * args.video_scale))
|
out = cv2.VideoWriter(args.video_out, cv2.VideoWriter_fourcc('F','F','V','1'), args.video_fps, (width * args.video_scale, height * args.video_scale))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -289,8 +295,8 @@ try:
|
||||||
packet.extend(bytearray(struct.pack("f", f.translation[0])))
|
packet.extend(bytearray(struct.pack("f", f.translation[0])))
|
||||||
packet.extend(bytearray(struct.pack("f", f.translation[1])))
|
packet.extend(bytearray(struct.pack("f", f.translation[1])))
|
||||||
packet.extend(bytearray(struct.pack("f", f.translation[2])))
|
packet.extend(bytearray(struct.pack("f", f.translation[2])))
|
||||||
if not log is None:
|
if log is not None:
|
||||||
log.write(f"{frame_count},{now},{width},{height},{args.fps},{face_num},{f.id},{f.eye_blink[0]},{f.eye_blink[1]},{f.conf},{f.success},{f.pnp_error},{f.quaternion[0]},{f.quaternion[1]},{f.quaternion[2]},{f.quaternion[3]},{f.euler[0]},{f.euler[1]},{f.euler[2]},{f.rotation[0]},{f.rotation[1]},{f.rotation[2]},{f.translation[0]},{f.translation[1]},{f.translation[2]}")
|
log.write(f"{frame_count},{now},{width},{height},{fps},{face_num},{f.id},{f.eye_blink[0]},{f.eye_blink[1]},{f.conf},{f.success},{f.pnp_error},{f.quaternion[0]},{f.quaternion[1]},{f.quaternion[2]},{f.quaternion[3]},{f.euler[0]},{f.euler[1]},{f.euler[2]},{f.rotation[0]},{f.rotation[1]},{f.rotation[2]},{f.translation[0]},{f.translation[1]},{f.translation[2]}")
|
||||||
for (x,y,c) in f.lms:
|
for (x,y,c) in f.lms:
|
||||||
packet.extend(bytearray(struct.pack("f", c)))
|
packet.extend(bytearray(struct.pack("f", c)))
|
||||||
if args.visualize > 1:
|
if args.visualize > 1:
|
||||||
|
|
@ -300,32 +306,23 @@ try:
|
||||||
for pt_num, (x,y,c) in enumerate(f.lms):
|
for pt_num, (x,y,c) in enumerate(f.lms):
|
||||||
packet.extend(bytearray(struct.pack("f", y)))
|
packet.extend(bytearray(struct.pack("f", y)))
|
||||||
packet.extend(bytearray(struct.pack("f", x)))
|
packet.extend(bytearray(struct.pack("f", x)))
|
||||||
if not log is None:
|
if log is not None:
|
||||||
log.write(f",{y},{x},{c}")
|
log.write(f",{y},{x},{c}")
|
||||||
if pt_num == 66 and (f.eye_blink[0] < 0.30 or c < 0.30):
|
if pt_num == 66 and (f.eye_blink[0] < 0.30 or c < 0.20):
|
||||||
continue
|
continue
|
||||||
if pt_num == 67 and (f.eye_blink[1] < 0.30 or c < 0.30):
|
if pt_num == 67 and (f.eye_blink[1] < 0.30 or c < 0.20):
|
||||||
continue
|
continue
|
||||||
x = int(x + 0.5)
|
x = int(x + 0.5)
|
||||||
y = int(y + 0.5)
|
y = int(y + 0.5)
|
||||||
if args.visualize != 0 or not out is None:
|
if args.visualize != 0 or out is not None:
|
||||||
if args.visualize > 3:
|
if args.visualize > 3:
|
||||||
frame = cv2.putText(frame, str(pt_num), (int(y), int(x)), cv2.FONT_HERSHEY_SIMPLEX, 0.25, (255,255,0))
|
frame = cv2.putText(frame, str(pt_num), (int(y), int(x)), cv2.FONT_HERSHEY_SIMPLEX, 0.25, (255,255,0))
|
||||||
color = (0, 255, 0)
|
color = (0, 255, 0)
|
||||||
if pt_num >= 66:
|
if pt_num >= 66:
|
||||||
color = (255, 255, 0)
|
color = (255, 255, 0)
|
||||||
if not (x < 0 or y < 0 or x >= height or y >= width):
|
if not (x < 0 or y < 0 or x >= height or y >= width):
|
||||||
frame[int(x), int(y)] = color
|
cv2.circle(frame, (y, x), 1, color, -1)
|
||||||
x += 1
|
if args.pnp_points != 0 and (args.visualize != 0 or out is not None) and f.rotation is not None:
|
||||||
if not (x < 0 or y < 0 or x >= height or y >= width):
|
|
||||||
frame[int(x), int(y)] = color
|
|
||||||
y += 1
|
|
||||||
if not (x < 0 or y < 0 or x >= height or y >= width):
|
|
||||||
frame[int(x), int(y)] = color
|
|
||||||
x -= 1
|
|
||||||
if not (x < 0 or y < 0 or x >= height or y >= width):
|
|
||||||
frame[int(x), int(y)] = color
|
|
||||||
if args.pnp_points != 0 and (args.visualize != 0 or not out is None) and f.rotation is not None:
|
|
||||||
if args.pnp_points > 1:
|
if args.pnp_points > 1:
|
||||||
projected = cv2.projectPoints(f.face_3d[0:66], f.rotation, f.translation, tracker.camera, tracker.dist_coeffs)
|
projected = cv2.projectPoints(f.face_3d[0:66], f.rotation, f.translation, tracker.camera, tracker.dist_coeffs)
|
||||||
else:
|
else:
|
||||||
|
|
@ -348,7 +345,7 @@ try:
|
||||||
packet.extend(bytearray(struct.pack("f", x)))
|
packet.extend(bytearray(struct.pack("f", x)))
|
||||||
packet.extend(bytearray(struct.pack("f", -y)))
|
packet.extend(bytearray(struct.pack("f", -y)))
|
||||||
packet.extend(bytearray(struct.pack("f", -z)))
|
packet.extend(bytearray(struct.pack("f", -z)))
|
||||||
if not log is None:
|
if log is not None:
|
||||||
log.write(f",{x},{-y},{-z}")
|
log.write(f",{x},{-y},{-z}")
|
||||||
if f.current_features is None:
|
if f.current_features is None:
|
||||||
f.current_features = {}
|
f.current_features = {}
|
||||||
|
|
@ -356,16 +353,16 @@ try:
|
||||||
if not feature in f.current_features:
|
if not feature in f.current_features:
|
||||||
f.current_features[feature] = 0
|
f.current_features[feature] = 0
|
||||||
packet.extend(bytearray(struct.pack("f", f.current_features[feature])))
|
packet.extend(bytearray(struct.pack("f", f.current_features[feature])))
|
||||||
if not log is None:
|
if log is not None:
|
||||||
log.write(f",{f.current_features[feature]}")
|
log.write(f",{f.current_features[feature]}")
|
||||||
if not log is None:
|
if log is not None:
|
||||||
log.write("\r\n")
|
log.write("\r\n")
|
||||||
log.flush()
|
log.flush()
|
||||||
|
|
||||||
if detected and len(faces) < 40:
|
if detected and len(faces) < 40:
|
||||||
sock.sendto(packet, (target_ip, target_port))
|
sock.sendto(packet, (target_ip, target_port))
|
||||||
|
|
||||||
if not out is None:
|
if out is not None:
|
||||||
video_frame = frame
|
video_frame = frame
|
||||||
if args.video_scale != 1:
|
if args.video_scale != 1:
|
||||||
video_frame = cv2.resize(frame, (width * args.video_scale, height * args.video_scale), interpolation=cv2.INTER_NEAREST)
|
video_frame = cv2.resize(frame, (width * args.video_scale, height * args.video_scale), interpolation=cv2.INTER_NEAREST)
|
||||||
|
|
@ -376,7 +373,7 @@ try:
|
||||||
if args.visualize != 0:
|
if args.visualize != 0:
|
||||||
cv2.imshow('OpenSeeFace Visualization', frame)
|
cv2.imshow('OpenSeeFace Visualization', frame)
|
||||||
if cv2.waitKey(1) & 0xFF == ord('q'):
|
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||||
if args.dump_points != "" and not faces is None and len(faces) > 0:
|
if args.dump_points != "" and faces is not None and len(faces) > 0:
|
||||||
np.set_printoptions(threshold=sys.maxsize, precision=15)
|
np.set_printoptions(threshold=sys.maxsize, precision=15)
|
||||||
pairs = [
|
pairs = [
|
||||||
(0, 16),
|
(0, 16),
|
||||||
|
|
@ -452,9 +449,10 @@ except KeyboardInterrupt:
|
||||||
print("Quitting")
|
print("Quitting")
|
||||||
|
|
||||||
input_reader.close()
|
input_reader.close()
|
||||||
if not out is None:
|
if out is not None:
|
||||||
out.release()
|
out.release()
|
||||||
cv2.destroyAllWindows()
|
if args.visualize != 0:
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
if args.silent == 0 and tracking_frames > 0:
|
if args.silent == 0 and tracking_frames > 0:
|
||||||
average_tracking_time = 1000 * tracking_time / tracking_frames
|
average_tracking_time = 1000 * tracking_time / tracking_frames
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class EscapiReader(VideoReader):
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
self.device = capture
|
self.device = capture
|
||||||
escapi.count_capture_devices()
|
escapi.count_capture_devices()
|
||||||
self.name = str(escapi.device_name(self.device).decode('utf8'))
|
self.name = str(escapi.device_name(self.device).decode('utf8', 'surrogateescape'))
|
||||||
self.buffer = escapi.init_camera(self.device, self.width, self.height, self.fps)
|
self.buffer = escapi.init_camera(self.device, self.width, self.height, self.fps)
|
||||||
escapi.do_capture(self.device)
|
escapi.do_capture(self.device)
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
|
|
@ -111,6 +111,7 @@ class OpenCVReader(VideoReader):
|
||||||
super(OpenCVReader, self).__init__(capture, camera=True)
|
super(OpenCVReader, self).__init__(capture, camera=True)
|
||||||
self.cap.set(3, width)
|
self.cap.set(3, width)
|
||||||
self.cap.set(4, height)
|
self.cap.set(4, height)
|
||||||
|
self.cap.set(38, 1)
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
return super(OpenCVReader, self).is_open()
|
return super(OpenCVReader, self).is_open()
|
||||||
def is_ready(self):
|
def is_ready(self):
|
||||||
|
|
@ -120,6 +121,30 @@ class OpenCVReader(VideoReader):
|
||||||
def close(self):
|
def close(self):
|
||||||
super(OpenCVReader, self).close()
|
super(OpenCVReader, self).close()
|
||||||
|
|
||||||
|
class V4L2Reader(OpenCVReader):
|
||||||
|
def __init__(self, capture, width, height, fps, dformat):
|
||||||
|
self.cap = cv2.VideoCapture(capture, cv2.CAP_V4L2)
|
||||||
|
if dformat:
|
||||||
|
fourcc = cv2.VideoWriter.fourcc(*dformat)
|
||||||
|
self.cap.set(6, fourcc)
|
||||||
|
self.device = None
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.fps = fps
|
||||||
|
self.name = str(capture)
|
||||||
|
self.cap.set(3, width)
|
||||||
|
self.cap.set(4, height)
|
||||||
|
self.cap.set(38, 1)
|
||||||
|
self.cap.set(5, fps)
|
||||||
|
def is_open(self):
|
||||||
|
return super(V4L2Reader, self).is_open()
|
||||||
|
def is_ready(self):
|
||||||
|
return super(V4L2Reader, self).is_ready()
|
||||||
|
def read(self):
|
||||||
|
return super(V4L2Reader, self).read()
|
||||||
|
def close(self):
|
||||||
|
super(V4L2Reader, self).close()
|
||||||
|
|
||||||
class RawReader:
|
class RawReader:
|
||||||
def __init__(self, width, height):
|
def __init__(self, width, height):
|
||||||
self.width = int(width)
|
self.width = int(width)
|
||||||
|
|
@ -212,7 +237,7 @@ class InputReader():
|
||||||
devices = escapi.count_capture_devices()
|
devices = escapi.count_capture_devices()
|
||||||
found = None
|
found = None
|
||||||
for i in range(devices):
|
for i in range(devices):
|
||||||
escapi_name = str(escapi.device_name(i).decode('utf8'))
|
escapi_name = str(escapi.device_name(i).decode('utf8', 'surrogateescape'))
|
||||||
if name == escapi_name:
|
if name == escapi_name:
|
||||||
found = i
|
found = i
|
||||||
if found is None:
|
if found is None:
|
||||||
|
|
@ -231,6 +256,14 @@ class InputReader():
|
||||||
print(f"Escapi failed. Falling back to OpenCV. If this fails, please change your camera settings.", file=sys.stderr)
|
print(f"Escapi failed. Falling back to OpenCV. If this fails, please change your camera settings.", file=sys.stderr)
|
||||||
self.reader = OpenCVReader(int(capture), width, height, fps)
|
self.reader = OpenCVReader(int(capture), width, height, fps)
|
||||||
self.name = self.reader.name
|
self.name = self.reader.name
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
try:
|
||||||
|
self.reader = V4L2Reader(int(capture), width, height, fps, dcap)
|
||||||
|
except:
|
||||||
|
print("V4L2 exception: ")
|
||||||
|
traceback.print_exc()
|
||||||
|
print(f"V4L2 failed. Falling back to OpenCV. If this fails, please change your camera settings.", file=sys.stderr)
|
||||||
|
self.reader = OpenCVReader(int(capture), width, height, fps)
|
||||||
else:
|
else:
|
||||||
self.reader = OpenCVReader(int(capture), width, height, fps)
|
self.reader = OpenCVReader(int(capture), width, height, fps)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
33
make_exe.bat
33
make_exe.bat
|
|
@ -1,16 +1,31 @@
|
||||||
pyinstaller facetracker.spec --onedir ^
|
echo "Started"
|
||||||
--add-binary dshowcapture/dshowcapture_x86.dll;. ^
|
|
||||||
--add-binary dshowcapture/dshowcapture_x64.dll;. ^
|
:: A venv is used so it's easy to add the onnxruntime dll to the binary
|
||||||
--add-binary dshowcapture/libminibmcapture32.dll;. ^
|
|
||||||
--add-binary dshowcapture/libminibmcapture64.dll;. ^
|
echo "Creating venv"
|
||||||
--add-binary escapi/escapi_x86.dll;. ^
|
python -m venv venv
|
||||||
--add-binary escapi/escapi_x64.dll;. ^
|
|
||||||
|
echo "Activating venv"
|
||||||
|
call venv\Scripts\activate.bat
|
||||||
|
|
||||||
|
echo "Installing dependencies"
|
||||||
|
pip install wheel
|
||||||
|
|
||||||
|
pip install onnxruntime opencv-python==4.5.4.60 pillow numpy==1.23.0 pyinstaller
|
||||||
|
|
||||||
|
echo "Running pyinstaller"
|
||||||
|
pyinstaller facetracker.py --clean ^
|
||||||
|
--onedir ^
|
||||||
|
--add-binary dshowcapture/*.dll;. ^
|
||||||
|
--add-binary escapi/*.dll;. ^
|
||||||
|
--add-binary venv/lib/site-packages/onnxruntime/capi/*.dll;onnxruntime\capi ^
|
||||||
--add-binary msvcp140.dll;. ^
|
--add-binary msvcp140.dll;. ^
|
||||||
--add-binary vcomp140.dll;. ^
|
--add-binary vcomp140.dll;. ^
|
||||||
--add-binary concrt140.dll;. ^
|
--add-binary concrt140.dll;. ^
|
||||||
--add-binary vccorlib140.dll;. ^
|
--add-binary vccorlib140.dll;. ^
|
||||||
--add-binary run.bat;.
|
--add-binary run.bat;.
|
||||||
|
|
||||||
del dist\facetracker\cv2\opencv_videoio_ffmpeg420_64.dll
|
echo "Deleting opencv dll"
|
||||||
|
del dist\facetracker\cv2\opencv_videoio_*
|
||||||
|
|
||||||
pause
|
echo "Finished"
|
||||||
|
|
|
||||||
32
make_exe.sh
Executable file
32
make_exe.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PYTHON_BINARY=$(which python3)
|
||||||
|
|
||||||
|
if [[ -z "$PYTHON_BINARY" ]]; then
|
||||||
|
echo "python3 not found. Searching for python instead."
|
||||||
|
PYTHON_BINARY=$(which python)
|
||||||
|
if [[ -z "$PYTHON_BINARY" ]]; then
|
||||||
|
echo "Python 3 must be available on your path. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating venv"
|
||||||
|
"$PYTHON_BINARY" -m venv venv
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
echo "Installing packages"
|
||||||
|
pip install wheel # Make sure this is installed beforehand
|
||||||
|
|
||||||
|
pip install onnxruntime opencv-python==4.5.4.60 pillow numpy==1.23.0 pyinstaller
|
||||||
|
|
||||||
|
echo "Creating binary"
|
||||||
|
pyinstaller --onedir --clean facetracker.py \
|
||||||
|
--add-binary venv/lib/python3.*/site-packages/onnxruntime/capi/*.so:onnxruntime/capi
|
||||||
|
|
||||||
|
echo "Files should be available in the dist/ folder"
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
4
model.py
4
model.py
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is not used by the tracking application and currently outdated
|
# This file is not used by the tracking application and currently outdated
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import geffnet.mobilenetv3 # geffnet.mobilenetv3._gen_mobilenet_v3 needs to be patched to return the parameters instead of instantiating the network
|
import geffnet.mobilenetv3 # geffnet.mobilenetv3._gen_mobilenet_v3 needs to be patched to return the parameters instead of instantiating the network, based on commit c450c12ae6ffb1757f62dde3c2765da3c10f6def
|
||||||
from geffnet.efficientnet_builder import round_channels
|
from geffnet.efficientnet_builder import round_channels
|
||||||
|
|
||||||
class DSConv2d(nn.Module):
|
class DSConv2d(nn.Module):
|
||||||
|
|
@ -251,7 +251,7 @@ def AdapWingLoss(pre_hm, gt_hm):
|
||||||
img_dilate = cv2.morphologyEx(img_merge, cv2.MORPH_DILATE, kernel)
|
img_dilate = cv2.morphologyEx(img_merge, cv2.MORPH_DILATE, kernel)
|
||||||
img_dilate[img_dilate < 51] = 1 # 0*W+1
|
img_dilate[img_dilate < 51] = 1 # 0*W+1
|
||||||
img_dilate[img_dilate >= 51] = 11 # 1*W+1
|
img_dilate[img_dilate >= 51] = 11 # 1*W+1
|
||||||
img_dilate = np.array(img_dilate, dtype=np.int)
|
img_dilate = np.array(img_dilate, dtype=int)
|
||||||
img_dilate = img_dilate.transpose(2, 0, 1)
|
img_dilate = img_dilate.transpose(2, 0, 1)
|
||||||
dilated = torch.from_numpy(img_dilate).float()
|
dilated = torch.from_numpy(img_dilate).float()
|
||||||
if first:
|
if first:
|
||||||
|
|
|
||||||
BIN
models/lm_model4_opt.onnx
Normal file
BIN
models/lm_model4_opt.onnx
Normal file
Binary file not shown.
224
poetry.lock
generated
Normal file
224
poetry.lock
generated
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
[[package]]
|
||||||
|
name = "flatbuffers"
|
||||||
|
version = "2.0"
|
||||||
|
description = "The FlatBuffers serialization format for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numpy"
|
||||||
|
version = "1.22"
|
||||||
|
description = "NumPy is the fundamental package for array computing with Python."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<3.11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "onnxruntime"
|
||||||
|
version = "1.9.0"
|
||||||
|
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
flatbuffers = "*"
|
||||||
|
numpy = ">=1.16.6"
|
||||||
|
protobuf = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opencv-python"
|
||||||
|
version = "4.5.4.58"
|
||||||
|
description = "Wrapper package for OpenCV python bindings."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
numpy = ">=1.21.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "10.2.0"
|
||||||
|
description = "Python Imaging Library (Fork)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf"
|
||||||
|
version = "4.25.8"
|
||||||
|
description = "Protocol Buffers"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = ">=3.7,<3.11"
|
||||||
|
content-hash = "1511681290c580145b1117beffe728052d856eee3ad8419930626c6fc6c9a09d"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
flatbuffers = [
|
||||||
|
{file = "flatbuffers-2.0-py2.py3-none-any.whl", hash = "sha256:3751954f0604580d3219ae49a85fafec9d85eec599c0b96226e1bc0b48e57474"},
|
||||||
|
{file = "flatbuffers-2.0.tar.gz", hash = "sha256:12158ab0272375eab8db2d663ae97370c33f152b27801fa6024e1d6105fd4dd2"},
|
||||||
|
]
|
||||||
|
numpy = [
|
||||||
|
{file = "numpy-1.21.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:508b0b513fa1266875524ba8a9ecc27b02ad771fe1704a16314dc1a816a68737"},
|
||||||
|
{file = "numpy-1.21.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5dfe9d6a4c39b8b6edd7990091fea4f852888e41919d0e6722fe78dd421db0eb"},
|
||||||
|
{file = "numpy-1.21.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a10968963640e75cc0193e1847616ab4c718e83b6938ae74dea44953950f6b7"},
|
||||||
|
{file = "numpy-1.21.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c6249260890e05b8111ebfc391ed58b3cb4b33e63197b2ec7f776e45330721"},
|
||||||
|
{file = "numpy-1.21.3-cp310-cp310-win_amd64.whl", hash = "sha256:f8f4625536926a155b80ad2bbff44f8cc59e9f2ad14cdda7acf4c135b4dc8ff2"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e54af82d68ef8255535a6cdb353f55d6b8cf418a83e2be3569243787a4f4866f"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f41b018f126aac18583956c54544db437f25c7ee4794bcb23eb38bef8e5e192a"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50cd26b0cf6664cb3b3dd161ba0a09c9c1343db064e7c69f9f8b551f5104d654"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cc9b512e9fb590797474f58b7f6d1f1b654b3a94f4fa8558b48ca8b3cfc97cf"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88a5d6b268e9ad18f3533e184744acdaa2e913b13148160b1152300c949bbb5f"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c09418a14471c7ae69ba682e2428cae5b4420a766659605566c0fa6987f6b7e"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-win32.whl", hash = "sha256:90bec6a86b348b4559b6482e2b684db4a9a7eed1fa054b86115a48d58fbbf62a"},
|
||||||
|
{file = "numpy-1.21.3-cp37-cp37m-win_amd64.whl", hash = "sha256:043e83bfc274649c82a6f09836943e4a4aebe5e33656271c7dbf9621dd58b8ec"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:75621882d2230ab77fb6a03d4cbccd2038511491076e7964ef87306623aa5272"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:188031f833bbb623637e66006cf75e933e00e7231f67e2b45cf8189612bb5dc3"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:160ccc1bed3a8371bf0d760971f09bfe80a3e18646620e9ded0ad159d9749baa"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:29fb3dcd0468b7715f8ce2c0c2d9bbbaf5ae686334951343a41bd8d155c6ea27"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32437f0b275c1d09d9c3add782516413e98cd7c09e6baf4715cbce781fc29912"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e606e6316911471c8d9b4618e082635cfe98876007556e89ce03d52ff5e8fcf0"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a99a6b067e5190ac6d12005a4d85aa6227c5606fa93211f86b1dafb16233e57d"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dde972a1e11bb7b702ed0e447953e7617723760f420decb97305e66fb4afc54f"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-win32.whl", hash = "sha256:fe52dbe47d9deb69b05084abd4b0df7abb39a3c51957c09f635520abd49b29dd"},
|
||||||
|
{file = "numpy-1.21.3-cp38-cp38-win_amd64.whl", hash = "sha256:75eb7cadc8da49302f5b659d40ba4f6d94d5045fbd9569c9d058e77b0514c9e4"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2a6ee9620061b2a722749b391c0d80a0e2ae97290f1b32e28d5a362e21941ee4"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c4193f70f8069550a1788bd0cd3268ab7d3a2b70583dfe3b2e7f421e9aace06"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f15209fb535dd4c504a7762d3bc440779b0e37d50ed810ced209e5cea60d96"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c6c2d535a7beb1f8790aaa98fd089ceab2e3dd7ca48aca0af7dc60e6ef93ffe1"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bffa2eee3b87376cc6b31eee36d05349571c236d1de1175b804b348dc0941e3f"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14e7519fab2a4ed87d31f99c31a3796e4e1fe63a86ebdd1c5a1ea78ebd5896"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-win32.whl", hash = "sha256:dd0482f3fc547f1b1b5d6a8b8e08f63fdc250c58ce688dedd8851e6e26cff0f3"},
|
||||||
|
{file = "numpy-1.21.3-cp39-cp39-win_amd64.whl", hash = "sha256:300321e3985c968e3ae7fbda187237b225f3ffe6528395a5b7a5407f73cf093e"},
|
||||||
|
{file = "numpy-1.21.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98339aa9911853f131de11010f6dd94c8cec254d3d1f7261528c3b3e3219f139"},
|
||||||
|
{file = "numpy-1.21.3.zip", hash = "sha256:63571bb7897a584ca3249c86dd01c10bcb5fe4296e3568b2e9c1a55356b6410e"},
|
||||||
|
]
|
||||||
|
onnxruntime = [
|
||||||
|
{file = "onnxruntime-1.9.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:48f0fcf3c9aa6836584e64abe63fa7395c02066d3259bbdeb489b4d172e0127a"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9d772a6330cb85e7723f84e357320a1603e3824a92aab4ef36fc3a41e64f16"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bdb861822a63404cca7b46dce86d48bbc21c906a4b4ed13969bc89763ac7f96"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp36-cp36m-win32.whl", hash = "sha256:e3f8f7d5d4d66e3a4a2b731a000d3142a53a5403e8814e68bbd659514e815899"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cf3edbc54bfe99a119d73cd65398a2ec68ae3af2557ab7e645976314a8d11aa1"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:c8ff9c914b2b1c3b022dedc199e3f971e340d8923a1ef42d66530508fa367bf6"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d03ff4a2717c4149acc7c649fd66a67e81ec44c9e6e2a00df1d6e9ca843f1b7"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a2315e2244ac371742f6e30da5367c680c3e84c31e291a35f8ddfab09c3c82"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:fa927b1825f2851c0c8f3948515a56d76cb0686da9acd1d6f8fafe552c8d8fec"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9ccaf6a0365f2b86efe21681416b8cfe97f084a7d53bd1cf2bf889a0aef2b0d3"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:d20ce3448babe89a77cc9d357730767deb3617e36439bddcd006f28abc72b416"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd1d6647245aa38e1099cfd355d84e807de5350d5216e84ceefd91c64ce243d"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f00620fc0f51bc4d90ae6d96ceb4b6538e3bd1e328178104118ac672f37c40d"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp38-cp38-win32.whl", hash = "sha256:e1c1fe3f7d960eeffc02a5f196d85529254eefd59cbeecd8abee0a9467b5c2d8"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9002214af1b2317ab3a63a2f045f7d1363c207e661d475a877aa6499ca09d606"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7339cef9b918b88f1fec8109cfa0a8416f119c5968d00300a9186847d86e35de"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdec8538eb59e63a376d0677f7ec043ceb597d52ee88f1f7e250928893a0de7f"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab62b29429e0e62c11478b2a8a3af2646531fba7800736e8b201d8baa50b43a8"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp39-cp39-win32.whl", hash = "sha256:4aee9a893f93637341fd0e6b56fa3ab1c430d718d08d79a358603297a1575ad9"},
|
||||||
|
{file = "onnxruntime-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:bdf1327932227383b04093a51266474b2703b3fcf9c0f6f11c652d9652b76a5c"},
|
||||||
|
]
|
||||||
|
opencv-python = [
|
||||||
|
{file = "opencv-python-4.5.4.58.tar.gz", hash = "sha256:48288428f407bacba5f73d460feb4a1ecafe87db3d7cfc0730a49fb32f589bbf"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0eba0bfe62c48a02a5af3a0944e872c99f57f98653bed14d51c6991a58f9e1d1"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:9bcca50c5444b5cfb01624666b69f91ba8f2d2bf4ef37b111697aafdeb81c99f"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:8f7886acabaebf0361bd3dbccaa0d08e3f65ab13b7c739eb11e028f01ad13582"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:d4b1d0b98ee72ba5dd720166790fc93ce459281e138ee79b0d41420b3da52b2e"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-win32.whl", hash = "sha256:69a78e40a374ac14e4bf15a13dbb6c30fd2fbd5fcd3674d020a31b88861d5aaf"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp310-cp310-win_amd64.whl", hash = "sha256:315c357522b6310ef7a0718d9f0c5d3110e59c19140705499a3c29bdd8c0124f"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:887a61097092dc0bf23fa24646dbc8cfeeb753649cb28a3782a93a6879e3b7d2"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:22bcc3153a7d4f95aff79457eef81ef5e40ab1851b189e014412b5e9fbee2573"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:92e9b2261ec764229c948d77fe0d922ee033348ca6519939b87861016c1614b3"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp36-cp36m-win32.whl", hash = "sha256:0d6249a49122a78afc6685ddb1377a87e46414ae61c84535c4c6024397f1f3e8"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa144013b597e4dcabc8d8230edfe810319de01b5609556d415a20e2b707547"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:26feeeb280de179f5dbb8976ebf7ceb836bd263973cb5daec8ca36e8ef7b5773"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:4a13381bdfc0fb4b080efcc27c46561d0bd752f126226e9f19aa9cbcf6677f40"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ac852fcaac93439f2f7116ddffdc23fd366c872200ade2272446f9898180cecb"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02872e0a9358526646d691f390143e9c21109c210095314abaa0641211cda077"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-win32.whl", hash = "sha256:6b87bab220d17e03eeedbcc6652d9d7e7bb09886dbd0f810310697a948b4c6fd"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp37-cp37m-win_amd64.whl", hash = "sha256:a2a7f09b8843b85f3e1b02c5ea3ddc0cb9f5ad9698380109b37069ee8db7746d"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:c44f5c51e92322ed832607204249c190764dec6cf29e8ba6d679b10326be1c1b"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b2c198af083a693d42a82bddc4d1f7e6bb02c64192ff7fac1fd1d43a8cf1be6"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:637f4d3ad81bd27f273ede4c5fa6c26afb85c097c9715baf107cc270e37f5fea"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:2fff48a641a74d1def31c1e88f9e5ce50ba4d0f87d085dfbf8bc844e12f6cd54"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-win32.whl", hash = "sha256:8ddf4dcd8199209e33f21deb0c6d8ab62b21802816bba895fefc346b6d2e522d"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp38-cp38-win_amd64.whl", hash = "sha256:085c5fcf5a6479c34aca3fd0f59055e704083d6a44009d6583c675ff1a5a0625"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4abe9c4fb6fe16daa9fcdd68b5357d3530431341aa655203f8e84f394e1fe6d4"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b614fbd81aeda53ce28e645aaee18fda7c7f2a48eb7f1a70a7c6c3427946342"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215bdf069847d4e3b0447a34e9eb4046dd4ca523d41fe4381c1c55f6704fd0dc"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc34cdbfbab463750713118c8259a5d364547adab8ed91e94ba888349f33590a"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-win32.whl", hash = "sha256:9998ce60884f3cda074f02b56d2b57ee6bd863e2ddba132da2b0af3b9487d584"},
|
||||||
|
{file = "opencv_python-4.5.4.58-cp39-cp39-win_amd64.whl", hash = "sha256:5370a11757fbe94b176771269aff599f4da8676c2a672b13bcbca043f2e3eea8"},
|
||||||
|
]
|
||||||
|
pillow = [
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"},
|
||||||
|
{file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"},
|
||||||
|
{file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"},
|
||||||
|
{file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"},
|
||||||
|
{file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"},
|
||||||
|
{file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"},
|
||||||
|
{file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"},
|
||||||
|
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"},
|
||||||
|
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"},
|
||||||
|
{file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"},
|
||||||
|
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"},
|
||||||
|
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"},
|
||||||
|
{file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"},
|
||||||
|
{file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"},
|
||||||
|
]
|
||||||
|
protobuf = [
|
||||||
|
{file = "protobuf-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d80f80eb175bf5f1169139c2e0c5ada98b1c098e2b3c3736667f28cbbea39fc8"},
|
||||||
|
{file = "protobuf-3.19.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a529e7df52204565bcd33738a7a5f288f3d2d37d86caa5d78c458fa5fabbd54d"},
|
||||||
|
{file = "protobuf-3.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccea56d4dc38d35cd70c43c2da2f40ac0be0a355ef882242e8586c6d66666f"},
|
||||||
|
{file = "protobuf-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b30a7de128c46b5ecb343917d9fa737612a6e8280f440874e5cc2ba0d79b8f6"},
|
||||||
|
{file = "protobuf-3.19.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5935c8ce02e3d89c7900140a8a42b35bc037ec07a6aeb61cc108be8d3c9438a6"},
|
||||||
|
{file = "protobuf-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:74f33edeb4f3b7ed13d567881da8e5a92a72b36495d57d696c2ea1ae0cfee80c"},
|
||||||
|
{file = "protobuf-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:038daf4fa38a7e818dd61f51f22588d61755160a98db087a046f80d66b855942"},
|
||||||
|
{file = "protobuf-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e51561d72efd5bd5c91490af1f13e32bcba8dab4643761eb7de3ce18e64a853"},
|
||||||
|
{file = "protobuf-3.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6e8ea9173403219239cdfd8d946ed101f2ab6ecc025b0fda0c6c713c35c9981d"},
|
||||||
|
{file = "protobuf-3.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3532d9f7a6ebbe2392041350437953b6d7a792de10e629c1e4f5a6b1fe1ac6"},
|
||||||
|
{file = "protobuf-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:615b426a177780ce381ecd212edc1e0f70db8557ed72560b82096bd36b01bc04"},
|
||||||
|
{file = "protobuf-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d8919368410110633717c406ab5c97e8df5ce93020cfcf3012834f28b1fab1ea"},
|
||||||
|
{file = "protobuf-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71b0250b0cfb738442d60cab68abc166de43411f2a4f791d31378590bfb71bd7"},
|
||||||
|
{file = "protobuf-3.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3cd0458870ea7d1c58e948ac8078f6ba8a7ecc44a57e03032ed066c5bb318089"},
|
||||||
|
{file = "protobuf-3.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:655264ed0d0efe47a523e2255fc1106a22f6faab7cc46cfe99b5bae085c2a13e"},
|
||||||
|
{file = "protobuf-3.19.1-cp38-cp38-win32.whl", hash = "sha256:b691d996c6d0984947c4cf8b7ae2fe372d99b32821d0584f0b90277aa36982d3"},
|
||||||
|
{file = "protobuf-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:e7e8d2c20921f8da0dea277dfefc6abac05903ceac8e72839b2da519db69206b"},
|
||||||
|
{file = "protobuf-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd390367fc211cc0ffcf3a9e149dfeca78fecc62adb911371db0cec5c8b7472d"},
|
||||||
|
{file = "protobuf-3.19.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d83e1ef8cb74009bebee3e61cc84b1c9cd04935b72bca0cbc83217d140424995"},
|
||||||
|
{file = "protobuf-3.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d90676d6f426718463fe382ec6274909337ca6319d375eebd2044e6c6ac560"},
|
||||||
|
{file = "protobuf-3.19.1-cp39-cp39-win32.whl", hash = "sha256:e7b24c11df36ee8e0c085e5b0dc560289e4b58804746fb487287dda51410f1e2"},
|
||||||
|
{file = "protobuf-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:77d2fadcf369b3f22859ab25bd12bb8e98fb11e05d9ff9b7cd45b711c719c002"},
|
||||||
|
{file = "protobuf-3.19.1-py2.py3-none-any.whl", hash = "sha256:e813b1c9006b6399308e917ac5d298f345d95bb31f46f02b60cd92970a9afa17"},
|
||||||
|
{file = "protobuf-3.19.1.tar.gz", hash = "sha256:62a8e4baa9cb9e064eb62d1002eca820857ab2138440cb4b3ea4243830f94ca7"},
|
||||||
|
]
|
||||||
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "openseeface"
|
||||||
|
version = "1.20.4"
|
||||||
|
description = ""
|
||||||
|
authors = ["emilianavt"]
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.7,<3.11"
|
||||||
|
numpy = "^1.21.3"
|
||||||
|
opencv-python = "^4.5.4"
|
||||||
|
Pillow = "^8.4.0"
|
||||||
|
onnxruntime = "^1.9.0"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
@ -66,7 +66,8 @@ class RetinaFaceDetector():
|
||||||
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
||||||
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||||
options.log_severity_level = 3
|
options.log_severity_level = 3
|
||||||
self.session = onnxruntime.InferenceSession(model_path, sess_options=options)
|
providersList = onnxruntime.capi._pybind_state.get_available_providers()
|
||||||
|
self.session = onnxruntime.InferenceSession(model_path, sess_options=options, providers=providersList)
|
||||||
self.res_w, self.res_h = res
|
self.res_w, self.res_h = res
|
||||||
with open(json_path, "r") as prior_file:
|
with open(json_path, "r") as prior_file:
|
||||||
self.priorbox = np.array(json.loads(prior_file.read()))
|
self.priorbox = np.array(json.loads(prior_file.read()))
|
||||||
|
|
|
||||||
50
tracker.py
50
tracker.py
|
|
@ -50,7 +50,7 @@ def compensate(p1, p2):
|
||||||
def rotate_image(image, a, center):
|
def rotate_image(image, a, center):
|
||||||
(h, w) = image.shape[:2]
|
(h, w) = image.shape[:2]
|
||||||
a = np.rad2deg(a)
|
a = np.rad2deg(a)
|
||||||
M = cv2.getRotationMatrix2D((center[0], center[1]), a, 1.0)
|
M = cv2.getRotationMatrix2D((float(center[0]), float(center[1])), a, 1.0)
|
||||||
rotated = cv2.warpAffine(image, M, (w, h))
|
rotated = cv2.warpAffine(image, M, (w, h))
|
||||||
return rotated
|
return rotated
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ def worker_thread(session, frame, input, crop_info, queue, input_name, idx, trac
|
||||||
conf, lms = tracker.landmarks(output[0], crop_info)
|
conf, lms = tracker.landmarks(output[0], crop_info)
|
||||||
if conf > tracker.threshold:
|
if conf > tracker.threshold:
|
||||||
try:
|
try:
|
||||||
eye_state = tracker.get_eye_state(frame, lms, single=True)
|
eye_state = tracker.get_eye_state(frame, lms)
|
||||||
except:
|
except:
|
||||||
eye_state = [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
eye_state = [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
||||||
queue.put((session, conf, (lms, eye_state), crop_info, idx))
|
queue.put((session, conf, (lms, eye_state), crop_info, idx))
|
||||||
|
|
@ -328,7 +328,7 @@ class FaceInfo():
|
||||||
self.id = id
|
self.id = id
|
||||||
self.frame_count = -1
|
self.frame_count = -1
|
||||||
self.tracker = tracker
|
self.tracker = tracker
|
||||||
self.contour_pts = [0,1,8,15,16,27,28,29,30,31,32,33,34,35,36,39,42,45]
|
self.contour_pts = [0,1,8,15,16,27,28,29,30,31,32,33,34,35]
|
||||||
self.face_3d = copy.copy(self.tracker.face_3d)
|
self.face_3d = copy.copy(self.tracker.face_3d)
|
||||||
if self.tracker.model_type == -1:
|
if self.tracker.model_type == -1:
|
||||||
self.contour_pts = [0,2,8,14,16,27,30,33]
|
self.contour_pts = [0,2,8,14,16,27,30,33]
|
||||||
|
|
@ -497,7 +497,7 @@ class Tracker():
|
||||||
def __init__(self, width, height, model_type=3, detection_threshold=0.6, threshold=None, max_faces=1, discard_after=5, scan_every=3, bbox_growth=0.0, max_threads=4, silent=False, model_dir=None, no_gaze=False, use_retinaface=False, max_feature_updates=0, static_model=False, feature_level=2, try_hard=False):
|
def __init__(self, width, height, model_type=3, detection_threshold=0.6, threshold=None, max_faces=1, discard_after=5, scan_every=3, bbox_growth=0.0, max_threads=4, silent=False, model_dir=None, no_gaze=False, use_retinaface=False, max_feature_updates=0, static_model=False, feature_level=2, try_hard=False):
|
||||||
options = onnxruntime.SessionOptions()
|
options = onnxruntime.SessionOptions()
|
||||||
options.inter_op_num_threads = 1
|
options.inter_op_num_threads = 1
|
||||||
options.intra_op_num_threads = max(max_threads,4)
|
options.intra_op_num_threads = min(max_threads,4)
|
||||||
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
||||||
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||||
options.log_severity_level = 3
|
options.log_severity_level = 3
|
||||||
|
|
@ -506,7 +506,8 @@ class Tracker():
|
||||||
"lm_model0_opt.onnx",
|
"lm_model0_opt.onnx",
|
||||||
"lm_model1_opt.onnx",
|
"lm_model1_opt.onnx",
|
||||||
"lm_model2_opt.onnx",
|
"lm_model2_opt.onnx",
|
||||||
"lm_model3_opt.onnx"
|
"lm_model3_opt.onnx",
|
||||||
|
"lm_model4_opt.onnx"
|
||||||
]
|
]
|
||||||
model = "lm_modelT_opt.onnx"
|
model = "lm_modelT_opt.onnx"
|
||||||
if model_type >= 0:
|
if model_type >= 0:
|
||||||
|
|
@ -526,8 +527,12 @@ class Tracker():
|
||||||
self.retinaface_scan = RetinaFaceDetector(model_path=os.path.join(model_base_path, "retinaface_640x640_opt.onnx"), json_path=os.path.join(model_base_path, "priorbox_640x640.json"), threads=2, top_k=max_faces, res=(640, 640))
|
self.retinaface_scan = RetinaFaceDetector(model_path=os.path.join(model_base_path, "retinaface_640x640_opt.onnx"), json_path=os.path.join(model_base_path, "priorbox_640x640.json"), threads=2, top_k=max_faces, res=(640, 640))
|
||||||
self.use_retinaface = use_retinaface
|
self.use_retinaface = use_retinaface
|
||||||
|
|
||||||
|
# OTR 1.9 and later requires specifying providers
|
||||||
|
# explicitly as an argument in InferenceSession()
|
||||||
|
providersList = onnxruntime.capi._pybind_state.get_available_providers()
|
||||||
|
|
||||||
# Single face instance with multiple threads
|
# Single face instance with multiple threads
|
||||||
self.session = onnxruntime.InferenceSession(os.path.join(model_base_path, model), sess_options=options)
|
self.session = onnxruntime.InferenceSession(os.path.join(model_base_path, model), sess_options=options, providers=providersList)
|
||||||
|
|
||||||
# Multiple faces with single threads
|
# Multiple faces with single threads
|
||||||
self.sessions = []
|
self.sessions = []
|
||||||
|
|
@ -536,27 +541,25 @@ class Tracker():
|
||||||
for i in range(self.max_workers):
|
for i in range(self.max_workers):
|
||||||
options = onnxruntime.SessionOptions()
|
options = onnxruntime.SessionOptions()
|
||||||
options.inter_op_num_threads = 1
|
options.inter_op_num_threads = 1
|
||||||
options.intra_op_num_threads = max_threads // self.max_workers
|
options.intra_op_num_threads = min(max(max_threads // self.max_workers, 4), 1)
|
||||||
if options.intra_op_num_threads < 1:
|
if options.intra_op_num_threads < 1:
|
||||||
options.intra_op_num_threads = 1
|
options.intra_op_num_threads = 1
|
||||||
elif i < extra_threads:
|
elif i < extra_threads:
|
||||||
options.intra_op_num_threads += 1
|
options.intra_op_num_threads += 1
|
||||||
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
||||||
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||||
self.sessions.append(onnxruntime.InferenceSession(os.path.join(model_base_path, model), sess_options=options))
|
self.sessions.append(onnxruntime.InferenceSession(os.path.join(model_base_path, model), sess_options=options, providers=providersList))
|
||||||
self.input_name = self.session.get_inputs()[0].name
|
self.input_name = self.session.get_inputs()[0].name
|
||||||
|
|
||||||
options = onnxruntime.SessionOptions()
|
options = onnxruntime.SessionOptions()
|
||||||
options.inter_op_num_threads = 1
|
options.inter_op_num_threads = 1
|
||||||
options.intra_op_num_threads = max(max_threads,4)
|
options.intra_op_num_threads = 1
|
||||||
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
||||||
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||||
options.log_severity_level = 3
|
options.log_severity_level = 3
|
||||||
self.gaze_model = onnxruntime.InferenceSession(os.path.join(model_base_path, "mnv3_gaze32_split_opt.onnx"), sess_options=options)
|
self.gaze_model = onnxruntime.InferenceSession(os.path.join(model_base_path, "mnv3_gaze32_split_opt.onnx"), sess_options=options, providers=providersList)
|
||||||
options.intra_op_num_threads = 1
|
|
||||||
self.gaze_model_single = onnxruntime.InferenceSession(os.path.join(model_base_path, "mnv3_gaze32_split_opt.onnx"), sess_options=options)
|
|
||||||
|
|
||||||
self.detection = onnxruntime.InferenceSession(os.path.join(model_base_path, "mnv3_detection_opt.onnx"), sess_options=options)
|
self.detection = onnxruntime.InferenceSession(os.path.join(model_base_path, "mnv3_detection_opt.onnx"), sess_options=options, providers=providersList)
|
||||||
self.faces = []
|
self.faces = []
|
||||||
|
|
||||||
# Image normalization constants
|
# Image normalization constants
|
||||||
|
|
@ -759,12 +762,12 @@ class Tracker():
|
||||||
return (avg_conf, np.array(lms))
|
return (avg_conf, np.array(lms))
|
||||||
|
|
||||||
def estimate_depth(self, face_info):
|
def estimate_depth(self, face_info):
|
||||||
lms = np.concatenate((face_info.lms, np.array([[face_info.eye_state[0][1], face_info.eye_state[0][2], face_info.eye_state[0][3]], [face_info.eye_state[1][1], face_info.eye_state[1][2], face_info.eye_state[1][3]]], np.float)), 0)
|
lms = np.concatenate((face_info.lms, np.array([[face_info.eye_state[0][1], face_info.eye_state[0][2], face_info.eye_state[0][3]], [face_info.eye_state[1][1], face_info.eye_state[1][2], face_info.eye_state[1][3]]], np.float32)), 0)
|
||||||
|
|
||||||
image_pts = np.array(lms)[face_info.contour_pts, 0:2]
|
image_pts = np.array(lms)[face_info.contour_pts, 0:2]
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
if not face_info.rotation is None:
|
if face_info.rotation is not None:
|
||||||
success, face_info.rotation, face_info.translation = cv2.solvePnP(face_info.contour, image_pts, self.camera, self.dist_coeffs, useExtrinsicGuess=True, rvec=np.transpose(face_info.rotation), tvec=np.transpose(face_info.translation), flags=cv2.SOLVEPNP_ITERATIVE)
|
success, face_info.rotation, face_info.translation = cv2.solvePnP(face_info.contour, image_pts, self.camera, self.dist_coeffs, useExtrinsicGuess=True, rvec=np.transpose(face_info.rotation), tvec=np.transpose(face_info.translation), flags=cv2.SOLVEPNP_ITERATIVE)
|
||||||
else:
|
else:
|
||||||
rvec = np.array([0, 0, 0], np.float32)
|
rvec = np.array([0, 0, 0], np.float32)
|
||||||
|
|
@ -780,8 +783,10 @@ class Tracker():
|
||||||
face_info.translation = np.array([0.0, 0.0, 0.0], np.float32)
|
face_info.translation = np.array([0.0, 0.0, 0.0], np.float32)
|
||||||
return False, np.zeros(4), np.zeros(3), 99999., pts_3d, lms
|
return False, np.zeros(4), np.zeros(3), 99999., pts_3d, lms
|
||||||
else:
|
else:
|
||||||
face_info.rotation = np.transpose(face_info.rotation)
|
# OpenCV returns (3,1); flatten so [0]/[1]/[2] are scalars for UDP packing
|
||||||
face_info.translation = np.transpose(face_info.translation)
|
# and 1D .dot() math does not broadcast to (1,3).
|
||||||
|
face_info.rotation = np.asarray(face_info.rotation, dtype=np.float32).reshape(3)
|
||||||
|
face_info.translation = np.asarray(face_info.translation, dtype=np.float32).reshape(3)
|
||||||
|
|
||||||
rmat, _ = cv2.Rodrigues(rotation)
|
rmat, _ = cv2.Rodrigues(rotation)
|
||||||
inverse_rotation = np.linalg.inv(rmat)
|
inverse_rotation = np.linalg.inv(rmat)
|
||||||
|
|
@ -830,7 +835,7 @@ class Tracker():
|
||||||
reference = rmat.dot(pt)
|
reference = rmat.dot(pt)
|
||||||
reference = reference + face_info.translation
|
reference = reference + face_info.translation
|
||||||
reference = self.camera.dot(reference)
|
reference = self.camera.dot(reference)
|
||||||
depth = reference[2]
|
depth = float(reference[2])
|
||||||
pt_3d = np.array([lms[66+i][0] * depth, lms[66+i][1] * depth, depth], np.float32)
|
pt_3d = np.array([lms[66+i][0] * depth, lms[66+i][1] * depth, depth], np.float32)
|
||||||
pt_3d = self.inverse_camera.dot(pt_3d)
|
pt_3d = self.inverse_camera.dot(pt_3d)
|
||||||
pt_3d = pt_3d - face_info.translation
|
pt_3d = pt_3d - face_info.translation
|
||||||
|
|
@ -917,11 +922,11 @@ class Tracker():
|
||||||
x1, y1 = clamp_to_im(center - radius, h, w)
|
x1, y1 = clamp_to_im(center - radius, h, w)
|
||||||
x2, y2 = clamp_to_im(center + radius + 1, h, w)
|
x2, y2 = clamp_to_im(center + radius + 1, h, w)
|
||||||
offset = np.array((x1, y1))
|
offset = np.array((x1, y1))
|
||||||
lms = (lms[:, 0:2] - offset).astype(np.int)
|
lms = (lms[:, 0:2] - offset).astype(int)
|
||||||
frame = frame[y1:y2, x1:x2]
|
frame = frame[y1:y2, x1:x2]
|
||||||
return frame, lms, offset
|
return frame, lms, offset
|
||||||
|
|
||||||
def get_eye_state(self, frame, lms, single=False):
|
def get_eye_state(self, frame, lms):
|
||||||
if self.no_gaze:
|
if self.no_gaze:
|
||||||
return [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
return [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
||||||
lms = np.array(lms)
|
lms = np.array(lms)
|
||||||
|
|
@ -937,10 +942,7 @@ class Tracker():
|
||||||
return [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
return [(1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
|
||||||
both_eyes = np.concatenate((right_eye, left_eye))
|
both_eyes = np.concatenate((right_eye, left_eye))
|
||||||
results = None
|
results = None
|
||||||
if single:
|
results = self.gaze_model.run([], {self.input_name: both_eyes})
|
||||||
results = self.gaze_model_single.run([], {self.input_name: both_eyes})
|
|
||||||
else:
|
|
||||||
results = self.gaze_model.run([], {self.input_name: both_eyes})
|
|
||||||
open = [0, 0]
|
open = [0, 0]
|
||||||
open[0] = 1#results[1][0].argmax()
|
open[0] = 1#results[1][0].argmax()
|
||||||
open[1] = 1#results[1][1].argmax()
|
open[1] = 1#results[1][1].argmax()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue