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

@@ -990,6 +990,10 @@ namespace ANSCENTER {
cv::waitKey(1);
#endif
// Deduplicate: if two trackIds claim the same plate text, keep the one
// with the higher accumulated score to prevent plate flickering
ensureUniquePlateText(output, cameraId);
return output;
}
@@ -2313,6 +2317,10 @@ namespace ANSCENTER {
output.push_back(std::move(lprObject));
}
// Deduplicate: if two trackIds claim the same plate text, keep the one
// with the higher accumulated score to prevent plate flickering
ensureUniquePlateText(output, cameraId);
return output;
}
catch (const cv::Exception& e) {
@@ -2701,4 +2709,102 @@ namespace ANSCENTER {
return AnalyseLicensePlateText(ocrText);
}
}
void ANSALPR_OD::ensureUniquePlateText(std::vector<Object>& results, const std::string& cameraId)
{
if (results.empty()) return;
auto isEmptyPlate = [](const std::string& plate) {
return plate.empty();
};
auto& identities = _plateIdentities[cameraId];
// Step 1: Build map of plateText → candidate indices
std::unordered_map<std::string, std::vector<size_t>> plateCandidates;
for (size_t i = 0; i < results.size(); ++i) {
if (isEmptyPlate(results[i].className)) continue;
plateCandidates[results[i].className].push_back(i);
}
// Step 2: Resolve duplicates using accumulated scores
for (auto& [plateText, indices] : plateCandidates) {
if (indices.size() <= 1) continue;
// Find the candidate with the highest accumulated score
size_t winner = indices[0];
float bestScore = 0.0f;
for (size_t idx : indices) {
int tid = results[idx].trackId;
float score = results[idx].confidence; // fallback for new trackIds
auto it = identities.find(tid);
if (it != identities.end() && it->second.plateText == plateText) {
score = it->second.accumulatedScore + results[idx].confidence;
}
if (score > bestScore) {
bestScore = score;
winner = idx;
}
}
// Clear plate text from non-winners
for (size_t idx : indices) {
if (idx != winner) {
results[idx].className.clear();
}
}
}
// Step 3: Update accumulated scores — winners accumulate, losers decay
constexpr float DECAY_FACTOR = 0.8f;
constexpr float MIN_SCORE = 0.1f;
for (auto& r : results) {
int tid = r.trackId;
if (isEmptyPlate(r.className)) {
// Lost dedup or empty — decay
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.plateText == r.className) {
it->second.accumulatedScore += r.confidence;
} else {
it->second.plateText = r.className;
it->second.accumulatedScore = r.confidence;
}
} else {
identities[tid] = { r.className, r.confidence };
}
}
// Step 4: Clean up trackIds no longer in the scene
std::unordered_set<int> activeTrackIds;
for (const auto& r : results) {
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;
}
}
// Step 5: Remove entries with cleared plate text from results
results.erase(
std::remove_if(results.begin(), results.end(),
[](const Object& o) { return o.className.empty(); }),
results.end());
}
};