Add ANSALPR country format

This commit is contained in:
2026-04-28 19:23:46 +10:00
parent cd5d6f1923
commit e1f37ec767
4 changed files with 206 additions and 4 deletions

View File

@@ -145,7 +145,11 @@
"Bash(grep -nE \"^\\\\}$|warmupModel\\\\\\(|#if 0|#endif|ANSONNXCL_legacy_Init\" C:/Projects/CLionProjects/ANSCORE/modules/ANSODEngine/ANSONNXCL.cpp)", "Bash(grep -nE \"^\\\\}$|warmupModel\\\\\\(|#if 0|#endif|ANSONNXCL_legacy_Init\" C:/Projects/CLionProjects/ANSCORE/modules/ANSODEngine/ANSONNXCL.cpp)",
"Bash(git -C C:/Projects/CLionProjects/ANSCORE restore modules/ANSODEngine/ANSONNXCL.h modules/ANSODEngine/ANSONNXCL.cpp)", "Bash(git -C C:/Projects/CLionProjects/ANSCORE restore modules/ANSODEngine/ANSONNXCL.h modules/ANSODEngine/ANSONNXCL.cpp)",
"Bash(git -C C:/Projects/CLionProjects/ANSCORE status --short modules/)", "Bash(git -C C:/Projects/CLionProjects/ANSCORE status --short modules/)",
"Bash(git -C C:/Projects/CLionProjects/ANSCORE diff --stat HEAD modules/ANSODEngine/ANSONNXCL.cpp modules/ANSODEngine/ANSONNXOBB.cpp modules/ANSODEngine/ANSONNXPOSE.cpp modules/ANSODEngine/ANSONNXSEG.cpp modules/ANSODEngine/ANSYOLO12OD.cpp engines/ONNXEngine/ONNXEngine.cpp engines/ONNXEngine/ONNXSAM3.cpp)" "Bash(git -C C:/Projects/CLionProjects/ANSCORE diff --stat HEAD modules/ANSODEngine/ANSONNXCL.cpp modules/ANSODEngine/ANSONNXOBB.cpp modules/ANSODEngine/ANSONNXPOSE.cpp modules/ANSODEngine/ANSONNXSEG.cpp modules/ANSODEngine/ANSYOLO12OD.cpp engines/ONNXEngine/ONNXEngine.cpp engines/ONNXEngine/ONNXSAM3.cpp)",
"Bash(awk -F'[:\\(\\)]' '{gsub\\(/^ +| +$/,\"\",$2\\); split\\($2,a,\" \"\\); ms=a[1]; if \\(ms+0 >= 500\\) print $0}')",
"Bash(awk -F'[:\\(\\)]' '{split\\($2,a,\" \"\\); ms=a[1]; if \\(ms+0 >= 100\\) print $0}')",
"Bash(grep -cE \"^\\\\s*Plate: [^ ]+$\" \"C:/Users/nghia/Downloads/Log4.txt\")",
"Bash(grep -oE \"Plate: [^ ]+$\" \"C:/Users/nghia/Downloads/Log5.txt\")"
] ]
} }
} }

View File

@@ -194,6 +194,12 @@ namespace ANSCENTER
else _country = Country::JAPAN; // Default for OCR mode else _country = Country::JAPAN; // Default for OCR mode
} }
// Install the Vietnam-only plate-format defaults now that the
// country is known. Idempotent — clears `_plateFormats` first.
// SetCountry() also calls this so a runtime country switch picks
// up the right format set without an Initialize / restart.
ApplyCountryDefaultPlateFormats();
// Store the original model zip path — the OCR models (ansocrdec.onnx, // Store the original model zip path — the OCR models (ansocrdec.onnx,
// ansocrcls.onnx, ansocrrec.onnx, dict_ch.txt) are bundled inside the // ansocrcls.onnx, ansocrrec.onnx, dict_ch.txt) are bundled inside the
// same ALPR model zip, so we reuse it for ANSONNXOCR initialization. // same ALPR model zip, so we reuse it for ANSONNXOCR initialization.
@@ -929,6 +935,140 @@ namespace ANSCENTER
return stripped; return stripped;
} }
// ────────────────────────────────────────────────────────────────────
// Country-specific post-processing. See header for full contract.
// Mirrors the ANSALPR_OD pipeline so OD and OCR engines emit the
// same canonical form for Vietnam / Indonesia / Australia / USA,
// while leaving Japan / China text untouched (kana / kanji would
// be destroyed by an ASCII alnum filter, so we pass-through).
// ────────────────────────────────────────────────────────────────────
std::string ANSALPR_OCR::AnalyseLicensePlateText(const std::string& ocrText) {
if (ocrText.empty()) return ocrText;
// Country gate — Vietnam-only. Every other country (Japan, China,
// Indonesia, Australia, USA, …) returns the input unchanged so the
// upstream StripPlateSeparators output flows through verbatim and
// kana / kanji / region-specific punctuation is preserved.
if (_country != Country::VIETNAM) {
return ocrText;
}
try {
// Vietnam: keep only ASCII alphanumerics (StripPlateSeparators
// already removed the common separators; this is a defensive
// second pass that also catches any stray punctuation the OCR
// may have emitted). Uppercase so the format-match literals
// 'M', 'D', 'N', 'G', 'Q', 'T', 'C', 'V' work against the
// uppercase plate output.
std::string analysedLP;
analysedLP.reserve(ocrText.size());
for (char c : ocrText) {
if (std::isalnum(static_cast<unsigned char>(c))) {
analysedLP += c;
}
}
std::transform(analysedLP.begin(), analysedLP.end(), analysedLP.begin(),
[](unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
// Format validation: when `_plateFormats` is configured (which
// it is by default for Vietnam via ApplyCountryDefaultPlateFormats),
// reject plates that don't match any pattern. Empty list = no
// validation (caller cleared it via SetPlateFormats), in which
// case everything is accepted just like ANSALPR_OD does.
if (!analysedLP.empty() && !_plateFormats.empty()
&& !MatchesPlateFormat(analysedLP)) {
ANS_DBG("ALPR_Format",
"REJECT plate='%s' (no _plateFormats match)",
analysedLP.c_str());
return "";
}
return analysedLP;
}
catch (const std::exception& e) {
this->_logger.LogFatal("ANSALPR_OCR::AnalyseLicensePlateText",
e.what(), __FILE__, __LINE__);
return "";
}
}
bool ANSALPR_OCR::MatchesPlateFormat(const std::string& plate) const {
if (_plateFormats.empty()) {
return true; // No formats configured → accept all.
}
for (const auto& format : _plateFormats) {
if (plate.size() != format.size()) continue;
bool matches = true;
for (size_t i = 0; i < format.size(); ++i) {
char f = format[i];
char p = plate[i];
if (f == 'd') {
if (!std::isdigit(static_cast<unsigned char>(p))) { matches = false; break; }
}
else if (f == 'l') {
if (!std::isalpha(static_cast<unsigned char>(p))) { matches = false; break; }
}
else {
// Literal character — exact match required.
if (p != f) { matches = false; break; }
}
}
if (matches) return true;
}
return false;
}
void ANSALPR_OCR::ApplyCountryDefaultPlateFormats() {
// Reset first so repeated SetCountry calls don't accumulate stale
// patterns from previous countries.
_plateFormats.clear();
switch (_country) {
case Country::VIETNAM: {
// Same 11 patterns ANSALPR_OD installs for Vietnam. Keeping
// these identical ensures the OD- and OCR-based pipelines
// accept exactly the same plate strings in production.
// ddlddddd — standard car (e.g. 29A12345)
// ddldddd — short variant
// ddldddddd — extended variant
// ddllddddd — two-letter series
// ddMDdddddd — military
// dddddNGdd — diplomatic NG
// dddddQTdd — diplomatic QT
// dddddCVdd — diplomatic CV
// dddddNNdd — diplomatic NN
// lldddd — special two-letter prefix
_plateFormats.push_back("ddlddddd");
_plateFormats.push_back("ddldddd");
_plateFormats.push_back("ddldddddd");
_plateFormats.push_back("ddllddddd");
_plateFormats.push_back("ddllddddd");
_plateFormats.push_back("ddMDdddddd");
_plateFormats.push_back("dddddNGdd");
_plateFormats.push_back("dddddQTdd");
_plateFormats.push_back("dddddCVdd");
_plateFormats.push_back("dddddNNdd");
_plateFormats.push_back("lldddd");
ANS_DBG("ALPR_Format",
"ApplyCountryDefaultPlateFormats: VIETNAM (%zu patterns)",
_plateFormats.size());
break;
}
case Country::CHINA:
case Country::AUSTRALIA:
case Country::USA:
case Country::INDONESIA:
case Country::JAPAN:
default:
// No default patterns configured for these countries. The
// caller can still install custom formats via SetPlateFormat
// / SetPlateFormats; otherwise format validation accepts all.
ANS_DBG("ALPR_Format",
"ApplyCountryDefaultPlateFormats: country=%d (no default patterns)",
static_cast<int>(_country));
break;
}
}
std::string ANSALPR_OCR::RecoverKanaFromBottomHalf( std::string ANSALPR_OCR::RecoverKanaFromBottomHalf(
const cv::Mat& plateROI, int halfH) const const cv::Mat& plateROI, int halfH) const
{ {
@@ -1641,6 +1781,13 @@ namespace ANSCENTER
combinedText = StripPlateSeparators(combinedText); combinedText = StripPlateSeparators(combinedText);
if (combinedText.empty()) continue; if (combinedText.empty()) continue;
// Vietnam-only: alnum filter + uppercase + format validation
// against `_plateFormats`. No-op on every other country.
// Same canonical form ANSALPR_OD emits, so OD- and OCR-based
// pipelines can be A/B compared on the same input video.
combinedText = AnalyseLicensePlateText(combinedText);
if (combinedText.empty()) continue;
Object lprObject = lprOutput[info.origIndex]; Object lprObject = lprOutput[info.origIndex];
lprObject.cameraId = cameraId; lprObject.cameraId = cameraId;
@@ -1906,6 +2053,13 @@ namespace ANSCENTER
combined = StripPlateSeparators(combined); combined = StripPlateSeparators(combined);
if (combined.empty()) continue; if (combined.empty()) continue;
// Vietnam-only post-processing (alnum + uppercase + format
// validation). Same gate as the full-frame path so OD and
// OCR engines emit identical strings on Vietnam input, and
// non-Vietnam countries pass through unchanged.
combined = AnalyseLicensePlateText(combined);
if (combined.empty()) continue;
Object out = pm.lpObj; Object out = pm.lpObj;
out.className = combined; // raw OCR — no ALPRChecker out.className = combined; // raw OCR — no ALPRChecker
out.cameraId = cameraId; out.cameraId = cameraId;
@@ -2014,6 +2168,10 @@ namespace ANSCENTER
if (_ocrEngine) { if (_ocrEngine) {
_ocrEngine->SetCountry(country); _ocrEngine->SetCountry(country);
} }
// Re-install country-default plate formats so a runtime country
// switch picks up Vietnam's format list (or clears it when leaving
// Vietnam) without needing an Initialize / restart.
ApplyCountryDefaultPlateFormats();
// Log every SetCountry call so runtime country switches are // Log every SetCountry call so runtime country switches are
// visible and we can confirm the update landed on the right // visible and we can confirm the update landed on the right
// handle. The recovery + rectification gates read _country on // handle. The recovery + rectification gates read _country on

View File

@@ -109,6 +109,41 @@ namespace ANSCENTER
// --- Original model zip path (reused for ANSONNXOCR initialization) --- // --- Original model zip path (reused for ANSONNXOCR initialization) ---
std::string _modelZipFilePath; std::string _modelZipFilePath;
// ────────────────────────────────────────────────────────────────
// Country-specific plate-format processing — Vietnam-only.
//
// When `_country == VIETNAM`, `AnalyseLicensePlateText` strips
// any remaining non-alphanumeric characters, uppercases the
// result, and validates it against `_plateFormats` (the same
// 11-pattern list ANSALPR_OD installs for Vietnam). Plates
// that don't match any configured format are dropped — the
// return value is "" and the caller skips the plate.
//
// Every other country (Japan, China, Indonesia, Australia, USA,
// …) returns the input unchanged so the upstream
// StripPlateSeparators output flows through verbatim, preserving
// kana / kanji / region-specific punctuation.
//
// Hooked in after StripPlateSeparators and before the ALPRChecker
// voter, so locked text + skip-when-locked output are emitted in
// the canonical Vietnam form just like ANSALPR_OD's path.
// ────────────────────────────────────────────────────────────────
[[nodiscard]] std::string AnalyseLicensePlateText(const std::string& ocrText);
// Format match helper — patterns: 'd' = any digit, 'l' = any
// letter, every other character is a literal that must match
// exactly. Returns true when `_plateFormats` is empty (i.e.
// no formats configured → no validation, accept all).
[[nodiscard]] bool MatchesPlateFormat(const std::string& plate) const;
// Reset `_plateFormats` to the default list for the current
// `_country`. Called at the end of Initialize (after country.txt
// is read) and from SetCountry so a runtime country switch picks
// up the right format set on the very next frame. Currently
// populates Vietnam's 11 standard patterns; other countries leave
// the list empty (= no validation).
void ApplyCountryDefaultPlateFormats();
// --- Colour detection helpers --- // --- Colour detection helpers ---
[[nodiscard]] std::string DetectLPColourDetector(const cv::Mat& lprROI, const std::string& cameraId); [[nodiscard]] std::string DetectLPColourDetector(const cv::Mat& lprROI, const std::string& cameraId);
[[nodiscard]] std::string DetectLPColourCached(const cv::Mat& lprROI, const std::string& cameraId, const std::string& plateText); [[nodiscard]] std::string DetectLPColourCached(const cv::Mat& lprROI, const std::string& cameraId, const std::string& plateText);

View File

@@ -3953,9 +3953,14 @@ int ALPR_OCR_VideoTest() {
return -1; return -1;
} }
// Step 2: Set country (JAPAN = 5 — adjust to match the dataset if needed) // Step 2: Set country (VIETNAM = 0 — adjust to match the dataset if needed).
ANSALPR_SetCountry(&infHandle, 1); // Country codes match country.txt parsing in ANSALPR_OCR::Initialize:
std::cout << "Country set to JAPAN" << std::endl; // 0 = VIETNAM, 1 = CHINA, 2 = AUSTRALIA, 3 = USA, 4 = INDONESIA, 5 = JAPAN.
// Setting VIETNAM here also activates ANSALPR_OCR's Vietnam-only
// post-processing (alnum filter + uppercase + plate-format validation
// against the 11 standard Vietnam patterns).
ANSALPR_SetCountry(&infHandle, 0);
std::cout << "Country set to VIETNAM" << std::endl;
// Step 3: Load engine // Step 3: Load engine
auto engineStart = std::chrono::high_resolution_clock::now(); auto engineStart = std::chrono::high_resolution_clock::now();