#include "ANSFR.h" #include #include "ANSOVFaceDetector.h" #include "SCRFDFaceDetector.h" #include "ANSFaceRecognizer.h" #include #include #include #include #include #include // Add this at the top of your header or before the function //#define DEBUG_TIME #ifdef DEBUG_TIME #define TIME_POINT auto #define START_TIMER(name) auto timer_##name = std::chrono::high_resolution_clock::now() #define END_TIMER(name, label) \ do { \ auto timer_##name##_end = std::chrono::high_resolution_clock::now(); \ auto duration_us = std::chrono::duration_cast(timer_##name##_end - timer_##name).count(); \ std::cout << label << ": " << (duration_us / 1000.0) << " ms" << std::endl; \ } while(0) #define END_TIMER_STORE(name, var) \ do { \ auto timer_##name##_end = std::chrono::high_resolution_clock::now(); \ var = std::chrono::duration_cast(timer_##name##_end - timer_##name).count(); \ } while(0) #else #define START_TIMER(name) #define END_TIMER(name, label) #define END_TIMER_STORE(name, var) #endif static bool ansfrLicenceValid = false; // Global once_flag to protect license checking static std::once_flag ansfrLicenseOnceFlag; namespace ANSCENTER { std::vector FaceChecker::UniqueFaces(const std::vector& faces) { std::lock_guard lock(_mutex); try{ std::unordered_map uniqueFacesMap; for (const auto& face : faces) { std::string key = face.userId + "_" + face.userName + "_" + face.cameraId; // If the face with the same userId, userName, and cameraId is not in the map, add it if (uniqueFacesMap.find(key) == uniqueFacesMap.end()) { uniqueFacesMap[key] = face; } else { // If it already exists, compare confidence scores if (face.confidence > uniqueFacesMap[key].confidence) { // Update the map to store the face with the highest confidence uniqueFacesMap[key] = face; } } } std::vector resultFaces; for (const auto& face : faces) { std::string key = face.userId + "_" + face.userName + "_" + face.cameraId; if (uniqueFacesMap[key].confidence == face.confidence) { // Keep the highest confidence face resultFaces.push_back(face); } else { // Update the other duplicate face to be "Unknown" FaceResultObject unknownFace = face; unknownFace.userId = "0000"; unknownFace.userName = "Unknown"; resultFaces.push_back(unknownFace); } } return resultFaces; } catch (std::exception& e) { return std::vector(); } } std::vector FaceChecker::ValidateDetectedFaces(const std::vector& faces) { std::lock_guard lock(_mutex); try { std::vector detectedFaces; std::unordered_set detectedFaceIds; // Store detected userIds for this frame for (const auto& faceResult : faces) { const std::string& cameraId = faceResult.cameraId; // Create a FaceDetection object FaceDetection detection; detection.faceObject = faceResult; detection.faceConfLevel = 1; // Get or create frame buffer for this camera auto& frameBuffer = frameBufferMap[cameraId]; bool found = false; // If the face is already in the buffer, increase the confidence level and update the face in buffer for (auto& frame : frameBuffer) { for (auto& detectionInBuffer : frame) { if ((detectionInBuffer.faceObject.userId == detection.faceObject.userId) && (detectionInBuffer.faceObject.userName == detection.faceObject.userName) && (detectionInBuffer.faceObject.cameraId == detection.faceObject.cameraId) && (detection.faceObject.confidence > minScore)) { // Found: Increase confidence & update face info detectionInBuffer.faceConfLevel += 1; if (detectionInBuffer.faceConfLevel >= MAX_FACE_CHECKER_FRAMES) detectionInBuffer.faceConfLevel = MAX_FACE_CHECKER_FRAMES; detectionInBuffer.faceObject = detection.faceObject; detection.faceConfLevel = detectionInBuffer.faceConfLevel; // Sync confidence level found = true; break; } } } // Store detected faceId to avoid multiple decrements later detectedFaceIds.insert(detection.faceObject.userId); // If face not found in the buffer, add it (except for unknown faces) if (!found && detection.faceObject.userId != "0000") { frameBuffer.push_back({ detection }); } // Add face to the detected list if (detection.faceConfLevel >= FACE_CONFIDENT_LEVEL) { detectedFaces.push_back(detection.faceObject); } else { FaceResultObject unknownFace = detection.faceObject; unknownFace.userId = "0000"; unknownFace.userName = "Unknown"; detectedFaces.push_back(unknownFace); } } // **Reduce confidence level for all faces in buffer ONCE if they were not detected** auto& frameBuffer = frameBufferMap[faces.empty() ? "" : faces[0].cameraId]; // Ensure buffer exists for (auto& frame : frameBuffer) { for (auto& detectionInBuffer : frame) { if (detectedFaceIds.find(detectionInBuffer.faceObject.userId) == detectedFaceIds.end()) { if (detectionInBuffer.faceConfLevel > 0) { detectionInBuffer.faceConfLevel--; // Safe decrement } } } } // Remove faces in buffer if their confidence level is less than 0 for (auto& frame : frameBuffer) { frame.erase(std::remove_if(frame.begin(), frame.end(), [](const FaceDetection& detection) { return detection.faceConfLevel < 0; }), frame.end()); } // Remove empty frames from buffer frameBuffer.erase(std::remove_if(frameBuffer.begin(), frameBuffer.end(), [](const std::vector& frame) { return frame.empty(); }), frameBuffer.end()); // If buffer reaches max size, remove the oldest frame if (frameBuffer.size() >= maxFrames) { frameBuffer.erase(frameBuffer.begin()); } return UniqueFaces(detectedFaces); } catch (std::exception& e) { return std::vector(); } } /// /// Start Facial Recognition class /// static void VerifyGlobalANSFRLicense(const std::string& licenseKey) { try { ansfrLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1004, "ANSFR");//Default productId=1004 if (!ansfrLicenceValid) { // we also support ANSTS license ansfrLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS) } } catch (std::exception& e) { ansfrLicenceValid = false; } } void ANSFacialRecognition::CheckLicense() { try { // Check once globally std::call_once(ansfrLicenseOnceFlag, [this]() { VerifyGlobalANSFRLicense(_licenseKey); }); // Update this instance's local license flag _licenseValid = ansfrLicenceValid; } catch (const std::exception& e) { this->_logger.LogFatal("ANSODBase::CheckLicense. Error:", e.what(), __FILE__, __LINE__); } } ANSFacialRecognition::ANSFacialRecognition() : core(std::make_shared()) { _licenseValid = false; } int ANSFacialRecognition::Initialize( const std::string& licenseKey, const std::string& configFile, const std::string& databaseFilePath, const std::string& recognizerFilePath, const std::string& detectorFilePath, int precisionType, float knownPersonThreshold, bool enableAgeGender, bool enableFaceEmotions, bool enableHeadPose, int minFaceSize, float faceDetectorScoreThreshold, bool faceLiveness, bool antiSpoofing) { try { // Store initialization parameters _licenseKey = licenseKey; _detectorFilePath = detectorFilePath; _recognizerFilePath = recognizerFilePath; _databaseFilePath = databaseFilePath; _precisionType = 2; // Force FP32 // Validate and set thresholds if (faceDetectorScoreThreshold <= 0.0f || faceDetectorScoreThreshold >= 1.0f) { _faceDetectorScoreThreshold = 0.5f; LogThreadSafe("ANSFacialRecognition::Initialize", "Invalid detector threshold, using default: 0.5"); } else { _faceDetectorScoreThreshold = faceDetectorScoreThreshold; } if (knownPersonThreshold <= 0.0f || knownPersonThreshold >= 1.0f) { _config._knownPersonThreshold = 0.35f; LogThreadSafe("ANSFacialRecognition::Initialize", "Invalid known person threshold, using default: 0.35"); } else { _config._knownPersonThreshold = knownPersonThreshold; } // Set minimum face size _minFaceSize = (minFaceSize > 0) ? minFaceSize : 50; // Configure feature flags { std::lock_guard lock(_configMutex); _enableAgeGender = enableAgeGender; _enableFaceEmotions = enableFaceEmotions; _enableHeadPose = enableHeadPose; _enableAntiSpoof = antiSpoofing; _enableFaceliveness = faceLiveness; _enableFaceQueue = false; _queueSize = QUEUE_SIZE; _faceThresholdSize = FACE_THRESHOLD_SIZE; _removeFakeFaces = false; } // Validate license CheckLicense(); if (!_licenseValid) { LogThreadSafe("ANSFacialRecognition::Initialize", "Invalid license key"); return 0; } // Detect hardware #ifdef CPU_MODE engineType = EngineType::CPU; #else engineType = ANSLicenseHelper::CheckHardwareInformation(); #endif // Initialize configuration _config._databasePath = _databaseFilePath; _config._recOutputDim = 512; _config._detThresholdNMS = 0.5f; _config._detThresholdBbox = 0.80f; _config._videoFrameHeight = 480; _config._videoFrameWidth = 640; // Load config file if exists if (FileExist(configFile)) { if (!ANSFRHelper::LoadConfigFile(configFile, _config)) { LogThreadSafe("ANSFacialRecognition::Initialize", "Unable to load configuration file: " + configFile); } else { // Restore database path if provided if (!_databaseFilePath.empty()) { _config._databasePath = _databaseFilePath; } } } // Set model paths if (FileExist(_detectorFilePath)) { _config._detEngineFile = _detectorFilePath; } if (FileExist(_recognizerFilePath)) { _config._recEngineFile = _recognizerFilePath; } // Validate database path if (_config._databasePath.empty()) { LogThreadSafe("ANSFacialRecognition::Initialize", "Invalid database path: empty"); return -1; } // Create database folders std::string faceDatabaseFolder = GetParentFolder(_config._databasePath); if (!FolderExist(faceDatabaseFolder)) { std::filesystem::create_directories(faceDatabaseFolder); } _config._gen_imgSource = CreateFilePath(faceDatabaseFolder, "data"); if (!FolderExist(_config._gen_imgSource)) { std::filesystem::create_directories(_config._gen_imgSource); } // Initialize database { std::lock_guard lock(_databaseMutex); if (!_db->Initialize(_config._databasePath, _config._recOutputDim, _config._gen_imgSource)) { LogThreadSafe("ANSFacialRecognition::Initialize", "Failed to initialize database: " + _config._databasePath); return -2; } _userDict = _db->GetUserDict(); } // Validate recognizer model if (!FileExist(_config._recEngineFile)) { LogThreadSafe("ANSFacialRecognition::Initialize", "Recognizer model not found: " + _config._recEngineFile); return -3; } // Mark as initialized _isInitialized=true; return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::Initialize", "Initialization failed: " + std::string(e.what())); _isInitialized=false; return -4; } } bool ANSFacialRecognition::LoadEngine() { try { // Unload existing engine (handles its own locking) UnloadEngine(); // Initialize core components (lock during initialization) { std::lock_guard lock(_configMutex); if (!InitializeDetector()) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to initialize detector"); return false; } if (!InitializeRecognizer()) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to initialize recognizer"); return false; } _recognizerModelFolder = _recognizer->GetModelFolder(); } // Verify model folder exists if (!FolderExist(_recognizerModelFolder)) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Model folder not found: " + _recognizerModelFolder); return false; } // Configure device std::string deviceName = GetOpenVINODevice(); if (deviceName == "NPU") { // Configure NPU with GPU fallback try { core->set_property("NPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY)); core->set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY)); core->set_property("GPU", ov::streams::num(ov::streams::AUTO)); deviceName = "AUTO:NPU,GPU"; LogThreadSafe("ANSFacialRecognition::LoadEngine", "Configured NPU with GPU fallback"); } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to configure NPU/GPU: " + std::string(e.what())); // Fall back to default device } } // Initialize attribute detection models bool attributeModelsSuccess = true; if (!InitializeAntispoofingModel(deviceName)) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Warning: Failed to initialize anti-spoofing model"); attributeModelsSuccess = false; } if (!InitializeAgeGenderModel(deviceName)) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Warning: Failed to initialize age-gender model"); attributeModelsSuccess = false; } if (!InitializeEmotionModel(deviceName)) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Warning: Failed to initialize emotion model"); attributeModelsSuccess = false; } if (!InitializeHeadPoseModel(deviceName)) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Warning: Failed to initialize head pose model"); attributeModelsSuccess = false; } // Reload database/embeddings if (!Reload()) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to reload database/embeddings"); return false; } // Mark as initialized { std::lock_guard lock(_configMutex); _isInitialized = true; } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::LoadEngine", e.what()); // Mark as not initialized on failure { std::lock_guard lock(_configMutex); _isInitialized = false; } return false; } } void ANSFacialRecognition::UnloadEngine() { try { // Clean up detector { std::lock_guard lock(_detectionMutex); if (_detector) _detector.reset(); } // Clean up recognizer { std::lock_guard lock(_recognitionMutex); if (_recognizer) _recognizer.reset(); } // Clean up anti-spoofing detector { std::lock_guard lock(_antispoofMutex); if (_antiSpoofDetector) _antiSpoofDetector.reset(); } // Clean up age-gender detector { std::lock_guard lock(_ageGenderMutex); if (_ageGenderDetector) _ageGenderDetector.reset(); } // Clean up emotions detector { std::lock_guard lock(_emotionsMutex); if (_emotionsDetector) _emotionsDetector.reset(); } // Clean up head pose detector { std::lock_guard lock(_headPoseMutex); if (_headPoseDetector) _headPoseDetector.reset(); } // Clear user dictionary { std::lock_guard lock(_databaseMutex); _userDict.clear(); } } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::UnloadEngine", "Error during unload: " + std::string(e.what())); } } bool ANSFacialRecognition::Reload() { try { // Skip rebuild if index is already up-to-date (no mutations since last Reload). // exchange(false) atomically reads + clears: if it was already false, nothing to do. if (!_reloadNeeded.exchange(false)) { LogThreadSafe("ANSFacialRecognition::Reload", "Index already up-to-date, skipping rebuild", LogLevel::Debug); return true; } // Validate initialization first (without locks) if (!_recognizer) { LogThreadSafe("ANSFacialRecognition::Reload", "Recognizer not initialized"); _reloadNeeded.store(true); // Restore flag — reload still needed return false; } if (!_db) { LogThreadSafe("ANSFacialRecognition::Reload", "Database not initialized"); _reloadNeeded.store(true); // Restore flag — reload still needed return false; } // ============================================================ // PHASE 1: Build new index in temporary structures (NO INDEX LOCK) // Inference continues unblocked using the old index. // ============================================================ auto* concreteRecognizer = dynamic_cast(_recognizer.get()); if (!concreteRecognizer) { LogThreadSafe("ANSFacialRecognition::Reload", "Recognizer is not ANSFaceRecognizer type"); _reloadNeeded.store(true); // Restore flag — reload still needed return false; } int embeddingSize = concreteRecognizer->GetEmbeddingSize(); std::unordered_map newFaceIdToUserId; std::map newUserDict; // Read all data from database into temporaries std::vector allEmbeddings; std::vector allIds; { std::lock_guard lock(_databaseMutex); std::vector records; int rc = _db->GetAllEmbeddingRecords(records); if (rc != 0) { LogThreadSafe("ANSFacialRecognition::Reload", "Failed to load embedding records from database"); _reloadNeeded.store(true); // Restore flag — reload still needed return false; } // Pre-allocate for bulk GPU transfer allEmbeddings.reserve(records.size() * embeddingSize); allIds.reserve(records.size()); for (const auto& record : records) { faiss::idx_t faceId = static_cast(record.faceId); newFaceIdToUserId[faceId] = record.userId; allIds.push_back(faceId); // L2-normalize before storing (IP metric on unit vectors = cosine sim) std::vector normEmb = record.embedding; ANSFaceRecognizer::L2NormalizeInPlace(normEmb); allEmbeddings.insert(allEmbeddings.end(), normEmb.begin(), normEmb.end()); } newUserDict = _db->GetUserDict(); } // Always build FAISS index on CPU. This avoids thread-safety issues with // FAISS GPU StandardGpuResources (not thread-safe) — Reload() can run // concurrently with inference without sharing GPU memory pools. // CPU FAISS search is fast enough for face databases (<5ms for 10K faces) // and is a negligible fraction of total inference time. auto cpuIndex = new faiss::IndexFlatIP(embeddingSize); auto newFaissIndex = std::make_shared(cpuIndex); newFaissIndex->own_fields = true; if (!allIds.empty()) { newFaissIndex->add_with_ids( static_cast(allIds.size()), allEmbeddings.data(), allIds.data()); } LogThreadSafe("ANSFacialRecognition::Reload", "Built new index with " + std::to_string(newFaissIndex->ntotal) + " embeddings, " + std::to_string(newUserDict.size()) + " users", LogLevel::Info); // ============================================================ // PHASE 2: Atomic swap (brief exclusive lock ~microseconds) // Inference is blocked only during this swap. // ============================================================ { std::unique_lock lock(_indexSwapMutex); concreteRecognizer->SwapIndex( std::move(newFaissIndex), std::move(newFaceIdToUserId)); _userDict = std::move(newUserDict); } LogThreadSafe("ANSFacialRecognition::Reload", "Index swap completed successfully", LogLevel::Info); return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::Reload", "Failed to reload: " + std::string(e.what())); _reloadNeeded.store(true); // Restore flag — reload still needed return false; } } bool ANSFacialRecognition::UpdateUserDictionary() { try { // Validate database initialization first (without lock) if (!_db) { LogThreadSafe("ANSFacialRecognition::UpdateUserDictionary", "Database not initialized"); return false; } // Load user dictionary with database lock std::map newUserDict; { std::lock_guard lock(_databaseMutex); newUserDict = _db->GetUserDict(); } // Validate the loaded dictionary if (newUserDict.empty()) { LogThreadSafe("ANSFacialRecognition::UpdateUserDictionary", "Warning: User dictionary is empty"); } // Swap userDict under exclusive lock (same lock as Reload) { std::unique_lock lock(_indexSwapMutex); _userDict = std::move(newUserDict); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::UpdateUserDictionary", "Failed to update user dictionary: " + std::string(e.what())); return false; } } ANSFacialRecognition::~ANSFacialRecognition() noexcept { try { // Destroy engine and resources Destroy(); // Clean up camera data { std::lock_guard lock(_cameraMutex); for (auto& [cameraId, cameraData] : _cameras) { cameraData.clear(); } _cameras.clear(); } } catch (const std::exception& e) { // Log but don't throw from destructor LogThreadSafe("ANSFacialRecognition::Destructor", "Error during destruction: " + std::string(e.what())); } catch (...) { // Catch all other exceptions - never throw from destructor } } void ANSFacialRecognition::Destroy() { try { // Unload engine (handles its own locking) UnloadEngine(); // Clean up database connection { std::lock_guard lock(_databaseMutex); if (_db) _db.reset(); } } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::Destroy", "Error during cleanup: " + std::string(e.what())); } } ModelConfig ANSFacialRecognition::CreateDetectorModelConfig() { // NO LOCK NEEDED - Reading configuration values only try { ModelConfig config; config.modelType = ANSCENTER::ModelType::FACEDETECT; config.detectionType = ANSCENTER::DetectionType::FACEDETECTOR; config.precisionType = static_cast(_precisionType); config.autoGPUDetection = true; config.modelMNSThreshold = _config._detThresholdNMS; config.detectionScoreThreshold = _config._detThresholdBbox; config.modelConfThreshold = 0.48f; return config; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::CreateDetectorModelConfig", "Failed to create config: " + std::string(e.what())); return ModelConfig(); } } bool ANSFacialRecognition::InitializeDetector() { try { // Create model configuration ModelConfig detectorConfig = CreateDetectorModelConfig(); detectorConfig.detectionScoreThreshold = _faceDetectorScoreThreshold; // Create appropriate detector based on engine type if (engineType == EngineType::NVIDIA_GPU) { _detector = std::make_unique(); } else { _detector = std::make_unique(); } _detector->SetMaxSlotsPerGpu(m_maxSlotsPerGpu); // LOCK DURING INITIALIZATION bool initSuccess; { std::lock_guard lock(_detectionMutex); std::string labelMap; initSuccess = _detector->Initialize( _licenseKey, detectorConfig, _detectorFilePath, "", labelMap ); if (initSuccess) { // Update detection threshold after initialization initSuccess = _detector->UpdateDetectionThreshold(_faceDetectorScoreThreshold); } } if (!initSuccess) { LogThreadSafe("ANSFacialRecognition::InitializeDetector", "Failed to initialize detector - check file path: " + _detectorFilePath); return false; } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeDetector", "Failed to initialize: " + std::string(e.what())); return false; } } bool ANSFacialRecognition::InitializeRecognizer() { try { // Create recognizer instance _recognizer = std::make_unique(); _recognizer->SetMaxSlotsPerGpu(m_maxSlotsPerGpu); // Configure model ModelConfig recognizerConfig; recognizerConfig.modelType = ANSCENTER::ModelType::FACERECOGNIZE; recognizerConfig.detectionType = ANSCENTER::DetectionType::FACERECOGNIZER; recognizerConfig.precisionType = ANSCENTER::PrecisionType::FP32; recognizerConfig.autoGPUDetection = true; recognizerConfig.modelMNSThreshold = _config._detThresholdNMS; recognizerConfig.detectionScoreThreshold = _config._detThresholdBbox; recognizerConfig.modelConfThreshold = 0.48f; recognizerConfig.unknownPersonThreshold = _config._knownPersonThreshold; // LOCK DURING INITIALIZATION std::string labelMap; bool initSuccess; { std::lock_guard lock(_recognitionMutex); initSuccess = _recognizer->Initialize( _licenseKey, recognizerConfig, _recognizerFilePath, "", labelMap ); } if (!initSuccess) { LogThreadSafe("ANSFacialRecognition::InitializeRecognizer", "Failed to initialize recognizer - check file path: " + _recognizerFilePath); return false; } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeRecognizer", "Failed to initialize: " + std::string(e.what())); return false; } } bool ANSFacialRecognition::InitializeAntispoofingModel(const std::string& deviceName) { try { std::string antispoofModelPath = CreateFilePath(_recognizerModelFolder, "antispoof.xml"); if (!FileExist(antispoofModelPath)) { LogThreadSafe("ANSFacialRecognition::InitializeAntispoofingModel", "Anti-spoofing model not found: " + antispoofModelPath); // Disable anti-spoofing if model not available { std::lock_guard lock(_configMutex); _enableAntiSpoof = false; } return false; } // LOCK ONLY DURING MODEL INITIALIZATION { std::lock_guard lock(_antispoofMutex); // Reset existing detector _antiSpoofDetector.reset(); // Create new detector _antiSpoofDetector = std::make_unique(antispoofModelPath, true); // Compile model for target device ov::CompiledModel compiledModel = core->compile_model( _antiSpoofDetector->read(*core), deviceName ); // Create inference request _antiSpoofDetector->request = compiledModel.create_infer_request(); _antiSpoofDetector->inTensor = _antiSpoofDetector->request.get_input_tensor(); _antiSpoofDetector->inTensor.set_shape(_antiSpoofDetector->inShape); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeAntispoofingModel", "Failed to initialize: " + std::string(e.what())); // Disable anti-spoofing on failure { std::lock_guard lock(_configMutex); _enableAntiSpoof = false; } return false; } } bool ANSFacialRecognition::InitializeAgeGenderModel(const std::string& deviceName) { try { std::string ageGenderModelPath = CreateFilePath(_recognizerModelFolder, "agegender.xml"); if (!FileExist(ageGenderModelPath)) { LogThreadSafe("ANSFacialRecognition::InitializeAgeGenderModel", "Age-gender model not found: " + ageGenderModelPath); // Disable age-gender detection if model not available { std::lock_guard lock(_configMutex); _enableAgeGender = false; } return false; } // LOCK ONLY DURING MODEL INITIALIZATION { std::lock_guard lock(_ageGenderMutex); // Reset existing detector _ageGenderDetector.reset(); // Create new detector _ageGenderDetector = std::make_unique(ageGenderModelPath, true); // Compile model for target device ov::CompiledModel compiledModel = core->compile_model( _ageGenderDetector->read(*core), deviceName ); // Create inference request _ageGenderDetector->request = compiledModel.create_infer_request(); _ageGenderDetector->inTensor = _ageGenderDetector->request.get_input_tensor(); _ageGenderDetector->inTensor.set_shape(_ageGenderDetector->inShape); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeAgeGenderModel", "Failed to initialize: " + std::string(e.what())); // Disable age-gender detection on failure { std::lock_guard lock(_configMutex); _enableAgeGender = false; } return false; } } bool ANSFacialRecognition::InitializeEmotionModel(const std::string& deviceName) { try { std::string emotionModelPath = CreateFilePath(_recognizerModelFolder, "emotionsrecognition.xml"); if (!FileExist(emotionModelPath)) { LogThreadSafe("ANSFacialRecognition::InitializeEmotionModel", "Emotion model not found: " + emotionModelPath); // Disable emotion detection if model not available { std::lock_guard lock(_configMutex); _enableFaceEmotions = false; } return false; } // LOCK ONLY DURING MODEL INITIALIZATION { std::lock_guard lock(_emotionsMutex); // Reset existing detector _emotionsDetector.reset(); // Create new detector _emotionsDetector = std::make_unique(emotionModelPath, true); // Compile model for target device ov::CompiledModel compiledModel = core->compile_model( _emotionsDetector->read(*core), deviceName ); // Create inference request _emotionsDetector->request = compiledModel.create_infer_request(); _emotionsDetector->inTensor = _emotionsDetector->request.get_input_tensor(); _emotionsDetector->inTensor.set_shape(_emotionsDetector->inShape); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeEmotionModel", "Failed to initialize: " + std::string(e.what())); // Disable emotion detection on failure { std::lock_guard lock(_configMutex); _enableFaceEmotions = false; } return false; } } bool ANSFacialRecognition::InitializeHeadPoseModel(const std::string& deviceName) { try { std::string headPoseModelPath = CreateFilePath(_recognizerModelFolder, "headpose.xml"); if (!FileExist(headPoseModelPath)) { LogThreadSafe("ANSFacialRecognition::InitializeHeadPoseModel", "Head pose model not found: " + headPoseModelPath); // Disable head pose detection if model not available { std::lock_guard lock(_configMutex); _enableHeadPose = false; } return false; } // LOCK ONLY DURING MODEL INITIALIZATION { std::lock_guard lock(_headPoseMutex); // Reset existing detector _headPoseDetector.reset(); // Create new detector _headPoseDetector = std::make_unique(headPoseModelPath, true); // Compile model for target device ov::CompiledModel compiledModel = core->compile_model( _headPoseDetector->read(*core), deviceName ); // Create inference request _headPoseDetector->request = compiledModel.create_infer_request(); _headPoseDetector->inTensor = _headPoseDetector->request.get_input_tensor(); _headPoseDetector->inTensor.set_shape(_headPoseDetector->inShape); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InitializeHeadPoseModel", "Failed to initialize: " + std::string(e.what())); // Disable head pose on failure { std::lock_guard lock(_configMutex); _enableHeadPose = false; } return false; } } bool ANSFacialRecognition::UpdateParameters( float knownPersonThreshold, bool enableAgeGender, bool enableFaceEmotions, bool enableHeadPose, int minFaceSize, float faceDetectorScoreThreshold, bool faceLiveness, bool antiSpoof, bool removeFakeFace) { // Validate inputs if (knownPersonThreshold < 0.0f || knownPersonThreshold > 1.0f) { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Invalid known person threshold: " + std::to_string(knownPersonThreshold) + " (valid range: 0.0-1.0)"); return false; } if (faceDetectorScoreThreshold < 0.0f || faceDetectorScoreThreshold > 1.0f) { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Invalid detector threshold: " + std::to_string(faceDetectorScoreThreshold) + " (valid range: 0.0-1.0)"); return false; } try { // Update configuration (with config lock) { std::lock_guard lock(_configMutex); _enableAgeGender = enableAgeGender; _enableFaceEmotions = enableFaceEmotions; _enableHeadPose = enableHeadPose; _enableAntiSpoof = antiSpoof; _enableFaceliveness = faceLiveness; _removeFakeFaces = removeFakeFace; // Enforce minimum face size _minFaceSize = (minFaceSize > 0) ? minFaceSize : 30; } // Update recognizer (with recognition lock) bool recognizerUpdateResult = false; { std::lock_guard lock(_recognitionMutex); if (_recognizer) { recognizerUpdateResult = _recognizer->UpdateParamater(knownPersonThreshold); if (recognizerUpdateResult) { _config._knownPersonThreshold = knownPersonThreshold; } else { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Failed to update face recognizer with threshold " + std::to_string(knownPersonThreshold)); } } else { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Recognizer not initialized"); } } // Update detector (with detection lock) bool detectorUpdateResult = true; if (faceDetectorScoreThreshold > 0.0f) { std::lock_guard lock(_detectionMutex); if (_detector) { detectorUpdateResult = _detector->UpdateDetectionThreshold(faceDetectorScoreThreshold); if (detectorUpdateResult) { _faceDetectorScoreThreshold = faceDetectorScoreThreshold; } else { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Failed to update face detector with threshold " + std::to_string(faceDetectorScoreThreshold)); } } else { LogThreadSafe("ANSFacialRecognition::UpdateParameters", "Detector not initialized"); detectorUpdateResult = false; } } return recognizerUpdateResult && detectorUpdateResult; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::UpdateParameters", e.what()); return false; } } bool ANSFacialRecognition::GetFaceParameters( float& knownPersonThreshold, bool& enableAgeGender, bool& enableFaceEmotions, bool& enableHeadPose, int& minFaceSize, float& faceDetectorScoreThreshold, bool& faceLiveness, bool& antiSpoof, bool& removeFakeFaces) { try { // LOCK DURING CONFIGURATION READ std::lock_guard lock(_configMutex); knownPersonThreshold = this->_config._knownPersonThreshold; enableAgeGender = this->_enableAgeGender; enableFaceEmotions = this->_enableFaceEmotions; enableHeadPose = this->_enableHeadPose; minFaceSize = this->_minFaceSize; faceLiveness = this->_enableFaceliveness; antiSpoof = this->_enableAntiSpoof; removeFakeFaces = this->_removeFakeFaces; // Detector threshold may need detection mutex if detector state can change if (_detector) { faceDetectorScoreThreshold = _detector->GetDetectionThreshold(); } else { faceDetectorScoreThreshold = this->_faceDetectorScoreThreshold; LogThreadSafe("ANSFacialRecognition::GetFaceParameters", "Detector not initialized"); } return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFaceParameters", e.what()); return false; } } bool ANSFacialRecognition::UpdateFaceQueue(int queueSize,int faceThresholdSize,bool enableFaceQueue) { try { // LOCK DURING CONFIGURATION UPDATE std::lock_guard lock(_configMutex); _queueSize = queueSize; _faceThresholdSize = faceThresholdSize; _enableFaceQueue = enableFaceQueue; return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::UpdateFaceQueue", e.what()); return false; } } bool ANSFacialRecognition::GetFaceQueue(int& queueSize,int& faceThresholdSize,bool& enableFaceQueue) { try { std::lock_guard lock(_configMutex); queueSize = _queueSize; faceThresholdSize = _faceThresholdSize; enableFaceQueue = _enableFaceQueue; return true; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFaceQueue", e.what()); return false; } } int ANSFacialRecognition::InsertUser(const std::string& userCode,const std::string& userName) { try { int userId; { std::lock_guard lock(_databaseMutex); userId = _db->InsertUser(userCode, userName); } if (userId <= 0) { LogThreadSafe("ANSFacialRecognition::InsertUser", "Failed to insert user: " + userCode); } return userId; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InsertUser", "User code " + userCode + ": " + e.what()); return 0; } } int ANSFacialRecognition::DeleteUser(int userId) { try { // First delete all faces for this user int result = DeleteFacesByUser(userId); // Then delete the user record (lock during database access) { std::lock_guard lock(_databaseMutex); result = _db->DeleteUser(userId); } if (result <= 0) { LogThreadSafe("ANSFacialRecognition::DeleteUser", "Failed to delete user ID " + std::to_string(userId) + " from database"); } else { // User record deleted — mark dirty so Reload() refreshes _userDict. // (DeleteFacesByUser above already reloaded for face removal; // this second Reload rebuilds _userDict without the deleted user.) _reloadNeeded.store(true); Reload(); } return result; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::DeleteUser", "User ID " + std::to_string(userId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::DeleteUsers(const std::vector& userIds) { int deletedCount = 0; for (int userId : userIds) { try { // First delete all faces for this user DeleteFacesByUser(userId); // Then delete the user record (lock during database access) int result; { std::lock_guard lock(_databaseMutex); result = _db->DeleteUser(userId); } if (result <= 0) { LogThreadSafe("ANSFacialRecognition::DeleteUsers", "Failed to delete user ID " + std::to_string(userId) + " from database"); } else { deletedCount++; } } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::DeleteUsers", "User ID " + std::to_string(userId) + ": " + e.what()); } } if (deletedCount > 0) { _reloadNeeded.store(true); Reload(); } return deletedCount; } int ANSFacialRecognition::InsertFace(int userId, const cv::Mat& image) { // Early validation checks (read-only, thread-safe) if (!ValidateInput(image)) { return 0; } try { // Prepare input frame (CPU operation, independent) cv::Mat frame = PrepareInputFrame(image); if (frame.empty()) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Failed to prepare input frame for user ID " + std::to_string(userId)); return 0; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { std::lock_guard lock(_detectionMutex); detectedFaces = _detector->RunInference(frame, "", false, true, false); } if (detectedFaces.empty()) { LogThreadSafe("ANSFacialRecognition::InsertFace", "No faces detected for user ID " + std::to_string(userId)); return 0; } // Get largest face (CPU operation) Object faceObject = GetLargestObject(detectedFaces); // LOCK ONLY DURING RECOGNITION (shared recognizer) std::vector embedding; { std::lock_guard lock(_recognitionMutex); embedding = _recognizer->Feature(frame, faceObject); } // Validate embedding if (embedding.empty() || embedding.size() != _config._recOutputDim) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Invalid embedding generated for user ID " + std::to_string(userId) + " (size: " + std::to_string(embedding.size()) + ")"); return 0; } // Prepare storage folder (filesystem operation, no lock) std::string userImageFolderName = "User" + std::to_string(userId); std::string imageFolder = CreateFilePath(_config._gen_imgSource, userImageFolderName); if (!FolderExist(imageFolder)) { try { std::filesystem::create_directory(imageFolder); } catch (const std::filesystem::filesystem_error& e) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Failed to create directory for user ID " + std::to_string(userId) + ": " + e.what()); return 0; } } // Generate unique filename boost::uuids::uuid uuid = boost::uuids::random_generator()(); std::string imageFileName = Underscore2Dash(boost::uuids::to_string(uuid)) + ".jpg"; std::string imagePath = CreateFilePath(imageFolder, imageFileName); // Save face image if (!cv::imwrite(imagePath, faceObject.mask)) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Failed to save face image for user ID " + std::to_string(userId)); return 0; } // Verify file was created if (!FileExist(imagePath)) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Image file verification failed for user ID " + std::to_string(userId) + " at path: " + imagePath); return 0; } // Insert into database (lock during database access) int faceId; { std::lock_guard lock(_databaseMutex); faceId = _db->InsertFace(userId, imagePath, embedding.data()); } if (faceId <= 0) { LogThreadSafe("ANSFacialRecognition::InsertFace", "Failed to insert face into database for user ID " + std::to_string(userId)); } else { // Fix #3: Auto-reload FAISS index so new face is searchable immediately _reloadNeeded.store(true); Reload(); } return faceId; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InsertFace", "User ID " + std::to_string(userId) + ": " + e.what()); return 0; } } std::vector ANSFacialRecognition::InsertMultipleFaces(int userId, const cv::Mat& image) { std::vector faceIds; // Early validation checks (read-only, thread-safe) if (!ValidateInput(image)) { return faceIds; } try { // Prepare input frame (CPU operation, independent) cv::Mat frame = PrepareInputFrame(image); if (frame.empty()) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "Failed to prepare input frame for user ID " + std::to_string(userId)); return faceIds; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { std::lock_guard lock(_detectionMutex); detectedFaces = _detector->RunInference(frame, "", false, true, false); } if (detectedFaces.empty()) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "No faces detected for user ID " + std::to_string(userId)); return faceIds; } // Get largest face Object faceObject = GetLargestObject(detectedFaces); // LOCK ONLY DURING RECOGNITION (shared recognizer) std::vector embedding; { std::lock_guard lock(_recognitionMutex); embedding = _recognizer->Feature(frame, faceObject); } if (embedding.empty() || embedding.size() != _config._recOutputDim) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "Invalid embedding generated for user ID " + std::to_string(userId)); return faceIds; } // Prepare storage folder (filesystem operation, no lock) std::string userImageFolderName = "User" + std::to_string(userId); std::string imageFolder = CreateFilePath(_config._gen_imgSource, userImageFolderName); if (!FolderExist(imageFolder)) { std::filesystem::create_directory(imageFolder); } // Generate unique filename boost::uuids::uuid uuid = boost::uuids::random_generator()(); std::string imageFileName = Underscore2Dash(boost::uuids::to_string(uuid)) + ".jpg"; std::string imagePath = CreateFilePath(imageFolder, imageFileName); // Save face image if (!cv::imwrite(imagePath, faceObject.mask)) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "Failed to save face image for user ID " + std::to_string(userId)); return faceIds; } if (!FileExist(imagePath)) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "Image file verification failed for user ID " + std::to_string(userId)); return faceIds; } // Insert into database (lock during database access) int faceId; { std::lock_guard lock(_databaseMutex); faceId = _db->InsertFace(userId, imagePath, embedding.data()); } if (faceId > 0) { faceIds.push_back(faceId); } else { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "Failed to insert face into database for user ID " + std::to_string(userId)); } // Auto-reload FAISS index so newly inserted faces are searchable if (!faceIds.empty()) { _reloadNeeded.store(true); Reload(); } return faceIds; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::InsertMultipleFaces", "User ID " + std::to_string(userId) + ": " + e.what()); return faceIds; } } int ANSFacialRecognition::DeleteFace(int faceId) { try { int result; { std::lock_guard lock(_databaseMutex); result = _db->DeleteFace(faceId); } if (result <= 0) { LogThreadSafe("ANSFacialRecognition::DeleteFace", "Failed to delete face ID " + std::to_string(faceId)); } else { // Fix #3: Auto-reload FAISS index so deleted face is no longer searchable _reloadNeeded.store(true); Reload(); } return result; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::DeleteFace", "Face ID " + std::to_string(faceId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::UpdateUser(int userId, const std::string& userCode, const std::string& userName) { try { // Validate input if (userId <= 0) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Invalid user ID: " + std::to_string(userId)); return 0; } if (userCode.empty() && userName.empty()) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Both userCode and userName are empty for user ID " + std::to_string(userId)); return 0; } // Validate database initialization if (!_db) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Database not initialized"); return 0; } // Update user in database int result; { std::lock_guard lock(_databaseMutex); result = _db->UpdateUser(userId, userCode, userName); } if (result <= 0) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Failed to update user ID " + std::to_string(userId) + " in database"); return 0; } // Update user dictionary to reflect changes if (!UpdateUserDictionary()) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Warning: User ID " + std::to_string(userId) + " updated in database but failed to refresh user dictionary"); // Consider: Should this be a critical error? // return 0; // Uncomment if dictionary sync is critical } LogThreadSafe("ANSFacialRecognition::UpdateUser", "Successfully updated user ID " + std::to_string(userId), LogLevel::Info); return result; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::UpdateUser", "Error updating user ID " + std::to_string(userId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::GetUser(int userId, UserRecord& userRecord) { try { // Validate input if (userId <= 0) { LogThreadSafe("ANSFacialRecognition::GetUser", "Invalid user ID: " + std::to_string(userId)); return 0; } // Validate database initialization if (!_db) { LogThreadSafe("ANSFacialRecognition::GetUser", "Database not initialized"); return 0; } // Initialize user ID userRecord.UserId = userId; // LOCK ONLY DURING DATABASE ACCESS { std::lock_guard lock(_databaseMutex); // Get user basic information int result = _db->GetUser(userId, userRecord.UserCode, userRecord.UserName); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetUser", "User ID " + std::to_string(userId) + " not found in database"); return 0; } // Get user's face IDs result = _db->GetFaceIdsByUser(userId, userRecord.FaceIds); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetUser", "No faces found for user ID " + std::to_string(userId)); // Consider: Should this be an error or just a warning? // A user might exist but have no registered faces yet return 0; } } // Optional: Log successful retrieval LogThreadSafe("ANSFacialRecognition::GetUser", "Successfully retrieved user ID " + std::to_string(userId) + " with " + std::to_string(userRecord.FaceIds.size()) + " face(s)"); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetUser", "Error retrieving user ID " + std::to_string(userId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::GetUser(int userId, const std::string& userCode,const std::string& userName, UserRecord& userRecord) { try { // Initialize user ID userRecord.UserId = userId; userRecord.UserCode = userCode; userRecord.UserName = userName; // LOCK ONLY DURING DATABASE ACCESS { std::lock_guard lock(_databaseMutex); // Get user's face IDs int result = _db->GetFaceIdsByUser(userId, userRecord.FaceIds); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetUser", "No faces found for user ID " + std::to_string(userId)); return 0; } } return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetUser", "User ID " + std::to_string(userId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::GetUsers(std::vector& userRecords,std::vector& userIds) { // Clear output parameters userRecords.clear(); userIds.clear(); try { std::vector userCodes; std::vector userNames; std::vector> faceIdsByUser; // Fix #12: Single batch query with JOIN replaces N+1 individual queries { std::lock_guard lock(_databaseMutex); int result = _db->GetUsersWithFaceIds(userIds, userCodes, userNames, faceIdsByUser); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetUsers", "Failed to query users from database"); return 0; } } if (userIds.empty()) { return 0; } // Build UserRecord objects directly from batch results userRecords.reserve(userIds.size()); for (size_t i = 0; i < userIds.size(); ++i) { UserRecord userRecord; userRecord.UserId = userIds[i]; userRecord.UserCode = std::move(userCodes[i]); userRecord.UserName = std::move(userNames[i]); userRecord.FaceIds = std::move(faceIdsByUser[i]); userRecords.push_back(std::move(userRecord)); } return static_cast(userRecords.size()); } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetUsers", e.what()); userRecords.clear(); userIds.clear(); return 0; } } int ANSFacialRecognition::GetFace(int faceId, FaceRecord& faceRecord) { try { // Initialize the record with the face ID faceRecord.FaceId = faceId; // LOCK ONLY DURING DATABASE ACCESS int result; { std::lock_guard lock(_databaseMutex); result = _db->GetFaceById(faceId, faceRecord.UserId, faceRecord.ImagePath); } if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetFace", "Face ID " + std::to_string(faceId) + " not found in database"); } return result; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFace", "Face ID " + std::to_string(faceId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::GetFaces(int userId, std::vector& faceRecords) { // Clear output parameter faceRecords.clear(); try { // Get face IDs from database (lock during database access) std::vector faceIds; { std::lock_guard lock(_databaseMutex); int result = _db->GetFaceIdsByUser(userId, faceIds); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetFaces", "Failed to query face IDs for user " + std::to_string(userId)); return 0; } } if (faceIds.empty()) { return 0; } // Reserve space for efficiency faceRecords.reserve(faceIds.size()); // Retrieve each face record for (int faceId : faceIds) { FaceRecord faceRecord; int result = GetFace(faceId, faceRecord); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::GetFaces", "Failed to retrieve face ID " + std::to_string(faceId) + " for user " + std::to_string(userId)); faceRecords.clear(); // Clear partial results on error return 0; } faceRecords.push_back(std::move(faceRecord)); } return static_cast(faceRecords.size()); } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFaces", std::string("User ") + std::to_string(userId) + ": " + e.what()); faceRecords.clear(); return 0; } } int ANSFacialRecognition::DeleteFacesByUser(int userId) { try { std::vector faceIds; // Get face IDs for this user (lock during database access) { std::lock_guard lock(_databaseMutex); int result = _db->GetFaceIdsByUser(userId, faceIds); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser", "Failed to query faces for user ID " + std::to_string(userId)); return 0; } } if (faceIds.empty()) { LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser", "No faces found for user ID " + std::to_string(userId)); return 0; } // Delete each face from database (lock per deletion) for (int faceId : faceIds) { std::lock_guard lock(_databaseMutex); int result = _db->DeleteFace(faceId); if (result <= 0) { LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser", "Failed to delete face ID " + std::to_string(faceId) + " for user ID " + std::to_string(userId)); return 0; } } // Delete user's image folder (filesystem operation, no lock needed) std::string userImageFolderName = "User" + std::to_string(userId); std::string imageFolder = CreateFilePath(_config._gen_imgSource, userImageFolderName); if (FolderExist(imageFolder)) { DeleteFolder(imageFolder); } // Fix #3: Auto-reload FAISS index after batch face deletion _reloadNeeded.store(true); Reload(); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser", "User ID " + std::to_string(userId) + ": " + e.what()); return 0; } } int ANSFacialRecognition::GetUser(int userId, std::string& userRecord) { // NO LOCK NEEDED - Delegates to another method that handles locking try { UserRecord record; // Get user record (this method handles its own locking) int result = GetUser(userId, record); if (result <= 0) { userRecord.clear(); return 0; } // Serialize to JSON (CPU operation, thread-safe) userRecord = ANSFRHelper::UserRecordToJsonString(record); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetUser", "User ID " + std::to_string(userId) + ": " + e.what()); userRecord.clear(); return 0; } } int ANSFacialRecognition::GetUsers(std::string& userRecords, std::vector& userIds) { // NO LOCK NEEDED - Delegates to another method that handles locking // Clear output parameters userRecords.clear(); userIds.clear(); try { std::vector records; // Get user records (this method handles its own locking) int result = GetUsers(records, userIds); if (result <= 0 || records.empty()) { return 0; } // Serialize to JSON (CPU operation, thread-safe) userRecords = ANSFRHelper::UserRecordsToJsonString(records); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetUsers", e.what()); userRecords.clear(); userIds.clear(); return 0; } } int ANSFacialRecognition::GetFace(int faceId, std::string& faceRecord) { // NO LOCK NEEDED - Delegates to another method that handles locking try { FaceRecord record; // Get face record (this method handles its own locking) int result = GetFace(faceId, record); if (result <= 0) { faceRecord.clear(); return 0; } // Serialize to JSON (CPU operation, thread-safe) faceRecord = ANSFRHelper::FaceRecordToJsonString(record); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFace", "Face ID " + std::to_string(faceId) + ": " + e.what()); faceRecord.clear(); return 0; } } int ANSFacialRecognition::GetFaces(int userId, std::string& faceRecords) { // NO LOCK NEEDED - Delegates to another method that handles locking if needed try { std::vector records; // Get face records (this method should handle its own locking if needed) int result = GetFaces(userId, records); if (result <= 0 || records.empty()) { faceRecords.clear(); return 0; } // Serialize to JSON (CPU operation, thread-safe) faceRecords = ANSFRHelper::FaceRecordsToJsonString(records); return 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::GetFaces", e.what()); faceRecords.clear(); return 0; } } int ANSFacialRecognition::CheckFace(const cv::Mat& image) { // Early validation checks (read-only, thread-safe) if (!ValidateInput(image)) { return 0; } try { // Prepare input frame (CPU operation, independent) cv::Mat frame = PrepareInputFrame(image); if (frame.empty()) { LogThreadSafe("ANSFacialRecognition::CheckFace", "Failed to prepare input frame"); return 0; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { std::lock_guard lock(_detectionMutex); detectedFaces = _detector->RunInference(frame, "", false, true, false); } if (detectedFaces.empty()) { return 0; } // Filter faces (CPU operation, independent) auto validFaces = CheckFaces(detectedFaces,true); return validFaces.empty() ? 0 : 1; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::CheckFace", e.what()); return 0; } } std::vector ANSFacialRecognition::Recognize(const cv::Mat& input) { return Recognize(input, "FRCAM"); } std::vector ANSFacialRecognition::Recognize(const cv::Mat& input, const std::string& camera_id) { std::vector resultObjects; // Early validation checks (read-only, thread-safe) if (!ValidateInitialization() || !ValidateInput(input) || !ValidateComponents()) { return resultObjects; } try { // Prepare input frame (CPU operation, independent per camera) cv::Mat frame = PrepareInputFrame(input); if (frame.empty()) { LogError("ANSFacialRecognition::Recognize", "Failed to prepare input frame", camera_id); return resultObjects; } // Sanity check config dimensions if (_config._recInputShape.size() < 3) { LogError("ANSFacialRecognition::Recognize", "Invalid _recInputShape size", camera_id); return resultObjects; } int target_height = _config._recInputShape[1]; int target_width = _config._recInputShape[2]; // Resize if needed if (frame.cols != target_width || frame.rows != target_height) { cv::resize(frame, frame, cv::Size(target_width, target_height)); } // Prepare dummy detection (1 full-frame face box) std::vector outputBbox; Object bbox; bbox.box = cv::Rect(0, 0, std::min(target_width, frame.cols), std::min(target_height, frame.rows)); bbox.confidence = 1.0f; bbox.mask = frame; bbox.cameraId = camera_id; outputBbox.push_back(bbox); // LOCK ONLY DURING RECOGNITION (shared recognizer with GPU buffers) std::vector recognizedFaces; { std::lock_guard lock(_recognitionMutex); recognizedFaces = RecognizeFaces(frame, outputBbox); } resultObjects = UpdateFaceAttributes(recognizedFaces, camera_id); return resultObjects; } catch (const std::exception& e) { LogError("ANSFacialRecognition::Recognize", e.what(), camera_id); } catch (...) { LogError("ANSFacialRecognition::Recognize", "Unknown exception", camera_id); } return std::vector(); } std::vector ANSFacialRecognition::Inference(const cv::Mat& input) { return Inference(input, "FRCAM"); } std::vector ANSFacialRecognition::Inference(const cv::Mat& input, const std::string& camera_id) { START_TIMER(total); std::vector resultObjects; // Early validation checks (read-only, thread-safe) START_TIMER(validate_init); bool init_valid = ValidateInitialization(); END_TIMER(validate_init, "ValidateInitialization Time"); if (!init_valid) { END_TIMER(total, "Total Inference Time (early exit - init)"); return resultObjects; } START_TIMER(validate_input); bool input_valid = ValidateInput(input); END_TIMER(validate_input, "ValidateInput Time"); if (!input_valid) { END_TIMER(total, "Total Inference Time (early exit - input)"); return resultObjects; } START_TIMER(validate_components); bool components_valid = ValidateComponents(); END_TIMER(validate_components, "ValidateComponents Time"); if (!components_valid) { END_TIMER(total, "Total Inference Time (early exit - components)"); return resultObjects; } try { // Prepare input frame (CPU operation, independent per camera) START_TIMER(prepare_frame); cv::Mat frame = PrepareInputFrame(input); END_TIMER(prepare_frame, "PrepareInputFrame Time"); if (frame.empty()) { LogError("ANSFacialRecognition::Inference", "Failed to prepare input frame", camera_id); END_TIMER(total, "Total Inference Time (early exit - empty frame)"); return resultObjects; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { START_TIMER(detection); std::lock_guard lock(_detectionMutex); START_TIMER(detection_actual); detectedFaces = DetectFaces(frame, camera_id); END_TIMER(detection_actual, "Face Detection (actual) Time"); END_TIMER(detection, "Face Detection (with lock) Time"); } if (detectedFaces.empty()) { END_TIMER(total, "Total Inference Time (no faces detected)"); return resultObjects; } // Filter faces (CPU operation, independent per camera) START_TIMER(filter); std::vector validFaces = CheckFaces(detectedFaces, true); END_TIMER(filter, "Filter Faces Time"); if (validFaces.empty()) { END_TIMER(total, "Total Inference Time (no valid faces)"); return resultObjects; } // LOCK ONLY DURING RECOGNITION (shared recognizer with GPU buffers) std::vector recognizedFaces; { START_TIMER(recognition); std::lock_guard lock(_recognitionMutex); START_TIMER(recognition_actual); recognizedFaces = RecognizeFaces(frame, validFaces); END_TIMER(recognition_actual, "Face Recognition (actual) Time"); END_TIMER(recognition, "Face Recognition (with lock) Time"); } // Post-process faces (CPU operation, independent per camera) if (!recognizedFaces.empty()) { START_TIMER(postprocess); resultObjects=UpdateFaceAttributes(recognizedFaces, camera_id); END_TIMER(postprocess, "Update Face Attributes Time"); // Deduplicate: if two faces matched the same userId, keep only the highest confidence // Pass camera data for trackId-based identity persistence to prevent flickering CameraData* camDataPtr = (!camera_id.empty()) ? &GetCameraData(camera_id) : nullptr; ensureUniqueUserIdWithHighestConfidence(resultObjects, camDataPtr); } } catch (const std::exception& e) { LogError("ANSFacialRecognition::Inference", e.what(), camera_id); END_TIMER(total, "Total Inference Time (exception)"); return resultObjects; } catch (...) { LogError("ANSFacialRecognition::Inference", "Unknown exception", camera_id); END_TIMER(total, "Total Inference Time (unknown exception)"); return resultObjects; } END_TIMER(total, "Total Inference Time"); // Fix #9: std::cout removed from production DLL return resultObjects; } std::vector ANSFacialRecognition::Detect(const cv::Mat& input) { return Detect(input, "FRCAM"); } std::vector ANSFacialRecognition::Detect(const cv::Mat& input,const std::string& camera_id) { std::vector resultObjects; // Early validation checks (read-only, thread-safe) if (!ValidateInitialization() || !ValidateInput(input) || !ValidateComponents()) { return resultObjects; } try { // Prepare input frame (CPU operation, independent per camera) cv::Mat frame = PrepareInputFrame(input); if (frame.empty()) { LogError("ANSFacialRecognition::Detect", "Failed to prepare input frame", camera_id); return resultObjects; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { std::lock_guard lock(_detectionMutex); detectedFaces = DetectFaces(frame, camera_id); } if (detectedFaces.empty()) { return resultObjects; } // Filter faces (CPU operation, independent per camera) auto validFaces = CheckFaces(detectedFaces, true); if (validFaces.empty()) { return resultObjects; } // Build face result objects with attributes (CPU/GPU mixed operations) resultObjects = BuildFaceResultObjects(validFaces, input, camera_id); return resultObjects; } catch (const std::exception& e) { LogError("ANSFacialRecognition::Detect", e.what(), camera_id); } catch (...) { LogError("ANSFacialRecognition::Detect", "Unknown exception", camera_id); } return resultObjects; } std::vector ANSFacialRecognition::FaceDetect(const cv::Mat& input) { return FaceDetect(input, "FRCAM"); } std::vector ANSFacialRecognition::FaceDetect(const cv::Mat& input,const std::string& camera_id) { std::vector resultObjects; // Early validation checks (read-only, thread-safe) if (!ValidateInitialization() || !ValidateInput(input)) { return resultObjects; } try { // Prepare input frame (CPU operation, independent per camera) cv::Mat frame = PrepareInputFrame(input); if (frame.empty()) { LogError("ANSFacialRecognition::FaceDetect", "Failed to prepare input frame", camera_id); return resultObjects; } // LOCK ONLY DURING DETECTION (shared detector) std::vector detectedFaces; { std::lock_guard lock(_detectionMutex); detectedFaces = DetectFaces(frame, camera_id); } if (detectedFaces.empty()) { return resultObjects; } // Filter faces (CPU operation, independent per camera) auto validFaces = CheckFaces(detectedFaces); return validFaces; } catch (const std::exception& e) { LogError("ANSFacialRecognition::FaceDetect", e.what(), camera_id); } catch (...) { LogError("ANSFacialRecognition::FaceDetect", "Unknown exception", camera_id); } return resultObjects; } std::vector ANSFacialRecognition::PostProcess(const std::vector& detectedObjects,const std::string& cameraId) { //ScopedTimer("Post Process"); if (!_enableFaceQueue) { return detectedObjects; } // Lock ONLY for queue operations std::lock_guard lock(_faceQueueMutex); // Enqueue current detection EnqueueDetection(detectedObjects, cameraId); // Dequeue and process std::deque> faceQueue = DequeueDetection(cameraId); std::vector faceObjects = FindFrequentFaces(faceQueue,detectedObjects,this->_faceThresholdSize); return faceObjects; } std::string ANSFacialRecognition::PolygonToString(const std::vector& polygon) { if (polygon.empty()) { return ""; } try { std::string result; result.reserve(polygon.size() * 20); char buffer[64]; for (size_t i = 0; i < polygon.size(); ++i) { if (i > 0) { snprintf(buffer, sizeof(buffer), ";%.3f;%.3f", polygon[i].x, polygon[i].y); } else { snprintf(buffer, sizeof(buffer), "%.3f;%.3f", polygon[i].x, polygon[i].y); } result += buffer; } return result; } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::PolygonToString()", e.what(), __FILE__, __LINE__); return ""; } } std::string ANSFacialRecognition::KeypointsToString(const std::vector& kps) { if (kps.empty()) { return ""; } try { // Pre-allocate: each float ~10 chars (e.g., "123.456;") std::string result; result.reserve(kps.size() * 10); char buffer[32]; for (size_t i = 0; i < kps.size(); ++i) { if (i > 0) result += ';'; snprintf(buffer, sizeof(buffer), "%.3f", kps[i]); result += buffer; } return result; } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::KeypointsToString()", e.what(), __FILE__, __LINE__); return ""; } } // Using nlohmann/json (much faster) static std::string DoubleEscapeUnicode(const std::string& utf8Str) { bool hasNonAscii = false; for (unsigned char c : utf8Str) { if (c >= 0x80) { hasNonAscii = true; break; } } if (!hasNonAscii) return utf8Str; std::string result; result.reserve(utf8Str.size() * 2); size_t i = 0; while (i < utf8Str.size()) { unsigned char c = static_cast(utf8Str[i]); if (c < 0x80) { result += utf8Str[i++]; continue; } uint32_t cp = 0; if ((c & 0xE0) == 0xC0 && i + 1 < utf8Str.size()) { cp = ((c & 0x1F) << 6) | (static_cast(utf8Str[i + 1]) & 0x3F); i += 2; } else if ((c & 0xF0) == 0xE0 && i + 2 < utf8Str.size()) { cp = ((c & 0x0F) << 12) | ((static_cast(utf8Str[i + 1]) & 0x3F) << 6) | (static_cast(utf8Str[i + 2]) & 0x3F); i += 3; } else if ((c & 0xF8) == 0xF0 && i + 3 < utf8Str.size()) { cp = ((c & 0x07) << 18) | ((static_cast(utf8Str[i + 1]) & 0x3F) << 12) | ((static_cast(utf8Str[i + 2]) & 0x3F) << 6) | (static_cast(utf8Str[i + 3]) & 0x3F); i += 4; } else { i++; continue; } if (cp <= 0xFFFF) { char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", cp); result += buf; } else { cp -= 0x10000; char buf[16]; snprintf(buf, sizeof(buf), "\\u%04x\\u%04x", 0xD800 + (uint16_t)(cp >> 10), 0xDC00 + (uint16_t)(cp & 0x3FF)); result += buf; } } return result; } std::string ANSFacialRecognition::FaceObjectsToJsonString(const std::vector& faces) { START_TIMER(json_total); try { nlohmann::json root; auto& results = root["results"] = nlohmann::json::array(); START_TIMER(json_build); for (const auto& face : faces) { results.push_back({ {"user_id", face.userId}, {"user_name", DoubleEscapeUnicode(face.userName)}, {"similarity", std::to_string(face.similarity)}, {"is_unknown", std::to_string(face.isUnknown)}, {"prob", std::to_string(face.confidence)}, {"x", std::to_string(face.box.x)}, {"y", std::to_string(face.box.y)}, {"width", std::to_string(face.box.width)}, {"height", std::to_string(face.box.height)}, {"extra_info", face.extraInformation}, {"camera_id", face.cameraId}, {"track_id", std::to_string(face.trackId)}, {"polygon", PolygonToString(face.polygon)}, {"kps", KeypointsToString(face.kps)} }); } END_TIMER(json_build, "JSON Build Time"); START_TIMER(json_serialize); std::string result = root.dump(); END_TIMER(json_serialize, "JSON Serialize Time"); END_TIMER(json_total, "JSON Conversion Total Time"); return result; } catch (const std::exception& e) { END_TIMER(json_total, "JSON Conversion Time (error)"); LogThreadSafe("ANSFacialRecognition::FaceObjectsToJsonString", e.what()); return R"({"results":[],"error":"Serialization failed"})"; } } std::string ANSFacialRecognition::FaceToJsonString(const std::vector& dets) { nlohmann::json root; nlohmann::json detectedObjects = nlohmann::json::array(); for (const auto& det : dets) { nlohmann::json detectedNode; detectedNode["class_id"] = std::to_string(det.classId); detectedNode["track_id"] = std::to_string(det.trackId); detectedNode["class_name"] = DoubleEscapeUnicode(det.className); detectedNode["prob"] = std::to_string(det.confidence); detectedNode["x"] = std::to_string(det.box.x); detectedNode["y"] = std::to_string(det.box.y); detectedNode["width"] = std::to_string(det.box.width); detectedNode["height"] = std::to_string(det.box.height); detectedNode["mask"] = ""; // TODO: convert masks to comma separated string detectedNode["extra_info"] = det.extraInfo; detectedNode["camera_id"] = det.cameraId; detectedNode["polygon"] = PolygonToString(det.polygon); detectedNode["kps"] = KeypointsToString(det.kps); detectedObjects.push_back(detectedNode); } root["results"] = detectedObjects; return root.dump(); } // Validation helper methods bool ANSFacialRecognition::ValidateInitialization() { //ScopedTimer("Validate Initialization"); if (!_isInitialized) { _logger.LogError("ANSFacialRecognition::Inference()", "Models are not initialized.", __FILE__, __LINE__); return false; } return true; } bool ANSFacialRecognition::ValidateInput(const cv::Mat& input) { //ScopedTimer("ValidateInput"); constexpr int MIN_DIMENSION = 10; if (input.empty() || input.cols < MIN_DIMENSION || input.rows < MIN_DIMENSION) { _logger.LogFatal("ANSFacialRecognition::Inference()", "Input image is invalid or too small", __FILE__, __LINE__); return false; } return true; } bool ANSFacialRecognition::ValidateComponents() { //ScopedTimer("ValidateComponents"); if (!_detector) { _logger.LogFatal("ANSFacialRecognition::Inference()", "_detector is null", __FILE__, __LINE__); return false; } if (!_recognizer) { _logger.LogFatal("ANSFacialRecognition::Inference()", "_recognizer is null", __FILE__, __LINE__); return false; } return true; } // Frame preparation cv::Mat ANSFacialRecognition::PrepareInputFrame(const cv::Mat& input) { // If input is already BGR and you don't modify it downstream if (input.channels() == 3) { return input; // Just return reference (no copy!) } // Only convert if grayscale cv::Mat frame; cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR); return frame; } void ANSFacialRecognition::ensureUniqueUserIdWithHighestConfidence(std::vector& resultObjects, CameraData* camData) { if (resultObjects.empty()) { return; } auto isUnknownId = [](const std::string& id) { return id == "0000" || id == "0" || id.empty(); }; if (camData) { auto& identities = camData->trackIdentities; // Step 1: Build map of userId → candidate face indices (before modifying anything) std::unordered_map> userIdCandidates; for (size_t i = 0; i < resultObjects.size(); ++i) { if (isUnknownId(resultObjects[i].userId)) continue; userIdCandidates[resultObjects[i].userId].push_back(i); } // Step 2: Resolve duplicate userIds using accumulated scores for (auto& [uid, indices] : userIdCandidates) { if (indices.size() <= 1) continue; // Find the candidate with the highest accumulated score for this userId size_t winner = indices[0]; float bestScore = 0.0f; for (size_t idx : indices) { int tid = resultObjects[idx].trackId; float score = resultObjects[idx].confidence; // fallback for new trackIds auto it = identities.find(tid); if (it != identities.end() && it->second.userId == uid) { // Use accumulated score + this frame's confidence score = it->second.accumulatedScore + resultObjects[idx].confidence; } if (score > bestScore) { bestScore = score; winner = idx; } } // Mark all non-winners as unknown for (size_t idx : indices) { if (idx != winner) { MarkAsUnknown(resultObjects[idx]); } } } // Step 3: Update accumulated scores after dedup. // - Winners: add this frame's confidence to their score // - Losers (marked Unknown by dedup): decay their score (×0.8) // so they gradually lose claim but don't reset instantly // (avoids the alternating-win problem where neither can accumulate) // - Losers that decay below a threshold are erased entirely constexpr float DECAY_FACTOR = 0.8f; constexpr float MIN_SCORE = 0.1f; for (auto& r : resultObjects) { int tid = r.trackId; if (isUnknownId(r.userId)) { // Lost dedup or genuinely unknown — decay existing score auto it = identities.find(tid); if (it != identities.end()) { it->second.accumulatedScore *= DECAY_FACTOR; if (it->second.accumulatedScore < MIN_SCORE) { identities.erase(it); } } continue; } auto it = identities.find(tid); if (it != identities.end()) { if (it->second.userId == r.userId) { // Same identity as before and won dedup — accumulate it->second.accumulatedScore += r.confidence; } else { // Different identity — start fresh it->second.userId = r.userId; it->second.accumulatedScore = r.confidence; } } else { // New trackId — initialize identities[tid] = { r.userId, r.confidence }; } } // Step 3: Clean up trackIds no longer in the scene std::unordered_set activeTrackIds; for (const auto& r : resultObjects) { activeTrackIds.insert(r.trackId); } for (auto it = identities.begin(); it != identities.end(); ) { if (activeTrackIds.find(it->first) == activeTrackIds.end()) { it = identities.erase(it); } else { ++it; } } } else { // No camera data: fall back to simple confidence-based dedup std::unordered_map highestConfidenceIndices; highestConfidenceIndices.reserve(resultObjects.size()); for (size_t i = 0; i < resultObjects.size(); ++i) { const std::string& userId = resultObjects[i].userId; if (isUnknownId(userId)) continue; auto it = highestConfidenceIndices.find(userId); if (it == highestConfidenceIndices.end()) { highestConfidenceIndices[userId] = i; } else { size_t existingIndex = it->second; if (resultObjects[i].confidence > resultObjects[existingIndex].confidence) { MarkAsUnknown(resultObjects[existingIndex]); highestConfidenceIndices[userId] = i; } else { MarkAsUnknown(resultObjects[i]); } } } } } std::vector ANSFacialRecognition::CheckFaces(const std::vector& faceObjects,bool checkFaceSize) { // NO LOCK NEEDED - Pure filtering operation on local data std::vector validFaceObjects; validFaceObjects.reserve(faceObjects.size()); try { for (const auto& face : faceObjects) { // Skip faces smaller than minimum size if checking is enabled if (checkFaceSize && (face.box.width <= _minFaceSize || face.box.height <= _minFaceSize)) { continue; } validFaceObjects.push_back(face); } } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::CheckFaces", e.what()); } return validFaceObjects; } // Pipeline stage methods with timing std::vector ANSFacialRecognition::DetectFaces(const cv::Mat& frame, const std::string& camera_id) { //ScopedTimer timer("Face Detection"); return _detector->RunInference(frame, camera_id, true, _enableHeadPose, _enableFaceliveness); } std::vector ANSFacialRecognition::RecognizeFaces(const cv::Mat& frame, const std::vector& validFaces) { // Hold shared lock during the entire Match operation to prevent // Reload() from swapping the index while we're using it. // Fix #6: Pass _userDict by const& — shared_lock guarantees it won't // be modified during Match (only unique_lock in Reload can write). std::shared_lock lock(_indexSwapMutex); return _recognizer->Match(frame, validFaces, _userDict); } std::string ANSFacialRecognition::GetOpenVINODevice() { ov::Core core; std::vector available_devices = core.get_available_devices(); // Prioritize devices: NPU > GPU > CPU std::vector priority_devices = { "NPU","GPU","CPU" }; for (const auto& device : priority_devices) { if (std::find(available_devices.begin(), available_devices.end(), device) != available_devices.end()) { return device; // Return the first available device based on priority } } return "CPU"; } std::vector ANSFacialRecognition::UpdateFaceAttributes( const std::vector& resultObjects, const std::string& camera_id) { std::vector updatedResults; try { const size_t faceCount = resultObjects.size(); if (faceCount == 0) return updatedResults; // Determine if this is a full-attribute frame or head-pose-only frame bool runSlowAttributes = true; CameraData* camDataPtr = nullptr; if (!camera_id.empty() && _attributeInterval > 1) { camDataPtr = &GetCameraData(camera_id); // ── Adaptive interval (#5): adjust based on scene stability ── // Count current trackIds int currentTrackIdCount = static_cast(faceCount); if (currentTrackIdCount == camDataPtr->previousTrackIdCount) { camDataPtr->stableFrameCount++; } else { camDataPtr->stableFrameCount = 0; } camDataPtr->previousTrackIdCount = currentTrackIdCount; // Adapt interval: stable scene → increase (max 10), changes → decrease (min 2) if (camDataPtr->stableFrameCount > 30) { camDataPtr->currentAttributeInterval = std::min(10, _attributeInterval * 2); } else if (camDataPtr->stableFrameCount > 10) { camDataPtr->currentAttributeInterval = _attributeInterval; } else { camDataPtr->currentAttributeInterval = std::max(2, _attributeInterval / 2); } camDataPtr->attributeFrameCounter++; if (camDataPtr->attributeFrameCounter >= camDataPtr->currentAttributeInterval) { camDataPtr->attributeFrameCounter = 0; runSlowAttributes = true; } else { runSlowAttributes = false; } } // Pre-allocate per-face working data std::vector results(resultObjects.begin(), resultObjects.end()); std::vector masks(faceCount); std::vector maskValid(faceCount, false); std::vector skipAttributes(faceCount, false); std::vector runAgeGender(faceCount, false); std::vector runEmotions(faceCount, false); std::vector runHeadPose(faceCount, false); std::vector useCachedAttrs(faceCount, false); std::vector ageGenderResults(faceCount, AgeGenderResults{}); std::vector> emotionsResults(faceCount); std::vector headPoseResults(faceCount, HeadPoseResults{}); std::vector faces; faces.reserve(faceCount); START_TIMER(attr_phase1); // ---- Phase 1: Setup + antispoof (sequential) ---- for (size_t i = 0; i < faceCount; i++) { cv::Rect faceBox = resultObjects[i].box; faces.emplace_back(i, faceBox); Face& face = faces[i]; face.ageGenderEnable(this->_enableAgeGender && _ageGenderDetector && _ageGenderDetector->enabled()); face.emotionsEnable(this->_enableFaceEmotions && _emotionsDetector && _emotionsDetector->enabled()); face.headPoseEnable(this->_enableHeadPose && _headPoseDetector && _headPoseDetector->enabled()); face.antispoofingEnable(this->_enableAntiSpoof && _antiSpoofDetector && _antiSpoofDetector->enabled()); face.faceLivenessEnable(this->_enableFaceliveness); // Clone mask once per face masks[i] = (!results[i].mask.empty()) ? resultObjects[i].mask.clone() : cv::Mat(); maskValid[i] = !masks[i].empty() && masks[i].cols >= 5 && masks[i].rows >= 5; // Check if we can use cached attributes for this face bool hasCachedAttrs = false; if (!runSlowAttributes && camDataPtr) { auto it = camDataPtr->cachedAttributes.find(resultObjects[i].trackId); if (it != camDataPtr->cachedAttributes.end()) { hasCachedAttrs = true; } } // Determine if slow attributes should run for this face bool runSlowForThisFace = runSlowAttributes || !hasCachedAttrs; bool fakeFaceDetected = false; if (face.isFaceLivenessEnabled() && !results[i].extraInformation.empty()) { const std::string& livenessResult = results[i].extraInformation; if (livenessResult == "real") { face.updateFaceLiveness(1); } else if (livenessResult == "fake") { face.updateFaceLiveness(0); fakeFaceDetected = true; } } // Antispoof: only on full-attribute frames or new faces if (runSlowForThisFace && !fakeFaceDetected && face.isAntispoofingEnabled() && maskValid[i]) { float confidence; { std::lock_guard lock(_antispoofMutex); confidence = _antiSpoofDetector->runInfer(masks[i]); } face.updateRealFaceConfidence(confidence); if (!face.isReal()) fakeFaceDetected = true; } if (_removeFakeFaces && fakeFaceDetected) { skipAttributes[i] = true; MarkAsUnknown(results[i]); } // Skip attribute inference for faces below minimum size threshold if (!skipAttributes[i] && (resultObjects[i].box.width < _minFaceSize || resultObjects[i].box.height < _minFaceSize)) { skipAttributes[i] = true; MarkAsUnknown(results[i]); } // Slow attributes: only on attribute frames or new faces runAgeGender[i] = !skipAttributes[i] && runSlowForThisFace && face.isAgeGenderEnabled() && maskValid[i]; runEmotions[i] = !skipAttributes[i] && runSlowForThisFace && face.isEmotionsEnabled() && maskValid[i]; // Head pose: ALWAYS runs (every frame) runHeadPose[i] = !skipAttributes[i] && face.isHeadPoseEnabled() && maskValid[i]; // Track whether to use cache for output useCachedAttrs[i] = !skipAttributes[i] && !runSlowForThisFace && hasCachedAttrs; } END_TIMER(attr_phase1, " [Attr] Phase1 (setup+antispoof)"); // ---- Phase 2: Run attribute models sequentially ---- // On cached frames: only head pose runs. AgeGender + Emotions are skipped. START_TIMER(attr_agegender); { std::lock_guard lock(_ageGenderMutex); for (size_t i = 0; i < faceCount; i++) { if (!runAgeGender[i]) continue; ageGenderResults[i] = _ageGenderDetector->runInfer(masks[i]); } } END_TIMER(attr_agegender, " [Attr] AgeGender (all faces)"); START_TIMER(attr_emotions); { std::lock_guard lock(_emotionsMutex); for (size_t i = 0; i < faceCount; i++) { if (!runEmotions[i]) continue; emotionsResults[i] = _emotionsDetector->runInfer(masks[i]); } } END_TIMER(attr_emotions, " [Attr] Emotions (all faces)"); // Head pose: ALWAYS runs (every frame, real-time accuracy) START_TIMER(attr_headpose); { std::lock_guard lock(_headPoseMutex); for (size_t i = 0; i < faceCount; i++) { if (!runHeadPose[i]) continue; headPoseResults[i] = _headPoseDetector->runInfer(masks[i]); } } END_TIMER(attr_headpose, " [Attr] HeadPose (all faces)"); // ---- Phase 3: Collect results and build output ---- for (size_t i = 0; i < faceCount; i++) { if (skipAttributes[i]) continue; Face& face = faces[i]; FaceResultObject& resultObject = results[i]; if (useCachedAttrs[i] && camDataPtr) { // Cached frame: use cached JSON but splice in fresh head pose auto& cached = camDataPtr->cachedAttributes[resultObjects[i].trackId]; if (runHeadPose[i]) { // Check extreme head pose → mark unknown if (std::abs(headPoseResults[i].angle_y) > 60 || std::abs(headPoseResults[i].angle_r) > 60 || std::abs(headPoseResults[i].angle_p) > 60) { MarkAsUnknown(resultObject); } // Replace Pose(r,p,y) in cached JSON with fresh values std::string json = cached.attributeJson; std::string newPose = "(" + std::to_string(static_cast(headPoseResults[i].angle_r)) + ", " + std::to_string(static_cast(headPoseResults[i].angle_p)) + ", " + std::to_string(static_cast(headPoseResults[i].angle_y)) + ")"; // Find and replace the pose value in the JSON size_t poseKeyPos = json.find("\"Pose(r,p,y)\""); if (poseKeyPos != std::string::npos) { size_t valueStart = json.find("\"(", poseKeyPos + 13); size_t valueEnd = json.find(")\"", valueStart + 1); if (valueStart != std::string::npos && valueEnd != std::string::npos) { json.replace(valueStart + 1, valueEnd - valueStart, newPose); } } resultObject.extraInformation = json; } else { resultObject.extraInformation = cached.attributeJson; } if (cached.isUnknown) MarkAsUnknown(resultObject); } else { // Full attribute frame: build from scratch if (runAgeGender[i]) { face.updateGender(ageGenderResults[i].maleProb); face.updateAge(ageGenderResults[i].age); } if (runEmotions[i]) { face.updateEmotions(emotionsResults[i]); } if (runHeadPose[i]) { face.updateHeadPose(headPoseResults[i]); if (std::abs(headPoseResults[i].angle_y) > 60 || std::abs(headPoseResults[i].angle_r) > 60 || std::abs(headPoseResults[i].angle_p) > 60) { MarkAsUnknown(resultObject); } } std::string faceAtt = ANSFRHelper::FaceAttributeToJsonString(face); resultObject.extraInformation = faceAtt; // Cache for future frames if (camDataPtr) { CachedFaceAttributes& cache = camDataPtr->cachedAttributes[resultObjects[i].trackId]; cache.attributeJson = faceAtt; cache.isUnknown = resultObject.isUnknown; } } updatedResults.push_back(resultObject); } // Clean up stale cache entries (trackIds not seen this frame) if (camDataPtr && runSlowAttributes) { std::unordered_set activeTrackIds; for (const auto& r : resultObjects) activeTrackIds.insert(r.trackId); for (auto it = camDataPtr->cachedAttributes.begin(); it != camDataPtr->cachedAttributes.end(); ) { if (activeTrackIds.find(it->first) == activeTrackIds.end()) it = camDataPtr->cachedAttributes.erase(it); else ++it; } } return updatedResults; } catch (const std::bad_alloc& e) { LogThreadSafe("ANSFacialRecognition::updateFaceAttributes", std::string("Memory allocation failed: ") + e.what()); return updatedResults; } catch (const std::exception& e) { LogThreadSafe("ANSFacialRecognition::updateFaceAttributes", e.what()); return updatedResults; } } std::vector ANSFacialRecognition::BuildFaceResultObjects(const std::vector& validFaces,const cv::Mat& originalInput,const std::string& camera_id) { std::vector resultObjects; resultObjects.reserve(validFaces.size()); for (size_t i = 0; i < validFaces.size(); ++i) { const Object& faceObj = validFaces[i]; // Create face attribute object cv::Rect rect = faceObj.box; Face face(i, rect); // Enable attributes based on configuration face.ageGenderEnable(_enableAgeGender && _ageGenderDetector && _ageGenderDetector->enabled()); face.emotionsEnable(_enableFaceEmotions && _emotionsDetector && _emotionsDetector->enabled()); face.headPoseEnable(_enableHeadPose && _headPoseDetector && _headPoseDetector->enabled()); face.antispoofingEnable(_enableAntiSpoof && _antiSpoofDetector && _antiSpoofDetector->enabled()); // Process face attributes if mask is valid if (!faceObj.mask.empty() && faceObj.mask.cols > 4 && faceObj.mask.rows > 4) { ProcessFaceAttributes(face, faceObj.mask); } // Build result object FaceResultObject result; result.isUnknown = true; result.userId = "0"; result.userName = "Unknown"; result.similarity = 0.0f; result.confidence = 1.0f; result.cameraId = camera_id; result.trackId = faceObj.trackId; // Clamp bounding box to image boundaries result.box.x = std::max(0.0f, static_cast(faceObj.box.x)); result.box.y = std::max(0.0f, static_cast(faceObj.box.y)); result.box.width = std::min( static_cast(originalInput.cols) - result.box.x, static_cast(faceObj.box.width) ); result.box.height = std::min( static_cast(originalInput.rows) - result.box.y, static_cast(faceObj.box.height) ); // Copy face data result.mask = faceObj.mask; result.polygon = faceObj.polygon; result.kps = faceObj.kps; // Serialize face attributes to JSON result.extraInformation = ANSFRHelper::FaceAttributeToJsonString(face); resultObjects.push_back(result); } return resultObjects; } void ANSFacialRecognition::ProcessFaceAttributes(Face& face,const cv::Mat& mask) { // Age & Gender if (face.isAgeGenderEnabled()) { AgeGenderResults ageGenderResult; { std::lock_guard lock(_ageGenderMutex); ageGenderResult = _ageGenderDetector->runInfer(mask); } face.updateGender(ageGenderResult.maleProb); face.updateAge(ageGenderResult.age); } // Emotions if (face.isEmotionsEnabled()) { std::map emotions; { std::lock_guard lock(_emotionsMutex); emotions = _emotionsDetector->runInfer(mask); } face.updateEmotions(emotions); } // Head Pose if (face.isHeadPoseEnabled()) { HeadPoseResults headPoseResult; { std::lock_guard lock(_headPoseMutex); headPoseResult = _headPoseDetector->runInfer(mask); } face.updateHeadPose(headPoseResult); } // Anti-Spoofing if (face.isAntispoofingEnabled()) { float confidence; { std::lock_guard lock(_antispoofMutex); confidence = _antiSpoofDetector->runInfer(mask); } face.updateRealFaceConfidence(confidence); } } void ANSFacialRecognition::MarkAsUnknown(FaceResultObject& face) { face.userId = "0000"; face.isUnknown = true; face.userName = "Unknown"; face.confidence = 1.0; } void ANSFacialRecognition::EnqueueDetection(const std::vector& detectedObjects, const std::string& cameraId) { try { CameraData& camera = GetCameraData(cameraId);// Retrieve camera data if (camera._detectionQueue.size() == this->_queueSize) { camera._detectionQueue.pop_front(); // Remove the oldest element if the queue is full } camera._detectionQueue.push_back(detectedObjects); // Add the new detection, even if empty } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::EnqueueDetection()", e.what(), __FILE__, __LINE__); } } std::deque>ANSFacialRecognition::DequeueDetection(const std::string& cameraId) { try { CameraData& camera = GetCameraData(cameraId);// Retrieve camera data return camera._detectionQueue; } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::DequeueDetection()", e.what(), __FILE__, __LINE__); return std::deque>(); } } Object ANSFacialRecognition::GetLargestObject(const std::vector& objects) { try { if (!objects.empty()) { // Find the object with the largest size auto largestObjectIt = std::max_element(objects.begin(), objects.end(), [](const Object& a, const Object& b) { return (a.box.width * a.box.height) < (b.box.width * b.box.height); }); return *largestObjectIt; // Return the largest object } return Object(); } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::GetLargestObject()", e.what(), __FILE__, __LINE__); return Object(); } } bool ANSFacialRecognition::AreFacesSimilar(const FaceResultObject& face1, const FaceResultObject& face2) { // Check if both faces belong to the same camera and user return face1.cameraId == face2.cameraId && face1.userId == face2.userId && face1.userName == face2.userName; } // Helper function to generate a hash for a face based on AreFacesSimilar size_t ANSFacialRecognition::GenerateFaceHash(const FaceResultObject & face, const std::vector&detectedObjects) { for (size_t i = 0; i < detectedObjects.size(); ++i) { if (AreFacesSimilar(face, detectedObjects[i])) { return i; // Use index of `detectedObjects` as the hash key } } return static_cast(-1); // Invalid index if not similar } std::vector ANSFacialRecognition::FindFrequentFaces(const std::deque>&faceQueue, const std::vector&detectedObjects, int occurrenceThreshold) { try { // Map to count occurrences of each detected face using detectedObjects as keys std::unordered_map faceCount; // Iterate over the face queue to count occurrences for (const auto& faceArray : faceQueue) { for (const auto& face : faceArray) { if (face.userName != "Unknown") { // Skip checking "Unknown" faces size_t hash = GenerateFaceHash(face, detectedObjects); if (hash != static_cast(-1)) { // Valid hash faceCount[hash]++; } } } } // Collect frequent faces from detectedObjects std::vector frequentFaces; for (size_t i = 0; i < detectedObjects.size(); ++i) { const auto& detectedFace = detectedObjects[i]; if (detectedFace.userName == "Unknown") { // Add "Unknown" faces directly without checking frequentFaces.push_back(detectedFace); } else if (faceCount[i] >= occurrenceThreshold) { // Add frequent known faces frequentFaces.push_back(detectedFace); } } return frequentFaces; } catch (const std::exception& e) { this->_logger.LogFatal("ANSFacialRecognition::FindFrequentFaces()", e.what(), __FILE__, __LINE__); return std::vector(); } } // Helper methods with proper thread safety void ANSFacialRecognition::LogError(const std::string& function, const std::string& message, const std::string& camera_id) { std::ostringstream oss; oss << "Camera " << camera_id << ": " << message; std::lock_guard lock(_loggerMutex); _logger.LogError(function, oss.str(), __FILE__, __LINE__); } // Helper methods void ANSFacialRecognition::LogThreadSafe(const std::string& function, const std::string& message, LogLevel level) { std::lock_guard lock(_loggerMutex); switch (level) { case LogLevel::Debug: _logger.LogDebug(function, message, __FILE__, __LINE__); break; case LogLevel::Info: _logger.LogDebug(function, message, __FILE__, __LINE__); break; case LogLevel::Warn: _logger.LogWarn(function, message, __FILE__, __LINE__); break; case LogLevel::Fatal: _logger.LogFatal(function, message, __FILE__, __LINE__); break; case LogLevel::Error: default: _logger.LogError(function, message, __FILE__, __LINE__); break; } } }; // Working version //std::vector ANSFacialRecognition::Inference( // const cv::Mat& input, // const std::string& camera_id) //{ // std::lock_guard lock(_mutex); // std::vector resultObjects; // // // Early validation checks // if (!ValidateInitialization() || !ValidateInput(input) || !ValidateComponents()) { // return resultObjects; // } // // try { // // Start total timing // auto t_total_start = std::chrono::high_resolution_clock::now(); // // // Prepare input frame // auto t_prepare_start = std::chrono::high_resolution_clock::now(); // cv::Mat frame = PrepareInputFrame(input); // auto t_prepare_end = std::chrono::high_resolution_clock::now(); // float prepare_ms = std::chrono::duration(t_prepare_end - t_prepare_start).count(); // // if (frame.empty()) { // _logger.LogFatal("ANSFacialRecognition::Inference()", // "Failed to prepare input frame", __FILE__, __LINE__); // return resultObjects; // } // // // Detect faces // auto t_detect_start = std::chrono::high_resolution_clock::now(); // auto detectedFaces = DetectFaces(frame, camera_id); // auto t_detect_end = std::chrono::high_resolution_clock::now(); // float detect_ms = std::chrono::duration(t_detect_end - t_detect_start).count(); // // if (detectedFaces.empty()) { // _logger.LogDebug("ANSFacialRecognition::Inference()", // "No faces detected", __FILE__, __LINE__); // return resultObjects; // } // // // Filter faces // auto t_filter_start = std::chrono::high_resolution_clock::now(); // auto validFaces = FilterFaces(detectedFaces); // auto t_filter_end = std::chrono::high_resolution_clock::now(); // float filter_ms = std::chrono::duration(t_filter_end - t_filter_start).count(); // // if (validFaces.empty()) { // _logger.LogDebug("ANSFacialRecognition::Inference()", // "No valid faces after filtering", __FILE__, __LINE__); // return resultObjects; // } // // // Recognize faces (BATCH INFERENCE HAPPENS HERE) // auto t_recognize_start = std::chrono::high_resolution_clock::now(); // resultObjects = RecognizeFaces(frame, validFaces); // auto t_recognize_end = std::chrono::high_resolution_clock::now(); // float recognize_ms = std::chrono::duration(t_recognize_end - t_recognize_start).count(); // // // Post-process faces // auto t_postproc_start = std::chrono::high_resolution_clock::now(); // PostProcessFaces(resultObjects); // auto t_postproc_end = std::chrono::high_resolution_clock::now(); // float postproc_ms = std::chrono::duration(t_postproc_end - t_postproc_start).count(); // // // Apply head pose filtering if enabled // float headpose_ms = 0.0f; // if (_enableHeadPose) { // auto t_headpose_start = std::chrono::high_resolution_clock::now(); // resultObjects = PostProcessDetection(resultObjects, camera_id); // auto t_headpose_end = std::chrono::high_resolution_clock::now(); // headpose_ms = std::chrono::duration(t_headpose_end - t_headpose_start).count(); // } // // // Calculate total time // auto t_total_end = std::chrono::high_resolution_clock::now(); // float total_ms = std::chrono::duration(t_total_end - t_total_start).count(); // // // Calculate measured sum // float measured_sum = prepare_ms + detect_ms + filter_ms + recognize_ms + postproc_ms + headpose_ms; // float unaccounted_ms = total_ms - measured_sum; // // // Detailed logging // _logger.LogDebug("ANSFacialRecognition::Inference()", // "=== Timing Breakdown (Faces: " + std::to_string(validFaces.size()) + ") ===\n" // " Prepare Frame: " + std::to_string(prepare_ms) + " ms\n" // " Face Detection: " + std::to_string(detect_ms) + " ms\n" // " Face Filter: " + std::to_string(filter_ms) + " ms\n" // " Face Recognition: " + std::to_string(recognize_ms) + " ms (" + // std::to_string(recognize_ms / validFaces.size()) + " ms/face)\n" // " Post-Process: " + std::to_string(postproc_ms) + " ms\n" // " Head Pose: " + std::to_string(headpose_ms) + " ms\n" // " --------------------------------\n" // " Measured Sum: " + std::to_string(measured_sum) + " ms\n" // " Actual Total: " + std::to_string(total_ms) + " ms\n" // " Unaccounted: " + std::to_string(unaccounted_ms) + " ms\n" // " ================================", // __FILE__, __LINE__); // // return resultObjects; // } // catch (const std::exception& e) { // _logger.LogFatal("ANSFacialRecognition::Inference()", // e.what(), __FILE__, __LINE__); // } // catch (...) { // _logger.LogFatal("ANSFacialRecognition::Inference()", // "Unknown exception occurred", __FILE__, __LINE__); // } // return resultObjects; //} // Old code // std::vector ANSFacialRecognition::Detect(const cv::Mat& input, const std::string& camera_id) { // std::lock_guard lock(_mutex); // if (!_isInitialized) { // std::vector _outputBbox; // _outputBbox.clear(); // std::vector resultObjects; // this->_logger.LogError("ANSFacialRecognition::Detect()", "Models are not initialized.", __FILE__, __LINE__); // return resultObjects; // } // try { // std::vector _rawOutputBbox; // std::vector _outputBbox; // std::vector resultObjects; // resultObjects.clear(); // if (input.empty()) { // this->_logger.LogFatal("ANSFacialRecognition::Detect()", "image frame is empty", __FILE__, __LINE__); // return resultObjects; // } // if ((input.cols < 10) || (input.rows < 10)) return resultObjects; // // Convert grayscale images to 3-channel BGR if needed // cv::Mat frame; // if (input.channels() == 1) { // cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR); // } // else { // frame = input.clone(); // } // _rawOutputBbox = _detector->RunInference(frame,camera_id); // if (_rawOutputBbox.empty()) { // return resultObjects; // } // // For every detected face // for (size_t i = 0; i < _rawOutputBbox.size(); i++) { // cv::Rect rect = _rawOutputBbox[i].box; // Face face(i, rect); // face.ageGenderEnable(this->_enableAgeGender && _ageGenderDetector->enabled()); // face.emotionsEnable(this->_enableFaceEmotions && _emotionsDetector->enabled()); // face.headPoseEnable(this->_enableHeadPose && _headPoseDetector->enabled()); // face.antispoofingEnable(this->_enableAntiProof && _antisproofDetector->enabled()); // if (face.isAgeGenderEnabled()) { // if (!_rawOutputBbox[i].mask.empty()) { // cv::Mat mask = _rawOutputBbox[i].mask.clone(); // if (mask.empty()) continue; // if ((mask.cols < 5) && (mask.rows < 5)) continue; // AgeGenderResults ageGenderResult = _ageGenderDetector->runInfer(mask); // face.updateGender(ageGenderResult.maleProb); // face.updateAge(ageGenderResult.age); // mask.release(); // } // } // if (face.isEmotionsEnabled()) { // if (!_rawOutputBbox[i].mask.empty()) { // cv::Mat mask = _rawOutputBbox[i].mask.clone(); // if (mask.empty()) continue; // if ((mask.cols < 5) && (mask.rows < 5)) continue; // std::map emotions = _emotionsDetector->runInfer(mask); // face.updateEmotions(emotions); // mask.release(); // } // } // if (face.isHeadPoseEnabled()) { // if (!_rawOutputBbox[i].mask.empty()) { // cv::Mat mask = _rawOutputBbox[i].mask.clone(); // if (mask.empty()) continue; // if ((mask.cols < 5) && (mask.rows < 5)) continue; // HeadPoseResults headPoseResult = _headPoseDetector->runInfer(mask); // face.updateHeadPose(headPoseResult); // mask.release(); // } // } // if (face.isAntispoofingEnabled()) { // if (!_rawOutputBbox[i].mask.empty()) { // cv::Mat mask = _rawOutputBbox[i].mask.clone(); // if (mask.empty()) continue; // if ((mask.cols < 5) && (mask.rows < 5)) continue; // float confidence = _antisproofDetector->runInfer(mask); // face.updateRealFaceConfidence(confidence); // mask.release(); // } // } // // convert face into string to add-into extra information // bool isUnknown; // isUnknown = false; // FaceResultObject resultObject; // resultObject.isUnknown = isUnknown; // resultObject.userId = "0000"; // resultObject.userName = "Unknown"; // resultObject.similarity = 0; // resultObject.confidence = 1.0; // float x = _rawOutputBbox[i].box.x; // float y = _rawOutputBbox[i].box.y; // float w = _rawOutputBbox[i].box.width;; // float h = _rawOutputBbox[i].box.height; // resultObject.box.x = x; // resultObject.box.y = y; // resultObject.box.width = w; // resultObject.box.height = h; // resultObject.box.x = MAX(0.0f, resultObject.box.x); // resultObject.box.y = MAX(0.0f, resultObject.box.y); // resultObject.box.width = MIN((float)input.cols - resultObject.box.x, resultObject.box.width); // resultObject.box.height = MIN((float)input.rows - resultObject.box.y, resultObject.box.height); // resultObject.mask = _rawOutputBbox[i].mask; // resultObject.polygon = _rawOutputBbox[i].polygon; // resultObject.kps = _rawOutputBbox[i].kps; // std::string faceAtt = ANSFRHelper::FaceAttributeToJsonString(face); // resultObject.extraInformation = faceAtt;//coversion from face to string // resultObject.cameraId = camera_id; // resultObject.trackId = _rawOutputBbox[i].trackId; // resultObjects.push_back(resultObject); // } // frame.release(); // _outputBbox.clear(); // return PostProcessDetection(resultObjects, camera_id); // } // catch (std::exception& e) { // this->_logger.LogFatal("ANSFacialRecognition::Detect()", e.what(), __FILE__, __LINE__); // std::vector resultObjects; // resultObjects.clear(); // return resultObjects; // } // } // std::vector ANSFacialRecognition::Inference(const cv::Mat& input, const std::string& camera_id) { // std::lock_guard lock(_mutex); // if (!_isInitialized) { // std::vector _outputBbox; // _outputBbox.clear(); // std::vector resultObjects; // this->_logger.LogError("ANSFacialRecognition::Inference()", "Models are not initialized.", __FILE__, __LINE__); // return resultObjects; // } // try { // std::vector resultObjects; // std::vector _outputBbox; // if (input.empty()) { // this->_logger.LogFatal("ANSFacialRecognition::Inference()", "image frame is empty", __FILE__, __LINE__); // return resultObjects; // } // if ((input.cols < 10) || (input.rows < 10)) return resultObjects; // // Convert grayscale images to 3-channel BGR if needed // cv::Mat frame; // if (input.channels() == 1) { // cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR); // } // else { // frame = input.clone(); // } // _outputBbox = _detector->RunInference(frame,camera_id); // if (_outputBbox.empty()) { // return resultObjects; // } // // Filter out the faces that are too small // std::vector validFaces=checkFaces(_outputBbox, true); // resultObjects = _recognizer->Match(frame, validFaces, _userDict); // updateFaceAttributes(resultObjects); // ensureUniqueUserIdWithHighestConfidence(resultObjects); // frame.release(); // return PostProcessDetection(resultObjects, camera_id); // } // catch (std::exception& e) { // this->_logger.LogFatal("ANSFacialRecognition::Inference()", e.what(), __FILE__, __LINE__); // return std::vector(); // } // } //int ANSFacialRecognition::GetUser(int userId, UserRecord& userRecord) { // try { // // Initialize user ID // userRecord.UserId = userId; // // LOCK ONLY DURING DATABASE ACCESS // { // std::lock_guard lock(_databaseMutex); // // Get user basic information // int result = _db->GetUser(userId, userRecord.UserCode, userRecord.UserName); // if (result <= 0) { // LogThreadSafe("ANSFacialRecognition::GetUser", // "User ID " + std::to_string(userId) + " not found in database"); // return 0; // } // // Get user's face IDs // result = _db->GetFaceIdsByUser(userId, userRecord.FaceIds); // if (result <= 0) { // LogThreadSafe("ANSFacialRecognition::GetUser", // "No faces found for user ID " + std::to_string(userId)); // return 0; // } // } // return 1; // } // catch (const std::exception& e) { // LogThreadSafe("ANSFacialRecognition::GetUser", // "User ID " + std::to_string(userId) + ": " + e.what()); // return 0; // } //} //int ANSFacialRecognition::GetUser(int userId, std::string userCode, std::string userName, UserRecord& userRecord) { // try { // // Initialize user ID // userRecord.UserId = userId; // userRecord.UserCode = userCode; // userRecord.UserName = userName; // // LOCK ONLY DURING DATABASE ACCESS // { // std::lock_guard lock(_databaseMutex); // // Get user's face IDs // int result = _db->GetFaceIdsByUser(userId, userRecord.FaceIds); // if (result <= 0) { // LogThreadSafe("ANSFacialRecognition::GetUser", // "No faces found for user ID " + std::to_string(userId)); // return 0; // } // } // return 1; // } // catch (const std::exception& e) { // LogThreadSafe("ANSFacialRecognition::GetUser", // "User ID " + std::to_string(userId) + ": " + e.what()); // return 0; // } //} //bool ANSFacialRecognition::UpdateUserDictionary() { // try { // // Load embeddings and user dictionary (with database lock) // { // std::lock_guard lock(_databaseMutex); // if (!_db) { // LogThreadSafe("ANSFacialRecognition::UpdateUserDictionary", // "Database not initialized"); // return false; // } // _userDict = _db->GetUserDict(); // } // return true; // } // catch (const std::exception& e) { // LogThreadSafe("ANSFacialRecognition::UpdateUserDictionary", // "Failed to update user dictiornary: " + std::string(e.what())); // return false; // } //}