Add CPU/GPU gate and support new ANSALPR using OCR

This commit is contained in:
2026-04-12 17:16:16 +10:00
parent 27083a6530
commit 0a8aaed215
30 changed files with 1870 additions and 2166 deletions

View File

@@ -1,4 +1,5 @@
#include <iostream>
#define NOMINMAX
#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
@@ -31,6 +32,60 @@
#include <cuda_runtime.h>
#include "EPLoader.h"
// Decode \\uXXXX (literal backslash-u-hex) sequences back to UTF-8.
// VectorDetectionToJsonString double-escapes Unicode for LabVIEW compatibility,
// so JSON strings contain literal "\u54c1" text instead of actual Unicode chars.
static std::string DecodeUnicodeEscapes(const std::string& input) {
std::string result;
result.reserve(input.size());
size_t i = 0;
while (i < input.size()) {
if (i + 5 < input.size() && input[i] == '\\' && input[i + 1] == 'u') {
// Parse 4 hex digits
std::string hex = input.substr(i + 2, 4);
char* end = nullptr;
uint32_t cp = static_cast<uint32_t>(strtoul(hex.c_str(), &end, 16));
if (end == hex.c_str() + 4) {
// Check for surrogate pair (\\uD800-DBFF followed by \\uDC00-DFFF)
if (cp >= 0xD800 && cp <= 0xDBFF && i + 11 < input.size()
&& input[i + 6] == '\\' && input[i + 7] == 'u') {
std::string hex2 = input.substr(i + 8, 4);
uint32_t cp2 = static_cast<uint32_t>(strtoul(hex2.c_str(), &end, 16));
if (end == hex2.c_str() + 4 && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (cp2 - 0xDC00);
i += 12;
} else {
i += 6;
}
} else {
i += 6;
}
// Encode codepoint as UTF-8
if (cp < 0x80) {
result += static_cast<char>(cp);
} else if (cp < 0x800) {
result += static_cast<char>(0xC0 | (cp >> 6));
result += static_cast<char>(0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
result += static_cast<char>(0xE0 | (cp >> 12));
result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
result += static_cast<char>(0x80 | (cp & 0x3F));
} else {
result += static_cast<char>(0xF0 | (cp >> 18));
result += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
result += static_cast<char>(0x80 | (cp & 0x3F));
}
} else {
result += input[i++];
}
} else {
result += input[i++];
}
}
return result;
}
template<typename T>
T GetOptionalValue(const boost::property_tree::ptree& pt, std::string attribute, T defaultValue) {
if (pt.count(attribute)) {
@@ -3533,8 +3588,229 @@ int ANSLPR_OD_CPU_VideoTest() {
return (frameIndex > 0) ? 0 : -4;
}
// ── ANSALPR_OCR test: Japanese license plate detection using ANSONNXOCR ──
// Render UTF-8 text onto a cv::Mat using Windows GDI (supports CJK/Unicode).
// cv::putText only handles ASCII — Japanese characters render as '?'.
#ifdef WIN32
static void putTextUnicode(cv::Mat& img, const std::string& text, cv::Point org,
double fontScale, cv::Scalar color, int thickness) {
int wlen = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0);
std::wstring wtext(wlen - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, &wtext[0], wlen);
HDC hdc = CreateCompatibleDC(nullptr);
int fontHeight = (int)(fontScale * 30);
HFONT hFont = CreateFontW(fontHeight, 0, 0, 0,
(thickness > 2) ? FW_BOLD : FW_NORMAL,
FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Yu Gothic UI");
HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
SIZE sz;
GetTextExtentPoint32W(hdc, wtext.c_str(), (int)wtext.size(), &sz);
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = sz.cx;
bmi.bmiHeader.biHeight = -sz.cy;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* bits = nullptr;
HBITMAP hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdc, hBmp);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB((int)color[2], (int)color[1], (int)color[0]));
TextOutW(hdc, 0, 0, wtext.c_str(), (int)wtext.size());
cv::Mat textImg(sz.cy, sz.cx, CV_8UC4, bits);
for (int row = 0; row < sz.cy; ++row) {
for (int col = 0; col < sz.cx; ++col) {
cv::Vec4b px = textImg.at<cv::Vec4b>(row, col);
if (px[0] != 0 || px[1] != 0 || px[2] != 0) {
int dy = org.y + row;
int dx = org.x + col;
if (dy >= 0 && dy < img.rows && dx >= 0 && dx < img.cols) {
img.at<cv::Vec3b>(dy, dx) = cv::Vec3b(px[0], px[1], px[2]);
}
}
}
}
SelectObject(hdc, hOldBmp);
SelectObject(hdc, hOldFont);
DeleteObject(hBmp);
DeleteObject(hFont);
DeleteDC(hdc);
}
#endif
int ALPR_OCR_Test() {
std::cout << "=== ALPR_OCR_Test: Japanese License Plate (ANSALPR_OCR) ===" << std::endl;
std::filesystem::path currentPath = std::filesystem::current_path();
std::cout << "Current working directory: " << currentPath << std::endl;
ANSCENTER::ANSALPR* infHandle = nullptr;
std::string licenseKey = "";
std::string modelFilePath = "C:\\Projects\\ANSVIS\\Models\\ANS_GenericALPR_v2.0.zip";
std::string imagePath = "C:\\Programs\\ModelTraining\\JLPD\\data\\test7.jpg";
int engineType = 2; // ANSALPR_OCR
double detectionThreshold = 0.3;
double ocrThreshold = 0.5;
double colourThreshold = 0.0; // No colour detection for this test
// Step 1: Create handle
int createResult = CreateANSALPRHandle(&infHandle, licenseKey.c_str(),
modelFilePath.c_str(), "", engineType, detectionThreshold, ocrThreshold, colourThreshold);
std::cout << "CreateANSALPRHandle result: " << createResult << std::endl;
if (!createResult || !infHandle) {
std::cerr << "Failed to create ANSALPR_OCR handle" << std::endl;
return -1;
}
// Step 2: Set country to Japan
ANSALPR_SetCountry(&infHandle, 5); // JAPAN = 5
std::cout << "Country set to JAPAN" << std::endl;
// Step 3: Load engine
auto engineStart = std::chrono::high_resolution_clock::now();
int loadResult = LoadANSALPREngineHandle(&infHandle);
auto engineEnd = std::chrono::high_resolution_clock::now();
double engineMs = std::chrono::duration<double, std::milli>(engineEnd - engineStart).count();
std::cout << "LoadANSALPREngineHandle result: " << loadResult << " (" << engineMs << " ms)" << std::endl;
if (!loadResult) {
std::cerr << "Failed to load ANSALPR_OCR engine" << std::endl;
ReleaseANSALPRHandle(&infHandle);
return -2;
}
// Step 4: Load image
cv::Mat input = cv::imread(imagePath, cv::IMREAD_COLOR);
if (input.empty()) {
std::cerr << "Failed to load image: " << imagePath << std::endl;
ReleaseANSALPRHandle(&infHandle);
return -3;
}
std::cout << "Image loaded: " << input.cols << "x" << input.rows << std::endl;
cv::Mat frame = input.clone();
int width = frame.cols;
int height = frame.rows;
// Convert to raw BGR bytes for RunInferenceBinary
unsigned int bufferLength = static_cast<unsigned int>(frame.total() * frame.elemSize());
unsigned char* imageBytes = new unsigned char[bufferLength];
std::memcpy(imageBytes, frame.data, bufferLength);
// Step 5: Warmup run
auto warmupStart = std::chrono::high_resolution_clock::now();
std::string detectionResult = ANSALPR_RunInferenceBinary(&infHandle, imageBytes, width, height);
auto warmupEnd = std::chrono::high_resolution_clock::now();
double warmupMs = std::chrono::duration<double, std::milli>(warmupEnd - warmupStart).count();
std::cout << "Warmup inference: " << warmupMs << " ms" << std::endl;
std::cout << "ALPR Result: " << detectionResult << std::endl;
// Step 6: Benchmark
const int benchmarkIterations = 10;
std::vector<double> times;
times.reserve(benchmarkIterations);
for (int i = 0; i < benchmarkIterations; ++i) {
auto t0 = std::chrono::high_resolution_clock::now();
std::string result = ANSALPR_RunInferenceBinary(&infHandle, imageBytes, width, height);
auto t1 = std::chrono::high_resolution_clock::now();
double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
times.push_back(ms);
std::cout << " Run " << (i + 1) << "/" << benchmarkIterations << ": " << ms << " ms" << std::endl;
}
std::sort(times.begin(), times.end());
double sum = std::accumulate(times.begin(), times.end(), 0.0);
double avg = sum / benchmarkIterations;
double median = (benchmarkIterations % 2 == 0)
? (times[benchmarkIterations / 2 - 1] + times[benchmarkIterations / 2]) / 2.0
: times[benchmarkIterations / 2];
std::cout << "\n=== Benchmark (" << benchmarkIterations << " runs) ===" << std::endl;
std::cout << " Avg: " << avg << " ms" << std::endl;
std::cout << " Median: " << median << " ms" << std::endl;
std::cout << " Min: " << times.front() << " ms" << std::endl;
std::cout << " Max: " << times.back() << " ms" << std::endl;
std::cout << " FPS: " << (1000.0 / avg) << std::endl;
delete[] imageBytes;
// Step 7: Draw results on image
if (!detectionResult.empty()) {
try {
boost::property_tree::ptree pt;
std::stringstream ss(detectionResult);
boost::property_tree::read_json(ss, pt);
BOOST_FOREACH(const boost::property_tree::ptree::value_type& child, pt.get_child("results")) {
const boost::property_tree::ptree& res = child.second;
const auto class_name_raw = GetData<std::string>(res, "class_name");
const std::string class_name = DecodeUnicodeEscapes(class_name_raw);
const auto x = GetData<int>(res, "x");
const auto y = GetData<int>(res, "y");
const auto w = GetData<int>(res, "width");
const auto h = GetData<int>(res, "height");
cv::rectangle(frame, cv::Rect(x, y, w, h), cv::Scalar(0, 255, 0), 2);
std::string extraInfo = GetOptionalValue<std::string>(res, "extra_info", "");
std::cout << " Plate: " << class_name << std::endl;
if (!extraInfo.empty()) {
std::cout << " extra_info: " << extraInfo << std::endl;
}
#ifdef WIN32
{
int textH = (int)(1.5 * 30);
int ty = y - 5 - textH;
if (ty < 0) ty = y + 3;
putTextUnicode(frame, class_name, cv::Point(x, ty),
1.5, cv::Scalar(0, 0, 255), 3);
}
#else
cv::putText(frame, class_name, cv::Point(x, y - 5),
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
#endif
}
}
catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl;
}
}
// Step 8: Display result
cv::Mat display;
double scale = std::min(1920.0 / frame.cols, 1080.0 / frame.rows);
if (scale < 1.0) {
cv::resize(frame, display, cv::Size(), scale, scale);
} else {
display = frame;
}
cv::namedWindow("ALPR_OCR_Test", cv::WINDOW_AUTOSIZE);
cv::imshow("ALPR_OCR_Test", display);
cv::waitKey(0);
// Cleanup
ReleaseANSALPRHandle(&infHandle);
cv::destroyAllWindows();
frame.release();
input.release();
std::cout << "=== ALPR_OCR_Test complete ===" << std::endl;
return 0;
}
int main()
{
#ifdef WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
// ANSLPR_OD_INDOInferences_FileTest();
//ANSLPR_OD_Inferences_FileTest();
//ANSLPR_OD_VideoTest();
@@ -3544,11 +3820,12 @@ int main()
// ANSLPR_CPU_Inferences_FileTest();
//}
//ANSLPR_SingleTask_Test();
ANSLPR_CPU_StressTest();
//ANSLPR_CPU_StressTest();
//ANSLPR_MultiGPU_StressTest();
//ANSLPR_MultiGPU_StressTest_SimulatedCam();
// ANSLPR_MultiGPU_StressTest_FilePlayer();
//ANSLPR_OD_CPU_VideoTest();
ALPR_OCR_Test();
return 0;
}