666 lines
24 KiB
C++
666 lines
24 KiB
C++
#include "ANSLPR_OCR.h"
|
|
#include "ANSONNXYOLO.h"
|
|
#include "ANSOnnxOCR.h"
|
|
#include "ANSOCRBase.h"
|
|
|
|
#include <json.hpp>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SEH wrapper for loading ONNX models — identical to the one in ANSLPR_OD.cpp
|
|
// ---------------------------------------------------------------------------
|
|
static void WriteEventLog(const char* message, WORD eventType = EVENTLOG_INFORMATION_TYPE) {
|
|
static HANDLE hEventLog = RegisterEventSourceA(NULL, "ANSLogger");
|
|
if (hEventLog) {
|
|
const char* msgs[1] = { message };
|
|
ReportEventA(hEventLog, eventType, 0, 0, NULL, 1, 0, msgs, NULL);
|
|
}
|
|
OutputDebugStringA(message);
|
|
OutputDebugStringA("\n");
|
|
}
|
|
|
|
struct LoadOnnxParams_OCR {
|
|
const std::string* licenseKey;
|
|
ANSCENTER::ModelConfig* config;
|
|
const std::string* modelFolder;
|
|
const char* modelName;
|
|
const char* classFile;
|
|
std::string* labels;
|
|
std::unique_ptr<ANSCENTER::ANSODBase>* detector;
|
|
bool enableTracker;
|
|
bool disableStabilization;
|
|
};
|
|
|
|
static bool LoadOnnxModel_OCR_Impl(const LoadOnnxParams_OCR& p) {
|
|
try {
|
|
auto onnxyolo = std::make_unique<ANSCENTER::ANSONNXYOLO>();
|
|
bool ok = onnxyolo->LoadModelFromFolder(
|
|
*p.licenseKey, *p.config, p.modelName, p.classFile,
|
|
*p.modelFolder, *p.labels);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
if (p.enableTracker) {
|
|
onnxyolo->SetTracker(ANSCENTER::TrackerType::BYTETRACK, true);
|
|
} else {
|
|
onnxyolo->SetTracker(ANSCENTER::TrackerType::BYTETRACK, false);
|
|
}
|
|
if (p.disableStabilization) {
|
|
onnxyolo->SetStabilization(false);
|
|
}
|
|
*p.detector = std::move(onnxyolo);
|
|
return true;
|
|
}
|
|
catch (...) {
|
|
p.detector->reset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool LoadOnnxModel_OCR_SEH(const LoadOnnxParams_OCR& p, DWORD* outCode) {
|
|
*outCode = 0;
|
|
__try {
|
|
return LoadOnnxModel_OCR_Impl(p);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
*outCode = GetExceptionCode();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
namespace ANSCENTER
|
|
{
|
|
ANSALPR_OCR::ANSALPR_OCR() {
|
|
engineType = EngineType::CPU;
|
|
}
|
|
|
|
ANSALPR_OCR::~ANSALPR_OCR() {
|
|
try {
|
|
Destroy();
|
|
}
|
|
catch (...) {}
|
|
}
|
|
|
|
bool ANSALPR_OCR::Initialize(const std::string& licenseKey, const std::string& modelZipFilePath,
|
|
const std::string& modelZipPassword, double detectorThreshold, double ocrThreshold, double colourThreshold) {
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
try {
|
|
_licenseKey = licenseKey;
|
|
_licenseValid = false;
|
|
_detectorThreshold = detectorThreshold;
|
|
_ocrThreshold = ocrThreshold;
|
|
_colorThreshold = colourThreshold;
|
|
_country = Country::JAPAN; // Default to JAPAN for OCR-based ALPR
|
|
CheckLicense();
|
|
if (!_licenseValid) {
|
|
this->_logger.LogError("ANSALPR_OCR::Initialize", "License is not valid.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Extract model folder
|
|
if (!FileExist(modelZipFilePath)) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::Initialize", "Model zip file does not exist: " + modelZipFilePath, __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
this->_logger.LogInfo("ANSALPR_OCR::Initialize", "Model zip file found: " + modelZipFilePath, __FILE__, __LINE__);
|
|
|
|
// Unzip model zip file
|
|
std::vector<std::string> passwordArray;
|
|
if (!modelZipPassword.empty()) passwordArray.push_back(modelZipPassword);
|
|
passwordArray.push_back("AnsDemoModels20@!");
|
|
passwordArray.push_back("Sh7O7nUe7vJ/417W0gWX+dSdfcP9hUqtf/fEqJGqxYL3PedvHubJag==");
|
|
passwordArray.push_back("3LHxGrjQ7kKDJBD9MX86H96mtKLJaZcTYXrYRdQgW8BKGt7enZHYMg==");
|
|
std::string modelName = GetFileNameWithoutExtension(modelZipFilePath);
|
|
|
|
for (size_t i = 0; i < passwordArray.size(); i++) {
|
|
if (ExtractPasswordProtectedZip(modelZipFilePath, passwordArray[i], modelName, _modelFolder, false))
|
|
break;
|
|
}
|
|
|
|
if (!FolderExist(_modelFolder)) {
|
|
this->_logger.LogError("ANSALPR_OCR::Initialize", "Output model folder does not exist: " + _modelFolder, __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Check country from country.txt
|
|
std::string countryFile = CreateFilePath(_modelFolder, "country.txt");
|
|
if (FileExist(countryFile)) {
|
|
std::ifstream infile(countryFile);
|
|
std::string countryStr;
|
|
std::getline(infile, countryStr);
|
|
infile.close();
|
|
if (countryStr == "0") _country = Country::VIETNAM;
|
|
else if (countryStr == "1") _country = Country::CHINA;
|
|
else if (countryStr == "2") _country = Country::AUSTRALIA;
|
|
else if (countryStr == "3") _country = Country::USA;
|
|
else if (countryStr == "4") _country = Country::INDONESIA;
|
|
else if (countryStr == "5") _country = Country::JAPAN;
|
|
else _country = Country::JAPAN; // Default for OCR mode
|
|
}
|
|
|
|
// Store the original model zip path — the OCR models (ansocrdec.onnx,
|
|
// ansocrcls.onnx, ansocrrec.onnx, dict_ch.txt) are bundled inside the
|
|
// same ALPR model zip, so we reuse it for ANSONNXOCR initialization.
|
|
_modelZipFilePath = modelZipFilePath;
|
|
|
|
// Initialize ALPRChecker
|
|
alprChecker.Init(MAX_ALPR_FRAME);
|
|
|
|
_lpColourModelConfig.detectionScoreThreshold = _colorThreshold;
|
|
_lpdmodelConfig.detectionScoreThreshold = _detectorThreshold;
|
|
|
|
return true;
|
|
}
|
|
catch (std::exception& e) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::Initialize", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSALPR_OCR::LoadEngine() {
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
try {
|
|
WriteEventLog("ANSALPR_OCR::LoadEngine: Step 1 - Starting engine load");
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 1: Starting engine load", __FILE__, __LINE__);
|
|
|
|
// Detect hardware
|
|
_lpdmodelConfig.detectionScoreThreshold = _detectorThreshold;
|
|
_lpColourModelConfig.detectionScoreThreshold = _colorThreshold;
|
|
|
|
if (_lpdmodelConfig.detectionScoreThreshold < 0.25) _lpdmodelConfig.detectionScoreThreshold = 0.25;
|
|
if (_lpdmodelConfig.detectionScoreThreshold > 0.95) _lpdmodelConfig.detectionScoreThreshold = 0.95;
|
|
|
|
engineType = ANSLicenseHelper::CheckHardwareInformation();
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Detected engine type: " + std::to_string(static_cast<int>(engineType)), __FILE__, __LINE__);
|
|
|
|
float confThreshold = 0.5f;
|
|
float MNSThreshold = 0.5f;
|
|
_lpdmodelConfig.modelConfThreshold = confThreshold;
|
|
_lpdmodelConfig.modelMNSThreshold = MNSThreshold;
|
|
_lpColourModelConfig.modelConfThreshold = confThreshold;
|
|
_lpColourModelConfig.modelMNSThreshold = MNSThreshold;
|
|
|
|
std::string lprModel = CreateFilePath(_modelFolder, "lpd.onnx");
|
|
std::string colorModel = CreateFilePath(_modelFolder, "lpc.onnx");
|
|
|
|
bool valid = false;
|
|
|
|
// ── Step 2: Load LP detector with ONNX Runtime ───────────────
|
|
if (FileExist(lprModel)) {
|
|
WriteEventLog("ANSALPR_OCR::LoadEngine: Step 2 - Loading LP detector with ONNX Runtime");
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 2: Loading LP detector with ONNX Runtime", __FILE__, __LINE__);
|
|
_lpdmodelConfig.detectionType = DetectionType::DETECTION;
|
|
_lpdmodelConfig.modelType = ModelType::ONNXYOLO;
|
|
std::string _lprClasses;
|
|
{
|
|
LoadOnnxParams_OCR p{};
|
|
p.licenseKey = &_licenseKey;
|
|
p.config = &_lpdmodelConfig;
|
|
p.modelFolder = &_modelFolder;
|
|
p.modelName = "lpd";
|
|
p.classFile = "lpd.names";
|
|
p.labels = &_lprClasses;
|
|
p.detector = &_lpDetector;
|
|
p.enableTracker = true;
|
|
p.disableStabilization = true;
|
|
|
|
DWORD sehCode = 0;
|
|
bool lpSuccess = LoadOnnxModel_OCR_SEH(p, &sehCode);
|
|
if (sehCode != 0) {
|
|
char buf[256];
|
|
snprintf(buf, sizeof(buf),
|
|
"ANSALPR_OCR::LoadEngine: Step 2 LPD SEH exception 0x%08X — LP detector disabled", sehCode);
|
|
WriteEventLog(buf, EVENTLOG_ERROR_TYPE);
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine",
|
|
"Step 2: LP detector crashed (SEH). LP detector disabled.", __FILE__, __LINE__);
|
|
if (_lpDetector) _lpDetector.reset();
|
|
}
|
|
else if (!lpSuccess) {
|
|
this->_logger.LogError("ANSALPR_OCR::LoadEngine",
|
|
"Failed to load LP detector (ONNX Runtime).", __FILE__, __LINE__);
|
|
if (_lpDetector) _lpDetector.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_lpDetector) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine", "LP detector failed to load. Cannot proceed.", __FILE__, __LINE__);
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
|
|
// ── Step 3: Load OCR engine (ANSONNXOCR) ─────────────────────
|
|
// The OCR models (ansocrdec.onnx, ansocrcls.onnx, ansocrrec.onnx,
|
|
// dict_ch.txt) are bundled inside the same ALPR model zip, so we
|
|
// pass the original ALPR zip path to ANSONNXOCR::Initialize.
|
|
// ANSOCRBase::Initialize will extract it (no-op if already done)
|
|
// and discover the OCR model files in the extracted folder.
|
|
WriteEventLog("ANSALPR_OCR::LoadEngine: Step 3 - Loading OCR engine (ANSONNXOCR)");
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 3: Loading OCR engine (ANSONNXOCR)", __FILE__, __LINE__);
|
|
|
|
// Verify OCR model files exist in the already-extracted folder
|
|
std::string ocrDetModel = CreateFilePath(_modelFolder, "ansocrdec.onnx");
|
|
std::string ocrRecModel = CreateFilePath(_modelFolder, "ansocrrec.onnx");
|
|
if (!FileExist(ocrDetModel) || !FileExist(ocrRecModel)) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine",
|
|
"OCR model files not found in model folder: " + _modelFolder +
|
|
" (expected ansocrdec.onnx, ansocrrec.onnx)", __FILE__, __LINE__);
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
|
|
_ocrEngine = std::make_unique<ANSONNXOCR>();
|
|
|
|
// Determine OCR language based on country
|
|
OCRLanguage ocrLang = OCRLanguage::ENGLISH;
|
|
switch (_country) {
|
|
case Country::JAPAN: ocrLang = OCRLanguage::JAPANESE; break;
|
|
case Country::CHINA: ocrLang = OCRLanguage::CHINESE; break;
|
|
case Country::VIETNAM: ocrLang = OCRLanguage::ENGLISH; break;
|
|
case Country::AUSTRALIA: ocrLang = OCRLanguage::ENGLISH; break;
|
|
case Country::USA: ocrLang = OCRLanguage::ENGLISH; break;
|
|
case Country::INDONESIA: ocrLang = OCRLanguage::ENGLISH; break;
|
|
default: ocrLang = OCRLanguage::ENGLISH; break;
|
|
}
|
|
|
|
OCRModelConfig ocrModelConfig;
|
|
ocrModelConfig.ocrLanguage = ocrLang;
|
|
ocrModelConfig.useDetector = true;
|
|
ocrModelConfig.useRecognizer = true;
|
|
ocrModelConfig.useCLS = true;
|
|
ocrModelConfig.useLayout = false;
|
|
ocrModelConfig.useTable = false;
|
|
ocrModelConfig.useTensorRT = false;
|
|
ocrModelConfig.enableMKLDNN = false;
|
|
ocrModelConfig.useDilation = true;
|
|
ocrModelConfig.useAngleCLS = false;
|
|
ocrModelConfig.gpuId = 0;
|
|
ocrModelConfig.detectionDBThreshold = 0.5;
|
|
ocrModelConfig.detectionBoxThreshold = 0.3;
|
|
ocrModelConfig.detectionDBUnclipRatio = 1.2;
|
|
ocrModelConfig.clsThreshold = 0.9;
|
|
ocrModelConfig.limitSideLen = 2560;
|
|
|
|
// Pass the original ALPR model zip path — ANSOCRBase::Initialize
|
|
// will extract it to the same folder (already done, so extraction
|
|
// is a no-op) and set up ansocrdec.onnx / ansocrcls.onnx /
|
|
// ansocrrec.onnx / dict_ch.txt paths automatically.
|
|
bool ocrSuccess = _ocrEngine->Initialize(_licenseKey, ocrModelConfig, _modelZipFilePath, "", 0);
|
|
if (!ocrSuccess) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine", "Failed to initialize OCR engine (ANSONNXOCR).", __FILE__, __LINE__);
|
|
_ocrEngine.reset();
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
|
|
// Set ALPR mode and country on the OCR engine
|
|
_ocrEngine->SetOCRMode(OCRMode::OCR_ALPR);
|
|
_ocrEngine->SetCountry(_country);
|
|
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 3: OCR engine loaded successfully.", __FILE__, __LINE__);
|
|
|
|
// ── Step 4: Load colour classifier (optional) ────────────────
|
|
if (FileExist(colorModel) && (_lpColourModelConfig.detectionScoreThreshold > 0)) {
|
|
WriteEventLog("ANSALPR_OCR::LoadEngine: Step 4 - Loading colour classifier with ONNX Runtime");
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 4: Loading colour classifier with ONNX Runtime", __FILE__, __LINE__);
|
|
_lpColourModelConfig.detectionType = DetectionType::CLASSIFICATION;
|
|
_lpColourModelConfig.modelType = ModelType::ONNXYOLO;
|
|
{
|
|
LoadOnnxParams_OCR p{};
|
|
p.licenseKey = &_licenseKey;
|
|
p.config = &_lpColourModelConfig;
|
|
p.modelFolder = &_modelFolder;
|
|
p.modelName = "lpc";
|
|
p.classFile = "lpc.names";
|
|
p.labels = &_lpColourLabels;
|
|
p.detector = &_lpColourDetector;
|
|
p.enableTracker = false;
|
|
p.disableStabilization = false;
|
|
|
|
DWORD sehCode = 0;
|
|
bool colourSuccess = LoadOnnxModel_OCR_SEH(p, &sehCode);
|
|
if (sehCode != 0) {
|
|
char buf[256];
|
|
snprintf(buf, sizeof(buf),
|
|
"ANSALPR_OCR::LoadEngine: Step 4 LPC SEH exception 0x%08X — colour detection disabled", sehCode);
|
|
WriteEventLog(buf, EVENTLOG_ERROR_TYPE);
|
|
this->_logger.LogError("ANSALPR_OCR::LoadEngine",
|
|
"Step 4: Colour classifier crashed. Colour detection disabled.", __FILE__, __LINE__);
|
|
if (_lpColourDetector) _lpColourDetector.reset();
|
|
}
|
|
else if (!colourSuccess) {
|
|
this->_logger.LogError("ANSALPR_OCR::LoadEngine",
|
|
"Failed to load colour detector (ONNX Runtime). Colour detection disabled.", __FILE__, __LINE__);
|
|
if (_lpColourDetector) _lpColourDetector.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
valid = true;
|
|
_isInitialized = valid;
|
|
WriteEventLog(("ANSALPR_OCR::LoadEngine: Step 5 - Engine load complete. Valid = " + std::to_string(valid)).c_str());
|
|
this->_logger.LogInfo("ANSALPR_OCR::LoadEngine", "Step 5: Engine load complete. Valid = " + std::to_string(valid), __FILE__, __LINE__);
|
|
return valid;
|
|
}
|
|
catch (std::exception& e) {
|
|
WriteEventLog(("ANSALPR_OCR::LoadEngine: C++ exception: " + std::string(e.what())).c_str(), EVENTLOG_ERROR_TYPE);
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine", std::string("C++ exception: ") + e.what(), __FILE__, __LINE__);
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
catch (...) {
|
|
WriteEventLog("ANSALPR_OCR::LoadEngine: Unknown exception", EVENTLOG_ERROR_TYPE);
|
|
this->_logger.LogFatal("ANSALPR_OCR::LoadEngine", "Unknown exception", __FILE__, __LINE__);
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ── Colour detection (same pattern as ANSALPR_OD) ────────────────────
|
|
std::string ANSALPR_OCR::DetectLPColourDetector(const cv::Mat& lprROI, const std::string& cameraId) {
|
|
if (_lpColourModelConfig.detectionScoreThreshold <= 0.0f) return {};
|
|
if (!_lpColourDetector) return {};
|
|
if (lprROI.empty()) return {};
|
|
|
|
try {
|
|
std::vector<Object> colourOutputs = _lpColourDetector->RunInference(lprROI, cameraId);
|
|
if (colourOutputs.empty()) return {};
|
|
|
|
const auto& bestDetection = *std::max_element(
|
|
colourOutputs.begin(), colourOutputs.end(),
|
|
[](const Object& a, const Object& b) { return a.confidence < b.confidence; }
|
|
);
|
|
return bestDetection.className;
|
|
}
|
|
catch (const std::exception& e) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::DetectLPColourDetector", e.what(), __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
std::string ANSALPR_OCR::DetectLPColourCached(const cv::Mat& lprROI, const std::string& cameraId, const std::string& plateText) {
|
|
if (plateText.empty()) {
|
|
return DetectLPColourDetector(lprROI, cameraId);
|
|
}
|
|
|
|
// Check cache first
|
|
{
|
|
std::lock_guard<std::mutex> cacheLock(_colourCacheMutex);
|
|
auto it = _colourCache.find(plateText);
|
|
if (it != _colourCache.end()) {
|
|
it->second.hitCount++;
|
|
return it->second.colour;
|
|
}
|
|
}
|
|
|
|
// Cache miss — run classifier
|
|
std::string colour = DetectLPColourDetector(lprROI, cameraId);
|
|
|
|
if (!colour.empty()) {
|
|
std::lock_guard<std::mutex> cacheLock(_colourCacheMutex);
|
|
if (_colourCache.size() >= COLOUR_CACHE_MAX_SIZE) {
|
|
_colourCache.clear();
|
|
}
|
|
_colourCache[plateText] = { colour, 0 };
|
|
}
|
|
|
|
return colour;
|
|
}
|
|
|
|
// ── OCR on a single plate ROI ────────────────────────────────────────
|
|
// Returns the plate text via the out-parameter and populates alprExtraInfo
|
|
// with the structured ALPR JSON (zone parts) when ALPR mode is active.
|
|
std::string ANSALPR_OCR::RunOCROnPlate(const cv::Mat& plateROI, const std::string& cameraId) {
|
|
if (!_ocrEngine || plateROI.empty()) return "";
|
|
if (plateROI.cols < 10 || plateROI.rows < 10) return "";
|
|
|
|
try {
|
|
// Run the full ANSONNXOCR pipeline on the cropped plate image
|
|
std::vector<OCRObject> ocrResults = _ocrEngine->RunInference(plateROI, cameraId);
|
|
|
|
if (ocrResults.empty()) return "";
|
|
|
|
// If ALPR mode is active and we have plate formats, use the
|
|
// structured ALPR post-processing to get correct zone ordering
|
|
// (e.g. "品川 302 ま 93-15" instead of "品川30293-15ま")
|
|
const auto& alprFormats = _ocrEngine->GetALPRFormats();
|
|
if (_ocrEngine->GetOCRMode() == OCRMode::OCR_ALPR && !alprFormats.empty()) {
|
|
auto alprResults = ANSOCRUtility::ALPRPostProcessing(
|
|
ocrResults, alprFormats,
|
|
plateROI.cols, plateROI.rows,
|
|
_ocrEngine.get(), plateROI);
|
|
|
|
if (!alprResults.empty()) {
|
|
return alprResults[0].fullPlateText;
|
|
}
|
|
}
|
|
|
|
// Fallback: simple concatenation sorted by Y then X
|
|
std::sort(ocrResults.begin(), ocrResults.end(),
|
|
[](const OCRObject& a, const OCRObject& b) {
|
|
int rowThreshold = std::min(a.box.height, b.box.height) / 2;
|
|
if (std::abs(a.box.y - b.box.y) > rowThreshold) {
|
|
return a.box.y < b.box.y;
|
|
}
|
|
return a.box.x < b.box.x;
|
|
}
|
|
);
|
|
|
|
std::string fullText;
|
|
for (const auto& obj : ocrResults) {
|
|
if (!obj.className.empty()) {
|
|
fullText += obj.className;
|
|
}
|
|
}
|
|
|
|
return fullText;
|
|
}
|
|
catch (const std::exception& e) {
|
|
this->_logger.LogError("ANSALPR_OCR::RunOCROnPlate", e.what(), __FILE__, __LINE__);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ── Main inference pipeline ──────────────────────────────────────────
|
|
std::vector<Object> ANSALPR_OCR::RunInference(const cv::Mat& input, const std::string& cameraId) {
|
|
if (!_licenseValid) {
|
|
this->_logger.LogError("ANSALPR_OCR::RunInference", "Invalid license", __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
if (!_isInitialized) {
|
|
this->_logger.LogError("ANSALPR_OCR::RunInference", "Model is not initialized", __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
if (input.empty() || input.cols < 5 || input.rows < 5) {
|
|
this->_logger.LogError("ANSALPR_OCR::RunInference", "Input image is empty or too small", __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
if (!_lpDetector) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::RunInference", "_lpDetector is null", __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
if (!_ocrEngine) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::RunInference", "_ocrEngine is null", __FILE__, __LINE__);
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
// Convert grayscale to BGR if necessary
|
|
cv::Mat localFrame;
|
|
if (input.channels() == 1) {
|
|
cv::cvtColor(input, localFrame, cv::COLOR_GRAY2BGR);
|
|
}
|
|
const cv::Mat& frame = (input.channels() == 1) ? localFrame : input;
|
|
|
|
const int frameWidth = frame.cols;
|
|
const int frameHeight = frame.rows;
|
|
|
|
// Step 1: Detect license plates
|
|
std::vector<Object> lprOutput = _lpDetector->RunInference(frame, cameraId);
|
|
|
|
if (lprOutput.empty()) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<Object> output;
|
|
output.reserve(lprOutput.size());
|
|
|
|
for (auto& lprObject : lprOutput) {
|
|
const cv::Rect& box = lprObject.box;
|
|
|
|
// Calculate safe cropped region
|
|
const int x1 = std::max(0, box.x);
|
|
const int y1 = std::max(0, box.y);
|
|
const int width = std::min(frameWidth - x1, box.width);
|
|
const int height = std::min(frameHeight - y1, box.height);
|
|
|
|
if (width <= 0 || height <= 0) continue;
|
|
|
|
cv::Rect lprPos(x1, y1, width, height);
|
|
cv::Mat plateROI = frame(lprPos);
|
|
|
|
// Step 2: Run OCR on the detected plate
|
|
std::string ocrText = RunOCROnPlate(plateROI, cameraId);
|
|
|
|
if (ocrText.empty()) continue;
|
|
|
|
lprObject.cameraId = cameraId;
|
|
|
|
// Use ALPRChecker for text stabilization if enabled
|
|
if (_enableALPRChecker) {
|
|
lprObject.className = alprChecker.checkPlateByTrackId(cameraId, ocrText, lprObject.trackId);
|
|
} else {
|
|
lprObject.className = ocrText;
|
|
}
|
|
|
|
if (lprObject.className.empty()) continue;
|
|
|
|
// Step 3: Colour detection (optional)
|
|
std::string colour = DetectLPColourCached(plateROI, cameraId, lprObject.className);
|
|
if (!colour.empty()) {
|
|
lprObject.extraInfo = "color:" + colour;
|
|
}
|
|
|
|
output.push_back(std::move(lprObject));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
catch (const cv::Exception& e) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::RunInference", std::string("OpenCV Exception: ") + e.what(), __FILE__, __LINE__);
|
|
}
|
|
catch (const std::exception& e) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::RunInference", e.what(), __FILE__, __LINE__);
|
|
}
|
|
catch (...) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::RunInference", "Unknown exception occurred", __FILE__, __LINE__);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
// ── Inference wrappers ───────────────────────────────────────────────
|
|
bool ANSALPR_OCR::Inference(const cv::Mat& input, std::string& lprResult) {
|
|
if (input.empty()) return false;
|
|
if (input.cols < 5 || input.rows < 5) return false;
|
|
return Inference(input, lprResult, "CustomCam");
|
|
}
|
|
|
|
bool ANSALPR_OCR::Inference(const cv::Mat& input, std::string& lprResult, const std::string& cameraId) {
|
|
if (input.empty()) return false;
|
|
if (input.cols < 5 || input.rows < 5) return false;
|
|
|
|
try {
|
|
std::vector<Object> results = RunInference(input, cameraId);
|
|
lprResult = VectorDetectionToJsonString(results);
|
|
return !results.empty();
|
|
}
|
|
catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSALPR_OCR::Inference(const cv::Mat& input, const std::vector<cv::Rect>& Bbox, std::string& lprResult) {
|
|
return Inference(input, Bbox, lprResult, "CustomCam");
|
|
}
|
|
|
|
bool ANSALPR_OCR::Inference(const cv::Mat& input, const std::vector<cv::Rect>& Bbox, std::string& lprResult, const std::string& cameraId) {
|
|
if (input.empty()) return false;
|
|
if (input.cols < 5 || input.rows < 5) return false;
|
|
|
|
try {
|
|
if (Bbox.empty()) {
|
|
return Inference(input, lprResult, cameraId);
|
|
}
|
|
|
|
// For cropped images, run OCR on each bounding box
|
|
std::vector<Object> allResults;
|
|
cv::Mat frame;
|
|
if (input.channels() == 1) {
|
|
cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR);
|
|
} else {
|
|
frame = input;
|
|
}
|
|
|
|
for (const auto& bbox : Bbox) {
|
|
int x1 = std::max(0, bbox.x);
|
|
int y1 = std::max(0, bbox.y);
|
|
int w = std::min(frame.cols - x1, bbox.width);
|
|
int h = std::min(frame.rows - y1, bbox.height);
|
|
|
|
if (w < 5 || h < 5) continue;
|
|
|
|
cv::Rect safeRect(x1, y1, w, h);
|
|
cv::Mat cropped = frame(safeRect);
|
|
|
|
std::vector<Object> results = RunInference(cropped, cameraId);
|
|
|
|
// Adjust bounding boxes back to full image coordinates
|
|
for (auto& obj : results) {
|
|
obj.box.x += x1;
|
|
obj.box.y += y1;
|
|
allResults.push_back(std::move(obj));
|
|
}
|
|
}
|
|
|
|
lprResult = VectorDetectionToJsonString(allResults);
|
|
return !allResults.empty();
|
|
}
|
|
catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ANSALPR_OCR::SetCountry(Country country) {
|
|
_country = country;
|
|
if (_ocrEngine) {
|
|
_ocrEngine->SetCountry(country);
|
|
}
|
|
}
|
|
|
|
bool ANSALPR_OCR::Destroy() {
|
|
try {
|
|
if (_lpDetector) {
|
|
_lpDetector->Destroy();
|
|
_lpDetector.reset();
|
|
}
|
|
if (_lpColourDetector) {
|
|
_lpColourDetector->Destroy();
|
|
_lpColourDetector.reset();
|
|
}
|
|
if (_ocrEngine) {
|
|
_ocrEngine->Destroy();
|
|
_ocrEngine.reset();
|
|
}
|
|
_isInitialized = false;
|
|
return true;
|
|
}
|
|
catch (std::exception& e) {
|
|
this->_logger.LogFatal("ANSALPR_OCR::Destroy", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace ANSCENTER
|