Add OCR utilities and backend integration for enhanced screen dump analysis:
- Introduce `OcrUtils` with fuzzy-matching logic for text recognition on screenshots. - Implement dual-pass OCR to handle light-on-dark and dark-on-light text cases. - Replace hardcoded "Подтверждаю" button tap logic with OCR-based detection and fallback geometry. - Update `TaskDumpRecorder` to utilize `NetworkService` for uploading screen dumps, images, and metadata to the backend. - Add support for `/monitoring/dump` endpoint in `NetworkService`. - Enhance UI functionality with new error handling and progress feedback during dump creation and upload.
This commit is contained in:
parent
11e8f00e42
commit
f611639532
@ -165,6 +165,9 @@ if (WIN32)
|
|||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}")
|
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}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/eng.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}/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}/no_devices.png" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/ADBKeyboard.apk" DESTINATION "${CMAKE_BINARY_DIR}/assets")
|
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"
|
"${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata"
|
||||||
"$<TARGET_FILE_DIR:ARC>/eng.traineddata"
|
"$<TARGET_FILE_DIR:ARC>/eng.traineddata"
|
||||||
|
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
"$<TARGET_FILE_DIR:ARC>/tessdata"
|
||||||
|
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata"
|
||||||
|
"$<TARGET_FILE_DIR:ARC>/tessdata/rus.traineddata"
|
||||||
|
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/eng.traineddata"
|
||||||
|
"$<TARGET_FILE_DIR:ARC>/tessdata/eng.traineddata"
|
||||||
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/config.ini"
|
"${CMAKE_CURRENT_SOURCE_DIR}/config.ini"
|
||||||
"$<TARGET_FILE_DIR:ARC>/config.ini"
|
"$<TARGET_FILE_DIR:ARC>/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}/images" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" 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}")
|
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}/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}/no_devices.png" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/ADBKeyboard.apk" DESTINATION "${CMAKE_BINARY_DIR}/assets")
|
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/assets/ADBKeyboard.apk" DESTINATION "${CMAKE_BINARY_DIR}/assets")
|
||||||
|
|||||||
@ -2,13 +2,47 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
#include <tesseract/baseapi.h>
|
#include <tesseract/baseapi.h>
|
||||||
#include <leptonica/allheaders.h>
|
#include <leptonica/allheaders.h>
|
||||||
|
|
||||||
namespace {
|
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-вывода
|
// Расстояние Левенштейна на QString — нужен для нечёткого сравнения OCR-вывода
|
||||||
// с искомым словом. Для "Подтверждаю" допускаем 1-2 ошибки.
|
// с искомым словом. Для "Подтверждаю" допускаем 1-2 ошибки.
|
||||||
int editDistance(const QString &a, const QString &b) {
|
int editDistance(const QString &a, const QString &b) {
|
||||||
@ -94,10 +128,17 @@ QPoint findTextCenter(const QByteArray &png,
|
|||||||
if (inverted) prepared = inverted;
|
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();
|
auto *api = new tesseract::TessBaseAPI();
|
||||||
if (api->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "rus")) {
|
if (api->Init(tessdata.toUtf8().constData(), "rus")) {
|
||||||
qWarning() << "[OcrUtils] Tesseract init failed (tessdata in"
|
qWarning() << "[OcrUtils] Tesseract init failed for datapath" << tessdata
|
||||||
<< QCoreApplication::applicationDirPath() << ")";
|
<< "— rus.traineddata присутствует, но не загрузился"
|
||||||
|
<< "(возможно, повреждён или несовместимая версия)";
|
||||||
delete api;
|
delete api;
|
||||||
if (inverted) pixDestroy(&inverted);
|
if (inverted) pixDestroy(&inverted);
|
||||||
pixDestroy(&gray);
|
pixDestroy(&gray);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user