2026-03-28 16:54:11 +11:00
|
|
|
// dllmain.cpp : Defines the entry point for the DLL application.
|
|
|
|
|
#pragma once
|
|
|
|
|
#include "pch.h"
|
|
|
|
|
#include "ANSLPR.h"
|
|
|
|
|
#include "ANSLPR_CPU.h"
|
|
|
|
|
#include "ANSLPR_OD.h"
|
2026-04-12 17:16:16 +10:00
|
|
|
#include "ANSLPR_OCR.h"
|
2026-03-28 16:54:11 +11:00
|
|
|
#include "ANSLibsLoader.h"
|
|
|
|
|
#include "ANSGpuFrameRegistry.h" // gpu_frame_lookup(cv::Mat*)
|
|
|
|
|
#include <unordered_set>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <condition_variable>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
// Write a message to Windows Event Log (Application log, source "ANSLogger").
|
|
|
|
|
// Works even when spdlog is not initialized — useful for crash diagnostics.
|
|
|
|
|
static void WriteWindowsEventLog(const char* message, WORD eventType = EVENTLOG_ERROR_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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle registry with refcount — prevents use-after-free when
|
|
|
|
|
// ReleaseANSALPRHandle is called while inference is still running.
|
|
|
|
|
// refcount: starts at 1 on Register. AcquireALPRHandle increments,
|
|
|
|
|
// ReleaseALPRHandleRef decrements. Object is destroyed when refcount hits 0.
|
|
|
|
|
static std::unordered_map<ANSCENTER::ANSALPR*, int>& ALPRHandleRegistry() {
|
|
|
|
|
static std::unordered_map<ANSCENTER::ANSALPR*, int> s;
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
static std::mutex& ALPRHandleRegistryMutex() {
|
|
|
|
|
static std::mutex m;
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
static std::condition_variable& ALPRHandleRegistryCV() {
|
|
|
|
|
static std::condition_variable cv;
|
|
|
|
|
return cv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void RegisterALPRHandle(ANSCENTER::ANSALPR* h) {
|
|
|
|
|
std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
|
|
|
|
|
ALPRHandleRegistry()[h] = 1; // refcount = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Acquire a handle for use (increment refcount). Returns the handle
|
|
|
|
|
// if valid, nullptr if already released.
|
|
|
|
|
static ANSCENTER::ANSALPR* AcquireALPRHandle(ANSCENTER::ANSALPR* h) {
|
|
|
|
|
std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
|
|
|
|
|
auto it = ALPRHandleRegistry().find(h);
|
|
|
|
|
if (it == ALPRHandleRegistry().end()) return nullptr;
|
|
|
|
|
it->second++;
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release a use of the handle (decrement refcount).
|
|
|
|
|
// Returns true if this was the last reference (caller should destroy).
|
|
|
|
|
static bool ReleaseALPRHandleRef(ANSCENTER::ANSALPR* h) {
|
|
|
|
|
std::lock_guard<std::mutex> lk(ALPRHandleRegistryMutex());
|
|
|
|
|
auto it = ALPRHandleRegistry().find(h);
|
|
|
|
|
if (it == ALPRHandleRegistry().end()) return false;
|
|
|
|
|
it->second--;
|
|
|
|
|
if (it->second <= 0) {
|
|
|
|
|
ALPRHandleRegistry().erase(it);
|
|
|
|
|
ALPRHandleRegistryCV().notify_all();
|
|
|
|
|
return true; // Caller should destroy
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unregister and wait for all in-flight uses to finish.
|
|
|
|
|
// Decrements the creation refcount and blocks until refcount hits 0.
|
|
|
|
|
// Returns true if caller should destroy the object.
|
|
|
|
|
static bool UnregisterALPRHandle(ANSCENTER::ANSALPR* h) {
|
|
|
|
|
std::unique_lock<std::mutex> lk(ALPRHandleRegistryMutex());
|
|
|
|
|
auto it = ALPRHandleRegistry().find(h);
|
|
|
|
|
if (it == ALPRHandleRegistry().end()) return false;
|
|
|
|
|
it->second--; // Remove creation ref
|
|
|
|
|
// Wait for in-flight inferences to finish (30s timeout as safety net)
|
|
|
|
|
bool ok = ALPRHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() {
|
|
|
|
|
auto it2 = ALPRHandleRegistry().find(h);
|
|
|
|
|
return it2 == ALPRHandleRegistry().end() || it2->second <= 0;
|
|
|
|
|
});
|
|
|
|
|
if (!ok) {
|
|
|
|
|
OutputDebugStringA("WARNING: UnregisterALPRHandle timed out waiting for in-flight inference\n");
|
|
|
|
|
}
|
|
|
|
|
ALPRHandleRegistry().erase(h);
|
|
|
|
|
return true; // Safe to destroy now
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RAII guard — ensures ReleaseALPRHandleRef is always called, preventing
|
|
|
|
|
// refcount leaks that would cause UnregisterALPRHandle to deadlock.
|
|
|
|
|
class ALPRHandleGuard {
|
|
|
|
|
ANSCENTER::ANSALPR* engine;
|
|
|
|
|
public:
|
|
|
|
|
explicit ALPRHandleGuard(ANSCENTER::ANSALPR* e) : engine(e) {}
|
|
|
|
|
~ALPRHandleGuard() { if (engine) ReleaseALPRHandleRef(engine); }
|
|
|
|
|
ANSCENTER::ANSALPR* get() const { return engine; }
|
|
|
|
|
explicit operator bool() const { return engine != nullptr; }
|
|
|
|
|
ALPRHandleGuard(const ALPRHandleGuard&) = delete;
|
|
|
|
|
ALPRHandleGuard& operator=(const ALPRHandleGuard&) = delete;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
// RAII guard — sets the per-thread current GPU frame pointer and always
|
|
|
|
|
// clears it on scope exit, even if the wrapped inference call throws.
|
|
|
|
|
// Without this, a throwing RunInference leaves tl_currentGpuFrame pointing
|
|
|
|
|
// at a GpuFrameData that may be freed before the next call on this thread,
|
|
|
|
|
// causing use-after-free or stale NV12 data on subsequent frames.
|
|
|
|
|
class GpuFrameScope {
|
|
|
|
|
public:
|
|
|
|
|
explicit GpuFrameScope(GpuFrameData* f) { tl_currentGpuFrame() = f; }
|
|
|
|
|
~GpuFrameScope() { tl_currentGpuFrame() = nullptr; }
|
|
|
|
|
GpuFrameScope(const GpuFrameScope&) = delete;
|
|
|
|
|
GpuFrameScope& operator=(const GpuFrameScope&) = delete;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
BOOL APIENTRY DllMain( HMODULE hModule,
|
|
|
|
|
DWORD ul_reason_for_call,
|
|
|
|
|
LPVOID lpReserved
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
switch (ul_reason_for_call)
|
|
|
|
|
{
|
|
|
|
|
case DLL_PROCESS_ATTACH:
|
|
|
|
|
case DLL_THREAD_ATTACH:
|
|
|
|
|
case DLL_THREAD_DETACH:
|
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int CreateANSALPRHandle_Impl(ANSCENTER::ANSALPR** Handle, const char* licenseKey, const char* modelZipFilePath, const char* modelZipPassword,
|
|
|
|
|
int engineType, double detectorThreshold, double ocrThreshold, double colourThreshold) {
|
|
|
|
|
try {
|
|
|
|
|
// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
|
|
|
|
|
ANSCENTER::ANSLibsLoader::Initialize();
|
|
|
|
|
|
|
|
|
|
// Release existing handle if called twice (prevents leak from LabVIEW)
|
|
|
|
|
if (*Handle) {
|
|
|
|
|
if (UnregisterALPRHandle(*Handle)) {
|
|
|
|
|
(*Handle)->Destroy();
|
|
|
|
|
delete *Handle;
|
|
|
|
|
}
|
|
|
|
|
*Handle = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (engineType == 0) {
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSALPR_CPU();// built-in paddle OCR
|
|
|
|
|
}
|
|
|
|
|
else if (engineType == 1) {
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSALPR_OD();
|
|
|
|
|
}
|
2026-04-12 17:16:16 +10:00
|
|
|
else if (engineType == 2) {
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSALPR_OCR();// ONNX OCR (PaddleOCR v5)
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
else {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if (*Handle == nullptr) return 0;
|
|
|
|
|
else {
|
|
|
|
|
RegisterALPRHandle(*Handle);
|
|
|
|
|
int result = (*Handle)->Initialize(licenseKey, modelZipFilePath, modelZipPassword, detectorThreshold, ocrThreshold,colourThreshold);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int CreateANSALPRHandle(ANSCENTER::ANSALPR * *Handle, const char* licenseKey, const char* modelZipFilePath, const char* modelZipPassword,
|
|
|
|
|
int engineType, double detectorThreshold, double ocrThreshold, double colourThreshold) {
|
|
|
|
|
__try {
|
|
|
|
|
return CreateANSALPRHandle_Impl(Handle, licenseKey, modelZipFilePath, modelZipPassword, engineType, detectorThreshold, ocrThreshold, colourThreshold);
|
|
|
|
|
}
|
|
|
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
|
char buf[256];
|
|
|
|
|
snprintf(buf, sizeof(buf), "ANSLPR CreateANSALPRHandle: SEH exception 0x%08X caught during initialization", GetExceptionCode());
|
|
|
|
|
WriteWindowsEventLog(buf);
|
|
|
|
|
if (Handle) *Handle = nullptr;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static int LoadANSALPREngineHandle_Impl(ANSCENTER::ANSALPR** Handle) {
|
|
|
|
|
try {
|
|
|
|
|
if (*Handle != nullptr) {
|
|
|
|
|
int result = (*Handle)->LoadEngine();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int LoadANSALPREngineHandle(ANSCENTER::ANSALPR** Handle) {
|
|
|
|
|
__try {
|
|
|
|
|
return LoadANSALPREngineHandle_Impl(Handle);
|
|
|
|
|
}
|
|
|
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
|
char buf[256];
|
|
|
|
|
snprintf(buf, sizeof(buf), "ANSLPR LoadANSALPREngineHandle: SEH exception 0x%08X caught during engine load", GetExceptionCode());
|
|
|
|
|
WriteWindowsEventLog(buf);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInference(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength) {
|
|
|
|
|
if (!Handle || !*Handle) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_UNCHANGED);
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
bool result = engine->Inference(frame, lprResult);
|
|
|
|
|
frame.release();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInferenceWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId) {
|
|
|
|
|
if (!Handle || !*Handle) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_UNCHANGED);
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
bool result = engine->Inference(frame, lprResult, cameraId);
|
|
|
|
|
frame.release();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInferenceInCroppedImages(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes) {
|
|
|
|
|
if (!Handle || !*Handle) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
bool result = engine->Inference(frame, Bboxes, lprResult);
|
|
|
|
|
frame.release();
|
|
|
|
|
Bboxes.clear();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId) {
|
|
|
|
|
if (!Handle || !*Handle) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
bool result = engine->Inference(frame, Bboxes, lprResult, cameraId);
|
|
|
|
|
frame.release();
|
|
|
|
|
Bboxes.clear();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInferenceBinary(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) {
|
|
|
|
|
if (!Handle || !*Handle || !jpeg_bytes || width == 0 || height == 0) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone();
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
bool result = engine->Inference(frame, lprResult);
|
|
|
|
|
frame.release();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API std::string ANSALPR_RunInferenceBinaryInCroppedImages(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, const char* strBboxes) {
|
|
|
|
|
if (!Handle || !*Handle) return "";
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return "";
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
try {
|
|
|
|
|
cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone();
|
|
|
|
|
if (frame.empty()) return "";
|
|
|
|
|
std::string lprResult;
|
|
|
|
|
std::vector<cv::Rect> Bboxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
bool result = engine->Inference(frame, Bboxes, lprResult);
|
|
|
|
|
frame.release();
|
|
|
|
|
return lprResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int ReleaseANSALPRHandle_Impl(ANSCENTER::ANSALPR** Handle) {
|
|
|
|
|
try {
|
|
|
|
|
if (!Handle || !*Handle) return 1;
|
|
|
|
|
if (!UnregisterALPRHandle(*Handle)) {
|
|
|
|
|
*Handle = nullptr;
|
|
|
|
|
return 1; // Not in registry — already freed
|
|
|
|
|
}
|
|
|
|
|
(*Handle)->Destroy();
|
|
|
|
|
delete *Handle;
|
|
|
|
|
*Handle = nullptr;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
if (Handle) *Handle = nullptr;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ReleaseANSALPRHandle(ANSCENTER::ANSALPR** Handle) {
|
|
|
|
|
__try {
|
|
|
|
|
return ReleaseANSALPRHandle_Impl(Handle);
|
|
|
|
|
}
|
|
|
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//// For LabVIEW API
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInference_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult) {
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInference(Handle, jpeg_string, bufferLength);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId, LStrHandle detectionResult)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceBinary_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceBinary(Handle, jpeg_bytes, width, height);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceInCroppedImages_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, LStrHandle detectionResult) {
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceInCroppedImagesWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV(
|
|
|
|
|
ANSCENTER::ANSALPR** Handle,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int getJpegString,
|
|
|
|
|
int jpegImageSize,
|
|
|
|
|
LStrHandle detectionResult,
|
|
|
|
|
LStrHandle imageStr)
|
|
|
|
|
{
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
|
|
|
|
|
|
|
|
|
int originalWidth = localImage.cols;
|
|
|
|
|
int originalHeight = localImage.rows;
|
|
|
|
|
|
|
|
|
|
if (originalWidth == 0 || originalHeight == 0) {
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
std::vector<ANSCENTER::Object> outputs;
|
|
|
|
|
{
|
|
|
|
|
// Scoped NV12 fast-path pointer; cleared on any exit path (normal or
|
|
|
|
|
// throw) so the next call on this thread cannot see a stale frame.
|
|
|
|
|
GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
|
|
|
|
|
outputs = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
bool getJpeg = (getJpegString == 1);
|
|
|
|
|
std::string stImage;
|
|
|
|
|
|
|
|
|
|
int maxImageSize = originalWidth;
|
|
|
|
|
bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);
|
|
|
|
|
|
|
|
|
|
float ratio = 1.0f;
|
|
|
|
|
int newWidth = originalWidth;
|
|
|
|
|
int newHeight = originalHeight;
|
|
|
|
|
|
|
|
|
|
if (resizeNeeded) {
|
|
|
|
|
newWidth = jpegImageSize;
|
|
|
|
|
newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
|
|
|
|
|
ratio = static_cast<float>(newWidth) / originalWidth;
|
|
|
|
|
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert to JPEG only if requested (avoid expensive encode when not needed)
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
cv::Mat processedImage;
|
|
|
|
|
if (resizeNeeded) {
|
|
|
|
|
cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
processedImage = localImage; // shallow copy (header only, no pixel copy)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uchar> buf;
|
|
|
|
|
if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
|
|
|
|
|
stImage.assign(buf.begin(), buf.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string stDetectionResult = engine->VectorDetectionToJsonString(outputs);
|
|
|
|
|
if (stDetectionResult.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
int size = static_cast<int>(stDetectionResult.length());
|
|
|
|
|
MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);
|
|
|
|
|
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
if (stImage.empty()) return 0;
|
|
|
|
|
size = static_cast<int>(stImage.length());
|
|
|
|
|
error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*imageStr)->cnt = size;
|
|
|
|
|
memcpy((*imageStr)->str, stImage.c_str(), size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_CPP(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize, std::string& detectionResult, std::string& imageStr) {
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
|
|
|
|
|
|
|
|
|
int originalWidth = localImage.cols;
|
|
|
|
|
int originalHeight = localImage.rows;
|
|
|
|
|
int maxImageSize = originalWidth;
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
std::vector<ANSCENTER::Object> outputs;
|
|
|
|
|
{
|
|
|
|
|
// Scoped NV12 fast-path pointer; cleared on any exit path (normal or throw).
|
|
|
|
|
GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
|
|
|
|
|
outputs = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
bool getJpeg = (getJpegString == 1);
|
|
|
|
|
std::string stImage;
|
|
|
|
|
|
|
|
|
|
if ((jpegImageSize > 0) && (jpegImageSize < maxImageSize)) {
|
|
|
|
|
int newWidth = jpegImageSize;
|
|
|
|
|
int newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
|
|
|
|
|
float ratio = static_cast<float>(newWidth) / originalWidth;
|
|
|
|
|
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
cv::Mat processedImage;
|
|
|
|
|
if ((jpegImageSize > 0) && (jpegImageSize < maxImageSize)) {
|
|
|
|
|
int newWidth = jpegImageSize;
|
|
|
|
|
int newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
|
|
|
|
|
cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
processedImage = localImage; // shallow copy (header only)
|
|
|
|
|
}
|
|
|
|
|
std::vector<uchar> buf;
|
|
|
|
|
if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
|
|
|
|
|
stImage.assign(buf.begin(), buf.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
detectionResult = engine->VectorDetectionToJsonString(outputs);
|
|
|
|
|
|
|
|
|
|
if (detectionResult.empty()) return 0;
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
if (stImage.empty()) return 0;
|
|
|
|
|
imageStr = stImage;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV(
|
|
|
|
|
ANSCENTER::ANSALPR** Handle,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int maxImageSize,
|
|
|
|
|
const char* strBboxes,
|
|
|
|
|
LStrHandle detectionResult)
|
|
|
|
|
{
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
|
|
|
|
|
|
|
|
|
std::vector<ANSCENTER::Object> objectDetectionResults;
|
|
|
|
|
std::vector<cv::Rect> bBox = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
|
|
|
|
|
const int originalWidth = localImage.cols;
|
|
|
|
|
const int originalHeight = localImage.rows;
|
|
|
|
|
|
|
|
|
|
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;
|
|
|
|
|
|
|
|
|
|
if (bBox.empty()) {
|
2026-04-15 09:23:05 +10:00
|
|
|
// Full-frame path: NV12 fast-path is only safe for the full-frame
|
|
|
|
|
// inference call. Scope the TL pointer so it is cleared on any exit
|
|
|
|
|
// path (normal or thrown) and is NOT seen by any subsequent
|
|
|
|
|
// cropped-image inference (which would mismatch the NV12 cache).
|
|
|
|
|
GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
|
2026-03-28 16:54:11 +11:00
|
|
|
objectDetectionResults = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-04-15 09:23:05 +10:00
|
|
|
else {
|
2026-03-28 16:54:11 +11:00
|
|
|
for (const auto& rect : bBox) {
|
|
|
|
|
cv::Rect scaledRect;
|
|
|
|
|
scaledRect.x = static_cast<int>(rect.x * scaleFactor);
|
|
|
|
|
scaledRect.y = static_cast<int>(rect.y * scaleFactor);
|
|
|
|
|
scaledRect.width = static_cast<int>(rect.width * scaleFactor);
|
|
|
|
|
scaledRect.height = static_cast<int>(rect.height * scaleFactor);
|
|
|
|
|
|
|
|
|
|
scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
|
|
|
|
|
if (scaledRect.width <= 0 || scaledRect.height <= 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const cv::Mat croppedImage = localImage(scaledRect);
|
|
|
|
|
std::vector<ANSCENTER::Object> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);
|
|
|
|
|
|
|
|
|
|
for (auto& obj : croppedDetectionResults) {
|
|
|
|
|
obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
|
|
|
|
|
obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
|
|
|
|
|
obj.box.width /= scaleFactor;
|
|
|
|
|
obj.box.height /= scaleFactor;
|
|
|
|
|
|
|
|
|
|
objectDetectionResults.push_back(std::move(obj));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string stDetectionResult = engine->VectorDetectionToJsonString(objectDetectionResults);
|
|
|
|
|
if (stDetectionResult.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
const int size = static_cast<int>(stDetectionResult.length());
|
|
|
|
|
MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
// Dedicated pipeline-mode export. Unlike ANSALPR_RunInferencesComplete_LV
|
|
|
|
|
// this function:
|
|
|
|
|
// 1. Always runs with tracker OFF, voting OFF, dedup OFF — it targets
|
|
|
|
|
// callers that already have precise per-vehicle bboxes and want raw,
|
|
|
|
|
// stateless results.
|
|
|
|
|
// 2. Issues ONE batched LP-detect call and ONE batched recognizer call
|
|
|
|
|
// per frame via ANSALPR::RunInferencesBatch, instead of looping
|
|
|
|
|
// engine->RunInference once per crop. This eliminates the per-shape
|
|
|
|
|
// ORT/TRT allocator churn that causes ANSALPR_OCR memory growth when
|
|
|
|
|
// the legacy pipeline-mode loop is used under LabVIEW worker threads.
|
|
|
|
|
// 3. Does NOT touch tl_currentGpuFrame — the caller is working with
|
|
|
|
|
// cropped regions, not the NV12-keyed source Mat, so the fast-path
|
|
|
|
|
// pointer is meaningless here. Eliminates a class of UAF risk.
|
|
|
|
|
// Output coordinates are rescaled back into the caller's resized space,
|
|
|
|
|
// matching the convention used by ANSALPR_RunInferencesComplete_LV.
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferencesBatch_LV(
|
|
|
|
|
ANSCENTER::ANSALPR** Handle,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int maxImageSize,
|
|
|
|
|
const char* strBboxes,
|
|
|
|
|
LStrHandle detectionResult)
|
|
|
|
|
{
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(*Handle));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& frame = **cvImage;
|
|
|
|
|
const int frameW = frame.cols;
|
|
|
|
|
const int frameH = frame.rows;
|
|
|
|
|
if (frameW <= 0 || frameH <= 0) return -2;
|
|
|
|
|
|
|
|
|
|
// Same scaling convention as ANSALPR_RunInferencesComplete_LV:
|
|
|
|
|
// the bboxes in `strBboxes` are in a resized coordinate space;
|
|
|
|
|
// scale them up to the full frame for the crop, then rescale the
|
|
|
|
|
// plate outputs back to the caller's space.
|
|
|
|
|
const double scale =
|
|
|
|
|
(maxImageSize > 0) ? static_cast<double>(frameW) / maxImageSize : 1.0;
|
|
|
|
|
|
|
|
|
|
std::vector<cv::Rect> rawBoxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
std::vector<cv::Rect> scaledBoxes;
|
|
|
|
|
scaledBoxes.reserve(rawBoxes.size());
|
|
|
|
|
const cv::Rect frameRect(0, 0, frameW, frameH);
|
|
|
|
|
for (const auto& r : rawBoxes) {
|
|
|
|
|
cv::Rect s(
|
|
|
|
|
static_cast<int>(r.x * scale),
|
|
|
|
|
static_cast<int>(r.y * scale),
|
|
|
|
|
static_cast<int>(r.width * scale),
|
|
|
|
|
static_cast<int>(r.height * scale));
|
|
|
|
|
s &= frameRect;
|
|
|
|
|
if (s.width > 0 && s.height > 0) scaledBoxes.push_back(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Empty bbox list → fall through to full-frame RunInference so
|
|
|
|
|
// existing LabVIEW code that passes "" still works, matching the
|
|
|
|
|
// behaviour of ANSALPR_RunInferencesComplete_LV.
|
|
|
|
|
std::vector<ANSCENTER::Object> results;
|
|
|
|
|
if (scaledBoxes.empty()) {
|
|
|
|
|
results = engine->RunInference(frame, cameraId);
|
|
|
|
|
} else {
|
|
|
|
|
results = engine->RunInferencesBatch(frame, scaledBoxes, cameraId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rescale plate boxes back into the caller's resized space.
|
|
|
|
|
if (scale != 1.0) {
|
|
|
|
|
const double inv = 1.0 / scale;
|
|
|
|
|
for (auto& o : results) {
|
|
|
|
|
o.box.x = static_cast<int>(o.box.x * inv);
|
|
|
|
|
o.box.y = static_cast<int>(o.box.y * inv);
|
|
|
|
|
o.box.width = static_cast<int>(o.box.width * inv);
|
|
|
|
|
o.box.height = static_cast<int>(o.box.height * inv);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string json = engine->VectorDetectionToJsonString(results);
|
|
|
|
|
if (json.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
const int size = static_cast<int>(json.length());
|
|
|
|
|
MgErr err = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (err != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, json.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& /*ex*/) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_SetFormat(ANSCENTER::ANSALPR** Handle, const char* format) {
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!format) return -1;
|
|
|
|
|
try {
|
|
|
|
|
std::string strFormat(format);
|
|
|
|
|
(*Handle)->SetPlateFormat(strFormat);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_SetFormats(ANSCENTER::ANSALPR** Handle, const char* formats){// semi separated formats
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!formats) return -1;
|
|
|
|
|
try {
|
|
|
|
|
std::vector<std::string> formatList;
|
|
|
|
|
std::string token;
|
|
|
|
|
std::istringstream tokenStream(formats);
|
|
|
|
|
while (getline(tokenStream, token, ';')) {
|
|
|
|
|
formatList.push_back(token);
|
|
|
|
|
}
|
|
|
|
|
(*Handle)->SetPlateFormats(formatList);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 11:55:37 +10:00
|
|
|
// ALPRChecker: 1 = enabled (full-frame auto-detected), 0 = disabled (raw OCR)
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_SetALPRCheckerEnabled(ANSCENTER::ANSALPR** Handle, int enable) {
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetALPRCheckerEnabled(enable != 0);
|
|
|
|
|
ANS_DBG("ALPR_Checker", "SetALPRCheckerEnabled=%d (%s)",
|
|
|
|
|
enable, enable ? "ON" : "OFF");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 17:16:16 +10:00
|
|
|
extern "C" ANSLPR_API int ANSALPR_SetCountry(ANSCENTER::ANSALPR** Handle, int country) {
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetCountry(static_cast<ANSCENTER::Country>(country));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
extern "C" ANSLPR_API int ANSALPR_GetFormats(ANSCENTER::ANSALPR** Handle, LStrHandle Lstrformats)// semi separated formats
|
|
|
|
|
{
|
|
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
if (!Lstrformats) return -1;
|
|
|
|
|
try {
|
|
|
|
|
std::vector<std::string> formatList = (*Handle)->GetPlateFormats();
|
|
|
|
|
std::string formats;
|
|
|
|
|
for (const auto& format : formatList) {
|
|
|
|
|
formats += format + ";";
|
|
|
|
|
}
|
|
|
|
|
if (!formats.empty()) {
|
|
|
|
|
formats.pop_back(); // Remove the last semicolon
|
|
|
|
|
}
|
|
|
|
|
int size = static_cast<int>(formats.length());
|
|
|
|
|
MgErr error = DSSetHandleSize(Lstrformats, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
(*Lstrformats)->cnt = size;
|
|
|
|
|
memcpy((*Lstrformats)->str, formats.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 14:10:21 +11:00
|
|
|
// Unicode conversion utilities for LabVIEW wrapper classes
|
2026-03-31 21:52:47 +11:00
|
|
|
extern "C" ANSLPR_API int ANSLPR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM) {
|
2026-03-31 14:10:21 +11:00
|
|
|
try {
|
|
|
|
|
if (!utf8Str || !result) return -1;
|
|
|
|
|
int len = (int)strlen(utf8Str);
|
|
|
|
|
if (len == 0) return 0;
|
2026-03-31 21:52:47 +11:00
|
|
|
const char bom[2] = { '\xFF', '\xFE' };
|
2026-03-31 14:10:21 +11:00
|
|
|
bool hasUnicodeEscapes = false;
|
2026-03-31 21:52:47 +11:00
|
|
|
for (int i = 0; i + 1 < len; i++) {
|
|
|
|
|
if (utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') { hasUnicodeEscapes = true; break; }
|
2026-03-31 14:10:21 +11:00
|
|
|
}
|
|
|
|
|
if (hasUnicodeEscapes) {
|
|
|
|
|
std::string utf16le;
|
2026-03-31 21:52:47 +11:00
|
|
|
if (includeBOM) utf16le.assign(bom, 2);
|
|
|
|
|
utf16le.reserve(len * 2 + 2);
|
2026-03-31 14:10:21 +11:00
|
|
|
for (int i = 0; i < len; ) {
|
|
|
|
|
if (i + 5 < len && utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') {
|
|
|
|
|
char hex[5] = { utf8Str[i + 2], utf8Str[i + 3], utf8Str[i + 4], utf8Str[i + 5], 0 };
|
|
|
|
|
uint16_t cp = (uint16_t)strtoul(hex, nullptr, 16);
|
|
|
|
|
utf16le += static_cast<char>(cp & 0xFF);
|
|
|
|
|
utf16le += static_cast<char>((cp >> 8) & 0xFF);
|
|
|
|
|
i += 6;
|
|
|
|
|
} else {
|
|
|
|
|
utf16le += utf8Str[i];
|
|
|
|
|
utf16le += '\0';
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int size = (int)utf16le.size();
|
|
|
|
|
MgErr error = DSSetHandleSize(result, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return -2;
|
|
|
|
|
(*result)->cnt = size;
|
|
|
|
|
memcpy((*result)->str, utf16le.data(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, nullptr, 0);
|
|
|
|
|
if (wideLen <= 0) return 0;
|
|
|
|
|
std::wstring wideStr(wideLen, 0);
|
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, &wideStr[0], wideLen);
|
2026-03-31 21:52:47 +11:00
|
|
|
int dataSize = wideLen * (int)sizeof(wchar_t);
|
|
|
|
|
int bomSize = includeBOM ? 2 : 0;
|
|
|
|
|
int totalSize = bomSize + dataSize;
|
|
|
|
|
MgErr error = DSSetHandleSize(result, sizeof(int32) + totalSize * sizeof(uChar));
|
2026-03-31 14:10:21 +11:00
|
|
|
if (error != noErr) return -2;
|
2026-03-31 21:52:47 +11:00
|
|
|
(*result)->cnt = totalSize;
|
|
|
|
|
if (includeBOM) memcpy((*result)->str, bom, 2);
|
|
|
|
|
memcpy((*result)->str + bomSize, wideStr.data(), dataSize);
|
2026-03-31 14:10:21 +11:00
|
|
|
return 1;
|
|
|
|
|
#else
|
|
|
|
|
return 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSLPR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result) {
|
|
|
|
|
try {
|
|
|
|
|
if (!utf16leBytes || byteLen <= 0 || !result) return -1;
|
|
|
|
|
bool isUtf16le = (byteLen >= 2 && byteLen % 2 == 0);
|
|
|
|
|
if (isUtf16le) {
|
|
|
|
|
bool isAscii = true;
|
|
|
|
|
for (int i = 1; i < byteLen; i += 2) {
|
|
|
|
|
if (utf16leBytes[i] != 0x00) { isAscii = false; break; }
|
|
|
|
|
}
|
|
|
|
|
if (isAscii) {
|
|
|
|
|
int asciiLen = byteLen / 2;
|
|
|
|
|
MgErr error = DSSetHandleSize(result, sizeof(int32) + asciiLen * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return -2;
|
|
|
|
|
(*result)->cnt = asciiLen;
|
|
|
|
|
for (int i = 0; i < asciiLen; i++) (*result)->str[i] = utf16leBytes[i * 2];
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
int wideLen = byteLen / (int)sizeof(wchar_t);
|
|
|
|
|
const wchar_t* wideStr = reinterpret_cast<const wchar_t*>(utf16leBytes);
|
|
|
|
|
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, nullptr, 0, nullptr, nullptr);
|
|
|
|
|
if (utf8Len <= 0) return 0;
|
|
|
|
|
std::string utf8Str(utf8Len, 0);
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, &utf8Str[0], utf8Len, nullptr, nullptr);
|
|
|
|
|
MgErr error = DSSetHandleSize(result, sizeof(int32) + utf8Len * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return -2;
|
|
|
|
|
(*result)->cnt = utf8Len;
|
|
|
|
|
memcpy((*result)->str, utf8Str.data(), utf8Len);
|
|
|
|
|
return 1;
|
|
|
|
|
#else
|
|
|
|
|
return 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
// ============================================================================
|
|
|
|
|
// V2 API — accepts uint64_t handle by value (eliminates LabVIEW buffer reuse bug)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
#define ALPR_V2_HANDLE_SETUP(handleVal) \
|
|
|
|
|
ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal); \
|
|
|
|
|
if (_v2Direct == nullptr) return 0; \
|
|
|
|
|
ANSCENTER::ANSALPR* _v2Arr[1] = { _v2Direct }; \
|
|
|
|
|
ANSCENTER::ANSALPR** Handle = &_v2Arr[0];
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInference_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult) {
|
|
|
|
|
ALPR_V2_HANDLE_SETUP(handleVal);
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInference(Handle, jpeg_string, bufferLength);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceWithCamID_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId, LStrHandle detectionResult) {
|
|
|
|
|
ALPR_V2_HANDLE_SETUP(handleVal);
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceBinary_LV_V2(uint64_t handleVal, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
|
|
|
|
|
ALPR_V2_HANDLE_SETUP(handleVal);
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceBinary(Handle, jpeg_bytes, width, height);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceInCroppedImages_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, LStrHandle detectionResult) {
|
|
|
|
|
ALPR_V2_HANDLE_SETUP(handleVal);
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceInCroppedImagesWithCamID_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
|
|
|
|
|
ALPR_V2_HANDLE_SETUP(handleVal);
|
|
|
|
|
try {
|
|
|
|
|
std::string st = ANSALPR_RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
|
|
|
|
|
if (st.empty()) return 0;
|
|
|
|
|
int size = static_cast<int>(st.length());
|
|
|
|
|
MgErr error;
|
|
|
|
|
error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error == noErr)
|
|
|
|
|
{
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, st.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV_V2(
|
|
|
|
|
uint64_t handleVal,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int getJpegString,
|
|
|
|
|
int jpegImageSize,
|
|
|
|
|
LStrHandle detectionResult,
|
|
|
|
|
LStrHandle imageStr)
|
|
|
|
|
{
|
|
|
|
|
ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal);
|
|
|
|
|
if (_v2Direct == nullptr) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
|
|
|
|
|
|
|
|
|
int originalWidth = localImage.cols;
|
|
|
|
|
int originalHeight = localImage.rows;
|
|
|
|
|
|
|
|
|
|
if (originalWidth == 0 || originalHeight == 0) {
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
std::vector<ANSCENTER::Object> outputs;
|
|
|
|
|
{
|
|
|
|
|
// Scoped NV12 fast-path pointer; cleared on any exit path (normal or throw).
|
|
|
|
|
GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
|
|
|
|
|
outputs = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
bool getJpeg = (getJpegString == 1);
|
|
|
|
|
std::string stImage;
|
|
|
|
|
|
|
|
|
|
int maxImageSize = originalWidth;
|
|
|
|
|
bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);
|
|
|
|
|
|
|
|
|
|
float ratio = 1.0f;
|
|
|
|
|
int newWidth = originalWidth;
|
|
|
|
|
int newHeight = originalHeight;
|
|
|
|
|
|
|
|
|
|
if (resizeNeeded) {
|
|
|
|
|
newWidth = jpegImageSize;
|
|
|
|
|
newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
|
|
|
|
|
ratio = static_cast<float>(newWidth) / originalWidth;
|
|
|
|
|
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (auto& obj : outputs) {
|
|
|
|
|
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
|
|
|
|
|
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
|
|
|
|
|
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
|
|
|
|
|
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
cv::Mat processedImage;
|
|
|
|
|
if (resizeNeeded) {
|
|
|
|
|
cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
processedImage = localImage; // shallow copy (header only)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uchar> buf;
|
|
|
|
|
if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
|
|
|
|
|
stImage.assign(buf.begin(), buf.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string stDetectionResult = engine->VectorDetectionToJsonString(outputs);
|
|
|
|
|
if (stDetectionResult.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
int size = static_cast<int>(stDetectionResult.length());
|
|
|
|
|
MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);
|
|
|
|
|
|
|
|
|
|
if (getJpeg) {
|
|
|
|
|
if (stImage.empty()) return 0;
|
|
|
|
|
size = static_cast<int>(stImage.length());
|
|
|
|
|
error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*imageStr)->cnt = size;
|
|
|
|
|
memcpy((*imageStr)->str, stImage.c_str(), size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
// V2 uint64_t handle variant of ANSALPR_RunInferencesBatch_LV.
|
|
|
|
|
// See the non-V2 version for semantics — identical behaviour, differs only
|
|
|
|
|
// in how the caller passes the ALPR engine handle (by value instead of via
|
|
|
|
|
// a LabVIEW Handle** pointer-to-pointer).
|
|
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferencesBatch_LV_V2(
|
|
|
|
|
uint64_t handleVal,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int maxImageSize,
|
|
|
|
|
const char* strBboxes,
|
|
|
|
|
LStrHandle detectionResult)
|
|
|
|
|
{
|
|
|
|
|
ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal);
|
|
|
|
|
if (_v2Direct == nullptr) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& frame = **cvImage;
|
|
|
|
|
const int frameW = frame.cols;
|
|
|
|
|
const int frameH = frame.rows;
|
|
|
|
|
if (frameW <= 0 || frameH <= 0) return -2;
|
|
|
|
|
|
|
|
|
|
const double scale =
|
|
|
|
|
(maxImageSize > 0) ? static_cast<double>(frameW) / maxImageSize : 1.0;
|
|
|
|
|
|
|
|
|
|
std::vector<cv::Rect> rawBoxes = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
std::vector<cv::Rect> scaledBoxes;
|
|
|
|
|
scaledBoxes.reserve(rawBoxes.size());
|
|
|
|
|
const cv::Rect frameRect(0, 0, frameW, frameH);
|
|
|
|
|
for (const auto& r : rawBoxes) {
|
|
|
|
|
cv::Rect s(
|
|
|
|
|
static_cast<int>(r.x * scale),
|
|
|
|
|
static_cast<int>(r.y * scale),
|
|
|
|
|
static_cast<int>(r.width * scale),
|
|
|
|
|
static_cast<int>(r.height * scale));
|
|
|
|
|
s &= frameRect;
|
|
|
|
|
if (s.width > 0 && s.height > 0) scaledBoxes.push_back(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<ANSCENTER::Object> results;
|
|
|
|
|
if (scaledBoxes.empty()) {
|
|
|
|
|
results = engine->RunInference(frame, cameraId);
|
|
|
|
|
} else {
|
|
|
|
|
results = engine->RunInferencesBatch(frame, scaledBoxes, cameraId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (scale != 1.0) {
|
|
|
|
|
const double inv = 1.0 / scale;
|
|
|
|
|
for (auto& o : results) {
|
|
|
|
|
o.box.x = static_cast<int>(o.box.x * inv);
|
|
|
|
|
o.box.y = static_cast<int>(o.box.y * inv);
|
|
|
|
|
o.box.width = static_cast<int>(o.box.width * inv);
|
|
|
|
|
o.box.height = static_cast<int>(o.box.height * inv);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string json = engine->VectorDetectionToJsonString(results);
|
|
|
|
|
if (json.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
const int size = static_cast<int>(json.length());
|
|
|
|
|
MgErr err = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (err != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, json.c_str(), size);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& /*ex*/) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV_V2(
|
|
|
|
|
uint64_t handleVal,
|
|
|
|
|
cv::Mat** cvImage,
|
|
|
|
|
const char* cameraId,
|
|
|
|
|
int maxImageSize,
|
|
|
|
|
const char* strBboxes,
|
|
|
|
|
LStrHandle detectionResult)
|
|
|
|
|
{
|
|
|
|
|
ANSCENTER::ANSALPR* _v2Direct = reinterpret_cast<ANSCENTER::ANSALPR*>(handleVal);
|
|
|
|
|
if (_v2Direct == nullptr) return -1;
|
|
|
|
|
if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;
|
|
|
|
|
|
|
|
|
|
ALPRHandleGuard guard(AcquireALPRHandle(_v2Direct));
|
|
|
|
|
if (!guard) return -3;
|
|
|
|
|
auto* engine = guard.get();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
|
|
|
|
|
|
|
|
|
std::vector<ANSCENTER::Object> objectDetectionResults;
|
|
|
|
|
std::vector<cv::Rect> bBox = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
|
|
|
|
|
|
|
|
|
const int originalWidth = localImage.cols;
|
|
|
|
|
const int originalHeight = localImage.rows;
|
|
|
|
|
|
|
|
|
|
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;
|
|
|
|
|
|
|
|
|
|
if (bBox.empty()) {
|
2026-04-15 09:23:05 +10:00
|
|
|
// Full-frame path: NV12 fast-path is only safe for the full-frame
|
|
|
|
|
// inference call. Scope the TL pointer so it is cleared on any exit
|
|
|
|
|
// path (normal or thrown) and is NOT seen by any subsequent
|
|
|
|
|
// cropped-image inference (which would mismatch the NV12 cache).
|
|
|
|
|
GpuFrameScope _gfs(ANSGpuFrameRegistry::instance().lookup(*cvImage));
|
2026-03-28 16:54:11 +11:00
|
|
|
objectDetectionResults = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-04-15 09:23:05 +10:00
|
|
|
else {
|
2026-03-28 16:54:11 +11:00
|
|
|
for (const auto& rect : bBox) {
|
|
|
|
|
cv::Rect scaledRect;
|
|
|
|
|
scaledRect.x = static_cast<int>(rect.x * scaleFactor);
|
|
|
|
|
scaledRect.y = static_cast<int>(rect.y * scaleFactor);
|
|
|
|
|
scaledRect.width = static_cast<int>(rect.width * scaleFactor);
|
|
|
|
|
scaledRect.height = static_cast<int>(rect.height * scaleFactor);
|
|
|
|
|
|
|
|
|
|
scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
|
|
|
|
|
if (scaledRect.width <= 0 || scaledRect.height <= 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const cv::Mat croppedImage = localImage(scaledRect);
|
|
|
|
|
std::vector<ANSCENTER::Object> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);
|
|
|
|
|
|
|
|
|
|
for (auto& obj : croppedDetectionResults) {
|
|
|
|
|
obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
|
|
|
|
|
obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
|
|
|
|
|
obj.box.width /= scaleFactor;
|
|
|
|
|
obj.box.height /= scaleFactor;
|
|
|
|
|
|
|
|
|
|
objectDetectionResults.push_back(std::move(obj));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string stDetectionResult = engine->VectorDetectionToJsonString(objectDetectionResults);
|
|
|
|
|
if (stDetectionResult.empty()) return 0;
|
|
|
|
|
|
|
|
|
|
const int size = static_cast<int>(stDetectionResult.length());
|
|
|
|
|
MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
|
|
|
|
|
if (error != noErr) return 0;
|
|
|
|
|
|
|
|
|
|
(*detectionResult)->cnt = size;
|
|
|
|
|
memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|