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:
2026-04-01 08:01:22 +11:00
parent ccfc5964d4
commit 6c6d1c0e0b
7 changed files with 249 additions and 34 deletions

View File

@@ -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]);
}
}
}
}

View File

@@ -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;