mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-25 18:03:19 +02:00
Conflicts: interface/src/Application.cpp interface/src/devices/DdeFaceTracker.cpp interface/src/devices/DdeFaceTracker.h
618 lines
22 KiB
C++
618 lines
22 KiB
C++
//
|
|
// DdeFaceTracker.cpp
|
|
//
|
|
//
|
|
// Created by Clement on 8/2/14.
|
|
// Copyright 2014 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include <SharedUtil.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QTimer>
|
|
|
|
#include <GLMHelpers.h>
|
|
#include <NumericalConstants.h>
|
|
|
|
#include "Application.h"
|
|
#include "DdeFaceTracker.h"
|
|
#include "FaceshiftConstants.h"
|
|
#include "InterfaceLogging.h"
|
|
#include "Menu.h"
|
|
|
|
|
|
static const QHostAddress DDE_SERVER_ADDR("127.0.0.1");
|
|
static const quint16 DDE_SERVER_PORT = 64204;
|
|
static const quint16 DDE_CONTROL_PORT = 64205;
|
|
#if defined(Q_OS_WIN)
|
|
static const QString DDE_PROGRAM_PATH = "/dde/dde.exe";
|
|
#elif defined(Q_OS_MAC)
|
|
static const QString DDE_PROGRAM_PATH = "/dde.app/Contents/MacOS/dde";
|
|
#endif
|
|
static const QStringList DDE_ARGUMENTS = QStringList()
|
|
<< "--udp=" + DDE_SERVER_ADDR.toString() + ":" + QString::number(DDE_SERVER_PORT)
|
|
<< "--receiver=" + QString::number(DDE_CONTROL_PORT)
|
|
<< "--headless";
|
|
|
|
static const int NUM_EXPRESSIONS = 46;
|
|
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int);
|
|
static const int MAX_NAME_SIZE = 31;
|
|
|
|
// There's almost but not quite a 1-1 correspondence between DDE's 46 and Faceshift 1.3's 48 packets.
|
|
// The best guess at mapping is to:
|
|
// - Swap L and R values
|
|
// - Skip two Faceshift values: JawChew (22) and LipsLowerDown (37)
|
|
static const int DDE_TO_FACESHIFT_MAPPING[] = {
|
|
1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14,
|
|
16,
|
|
18, 17,
|
|
19,
|
|
23,
|
|
21,
|
|
// Skip JawChew
|
|
20,
|
|
25, 24, 27, 26, 29, 28, 31, 30, 33, 32,
|
|
34, 35, 36,
|
|
// Skip LipsLowerDown
|
|
38, 39, 40, 41, 42, 43, 44, 45,
|
|
47, 46
|
|
};
|
|
|
|
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
|
|
// less than this.
|
|
static const float DDE_COEFFICIENT_SCALES[] = {
|
|
1.0f, // EyeBlink_L
|
|
1.0f, // EyeBlink_R
|
|
1.0f, // EyeSquint_L
|
|
1.0f, // EyeSquint_R
|
|
1.0f, // EyeDown_L
|
|
1.0f, // EyeDown_R
|
|
1.0f, // EyeIn_L
|
|
1.0f, // EyeIn_R
|
|
1.0f, // EyeOpen_L
|
|
1.0f, // EyeOpen_R
|
|
1.0f, // EyeOut_L
|
|
1.0f, // EyeOut_R
|
|
1.0f, // EyeUp_L
|
|
1.0f, // EyeUp_R
|
|
3.0f, // BrowsD_L
|
|
3.0f, // BrowsD_R
|
|
3.0f, // BrowsU_C
|
|
3.0f, // BrowsU_L
|
|
3.0f, // BrowsU_R
|
|
1.0f, // JawFwd
|
|
2.0f, // JawLeft
|
|
1.8f, // JawOpen
|
|
1.0f, // JawChew
|
|
2.0f, // JawRight
|
|
1.5f, // MouthLeft
|
|
1.5f, // MouthRight
|
|
1.5f, // MouthFrown_L
|
|
1.5f, // MouthFrown_R
|
|
2.5f, // MouthSmile_L
|
|
2.5f, // MouthSmile_R
|
|
1.0f, // MouthDimple_L
|
|
1.0f, // MouthDimple_R
|
|
1.0f, // LipsStretch_L
|
|
1.0f, // LipsStretch_R
|
|
1.0f, // LipsUpperClose
|
|
1.0f, // LipsLowerClose
|
|
1.0f, // LipsUpperUp
|
|
1.0f, // LipsLowerDown
|
|
1.0f, // LipsUpperOpen
|
|
1.0f, // LipsLowerOpen
|
|
1.5f, // LipsFunnel
|
|
2.5f, // LipsPucker
|
|
1.5f, // ChinLowerRaise
|
|
1.5f, // ChinUpperRaise
|
|
1.0f, // Sneer
|
|
3.0f, // Puff
|
|
1.0f, // CheekSquint_L
|
|
1.0f // CheekSquint_R
|
|
};
|
|
|
|
struct Packet {
|
|
//roughly in mm
|
|
float focal_length[1];
|
|
float translation[3];
|
|
|
|
//quaternion
|
|
float rotation[4];
|
|
|
|
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
|
|
// less than this.
|
|
float expressions[NUM_EXPRESSIONS];
|
|
|
|
//avatar id selected on the UI
|
|
int avatar_id;
|
|
|
|
//client name, arbitrary length
|
|
char name[MAX_NAME_SIZE + 1];
|
|
};
|
|
|
|
static const float STARTING_DDE_MESSAGE_TIME = 0.033f;
|
|
|
|
static const int CALIBRATION_SAMPLES = 150;
|
|
|
|
#ifdef WIN32
|
|
// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeBlinks' will be default initialized
|
|
// warning C4351: new behavior: elements of array 'DdeFaceTracker::_filteredEyeBlinks' will be default initialized
|
|
// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeCoefficients' will be default initialized
|
|
#pragma warning(disable:4351)
|
|
#endif
|
|
|
|
DdeFaceTracker::DdeFaceTracker() :
|
|
DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT)
|
|
{
|
|
|
|
}
|
|
|
|
DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort) :
|
|
_ddeProcess(NULL),
|
|
_ddeStopping(false),
|
|
_host(host),
|
|
_serverPort(serverPort),
|
|
_controlPort(controlPort),
|
|
_lastReceiveTimestamp(0),
|
|
_reset(false),
|
|
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
|
|
_rightBlinkIndex(1),
|
|
_leftEyeOpenIndex(8),
|
|
_rightEyeOpenIndex(9),
|
|
_browDownLeftIndex(14),
|
|
_browDownRightIndex(15),
|
|
_browUpCenterIndex(16),
|
|
_browUpLeftIndex(17),
|
|
_browUpRightIndex(18),
|
|
_mouthSmileLeftIndex(28),
|
|
_mouthSmileRightIndex(29),
|
|
_jawOpenIndex(21),
|
|
_lastMessageReceived(0),
|
|
_averageMessageTime(STARTING_DDE_MESSAGE_TIME),
|
|
_lastHeadTranslation(glm::vec3(0.0f)),
|
|
_filteredHeadTranslation(glm::vec3(0.0f)),
|
|
_lastBrowUp(0.0f),
|
|
_filteredBrowUp(0.0f),
|
|
_lastEyeBlinks(),
|
|
_filteredEyeBlinks(),
|
|
_lastEyeCoefficients(),
|
|
_isCalibrating(false),
|
|
_calibrationValues(),
|
|
_calibrationCount(0),
|
|
_calibrationBillboard(NULL),
|
|
_calibrationBillboardID(0),
|
|
_calibrationMessage(QString())
|
|
{
|
|
_coefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
_blendshapeCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
_coefficientAverages.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
_calibrationValues.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
|
|
_eyeStates[0] = EYE_OPEN;
|
|
_eyeStates[1] = EYE_OPEN;
|
|
|
|
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
|
|
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
|
|
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
|
|
SLOT(socketStateChanged(QAbstractSocket::SocketState)));
|
|
}
|
|
|
|
DdeFaceTracker::~DdeFaceTracker() {
|
|
setEnabled(false);
|
|
|
|
if (_isCalibrating) {
|
|
cancelCalibration();
|
|
}
|
|
}
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(default:4351)
|
|
#endif
|
|
|
|
void DdeFaceTracker::init() {
|
|
FaceTracker::init();
|
|
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted);
|
|
}
|
|
|
|
void DdeFaceTracker::setEnabled(bool enabled) {
|
|
if (!_isInitialized) {
|
|
// Don't enable until have explicitly initialized
|
|
return;
|
|
}
|
|
#ifdef HAVE_DDE
|
|
|
|
if (_isCalibrating) {
|
|
cancelCalibration();
|
|
}
|
|
|
|
|
|
// isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket.
|
|
_udpSocket.close();
|
|
if (enabled) {
|
|
_udpSocket.bind(_host, _serverPort);
|
|
}
|
|
|
|
const char* DDE_EXIT_COMMAND = "exit";
|
|
|
|
if (enabled && !_ddeProcess) {
|
|
// Terminate any existing DDE process, perhaps left running after an Interface crash
|
|
_udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort);
|
|
_ddeStopping = false;
|
|
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Starting";
|
|
_ddeProcess = new QProcess(qApp);
|
|
connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
|
|
_ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS);
|
|
}
|
|
|
|
if (!enabled && _ddeProcess) {
|
|
_ddeStopping = true;
|
|
_udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort);
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Stopping";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DdeFaceTracker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
|
if (_ddeProcess) {
|
|
if (_ddeStopping) {
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Stopped";
|
|
|
|
} else {
|
|
qCWarning(interfaceapp) << "DDE Face Tracker: Stopped unexpectedly";
|
|
Menu::getInstance()->setIsOptionChecked(MenuOption::NoFaceTracking, true);
|
|
}
|
|
_udpSocket.close();
|
|
delete _ddeProcess;
|
|
_ddeProcess = NULL;
|
|
}
|
|
}
|
|
|
|
void DdeFaceTracker::reset() {
|
|
if (_udpSocket.state() == QAbstractSocket::BoundState) {
|
|
_reset = true;
|
|
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Reset";
|
|
|
|
const char* DDE_RESET_COMMAND = "reset";
|
|
_udpSocket.writeDatagram(DDE_RESET_COMMAND, DDE_SERVER_ADDR, _controlPort);
|
|
|
|
FaceTracker::reset();
|
|
|
|
_reset = true;
|
|
}
|
|
}
|
|
|
|
bool DdeFaceTracker::isActive() const {
|
|
static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs
|
|
return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS);
|
|
}
|
|
|
|
//private slots and methods
|
|
void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) {
|
|
qCWarning(interfaceapp) << "DDE Face Tracker: Socket error: " << _udpSocket.errorString();
|
|
}
|
|
|
|
void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) {
|
|
QString state;
|
|
switch(socketState) {
|
|
case QAbstractSocket::BoundState:
|
|
state = "Bound";
|
|
break;
|
|
case QAbstractSocket::ClosingState:
|
|
state = "Closing";
|
|
break;
|
|
case QAbstractSocket::ConnectedState:
|
|
state = "Connected";
|
|
break;
|
|
case QAbstractSocket::ConnectingState:
|
|
state = "Connecting";
|
|
break;
|
|
case QAbstractSocket::HostLookupState:
|
|
state = "Host Lookup";
|
|
break;
|
|
case QAbstractSocket::ListeningState:
|
|
state = "Listening";
|
|
break;
|
|
case QAbstractSocket::UnconnectedState:
|
|
state = "Unconnected";
|
|
break;
|
|
}
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Socket: " << state;
|
|
}
|
|
|
|
void DdeFaceTracker::readPendingDatagrams() {
|
|
QByteArray buffer;
|
|
while (_udpSocket.hasPendingDatagrams()) {
|
|
buffer.resize(_udpSocket.pendingDatagramSize());
|
|
_udpSocket.readDatagram(buffer.data(), buffer.size());
|
|
}
|
|
decodePacket(buffer);
|
|
}
|
|
|
|
float DdeFaceTracker::getBlendshapeCoefficient(int index) const {
|
|
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
|
|
}
|
|
|
|
void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
|
|
_lastReceiveTimestamp = usecTimestampNow();
|
|
|
|
if (buffer.size() > MIN_PACKET_SIZE) {
|
|
bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter);
|
|
|
|
Packet packet;
|
|
int bytesToCopy = glm::min((int)sizeof(packet), buffer.size());
|
|
memset(&packet.name, '\n', MAX_NAME_SIZE + 1);
|
|
memcpy(&packet, buffer.data(), bytesToCopy);
|
|
|
|
glm::vec3 translation;
|
|
memcpy(&translation, packet.translation, sizeof(packet.translation));
|
|
glm::quat rotation;
|
|
memcpy(&rotation, &packet.rotation, sizeof(packet.rotation));
|
|
if (_reset || (_lastMessageReceived == 0)) {
|
|
memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3));
|
|
memcpy(&_referenceRotation, &rotation, sizeof(glm::quat));
|
|
_reset = false;
|
|
}
|
|
|
|
// Compute relative translation
|
|
float LEAN_DAMPING_FACTOR = 75.0f;
|
|
translation -= _referenceTranslation;
|
|
translation /= LEAN_DAMPING_FACTOR;
|
|
translation.x *= -1;
|
|
if (isFiltering) {
|
|
glm::vec3 linearVelocity = (translation - _lastHeadTranslation) / _averageMessageTime;
|
|
const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
float velocityFilter = glm::clamp(1.0f - glm::length(linearVelocity) *
|
|
LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
_filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * translation;
|
|
_lastHeadTranslation = translation;
|
|
_headTranslation = _filteredHeadTranslation;
|
|
} else {
|
|
_headTranslation = translation;
|
|
}
|
|
|
|
// Compute relative rotation
|
|
rotation = glm::inverse(_referenceRotation) * rotation;
|
|
if (isFiltering) {
|
|
glm::quat r = rotation * glm::inverse(_headRotation);
|
|
float theta = 2 * acos(r.w);
|
|
glm::vec3 angularVelocity;
|
|
if (theta > EPSILON) {
|
|
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
|
|
angularVelocity = theta / _averageMessageTime * glm::vec3(r.x, r.y, r.z) / rMag;
|
|
} else {
|
|
angularVelocity = glm::vec3(0, 0, 0);
|
|
}
|
|
const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
_headRotation = safeMix(_headRotation, rotation, glm::clamp(glm::length(angularVelocity) *
|
|
ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f));
|
|
} else {
|
|
_headRotation = rotation;
|
|
}
|
|
|
|
// Translate DDE coefficients to Faceshift compatible coefficients
|
|
for (int i = 0; i < NUM_EXPRESSIONS; i++) {
|
|
_coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i];
|
|
}
|
|
|
|
// Calibration
|
|
if (_isCalibrating) {
|
|
addCalibrationDatum();
|
|
}
|
|
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
_coefficients[i] -= _coefficientAverages[i];
|
|
}
|
|
|
|
// Use BrowsU_C to control both brows' up and down
|
|
float browUp = _coefficients[_browUpCenterIndex];
|
|
if (isFiltering) {
|
|
const float BROW_VELOCITY_FILTER_STRENGTH = 0.5f;
|
|
float velocity = fabs(browUp - _lastBrowUp) / _averageMessageTime;
|
|
float velocityFilter = glm::clamp(velocity * BROW_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
_filteredBrowUp = velocityFilter * browUp + (1.0f - velocityFilter) * _filteredBrowUp;
|
|
_lastBrowUp = browUp;
|
|
browUp = _filteredBrowUp;
|
|
_coefficients[_browUpCenterIndex] = browUp;
|
|
}
|
|
_coefficients[_browUpLeftIndex] = browUp;
|
|
_coefficients[_browUpRightIndex] = browUp;
|
|
_coefficients[_browDownLeftIndex] = -browUp;
|
|
_coefficients[_browDownRightIndex] = -browUp;
|
|
|
|
// Offset jaw open coefficient
|
|
static const float JAW_OPEN_THRESHOLD = 0.1f;
|
|
_coefficients[_jawOpenIndex] = _coefficients[_jawOpenIndex] - JAW_OPEN_THRESHOLD;
|
|
|
|
// Offset smile coefficients
|
|
static const float SMILE_THRESHOLD = 0.5f;
|
|
_coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD;
|
|
_coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD;
|
|
|
|
// Velocity filter EyeBlink values
|
|
const float DDE_EYEBLINK_SCALE = 3.0f;
|
|
float eyeBlinks[] = { DDE_EYEBLINK_SCALE * _coefficients[_leftBlinkIndex], DDE_EYEBLINK_SCALE * _coefficients[_rightBlinkIndex] };
|
|
if (isFiltering) {
|
|
const float BLINK_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
for (int i = 0; i < 2; i++) {
|
|
float velocity = fabs(eyeBlinks[i] - _lastEyeBlinks[i]) / _averageMessageTime;
|
|
float velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
_filteredEyeBlinks[i] = velocityFilter * eyeBlinks[i] + (1.0f - velocityFilter) * _filteredEyeBlinks[i];
|
|
_lastEyeBlinks[i] = eyeBlinks[i];
|
|
}
|
|
}
|
|
|
|
// Finesse EyeBlink values
|
|
float eyeCoefficients[2];
|
|
for (int i = 0; i < 2; i++) {
|
|
// Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
|
|
// -ve values control EyeOpen; +ve values control EyeBlink
|
|
static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
|
|
eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
|
|
|
|
// Change to closing or opening states
|
|
const float EYE_CONTROL_HYSTERISIS = 0.25f;
|
|
const float EYE_CLOSING_THRESHOLD = 0.8f;
|
|
const float EYE_OPENING_THRESHOLD = EYE_CONTROL_THRESHOLD - EYE_CONTROL_HYSTERISIS;
|
|
if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > EYE_CLOSING_THRESHOLD) {
|
|
_eyeStates[i] = EYE_CLOSING;
|
|
} else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
|
|
&& eyeCoefficients[i] < EYE_OPENING_THRESHOLD) {
|
|
_eyeStates[i] = EYE_OPENING;
|
|
}
|
|
|
|
const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
|
|
const float EYE_OPEN_SCALE = 0.2f;
|
|
if (_eyeStates[i] == EYE_CLOSING) {
|
|
// Close eyelid until it's fully closed
|
|
float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
|
|
if (closingValue >= 1.0) {
|
|
_eyeStates[i] = EYE_CLOSED;
|
|
eyeCoefficients[i] = 1.0;
|
|
} else {
|
|
eyeCoefficients[i] = closingValue;
|
|
}
|
|
} else if (_eyeStates[i] == EYE_OPENING) {
|
|
// Open eyelid until it meets the current adjusted value
|
|
float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
|
|
if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
|
|
_eyeStates[i] = EYE_OPEN;
|
|
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
|
} else {
|
|
eyeCoefficients[i] = openingValue;
|
|
}
|
|
} else if (_eyeStates[i] == EYE_OPEN) {
|
|
// Reduce eyelid movement
|
|
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
|
} else if (_eyeStates[i] == EYE_CLOSED) {
|
|
// Keep eyelid fully closed
|
|
eyeCoefficients[i] = 1.0;
|
|
}
|
|
}
|
|
if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
|
|
// Couple eyelids
|
|
eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
|
|
}
|
|
_lastEyeCoefficients[0] = eyeCoefficients[0];
|
|
_lastEyeCoefficients[1] = eyeCoefficients[1];
|
|
|
|
// Use EyeBlink values to control both EyeBlink and EyeOpen
|
|
if (eyeCoefficients[0] > 0) {
|
|
_coefficients[_leftBlinkIndex] = eyeCoefficients[0];
|
|
_coefficients[_leftEyeOpenIndex] = 0.0f;
|
|
} else {
|
|
_coefficients[_leftBlinkIndex] = 0.0f;
|
|
_coefficients[_leftEyeOpenIndex] = -eyeCoefficients[0];
|
|
}
|
|
if (eyeCoefficients[1] > 0) {
|
|
_coefficients[_rightBlinkIndex] = eyeCoefficients[1];
|
|
_coefficients[_rightEyeOpenIndex] = 0.0f;
|
|
} else {
|
|
_coefficients[_rightBlinkIndex] = 0.0f;
|
|
_coefficients[_rightEyeOpenIndex] = -eyeCoefficients[1];
|
|
}
|
|
|
|
// Scale all coefficients
|
|
for (int i = 0; i < NUM_EXPRESSIONS; i++) {
|
|
_blendshapeCoefficients[i]
|
|
= glm::clamp(DDE_COEFFICIENT_SCALES[i] * _coefficients[i], 0.0f, 1.0f);
|
|
}
|
|
|
|
// Calculate average frame time
|
|
const float FRAME_AVERAGING_FACTOR = 0.99f;
|
|
quint64 usecsNow = usecTimestampNow();
|
|
if (_lastMessageReceived != 0) {
|
|
_averageMessageTime = FRAME_AVERAGING_FACTOR * _averageMessageTime
|
|
+ (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f;
|
|
}
|
|
_lastMessageReceived = usecsNow;
|
|
|
|
FaceTracker::countFrame();
|
|
|
|
} else {
|
|
qCWarning(interfaceapp) << "DDE Face Tracker: Decode error";
|
|
}
|
|
|
|
if (_isCalibrating && _calibrationCount > CALIBRATION_SAMPLES) {
|
|
finishCalibration();
|
|
}
|
|
}
|
|
|
|
static const int CALIBRATION_BILLBOARD_WIDTH = 240;
|
|
static const int CALIBRATION_BILLBOARD_HEIGHT = 180;
|
|
static const int CALIBRATION_BILLBOARD_TOP_MARGIN = 60;
|
|
static const int CALIBRATION_BILLBOARD_LEFT_MARGIN = 30;
|
|
static const int CALIBRATION_BILLBOARD_FONT_SIZE = 16;
|
|
static const float CALIBRATION_BILLBOARD_ALPHA = 0.5f;
|
|
static QString CALIBRATION_INSTRUCTION_MESSAGE = "Hold still to calibrate";
|
|
|
|
void DdeFaceTracker::calibrate() {
|
|
if (!_isCalibrating) {
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration started";
|
|
|
|
_isCalibrating = true;
|
|
_calibrationCount = 0;
|
|
_calibrationMessage = CALIBRATION_INSTRUCTION_MESSAGE + "\n\n";
|
|
|
|
_calibrationBillboard = new TextOverlay();
|
|
_calibrationBillboard->setTopMargin(CALIBRATION_BILLBOARD_TOP_MARGIN);
|
|
_calibrationBillboard->setLeftMargin(CALIBRATION_BILLBOARD_LEFT_MARGIN);
|
|
_calibrationBillboard->setFontSize(CALIBRATION_BILLBOARD_FONT_SIZE);
|
|
_calibrationBillboard->setText(CALIBRATION_INSTRUCTION_MESSAGE);
|
|
_calibrationBillboard->setAlpha(CALIBRATION_BILLBOARD_ALPHA);
|
|
glm::vec2 viewport = qApp->getCanvasSize();
|
|
_calibrationBillboard->setX((viewport.x - CALIBRATION_BILLBOARD_WIDTH) / 2);
|
|
_calibrationBillboard->setY((viewport.y - CALIBRATION_BILLBOARD_HEIGHT) / 2);
|
|
_calibrationBillboard->setWidth(CALIBRATION_BILLBOARD_WIDTH);
|
|
_calibrationBillboard->setHeight(CALIBRATION_BILLBOARD_HEIGHT);
|
|
_calibrationBillboardID = qApp->getOverlays().addOverlay(_calibrationBillboard);
|
|
|
|
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
_calibrationValues[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DdeFaceTracker::addCalibrationDatum() {
|
|
const int LARGE_TICK_INTERVAL = 30;
|
|
const int SMALL_TICK_INTERVAL = 6;
|
|
int samplesLeft = CALIBRATION_SAMPLES - _calibrationCount;
|
|
if (samplesLeft % LARGE_TICK_INTERVAL == 0) {
|
|
_calibrationMessage += QString::number(samplesLeft / LARGE_TICK_INTERVAL);
|
|
_calibrationBillboard->setText(_calibrationMessage);
|
|
} else if (samplesLeft % SMALL_TICK_INTERVAL == 0) {
|
|
_calibrationMessage += ".";
|
|
_calibrationBillboard->setText(_calibrationMessage);
|
|
}
|
|
|
|
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
_calibrationValues[i] += _coefficients[i];
|
|
}
|
|
|
|
_calibrationCount += 1;
|
|
}
|
|
|
|
void DdeFaceTracker::cancelCalibration() {
|
|
qApp->getOverlays().deleteOverlay(_calibrationBillboardID);
|
|
_calibrationBillboard = NULL;
|
|
_isCalibrating = false;
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration cancelled";
|
|
}
|
|
|
|
void DdeFaceTracker::finishCalibration() {
|
|
qApp->getOverlays().deleteOverlay(_calibrationBillboardID);
|
|
_calibrationBillboard = NULL;
|
|
_isCalibrating = false;
|
|
|
|
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
_coefficientAverages[i] = _calibrationValues[i] / (float)CALIBRATION_SAMPLES;
|
|
}
|
|
|
|
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration finished";
|
|
}
|