Files
ANSCORE/modules/ANSFR/ANSFR.cpp

3553 lines
142 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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), LogLevel::Info);
return result;
}
catch (const std::exception& e) {
LogThreadSafe("ANSFacialRecognition::UpdateUser",
"Error updating user ID " + std::to_string(userId) + ": " + e.what());
return 0;
}
}
int ANSFacialRecognition::GetUser(int userId, UserRecord& userRecord) {
try {
// Validate input
if (userId <= 0) {
LogThreadSafe("ANSFacialRecognition::GetUser",
"Invalid user ID: " + std::to_string(userId));
return 0;
}
// Validate database initialization
if (!_db) {
LogThreadSafe("ANSFacialRecognition::GetUser",
"Database not initialized");
return 0;
}
// Initialize user ID
userRecord.UserId = userId;
// LOCK ONLY DURING DATABASE ACCESS
{
std::lock_guard<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;
}
}
// No faces is a valid state (user just created or faces already removed)
if (faceIds.empty()) {
LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser",
"No faces found for user ID " + std::to_string(userId) + " — skipping",
LogLevel::Debug);
return 1;
}
// 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");
// Deduplicate: if two faces matched the same userId, keep only the highest confidence
// Pass camera data for trackId-based identity persistence to prevent flickering
CameraData* camDataPtr = (!camera_id.empty()) ? &GetCameraData(camera_id) : nullptr;
ensureUniqueUserIdWithHighestConfidence(resultObjects, camDataPtr);
}
}
catch (const std::exception& e) {
LogError("ANSFacialRecognition::Inference", e.what(), camera_id);
END_TIMER(total, "Total Inference Time (exception)");
return resultObjects;
}
catch (...) {
LogError("ANSFacialRecognition::Inference", "Unknown exception", camera_id);
END_TIMER(total, "Total Inference Time (unknown exception)");
return resultObjects;
}
END_TIMER(total, "Total Inference Time");
// Fix #9: std::cout removed from production DLL
return resultObjects;
}
std::vector<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)
static std::string DoubleEscapeUnicode(const std::string& utf8Str) {
bool hasNonAscii = false;
for (unsigned char c : utf8Str) {
if (c >= 0x80) { hasNonAscii = true; break; }
}
if (!hasNonAscii) return utf8Str;
std::string result;
result.reserve(utf8Str.size() * 2);
size_t i = 0;
while (i < utf8Str.size()) {
unsigned char c = static_cast<unsigned char>(utf8Str[i]);
if (c < 0x80) { result += utf8Str[i++]; continue; }
uint32_t cp = 0;
if ((c & 0xE0) == 0xC0 && i + 1 < utf8Str.size()) {
cp = ((c & 0x1F) << 6) | (static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F); i += 2;
} else if ((c & 0xF0) == 0xE0 && i + 2 < utf8Str.size()) {
cp = ((c & 0x0F) << 12) | ((static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F) << 6) | (static_cast<unsigned char>(utf8Str[i + 2]) & 0x3F); i += 3;
} else if ((c & 0xF8) == 0xF0 && i + 3 < utf8Str.size()) {
cp = ((c & 0x07) << 18) | ((static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F) << 12) | ((static_cast<unsigned char>(utf8Str[i + 2]) & 0x3F) << 6) | (static_cast<unsigned char>(utf8Str[i + 3]) & 0x3F); i += 4;
} else { i++; continue; }
if (cp <= 0xFFFF) { char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", cp); result += buf; }
else { cp -= 0x10000; char buf[16]; snprintf(buf, sizeof(buf), "\\u%04x\\u%04x", 0xD800 + (uint16_t)(cp >> 10), 0xDC00 + (uint16_t)(cp & 0x3FF)); result += buf; }
}
return result;
}
std::string ANSFacialRecognition::FaceObjectsToJsonString(const std::vector<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", DoubleEscapeUnicode(face.userName)},
{"similarity", std::to_string(face.similarity)},
{"is_unknown", std::to_string(face.isUnknown)},
{"prob", std::to_string(face.confidence)},
{"x", std::to_string(face.box.x)},
{"y", std::to_string(face.box.y)},
{"width", std::to_string(face.box.width)},
{"height", std::to_string(face.box.height)},
{"extra_info", face.extraInformation},
{"camera_id", face.cameraId},
{"track_id", std::to_string(face.trackId)},
{"polygon", PolygonToString(face.polygon)},
{"kps", KeypointsToString(face.kps)}
});
}
END_TIMER(json_build, "JSON Build Time");
START_TIMER(json_serialize);
std::string result = root.dump();
END_TIMER(json_serialize, "JSON Serialize Time");
END_TIMER(json_total, "JSON Conversion Total Time");
return result;
}
catch (const std::exception& e) {
END_TIMER(json_total, "JSON Conversion Time (error)");
LogThreadSafe("ANSFacialRecognition::FaceObjectsToJsonString", e.what());
return R"({"results":[],"error":"Serialization failed"})";
}
}
std::string ANSFacialRecognition::FaceToJsonString(const std::vector<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"] = DoubleEscapeUnicode(det.className);
detectedNode["prob"] = std::to_string(det.confidence);
detectedNode["x"] = std::to_string(det.box.x);
detectedNode["y"] = std::to_string(det.box.y);
detectedNode["width"] = std::to_string(det.box.width);
detectedNode["height"] = std::to_string(det.box.height);
detectedNode["mask"] = ""; // TODO: convert masks to comma separated string
detectedNode["extra_info"] = det.extraInfo;
detectedNode["camera_id"] = det.cameraId;
detectedNode["polygon"] = PolygonToString(det.polygon);
detectedNode["kps"] = KeypointsToString(det.kps);
detectedObjects.push_back(detectedNode);
}
root["results"] = detectedObjects;
return root.dump();
}
// Validation helper methods
bool ANSFacialRecognition::ValidateInitialization() {
//ScopedTimer("Validate Initialization");
if (!_isInitialized) {
_logger.LogError("ANSFacialRecognition::Inference()",
"Models are not initialized.", __FILE__, __LINE__);
return false;
}
return true;
}
bool ANSFacialRecognition::ValidateInput(const cv::Mat& input) {
//ScopedTimer("ValidateInput");
constexpr int MIN_DIMENSION = 10;
if (input.empty() || input.cols < MIN_DIMENSION || input.rows < MIN_DIMENSION) {
_logger.LogFatal("ANSFacialRecognition::Inference()",
"Input image is invalid or too small", __FILE__, __LINE__);
return false;
}
return true;
}
bool ANSFacialRecognition::ValidateComponents() {
//ScopedTimer("ValidateComponents");
if (!_detector) {
_logger.LogFatal("ANSFacialRecognition::Inference()",
"_detector is null", __FILE__, __LINE__);
return false;
}
if (!_recognizer) {
_logger.LogFatal("ANSFacialRecognition::Inference()",
"_recognizer is null", __FILE__, __LINE__);
return false;
}
return true;
}
// Frame preparation
cv::Mat ANSFacialRecognition::PrepareInputFrame(const cv::Mat& input) {
// If input is already BGR and you don't modify it downstream
if (input.channels() == 3) {
return input; // Just return reference (no copy!)
}
// Only convert if grayscale
cv::Mat frame;
cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR);
return frame;
}
void ANSFacialRecognition::ensureUniqueUserIdWithHighestConfidence(std::vector<FaceResultObject>& resultObjects, CameraData* camData)
{
if (resultObjects.empty()) {
return;
}
auto isUnknownId = [](const std::string& id) {
return id == "0000" || id == "0" || id.empty();
};
if (camData) {
auto& identities = camData->trackIdentities;
// Step 1: Build map of userId → candidate face indices (before modifying anything)
std::unordered_map<std::string, std::vector<size_t>> userIdCandidates;
for (size_t i = 0; i < resultObjects.size(); ++i) {
if (isUnknownId(resultObjects[i].userId)) continue;
userIdCandidates[resultObjects[i].userId].push_back(i);
}
// Step 2: Resolve duplicate userIds using accumulated scores
for (auto& [uid, indices] : userIdCandidates) {
if (indices.size() <= 1) continue;
// Find the candidate with the highest accumulated score for this userId
size_t winner = indices[0];
float bestScore = 0.0f;
for (size_t idx : indices) {
int tid = resultObjects[idx].trackId;
float score = resultObjects[idx].confidence; // fallback for new trackIds
auto it = identities.find(tid);
if (it != identities.end() && it->second.userId == uid) {
// Use accumulated score + this frame's confidence
score = it->second.accumulatedScore + resultObjects[idx].confidence;
}
if (score > bestScore) {
bestScore = score;
winner = idx;
}
}
// Mark all non-winners as unknown
for (size_t idx : indices) {
if (idx != winner) {
MarkAsUnknown(resultObjects[idx]);
}
}
}
// Step 3: Update accumulated scores after dedup.
// - Winners: add this frame's confidence to their score
// - Losers (marked Unknown by dedup): decay their score (×0.8)
// so they gradually lose claim but don't reset instantly
// (avoids the alternating-win problem where neither can accumulate)
// - Losers that decay below a threshold are erased entirely
constexpr float DECAY_FACTOR = 0.8f;
constexpr float MIN_SCORE = 0.1f;
for (auto& r : resultObjects) {
int tid = r.trackId;
if (isUnknownId(r.userId)) {
// Lost dedup or genuinely unknown — decay existing score
auto it = identities.find(tid);
if (it != identities.end()) {
it->second.accumulatedScore *= DECAY_FACTOR;
if (it->second.accumulatedScore < MIN_SCORE) {
identities.erase(it);
}
}
continue;
}
auto it = identities.find(tid);
if (it != identities.end()) {
if (it->second.userId == r.userId) {
// Same identity as before and won dedup — accumulate
it->second.accumulatedScore += r.confidence;
} else {
// Different identity — start fresh
it->second.userId = r.userId;
it->second.accumulatedScore = r.confidence;
}
} else {
// New trackId — initialize
identities[tid] = { r.userId, r.confidence };
}
}
// Step 3: Clean up trackIds no longer in the scene
std::unordered_set<int> activeTrackIds;
for (const auto& r : resultObjects) {
activeTrackIds.insert(r.trackId);
}
for (auto it = identities.begin(); it != identities.end(); ) {
if (activeTrackIds.find(it->first) == activeTrackIds.end()) {
it = identities.erase(it);
} else {
++it;
}
}
} else {
// No camera data: fall back to simple confidence-based dedup
std::unordered_map<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;
if (isUnknownId(userId)) continue;
auto it = highestConfidenceIndices.find(userId);
if (it == highestConfidenceIndices.end()) {
highestConfidenceIndices[userId] = i;
} else {
size_t existingIndex = it->second;
if (resultObjects[i].confidence > resultObjects[existingIndex].confidence) {
MarkAsUnknown(resultObjects[existingIndex]);
highestConfidenceIndices[userId] = i;
} else {
MarkAsUnknown(resultObjects[i]);
}
}
}
}
}
std::vector<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;
// }
//}