1
0
forked from BRT/arc

Switch Tesseract initialization to memory-based loading of rus.traineddata to fix issues with paths containing Cyrillic characters (Windows).

This commit is contained in:
slava 2026-04-30 19:52:07 +07:00
parent f611639532
commit d58100083f
3 changed files with 32 additions and 26 deletions

View File

@ -11,34 +11,37 @@
namespace { namespace {
// Возвращает путь, который надо передавать в `TessBaseAPI::Init`, либо пустую // Кэш байт rus.traineddata. Грузим файл через Qt (Unicode-aware пути) и
// строку, если ни в одной из ожидаемых раскладок rus.traineddata не найден. // отдаём Tesseract в виде in-memory blob через Init(data, size, lang, ...).
// На Windows (vcpkg) Tesseract 5 без подкаталога `tessdata/` иногда падает // Это обходит проблему, что на Windows Tesseract открывает traineddata через
// молча — пробуем оба варианта и логируем результат. // `fopen`, который использует ANSI-кодировку (cp1251 на русской системе).
QString resolveTessdataPath() { // Если путь к exe содержит кириллицу (например, OneDrive\Рабочий стол) —
static QString cached; // fopen не находит файл и Init возвращает -1. Memory-init не зависит от пути.
static bool resolved = false; const QByteArray &tessdataBytes() {
if (resolved) return cached; static QByteArray bytes;
resolved = true; static bool tried = false;
if (tried) return bytes;
tried = true;
const QString appDir = QCoreApplication::applicationDirPath(); const QString appDir = QCoreApplication::applicationDirPath();
const QStringList candidates = { const QStringList candidates = {
appDir + "/tessdata", // канонический путь appDir + "/tessdata/rus.traineddata", // канонический путь
appDir, // legacy: traineddata лежат рядом с exe appDir + "/rus.traineddata", // legacy: рядом с exe
}; };
for (const QString &dir : candidates) { for (const QString &path : candidates) {
const QString file = dir + "/rus.traineddata"; QFile f(path);
if (QFile::exists(file)) { if (!f.open(QIODevice::ReadOnly)) continue;
// Tesseract принимает datapath как РОДИТЕЛЯ tessdata-каталога, bytes = f.readAll();
// но также понимает каталог, где лежат сами .traineddata. f.close();
cached = QDir::toNativeSeparators(dir); if (!bytes.isEmpty()) {
qDebug() << "[OcrUtils] tessdata located at" << cached; qDebug() << "[OcrUtils] loaded rus.traineddata from" << path
return cached; << "(" << bytes.size() << "bytes)";
return bytes;
} }
} }
qWarning() << "[OcrUtils] rus.traineddata NOT FOUND. Checked:" << candidates qWarning() << "[OcrUtils] rus.traineddata NOT FOUND. Checked:" << candidates
<< "— OCR будет работать в fallback-режиме без поиска текста."; << "— OCR будет работать в fallback-режиме без поиска текста.";
return {}; return bytes;
} }
@ -128,17 +131,20 @@ QPoint findTextCenter(const QByteArray &png,
if (inverted) prepared = inverted; if (inverted) prepared = inverted;
} }
const QString tessdata = resolveTessdataPath(); const QByteArray &td = tessdataBytes();
if (tessdata.isEmpty()) { if (td.isEmpty()) {
if (inverted) pixDestroy(&inverted); if (inverted) pixDestroy(&inverted);
pixDestroy(&gray); pixDestroy(&gray);
return {-1, -1}; return {-1, -1};
} }
auto *api = new tesseract::TessBaseAPI(); auto *api = new tesseract::TessBaseAPI();
if (api->Init(tessdata.toUtf8().constData(), "rus")) { // Init(data, size, lang, ...) — грузим traineddata из памяти, чтобы
qWarning() << "[OcrUtils] Tesseract init failed for datapath" << tessdata // обойти fopen на путях с кириллицей (Windows ANSI-API).
<< "— rus.traineddata присутствует, но не загрузился" if (api->Init(td.constData(), td.size(), "rus",
<< "(возможно, повреждён или несовместимая версия)"; tesseract::OEM_DEFAULT,
nullptr, 0, nullptr, nullptr, false, nullptr)) {
qWarning() << "[OcrUtils] Tesseract Init(memory) failed —"
<< "traineddata повреждён или несовместимая версия";
delete api; delete api;
if (inverted) pixDestroy(&inverted); if (inverted) pixDestroy(&inverted);
pixDestroy(&gray); pixDestroy(&gray);

BIN
tessdata/eng.traineddata Normal file

Binary file not shown.

BIN
tessdata/rus.traineddata Normal file

Binary file not shown.