Files
ANSCORE/modules/ANSFR/ANSFR.cpp

3437 lines
136 KiB
C++

#include "ANSFR.h"
#include <opencv2/imgcodecs.hpp>
#include "ANSOVFaceDetector.h"
#include "SCRFDFaceDetector.h"
#include "ANSFaceRecognizer.h"
#include <faiss/IndexIDMap.h>
#include <faiss/IndexFlat.h>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <json.hpp>
// 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<std::chrono::microseconds>(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<std::chrono::microseconds>(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<FaceResultObject> FaceChecker::UniqueFaces(const std::vector<FaceResultObject>& faces) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try{
std::unordered_map<std::string, FaceResultObject> 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<FaceResultObject> 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<FaceResultObject>();
}
}
std::vector<FaceResultObject> FaceChecker::ValidateDetectedFaces(const std::vector<FaceResultObject>& faces) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
std::vector<FaceResultObject> detectedFaces;
std::unordered_set<std::string> 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<FaceDetection>& 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<FaceResultObject>();
}
}
/// <summary>
/// Start Facial Recognition class
/// </summary>
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<ov::Core>())
{
_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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(_configMutex);
_isInitialized = false;
}
return false;
}
}
void ANSFacialRecognition::UnloadEngine() {
try {
// Clean up detector
{
std::lock_guard<std::mutex> lock(_detectionMutex);
if (_detector) _detector.reset();
}
// Clean up recognizer
{
std::lock_guard<std::mutex> lock(_recognitionMutex);
if (_recognizer) _recognizer.reset();
}
// Clean up anti-spoofing detector
{
std::lock_guard<std::mutex> lock(_antispoofMutex);
if (_antiSpoofDetector) _antiSpoofDetector.reset();
}
// Clean up age-gender detector
{
std::lock_guard<std::mutex> lock(_ageGenderMutex);
if (_ageGenderDetector) _ageGenderDetector.reset();
}
// Clean up emotions detector
{
std::lock_guard<std::mutex> lock(_emotionsMutex);
if (_emotionsDetector) _emotionsDetector.reset();
}
// Clean up head pose detector
{
std::lock_guard<std::mutex> lock(_headPoseMutex);
if (_headPoseDetector) _headPoseDetector.reset();
}
// Clear user dictionary
{
std::lock_guard<std::mutex> 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<ANSFaceRecognizer*>(_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<faiss::idx_t, std::string> newFaceIdToUserId;
std::map<std::string, std::string> newUserDict;
// Read all data from database into temporaries
std::vector<float> allEmbeddings;
std::vector<faiss::idx_t> allIds;
{
std::lock_guard<std::mutex> lock(_databaseMutex);
std::vector<FaceEmbeddingRecord> 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<faiss::idx_t>(record.faceId);
newFaceIdToUserId[faceId] = record.userId;
allIds.push_back(faceId);
// L2-normalize before storing (IP metric on unit vectors = cosine sim)
std::vector<float> 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<faiss::IndexIDMap>(cpuIndex);
newFaissIndex->own_fields = true;
if (!allIds.empty()) {
newFaissIndex->add_with_ids(
static_cast<faiss::idx_t>(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<std::shared_mutex> 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<std::string, std::string> newUserDict;
{
std::lock_guard<std::mutex> 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<std::shared_mutex> 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<std::mutex> 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<std::mutex> 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<ANSCENTER::PrecisionType>(_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<ANSSCRFDFD>();
}
else {
_detector = std::make_unique<ANSOVFD>();
}
_detector->SetMaxSlotsPerGpu(m_maxSlotsPerGpu);
// LOCK DURING INITIALIZATION
bool initSuccess;
{
std::lock_guard<std::mutex> 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<ANSFaceRecognizer>();
_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<std::mutex> 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<std::mutex> lock(_configMutex);
_enableAntiSpoof = false;
}
return false;
}
// LOCK ONLY DURING MODEL INITIALIZATION
{
std::lock_guard<std::mutex> lock(_antispoofMutex);
// Reset existing detector
_antiSpoofDetector.reset();
// Create new detector
_antiSpoofDetector = std::make_unique<AntispoofingClassifier>(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<std::mutex> 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<std::mutex> lock(_configMutex);
_enableAgeGender = false;
}
return false;
}
// LOCK ONLY DURING MODEL INITIALIZATION
{
std::lock_guard<std::mutex> lock(_ageGenderMutex);
// Reset existing detector
_ageGenderDetector.reset();
// Create new detector
_ageGenderDetector = std::make_unique<AgeGenderDetection>(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<std::mutex> 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<std::mutex> lock(_configMutex);
_enableFaceEmotions = false;
}
return false;
}
// LOCK ONLY DURING MODEL INITIALIZATION
{
std::lock_guard<std::mutex> lock(_emotionsMutex);
// Reset existing detector
_emotionsDetector.reset();
// Create new detector
_emotionsDetector = std::make_unique<EmotionsDetection>(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<std::mutex> 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<std::mutex> lock(_configMutex);
_enableHeadPose = false;
}
return false;
}
// LOCK ONLY DURING MODEL INITIALIZATION
{
std::lock_guard<std::mutex> lock(_headPoseMutex);
// Reset existing detector
_headPoseDetector.reset();
// Create new detector
_headPoseDetector = std::make_unique<HeadPoseDetection>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>& 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<std::mutex> 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<Object> detectedFaces;
{
std::lock_guard<std::mutex> 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<float> embedding;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<int> ANSFacialRecognition::InsertMultipleFaces(int userId, const cv::Mat& image) {
std::vector<int> 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<Object> detectedFaces;
{
std::lock_guard<std::mutex> 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<float> embedding;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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));
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<std::mutex> 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<std::mutex> 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<UserRecord>& userRecords,std::vector<int>& userIds)
{
// Clear output parameters
userRecords.clear();
userIds.clear();
try {
std::vector<std::string> userCodes;
std::vector<std::string> userNames;
std::vector<std::vector<int>> faceIdsByUser;
// Fix #12: Single batch query with JOIN replaces N+1 individual queries
{
std::lock_guard<std::mutex> 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<int>(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<std::mutex> 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<FaceRecord>& faceRecords) {
// Clear output parameter
faceRecords.clear();
try {
// Get face IDs from database (lock during database access)
std::vector<int> faceIds;
{
std::lock_guard<std::mutex> 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<int>(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<int> faceIds;
// Get face IDs for this user (lock during database access)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<int>& userIds) {
// NO LOCK NEEDED - Delegates to another method that handles locking
// Clear output parameters
userRecords.clear();
userIds.clear();
try {
std::vector<UserRecord> 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<FaceRecord> 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<Object> detectedFaces;
{
std::lock_guard<std::mutex> 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<FaceResultObject> ANSFacialRecognition::Recognize(const cv::Mat& input) {
return Recognize(input, "FRCAM");
}
std::vector<FaceResultObject> ANSFacialRecognition::Recognize(const cv::Mat& input, const std::string& camera_id) {
std::vector<FaceResultObject> 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<Object> 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<FaceResultObject> recognizedFaces;
{
std::lock_guard<std::mutex> 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<FaceResultObject>();
}
std::vector<FaceResultObject> ANSFacialRecognition::Inference(const cv::Mat& input) {
return Inference(input, "FRCAM");
}
std::vector<FaceResultObject> ANSFacialRecognition::Inference(const cv::Mat& input, const std::string& camera_id)
{
START_TIMER(total);
std::vector<FaceResultObject> 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<Object> detectedFaces;
{
START_TIMER(detection);
std::lock_guard<std::mutex> 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<Object> 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<FaceResultObject> recognizedFaces;
{
START_TIMER(recognition);
std::lock_guard<std::mutex> 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");
}
}
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<FaceResultObject> ANSFacialRecognition::Detect(const cv::Mat& input) {
return Detect(input, "FRCAM");
}
std::vector<FaceResultObject> ANSFacialRecognition::Detect(const cv::Mat& input,const std::string& camera_id)
{
std::vector<FaceResultObject> 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<Object> detectedFaces;
{
std::lock_guard<std::mutex> 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<Object> ANSFacialRecognition::FaceDetect(const cv::Mat& input) {
return FaceDetect(input, "FRCAM");
}
std::vector<Object> ANSFacialRecognition::FaceDetect(const cv::Mat& input,const std::string& camera_id)
{
std::vector<Object> 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<Object> detectedFaces;
{
std::lock_guard<std::mutex> 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<FaceResultObject> ANSFacialRecognition::PostProcess(const std::vector<FaceResultObject>& detectedObjects,const std::string& cameraId)
{
//ScopedTimer("Post Process");
if (!_enableFaceQueue) {
return detectedObjects;
}
// Lock ONLY for queue operations
std::lock_guard<std::mutex> lock(_faceQueueMutex);
// Enqueue current detection
EnqueueDetection(detectedObjects, cameraId);
// Dequeue and process
std::deque<std::vector<FaceResultObject>> faceQueue = DequeueDetection(cameraId);
std::vector<FaceResultObject> faceObjects = FindFrequentFaces(faceQueue,detectedObjects,this->_faceThresholdSize);
return faceObjects;
}
std::string ANSFacialRecognition::PolygonToString(const std::vector<cv::Point2f>& 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<float>& 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)
std::string ANSFacialRecognition::FaceObjectsToJsonString(const std::vector<FaceResultObject>& 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", 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(-1, ' ', true);
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<Object>& 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"] = 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(-1, ' ', true);
}
// 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<FaceResultObject>& resultObjects)
{
// NO LOCK NEEDED - Operating on thread-local resultObjects
// Each camera has its own vector, no shared state
if (resultObjects.empty()) {
return;
}
// Map userId to index of face with highest confidence
std::unordered_map<std::string, size_t> highestConfidenceIndices;
highestConfidenceIndices.reserve(resultObjects.size());
for (size_t i = 0; i < resultObjects.size(); ++i) {
const std::string& userId = resultObjects[i].userId;
// Skip unknown faces
if (userId == "0000") {
continue;
}
auto it = highestConfidenceIndices.find(userId);
if (it == highestConfidenceIndices.end()) {
// First occurrence of this userId
highestConfidenceIndices[userId] = i;
}
else {
// Duplicate userId found - keep only highest confidence
size_t existingIndex = it->second;
if (resultObjects[i].confidence > resultObjects[existingIndex].confidence) {
// Current face has higher confidence - mark previous as unknown
MarkAsUnknown(resultObjects[existingIndex]);
highestConfidenceIndices[userId] = i;
}
else {
// Existing face has higher confidence - mark current as unknown
MarkAsUnknown(resultObjects[i]);
}
}
}
}
std::vector<Object> ANSFacialRecognition::CheckFaces(const std::vector<Object>& faceObjects,bool checkFaceSize)
{
// NO LOCK NEEDED - Pure filtering operation on local data
std::vector<Object> 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<Object> 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<FaceResultObject> ANSFacialRecognition::RecognizeFaces(const cv::Mat& frame, const std::vector<Object>& 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<std::shared_mutex> lock(_indexSwapMutex);
return _recognizer->Match(frame, validFaces, _userDict);
}
std::string ANSFacialRecognition::GetOpenVINODevice() {
ov::Core core;
std::vector<std::string> available_devices = core.get_available_devices();
// Prioritize devices: NPU > GPU > CPU
std::vector<std::string> 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<FaceResultObject> ANSFacialRecognition::UpdateFaceAttributes(
const std::vector<FaceResultObject>& resultObjects,
const std::string& camera_id)
{
std::vector<FaceResultObject> 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<int>(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<FaceResultObject> results(resultObjects.begin(), resultObjects.end());
std::vector<cv::Mat> masks(faceCount);
std::vector<bool> maskValid(faceCount, false);
std::vector<bool> skipAttributes(faceCount, false);
std::vector<bool> runAgeGender(faceCount, false);
std::vector<bool> runEmotions(faceCount, false);
std::vector<bool> runHeadPose(faceCount, false);
std::vector<bool> useCachedAttrs(faceCount, false);
std::vector<AgeGenderResults> ageGenderResults(faceCount, AgeGenderResults{});
std::vector<std::map<std::string, float>> emotionsResults(faceCount);
std::vector<HeadPoseResults> headPoseResults(faceCount, HeadPoseResults{});
std::vector<Face> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>(headPoseResults[i].angle_r)) +
", " + std::to_string(static_cast<int>(headPoseResults[i].angle_p)) +
", " + std::to_string(static_cast<int>(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<int> 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<FaceResultObject> ANSFacialRecognition::BuildFaceResultObjects(const std::vector<Object>& validFaces,const cv::Mat& originalInput,const std::string& camera_id)
{
std::vector<FaceResultObject> 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<float>(faceObj.box.x));
result.box.y = std::max(0.0f, static_cast<float>(faceObj.box.y));
result.box.width = std::min(
static_cast<float>(originalInput.cols) - result.box.x,
static_cast<float>(faceObj.box.width)
);
result.box.height = std::min(
static_cast<float>(originalInput.rows) - result.box.y,
static_cast<float>(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<std::mutex> lock(_ageGenderMutex);
ageGenderResult = _ageGenderDetector->runInfer(mask);
}
face.updateGender(ageGenderResult.maleProb);
face.updateAge(ageGenderResult.age);
}
// Emotions
if (face.isEmotionsEnabled()) {
std::map<std::string, float> emotions;
{
std::lock_guard<std::mutex> lock(_emotionsMutex);
emotions = _emotionsDetector->runInfer(mask);
}
face.updateEmotions(emotions);
}
// Head Pose
if (face.isHeadPoseEnabled()) {
HeadPoseResults headPoseResult;
{
std::lock_guard<std::mutex> lock(_headPoseMutex);
headPoseResult = _headPoseDetector->runInfer(mask);
}
face.updateHeadPose(headPoseResult);
}
// Anti-Spoofing
if (face.isAntispoofingEnabled()) {
float confidence;
{
std::lock_guard<std::mutex> 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<FaceResultObject>& 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<std::vector<FaceResultObject>>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<std::vector<FaceResultObject>>();
}
}
Object ANSFacialRecognition::GetLargestObject(const std::vector<Object>& 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<FaceResultObject>&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<size_t>(-1); // Invalid index if not similar
}
std::vector<FaceResultObject> ANSFacialRecognition::FindFrequentFaces(const std::deque<std::vector<FaceResultObject>>&faceQueue,
const std::vector<FaceResultObject>&detectedObjects,
int occurrenceThreshold)
{
try {
// Map to count occurrences of each detected face using detectedObjects as keys
std::unordered_map<size_t, int> 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<size_t>(-1)) { // Valid hash
faceCount[hash]++;
}
}
}
}
// Collect frequent faces from detectedObjects
std::vector<FaceResultObject> 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<FaceResultObject>();
}
}
// 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<std::mutex> 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<std::mutex> 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<FaceResultObject> ANSFacialRecognition::Inference(
// const cv::Mat& input,
// const std::string& camera_id)
//{
// std::lock_guard<std::recursive_mutex> lock(_mutex);
// std::vector<FaceResultObject> 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<float, std::milli>(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<float, std::milli>(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<float, std::milli>(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<float, std::milli>(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<float, std::milli>(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<float, std::milli>(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<float, std::milli>(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<FaceResultObject> ANSFacialRecognition::Detect(const cv::Mat& input, const std::string& camera_id) {
// std::lock_guard<std::recursive_mutex> lock(_mutex);
// if (!_isInitialized) {
// std::vector<Object> _outputBbox;
// _outputBbox.clear();
// std::vector<FaceResultObject> resultObjects;
// this->_logger.LogError("ANSFacialRecognition::Detect()", "Models are not initialized.", __FILE__, __LINE__);
// return resultObjects;
// }
// try {
// std::vector<Object> _rawOutputBbox;
// std::vector<Object> _outputBbox;
// std::vector<FaceResultObject> 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<std::string, float> 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<FaceResultObject> resultObjects;
// resultObjects.clear();
// return resultObjects;
// }
// }
// std::vector<FaceResultObject> ANSFacialRecognition::Inference(const cv::Mat& input, const std::string& camera_id) {
// std::lock_guard<std::recursive_mutex> lock(_mutex);
// if (!_isInitialized) {
// std::vector<Object> _outputBbox;
// _outputBbox.clear();
// std::vector<FaceResultObject> resultObjects;
// this->_logger.LogError("ANSFacialRecognition::Inference()", "Models are not initialized.", __FILE__, __LINE__);
// return resultObjects;
// }
// try {
// std::vector<FaceResultObject> resultObjects;
// std::vector<Object> _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<Object> 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<FaceResultObject>();
// }
// }
//int ANSFacialRecognition::GetUser(int userId, UserRecord& userRecord) {
// try {
// // Initialize user ID
// userRecord.UserId = userId;
// // LOCK ONLY DURING DATABASE ACCESS
// {
// std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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;
// }
//}