Add CPU/GPU gate and support new ANSALPR using OCR
This commit is contained in:
@@ -272,6 +272,82 @@ static bool LoadLpcModel_SEH(const LoadLpcParams& p, DWORD* outCode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generic SEH wrapper for loading an ANSONNXYOLO model (used by the CPU /
|
||||
// AMD / Intel fallback path where TensorRT is unavailable).
|
||||
//
|
||||
// Why SEH is required here
|
||||
// ------------------------
|
||||
// DirectML / OpenVINO / CUDA ORT session creation can crash with an
|
||||
// asynchronous hardware fault (STATUS_ACCESS_VIOLATION 0xC0000005) when
|
||||
// the underlying provider driver is in a bad state. C++ `try/catch` does
|
||||
// NOT catch SEH exceptions on MSVC unless the translator is explicitly
|
||||
// installed. Without this SEH wrapper the AV propagates up through
|
||||
// ANSALPR_OD::LoadEngine into LoadANSALPREngineHandle, which logs
|
||||
// "SEH exception 0xC0000005 caught during engine load" and returns 0 —
|
||||
// the user sees a generic error with no way to tell which detector
|
||||
// (LPD / OCR / LPC) failed.
|
||||
//
|
||||
// Wrapping each detector creation lets us:
|
||||
// 1. Isolate the failing detector without taking down the whole load.
|
||||
// 2. Log a precise error message indicating which model crashed.
|
||||
// 3. Let the caller zero out the unique_ptr so Destroy() won't run a
|
||||
// half-initialised engine during cleanup.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct LoadOnnxParams {
|
||||
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_Impl(const LoadOnnxParams& 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); // upcast ANSONNXYOLO -> ANSODBase
|
||||
return true;
|
||||
}
|
||||
catch (...) {
|
||||
p.detector->reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadOnnxModel_SEH(const LoadOnnxParams& p, DWORD* outCode) {
|
||||
// IMPORTANT: a function containing __try/__except must not run C++
|
||||
// destructors in the handler body — the CRT's SEH unwind can collide
|
||||
// with C++ unwind and call std::terminate. We therefore defer any
|
||||
// cleanup (unique_ptr::reset) to the caller, which runs outside the
|
||||
// SEH context. This mirrors the LoadLpcModel_SEH pattern above.
|
||||
*outCode = 0;
|
||||
__try {
|
||||
return LoadOnnxModel_Impl(p);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
*outCode = GetExceptionCode();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//#define FNS_DEBUG
|
||||
namespace ANSCENTER {
|
||||
|
||||
@@ -285,6 +361,12 @@ namespace ANSCENTER {
|
||||
|
||||
ANSALPR_OD::ANSALPR_OD() {
|
||||
valid = false;
|
||||
// Default to safest engine (CPU). LoadEngine() overrides this after
|
||||
// CheckHardwareInformation() runs. We must not leave engineType
|
||||
// uninitialised because vendor predicates (isNvidiaEngine() etc.)
|
||||
// gate NV12/CUDA paths and could otherwise activate the CUDA runtime
|
||||
// on AMD/Intel hardware.
|
||||
engineType = ANSCENTER::EngineType::CPU;
|
||||
};
|
||||
ANSALPR_OD::~ANSALPR_OD() {
|
||||
try {
|
||||
@@ -485,8 +567,16 @@ namespace ANSCENTER {
|
||||
WriteEventLog("ANSALPR_OD::LoadEngine: Step 2 - Checking hardware information");
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 2: Checking hardware information", __FILE__, __LINE__);
|
||||
engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation();//
|
||||
WriteEventLog(("ANSALPR_OD::LoadEngine: Step 2 complete - Engine type = " + std::to_string(static_cast<int>(engineType))).c_str());
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 2 complete: Engine type = " + std::to_string(static_cast<int>(engineType)), __FILE__, __LINE__);
|
||||
const char* vendorTag =
|
||||
isNvidiaEngine() ? "NVIDIA_GPU (TensorRT + NV12/CUDA fast path)" :
|
||||
isAmdEngine() ? "AMD_GPU (DirectML via ONNX Runtime, NV12/CUDA DISABLED)" :
|
||||
isIntelEngine() ? "OPENVINO_GPU (OpenVINO via ONNX Runtime, NV12/CUDA DISABLED)" :
|
||||
"CPU (ONNX Runtime, NV12/CUDA DISABLED)";
|
||||
WriteEventLog(("ANSALPR_OD::LoadEngine: Step 2 complete - Engine type = " +
|
||||
std::to_string(static_cast<int>(engineType)) + " [" + vendorTag + "]").c_str());
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine",
|
||||
"Step 2 complete: Engine type = " + std::to_string(static_cast<int>(engineType)) +
|
||||
" [" + vendorTag + "]", __FILE__, __LINE__);
|
||||
|
||||
valid = false;
|
||||
if (_lpDetector) _lpDetector.reset();
|
||||
@@ -788,54 +878,132 @@ namespace ANSCENTER {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ONNX Runtime fallback path (CPU or when TensorRT fails)
|
||||
// ONNX Runtime fallback path (CPU / AMD / Intel — or NVIDIA when
|
||||
// TensorRT build failed). Each detector is loaded through a
|
||||
// dedicated SEH wrapper (LoadOnnxModel_SEH) so that an
|
||||
// AV / STATUS_ACCESS_VIOLATION raised deep inside the ONNX
|
||||
// Runtime session creator (e.g. from a misbehaving DirectML
|
||||
// / OpenVINO / CUDA provider driver) does not tear down the
|
||||
// whole LoadEngine call. The wrapper logs the exact detector
|
||||
// that failed and zeros out the corresponding unique_ptr.
|
||||
if (!valid) {
|
||||
if (FileExist(lprModel) && (FileExist(ocrModel)))
|
||||
{
|
||||
bool lpSuccess = false, ocrSuccess = false;
|
||||
|
||||
// ── Step 6: LPD ─────────────────────────────────────
|
||||
WriteEventLog("ANSALPR_OD::LoadEngine: Step 6 - Loading LP detector with ONNX Runtime");
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 6: Loading LP detector with ONNX Runtime", __FILE__, __LINE__);
|
||||
_lpdmodelConfig.detectionType = DetectionType::DETECTION;
|
||||
_lpdmodelConfig.modelType = ModelType::ONNXYOLO;
|
||||
_lpdmodelConfig.modelType = ModelType::ONNXYOLO;
|
||||
std::string _lprClasses;
|
||||
_lpDetector = std::make_unique<ANSCENTER::ANSONNXYOLO>();// Yolo
|
||||
bool lpSuccess = _lpDetector->LoadModelFromFolder(_licenseKey, _lpdmodelConfig, "lpd", "lpd.names", _modelFolder, _lprClasses);
|
||||
if (!lpSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine", "Failed to load LP detector (ONNX Runtime).", __FILE__, __LINE__);
|
||||
_lpDetector.reset();
|
||||
}
|
||||
else {
|
||||
// Enable tracker on LP detector for stable bounding box tracking,
|
||||
// but disable stabilization (no ghost plates — ALPRChecker handles text stabilization)
|
||||
_lpDetector->SetTracker(TrackerType::BYTETRACK, true);
|
||||
_lpDetector->SetStabilization(false);
|
||||
{
|
||||
LoadOnnxParams 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;
|
||||
lpSuccess = LoadOnnxModel_SEH(p, &sehCode);
|
||||
if (sehCode != 0) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"ANSALPR_OD::LoadEngine: Step 6 LPD SEH exception 0x%08X — LP detector disabled", sehCode);
|
||||
WriteEventLog(buf, EVENTLOG_ERROR_TYPE);
|
||||
this->_logger.LogFatal("ANSALPR_OD::LoadEngine",
|
||||
"Step 6: LP detector crashed (SEH 0x" + std::to_string(sehCode) + "). LP detector disabled.",
|
||||
__FILE__, __LINE__);
|
||||
lpSuccess = false;
|
||||
// Drop any half-initialised state outside SEH context.
|
||||
if (_lpDetector) _lpDetector.reset();
|
||||
}
|
||||
else if (!lpSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine",
|
||||
"Failed to load LP detector (ONNX Runtime).", __FILE__, __LINE__);
|
||||
if (_lpDetector) _lpDetector.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 7: OCR ─────────────────────────────────────
|
||||
WriteEventLog("ANSALPR_OD::LoadEngine: Step 7 - Loading OCR detector with ONNX Runtime");
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 7: Loading OCR detector with ONNX Runtime", __FILE__, __LINE__);
|
||||
_ocrModelConfig.detectionType = DetectionType::DETECTION;
|
||||
_ocrModelConfig.modelType = ModelType::ONNXYOLO;
|
||||
_ocrDetector = std::make_unique<ANSCENTER::ANSONNXYOLO>();// Yolo
|
||||
bool ocrSuccess = _ocrDetector->LoadModelFromFolder(_licenseKey, _ocrModelConfig, "ocr", "ocr.names", _modelFolder, _ocrLabels);
|
||||
if (!ocrSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine", "Failed to load OCR detector (ONNX Runtime).", __FILE__, __LINE__);
|
||||
_ocrDetector.reset();
|
||||
}
|
||||
else {
|
||||
_ocrDetector->SetTracker(TrackerType::BYTETRACK, false);
|
||||
_ocrModelConfig.modelType = ModelType::ONNXYOLO;
|
||||
{
|
||||
LoadOnnxParams p{};
|
||||
p.licenseKey = &_licenseKey;
|
||||
p.config = &_ocrModelConfig;
|
||||
p.modelFolder = &_modelFolder;
|
||||
p.modelName = "ocr";
|
||||
p.classFile = "ocr.names";
|
||||
p.labels = &_ocrLabels;
|
||||
p.detector = &_ocrDetector;
|
||||
p.enableTracker = false;
|
||||
p.disableStabilization = false;
|
||||
|
||||
DWORD sehCode = 0;
|
||||
ocrSuccess = LoadOnnxModel_SEH(p, &sehCode);
|
||||
if (sehCode != 0) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"ANSALPR_OD::LoadEngine: Step 7 OCR SEH exception 0x%08X — OCR detector disabled", sehCode);
|
||||
WriteEventLog(buf, EVENTLOG_ERROR_TYPE);
|
||||
this->_logger.LogFatal("ANSALPR_OD::LoadEngine",
|
||||
"Step 7: OCR detector crashed (SEH 0x" + std::to_string(sehCode) + "). OCR detector disabled.",
|
||||
__FILE__, __LINE__);
|
||||
ocrSuccess = false;
|
||||
// Drop any half-initialised state outside SEH context.
|
||||
if (_ocrDetector) _ocrDetector.reset();
|
||||
}
|
||||
else if (!ocrSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine",
|
||||
"Failed to load OCR detector (ONNX Runtime).", __FILE__, __LINE__);
|
||||
if (_ocrDetector) _ocrDetector.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to load the color model
|
||||
// ── Step 8: LPC (optional) ──────────────────────────
|
||||
if (FileExist(colorModel) && (_lpColourModelConfig.detectionScoreThreshold > 0)) {
|
||||
WriteEventLog("ANSALPR_OD::LoadEngine: Step 8 - Loading colour classifier with ONNX Runtime");
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 8: Loading colour classifier with ONNX Runtime", __FILE__, __LINE__);
|
||||
_lpColourModelConfig.detectionType = DetectionType::CLASSIFICATION;
|
||||
_lpColourModelConfig.modelType = ModelType::ONNXYOLO;
|
||||
_lpColourDetector = std::make_unique<ANSCENTER::ANSONNXYOLO>();// Classification with ONNX
|
||||
bool colourSuccess = _lpColourDetector->LoadModelFromFolder(_licenseKey, _lpColourModelConfig, "lpc", "lpc.names", _modelFolder, _lpColourLabels);
|
||||
if (!colourSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine", "Failed to load colour detector (ONNX Runtime). Colour detection disabled.", __FILE__, __LINE__);
|
||||
_lpColourDetector.reset();
|
||||
}
|
||||
else {
|
||||
_lpColourDetector->SetTracker(TrackerType::BYTETRACK, false);
|
||||
_lpColourModelConfig.modelType = ModelType::ONNXYOLO;
|
||||
{
|
||||
LoadOnnxParams 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_SEH(p, &sehCode);
|
||||
if (sehCode != 0) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"ANSALPR_OD::LoadEngine: Step 8 LPC SEH exception 0x%08X — colour detection disabled", sehCode);
|
||||
WriteEventLog(buf, EVENTLOG_ERROR_TYPE);
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine",
|
||||
"Step 8: Colour classifier crashed (SEH 0x" + std::to_string(sehCode) + "). Colour detection disabled.",
|
||||
__FILE__, __LINE__);
|
||||
// Drop any half-initialised state outside SEH context.
|
||||
if (_lpColourDetector) _lpColourDetector.reset();
|
||||
}
|
||||
else if (!colourSuccess) {
|
||||
this->_logger.LogError("ANSALPR_OD::LoadEngine",
|
||||
"Failed to load colour detector (ONNX Runtime). Colour detection disabled.", __FILE__, __LINE__);
|
||||
if (_lpColourDetector) _lpColourDetector.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,8 +1019,8 @@ namespace ANSCENTER {
|
||||
}
|
||||
}
|
||||
_isInitialized = valid;
|
||||
WriteEventLog(("ANSALPR_OD::LoadEngine: Step 8 - Engine load complete. Valid = " + std::to_string(valid)).c_str());
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 8: Engine load complete. Valid = " + std::to_string(valid), __FILE__, __LINE__);
|
||||
WriteEventLog(("ANSALPR_OD::LoadEngine: Step 9 - Engine load complete. Valid = " + std::to_string(valid)).c_str());
|
||||
this->_logger.LogInfo("ANSALPR_OD::LoadEngine", "Step 9: Engine load complete. Valid = " + std::to_string(valid), __FILE__, __LINE__);
|
||||
return valid;
|
||||
|
||||
}
|
||||
@@ -1467,8 +1635,11 @@ namespace ANSCENTER {
|
||||
constexpr int padding = 10;
|
||||
|
||||
// --- Compute display→full-res scale (once per frame, cheap) ---
|
||||
// NV12 GPU fast path is NVIDIA-only — cv::cuda::Stream/GpuMat
|
||||
// touch the CUDA runtime even when the helper would early-return,
|
||||
// which destabilises AMD/Intel hardware. Gate strictly on NVIDIA.
|
||||
float scaleX = 1.f, scaleY = 1.f;
|
||||
{
|
||||
if (isNvidiaEngine()) {
|
||||
auto* gpuData = tl_currentGpuFrame();
|
||||
if (gpuData && gpuData->width > frame.cols && gpuData->height > frame.rows) {
|
||||
scaleX = static_cast<float>(gpuData->width) / frame.cols;
|
||||
@@ -1484,8 +1655,9 @@ namespace ANSCENTER {
|
||||
|
||||
cv::Mat lprImage;
|
||||
|
||||
// Try GPU NV12 crop (NVIDIA decode: NV12 still in GPU VRAM)
|
||||
if (scaleX > 1.f) {
|
||||
// Try GPU NV12 crop (NVIDIA decode: NV12 still in GPU VRAM).
|
||||
// Skipped on AMD/Intel/CPU — see isNvidiaEngine() guard above.
|
||||
if (isNvidiaEngine() && scaleX > 1.f) {
|
||||
auto cropResult = _nv12Helper.tryNV12CropToBGR(
|
||||
frame, 0, box, padding, scaleX, scaleY,
|
||||
this->_logger, "LPR");
|
||||
@@ -1635,8 +1807,11 @@ namespace ANSCENTER {
|
||||
constexpr int padding = 10;
|
||||
|
||||
// --- Compute display→full-res scale (once per frame, cheap) ---
|
||||
// NVIDIA-only NV12 fast path — see isNvidiaEngine() discussion
|
||||
// above. cv::cuda::* types touch CUDA even inside the "guarded"
|
||||
// helper, so we must not even read tl_currentGpuFrame() on AMD.
|
||||
float scaleX2 = 1.f, scaleY2 = 1.f;
|
||||
{
|
||||
if (isNvidiaEngine()) {
|
||||
auto* gpuData = tl_currentGpuFrame();
|
||||
if (gpuData && gpuData->width > frame.cols && gpuData->height > frame.rows) {
|
||||
scaleX2 = static_cast<float>(gpuData->width) / frame.cols;
|
||||
@@ -1694,9 +1869,11 @@ namespace ANSCENTER {
|
||||
lprObject.cameraId = cameraId;
|
||||
lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
|
||||
|
||||
// Crop from full-res NV12 on GPU if available, otherwise display-res
|
||||
// Crop from full-res NV12 on GPU if available, otherwise display-res.
|
||||
// NV12 helper is NVIDIA-only — isNvidiaEngine() gate keeps
|
||||
// CUDA runtime inactive on AMD/Intel/CPU hardware.
|
||||
cv::Mat lprImage;
|
||||
if (scaleX2 > 1.f) {
|
||||
if (isNvidiaEngine() && scaleX2 > 1.f) {
|
||||
auto cropResult = _nv12Helper.tryNV12CropToBGR(
|
||||
frame, 0, lprObject.box, 0, scaleX2, scaleY2,
|
||||
this->_logger, "LPR");
|
||||
@@ -1760,10 +1937,12 @@ namespace ANSCENTER {
|
||||
lprObject.cameraId = cameraId;
|
||||
lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
|
||||
|
||||
// Crop from full-res NV12 on GPU if available, otherwise display-res
|
||||
// Crop from full-res NV12 on GPU if available, otherwise display-res.
|
||||
// NV12 helper is NVIDIA-only — isNvidiaEngine() gate keeps
|
||||
// CUDA runtime inactive on AMD/Intel/CPU hardware.
|
||||
cv::Rect lprPos(x1, y1, width, height);
|
||||
cv::Mat lprImage;
|
||||
if (scaleX2 > 1.f) {
|
||||
if (isNvidiaEngine() && scaleX2 > 1.f) {
|
||||
auto cropResult = _nv12Helper.tryNV12CropToBGR(
|
||||
frame, 0, lprPos, 0, scaleX2, scaleY2,
|
||||
this->_logger, "LPR");
|
||||
|
||||
Reference in New Issue
Block a user