Fix issue that generate two or more objects with the same LP numbers/user name in the same image frame
This commit is contained in:
@@ -2163,7 +2163,9 @@ namespace ANSCENTER {
|
||||
END_TIMER(postprocess, "Update Face Attributes Time");
|
||||
|
||||
// Deduplicate: if two faces matched the same userId, keep only the highest confidence
|
||||
ensureUniqueUserIdWithHighestConfidence(resultObjects);
|
||||
// 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) {
|
||||
@@ -2500,45 +2502,128 @@ namespace ANSCENTER {
|
||||
cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR);
|
||||
return frame;
|
||||
}
|
||||
void ANSFacialRecognition::ensureUniqueUserIdWithHighestConfidence(std::vector<FaceResultObject>& resultObjects)
|
||||
void ANSFacialRecognition::ensureUniqueUserIdWithHighestConfidence(std::vector<FaceResultObject>& resultObjects, CameraData* camData)
|
||||
{
|
||||
// NO LOCK NEEDED - Operating on thread-local resultObjects
|
||||
// Each camera has its own vector, no shared state
|
||||
|
||||
if (resultObjects.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Map userId to index of face with highest confidence
|
||||
std::unordered_map<std::string, size_t> highestConfidenceIndices;
|
||||
highestConfidenceIndices.reserve(resultObjects.size());
|
||||
auto isUnknownId = [](const std::string& id) {
|
||||
return id == "0000" || id == "0" || id.empty();
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < resultObjects.size(); ++i) {
|
||||
const std::string& userId = resultObjects[i].userId;
|
||||
if (camData) {
|
||||
auto& identities = camData->trackIdentities;
|
||||
|
||||
// Skip unknown faces
|
||||
if (userId == "0000") {
|
||||
continue;
|
||||
// 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);
|
||||
}
|
||||
|
||||
auto it = highestConfidenceIndices.find(userId);
|
||||
// Step 2: Resolve duplicate userIds using accumulated scores
|
||||
for (auto& [uid, indices] : userIdCandidates) {
|
||||
if (indices.size() <= 1) continue;
|
||||
|
||||
if (it == highestConfidenceIndices.end()) {
|
||||
// First occurrence of this userId
|
||||
highestConfidenceIndices[userId] = i;
|
||||
}
|
||||
else {
|
||||
// Duplicate userId found - keep only highest confidence
|
||||
size_t existingIndex = it->second;
|
||||
// Find the candidate with the highest accumulated score for this userId
|
||||
size_t winner = indices[0];
|
||||
float bestScore = 0.0f;
|
||||
|
||||
if (resultObjects[i].confidence > resultObjects[existingIndex].confidence) {
|
||||
// Current face has higher confidence - mark previous as unknown
|
||||
MarkAsUnknown(resultObjects[existingIndex]);
|
||||
highestConfidenceIndices[userId] = i;
|
||||
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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Existing face has higher confidence - mark current as unknown
|
||||
MarkAsUnknown(resultObjects[i]);
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ namespace ANSCENTER
|
||||
void Destroy();
|
||||
void SetMaxSlotsPerGpu(int n) { m_maxSlotsPerGpu = n; }
|
||||
private:
|
||||
struct CameraData; // Forward declaration for ensureUniqueUserIdWithHighestConfidence
|
||||
int m_maxSlotsPerGpu{ 1 }; // set by dllmain based on GPU topology
|
||||
int GetUser(int userId, UserRecord& userRecord);
|
||||
int GetUser(int userId, const std::string& userCode,const std::string& userName, UserRecord& userRecord);
|
||||
@@ -140,7 +141,7 @@ namespace ANSCENTER
|
||||
bool InitializeAgeGenderModel(const std::string& deviceName);
|
||||
bool InitializeEmotionModel(const std::string& deviceName);
|
||||
bool InitializeHeadPoseModel(const std::string& deviceName);
|
||||
void ensureUniqueUserIdWithHighestConfidence(std::vector<FaceResultObject>& resultObjects);
|
||||
void ensureUniqueUserIdWithHighestConfidence(std::vector<FaceResultObject>& resultObjects, CameraData* camData = nullptr);
|
||||
Object GetLargestObject(const std::vector<Object>& objects);
|
||||
bool AreFacesSimilar(const FaceResultObject& face1, const FaceResultObject& face2);
|
||||
size_t GenerateFaceHash(const FaceResultObject& face, const std::vector<FaceResultObject>& detectedObjects);
|
||||
@@ -232,6 +233,16 @@ namespace ANSCENTER
|
||||
int attributeFrameCounter = 0; // counts frames for attribute skip logic
|
||||
std::unordered_map<int, CachedFaceAttributes> cachedAttributes; // trackId → cached attrs
|
||||
|
||||
// Identity persistence: accumulated confidence per (trackId, userId) pair.
|
||||
// Each frame adds the recognition confidence to the running total.
|
||||
// When two trackIds claim the same userId, the one with the higher
|
||||
// accumulated score wins — preventing single-frame flickering.
|
||||
struct TrackIdentity {
|
||||
std::string userId;
|
||||
float accumulatedScore = 0.0f;
|
||||
};
|
||||
std::unordered_map<int, TrackIdentity> trackIdentities; // trackId → accumulated identity
|
||||
|
||||
// Adaptive interval state
|
||||
int currentAttributeInterval = 5; // current adaptive interval
|
||||
int stableFrameCount = 0; // frames with no new/lost trackIds
|
||||
@@ -246,6 +257,7 @@ namespace ANSCENTER
|
||||
_detectionQueue.clear();
|
||||
attributeFrameCounter = 0;
|
||||
cachedAttributes.clear();
|
||||
trackIdentities.clear();
|
||||
currentAttributeInterval = 5;
|
||||
stableFrameCount = 0;
|
||||
previousTrackIdCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user