Fix ALPR Batch and memory leak
This commit is contained in:
@@ -953,6 +953,163 @@ namespace ANSCENTER
|
||||
return {};
|
||||
}
|
||||
|
||||
// ── Stateless batched inference for pipeline mode ───────────────────
|
||||
// Caller supplies a full frame + a list of vehicle ROIs in FRAME
|
||||
// coordinates. We run ONE LP-detect call across all vehicle crops and
|
||||
// ONE text-recognizer call across every resulting plate (with the same
|
||||
// 2-row split heuristic as ANSALPR_OCR::RunInference), and NO tracker,
|
||||
// voting, spatial dedup, or per-camera accumulating state. This is the
|
||||
// drop-in replacement for the per-bbox loop inside
|
||||
// ANSALPR_RunInferencesComplete_LV (pipeline mode) and is exported as
|
||||
// ANSALPR_RunInferencesBatch_LV / _V2 in dllmain.cpp. Calling this on
|
||||
// ANSALPR_OCR avoids the ORT/TRT per-shape allocator churn that
|
||||
// causes unbounded memory growth when the loop version is used.
|
||||
std::vector<Object> ANSALPR_OCR::RunInferencesBatch(
|
||||
const cv::Mat& input,
|
||||
const std::vector<cv::Rect>& vehicleBoxes,
|
||||
const std::string& cameraId)
|
||||
{
|
||||
if (!_licenseValid) {
|
||||
this->_logger.LogError("ANSALPR_OCR::RunInferencesBatch", "Invalid license", __FILE__, __LINE__);
|
||||
return {};
|
||||
}
|
||||
if (!_isInitialized) {
|
||||
this->_logger.LogError("ANSALPR_OCR::RunInferencesBatch", "Model is not initialized", __FILE__, __LINE__);
|
||||
return {};
|
||||
}
|
||||
if (input.empty() || input.cols < 5 || input.rows < 5) return {};
|
||||
if (!_lpDetector) {
|
||||
this->_logger.LogFatal("ANSALPR_OCR::RunInferencesBatch", "_lpDetector is null", __FILE__, __LINE__);
|
||||
return {};
|
||||
}
|
||||
if (!_ocrEngine) {
|
||||
this->_logger.LogFatal("ANSALPR_OCR::RunInferencesBatch", "_ocrEngine is null", __FILE__, __LINE__);
|
||||
return {};
|
||||
}
|
||||
if (vehicleBoxes.empty()) return {};
|
||||
|
||||
try {
|
||||
// Promote grayscale input to BGR once (matches RunInference).
|
||||
cv::Mat localFrame;
|
||||
if (input.channels() == 1) {
|
||||
cv::cvtColor(input, localFrame, cv::COLOR_GRAY2BGR);
|
||||
}
|
||||
const cv::Mat& frame = (input.channels() == 1) ? localFrame : input;
|
||||
|
||||
// ── 1. Clamp and crop vehicle ROIs ────────────────────────
|
||||
const cv::Rect frameRect(0, 0, frame.cols, frame.rows);
|
||||
std::vector<cv::Mat> vehicleCrops;
|
||||
std::vector<cv::Rect> clamped;
|
||||
vehicleCrops.reserve(vehicleBoxes.size());
|
||||
clamped.reserve(vehicleBoxes.size());
|
||||
for (const auto& r : vehicleBoxes) {
|
||||
cv::Rect c = r & frameRect;
|
||||
if (c.width <= 5 || c.height <= 5) continue;
|
||||
vehicleCrops.emplace_back(frame(c));
|
||||
clamped.push_back(c);
|
||||
}
|
||||
if (vehicleCrops.empty()) return {};
|
||||
|
||||
// ── 2. ONE batched LP detection call across all vehicles ──
|
||||
std::vector<std::vector<Object>> lpBatch =
|
||||
_lpDetector->RunInferencesBatch(vehicleCrops, cameraId);
|
||||
|
||||
// ── 3. Flatten plates, splitting 2-row plates into top/bot ─
|
||||
// Same aspect-ratio heuristic as ANSALPR_OCR::RunInference
|
||||
// (lines ~820-870): narrow plates (aspect < 2.0) are split
|
||||
// horizontally into two recognizer crops, wide plates stay as
|
||||
// one. The recMap lets us stitch the per-crop OCR outputs
|
||||
// back into per-plate combined strings.
|
||||
struct PlateMeta {
|
||||
size_t vehIdx; // index into vehicleCrops / clamped
|
||||
Object lpObj; // LP detection in VEHICLE-local coords
|
||||
cv::Mat plateROI; // full plate crop (kept for colour)
|
||||
std::vector<size_t> cropIndices; // indices into allCrops below
|
||||
};
|
||||
std::vector<cv::Mat> allCrops;
|
||||
std::vector<PlateMeta> metas;
|
||||
allCrops.reserve(lpBatch.size() * 2);
|
||||
metas.reserve(lpBatch.size());
|
||||
for (size_t v = 0; v < lpBatch.size() && v < vehicleCrops.size(); ++v) {
|
||||
const cv::Mat& veh = vehicleCrops[v];
|
||||
const cv::Rect vehRect(0, 0, veh.cols, veh.rows);
|
||||
for (const auto& lp : lpBatch[v]) {
|
||||
cv::Rect lpBox = lp.box & vehRect;
|
||||
if (lpBox.width <= 0 || lpBox.height <= 0) continue;
|
||||
cv::Mat plateROI = veh(lpBox);
|
||||
|
||||
PlateMeta pm;
|
||||
pm.vehIdx = v;
|
||||
pm.lpObj = lp;
|
||||
pm.plateROI = plateROI;
|
||||
|
||||
const float aspect =
|
||||
static_cast<float>(plateROI.cols) /
|
||||
std::max(1, plateROI.rows);
|
||||
if (aspect < 2.0f && plateROI.rows >= 24) {
|
||||
const int halfH = plateROI.rows / 2;
|
||||
pm.cropIndices.push_back(allCrops.size());
|
||||
allCrops.push_back(plateROI(cv::Rect(0, 0, plateROI.cols, halfH)));
|
||||
pm.cropIndices.push_back(allCrops.size());
|
||||
allCrops.push_back(plateROI(cv::Rect(0, halfH, plateROI.cols, plateROI.rows - halfH)));
|
||||
} else {
|
||||
pm.cropIndices.push_back(allCrops.size());
|
||||
allCrops.push_back(plateROI);
|
||||
}
|
||||
metas.push_back(std::move(pm));
|
||||
}
|
||||
}
|
||||
if (allCrops.empty()) return {};
|
||||
|
||||
// ── 4. ONE batched recognizer call across every plate ────
|
||||
// ONNXOCRRecognizer buckets by width internally, so this is
|
||||
// typically 1-2 ORT Runs regardless of plate count.
|
||||
auto ocrResults = _ocrEngine->RecognizeTextBatch(allCrops);
|
||||
|
||||
// ── 5. Assemble — NO tracker, NO voting, NO dedup ────────
|
||||
std::vector<Object> output;
|
||||
output.reserve(metas.size());
|
||||
for (const auto& pm : metas) {
|
||||
std::string combined;
|
||||
for (size_t c : pm.cropIndices) {
|
||||
if (c >= ocrResults.size()) continue;
|
||||
const std::string& line = ocrResults[c].first;
|
||||
if (line.empty()) continue;
|
||||
if (!combined.empty()) combined += " ";
|
||||
combined += line;
|
||||
}
|
||||
if (combined.empty()) continue;
|
||||
|
||||
Object out = pm.lpObj;
|
||||
out.className = combined; // raw OCR — no ALPRChecker
|
||||
out.cameraId = cameraId;
|
||||
out.box.x += clamped[pm.vehIdx].x;
|
||||
out.box.y += clamped[pm.vehIdx].y;
|
||||
|
||||
// Colour lookup — text-keyed cache, bounded.
|
||||
std::string colour = DetectLPColourCached(
|
||||
pm.plateROI, cameraId, out.className);
|
||||
if (!colour.empty()) out.extraInfo = "color:" + colour;
|
||||
|
||||
output.push_back(std::move(out));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
catch (const cv::Exception& e) {
|
||||
this->_logger.LogFatal("ANSALPR_OCR::RunInferencesBatch",
|
||||
std::string("OpenCV Exception: ") + e.what(), __FILE__, __LINE__);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
this->_logger.LogFatal("ANSALPR_OCR::RunInferencesBatch",
|
||||
e.what(), __FILE__, __LINE__);
|
||||
}
|
||||
catch (...) {
|
||||
this->_logger.LogFatal("ANSALPR_OCR::RunInferencesBatch",
|
||||
"Unknown exception occurred", __FILE__, __LINE__);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ── Inference wrappers ───────────────────────────────────────────────
|
||||
bool ANSALPR_OCR::Inference(const cv::Mat& input, std::string& lprResult) {
|
||||
if (input.empty()) return false;
|
||||
|
||||
Reference in New Issue
Block a user