//-------------------------------------------------------------------------------------- // GestureDetector.cpp // // Definitions for the gesture detector and gesture detector trainer, as well definitions // for weak and strong classifiers. The gesture detector trainer uses the AdaBoost // learning alogorith. // // Advanced Technology Group (ATG) // Copyright (C) Microsoft Corporation. All rights reserved. //-------------------------------------------------------------------------------------- #pragma once #if defined(GESTURE_TRAINER) #include "GestureDetector.h" #include #include #include #include #include #include "FExample.h" #include "IExample.h" using namespace Examples; #include struct SVariable { float mean; float stddev; float W; //sum_weights; SVariable() {Reset();} void Reset() {mean = stddev = W = 0.0f;} void AddSample(float f, float weight = 1.0f) { static float maxSamples = 10000.0f; float Q = (stddev*stddev) * W; Q += weight * (W / (W+weight)) * (f-mean) * (f-mean); if (W < maxSamples) //avoid totally losing influence W += weight; mean += (f-mean) * (weight/ W); stddev = sqrt(Q / W); } }; using namespace std; namespace KinectGesture { //-------------------------------------------------------------------------------------- // Constants //-------------------------------------------------------------------------------------- // Each feature used to generate weak classifiers has a min, max and step. This controls // how many weak classifiers gets generated per feature set. These values are either // easy to understand, e.g. min and max values of an angle is 0 and 180, or they were // empirically derived, e.g. what is the min and max velocity that your hand joint can // move at. By making the step smaller, you'll get a finer grain of decision stump, resulting // in more weak classifiers generated for that feature. static const FLOAT fAngleMin = 0.0f; static const FLOAT fAngleMax = 180.0f; static const FLOAT fAngleStep = 2.0f; static const FLOAT fTimeSpaceAngleMin = 90.0f; static const FLOAT fTimeSpaceAngleMax = 180.0f; static const FLOAT fTimeSpaceAngleStep = 1.0f; static const FLOAT fSpeedMin = 0.0f; static const FLOAT fSpeedMax = 10.0f; static const FLOAT fSpeedStep = 0.05f; static const FLOAT fVelocityMin = -5.0f; static const FLOAT fVelocityMax = 5.0f; static const FLOAT fVelocityStep = 0.1f; static const FLOAT fAngleVelocityMin = -5.0f; static const FLOAT fAngleVelocityMax = 5.0f; static const FLOAT fAngleVelocityStep = 0.25f; static const FLOAT fAngleAccelMin = -500.0f; static const FLOAT fAngleAccelMax = 500.0f; static const FLOAT fAngleAccelStep = 10.0f; static const FLOAT fMuscleForceMin = -5.0f; static const FLOAT fMuscleForceMax = 5.0f; static const FLOAT fMuscleForceStep = 0.1f; static const FLOAT fMuscleTorqueMin = -5.0f; static const FLOAT fMuscleTorqueMax = 5.0f; static const FLOAT fMuscleTorqueStep = 0.1f; static const FLOAT fMusclePowerMin = -100.0f; static const FLOAT fMusclePowerMax = 100.0f; static const FLOAT fMusclePowerStep = 1.0f; static const FLOAT fDiffMuscleForceMin = -1.0f; static const FLOAT fDiffMuscleForceMax = 1.0f; static const FLOAT fDiffMuscleForceStep = 0.2f; static const FLOAT fPositionMin = -1.0f; static const FLOAT fPositionMax = 1.0f; static const FLOAT fPositionStep = 0.1f; static const FLOAT fVelocitySQMin = 0.0f; static const FLOAT fVelocitySQMax = 25.0f; static const FLOAT fVelocitySQStep = 0.1f; static const FLOAT fSpeedSQMin = 0.0f; static const FLOAT fSpeedSQMax = 100.0f; static const FLOAT fSpeedSQStep = 0.25f; static const FLOAT fAccelMin = 0.0f; static const FLOAT fAccelMax = 20.0f; static const FLOAT fAccelStep = 0.1f; static const FLOAT fBoneChangesMin = 0.0f; static const FLOAT fBoneChangesMax = 2.0f; static const FLOAT fBoneChangesStep = 0.01f; static const FLOAT fOpticalFlowMin = -0.5f; static const FLOAT fOpticalFlowMax = 0.5f; static const FLOAT fOpticalFlowStep = 0.01f; static const FLOAT fOpticalFlowLenSQMin = 0.0f; static const FLOAT fOpticalFlowLenSQMax = 0.25f; static const FLOAT fOpticalFlowLenSQStep = 0.001f; static const FLOAT fOpticalFlowTanMin = -3.15f; static const FLOAT fOpticalFlowTanMax = 3.15f; static const FLOAT fOpticalFlowTanStep = 0.1f; static const FLOAT fOpticalFlowDiffMin = -0.5f; static const FLOAT fOpticalFlowDiffMax = 0.5f; static const FLOAT fOpticalFlowDiffStep = 0.01f; static const DOUBLE fMinErrorThreshold = 0.25; static const DOUBLE fMaxErrorThreshold = 0.5; // At runtime we get a per frame results, so we need to filter the results to a per gesture // result. The filter is implemented as a sliding window with two parameters, the size of // the sliding window and a threshold, almost like a amplitude and frequency. These constants // define a matrix of possible values of these two parameters which we'll use to find the // most optimum pair of parameters for filtering. static const FLOAT fDetectionParamsMinThreshold = 0.0f; static const FLOAT fDetectionParamsMaxThreshold = 0.1f; static const FLOAT fDetectionParamsThresholdStep = 0.001f; static const UINT nDetectionParamsMinNumFrames = 1; static const UINT nDetectionParamsMaxNumFrames = 10; //-------------------------------------------------------------------------------------- // Name: GestureDetectorTrainer() // Desc: Constructor //-------------------------------------------------------------------------------------- GestureDetectorTrainer::GestureDetectorTrainer() : GestureDetector() { Reset(); m_nTotalNumGestures = 0; m_nNumTrainingGestures = 0; m_nNumThreadsForTraining = 0; m_nMaxNumThreads = omp_get_max_threads(); m_nNumWeakClassifiersAtRuntime = 0; m_fErrorThreshold = 0.0f; m_bUseSkeleton = TRUE; m_bUseOpticalFlow = FALSE; m_nFramesToSkip = 0; } //-------------------------------------------------------------------------------------- // Name: ~GestureDetectorTrainer // Desc: Destructor //-------------------------------------------------------------------------------------- GestureDetectorTrainer::~GestureDetectorTrainer() { Reset(); } //-------------------------------------------------------------------------------------- // Name: Reset // Desc: Reset all state and delete allocated memory for labeled example data //-------------------------------------------------------------------------------------- VOID GestureDetectorTrainer::Reset() { for ( UINT i = 0; i < KINECT_GESTURE_MAX_SIMULTANEOUS_GESTURES; i++ ) { m_StrongClassifier.Reset( i ); } m_uPreviousTimeStamp = 0; UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); for ( UINT i = 0; i < nNumExamples; i++ ) { if ( m_LabeledExamples.m_pExamples[ i ] ) { _aligned_free( m_LabeledExamples.m_pExamples[ i ] ); m_LabeledExamples.m_pExamples[ i ] = NULL; } } m_LabeledExamples.m_pExamples.clear(); m_LabeledExamples.m_iLabels.clear(); m_LabeledExamples.m_uTimeStamps.clear(); m_nTotalNumGestures = 0; m_nNumTrainingGestures = 0; } //-------------------------------------------------------------------------------------- // Name: Save // Desc: Save data //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::Save( const CHAR* szFileName ) { FILE* pFile = NULL; fopen_s( &pFile, szFileName, "wb" ); RETURN_ON_NULL( pFile ); // Write a text identifier fwrite( getGestureFileIDs()[0], strlen( getGestureFileIDs()[0] ) + 1, 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); // Write current version number FLOAT fBigEndianValue = ByteSwap32BitWrite( g_fCurrentVersion ); fwrite( &fBigEndianValue, sizeof( fBigEndianValue ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); const UINT nNumWeakClassifiers = m_StrongClassifier.GetNumWeakClassifiers(); const UINT nNumClassifierData = (UINT)m_ClassifierData.size(); //collect list of used ClassifierData vector vUsed; vUsed.reserve(nNumClassifierData); for ( UINT j = 0; j < nNumClassifierData; j++ ) { vUsed.push_back(0xffffffff); } UINT numUsed = 0; for ( UINT i = 0; i < nNumWeakClassifiers; i++ ) { WeakClassifier *pWC = m_StrongClassifier.GetWeakClassifierAt(i); UINT uDataIndex = pWC->GetDataIndex(); if ( vUsed[ uDataIndex ]==0xffffffff ) { vUsed[ uDataIndex ] = numUsed++; } } // Write numbers UINT32 uBigEndianValue = ByteSwap32BitWrite( (UINT32)nNumWeakClassifiers ); fwrite( &uBigEndianValue, sizeof( uBigEndianValue ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); uBigEndianValue = ByteSwap32BitWrite( (UINT32)numUsed ); fwrite( &uBigEndianValue, sizeof( uBigEndianValue ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); // Write ClassifierData UINT index = 0; for ( UINT i = 0; i < nNumClassifierData; i++ ) { if ( vUsed[ i ]!=0xffffffff ) { //also temporarily set UID to index to match saved index in weak classifier (just for compatibility, but doesn't really matter) m_ClassifierData[ i ]->SetID( index ); RETURN_ON_FAIL( m_ClassifierData[ i ]->Write( pFile ) ); vUsed[ i ] = index; index++; } } RETURN_ON_FAIL( m_StrongClassifier.Write( pFile ) ); // Write weak classifiers for ( UINT i = 0; i < nNumWeakClassifiers; i++ ) { WeakClassifier *pWC = m_StrongClassifier.GetWeakClassifierAt(i); //temporarily convert in-memory index to in-file index (should match between ClassifierData and vUsed) UINT memIndex = pWC->GetDataIndex(); ClassifierData *pData = m_ClassifierData[ pWC->GetDataIndex() ]; assert( vUsed[ pWC->GetDataIndex() ] != 0xffffffff ); assert( pData->GetID() == vUsed[ pWC->GetDataIndex() ] ); pWC->SetDataIndex( pData->GetID() ); RETURN_ON_FAIL( m_StrongClassifier.Write( pFile, i ) ); //restore in-mem index pWC->SetDataIndex( memIndex ); } fclose( pFile ); //restore UIDs for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( vUsed[ j ]!=0xffffffff ) { m_ClassifierData[ j ]->SetID( m_ClassifierData[ j ]->MakeUID() ); } } return S_OK; } //-------------------------------------------------------------------------------------- // Name: Train // Desc: Uses the AdaBoost training algorithm to train a strong classifier H(x) as a // weighted sum of weak classifiers h(x) //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::Train( CHAR* const* szLeFiles, const UINT uiLeFileCount, const DOUBLE fAccuracyLevel, const UINT nNumWeakClassifiersAtRuntime, const FLOAT fWeightOfFalsePositivesWhenFiltering, const BOOL bOnlyRetrainDetectionParameters, const UINT uCPUAvailableForTraining, BOOL useSkeleton, BOOL useOpticalFlow, UINT framesToSkip ) { m_nNumWeakClassifiersAtRuntime = nNumWeakClassifiersAtRuntime; m_bUseSkeleton = useSkeleton; m_bUseOpticalFlow = useOpticalFlow; m_nFramesToSkip = framesToSkip; // Accuracy level is an input value between between [0..1], which simply gets converted to a error threshold // value of maximum 0.5, since for AdaBoost weak classifiers has to be better than a 50/50 change to be correct m_fErrorThreshold = ( fAccuracyLevel * ( fMaxErrorThreshold - fMinErrorThreshold ) ) + fMinErrorThreshold; // Set how much CPU resources the user is willing to use for training. Calculate the number of threads from the % utilization m_nNumThreadsForTraining = (UINT)( 0.5f + ( m_nMaxNumThreads * uCPUAvailableForTraining / 100.0f ) ); BOOL bSetDynamic = ( m_nNumThreadsForTraining == m_nMaxNumThreads ) ? FALSE : TRUE; omp_set_dynamic( bSetDynamic ); omp_set_num_threads( m_nNumThreadsForTraining ); Reset(); printf( "\n\nStep 1 of 4: Loading Labeled Training Examples" ); DWORD dwStart = GetTickCount(); for ( UINT i = 0; i < uiLeFileCount; i++ ) { RETURN_ON_FAIL( LoadLabeledExamples( szLeFiles[ i ] ) ); } if( m_LabeledExamples.m_pExamples.size() == 0 ) { printf( "\nERROR: No labeled examples loaded.", GetNumExamples() ); return E_FAIL; } DWORD dwStop = GetTickCount(); DWORD dwSeconds = ( (dwStop - dwStart ) / 1000 ) % 60; DWORD dwMinutes = ( (dwStop - dwStart ) / 1000) / 60; printf( "\n\tNum Labeled Examples: %d", GetNumExamples() ); printf( "\n\tDuration: %d minutes, %d seconds", dwMinutes, dwSeconds ); printf( "\nDone\n" ); printf( "\n\nStep 2 of 4: Generating a Pool of Weak Classifiers" ); dwStart = GetTickCount(); if ( !bOnlyRetrainDetectionParameters ) { RETURN_ON_FAIL( TrainWeakClassifiers() ); } dwStop = GetTickCount(); dwSeconds = ( ( dwStop - dwStart ) / 1000 ) % 60; dwMinutes = ( ( dwStop - dwStart ) / 1000) / 60; printf( "\n\tNum weak classifiers generated: %d", GetNumWeakClassifiers() ); printf( "\n\tDuration: %d minutes, %d seconds", dwMinutes, dwSeconds ); printf( "\nDone\n" ); printf( "\n\nStep 3 of 4: Training Strong Classifier" ); dwStart = GetTickCount(); if ( !bOnlyRetrainDetectionParameters ) { RETURN_ON_FAIL( TrainStrongClassifier( TRUE ) ); Optimize( nNumWeakClassifiersAtRuntime ); } dwStop = GetTickCount(); dwSeconds = ( ( dwStop - dwStart ) / 1000 ) % 60; dwMinutes = ( ( dwStop - dwStart ) / 1000) / 60; printf( "\n\tNum weak classifiers: %d", GetNumWeakClassifiers() ); printf( "\n\tDuration: %d minutes, %d seconds", dwMinutes, dwSeconds ); printf( "\n\nStep 4 of 4: Optimizing detection parameters" ); dwStart = GetTickCount(); OptimizeDetectionParameters( fWeightOfFalsePositivesWhenFiltering ); dwStop = GetTickCount(); dwSeconds = ( ( dwStop - dwStart ) / 1000 ) % 60; dwMinutes = ( ( dwStop - dwStart ) / 1000) / 60; printf( "\n\tFiltering %d frames using detection threshold %f", GetNumFramesToFilter(), GetDetectionThreshold() ); printf( "\n\tDuration: %d minutes, %d seconds", dwMinutes, dwSeconds ); printf( "\nDone\n" ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: Test // Desc: Test the accuracy of the training algorithm //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::Test( const CHAR* szPath, const BOOL bTestOnTrainingData ) { if ( !bTestOnTrainingData ) { RETURN_ON_FAIL( LoadLabeledExamples( szPath ) ); } // Clear all the data ClassifierData::Initialize(); m_StrongClassifier.Reset( 0 ); m_uPreviousTimeStamp = 0; XMVECTOR vUpVector = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f ); printf( "\nTesting...\n" ); //test using our scoring method { int nGesturesLabeled[2] = {0,0}; int nFrames[3] = {0,0,0}; INT8 iLastLabel = 0; SVariable Stats[2]; const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); for ( UINT i = 0; i < nNumExamples; i++ ) { // Get the example from the ground truth training set GESTURE_SKELETON_TYPE* pSkeletonData = m_LabeledExamples.m_pExamples[ i ]; UINT64 uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; // Update the classifier data. Time stamps from Xed files are different to timestamps from runtime NUI_SKELETON_FRAMES LARGE_INTEGER liTimeStamp; liTimeStamp.QuadPart = uTimeStamp; Update( 0, pSkeletonData, liTimeStamp, vUpVector ); INT8 label = m_LabeledExamples.m_iLabels[ i ]; if (label == 0) { nFrames[2]++; iLastLabel = label; continue; //skip } if (label == g_iClassificationLabelCorrect) { nFrames[1]++; if (iLastLabel!=label) { nGesturesLabeled[1]++; } } else if (label == g_iClassificationLabelIncorrect) { nFrames[0]++; if (iLastLabel!=label) { nGesturesLabeled[0]++; //ToDo: evaluate per-move stats } } iLastLabel = label; Results results; m_StrongClassifier.Detect( 0, m_ClassifierData, &results, FALSE ); float fMin = m_StrongClassifier.GetMean( KinectGesture::StrongClassifier::eLabel_Incorrect ) + m_StrongClassifier.GetStdDev( KinectGesture::StrongClassifier::eLabel_Incorrect ); float fMax = m_StrongClassifier.GetDetectionThreshold(); float score = ( results.m_fConfidence - fMin ) / ( fMax - fMin ); Stats[label==g_iClassificationLabelCorrect ? 0 : 1].AddSample(score); } printf( "\nResults:" ); printf( "\n\tpositive frames: %d", nFrames[1] ); printf( "\n\tnegative frames: %d", nFrames[0] ); printf( "\n\tignored frames: %d", nFrames[2] ); printf( "\n\tpositive gestures: %d", nGesturesLabeled[1] ); printf( "\n\tnegative gestures: %d", nGesturesLabeled[0] ); printf( "\n\tpositive frames' score mean, std.dev: %f, %f", Stats[0].mean, Stats[0].stddev ); printf( "\n\tnegative frames' score mean, std.dev: %f, %f", Stats[1].mean, Stats[1].stddev ); } return S_OK; // run strong classifier on training data for verification UINT nNumGesturesDetected = 0; UINT nNumWrongGesturesDetected = 0; INT nNumPositiveExamples = 0; INT nNumTrueDetections = 0; INT nNumFalseDetections = 0; // We need to make sure the indices match up, so add two in the beginning and two at the end vector fFilteredClassificationResults; fFilteredClassificationResults.push_back( FALSE ); fFilteredClassificationResults.push_back( FALSE ); const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); for ( UINT i = 2; i < nNumExamples - 2; i++ ) { // Get the example from the ground truth training set GESTURE_SKELETON_TYPE* pSkeletonData = m_LabeledExamples.m_pExamples[ i ]; UINT64 uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; // Update the classifier data. Time stamps from Xed files are different to timestamps from runtime NUI_SKELETON_FRAMES LARGE_INTEGER liTimeStamp; liTimeStamp.QuadPart = uTimeStamp; Update( 0, pSkeletonData, liTimeStamp, vUpVector ); // Run the strong classifier on player 0 Results results; if ( m_StrongClassifier.Detect( 0, m_ClassifierData, &results, FALSE ) ) { // Testing for per gesture results if ( results.m_bFirstFrameDetected ) { // We cannot just detect if the first frame of the gesture correlate to the a // ground truth labeled example, since the detection might be a few frames off // but still detected the gesture correctly. We therefore allow for 2 frames // to either side when testing the detection results against the ground truth if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect || m_LabeledExamples.m_iLabels[ i - 1 ] == g_iClassificationLabelCorrect || m_LabeledExamples.m_iLabels[ i + 1 ] == g_iClassificationLabelCorrect || m_LabeledExamples.m_iLabels[ i - 2 ] == g_iClassificationLabelCorrect || m_LabeledExamples.m_iLabels[ i + 2 ] == g_iClassificationLabelCorrect ) { if ( ( m_LabeledExamples.m_iLabels[ i - 1 ] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i - 2 ] != g_iClassificationLabelCorrect ) || ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i - 1 ] != g_iClassificationLabelCorrect ) || ( m_LabeledExamples.m_iLabels[ i + 1] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i ] != g_iClassificationLabelCorrect ) || ( m_LabeledExamples.m_iLabels[ i + 2 ] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i + 1 ] != g_iClassificationLabelCorrect ) ) { nNumGesturesDetected++; } else { nNumWrongGesturesDetected++; } } else { nNumWrongGesturesDetected++; } } } fFilteredClassificationResults.push_back( results.m_bDetected ); // Testing for per frame results if ( m_StrongClassifier.Detect( 0, m_ClassifierData, &results, FALSE ) ) { if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect ) { nNumTrueDetections++; } else { nNumFalseDetections++; } } if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect ) { nNumPositiveExamples++; } } fFilteredClassificationResults.push_back( FALSE ); fFilteredClassificationResults.push_back( FALSE ); INT nNumTruePositiveGesturesGT = 0; INT nNumTruePositiveGesturesObserved = 0; INT nNumFalsePositiveGesturesObserved = 0; for ( UINT i = 1; i < nNumExamples; i++ ) { // Find a true positive gesture in GT. Count only the start of each sequence // of GT frames that make up the gesture if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i - 1 ] != g_iClassificationLabelCorrect ) { nNumTruePositiveGesturesGT++; } else { continue; } // Find a true positive gesture in the observed data during the GT frames. for ( UINT j = i; j < nNumExamples; j++) { if ( fFilteredClassificationResults[ j ] ) { nNumTruePositiveGesturesObserved++; break; } // Check for the end of the gesture in GT if ( m_LabeledExamples.m_iLabels[ j ] != g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ j - 1 ] == g_iClassificationLabelCorrect ) { break; } } // Find all true positive gesture in the observed data during the GT frames. INT nNumObserved = 0; for ( UINT j = i; j < nNumExamples; j++ ) { if ( fFilteredClassificationResults[ j ] && !fFilteredClassificationResults[ j - 1 ] ) { nNumObserved++; } // Check for the end of the gesture in GT if ( m_LabeledExamples.m_iLabels[ j ] != g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ j - 1 ] == g_iClassificationLabelCorrect ) { break; } } nNumFalsePositiveGesturesObserved += max( 0, nNumObserved - 1 ); // we only allow 1 detection during GT gesture } // Now we try to find false positives from observed data with no GT for ( UINT i = 1; i < nNumExamples; i++ ) { // Find an observed detection if ( fFilteredClassificationResults[ i ] && !fFilteredClassificationResults[i - 1] ) { } else { continue; } // Find a true positive gesture in the GT data during the observed frames. INT numGT = 0; for ( UINT j = i; j < nNumExamples; j++ ) { if ( m_LabeledExamples.m_iLabels[ j ] == g_iClassificationLabelCorrect ) { numGT++; break; } // Check for the end of the gesture in observed data if ( !fFilteredClassificationResults[ j ] && fFilteredClassificationResults[ j - 1 ] ) { break; } } nNumFalsePositiveGesturesObserved += max( 0, 1 - numGT ); } FLOAT fTruePositives = 0.0f; FLOAT fFalsePositives = 0.0f; if ( nNumTruePositiveGesturesGT < 1 && nNumTruePositiveGesturesObserved < 1 ) { // We have no GT or observed gestures, so no error fTruePositives = 100.0f; } else if ( nNumTruePositiveGesturesGT < 1 && nNumTruePositiveGesturesObserved >= 1 ) { // We have no GT gestures, but did find observed gestures, so report no error // since we're calculating false negatives here, not false positives fTruePositives = 100.0f; } else { fTruePositives = 100.0f * nNumTruePositiveGesturesObserved / (FLOAT)nNumTruePositiveGesturesGT; } if ( nNumTruePositiveGesturesGT < 1 && nNumFalsePositiveGesturesObserved < 1 ) { // We have no GT or observed gestures, so no error fFalsePositives = 0.0f; } else if ( nNumTruePositiveGesturesGT < 1 && nNumFalsePositiveGesturesObserved >= 1 ) { // We have no GT gestures, but did find observed gestures, so return highest error fFalsePositives = 100.0f; } else { fFalsePositives = 100.0f * nNumFalsePositiveGesturesObserved / (FLOAT)nNumTruePositiveGesturesGT; } // Output accuracy in true positives and false positives for per frame results FLOAT fAccuracy; if ( nNumPositiveExamples == 0 ) { fAccuracy = 100.0f; } else { fAccuracy = nNumTrueDetections * 100.0f / nNumPositiveExamples; } FLOAT fErrorFalsePositives = (FLOAT)nNumFalseDetections * 100.0f / ( nNumExamples - 4 - nNumPositiveExamples ); printf( "\n Raw Per Frame Results:" ); printf( "\n\t%% Accuracy True Positives: %f %% (%d/%d)", fAccuracy, nNumTrueDetections, nNumPositiveExamples ); printf( "\n\t%% Error False Positives: %f %% (%d/%d)", fErrorFalsePositives, nNumFalseDetections, nNumExamples - 4 - nNumPositiveExamples ); // Output accuracy in true positives and false positives for filtered per gesture results printf( "\n Filtered Per Gesture Results:" ); printf( "\n\t%% Accuracy True Positives: %f %% (%d/%d)", fTruePositives, nNumTruePositiveGesturesObserved, nNumTruePositiveGesturesGT ); printf( "\n\t%% Error False Positives: %f %% (%d/%d)", fFalsePositives, nNumFalsePositiveGesturesObserved, nNumTruePositiveGesturesGT ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: Test // Desc: Test the accuracy of the training algorithm. Used to find the best filtering // parameters //-------------------------------------------------------------------------------------- VOID GestureDetectorTrainer::Test( FLOAT* pTruePositives, FLOAT* pFalsePositives, vector& fRawClassificationResults ) { // Clear all the data ClassifierData::Initialize(); m_StrongClassifier.Reset( 0 ); m_uPreviousTimeStamp = 0; // run strong classifier on training data for verification INT nNumTruePositiveGesturesGT = 0; INT nNumTruePositiveGesturesObserved = 0; INT nNumFalsePositiveGesturesObserved = 0; const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); // Get all filtered data vector fFilteredClassificationResults; for ( UINT i = 0; i < nNumExamples; i++ ) { // Filter the cached raw detection results with the current detection thresholds Results results; m_StrongClassifier.FilterDetectionResults( 0, fRawClassificationResults[ i ], &results ); fFilteredClassificationResults.push_back( results.m_bDetected ); } for ( UINT i = 1; i < nNumExamples; i++ ) { // Find a true positive gesture in GT. Count only the start of each sequence // of GT frames that make up the gesture if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ i - 1 ] != g_iClassificationLabelCorrect ) { nNumTruePositiveGesturesGT++; } else { continue; } // Find a true positive gesture in the observed data during the GT frames. for ( UINT j = i; j < nNumExamples; j++) { if ( fFilteredClassificationResults[ j ] ) { nNumTruePositiveGesturesObserved++; break; } // Check for the end of the gesture in GT if ( m_LabeledExamples.m_iLabels[ j ] != g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ j - 1 ] == g_iClassificationLabelCorrect ) { break; } } // Find all true positive gesture in the observed data during the GT frames. INT nNumObserved = 0; for ( UINT j = i; j < nNumExamples; j++ ) { if ( fFilteredClassificationResults[ j ] && !fFilteredClassificationResults[ j - 1 ] ) { nNumObserved++; } // Check for the end of the gesture in GT if ( m_LabeledExamples.m_iLabels[ j ] != g_iClassificationLabelCorrect && m_LabeledExamples.m_iLabels[ j - 1 ] == g_iClassificationLabelCorrect ) { break; } } nNumFalsePositiveGesturesObserved += max( 0, nNumObserved - 1 ); // we only allow 1 detection during GT gesture } // Now we try to find false positives from observed data with no GT for ( UINT i = 1; i < nNumExamples; i++ ) { // Find an observed detection if ( fFilteredClassificationResults[ i ] && !fFilteredClassificationResults[i - 1] ) { } else { continue; } // Find a true positive gesture in the GT data during the observed frames. INT numGT = 0; for ( UINT j = i; j < nNumExamples; j++ ) { if ( m_LabeledExamples.m_iLabels[ j ] == g_iClassificationLabelCorrect ) { numGT++; break; } // Check for the end of the gesture in observed data if ( !fFilteredClassificationResults[ j ] && fFilteredClassificationResults[ j - 1 ] ) { break; } } nNumFalsePositiveGesturesObserved += max( 0, 1 - numGT ); } if ( nNumTruePositiveGesturesGT < 1 && nNumTruePositiveGesturesObserved < 1 ) { // We have no GT or observed gestures, so no error *pTruePositives = 100.0f; } else if ( nNumTruePositiveGesturesGT < 1 && nNumTruePositiveGesturesObserved >= 1 ) { // We have no GT gestures, but did find observed gestures, so report no error // since we're calculating false negatives here, not false positives *pTruePositives = 100.0f; } else { *pTruePositives = 100.0f * nNumTruePositiveGesturesObserved / (FLOAT)nNumTruePositiveGesturesGT; } if ( nNumTruePositiveGesturesGT < 1 && nNumFalsePositiveGesturesObserved < 1 ) { // We have no GT or observed gestures, so no error *pFalsePositives = 0.0f; } else if ( nNumTruePositiveGesturesGT < 1 && nNumFalsePositiveGesturesObserved >= 1 ) { // We have no GT gestures, but did find observed gestures, so return highest error *pFalsePositives = 100.0f; } else { *pFalsePositives = 100.0f * nNumFalsePositiveGesturesObserved / (FLOAT)nNumTruePositiveGesturesGT; } } //-------------------------------------------------------------------------------------- // Private structure used to find the most optimimum filter parameters for per gesture // detection. //-------------------------------------------------------------------------------------- struct DetectionParameters { FLOAT m_fThreshold; UINT m_nNumFramesToFilter; FLOAT m_fError; INT x; INT y; inline DetectionParameters& operator = ( const DetectionParameters& rhs) { m_fThreshold = rhs.m_fThreshold; m_nNumFramesToFilter = rhs.m_nNumFramesToFilter; m_fError = rhs.m_fError; x = rhs.x; y = rhs.y; return *this; } inline bool operator < ( const DetectionParameters& rhs ) { return ( m_fError < rhs.m_fError ); } }; //-------------------------------------------------------------------------------------- // Name: CalcSumOfNeighbours // Desc: Calculates the sum of all values in a 3x3 kernal //-------------------------------------------------------------------------------------- FLOAT CalcSumOfNeighbours( const INT x, const INT y, vector>& fValues ) { FLOAT fSum = 0.0f; for ( INT iNeighborY = y - 1; iNeighborY <= y + 1; iNeighborY++ ) { for ( INT iNeighborX = x - 1; iNeighborX <= x + 1; iNeighborX++ ) { fSum += fValues[ iNeighborY ][ iNeighborX ].m_fError; } } return fSum; } //-------------------------------------------------------------------------------------- // Name: OptimizeDetectionParameters // Desc: Since the classifier results are per frame and not per gesture, we need to // apply a filter on the raw per frame results. This is in the form of a sum of // a sliding window with a threshold. We therefore have two parameters to // find that will minimize both the error in true positives and false postives. //-------------------------------------------------------------------------------------- VOID GestureDetectorTrainer::OptimizeDetectionParameters( const FLOAT fWeightOfFalsePositivesWhenFiltering ) { printf( "\n\t-Generate matrix of test results using different detection parameters..." ); XMVECTOR vUpVector = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f ); { // Cache raw results from classification vector fRawClassificationResults; // Clear all the data ClassifierData::Initialize(); m_StrongClassifier.Reset( 0 ); m_uPreviousTimeStamp = 0; // Run strong classifier const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); for ( UINT i = 0; i < nNumExamples; i++ ) { // Get the example from the ground truth training set GESTURE_SKELETON_TYPE* pSkeletonData = m_LabeledExamples.m_pExamples[ i ]; UINT64 uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; // Update the classifier data. Time stamps from Xed files are different to timestamps from runtime NUI_SKELETON_FRAMES LARGE_INTEGER liTimeStamp; liTimeStamp.QuadPart = uTimeStamp; Update( 0, pSkeletonData, liTimeStamp, vUpVector ); // Run the strong classifier on player 0, without any filtering Results results; m_StrongClassifier.Detect( 0, m_ClassifierData, &results, FALSE ); // Store the raw classification result fRawClassificationResults.push_back( results.m_fConfidence ); } vector> values; vector> summedValues; // Fill in a 2d matrix of possible parameters UINT i = 0; for ( FLOAT fThreshold = fDetectionParamsMinThreshold; fThreshold <= fDetectionParamsMaxThreshold; fThreshold += fDetectionParamsThresholdStep ) { values.resize( i + 1 ); for ( UINT nNumFrames = nDetectionParamsMinNumFrames; nNumFrames <= nDetectionParamsMaxNumFrames; nNumFrames++ ) { DetectionParameters parameters; parameters.m_fThreshold = fThreshold; parameters.m_nNumFramesToFilter = nNumFrames; parameters.x = nNumFrames - nDetectionParamsMinNumFrames; parameters.y = i; SetDetectionThreshold( fThreshold ); SetNumFramesToFilter( nNumFrames ); FLOAT fTruePositiveResults; FLOAT fFalsePositiveResults; Test( &fTruePositiveResults, &fFalsePositiveResults, fRawClassificationResults ); // Combine the results of false negatives and false positives with a weighted sum. We can bias towards optimizing // for fewer false positives or fewer false negatives. parameters.m_fError = ( fabsf( 100.0f - fTruePositiveResults ) * ( 1.0f - fWeightOfFalsePositivesWhenFiltering ) ) + ( fFalsePositiveResults * fWeightOfFalsePositivesWhenFiltering ); values[ i ].push_back( parameters ); } i++; } INT nNumY = (INT)values.size(); INT nNumX = (INT)values[ 0 ].size(); // Fill it with the sum of the 8 surrounding neighbours and itself for each value in the 2d array summedValues.resize( nNumY ); for ( INT y = 0; y < nNumY; y++ ) { for ( INT x = 0; x < nNumX; x++ ) { DetectionParameters params = values[ y ][ x ]; // Just use FLT_MAX around the edge pixels, since these will be invalid anyway if ( y == 0 || y == ( nNumY - 1 ) || x == 0 || x == ( nNumX - 1 ) ) { params.m_fError = FLT_MAX / 9.0f; } else { params.m_fError = CalcSumOfNeighbours( x, y, values ); } summedValues[ y ].push_back( params ); } } nNumY = (INT)summedValues.size(); nNumX = (INT)summedValues[ 0 ].size(); DetectionParameters bestParams = { 0, 0, FLT_MAX }; vector medianXFilterMinValues; vector medianYFilterMinValues; // Find the minimum summed error values for ( INT y = 0; y < nNumY; y++ ) { for ( INT x = 0; x < nNumX; x++ ) { if ( summedValues[ y ][ x ] < bestParams ) { bestParams = summedValues[ y ][ x ]; } } } // Find all the values equal to this minimum value and add them to an array for finding the median value // Traverse horizontally for ( INT y = 0; y < nNumY; y++ ) { for ( INT x = 0; x < nNumX; x++ ) { if ( fabsf( summedValues[ y ][ x ].m_fError - bestParams.m_fError ) < FLT_EPSILON ) { medianXFilterMinValues.push_back( summedValues[ y ][ x ] ); } } } // Find all the values equal to this minimum value and add them to an array for finding the median value // Traverse vertically for ( INT x = 0; x < nNumX; x++ ) { for ( INT y = 0; y < nNumY; y++ ) { if ( fabsf( summedValues[ y ][ x ].m_fError - bestParams.m_fError ) < FLT_EPSILON ) { medianYFilterMinValues.push_back( summedValues[ y ][ x ] ); } } } // Find the median value in each array sort( medianXFilterMinValues.begin(), medianXFilterMinValues.end() ); sort( medianYFilterMinValues.begin(), medianYFilterMinValues.end() ); INT iMedian = (INT)(medianXFilterMinValues.size() - 1 ) / 2; DetectionParameters medianX = medianXFilterMinValues[ iMedian ]; DetectionParameters medianY = medianYFilterMinValues[ iMedian ]; // Find out which one is best FLOAT fSmallestXError = FLT_MAX; for ( INT i = max( iMedian - 2, 0 ); i < min( iMedian + 2, (INT)medianXFilterMinValues.size() ); i++ ) { FLOAT fSum = CalcSumOfNeighbours( medianXFilterMinValues[ i ].x, medianXFilterMinValues[ i ].y, summedValues ); if ( fSum < fSmallestXError ) { fSmallestXError = fSum; medianX = medianXFilterMinValues[ i ]; } } FLOAT fSmallestYError = FLT_MAX; for ( INT i = max( iMedian - 2, 0 ); i < min( iMedian + 2, (INT)medianYFilterMinValues.size() ); i++ ) { FLOAT fSum = CalcSumOfNeighbours( medianYFilterMinValues[ i ].x, medianYFilterMinValues[ i ].y, summedValues ); if ( fSum < fSmallestYError ) { fSmallestYError = fSum; medianY = medianYFilterMinValues[ i ]; } } // The best one is the one with the smallest summed error value bestParams = ( fSmallestXError > fSmallestYError ) ? medianY : medianX; SetDetectionThreshold( bestParams.m_fThreshold ); SetNumFramesToFilter( bestParams.m_nNumFramesToFilter ); } { printf( "\n\t-Generating stats for confidence values (for scoring)..." ); // now we have this threshold, we need to establish a range to use for scoring // we use the incorrect-labeled examples to find a minimum and the correct labeled examples to find a maximum // so we need to find the smallest response for ground truth (the range min) // and the largest response for a ground truth (the range max) FLOAT fRangeMin[StrongClassifier::eLabel_Count] = { FLT_MAX, FLT_MAX }; FLOAT fRangeMax[StrongClassifier::eLabel_Count] = { -FLT_MAX, -FLT_MAX }; FLOAT fMean[StrongClassifier::eLabel_Count] = { 0.0f, 0.0f }; FLOAT fStdDev[StrongClassifier::eLabel_Count] = { 0.0f, 0.0f }; FLOAT fEnergyMean = 0.0f; FLOAT fEnergyStdDev = 0.0f; // Cache raw results from classification vector fCorrectResults; vector fIncorrectResults; vector fEnergyResults; UINT nNotLabeledExamples = 0; const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); for ( UINT i = 0; i < nNumExamples; i++ ) { // Get the example from the ground truth training set GESTURE_SKELETON_TYPE* pSkeletonData = m_LabeledExamples.m_pExamples[ i ]; UINT64 uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; // Update the classifier data. Time stamps from Xed files are different to timestamps from runtime NUI_SKELETON_FRAMES LARGE_INTEGER liTimeStamp; liTimeStamp.QuadPart = uTimeStamp; Update( 0, pSkeletonData, liTimeStamp, vUpVector ); // Run the strong classifier on player 0 unfiltered Results results; m_StrongClassifier.Detect( 0, m_ClassifierData, &results, FALSE ); if( m_LabeledExamples.m_iLabels[i] == g_iClassificationLabelCorrect ) { fRangeMax[StrongClassifier::eLabel_Correct] = max( fRangeMax[StrongClassifier::eLabel_Correct], results.m_fConfidence ); fRangeMin[StrongClassifier::eLabel_Correct] = min( fRangeMin[StrongClassifier::eLabel_Correct], results.m_fConfidence ); fMean[StrongClassifier::eLabel_Correct] += results.m_fConfidence; // Store the raw classification result fCorrectResults.push_back( results.m_fConfidence ); // only store energy levels for 'correct' labels FLOAT fEnergy = GetEnergyLevel( 0 ); fEnergyMean += fEnergy; fEnergyResults.push_back( fEnergy ); } else if( m_LabeledExamples.m_iLabels[i] == g_iClassificationLabelIncorrect ) { fRangeMax[StrongClassifier::eLabel_Incorrect] = max( fRangeMax[StrongClassifier::eLabel_Incorrect], results.m_fConfidence ); fRangeMin[StrongClassifier::eLabel_Incorrect] = min( fRangeMin[StrongClassifier::eLabel_Incorrect], results.m_fConfidence ); fMean[StrongClassifier::eLabel_Incorrect] += results.m_fConfidence; // Store the raw classification result fIncorrectResults.push_back( results.m_fConfidence ); } else { nNotLabeledExamples++; } } if( fCorrectResults.size() > 0 ) { fMean[StrongClassifier::eLabel_Correct] /= (FLOAT)fCorrectResults.size(); } if( fIncorrectResults.size() > 0 ) { fMean[StrongClassifier::eLabel_Incorrect] /= (FLOAT)fIncorrectResults.size(); } if( fEnergyResults.size() > 0 ) { fEnergyMean /= (FLOAT)fEnergyResults.size(); } FLOAT fSumOfDifferences[StrongClassifier::eLabel_Count] = { 0.0f, 0.0f }; const UINT nNumResults[StrongClassifier::eLabel_Count] = { (UINT)( fCorrectResults.size() ), (UINT)( fIncorrectResults.size() ) }; for ( UINT i = 0; i < nNumResults[StrongClassifier::eLabel_Correct]; i++ ) { float fDiff = ( fMean[StrongClassifier::eLabel_Correct] - fCorrectResults[i] ); fSumOfDifferences[StrongClassifier::eLabel_Correct] += ( fDiff * fDiff ); } for ( UINT i = 0; i < nNumResults[StrongClassifier::eLabel_Incorrect]; i++ ) { float fDiff = ( fMean[StrongClassifier::eLabel_Incorrect] - fIncorrectResults[i] ); fSumOfDifferences[StrongClassifier::eLabel_Incorrect] += ( fDiff * fDiff ); } FLOAT fSumOfEnergyDifferences = 0.0f; const UINT nNumEnergyResults = (UINT)( fEnergyResults.size() ); for ( UINT i = 0; i < nNumEnergyResults; i++ ) { float fDiff = ( fEnergyMean - fEnergyResults[i] ); fSumOfEnergyDifferences += ( fDiff * fDiff ); } if( fCorrectResults.size() > 0 ) { fStdDev[StrongClassifier::eLabel_Correct] = sqrtf( fSumOfDifferences[StrongClassifier::eLabel_Correct] / (FLOAT)nNumResults[StrongClassifier::eLabel_Correct] ); } if( fIncorrectResults.size() > 0 ) { fStdDev[StrongClassifier::eLabel_Incorrect] = sqrtf( fSumOfDifferences[StrongClassifier::eLabel_Incorrect] / (FLOAT)nNumResults[StrongClassifier::eLabel_Incorrect] ); } if( fEnergyResults.size() > 0 ) { fEnergyStdDev = sqrtf( fSumOfEnergyDifferences / (FLOAT)nNumEnergyResults ); } // output stats printf( "\n\tExamples labeled as correct: %d", fCorrectResults.size() ); if( fCorrectResults.size() > 0 ) { printf( "\n\tMin = %3.5f, Max = %3.5f, Mean = %3.5f, StdDev = %3.5f", fRangeMin[StrongClassifier::eLabel_Correct], fRangeMax[StrongClassifier::eLabel_Correct], fMean[StrongClassifier::eLabel_Correct], fStdDev[StrongClassifier::eLabel_Correct] ); } printf( "\n\tExamples labeled as incorrect: %d", fIncorrectResults.size() ); if( fIncorrectResults.size() > 0 ) { printf( "\n\tMin = %3.5f, Max = %3.5f, Mean = %3.5f, StdDev = %3.5f", fRangeMin[StrongClassifier::eLabel_Incorrect], fRangeMax[StrongClassifier::eLabel_Incorrect], fMean[StrongClassifier::eLabel_Incorrect], fStdDev[StrongClassifier::eLabel_Incorrect] ); } printf( "\n\tEnergy:" ); printf( "\n\tMean = %3.5f, StdDev = %3.5f", fEnergyMean, fEnergyStdDev ); printf( "\n\tExamples not labeled: %d", nNotLabeledExamples ); SetRangeMax( fRangeMax[StrongClassifier::eLabel_Correct], StrongClassifier::eLabel_Correct ); SetRangeMax( fRangeMax[StrongClassifier::eLabel_Incorrect], StrongClassifier::eLabel_Incorrect ); SetRangeMin( fRangeMin[StrongClassifier::eLabel_Correct], StrongClassifier::eLabel_Correct ); SetRangeMin( fRangeMin[StrongClassifier::eLabel_Incorrect], StrongClassifier::eLabel_Incorrect ); SetMean( fMean[StrongClassifier::eLabel_Correct], StrongClassifier::eLabel_Correct ); SetMean( fMean[StrongClassifier::eLabel_Incorrect], StrongClassifier::eLabel_Incorrect ); SetStdDev( fStdDev[StrongClassifier::eLabel_Correct], StrongClassifier::eLabel_Correct ); SetStdDev( fStdDev[StrongClassifier::eLabel_Incorrect], StrongClassifier::eLabel_Incorrect ); SetEnergyMean( fEnergyMean ); SetEnergyStdDev( fEnergyStdDev ); } } //-------------------------------------------------------------------------------------- // Name: GetNumPositiveExamples // Desc: Returns the number of positive labeled examples //-------------------------------------------------------------------------------------- UINT GestureDetectorTrainer::GetNumPositiveExamples() { UINT nNumPositiveExamples = 0; for ( UINT i = 0; i < m_LabeledExamples.m_iLabels.size(); i++ ) { if ( m_LabeledExamples.m_iLabels[ i ] == g_iClassificationLabelCorrect ) { nNumPositiveExamples++; } } return nNumPositiveExamples; } //-------------------------------------------------------------------------------------- // Name: SaveLabeledExamples // Desc: Save the labeled examples to a binary file //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::SaveLabeledExamples( const CHAR* szFileName ) { FILE* pFile = NULL; fopen_s( &pFile, szFileName, "wb" ); if ( !pFile ) { printf( "\nFailed to open %s for saving labeled examples...\n", szFileName ); return E_FAIL; } // Write a text identifier fwrite( getLabeledExampleFileID(), getLabeledExampleFileIDLen(), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); // Write current version number fwrite( &g_fCurrentVersion, sizeof( g_fCurrentVersion ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); fwrite( &m_nTotalNumGestures, sizeof( m_nTotalNumGestures ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); fwrite( &m_nNumTrainingGestures, sizeof( m_nNumTrainingGestures), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); fwrite( &nNumExamples, sizeof( UINT ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); INT8 iLabel; UINT64 uTimeStamp; for ( UINT i = 0; i < nNumExamples; i++ ) { fwrite( m_LabeledExamples.m_pExamples[ i ], sizeof( GESTURE_SKELETON_TYPE ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); iLabel = m_LabeledExamples.m_iLabels[ i ]; fwrite( &iLabel, sizeof( INT8 ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; fwrite( &uTimeStamp, sizeof( UINT64 ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); } fclose( pFile ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: LoadLabeledExamples // Desc: Load labeled examples from binary file //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::LoadLabeledExamples( const CHAR* szFileName ) { FILE* pFile = NULL; fopen_s( &pFile, szFileName, "rb" ); if ( !pFile ) { printf( "\nERROR: Failed to open %s for loading labeled examples...\n", szFileName ); return E_FAIL; } // Reset(); // Check that this is indeed a gesture file CHAR szFileID[ 255 ]; fread( szFileID, getLabeledExampleFileIDLen(), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); // Check the file header if ( !strcmp( szFileID, getLabeledExampleFileID() ) ) { // Read the version number. This is only added for backwards compatibility when we need to change file formats in the future FLOAT fVersion; fread( &fVersion, sizeof( fVersion ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); #ifdef _XBOX fVersion = ByteSwap32Bit( fVersion ); #endif // Check that the file version is the same as the current version if ( fabsf( fVersion - g_fCurrentVersion ) > 1.0f ) { fclose( pFile ); printf( "\nError: File version %f != current version %f\n", fVersion, g_fCurrentVersion ); return E_FAIL; } fread( &m_nTotalNumGestures, sizeof( m_nTotalNumGestures ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); fread( &m_nNumTrainingGestures, sizeof( m_nNumTrainingGestures), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); UINT nNumExamples; fread( &nNumExamples, sizeof( UINT ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); if( nNumExamples > 0 ) { printf("\nLoading %d labeled examples from %s...", nNumExamples, szFileName); for ( UINT i = 0; i < nNumExamples; i++ ) { GESTURE_SKELETON_TYPE* pSkeletonData = (GESTURE_SKELETON_TYPE*)_aligned_malloc( sizeof( GESTURE_SKELETON_TYPE ), 16 ); RETURN_ON_NULL( pSkeletonData ); INT8 iLabel; UINT64 uTimeStamp; fread( pSkeletonData, sizeof( GESTURE_SKELETON_TYPE ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); fread( &iLabel, sizeof( INT8 ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); fread( &uTimeStamp, sizeof( UINT64 ), 1, pFile ); RETURN_ON_FILE_ERROR( pFile ); m_LabeledExamples.m_pExamples.push_back( pSkeletonData ); m_LabeledExamples.m_iLabels.push_back( iLabel ); m_LabeledExamples.m_uTimeStamps.push_back( uTimeStamp ); } } else { printf("\nWARNING: No labeled examples in the file %s.", szFileName); } } else { printf( "\nError: File type %s != required type %s in file %s\n", szFileID, getLabeledExampleFileID(), szFileName ); fclose( pFile ); return E_FAIL; } fclose( pFile ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: TrainWeakClassifiers // Desc: In order to optimize training time and use less memory at training time, we // do the training in two passes. The first pass finds all the interesting // weak classifiers that contribute to each future, and the second pass uses // these weak classifiers to do the final training. More accurate results can // be found when using all the weak classifiers from all features in just one // sinlge pass, but in our experiments memory usage easily went up to more than // 14 GBytes RAM and training times were in the order of hours, versus using only // about 3 GBytes RAM and a few minutes of training. //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::TrainWeakClassifiers() { DebugOutput output; // First pass finds all the interesting weak classifiers that contribute per feature GestureDetectorTrainer trainers[ ClassifierData::NUM_FEATURES ]; for ( UINT i = 0; i < ClassifierData::NUM_FEATURES; i++ ) { printf( "\n\t%d/%d - Feature: %s ", i + 1, ClassifierData::NUM_FEATURES, output.Print( (ClassifierData::EType)i ) ); trainers[ i ].m_nNumWeakClassifiersAtRuntime = m_nNumWeakClassifiersAtRuntime; trainers[ i ].m_bUseSkeleton = m_bUseSkeleton; trainers[ i ].m_bUseOpticalFlow = m_bUseOpticalFlow; trainers[ i ].m_nFramesToSkip = m_nFramesToSkip; RETURN_ON_FAIL( trainers[ i ].TrainWeakClassifiers( (ClassifierData::EType)i, m_LabeledExamples, m_fErrorThreshold ) ); printf( "(%d)", trainers[ i ].GetNumWeakClassifiers() ); } // Now add all these classifiers into one list that can be used in the seconds pass CombineWeakClassifiers( trainers, ClassifierData::NUM_FEATURES ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: TrainWeakClassifiers // Desc: Trains a strong classifier per feature set and returns the weak classifiers // that contributes to that strong classifier //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::TrainWeakClassifiers( ClassifierData::EType classifierDataType, LabeledExamples& labeledExamples, const DOUBLE fErrorThreshold ) { RETURN_ON_FAIL( GenerateWeakClassifiers( classifierDataType ) ); if (m_StrongClassifier.GetNumWeakClassifiers()==0) { return S_FALSE; } // Clear and duplicate the training example data m_LabeledExamples.m_iLabels.clear(); m_LabeledExamples.m_pExamples.clear(); m_LabeledExamples.m_uTimeStamps.clear(); for ( UINT i = 0; i < labeledExamples.m_iLabels.size(); i++ ) { m_LabeledExamples.m_iLabels.push_back( labeledExamples.m_iLabels[ i ] ); } for ( UINT i = 0; i < labeledExamples.m_pExamples.size(); i++ ) { m_LabeledExamples.m_pExamples.push_back( labeledExamples.m_pExamples[ i ] ); } for ( UINT i = 0; i < labeledExamples.m_uTimeStamps.size(); i++ ) { m_LabeledExamples.m_uTimeStamps.push_back( labeledExamples.m_uTimeStamps[ i ] ); } // Run Adaboost only on this feature set m_fErrorThreshold = fErrorThreshold; RETURN_ON_FAIL( TrainStrongClassifier( FALSE ) ); Optimize( 0, FALSE ); m_LabeledExamples.m_iLabels.clear(); m_LabeledExamples.m_pExamples.clear(); m_LabeledExamples.m_uTimeStamps.clear(); return S_OK; } //-------------------------------------------------------------------------------------- // Name: GenerateWeakClassifiers // Desc: Generates a set of weak classifiers as decision stumps, given some parameters // for min, max and the step value between min and max //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::GenerateWeakClassifiers( ClassifierData* pClassifierData, const FLOAT fMin, const FLOAT fMax, const FLOAT fStep, const BOOL bUseReject) { WeakClassifier weakClassifier; UINT uID; UINT uDataIndex[ 2 ] = { 0, 0 }; uDataIndex[ 0 ] = (UINT)m_ClassifierData.size(); // // Use the index in the array as the unique id // uID = (UINT)m_ClassifierData.size(); uID = pClassifierData->MakeUID(); pClassifierData->SetID( uID ); // Add the data m_ClassifierData.push_back( pClassifierData ); ClassifierData* pClassifierDataClone = pClassifierData; if (bUseReject) { // Now add rejection of inferred joints pClassifierDataClone = pClassifierData->Clone(); RETURN_ON_NULL( pClassifierDataClone ); pClassifierDataClone->SetRejectInfferedJoints( TRUE ); uDataIndex[ 1 ] = (UINT)m_ClassifierData.size(); // // Use the index in the array as the unique id // uID = (UINT)m_ClassifierData.size(); uID = pClassifierData->MakeUID(); pClassifierDataClone->SetID( uID ); // Add the cloned data m_ClassifierData.push_back( pClassifierDataClone ); } // For each threshold we generate 4 classifiers. Two for choosing to use or // reject inferred joints, and each one of those are also reversed, so that // the learning algorithm can find the best weak classifier // ClassifierData* pData[ 2 ] = { pClassifierData, pClassifierDataClone }; UINT max = bUseReject ? 2 : 1; for ( UINT i = 0; i < max; i++ ) { for ( FLOAT fThreshold = fMin; fThreshold <= fMax; fThreshold += fStep ) { // Setup the weak classifier weakClassifier.SetThreshold( fThreshold ); // weakClassifier.SetData( pData[ i ] ); weakClassifier.SetDataIndex( uDataIndex[ i ] ); // Add the weak classifiers weakClassifier.SetIsReversed( FALSE ); m_StrongClassifier.Add( weakClassifier ); } } for ( UINT i = 0; i < max; i++ ) { for ( FLOAT fThreshold = fMin; fThreshold <= fMax; fThreshold += fStep ) { // Setup the weak classifier weakClassifier.SetThreshold( fThreshold ); // weakClassifier.SetData( pData[ i ] ); weakClassifier.SetDataIndex( uDataIndex[ i ] ); // Add the weak classifiers weakClassifier.SetIsReversed( TRUE ); m_StrongClassifier.Add( weakClassifier ); } } return S_OK; } //-------------------------------------------------------------------------------------- // Name: GenerateWeakClassifiers // Desc: This is where all the classifiers are generated for each feature //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::GenerateWeakClassifiers( ClassifierData::EType classifierDataType ) { WeakClassifier weakClassifier; ClassifierData* pClassifierData; //a bit of a patch but we need to support both at the same time while building separate classifiers if (!m_bUseSkeleton && classifierDataType < ClassifierData::TYPE_OPTICAL_FLOW_X) { return S_FALSE; } if (!m_bUseOpticalFlow && classifierDataType >= ClassifierData::TYPE_OPTICAL_FLOW_X) { return S_FALSE; } GESTURE_JOINT_INDEX verticalAngleJoints[] = { GESTURE_JOINT_SHOULDER_LEFT, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_SHOULDER_RIGHT, GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_HIP_LEFT, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_HIP_RIGHT, GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_ANKLE_RIGHT }; UINT nNumClassifierData = ARRAYSIZE( verticalAngleJoints ); for ( UINT i = 0; i < nNumClassifierData; i++ ) { #ifdef ADD_TYPE_ANGLE if ( classifierDataType == ClassifierData::TYPE_ANGLE ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngles ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngles( GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_SPINE_SHOULDER, verticalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleMin, fAngleMax, fAngleStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_VELOCITY if ( classifierDataType == ClassifierData::TYPE_ANGLE_VELOCITY ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleVelocities ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleVelocities( GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_SPINE_SHOULDER, verticalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleVelocityMin, fAngleVelocityMax, fAngleVelocityStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_ACCELERATION if ( classifierDataType == ClassifierData::TYPE_ANGLE_ACCELERATION ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleAcceleration ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleAcceleration( GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_SPINE_SHOULDER, verticalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleAccelMin, fAngleAccelMax, fAngleAccelStep ) ); } #endif } GESTURE_JOINT_INDEX leftHorizontalAngleJoints[] = { GESTURE_JOINT_HEAD, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_HIP_LEFT, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_ANKLE_LEFT }; nNumClassifierData = ARRAYSIZE( leftHorizontalAngleJoints ); for ( UINT i = 0; i < nNumClassifierData; i++ ) { #ifdef ADD_TYPE_ANGLE if ( classifierDataType == ClassifierData::TYPE_ANGLE ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngles ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngles( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_LEFT, leftHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleMin, fAngleMax, fAngleStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_VELOCITY if ( classifierDataType == ClassifierData::TYPE_ANGLE_VELOCITY ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleVelocities ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleVelocities( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_LEFT, leftHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleVelocityMin, fAngleVelocityMax, fAngleVelocityStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_ACCELERATION if ( classifierDataType == ClassifierData::TYPE_ANGLE_ACCELERATION ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleAcceleration ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleAcceleration( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_LEFT, leftHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleAccelMin, fAngleAccelMax, fAngleAccelStep ) ); } #endif } GESTURE_JOINT_INDEX rightHorizontalAngleJoints[] = { GESTURE_JOINT_HEAD, GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_HIP_RIGHT, GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_ANKLE_RIGHT }; nNumClassifierData = ARRAYSIZE( rightHorizontalAngleJoints ); for ( UINT i = 0; i < nNumClassifierData; i++ ) { #ifdef ADD_TYPE_ANGLE if ( classifierDataType == ClassifierData::TYPE_ANGLE ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngles ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngles( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_RIGHT, rightHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleMin, fAngleMax, fAngleStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_VELOCITY if ( classifierDataType == ClassifierData::TYPE_ANGLE_VELOCITY ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleVelocities ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleVelocities( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_RIGHT, rightHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleVelocityMin, fAngleVelocityMax, fAngleVelocityStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_ACCELERATION if ( classifierDataType == ClassifierData::TYPE_ANGLE_ACCELERATION ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleAcceleration ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleAcceleration( GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_RIGHT, rightHorizontalAngleJoints[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleAccelMin, fAngleAccelMax, fAngleAccelStep ) ); } #endif } GESTURE_JOINT_INDEX generalAngleJoints[][3] = { { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_SHOULDER_LEFT }, { GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_SHOULDER_RIGHT }, { GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_HIP_LEFT }, { GESTURE_JOINT_ANKLE_RIGHT, GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_HIP_RIGHT }, { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_WRIST_RIGHT }, { GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_KNEE_RIGHT }, { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_KNEE_LEFT }, { GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_KNEE_RIGHT }, { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_KNEE_RIGHT }, { GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_SPINE_MID, GESTURE_JOINT_KNEE_LEFT }, }; nNumClassifierData = ARRAYSIZE( generalAngleJoints ); for ( UINT i = 0; i < nNumClassifierData; i++ ) { #ifdef ADD_TYPE_ANGLE if ( classifierDataType == ClassifierData::TYPE_ANGLE ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngles ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngles( generalAngleJoints[i][0], generalAngleJoints[i][1], generalAngleJoints[i][2] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleMin, fAngleMax, fAngleStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_VELOCITY if ( classifierDataType == ClassifierData::TYPE_ANGLE_VELOCITY ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleVelocities ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleVelocities( generalAngleJoints[i][0], generalAngleJoints[i][1], generalAngleJoints[i][2] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleVelocityMin, fAngleVelocityMax, fAngleVelocityStep ) ); } #endif #ifdef ADD_TYPE_ANGLE_ACCELERATION if ( classifierDataType == ClassifierData::TYPE_ANGLE_ACCELERATION ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingAngleAcceleration ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingAngleAcceleration( generalAngleJoints[i][0], generalAngleJoints[i][1], generalAngleJoints[i][2] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAngleAccelMin, fAngleAccelMax, fAngleAccelStep ) ); } #endif } #ifdef ADD_TYPE_TIME_SPACE_ANGLE if ( classifierDataType == ClassifierData::TYPE_TIME_SPACE_ANGLE ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingTimeSpaceAngles ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingTimeSpaceAngles( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fTimeSpaceAngleMin, fTimeSpaceAngleMax, fTimeSpaceAngleStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_POWER if ( classifierDataType == ClassifierData::TYPE_MUSCLE_POWER ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMusclePower ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMusclePower( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMusclePowerMin, fMusclePowerMax, fMusclePowerStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_FORCES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_FORCE_X ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleForceX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleForceX( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleForceMin, fMuscleForceMax, fMuscleForceStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_FORCES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_FORCE_Y ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleForceY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleForceY( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleForceMin, fMuscleForceMax, fMuscleForceStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_FORCES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_FORCE_Z ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleForceZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleForceZ( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleForceMin, fMuscleForceMax, fMuscleForceStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_TORQUES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_TORQUE_X ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleTorqueX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleTorqueX( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleTorqueMin, fMuscleTorqueMax, fMuscleTorqueStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_TORQUES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_TORQUE_Y ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleTorqueY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleTorqueY( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleTorqueMin, fMuscleTorqueMax, fMuscleTorqueStep ) ); } } #endif #ifdef ADD_TYPE_MUSCLE_TORQUES if ( classifierDataType == ClassifierData::TYPE_MUSCLE_TORQUE_Z ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingMuscleTorqueZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingMuscleTorqueZ( (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fMuscleTorqueMin, fMuscleTorqueMax, fMuscleTorqueStep ) ); } } #endif GESTURE_JOINT_INDEX positionJoints[] = { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_ANKLE_RIGHT }; nNumClassifierData = ARRAYSIZE( positionJoints ); #ifdef ADD_TYPE_DIFF_POSITION_X if ( classifierDataType == ClassifierData::TYPE_DIFF_POSITION_X ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffPositionX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffPositionX( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fPositionMin, fPositionMax, fPositionStep ) ); } } } } #endif #ifdef ADD_TYPE_DIFF_POSITION_Y if ( classifierDataType == ClassifierData::TYPE_DIFF_POSITION_Y ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffPositionY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffPositionY( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fPositionMin, fPositionMax, fPositionStep ) ); } } } } #endif #ifdef ADD_TYPE_DIFF_POSITION_Z if ( classifierDataType == ClassifierData::TYPE_DIFF_POSITION_Z ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffPositionZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffPositionZ( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fPositionMin, fPositionMax, fPositionStep ) ); } } } } #endif #ifdef ADD_TYPE_DIFF_MUSCLE_FORCE_X if ( classifierDataType == ClassifierData::TYPE_DIFF_MUSCLE_FORCE_X ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffMuscleForceX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffMuscleForceX( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fDiffMuscleForceMin, fDiffMuscleForceMax, fDiffMuscleForceStep ) ); } } } } #endif #ifdef ADD_TYPE_DIFF_MUSCLE_FORCE_Y if ( classifierDataType == ClassifierData::TYPE_DIFF_MUSCLE_FORCE_Y ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffMuscleForceY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffMuscleForceY( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fDiffMuscleForceMin, fDiffMuscleForceMax, fDiffMuscleForceStep ) ); } } } } #endif #ifdef ADD_TYPE_DIFF_MUSCLE_FORCE_Z if ( classifierDataType == ClassifierData::TYPE_DIFF_MUSCLE_FORCE_Z ) { for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { for ( UINT j = 0; j < nNumClassifierData; j++ ) { if ( i != (UINT)positionJoints[ j ] ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingDiffMuscleForceZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingDiffMuscleForceZ( positionJoints[ j ], (GESTURE_JOINT_INDEX)i ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fDiffMuscleForceMin, fDiffMuscleForceMax, fDiffMuscleForceStep ) ); } } } } #endif GESTURE_JOINT_INDEX jointVelocities[] = { GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_LEFT, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_SHOULDER_RIGHT, GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_ANKLE_RIGHT }; nNumClassifierData = ARRAYSIZE( jointVelocities ); #ifdef ADD_TYPE_POSITION_SPEED if ( classifierDataType == ClassifierData::TYPE_POSITION_SPEED ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionSpeed ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionSpeed( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fSpeedMin, fSpeedMax, fSpeedStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_SPEED_SQ if ( classifierDataType == ClassifierData::TYPE_POSITION_SPEED_SQ ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionSpeedSQ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionSpeedSQ( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fSpeedSQMin, fSpeedSQMax, fSpeedSQStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_ACCELERATION if ( classifierDataType == ClassifierData::TYPE_POSITION_ACCELERATION ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionAcceleration ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionAcceleration( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAccelMin, fAccelMax, fAccelStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_ACCELERATION_X if ( classifierDataType == ClassifierData::TYPE_POSITION_ACCELERATION_X ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionAccelerationX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionAccelerationX( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAccelMin, fAccelMax, fAccelStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_ACCELERATION_Y if ( classifierDataType == ClassifierData::TYPE_POSITION_ACCELERATION_Y ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionAccelerationY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionAccelerationY( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAccelMin, fAccelMax, fAccelStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_ACCELERATION_Z if ( classifierDataType == ClassifierData::TYPE_POSITION_ACCELERATION_Z ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionAccelerationZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionAccelerationZ( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fAccelMin, fAccelMax, fAccelStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITY_X if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITY_X ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocityX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocityX( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocityMin, fVelocityMax, fVelocityStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITY_Y if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITY_Y ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocityY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocityY( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocityMin, fVelocityMax, fVelocityStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITY_Z if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITY_Z ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocityZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocityZ( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocityMin, fVelocityMax, fVelocityStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITYSQ_X if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITYSQ_X ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocitySQX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocitySQX( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocitySQMin, fVelocitySQMax, fVelocitySQStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITYSQ_Y if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITYSQ_Y ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocitySQY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocitySQY( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocitySQMin, fVelocitySQMax, fVelocitySQStep ) ); } } #endif #ifdef ADD_TYPE_POSITION_VELOCITYSQ_Z if ( classifierDataType == ClassifierData::TYPE_POSITION_VELOCITYSQ_Z ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingPositionVelocitySQZ ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingPositionVelocitySQZ( jointVelocities[ i ] ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fVelocitySQMin, fVelocitySQMax, fVelocitySQStep ) ); } } #endif struct Bone { GESTURE_JOINT_INDEX parent; GESTURE_JOINT_INDEX child; }; Bone bones[] = { { GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_LEFT }, { GESTURE_JOINT_SHOULDER_LEFT, GESTURE_JOINT_ELBOW_LEFT }, { GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_WRIST_LEFT }, { GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_HAND_LEFT }, { GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SHOULDER_RIGHT }, { GESTURE_JOINT_SHOULDER_RIGHT, GESTURE_JOINT_ELBOW_RIGHT }, { GESTURE_JOINT_ELBOW_RIGHT, GESTURE_JOINT_WRIST_RIGHT }, { GESTURE_JOINT_WRIST_RIGHT, GESTURE_JOINT_HAND_RIGHT }, { GESTURE_JOINT_SPINE_BASE, GESTURE_JOINT_HIP_LEFT }, { GESTURE_JOINT_HIP_LEFT, GESTURE_JOINT_KNEE_LEFT }, { GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_ANKLE_LEFT }, { GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_FOOT_LEFT }, { GESTURE_JOINT_SPINE_BASE, GESTURE_JOINT_HIP_RIGHT }, { GESTURE_JOINT_SPINE_BASE, GESTURE_JOINT_KNEE_RIGHT }, { GESTURE_JOINT_KNEE_RIGHT, GESTURE_JOINT_ANKLE_RIGHT }, { GESTURE_JOINT_ANKLE_RIGHT, GESTURE_JOINT_FOOT_RIGHT }, { GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_SPINE_MID }, { GESTURE_JOINT_SPINE_BASE, GESTURE_JOINT_SPINE_MID }, { GESTURE_JOINT_SPINE_SHOULDER, GESTURE_JOINT_HEAD }, { GESTURE_JOINT_HEAD, GESTURE_JOINT_KNEE_LEFT }, { GESTURE_JOINT_HEAD, GESTURE_JOINT_KNEE_RIGHT }, { GESTURE_JOINT_HAND_LEFT, GESTURE_JOINT_HAND_RIGHT }, { GESTURE_JOINT_HAND_LEFT, GESTURE_JOINT_SHOULDER_RIGHT }, { GESTURE_JOINT_HAND_RIGHT, GESTURE_JOINT_SHOULDER_LEFT } }; nNumClassifierData = ARRAYSIZE( bones ); #ifdef ADD_TYPE_BONE_LENGTH_CHANGES if ( classifierDataType == ClassifierData::TYPE_BONE_LENGTH_CHANGES ) { for ( UINT i = 0; i < nNumClassifierData; i++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingBoneLengthChanges ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingBoneLengthChanges( bones[ i ].parent, bones[ i ].child ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fBoneChangesMin, fBoneChangesMax, fBoneChangesStep ) ); } } #endif #ifdef ADD_TYPE_OPTICAL_FLOW if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_X ) { for ( UINT ij = 0; ij < 3*3; ij++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowX ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowX( ij ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowMin, fOpticalFlowMax, fOpticalFlowStep, FALSE ) ); } } if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_Y ) { for ( UINT ij = 0; ij < 3*3; ij++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowY ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowY( ij ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowMin, fOpticalFlowMax, fOpticalFlowStep, FALSE ) ); } } if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_LENGTH_SQ ) { for ( UINT i = 0; i < 3; i++ ) { for ( UINT j = 0; j < 3; j++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowLengthSq ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowLengthSq( i, j ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowLenSQMin, fOpticalFlowLenSQMax, fOpticalFlowLenSQStep, FALSE ) ); } } } if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_TANGENT ) { for ( UINT i = 0; i < 3; i++ ) { for ( UINT j = 0; j < 3; j++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowTangent ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowTangent( i, j ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowTanMin, fOpticalFlowTanMax, fOpticalFlowTanStep, FALSE ) ); } } } if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_X_DIFF ) { for ( UINT a = 0; a < 3*3-1; a++ ) { for ( UINT b = a; b < 3*3; b++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowXDiff ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowXDiff( a, b ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowDiffMin, fOpticalFlowDiffMax, fOpticalFlowDiffStep, FALSE ) ); } } } if ( classifierDataType == ClassifierData::TYPE_OPTICAL_FLOW_Y_DIFF ) { for ( UINT a = 0; a < 3*3-1; a++ ) { for ( UINT b = a; b < 3*3; b++ ) { void* pMem; RETURN_ON_NULL( pMem = AllocateAligned( sizeof( ClassifierDataUsingOpticalFlowYDiff ), 4 ) ); pClassifierData = new (pMem) ClassifierDataUsingOpticalFlowYDiff( a, b ); RETURN_ON_FAIL( GenerateWeakClassifiers( pClassifierData, fOpticalFlowDiffMin, fOpticalFlowDiffMax, fOpticalFlowDiffStep, FALSE ) ); } } } #endif return S_OK; } //-------------------------------------------------------------------------------------- // Name: CombineWeakClassifiers // Desc: Combine all weak classifiers in the first pass so that it can be used in the // seconds pass of training for the final strong classifier //-------------------------------------------------------------------------------------- VOID GestureDetectorTrainer::CombineWeakClassifiers( GestureDetectorTrainer* pGestureDetectorTrainers, const UINT nNumTrainers ) { WeakClassifier weakClassifier; for ( UINT i = 0; i < nNumTrainers; i++ ) { GestureDetectorTrainer* pGestureDetectorTrainer = &pGestureDetectorTrainers[ i ]; for ( UINT j = 0; j < pGestureDetectorTrainer->GetNumWeakClassifiers(); j++ ) { WeakClassifier* pWeakClassifier = pGestureDetectorTrainer->m_StrongClassifier.GetWeakClassifierAt( j ); // Add the weak classifier weakClassifier.SetThreshold( pWeakClassifier->GetThreshold() ); // weakClassifier.SetData( pWeakClassifier->GetData() ); weakClassifier.SetDataIndex( pWeakClassifier->GetDataIndex() ); weakClassifier.SetIsReversed( pWeakClassifier->GetIsReversed() ); weakClassifier.SetAlpha( 0.0f ); m_StrongClassifier.Add( weakClassifier ); // pWeakClassifier->SetData( NULL ); pWeakClassifier->SetDataIndex( 0 ); } } } //-------------------------------------------------------------------------------------- // Name: TrainStrongClassifier // Desc: Implementation of the AdaBoost learning algorithm //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::TrainStrongClassifier( const BOOL bFinalPass ) { if ( bFinalPass ) { printf( "\n\t-Evaluating classifier data for each example skeleton frame..." ); } // Time how long this takes DWORD dwStart = GetTickCount(); // Clear all the data ClassifierData::Initialize(); m_StrongClassifier.Reset( 0 ); m_uPreviousTimeStamp = 0; XMVECTOR vUpVector = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f ); // Cache values from weak classifier data on ground truth so that // we don't have to call Update() NxN times, but only N times during training const UINT nNumExamples = (UINT)( m_LabeledExamples.m_pExamples.size() ); const UINT nNumClassifierData = (UINT)( m_ClassifierData.size() ); const UINT nNumWeakClassifiers = m_StrongClassifier.GetNumWeakClassifiers(); // Number of weak classifiers vector> cachedClassifierDataValues; // We're caching lots of values to optimize training time, so we better make sure // there aren't any bad memory allocations from STL try { cachedClassifierDataValues.resize( nNumClassifierData ); for ( UINT i = 0; i < nNumClassifierData; i++ ) { cachedClassifierDataValues[ i ].resize( nNumExamples ); } } catch ( bad_alloc& badAllocation ) { printf( "\n\nCould not allocate memory (%s)", badAllocation.what() ); return E_FAIL; } if ( !bFinalPass ) { printf("."); } // Do the work that would normally have been done millions of times in the inner loop of the training // and cache the results of the Update() with regards to each classifier data instance //#define FRAMES_TO_SKIP_AFTER_RESET (5) int nFramesToSkip = m_nFramesToSkip;//FRAMES_TO_SKIP_AFTER_RESET; int nFramesSkipped = 0; for ( UINT i = 0; i < nNumExamples; i++ ) { GESTURE_SKELETON_TYPE* pSkeletonData = m_LabeledExamples.m_pExamples[ i ]; UINT64 uTimeStamp = m_LabeledExamples.m_uTimeStamps[ i ]; LARGE_INTEGER liTimeStamp; liTimeStamp.QuadPart = uTimeStamp; if (Update( 0, pSkeletonData, liTimeStamp, vUpVector )) { nFramesToSkip = m_nFramesToSkip;//FRAMES_TO_SKIP_AFTER_RESET; } if (nFramesToSkip > 0) { m_LabeledExamples.m_iLabels[ i ] = 0; nFramesSkipped++; nFramesToSkip--; } // Let openMP run this loop on multiple threads #pragma omp parallel for for ( INT j = 0; j < (INT)nNumClassifierData; j++ ) { cachedClassifierDataValues[ j ][ i ] = m_ClassifierData[ j ]->GetValue( 0 ); } } if ( !bFinalPass ) { // printf("%d frames skipped", nFramesSkipped); printf("."); } // We're caching lots of values to optimize training time, so we better make sure // there aren't any bad memory allocations from STL vector> cachedClassificationsError; try { cachedClassificationsError.resize( nNumWeakClassifiers ); for ( UINT i = 0; i < nNumWeakClassifiers; i++ ) { cachedClassificationsError[ i ].resize( nNumExamples ); } } catch ( bad_alloc& badAllocation ) { printf( "\n\nCould not allocate memory (%s)", badAllocation.what() ); return E_FAIL; } vector cachedClassificationErrorSum; try { cachedClassificationErrorSum.resize( nNumWeakClassifiers ); } catch ( bad_alloc& badAllocation ) { printf( "\n\nCould not allocate memory (%s)", badAllocation.what() ); return E_FAIL; } if ( !bFinalPass ) { printf("."); } // Classify() will get called millions of times in the inner loop of training on already known data. // We therefore iIterate through all weak classifiers and cache the results of Classify() for each // weak classifier with regards to each example data #pragma omp parallel for for ( INT i = 0; i < (INT)nNumWeakClassifiers; i++ ) { UINT uSum = 0; WeakClassifier* pWeakClassifier = m_StrongClassifier.GetWeakClassifierAt( i ); // Iterate through all ground truth examples for ( UINT n = 0; n < nNumExamples; n++ ) { INT8 iGroundTruthLabel = m_LabeledExamples.m_iLabels[ n ]; if (iGroundTruthLabel==0) {//ignore this frame cachedClassificationsError[ i ][ n ] = 0; continue; } // UINT uClassifierDataIndex = pWeakClassifier->GetData()->GetID(); UINT uClassifierDataIndex = pWeakClassifier->GetDataIndex(); FLOAT fValue = cachedClassifierDataValues[ uClassifierDataIndex ][ n ]; INT8 iWeakClassifierResult = pWeakClassifier->Classify( fValue ); // if the weak classifier wrongly classifies this example, then contribute to the error cachedClassificationsError[ i ][ n ] = ( iWeakClassifierResult == iGroundTruthLabel ) ? 0 : 1; // For optimization, get the sum of the error uSum += cachedClassificationsError[ i ][ n ]; } cachedClassificationErrorSum[ i ] = uSum; } DWORD dwStop = GetTickCount(); if ( bFinalPass ) { printf( "Done" ); } else { printf("."); } dwStart = GetTickCount(); // User specified 0, which means use all weak classifiers at runtime, but only the ones that pass the error threshold const BOOL bUseAllWeakClassifiers = ( m_nNumWeakClassifiersAtRuntime == 0 ); // Number of examples in training set. N is normally used in literature. const UINT N = nNumExamples; // Number of iterations for training loop. T is normally used in literature. If it is an intermediate pass for one of the // feature sets, we simply use all the generated weak classifiers and stop when one of them reaches the error threshold. But, // if this is for the final pass where the final strong classifier is trained, we use the number of runtime classifiers specified by the user // const UINT T = bFinalPass ? ( bUseAllWeakClassifiers ? nNumWeakClassifiers : m_nNumWeakClassifiersAtRuntime ) : nNumWeakClassifiers; const UINT T = min( nNumWeakClassifiers, m_nNumWeakClassifiersAtRuntime ); // The highest error threshold is 0.5 since a weak classifier has to have a better than 50/50 change to be correct, anything higher means // that the weak classifier is simply too weak to contribute to solving the problem space DOUBLE skipRatio = (DOUBLE)(nNumExamples-nFramesSkipped) / (DOUBLE)nNumExamples; const DOUBLE fErrorThreshold = (bFinalPass ? 0.5 : m_fErrorThreshold); vector d( N, (1.0 / N) * skipRatio ); // Distributed weight for each example vector bClassifierChoosen( nNumWeakClassifiers, FALSE ); if ( bFinalPass ) { printf( "\n\t-Running AdaBoost using %d (of %d available) hardware threads...", m_nNumThreadsForTraining, m_nMaxNumThreads ); } // Optimization. Since we're auto generating the classifiers with a min->max range and step, there are most // likely lots of overlapping. If we do find that the cached results for classifiers next to each other in // the range are exactly the sample for all examples, the we simply remove one of them. Theoretically speaking // the removed classifier still has a chance to be boosted by the algorithm, but emperical results show that // we can safely remove these from the pool of classifiers. if ( !bFinalPass ) { for ( INT i = 1; i < (INT)nNumWeakClassifiers; i++ ) { // if the sum isn't the same, then no need to look through each then no need to compare each example if ( cachedClassificationErrorSum[ i ] != cachedClassificationErrorSum[ i - 1 ] ) { continue; } // if the sum is the same, then check the classification error on each example for both classifiers BOOL bClassifiersTheSame = TRUE; for ( INT n = 0; n < (INT)nNumExamples; n++ ) { if ( cachedClassificationsError[ i ][ n ] != cachedClassificationsError[ i - 1 ][ n ] ) { bClassifiersTheSame = FALSE; break; } } // if two neighbor classifiers give the same results, the remove it from the pool of classifiers that // AdaBoost will work with. if ( bClassifiersTheSame ) { bClassifierChoosen[ i ] = TRUE; } } } if ( !bFinalPass ) { printf("."); } // AdaBoost algorithm, iterate T times for ( UINT t = 0; t < T; t++ ) { // Find the best weak classifier, i.e. the weak classifier with the smallest error UINT uBestWeakClassifierIndex = 0; DOUBLE fMinError = FLT_MAX; // Let openMP run this loop on multiple threads #pragma omp parallel for // Iterate through all weak classifiers for ( INT i = 0; i < (INT)nNumWeakClassifiers; i++ ) { // We could remove a weak classifier from the list and add it to another list, but with // so many potential classifers, it's more optimal to just mark it when it's used if ( !bClassifierChoosen[ i ] ) { DOUBLE fError = 0.0; // Iterate through all ground truth examples for ( UINT n = 0; n < N; n++ ) { fError += d[ n ] * (DOUBLE)(cachedClassificationsError[ i ][ n ]); } // Let openMP know that this if() operation cannot be parallelized #pragma omp critical if ( fError < fMinError ) { fMinError = fError; uBestWeakClassifierIndex = i; } } } // A weak classifier's error has to be less than 0.5 to contribute successfully, since a weak classifier // has to to be better than 50/50 chance of doing a correct classification if ( fMinError / skipRatio >= fErrorThreshold ) { break; } bClassifierChoosen[ uBestWeakClassifierIndex ] = TRUE; WeakClassifier* pBestWeakClassifier = m_StrongClassifier.GetWeakClassifierAt( uBestWeakClassifierIndex ); // Get the confidence of the weak classifier as alpha (lower error => higher alpha) DOUBLE fAlpha = ( fMinError == 0.0f ) ? 0.0f : ( 0.5 * log( ( 1.0 - fMinError ) / fMinError ) ); pBestWeakClassifier->SetAlpha( (FLOAT)fAlpha ); // Emphasize the training examples that do not agree with the weak classifier h(x) DOUBLE Z = 0.0; #pragma omp parallel for for ( INT n = 0; n < (INT)N; n++ ) { INT8 iGroundTruthLabel = m_LabeledExamples.m_iLabels[ n ]; // UINT uClassifierDataIndex = pBestWeakClassifier->GetData()->GetID(); UINT uClassifierDataIndex = pBestWeakClassifier->GetDataIndex(); FLOAT fValue = cachedClassifierDataValues[ uClassifierDataIndex ][ n ]; d[ n ] *= exp( -fAlpha * iGroundTruthLabel * pBestWeakClassifier->Classify( fValue ) ); } // We need to normalize the distribution, but we want to parallelize it, so first get the sum for ( INT n = 0; n < (INT)N; n++ ) { Z += d[ n ]; } // Normalize to a probability distribution and use openMP to do this on multiple threads #pragma omp parallel for for ( INT n = 0; n < (INT)N; n++ ) { d[ n ] /= Z; } } dwStop = GetTickCount(); if ( bFinalPass ) { printf( "Done" ); } else { printf("."); } return S_OK; } //-------------------------------------------------------------------------------------- // Name: ByteSwapSkeletonData // Desc: Byteswamp skeleton data so that it can be used on PC //-------------------------------------------------------------------------------------- //#ifdef TARGET_X360 // VOID GestureDetectorTrainer::ByteSwapSkeletonDataRead( GESTURE_SKELETON_TYPE* pSkeletonData ) // { // pSkeletonData->m_trackingState = ByteSwap32BitRead( GESTURE_GET_TRACKING(pSkeletonData) ); // pSkeletonData->m_trackingID = ByteSwap32BitRead( GESTURE_GET_TRACKING_ID(pSkeletonData) ); // //pSkeletonData->dwEnrollmentIndex = ByteSwap32BitRead( pSkeletonData->dwEnrollmentIndex ); // //pSkeletonData->dwUserIndex = ByteSwap32BitRead( pSkeletonData->dwUserIndex ); // // for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) // { // pSkeletonData->m_jointPositions[ i ].m128_f32[ 0 ] = ByteSwap32BitRead( pSkeletonData->m_jointPositions[ i ].m128_f32[ 0 ] ); // pSkeletonData->m_jointPositions[ i ].m128_f32[ 1 ] = ByteSwap32BitRead( pSkeletonData->m_jointPositions[ i ].m128_f32[ 1 ] ); // pSkeletonData->m_jointPositions[ i ].m128_f32[ 2 ] = ByteSwap32BitRead( pSkeletonData->m_jointPositions[ i ].m128_f32[ 2 ] ); // pSkeletonData->m_jointPositions[ i ].m128_f32[ 3 ] = ByteSwap32BitRead( pSkeletonData->m_jointPositions[ i ].m128_f32[ 3 ] ); // } // // for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) // { // pSkeletonData->m_trackingState[ i ] = ByteSwap32BitRead( pSkeletonData->m_trackingState[ i ] ); // } // // pSkeletonData->dwQualityFlags = ByteSwap32BitRead( pSkeletonData->dwQualityFlags ); // } //#endif //-------------------------------------------------------------------------------------- // Name: SwapPositions // Desc: Used with MirroSkeletonData(), this simply swaps two vectors //-------------------------------------------------------------------------------------- static inline VOID SwapPositions( XMVECTOR& vPosition0, XMVECTOR& vPosition1 ) { XMVECTOR vTemp = vPosition0; vPosition0 = vPosition1; vPosition1 = vTemp; } //-------------------------------------------------------------------------------------- // Name: SwapTrackingStatus // Desc: Used with MirroSkeletonData(), this simply swaps two states //-------------------------------------------------------------------------------------- static inline VOID SwapTrackingStatus( GESTURE_SKELETON_TYPE* pSkeletonData, const GESTURE_JOINT_INDEX left, const GESTURE_JOINT_INDEX right ) { GESTURE_JOINT_TRACKING_STATE temp = GESTURE_GET_JOINT_TRACKING( pSkeletonData, left ); GESTURE_GET_JOINT_TRACKING( pSkeletonData, left ) = GESTURE_GET_JOINT_TRACKING( pSkeletonData, right ); GESTURE_GET_JOINT_TRACKING( pSkeletonData, right ) = temp; } //-------------------------------------------------------------------------------------- // Name: SwapJointData // Desc: Used with MirroSkeletonData(), this swaps data from two joints //-------------------------------------------------------------------------------------- VOID SwapJointData( GESTURE_SKELETON_TYPE* pSkeletonData, const GESTURE_JOINT_INDEX left, const GESTURE_JOINT_INDEX right ) { SwapPositions( GESTURE_GET_JOINT_POS( pSkeletonData, left ), GESTURE_GET_JOINT_POS( pSkeletonData, right ) ); SwapTrackingStatus( pSkeletonData, left, right ); } //-------------------------------------------------------------------------------------- // Name: MirrorSkeletonData // Desc: Mirror the skeleton data //-------------------------------------------------------------------------------------- VOID GestureDetectorTrainer::MirrorSkeletonData( GESTURE_SKELETON_TYPE* pSkeletonData ) { SwapJointData( pSkeletonData, GESTURE_JOINT_SHOULDER_LEFT, GESTURE_JOINT_SHOULDER_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_ELBOW_LEFT, GESTURE_JOINT_ELBOW_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_WRIST_LEFT, GESTURE_JOINT_WRIST_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_HAND_LEFT, GESTURE_JOINT_HAND_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_HIP_LEFT, GESTURE_JOINT_HIP_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_KNEE_LEFT, GESTURE_JOINT_KNEE_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_ANKLE_LEFT, GESTURE_JOINT_ANKLE_RIGHT ); SwapJointData( pSkeletonData, GESTURE_JOINT_FOOT_LEFT, GESTURE_JOINT_FOOT_RIGHT ); const XMVECTOR vMirror = XMVectorSet( -1.0f, 1.0f, 1.0f, 1.0f ); for ( UINT i = 0; i < GESTURE_JOINT_COUNT; i++ ) { GESTURE_GET_JOINT_POS( pSkeletonData, i ) *= vMirror; } #ifdef TARGET_X360 // durango target uses a hack here - dont want to swap the hips position twice! GESTURE_GET_POS( pSkeletonData ) *= vMirror; #endif } //-------------------------------------------------------------------------------------- // Name: GenerateLabeledExamples // Desc: Used tagged xed files to generate labeled training/testing examples as ground truth //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::GenerateLabeledExamples( const CHAR* szGestureName, CHAR* const* szExampleFiles, const UINT uiExampleFileCount, const CHAR* szDestPath, const BOOL bUseRawSkeletonData, const INT iSkeletonIndexToProcess, INT8 iLabels[], CHAR* const* szOutputFiles ) { Reset(); for ( UINT i = 0; i < uiExampleFileCount; i++ ) { if( iLabels == NULL ) { RETURN_ON_FAIL( GenerateLabeledExamplesFromRawSkeleton( szGestureName, szExampleFiles[ i ], iSkeletonIndexToProcess ) ); } else { RETURN_ON_FAIL( GenerateLabeledExamplesFromRawSkeleton( szGestureName, szExampleFiles[ i ], iSkeletonIndexToProcess, iLabels[ i ] ) ); } if( szOutputFiles ) { RETURN_ON_FAIL( SaveLabeledExamples( szOutputFiles[ i ] ) ); // Since we're saving to separate files we must reset between them Reset(); } } if( !szOutputFiles ) { RETURN_ON_FAIL( SaveLabeledExamples( szDestPath ) ); } return S_OK; } //-------------------------------------------------------------------------------------- // Name: GenerateLabeledExamplesFromRawSkeleton // Desc: Generate labeled examples using raw skeleton data in the xed files //-------------------------------------------------------------------------------------- HRESULT GestureDetectorTrainer::GenerateLabeledExamplesFromRawSkeleton( const CHAR* szGestureName, const CHAR* szExamplesFileName, INT iSkeletonIndexToProcess, INT8 iExampleLabel ) { wchar_t wideFilename[MAX_PATH]; memset( wideFilename, 0, MAX_PATH*sizeof(wchar_t) ); MultiByteToWideChar( CP_UTF8, 0, szExamplesFileName, strlen(szExamplesFileName), wideFilename, MAX_PATH ); IExample* ex = FExample::Create(wideFilename); Error::EExamplesError err = ex->Load(wideFilename); if( err != Error::Success ) { printf( "\n\nError: Unable to read %s. %i\n\n", szExamplesFileName, (int)err ); delete ex; return E_FAIL; } // TODO: only read and unpack skeleton frames err = ex->ReadAllFrames(); if( err != Error::Success ) { printf( "\n\nError: Unable to load frames from %s. %i\n\n", szExamplesFileName, (int)err ); delete ex; return E_FAIL; } err = ex->UnpackAllFrames(); if( err != Error::Success ) { printf( "\n\nError: Unable to unpack frames in %s. %i\n\n", szExamplesFileName, (int)err ); delete ex; return E_FAIL; } // We potentially open multiple files from multiple folders and we want to add all the labeled examples into one list const UINT uOffsetIndex = (UINT)m_LabeledExamples.m_pExamples.size(); printf( "\n %s\n\t-Reading skeleton frames from .e file and labeling...\n", szExamplesFileName ); BOOL bToDoSkeleton[GESTURE_SKELETON_COUNT]; memset( &bToDoSkeleton, 0, sizeof( BOOL ) * GESTURE_SKELETON_COUNT ); INT iCurrentSkeleton = iSkeletonIndexToProcess; BOOL bScanningSkeletons = ( iSkeletonIndexToProcess == -1 ); while ( iCurrentSkeleton < (INT)GESTURE_SKELETON_COUNT && ( iCurrentSkeleton == -1 || bToDoSkeleton[iCurrentSkeleton]) ) { BOOL bTrackedSkeleton = FALSE; BOOL bReset = FALSE; for( UINT64 frameIndex = 0; frameIndex < ex->GetFrameCount(); ++frameIndex ) { Examples::Frame frame; if(!ex->FetchFrame(frameIndex, frame)) { printf( "\n\nError: Unable to read %s. %i\n\n", szExamplesFileName, (int)err ); delete ex; return E_FAIL; } GESTURE_FRAME_TYPE& skeletonFrame = frame.m_skeletonFrame; INT8 iLabel = g_iClassificationLabelIncorrect; // If there's a valid label passed for this entire file just use it and continue if( iExampleLabel != 0 && (iExampleLabel == g_iClassificationLabelIncorrect || iExampleLabel == g_iClassificationLabelCorrect) ) { iLabel = iExampleLabel; m_nTotalNumGestures++; m_nNumTrainingGestures++; } //find which skeletons are active ever in the whole timeline if ( bScanningSkeletons ) { for ( INT iSkeletonIndex = 0; iSkeletonIndex < GESTURE_SKELETON_COUNT; iSkeletonIndex++ ) { GESTURE_SKELETON_TYPE& skeletonData = GESTURE_GET_SKEL( skeletonFrame, iSkeletonIndex ); // If not tracked, then ignore if ( GESTURE_GET_TRACKING( &skeletonData ) != GESTURE_SKELETON_NOT_TRACKED ) { bToDoSkeleton[iSkeletonIndex] = TRUE; if ( iCurrentSkeleton == -1 ) { iCurrentSkeleton = iSkeletonIndex; } continue; } } } //not a real loop, just to simplify the refactoring ;) if (iCurrentSkeleton >= 0) for( INT iSkeletonIndex = iCurrentSkeleton; iSkeletonIndex<=iCurrentSkeleton; iSkeletonIndex++) { GESTURE_SKELETON_TYPE& skeletonData = GESTURE_GET_SKEL( skeletonFrame, iSkeletonIndex ); if ( GESTURE_GET_TRACKING( &skeletonData ) != GESTURE_SKELETON_TRACKED ) { bTrackedSkeleton = FALSE; continue; } if( !bTrackedSkeleton ) { bTrackedSkeleton = TRUE; bReset = TRUE; } // Apply tilt correction on the data ApplyTiltCorrection( iSkeletonIndex, &GESTURE_GET_SKEL( skeletonFrame, iSkeletonIndex ), &GESTURE_GET_SKEL( skeletonFrame, iSkeletonIndex ), GESTURE_GET_NORMAL( skeletonFrame ), bReset ); // Add the skeleton data as a training example with the NUI_SKELETON_FRAME timestamp GESTURE_SKELETON_TYPE* pSkeletonData = (GESTURE_SKELETON_TYPE*)AllocateAligned( sizeof( GESTURE_SKELETON_TYPE ), 16 ); memcpy( pSkeletonData, &GESTURE_GET_SKEL( skeletonFrame, iSkeletonIndex ), sizeof( GESTURE_SKELETON_TYPE ) ); m_LabeledExamples.m_pExamples.push_back( pSkeletonData ); m_LabeledExamples.m_iLabels.push_back( iLabel ); m_LabeledExamples.m_uTimeStamps.push_back( frame.m_timestamp ); } //printf("%.1f%%... \r", ((float)i/(float)nNumDepthEvents)*100.0f ); } if ( iCurrentSkeleton < 0 ) break; bScanningSkeletons = false; bToDoSkeleton[iCurrentSkeleton] = FALSE; iCurrentSkeleton++; for ( INT iSkeletonIndex = iCurrentSkeleton; iSkeletonIndex < GESTURE_SKELETON_COUNT; iSkeletonIndex++ ) { if ( bToDoSkeleton[iSkeletonIndex] ) { iCurrentSkeleton = iSkeletonIndex; break; } } } printf( "\nDone" ); delete ex; return S_OK; } } // for ITF zlib usage, we need to define these functions for memory allocation #undef free #undef malloc namespace ITF { struct MemoryId { enum ITF_ALLOCATOR_IDS { ALLOCATOR_ID_FAKE, }; }; struct Memory { static void* mallocCategory( size_t size, MemoryId::ITF_ALLOCATOR_IDS id ); static void free(void* ptr); }; void* Memory::mallocCategory( size_t size, MemoryId::ITF_ALLOCATOR_IDS id ) { UNREFERENCED_PARAMETER( id ); return malloc( size ); } void Memory::free(void* ptr) { ::free( ptr ); } } #endif