Fix ALPR pipeline. Ready for production
This commit is contained in:
@@ -862,6 +862,39 @@ namespace ANSCENTER {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ANSALPR_OD::shouldUseALPRChecker(const cv::Size& imageSize, const std::string& cameraId) {
|
||||
// Force disabled → never use
|
||||
if (!_enableALPRChecker) return false;
|
||||
|
||||
// Small images are always pipeline crops — skip auto-detection
|
||||
if (imageSize.width < ImageSizeTracker::MIN_FULLFRAME_WIDTH) return false;
|
||||
|
||||
// Enabled: auto-detect pipeline vs full-frame by exact image size consistency.
|
||||
// Full-frame: same resolution every frame (e.g., 3840x2160 always).
|
||||
// Pipeline crops: vary by a few pixels (e.g., 496x453, 497x455) — exact match fails.
|
||||
auto& tracker = _imageSizeTrackers[cameraId];
|
||||
bool wasFullFrame = tracker.detectedFullFrame;
|
||||
if (imageSize == tracker.lastSize) {
|
||||
tracker.consistentCount++;
|
||||
if (tracker.consistentCount >= ImageSizeTracker::CONFIRM_THRESHOLD) {
|
||||
tracker.detectedFullFrame = true;
|
||||
}
|
||||
} else {
|
||||
tracker.lastSize = imageSize;
|
||||
tracker.consistentCount = 1;
|
||||
tracker.detectedFullFrame = false;
|
||||
}
|
||||
// Log state transitions
|
||||
if (tracker.detectedFullFrame != wasFullFrame) {
|
||||
ANS_DBG("ALPR_Checker", "cam=%s mode auto-detected: %s (img=%dx%d consistent=%d)",
|
||||
cameraId.c_str(),
|
||||
tracker.detectedFullFrame ? "FULL-FRAME (Layer2+3 ON)" : "PIPELINE (Layer2+3 OFF)",
|
||||
imageSize.width, imageSize.height, tracker.consistentCount);
|
||||
}
|
||||
return tracker.detectedFullFrame;
|
||||
}
|
||||
|
||||
std::vector<Object> ANSALPR_OD::RunInferenceSingleFrame(const cv::Mat& input, const std::string& cameraId) {
|
||||
// No coarse _mutex here — sub-components (detectors, alprChecker) have their own locks.
|
||||
// LabVIEW semaphore controls concurrency at the caller level.
|
||||
@@ -931,6 +964,13 @@ namespace ANSCENTER {
|
||||
// Run license plate detection
|
||||
cv::Mat activeFrame = frame(detectedArea);
|
||||
std::vector<Object> lprOutput = _lpDetector->RunInference(activeFrame, cameraId);
|
||||
for (size_t _di = 0; _di < lprOutput.size(); ++_di) {
|
||||
ANS_DBG("ALPR_Track", "cam=%s det[%zu] tid=%d box=(%d,%d,%d,%d) conf=%.2f",
|
||||
cameraId.c_str(), _di, lprOutput[_di].trackId,
|
||||
lprOutput[_di].box.x, lprOutput[_di].box.y,
|
||||
lprOutput[_di].box.width, lprOutput[_di].box.height,
|
||||
lprOutput[_di].confidence);
|
||||
}
|
||||
|
||||
if (lprOutput.empty()) {
|
||||
#ifdef FNS_DEBUG
|
||||
@@ -972,7 +1012,11 @@ namespace ANSCENTER {
|
||||
}
|
||||
|
||||
lprObject.cameraId = cameraId;
|
||||
lprObject.className = alprChecker.checkPlate(cameraId, ocrText, lprObject.box);
|
||||
if (shouldUseALPRChecker(cv::Size(frameWidth, frameHeight), cameraId)) {
|
||||
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, ocrText, lprObject.trackId);
|
||||
} else {
|
||||
lprObject.className = ocrText;
|
||||
}
|
||||
|
||||
if (lprObject.className.empty()) {
|
||||
continue;
|
||||
@@ -994,7 +1038,9 @@ namespace ANSCENTER {
|
||||
|
||||
// Deduplicate: if two trackIds claim the same plate text, keep the one
|
||||
// with the higher accumulated score to prevent plate flickering
|
||||
ensureUniquePlateText(output, cameraId);
|
||||
if (shouldUseALPRChecker(cv::Size(frameWidth, frameHeight), cameraId)) {
|
||||
ensureUniquePlateText(output, cameraId);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
@@ -1473,7 +1519,11 @@ namespace ANSCENTER {
|
||||
auto tValidate = dbg ? Clock::now() : Clock::time_point{};
|
||||
lprObject.cameraId = cameraId;
|
||||
lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
|
||||
lprObject.className = alprChecker.checkPlate(cameraId, ocrText, lprObject.box);
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, ocrText, lprObject.trackId);
|
||||
} else {
|
||||
lprObject.className = ocrText;
|
||||
}
|
||||
if (dbg) { totalValidateMs += std::chrono::duration<double, std::milli>(Clock::now() - tValidate).count(); }
|
||||
|
||||
if (lprObject.className.empty()) {
|
||||
@@ -1610,6 +1660,13 @@ namespace ANSCENTER {
|
||||
cv::Mat croppedObject = frame(objectPos);
|
||||
|
||||
std::vector<Object> lprOutput = _lpDetector->RunInference(croppedObject, cameraId);
|
||||
for (size_t _di = 0; _di < lprOutput.size(); ++_di) {
|
||||
ANS_DBG("ALPR_Track", "cam=%s bbox det[%zu] tid=%d box=(%d,%d,%d,%d) conf=%.2f",
|
||||
cameraId.c_str(), _di, lprOutput[_di].trackId,
|
||||
lprOutput[_di].box.x, lprOutput[_di].box.y,
|
||||
lprOutput[_di].box.width, lprOutput[_di].box.height,
|
||||
lprOutput[_di].confidence);
|
||||
}
|
||||
|
||||
for (auto& lprObject : lprOutput) {
|
||||
const cv::Rect& box = lprObject.box;
|
||||
@@ -1652,7 +1709,11 @@ namespace ANSCENTER {
|
||||
continue;
|
||||
}
|
||||
|
||||
lprObject.className = alprChecker.checkPlate(cameraId, ocrText, lprObject.box);
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, ocrText, lprObject.trackId);
|
||||
} else {
|
||||
lprObject.className = ocrText;
|
||||
}
|
||||
|
||||
if (lprObject.className.empty()) {
|
||||
continue;
|
||||
@@ -1670,6 +1731,13 @@ namespace ANSCENTER {
|
||||
else {
|
||||
// No bounding boxes - run on full frame
|
||||
std::vector<Object> lprOutput = _lpDetector->RunInference(frame, cameraId);
|
||||
for (size_t _di = 0; _di < lprOutput.size(); ++_di) {
|
||||
ANS_DBG("ALPR_Track", "cam=%s full det[%zu] tid=%d box=(%d,%d,%d,%d) conf=%.2f",
|
||||
cameraId.c_str(), _di, lprOutput[_di].trackId,
|
||||
lprOutput[_di].box.x, lprOutput[_di].box.y,
|
||||
lprOutput[_di].box.width, lprOutput[_di].box.height,
|
||||
lprOutput[_di].confidence);
|
||||
}
|
||||
detectedObjects.reserve(lprOutput.size());
|
||||
|
||||
for (auto& lprObject : lprOutput) {
|
||||
@@ -1704,7 +1772,11 @@ namespace ANSCENTER {
|
||||
|
||||
std::string rawText = DetectLicensePlateString(alignedLPR, cameraId);
|
||||
|
||||
lprObject.className = alprChecker.checkPlate(cameraId, rawText, lprObject.box);
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, rawText, lprObject.trackId);
|
||||
} else {
|
||||
lprObject.className = rawText;
|
||||
}
|
||||
|
||||
if (lprObject.className.empty()) {
|
||||
continue;
|
||||
@@ -1723,7 +1795,9 @@ namespace ANSCENTER {
|
||||
// Note: in Bbox mode, internal LP trackIds overlap across crops, so
|
||||
// dedup uses plate bounding box position (via Object::box) to distinguish.
|
||||
// The ensureUniquePlateText method handles this by plate text grouping.
|
||||
ensureUniquePlateText(detectedObjects, cameraId);
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
ensureUniquePlateText(detectedObjects, cameraId);
|
||||
}
|
||||
|
||||
lprResult = VectorDetectionToJsonString(detectedObjects);
|
||||
return true;
|
||||
@@ -2252,6 +2326,13 @@ namespace ANSCENTER {
|
||||
// Run license plate detection (should be thread-safe internally)
|
||||
cv::Mat activeFrame = frame(detectedArea);
|
||||
std::vector<Object> lprOutput = _lpDetector->RunInference(activeFrame, cameraId);
|
||||
for (size_t _di = 0; _di < lprOutput.size(); ++_di) {
|
||||
ANS_DBG("ALPR_Track", "cam=%s batch det[%zu] tid=%d box=(%d,%d,%d,%d) conf=%.2f",
|
||||
cameraId.c_str(), _di, lprOutput[_di].trackId,
|
||||
lprOutput[_di].box.x, lprOutput[_di].box.y,
|
||||
lprOutput[_di].box.width, lprOutput[_di].box.height,
|
||||
lprOutput[_di].confidence);
|
||||
}
|
||||
|
||||
if (lprOutput.empty()) {
|
||||
return {};
|
||||
@@ -2308,8 +2389,12 @@ namespace ANSCENTER {
|
||||
Object lprObject = lprOutput[origIdx];
|
||||
lprObject.cameraId = cameraId;
|
||||
|
||||
// Stabilize OCR text through ALPRChecker (spatial tracking + majority voting)
|
||||
lprObject.className = alprChecker.checkPlate(cameraId, ocrText, lprObject.box);
|
||||
// Stabilize OCR text through ALPRChecker (hybrid trackId + Levenshtein fallback)
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, ocrText, lprObject.trackId);
|
||||
} else {
|
||||
lprObject.className = ocrText;
|
||||
}
|
||||
|
||||
if (lprObject.className.empty()) {
|
||||
continue;
|
||||
@@ -2327,7 +2412,9 @@ namespace ANSCENTER {
|
||||
|
||||
// Deduplicate: if two trackIds claim the same plate text, keep the one
|
||||
// with the higher accumulated score to prevent plate flickering
|
||||
ensureUniquePlateText(output, cameraId);
|
||||
if (shouldUseALPRChecker(cv::Size(input.cols, input.rows), cameraId)) {
|
||||
ensureUniquePlateText(output, cameraId);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user