#include "FaceNet.h" namespace ANSCENTER { std::string ANSFaceNet::GetOpenVINODevice() { ov::Core core; std::vector available_devices = core.get_available_devices(); // Prioritize devices: NPU > GPU > CPU std::vector priority_devices = {"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 } } // Default to CPU if no prioritized devices are found return "CPU"; } bool ANSFaceNet::Initialize(std::string licenseKey,ModelConfig modelConfig, const std::string& modelZipFilePath,const std::string& modelZipPassword,std::string& labelMap) { bool result = ANSFRBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap); if (!result) return false; try { _modelConfig = modelConfig; _modelConfig.modelType = ModelType::FACERECOGNIZE; _modelConfig.detectionType = DetectionType::FACERECOGNIZER; // We need to get the modelfolder from here std::string faceidModel = CreateFilePath(_modelFolder, "ansfacenet.xml"); if (std::filesystem::exists(faceidModel)) { _modelFilePath = faceidModel; this->_logger.LogDebug("ANSFaceNet::Initialize. Loading ansfacenet weight", _modelFilePath, __FILE__, __LINE__); } else { this->_logger.LogError("ANSFaceNet::Initialize. Model ansfacenet.xml file is not exist", _modelFilePath, __FILE__, __LINE__); return false; } m_knownPersonThresh = _modelConfig.unknownPersonThreshold; if (m_knownPersonThresh == 0)m_knownPersonThresh = 0.35; std::string deviceName = GetOpenVINODevice(); ov::Core core; CnnConfig reid_config(_modelFilePath, "Face Re-Identification"); reid_config.m_deviceName = deviceName; //core.set_property(deviceName, ov::hint::performance_mode(ov::hint::PerformanceMode::THROUGHPUT)); reid_config.m_max_batch_size = 1; reid_config.m_core = core; this->faceRecognizer = std::make_unique(reid_config); if (faceRecognizer == nullptr) { this->_logger.LogFatal("ANSFaceNet::Initialize", "Failed to initialize face recognizer model", __FILE__, __LINE__); return false; } Init(); _isInitialized = true; return true; } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::Initialize", e.what(), __FILE__, __LINE__); return false; } } bool ANSFaceNet::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) { try { // We need to get the _modelFolder bool result = ANSFRBase::LoadModel(modelZipFilePath, modelZipPassword); if (!result) return false; // We need to get the modelfolder from here std::string faceidModel = CreateFilePath(_modelFolder, "ansfacenet.xml"); if (std::filesystem::exists(faceidModel)) { _modelFilePath = faceidModel; this->_logger.LogDebug("ANSFaceNet::LoadModel. Loading ansfacenet weight", _modelFilePath, __FILE__, __LINE__); } else { this->_logger.LogError("ANSFaceNet::Initialize. Model ansfacenet.xml file is not exist", _modelFilePath, __FILE__, __LINE__); return false; } return true; } catch (std::exception& e) { this->_logger.LogFatal("ArcFace50::LoadModel", e.what(), __FILE__, __LINE__); return false; } } bool ANSFaceNet::OptimizeModel(bool fp16, std::string& optimizedModelFolder) { if (!FileExist(_modelFilePath)) { optimizedModelFolder = ""; return false; } return true; } std::vector ANSFaceNet::Feature(const cv::Mat& image, const ANSCENTER::Object& bBox) { std::lock_guard lock(_mutex); std::vector embeddingResult; try { if (image.empty()) return embeddingResult; if ((image.cols < 10) || (image.rows < 10)) return embeddingResult; std::vector embeddings; std::vector face_rois; face_rois.clear(); embeddings.clear(); face_rois.push_back(bBox.mask); faceRecognizer->Compute(face_rois, &embeddings); embeddingResult.assign(embeddings[0].begin(), embeddings[0].end()); return embeddingResult; } catch (std::exception& e) { std::vector embeddingResult; embeddingResult.clear(); this->_logger.LogFatal("ANSFaceNet::Feature", e.what(), __FILE__, __LINE__); return embeddingResult; } } std::vector ANSFaceNet::Match( const cv::Mat& input, const std::vector& bBox, const std::map& userDict) { std::vector resultObjects; // Early validation before locking if (input.empty()) { return resultObjects; } if (input.cols < 10 || input.rows < 10) { return resultObjects; } std::lock_guard lock(_mutex); if (!_isInitialized) { _logger.LogError("ANSFaceNet::Match", "Model is not initialized", __FILE__, __LINE__); return resultObjects; } try { // Convert grayscale to 3-channel BGR if needed cv::Mat processedImage; if (input.channels() == 1) { cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR); } else { processedImage = input; } // Get embeddings std::vector> detectedEmbeddings = Forward(processedImage, bBox); // Search for matches std::vector names; std::vector sims; std::tie(names, sims) = SearchForFaces(detectedEmbeddings); // Check if we got results if (names.empty()) { _logger.LogError("ANSFaceNet::Match", "No face is match", __FILE__, __LINE__); return resultObjects; } // Pre-reserve result space const size_t resultCount = std::min(names.size(), bBox.size()); resultObjects.reserve(resultCount); // Build result objects for (size_t i = 0; i < resultCount; ++i) { FaceResultObject resultObject; // Determine if face is known or unknown const bool isUnknown = (sims[i] > m_knownPersonThresh); if (isUnknown) { resultObject.isUnknown = true; resultObject.userId = "0"; resultObject.userName = "Unknown"; resultObject.confidence = 1.0f; // 100% unknown } else { resultObject.isUnknown = false; resultObject.userId = names[i]; // Safe map lookup auto it = userDict.find(names[i]); resultObject.userName = (it != userDict.end()) ? it->second : names[i]; resultObject.confidence = 1.0f - sims[i]; } resultObject.similarity = sims[i]; // Copy bounding box (no clamping - keeping original logic) resultObject.box.x = bBox[i].box.x; resultObject.box.y = bBox[i].box.y; resultObject.box.width = bBox[i].box.width; resultObject.box.height = bBox[i].box.height; // Copy additional data resultObject.mask = bBox[i].mask; resultObject.cameraId = bBox[i].cameraId; resultObject.trackId = bBox[i].trackId; resultObject.polygon = bBox[i].polygon; resultObject.kps = bBox[i].kps; resultObject.extraInformation = bBox[i].extraInfo; resultObjects.push_back(std::move(resultObject)); } return resultObjects; } catch (const std::exception& e) { _logger.LogFatal("ANSFaceNet::Match", e.what(), __FILE__, __LINE__); return resultObjects; } } cv::Mat ANSFaceNet::GetCropFace(const cv::Mat& input, const ANSCENTER::Object& bBox) { try { cv::Mat processedImage; if (input.channels() == 1) { cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR); } else { processedImage = input; } std::vector outputBbox; outputBbox.push_back(bBox); std::vector crFaces; crFaces.clear(); ANSFRHelper::GetCroppedFaces(processedImage, outputBbox, FACE_WIDTH, FACE_HEIGHT, crFaces); return crFaces[0].faceMat; } catch (std::exception& e) { cv::Mat result; this->_logger.LogFatal("ANSFaceNet::GetCropFace", e.what(), __FILE__, __LINE__); return result; } } bool ANSFaceNet::LoadEngine(const std::string xmlModelPath, bool engineOptimisation) { try { if (!FileExist(xmlModelPath)) { this->_logger.LogError("ANSFaceNet::LoadEngine", "Cannot find the raw XML model file.", __FILE__, __LINE__); return false; } return true; } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::LoadEngine", e.what(), __FILE__, __LINE__); return false; } } // Private methods, can be replacable void ANSFaceNet::AddEmbedding(const std::string& className, float embedding[]) { //std::lock_guard lock(_mutex); try { // Check if faiss_index is initialized if (!faiss_index) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", "Search_index is not initialized.", __FILE__, __LINE__); return; } // Convert embedding array to vector and check size std::vector vec(embedding, embedding + FACE_EMBEDDING_SIZE); if (vec.size() != FACE_EMBEDDING_SIZE) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", "Embedding size does not match expected output dimension of 512.", __FILE__, __LINE__); return; } // Add class name and get index classNames.push_back(className); // Add embedding to faiss_index faiss_index->add(1, vec.data()); } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", e.what(), __FILE__, __LINE__); } } void ANSFaceNet::AddEmbedding(const std::string& className, const std::vector& embedding) { //std::lock_guard lock(_mutex); try { // Ensure faiss_index is initialized if (!faiss_index) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", "Search_index is not initialized.", __FILE__, __LINE__); return; } // Check embedding size if (embedding.size() != FACE_EMBEDDING_SIZE) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", "Embedding size does not match expected output dimension of 512.", __FILE__, __LINE__); return; } // Add class name classNames.push_back(className); // Add embedding to faiss_index faiss_index->add(1, embedding.data()); } catch (const std::exception& e) { this->_logger.LogFatal("ANSFaceNet::AddEmbedding", e.what(), __FILE__, __LINE__); } } std::vector> ANSFaceNet::Forward(const cv::Mat& input, std::vector outputBbox) { //std::lock_guard lock(_mutex); std::vector> detectedEmbeddings; try { std::vector embeddings; std::vector face_rois; detectedEmbeddings.clear(); if (outputBbox.size() > 0) { for (int i = 0; i < outputBbox.size(); i++) { face_rois.clear(); embeddings.clear(); std::vector embeddingRs; face_rois.push_back(outputBbox[i].mask); faceRecognizer->Compute(face_rois, &embeddings); embeddingRs.assign(embeddings[0].begin(), embeddings[0].end()); detectedEmbeddings.push_back(embeddingRs); } } return detectedEmbeddings; } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::Forward", e.what(), __FILE__, __LINE__); return detectedEmbeddings; } } std::tuple, std::vector> ANSFaceNet::SearchForFaces(const std::vector>& detectedEmbeddings) { //std::lock_guard lock(_mutex); std::vector detectedUsers; std::vector simValues; try { // Check if there are class names available if (!classNames.empty()) { const int k = 3; // Number of nearest neighbors to retrieve // Process each detected embedding for (const auto& embedding : detectedEmbeddings) { // Prepare vectors to hold search results std::vector indices(k); std::vector distances(k); // Perform the search for the k nearest neighbors faiss_index->search(1, embedding.data(), k, distances.data(), indices.data()); // Find the index with the maximum distance auto min_it = std::min_element(distances.begin(), distances.end()); int best_index = std::distance(distances.begin(), min_it); // Map the index to the corresponding class name and calculate similarity std::vector matchEmbedding(faiss_index->d); faiss_index->reconstruct(indices[best_index], matchEmbedding.data()); float similarity = 1 - CosineSimilarity(embedding, matchEmbedding, false); detectedUsers.push_back(classNames.at(indices[best_index])); simValues.push_back(std::abs(similarity)); } } else { // If no class names are available, mark all users as "unknown" detectedUsers.assign(detectedEmbeddings.size(), "0"); simValues.assign(detectedEmbeddings.size(), 1.0f); } return std::make_tuple(detectedUsers, simValues); } catch (const std::exception& e) { // Log the error and return default values for the failed search detectedUsers.assign(1, "0"); simValues.assign(1, 1.0f); this->_logger.LogFatal("ANSFaceNet::SearchForFaces", e.what(), __FILE__, __LINE__); return std::make_tuple(detectedUsers, simValues); } } void ANSFaceNet::Init() { try { classNames.clear(); if (faiss_index) { faiss_index->reset(); } else { faiss_index = std::make_unique(FACE_EMBEDDING_SIZE); } } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::Init", e.what(), __FILE__, __LINE__); } } ANSFaceNet::~ANSFaceNet() { try { Destroy(); } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::~ANSFaceNet()", e.what(), __FILE__, __LINE__); } } bool ANSFaceNet::Destroy() { try { // Clear all resources safely classNames.clear(); // Reset and release faiss_index if (faiss_index) { faiss_index->reset(); faiss_index.reset(); // Release shared pointer ownership } faceRecognizer.reset(); return true; } catch (std::exception& e) { this->_logger.LogFatal("ANSFaceNet::Destroy", e.what(), __FILE__, __LINE__); return false; } } }