Fix ALPR Batch and memory leak
This commit is contained in:
@@ -104,6 +104,19 @@ public:
|
||||
ALPRHandleGuard& operator=(const ALPRHandleGuard&) = delete;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
BOOL APIENTRY DllMain( HMODULE hModule,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved
|
||||
@@ -465,9 +478,6 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV(
|
||||
|
||||
try {
|
||||
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
||||
// Set thread-local NV12 frame data for fast-path inference
|
||||
// Cleared after first RunInference to prevent NV12 mismatch on cropped sub-images (OCR, etc.)
|
||||
tl_currentGpuFrame() = ANSGpuFrameRegistry::instance().lookup(*cvImage);
|
||||
|
||||
int originalWidth = localImage.cols;
|
||||
int originalHeight = localImage.rows;
|
||||
@@ -476,8 +486,13 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV(
|
||||
return -2;
|
||||
}
|
||||
|
||||
std::vector<ANSCENTER::Object> outputs = engine->RunInference(localImage, cameraId);
|
||||
tl_currentGpuFrame() = nullptr;
|
||||
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);
|
||||
}
|
||||
|
||||
bool getJpeg = (getJpegString == 1);
|
||||
std::string stImage;
|
||||
@@ -567,15 +582,17 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_CPP(ANSCENTER::ANSALP
|
||||
|
||||
try {
|
||||
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
||||
// Set thread-local NV12 frame data for fast-path inference
|
||||
// Cleared after first RunInference to prevent NV12 mismatch on cropped sub-images (OCR, etc.)
|
||||
tl_currentGpuFrame() = ANSGpuFrameRegistry::instance().lookup(*cvImage);
|
||||
|
||||
int originalWidth = localImage.cols;
|
||||
int originalHeight = localImage.rows;
|
||||
int maxImageSize = originalWidth;
|
||||
|
||||
std::vector<ANSCENTER::Object> outputs = engine->RunInference(localImage, cameraId);
|
||||
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);
|
||||
}
|
||||
bool getJpeg = (getJpegString == 1);
|
||||
std::string stImage;
|
||||
|
||||
@@ -646,9 +663,6 @@ extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV(
|
||||
|
||||
try {
|
||||
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
||||
// Set thread-local NV12 frame data for fast-path inference
|
||||
// Cleared after first RunInference to prevent NV12 mismatch on cropped sub-images (OCR, etc.)
|
||||
tl_currentGpuFrame() = ANSGpuFrameRegistry::instance().lookup(*cvImage);
|
||||
|
||||
std::vector<ANSCENTER::Object> objectDetectionResults;
|
||||
std::vector<cv::Rect> bBox = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
||||
@@ -659,10 +673,14 @@ extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV(
|
||||
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;
|
||||
|
||||
if (bBox.empty()) {
|
||||
// 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));
|
||||
objectDetectionResults = engine->RunInference(localImage, cameraId);
|
||||
}
|
||||
tl_currentGpuFrame() = nullptr; // Clear before crop-based inference
|
||||
if (!bBox.empty()) {
|
||||
else {
|
||||
for (const auto& rect : bBox) {
|
||||
cv::Rect scaledRect;
|
||||
scaledRect.x = static_cast<int>(rect.x * scaleFactor);
|
||||
@@ -708,6 +726,103 @@ extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV(
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" ANSLPR_API int ANSALPR_SetFormat(ANSCENTER::ANSALPR** Handle, const char* format) {
|
||||
if (!Handle || !*Handle) return -1;
|
||||
@@ -1042,9 +1157,6 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV_V2(
|
||||
|
||||
try {
|
||||
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
||||
// Set thread-local NV12 frame data for fast-path inference
|
||||
// Cleared after first RunInference to prevent NV12 mismatch on cropped sub-images (OCR, etc.)
|
||||
tl_currentGpuFrame() = ANSGpuFrameRegistry::instance().lookup(*cvImage);
|
||||
|
||||
int originalWidth = localImage.cols;
|
||||
int originalHeight = localImage.rows;
|
||||
@@ -1053,8 +1165,12 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV_V2(
|
||||
return -2;
|
||||
}
|
||||
|
||||
std::vector<ANSCENTER::Object> outputs = engine->RunInference(localImage, cameraId);
|
||||
tl_currentGpuFrame() = nullptr;
|
||||
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);
|
||||
}
|
||||
|
||||
bool getJpeg = (getJpegString == 1);
|
||||
std::string stImage;
|
||||
@@ -1132,6 +1248,85 @@ extern "C" ANSLPR_API int ANSALPR_RunInferenceComplete_LV_V2(
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV_V2(
|
||||
uint64_t handleVal,
|
||||
cv::Mat** cvImage,
|
||||
@@ -1150,9 +1345,6 @@ extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV_V2(
|
||||
|
||||
try {
|
||||
const cv::Mat& localImage = **cvImage; // No clone — RunInference takes const ref
|
||||
// Set thread-local NV12 frame data for fast-path inference
|
||||
// Cleared after first RunInference to prevent NV12 mismatch on cropped sub-images (OCR, etc.)
|
||||
tl_currentGpuFrame() = ANSGpuFrameRegistry::instance().lookup(*cvImage);
|
||||
|
||||
std::vector<ANSCENTER::Object> objectDetectionResults;
|
||||
std::vector<cv::Rect> bBox = ANSCENTER::ANSALPR::GetBoundingBoxes(strBboxes);
|
||||
@@ -1163,10 +1355,14 @@ extern "C" ANSLPR_API int ANSALPR_RunInferencesComplete_LV_V2(
|
||||
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;
|
||||
|
||||
if (bBox.empty()) {
|
||||
// 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));
|
||||
objectDetectionResults = engine->RunInference(localImage, cameraId);
|
||||
}
|
||||
tl_currentGpuFrame() = nullptr; // Clear before crop-based inference
|
||||
if (!bBox.empty()) {
|
||||
else {
|
||||
for (const auto& rect : bBox) {
|
||||
cv::Rect scaledRect;
|
||||
scaledRect.x = static_cast<int>(rect.x * scaleFactor);
|
||||
|
||||
Reference in New Issue
Block a user