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:
trnsmkot 2026-04-30 19:52:07 +07:00
parent 714132382b
commit 55d989258b
3 changed files with 32 additions and 26 deletions

View File

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

BIN
tessdata/eng.traineddata Normal file

Binary file not shown.

BIN
tessdata/rus.traineddata Normal file

Binary file not shown.