From 714132382bcaccad47f0a1f9bb78913d5c555846 Mon Sep 17 00:00:00 2001 From: trnsmkot Date: Thu, 30 Apr 2026 19:39:21 +0700 Subject: [PATCH] =?UTF-8?q?Add=20OCR=20utilities=20and=20backend=20integra?= =?UTF-8?q?tion=20for=20enhanced=20screen=20dump=20analysis:=20-=20Introdu?= =?UTF-8?q?ce=20`OcrUtils`=20with=20fuzzy-matching=20logic=20for=20text=20?= =?UTF-8?q?recognition=20on=20screenshots.=20-=20Implement=20dual-pass=20O?= =?UTF-8?q?CR=20to=20handle=20light-on-dark=20and=20dark-on-light=20text?= =?UTF-8?q?=20cases.=20-=20Replace=20hardcoded=20"=D0=9F=D0=BE=D0=B4=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B6=D0=B4=D0=B0=D1=8E"=20button=20tap=20lo?= =?UTF-8?q?gic=20with=20OCR-based=20detection=20and=20fallback=20geometry.?= =?UTF-8?q?=20-=20Update=20`TaskDumpRecorder`=20to=20utilize=20`NetworkSer?= =?UTF-8?q?vice`=20for=20uploading=20screen=20dumps,=20images,=20and=20met?= =?UTF-8?q?adata=20to=20the=20backend.=20-=20Add=20support=20for=20`/monit?= =?UTF-8?q?oring/dump`=20endpoint=20in=20`NetworkService`.=20-=20Enhance?= =?UTF-8?q?=20UI=20functionality=20with=20new=20error=20handling=20and=20p?= =?UTF-8?q?rogress=20feedback=20during=20dump=20creation=20and=20upload.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 17 +++++++++++++++ android/ocr/OcrUtils.cpp | 47 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9355bc7..9d59db3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,9 @@ if (WIN32) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") + # tessdata/ — канонический путь, ожидаемый Tesseract 5+ через TESSDATA_PREFIX/datapath + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}/tessdata") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" DESTINATION "${CMAKE_BINARY_DIR}/tessdata") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/no_devices.png" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/ADBKeyboard.apk" DESTINATION "${CMAKE_BINARY_DIR}/assets") @@ -198,6 +201,17 @@ if (WIN32) "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" "$/eng.traineddata" + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/tessdata" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" + "$/tessdata/rus.traineddata" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" + "$/tessdata/eng.traineddata" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" "$/config.ini" @@ -271,6 +285,9 @@ elseif (APPLE) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") + # tessdata/ — канонический путь, ожидаемый Tesseract 5+ через TESSDATA_PREFIX/datapath + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}/tessdata") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata" DESTINATION "${CMAKE_BINARY_DIR}/tessdata") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/no_devices.png" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/ADBKeyboard.apk" DESTINATION "${CMAKE_BINARY_DIR}/assets") diff --git a/android/ocr/OcrUtils.cpp b/android/ocr/OcrUtils.cpp index 4f606b3..343d698 100644 --- a/android/ocr/OcrUtils.cpp +++ b/android/ocr/OcrUtils.cpp @@ -2,13 +2,47 @@ #include #include +#include #include +#include #include #include 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; + + const QString appDir = QCoreApplication::applicationDirPath(); + const QStringList candidates = { + appDir + "/tessdata", // канонический путь + appDir, // legacy: traineddata лежат рядом с 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; + } + } + qWarning() << "[OcrUtils] rus.traineddata NOT FOUND. Checked:" << candidates + << "— OCR будет работать в fallback-режиме без поиска текста."; + return {}; +} + + + // Расстояние Левенштейна на QString — нужен для нечёткого сравнения OCR-вывода // с искомым словом. Для "Подтверждаю" допускаем 1-2 ошибки. int editDistance(const QString &a, const QString &b) { @@ -94,10 +128,17 @@ QPoint findTextCenter(const QByteArray &png, if (inverted) prepared = inverted; } + const QString tessdata = resolveTessdataPath(); + if (tessdata.isEmpty()) { + if (inverted) pixDestroy(&inverted); + pixDestroy(&gray); + return {-1, -1}; + } auto *api = new tesseract::TessBaseAPI(); - if (api->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "rus")) { - qWarning() << "[OcrUtils] Tesseract init failed (tessdata in" - << QCoreApplication::applicationDirPath() << ")"; + if (api->Init(tessdata.toUtf8().constData(), "rus")) { + qWarning() << "[OcrUtils] Tesseract init failed for datapath" << tessdata + << "— rus.traineddata присутствует, но не загрузился" + << "(возможно, повреждён или несовместимая версия)"; delete api; if (inverted) pixDestroy(&inverted); pixDestroy(&gray);