/******************************************************************************** * FaceTrackNoIR This program is a private project of the some enthusiastic * * gamers from Holland, who don't like to pay much for * * head-tracking. * * * * Copyright (C) 2011 Wim Vriend (Developing) * * Ron Hendriks (Researching and Testing) * * * * Homepage * * * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 3 of the License, or (at your * * option) any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * * more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, see . * *********************************************************************************/ /* Modifications (last one on top): 20110322 - WVR: Somehow the video-widget of faceAPI version 3.2.6. does not work with FaceTrackNoIR (Qt issue?!). To be able to use release 3.2.6 of faceAPI anyway, this console-app is used. It exchanges data with FaceTrackNoIR via shared-memory... */ //Precompiled header #include "stdafx.h" //FaceAPI headers #include "sm_api.h" #include "ftnoir_tracker_sm_types.h" #include "utils.h" //local headers #include "build_options.h" //namespaces using namespace std; using namespace sm::faceapi::samplecode; // // global variables // HANDLE hSMMemMap = NULL; SMMemMap *pMemData; HANDLE hSMMutex; smEngineHeadPoseData new_head_pose; bool stopCommand = false; //enums enum GROUP_ID { GROUP0=0, }; enum EVENT_ID { EVENT_PING=0, EVENT_INIT, }; enum INPUT_ID { INPUT0=0, }; //function definitions void updateHeadPose(smEngineHeadPoseData* temp_head_pose); bool SMCreateMapping(); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // //FaceAPI function implementations // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void STDCALL receiveLogMessage(void *, const char *buf, int /*buf_len*/) { Lock lock(g_mutex); // serialize logging calls from different threads to avoid garbled output. //cout << string(buf); } // Callback function for face-data void STDCALL receiveFaceData(void *, smEngineFaceData face_data, smCameraVideoFrame video_frame) { Lock lock(g_mutex); // Get info including data pointer to original image from camera smImageInfo video_frame_image_info; THROW_ON_ERROR(smImageGetInfo(video_frame.image_handle, &video_frame_image_info)); // reentrant, so ok // video_frame_image_info.plane_addr[*] now point to the image memory planes. // The memory is only valid until the end of this routine unless you call smImageAddRef(video_frame.image_handle). // So you can deep copy the image data here, or use smImageAddRef() and just copy the pointer. // If you use smImageAddRef() you are responsible for calling smImageDestroy() to avoid a memory leak later. // In this callback you will typically want to copy the smEngineFaceData data into your own data-structure. // Since the smEngineFaceData contains multiple pod types copying it is not atomic and // a mutex is required to avoid the race-condition with any thread simultaneously // reading from your data-structure. // Such a race condition will not crash your code but will create weird noise in the tracking data. if (g_do_face_data_printing) { //cout << video_frame << " " << face_data; // Save any face texture to a PNG file if (face_data.texture) { // Create a unique filename std::stringstream filename; filename << "face_" << video_frame.frame_num << ".png"; // Try saving to a file if (saveToPNGFile(filename.str(), face_data.texture->image_info) == SM_API_OK) { cout << "Saved face-texture to " << filename.str() << std::endl; } else { cout << "Error saving face-texture to " << filename.str() << std::endl; } } } } // Callback function for head-pose void STDCALL receiveHeadPose(void *,smEngineHeadPoseData head_pose, smCameraVideoFrame video_frame) { Lock lock(g_mutex); // Get info including data pointer to original image from camera smImageInfo video_frame_image_info; THROW_ON_ERROR(smImageGetInfo(video_frame.image_handle, &video_frame_image_info)); // reentrant, so ok // video_frame_image_info.plane_addr[*] now point to the image memory planes. // The memory is only valid until the end of this routine unless you call smImageAddRef(video_frame.image_handle). // So you can deep copy the image data here, or use smImageAddRef() and just copy the pointer. // If you use smImageAddRef() you are responsible for calling smImageDestroy() to avoid a memory leak later. // In this callback you will typically want to copy the smEngineFaceData data into your own data-structure. // Since the smEngineFaceData contains multiple pod types copying it is not atomic and // a mutex is required to avoid the race-condition with any thread simultaneously // reading from your data-structure. // Such a race condition will not crash your code but will create weird noise in the tracking data. if (g_do_head_pose_printing) { //cout << video_frame << " " << head_pose << std::endl; } //make a copy of the new head pose data and send it to simconnect //when we get a simmconnect frame event the new offset will be applied to the camera updateHeadPose(&head_pose); } // Create the first available camera detected on the system, and return its handle smCameraHandle createFirstCamera() { // Detect cameras smCameraInfoList info_list; THROW_ON_ERROR(smCameraCreateInfoList(&info_list)); if (info_list.num_cameras == 0) { throw runtime_error("No cameras were detected"); } else { cout << "The followings cameras were detected: " << endl; for (int i=0; ihandle,TRUE)); } else { THROW_ON_ERROR(smVideoDisplayCreate(engine_handle,&video_display_handle,0,TRUE)); } // Setup the VideoDisplay THROW_ON_ERROR(smVideoDisplaySetFlags(video_display_handle,g_overlay_flags)); // Get the handle to the window and change the title to "Hello World" smWindowHandle win_handle = 0; THROW_ON_ERROR(smVideoDisplayGetWindowHandle(video_display_handle,&win_handle)); SetWindowText(win_handle, _T("faceAPI Video-widget")); MoveWindow(win_handle, 0, 0, 250, 150, true); // Loop on the keyboard while (processKeyPress(engine_handle, video_display_handle) && !stopCommand) { // Read and print the current head-pose (if not using the callback mechanism) #if (USE_HEADPOSE_CALLBACK==0) #pragma message("Polling Headpose Manually") if (engine_licensed) { smEngineHeadPoseData head_pose; Lock lock(g_mutex); THROW_ON_ERROR(smHTCurrentHeadPose(engine_handle,&head_pose)); if (g_do_head_pose_printing) { std::cout << head_pose << std::endl; } } #endif // NOTE: If you have a windows event loop in your program you // will not need to call smAPIProcessEvents(). This manually redraws the video window. THROW_ON_ERROR(smAPIProcessEvents()); // Prevent CPU overload in our simple loop. const int frame_period_ms = 10; Sleep(frame_period_ms); // // Process the command sent by FaceTrackNoIR. // sprintf_s(msg, "Command: %d\n", pMemData->command); OutputDebugStringA(msg); if (pMemData) { switch (pMemData->command) { case FT_SM_START: THROW_ON_ERROR(smEngineStart(engine_handle)); // Start tracking pMemData->command = 0; // Reset break; case FT_SM_STOP: THROW_ON_ERROR(smEngineStop(engine_handle)); // Stop tracking pMemData->command = 0; // Reset break; case FT_SM_EXIT: THROW_ON_ERROR(smEngineStop(engine_handle)); // Stop tracking stopCommand = TRUE; pMemData->command = 0; // Reset break; default: pMemData->command = 0; // Reset // should never be reached break; } } } // While(1) // Destroy engine THROW_ON_ERROR(smEngineDestroy(&engine_handle)); // Destroy video display THROW_ON_ERROR(smVideoDisplayDestroy(&video_display_handle)); if ( pMemData != NULL ) { UnmapViewOfFile ( pMemData ); } if (hSMMutex != 0) { CloseHandle( hSMMutex ); } hSMMutex = 0; if (hSMMemMap != 0) { CloseHandle( hSMMemMap ); } hSMMemMap = 0; } // run() // Application entry point int _tmain(int /*argc*/, _TCHAR** /*argv*/) { OutputDebugString(_T("_tmain() says: Starting Function\n")); try { if (SMCreateMapping()) { run(); } } catch (exception &e) { cerr << e.what() << endl; } return smAPIQuit(); } // // This is called exactly once for each FaceAPI callback and must be within an exclusive lock // void updateHeadPose(smEngineHeadPoseData* temp_head_pose) { // // Check if the pointer is OK and wait for the Mutex. // if ( (pMemData != NULL) && (WaitForSingleObject(hSMMutex, 100) == WAIT_OBJECT_0) ) { // // Copy the Raw measurements directly to the client. // if (temp_head_pose->confidence > 0.0f) { memcpy(&pMemData->data.new_pose,temp_head_pose,sizeof(smEngineHeadPoseData)); } ReleaseMutex(hSMMutex); } }; // // Create a memory-mapping to the faceAPI data. // It contains the tracking data, a command-code from FaceTrackNoIR // // bool SMCreateMapping() { OutputDebugString(_T("FTCreateMapping says: Starting Function\n")); // // A FileMapping is used to create 'shared memory' between the faceAPI and FaceTrackNoIR. // // Try to create a FileMapping to the Shared Memory. // If one already exists: close it. // hSMMemMap = CreateFileMappingA( INVALID_HANDLE_VALUE , 00 , PAGE_READWRITE , 0 , sizeof( TFaceData ) + sizeof( HANDLE ) + 100, (LPCSTR) SM_MM_DATA ); if ( hSMMemMap != 0 ) { OutputDebugString(_T("FTCreateMapping says: FileMapping Created!\n")); } if ( ( hSMMemMap != 0 ) && ( (long) GetLastError == ERROR_ALREADY_EXISTS ) ) { CloseHandle( hSMMemMap ); hSMMemMap = 0; } // // Create a new FileMapping, Read/Write access // hSMMemMap = OpenFileMappingA( FILE_MAP_ALL_ACCESS , false , (LPCSTR) SM_MM_DATA ); if ( ( hSMMemMap != 0 ) ) { OutputDebugString(_T("FTCreateMapping says: FileMapping Created again...\n")); pMemData = (SMMemMap *) MapViewOfFile(hSMMemMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(TFaceData)); if (pMemData != NULL) { OutputDebugString(_T("FTCreateMapping says: MapViewOfFile OK.\n")); // pMemData->handle = handle; // The game uses the handle, to send a message that the Program-Name was set! } hSMMutex = CreateMutexA(NULL, false, SM_MUTEX); } else { OutputDebugString(_T("Error creating Shared Memory for faceAPI\n")); cerr << "Error creating Shared Memory for faceAPI" << endl; return false; } //if (pMemData != NULL) { // pMemData->data.DataID = 1; // pMemData->data.CamWidth = 100; // pMemData->data.CamHeight = 250; //} return true; }