Как написать программу распознавания изображений
Перейти к содержимому

Как написать программу распознавания изображений

  • автор:

Как написать программу для распознавания тестов по фотографии?

Есть идея создать что-то на подобии ZipGrade (https://www.zipgrade.com/) — это мобильное приложение которое по фотографии сделанной специальной формы распознает ответы на вопросы и считает количество правильных и не правильных ответов. Вопрос заключается в том как можно реализовать приблизительно такой функционал распознавания ответов в форме по фото на python (если можно, то показать минимальный пример распознавания по фотографий в формах)?

Отслеживать
задан 19 мая 2023 в 15:40
251 2 2 серебряных знака 13 13 бронзовых знаков

Для этого стоит использовать обученную модель компьютерного зрения, я знаю что есть библиотеки с уже обученными моделями, распознающими текст по фото, но не уверен, что есть такие, которые распознают выбранные ответы со специальной формы(посмотрел форму по ссылке). Поэтому есть 2 варианта, либо использовать более простую форму, использующую пары № вопроса — ответ, либо заняться обучением модели самому и научить ее распознавать по фото номер вопроса и ответ. Для этого подойдет библиотека cv2

19 мая 2023 в 15:46

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

123.png

Минимальный пример кода:

import cv2 import numpy as np # загрузка изображения img = cv2.imread('123.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Бинаризация изображения ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) # Поиск внешних контуров contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) lst = [] for inx, contour in enumerate(reversed(contours), 1): # Получение координат и размеров описывающего прямоугольника x, y, w, h = cv2.boundingRect(contour) # Выделение области roi = thresh[y:y+h, x:x+w] # Подсчет количества черных пикселей внутри прямоугольника black_pixels = cv2.countNonZero(roi) #print(black_pixels) q = 0 if black_pixels > 2700: #print(inx) q = 1 # Отображение результата на изображении cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) lst.append(q) chunks = [lst[i:i + 5] for i in range(0, len(lst), 5)] for inx, chunk in enumerate(chunks,1): max_index = chunk.index(1)+1 print(f'вопрос ответ ') # Отображение результата cv2.imshow("Result", img) cv2.waitKey(0) cv2.destroyAllWindows() вопрос 1 ответ 3 вопрос 2 ответ 4 

Отслеживать
ответ дан 20 мая 2023 в 11:44
4,835 4 4 золотых знака 8 8 серебряных знаков 22 22 бронзовых знака

спасибо за подробный ответ, а получается по такому же принципу выделения можно будет сделать и отображение не верного ответа?

20 мая 2023 в 20:29

и вопрос, будет ли данный код корректно работать при наличии на форме внешнего текста или его также придется программно выделять и удалять для дальнейшей обработки ответов?

20 мая 2023 в 20:30

Код работает на моей форме)) на других формах результаты будут некорректны. Один вариант из множества решений задачи. Рекомендую посетить канал youtube.com/@murtazasworkshop

Как написать приложение для распознавания предметов?

Хочу сделать приложение под Android на java: на фотографии распознавать стул это или стол.
Какие есть технологии для данной реализации?
Почитала про tensorflow. Вообще есть уже приложение tensorflow light, но хочу написать сама с нуля.
Как можно обучить модель? Как сам происходит этот процесс? Нужно делать 100000 фотографий для выборок? Обучение модели писать на питоне?

  • Вопрос задан более двух лет назад
  • 178 просмотров

Комментировать
Решения вопроса 0
Ответы на вопрос 1

Ну, если сама и с нуля — то это просто.
1. Сначала разбираемся, что такое нейросети. Для этого неплохо заранее изучить и понять алгоритмы машинного обучения, а они предполагают владение линейной алгеброй, статистикой, оптимизацией (в том числе численной).
По дороге разбираемся, что такое «обучение модели» и что такое «применение».
2. После этого изучаем как получить изображение и работать с ним — как оцифровать изображение, как его препарировать для дальнейшей работы.
3. После этого приступаем к изучению работы нейросетей, заточенных под работу с визуальными образами — ну тут можно ограничиться сверточными сетями. Правда предварительно надо разобраться с другими типами, но надеюсь, с этим справитесь легко.
4. Не забываем параллельно на достаточном уровне освоить инструменты программирования — но если вы собрались писать «с нуля» и на java, то с java придется разбираться глубоко, тем более, что образцов на этом языке для подражания в выбранной вами области не так и много. На английском — встречал, но это для вас, надеюсь, не препятствие.
Обучать модель написанную на одном языке, а потом ее перепрограммированить на другой — тот еще челендж. Но забавно, прибавляет сложностей к работе, повышает общий уровень профессиональных навыков и умений. Так что учить или нет Руthon дополнительно — решать вам самостоятельно.
5. Ну а после этого — все просто. Собираем все перечисленное выше «до купы» — и ву-аля! — нейросеть для распознавания предметов готова. Правда, ее после этого придется обучить — ну, для этого у вас уже-ж заготовлено пару (десятков) тысяч размеченных изображений разных предметов, так что тут затруднений у вас не будет.

Вот примерно как-то так. Впрочем, может какие шаги по дороге я пропустил — так что может список и немного расшириться. Тем более интересно, именно для этого и пишут нейросети в одиночку «с нуля» для развития себя как специалиста (правда, тут важно, что бы задел, который надо развивать уже был, и желательно — не элементарный).

А еще лучше, найти какую-нибудь книгу, , благо сейчас их уже предостаточно, где хотя-бы бегло описаны перечисленные выше этапы — и используя ее как Roadmap двигаться к намеченной цели, углубляясь в «ответвления» по мере продвижения и необходимости. По крайней мере можно наедятся, что автор проведет вас через все нужные вехи пути. И на форум приходить с с конкретными непонятками-вопросами. А не с «объясните, как происходит этот процесс, хочу сам его весь написать с нуля, вот только не знаю, какие технологии можно использовать».

Ответ написан более двух лет назад
Комментировать
Нравится 1 Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

image-recognition

  • Распознавание образов

Какой сервис использовать для распознавания лиц и дубликатов документов по фото?

  • 2 подписчика
  • 06 февр.
  • 72 просмотра

Как написать программу для распознавания лиц

Как написать программу для распознавания лиц

Александр Белозеров

Александр Белозеров Эксперт в разработке ПО.

Выясняем, как устроено распознавание лиц на видео и фото, и пробуем самостоятельно создать собственный детектор вместе с программистом Александром Белозеровым.

Где нужно распознавание лиц?

  • Государство: видеоаналитика используется службами безопасности стран для пограничного контроля, а в Москве так находили нарушителей карантина. Службы безопасности организаций, имеющих дело с секретностью, также используют алгоритмы идентификации для контроля доступа сотрудников к секретным объектам.
  • IT-индустрия: Microsoft, Google, Яндекс, ВКонтакте тоже разрабатывают собственные алгоритмы.
  • Медицина: технология помогает выявить болезни и отслеживать прогресс в лечении.
  • Банкинг: банки используют идентификацию по лицу, чтобы снять деньги в банкомате или получить кредит.
  • Образование: распознавание лица помогает поймать тех, кто списывает, — сервисы подключаются к камере на компьютере студента и отслеживают его поведение и движение глаз.
  • Персональные портативные устройства: на смартфонах помимо идентификации пользователя распознавание лица выполняет и развлекательную функцию — у приложений Samsung и Snapchat оно лежит в основе AR-фильтров и масок для лица.

Fullstack-разработчик на Python

Fullstack-разработчики могут в одиночку сделать IT-проект от архитектуры до интерфейса. Их навыки востребованы у работодателей, особенно в стартапах. Научитесь программировать на Python и JavaScript и создавайте сервисы с нуля.

картинка - 2023-03-14T190323.524

Профессия / 12 месяцев
Fullstack-разработчик на Python

Создавайте веб-проекты самостоятельно

dffsdd (3)

Как работает распознавание лиц: метод Виолы-Джонса

Один из способов распознать образ — найти контур объекта и исследовать его свойства. По этому принципу работает метод Виолы-Джонса с использованием признаков Хаара, который придумал венгерский математик Альфред Хаар.

Признаки — это набор геометрических фигур с черно-белым узором, их еще называют маски. Они помогают найти границы какой-либо формы, например очертания лица, линии бровей, носа или рта.

В алгоритме Виолы-Джонса маски накладываются на разные части кадра, а программа определяет, может ли в них находиться объект. Работает это так:

  1. Классификатор (алгоритм, который будет искать объект в кадре) обучают на фотографиях лиц и получают пороговое значение — его превышение будет сигнализировать о том, что в кадре есть лицо.
  2. В классификатор загружают изображение, на котором будут искать лицо.
  3. Классификатор накладывает на него маски и отдельно складывает яркость пикселей, попавших в белую часть маски, и яркость пикселей, попавших в черную часть маски. Потом из первого значения он вычитает второе.

Результат сравнивается с пороговой величиной.

Если результат меньше пороговой величины, значит, в части кадра нет лица, и алгоритм заканчивает свою работу. А если больше, он переходит к следующей части кадра.

На практике маски находят лицо на фотографии так:

Для разработки мы советуем кроссплатформенную среду разработки PyCharm — в ней есть полезные инструменты для управления версиями пакетов, анализа кода и его отладки. Скачать ее можно тут.

Шаг 3. Создайте новый проект на Python, мы назвали его opencv_face_recognition.

Шаг 4. По правому клику на названии проекта в дереве каталогов добавьте новый файл main.py.

Шаг 5. Установите библиотеку. Для этого в настройках (Settings) проекта нужно найти вкладку с управлением конфигурацией интерпретатора, нажать на «+», вбить в поиск последовательно «opencv-python» и «opencv-contrib-python» и установить эти пакеты.

Это облегченная версия, которая использует для расчетов только процессор, но процесс установки все равно может занять до 15 минут.

Шаг 6. Чтобы использовать установленную библиотеку OpenCV, импортируйте модуль cv2:

До тех пор пока мы не обратимся к каким-либо именам из него, он будет подсвечиваться серым, как неиспользуемый.

Шаг 7. Поместите ранее скачанный классификатор haarcascade_frontalface_default.xml в папку проекта

и загрузите его следующим образом:

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)

Обратите внимание, что в PyCharm для его корректной подсветки синтаксиса нам пришлось переписать первую строку с импортом.

Шаг 8. Загрузите изображение, на котором мы будем искать лицо, в режиме оттенков серого — информация о цвете алгоритму не важна, только его интенсивность:

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
img = cv2.imread(‘xfiles4.jpg’)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

Мы будем использовать кадр из сериала «Секретные материалы» с Малдером и Скалли; вы можете поместить в папку проекта любое другое фото.

Шаг 9. Напишите код. Для поиска лиц на изображении мы используем метод с сигнатурой (его именем и списком параметров)

cv2.CascadeClassifier.detectMultiScale (image [, scaleFactor [, minNeighbors [, flags [, minSize [, maxSize]]]]]):

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
img = cv2.imread(‘xfiles4.jpg’)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)

Нам хватит первых трех параметров.

  • В image надо будет передать 8-битную матрицу изображения.
  • scaleFactor показывает, во сколько раз мы будем уменьшать исходное изображение, пытаясь обнаружить объект: это делается потому, что мы изначально не знаем, какого размера будет лицо. Чем меньше будет этот коэффициент, тем дольше будет работать алгоритм.
  • minNeighbors влияет на качество обнаружения: чем больше его значение, тем меньше образов сможет обнаружить алгоритм, но тем точнее будет его работа.

Дефолтные значения для последних двух параметров — 1.1 и 3. Их можно заменить на любые другие и подобрать для конкретного случая, если на дефолтных значениях алгоритм будет работать плохо.

Шаг 10. Если лица будут обнаружены, функция вернет набор объектов типа Rect (x, y, w, h) — прямоугольников, начало которых задано парой координат (x, y), а ширина и высота — как w и h. В цикле for добавим эти прямоугольники в исходное изображение image при помощи cv2.rectangle(image, start_point, end_point, color, thickness) по координатам их противоположных вершин (x, y) и (x+w, y+h):

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
img = cv2.imread(‘xfiles4.jpg’)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)

Шаг 11. Для отображения результата и закрытия программы по нажатию клавиши добавим еще несколько строк: вывод картинки, ожидание нажатия любой клавиши и последующее закрытие окна. В итоге весь наш проект будет выглядеть так:

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
img = cv2.imread(‘xfiles4.jpg’)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow(‘img’, img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Шаг 12. Сочетанием клавиш Ctrl-Shift-F10 (Ctrl-Shift-R на MacOS) запустим скрипт:

Инструкция: распознаем лицо в видеопотоке с веб-камеры

Модернизируем наш код так, чтобы в реальном времени обнаруживать лица в кадре, например, на видео с веб-камеры.

Шаг 1. Создайте объект для захвата видеострима:

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
capture_io = cv2.VideoCapture(2)

Число для cv2.VideoCapture() придется поперебирать, индексом нашей внешней веб-камеры оказалась двойка.

Шаг 2. Считывать кадры с видеоустройства будем в вечном цикле: cv2.VideoCapture.read() возвращает булево значение об успешном считывании из потока (оно нам не нужно) в паре с самой картинкой. Преобразуем ее в 8-битную матрицу так же, как и раньше:

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
capture_io = cv2.VideoCapture(2)
while True:
_, img = capture_io.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

Шаг 3. Остается переписать только конец цикла — чтобы у него было условие выхода, например, по нажатию на «q» (от quit, «выйти»). cv2.waitKey(time) 10 мс ожидает ввода с клавиатуры юникод-символа, который приведет к закрытию стрима и программы.

import cv2.cv2 as cv2
face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)
capture_io = cv2.VideoCapture(2)
while True:
_, img = capture_io.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow(‘img’, img)
if cv2.waitKey(10) & 0xFF == ord(«q»):
break
capture_io.release()
cv2.destroyAllWindows()

Шаг 4. Код готов, нажимаем Ctrl-Shift-F10 (Ctrl-Shift-R на MacOS) и наблюдаем результат вживую:

Александр Белозеров Эксперт в разработке ПО.

Создание приложения для распознавания текста с изображений и аудиофайлов

Запись текста с фотографии листа или из аудиозаписи в текстовый файл, доступный для редактирования – довольно часто встречающаяся задача при работе в офисах или учёбы. Для распознавания текстов и аудио в платных сервисах и программах сегодня используются такие подходы, как машинное зрение и распознавание речи с использованием глубоких нейронных сетей.

Детектирование (обнаружение) и классификация символов на изображении осуществляется с использованием различных архитектур свёрточных нейронных сетей [1]. Обработка естественного языка основана на использовании глубоких рекуррентных нейронных сетей, состоящих из ячеек долгой краткосрочной памяти LSTM [2]. При создании соответствующих приложений для работы с текстами, этап реализации нейронных сетей можно пропустить, используя соответствующие свободно распространяемые библиотеки.

В данной статье я хочу поделиться реализацией приложения, позволяющего пользователю преобразовать и сохранить текстовую информацию из изображения листа или аудио-файла.

Архитектура приложения и используемый стек технологий

Архитектура разрабатываемого приложения приведена на рисунке 1.

Рисунок 1 – Архитектура разрабатываемого приложения

Архитектура на рисунке 1 реализована как клиент-сервер, в парадигме MVC (Model-View-Controller). Все данные разделяются на компоненты трёх видов: модель, представление и контроллер. Такая архитектура позволяет добиться расширяемости, за счёт независимости изменений каждого компонента.

Модуль интерфейса пользователя предназначен для формирования элементов пользовательского интерфейса и вызовов операций обработки данных. Код модуля размещается на сервере и выполняется в браузере пользователя. Пользователь работает с приложением с использованием браузера с поддержкой Java Script, для тестирования клиентской части использовался Google Chrome. В функции для клиентов входят: отображение информации, загрузка на сервер документов, получение результатов с сервера.

Серверные компоненты системы могут функционировать под управлением любого типа операционной системы, разработка и тестирование велась на Windows.

Модуль обработки операций реализует бизнес-логику программной системы. Он обрабатывает запросы пользователей, вызывает операции сервера проведения расчётов и сервера базы данных. Для реализации модуля была выбрана технология Java Servlet.

Для хранения и функционирования модуля обработки операций и модуля интерфейса пользователя был выбран веб сервер Apache Tomcat, обладающий высокой производительностью, гибким функционалом. Кроме того, Apache Tomcat не требует затрат на лицензирование. Взаимодействие между клиентской и серверной частями программной системы осуществляется по протоколу http (браузер на ПК).

Сервер проведения операций обработки изображений и аудио отвечает за задачи, связанные собственно с извлечением текстов из изображений и аудио-записей и их сохранения в текстовый файлы (использованы файлы *.docx). Указанный сервер реализован на языке Python с использованием библиотеки обработки изображений OpenCV и фреймворка TensorFlow.

Для хранения информации программной системы была выбрана система управления базами данных (СУБД) PostgreSQL. Плюсами выбранной СУБД являются отсутствие платы за лицензионное использование и кроссплатформенностью.

После разработки архитектуры были определены интегрированные среды разработки (IDE) компонент сервера и клиентской части. Для сервера проведения расчётов на Python используется PyCharm, для остальных компонент серверной и клиентской части системы — IntelliJ IDEA; обе IDE разработана компанией JetBrains, похожи по функционалу и интерфейсу, являются кроссплатформенными.

Исходный код приложения находится в репозитории, далее раскрою подробнее реализацию основных компонент приложения.

Разработка сервера обработки текстовых и аудио файлов

Для выполнения задачи распознавания текста с изображения используется технология OCR — обнаружение текстового содержимого на изображениях и перевод изображений в закодированный текст, который компьютер может легко понять. Изображение, содержащее текст, сканируется и анализируется, чтобы идентифицировать символы в нем. После идентификации символ преобразуется в машинно‑кодированный текст.

Для целей OCR использовался пакет PyTesseract, являющейся оболочной для Google Tesseract‑OCR Engine. Tesseract использует нейронные сети и двухступенчатый подход к распознаванию. В первый проход происходит распознавание символов. На втором этапе производится заполнение любых символов, которые имеют низкое значение вероятности правильного определения класса, символами, наиболее подходящими по контексту. Этапы выполняются на базе рекуррентной нейронной сети LSTM, наиболее подходящей для обработки естественного языка.

При работе с документом в формате PDF сначала документ извлекается из массива байт, сохраняется как временный файл и читается в память с использованием библиотеки fitz. Статический метод для этой операции приведён ниже.

import fitz @staticmethod def __getPDF(dictOfProject, parameters): for imageFromList in dictOfProject['InputDocument']: byteImage = bytes.fromhex(imageFromList) fileName = str(parameters.imagesFolder.joinpath('example.pdf').resolve()) file = open(fileName, 'wb') file.write(byteImage) file.close() pdfDocument = fitz.open(fileName) return pdfDocument

Затем документ постранично сохраняется в изображения в формате PNG, и с использованием библиотеки pytesseract извлекается текст из каждого изображения, сохраняясь в текстовый файл, созданный с использованием библиотеки docx.

Код методов для выполнения OCR:

import os import docx import cv2 @staticmethod def __recognitionText(pdfDocument, parameters): mydoc = docx.Document() for current_page in range(len(pdfDocument)): for image in pdfDocument.get_page_images(current_page): xref = image[0] fileName = str(parameters.imagesFolder.joinpath("page%s-%s.png" % (current_page, xref)).resolve()) # читать изображение с помощью OpenCV ConvertPdfToTextCalculator.__takeImage(parameters.pytesseract, mydoc,fileName) pdfDocument.close() return mydoc @staticmethod def __takeImage(pytesseract, mydoc,fileName): image = cv2.imread(fileName) # получаем строку pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' string = pytesseract.image_to_string(image, lang='rus') # добавляем в документ mydoc.add_paragraph(string)

Для обработки аудио была использована библиотека Speech Recognition (в коде обозначена как sr), основанная на использовании скрытой марковской модели [3] и представляющая собой инструмент для передачи речевых API (мной был использован Google Speech). Для работы со звуком как с объектом и разбивки его на отрезки по параметру тишины использован высокоуровневый интерфейс pydub, а именно класс AudioSegment и метод split_on_silence.

Метод создания объекта класса AudioSegment:

@staticmethod def __getWav(dictOfProject, parameters): for imageFromList in dictOfProject['InputDocument']: byteFile = bytes.fromhex(imageFromList) fileName = str(parameters.imagesFolder.joinpath('example.wav').resolve()) file = open(fileName, 'wb') file.write(byteFile) file.close() sound = parameters.AudioSegment.from_wav(fileName) return sound

Метод разбивки объекта на звуки, разделённые тишиной:

@staticmethod def __getSounds(soundDocument, parameters): chunks = parameters.split_on_silence(soundDocument, min_silence_len=500, silence_thresh=soundDocument.dBFS - 15, keep_silence=500, ) return chunks

Метод расшифровки и записи текстового файла:

def __recognitionSound(chunks, parameters): mydoc = docx.Document() r = parameters.sr.Recognizer() for i, audio_chunk in enumerate(chunks, start=1): chunk_filename = os.path.join(parameters.imagesFolder, f"chunk.wav") audio_chunk.export(chunk_filename, format="wav") start_time = datetime.now() print(datetime.now() - start_time) with parameters.sr.AudioFile(chunk_filename) as source: audio_listened = r.record(source) try: text = r.recognize_google(audio_listened, language=parameters.language) except parameters.sr.UnknownValueError as e: print("Error:", str(e)) else: text = f". " print(" -- :", text) mydoc.add_paragraph(text) print("Время выполнения: ") print(datetime.now() - start_time) return mydoc

Файлы *.pdf или *.wav поступают на сервер от клиента через модуль обработки операций, в виде массива байт. Для передачи данных на сервере использована технология сокетов. Для хранения настроек сервера и обработки входящих запросов был создан отдельный класс NetworkServer.

Основной файл запуска cvserver.py выглядит следующим образом:

import keyboard from core.clientServer.networkServer import NetworkServer if __name__ == '__main__': server = NetworkServer() port = 10000 server.init(port) server.start() print("Система запущена!") print("Для выхода введите q") keyboard.wait("q") server.stop() server.done() print("Работа завершена")

Код класса NetworkServer:

import socket from threading import Thread from core.clientsManager import ClientsManager from core.clientServer.clientHandler import ClientHandler from core.requests.requestFactory import RequestFactory class NetworkServer(object): def __init__(self): self._socket = None self._continueWork = False self._thread = None self._clientsManager = ClientsManager() self._requestFactory = RequestFactory() def init(self, portNumber): self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._socket.settimeout(0.1) self._socket.bind(("127.0.0.1", portNumber)) self._socket.listen(0) def done(self): self.stop() if not (self._socket is None): self._socket.close() self._socket = None def start(self): if not (self._thread is None): self.stop() self._continueWork = True self._thread = Thread(target=self.__work) self._thread.start() def stop(self): if self._thread is None: return self._continueWork = False self._thread.join() self._thread = None def __work(self): while self._continueWork: try: clientSocket, clientAddress = self._socket.accept() client = ClientHandler(self, clientSocket) self.getClientsManager().Add(client) client.run() except socket.timeout: pass except Exception as e: print("Exception occur: " + e.__str__()) raise def getClientsManager(self): return self._clientsManager def getRequestFactory(self): return self._requestFactory

Сервер может работать с несколькими потоками за счёт использования класса Thread(). Для выстраивания очередей используется вспомогательный класс ClientsManager.

from threading import Lock class ClientsManager(object): def __init__(self): self._clientsLock = Lock() self._clients = [] def Add(self, client): self._clientsLock.acquire() if not (client in self._clients): self._clients.append(client) self._clientsLock.release() def Remove(self, client): self._clientsLock.acquire() if client in self._clients: self._clients.remove(client) self._clientsLock.release()

Для работы с входящими запросами и отсылкой ответов используется класс ClientHandler.

from threading import Thread from core.requests.requestResponseBuilder import RequestResponseBuilder class ClientHandler(object): RECEIVE_TIMEOUT = 30 def __init__(self, parent, clientSocket): self._parent = parent self._socket = clientSocket self._thread = None def run(self): self._thread = Thread(target=self.__work) self._thread.start() def __work(self): self._socket.settimeout(self.RECEIVE_TIMEOUT) requestCode, requestBody = RequestResponseBuilder.readRequest(self._socket) responseBody = self._parent.getRequestFactory().handle(requestCode, requestBody) RequestResponseBuilder.writeResponse(self._socket, responseBody) self._socket.close() self._parent.getClientsManager().Remove(self)

Вспомогательный класс RequestFactory выполняет адресацию запросов к классам-обработчикам (RecognitionTextHandler и RecognitionSpeechHandler), в зависимости от кода запроса.

from typing import Optional from core.requests.recognitionTextHandler import RecognitionTextHandler from core.requests.recognitionSpeechHandler import RecognitionSpeechHandler from core.requests.requestCodes import RequestCodes class RequestFactory(object): def __init__(self) -> None: self._handlers = < RequestCodes.RecognitionText: RecognitionTextHandler(), RequestCodes.RecognitionSpeech: RecognitionSpeechHandler() >def handle(self, requestCode: int, requestBody: bytearray) -> Optional[bytes]: if requestCode in self._handlers: response = self._handlers[requestCode].handle(requestBody) else: response = None return response

В класс RequestResponseBuilder вынесены статические методы работы для «упаковки» и «распаковки» запросов и ответов в массивы байт.

Используемые в RequestFactory классы-обработчики запросов по сути нужны для передачи распакованного запроса и инструментов для обработки в классы-калькуляторы для выполнения собственно обработки изображений и аудио, статические методы из которых приведены в начале раздела.

Код класса – обработчика для распознавания текста:

import json from typing import Optional import pytesseract from core.calculators.convertPdfToTextCalculator import ConvertPdfToTextCalculator from core.optParameters import OptParameters from core.commonUtils import CommonUtils class RecognitionTextHandler(object): def __init__(self): self.pytesseract = pytesseract self.pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' def handle(self, packedParameters: bytearray) -> Optional[bytes]: if packedParameters is None or len(packedParameters) == 0: return None parametersStr = packedParameters.decode("utf8") parameters = json.loads(parametersStr) optParameters = OptParameters() optParameters.pytesseract = self.pytesseract optParameters.imagesFolder = CommonUtils.getSolutionFolder().joinpath("CVServer").joinpath("main").joinpath("data") result = ConvertPdfToTextCalculator.calculate(parameters, optParameters) try: response = json.dumps(result).encode("utf8") except: print('Error') return response

Код класса – обработчика для распознавания речи:

import json from typing import Optional import speech_recognition as sr from core.calculators.convertWavToTextCalculator import ConvertWavToTextCalculator from core.optParameters import OptParameters from core.commonUtils import CommonUtils from pydub import AudioSegment from pydub.silence import split_on_silence class RecognitionSpeechHandler(object): def __init__(self): self.sr = sr self.AudioSegment = AudioSegment self.split_on_silence = split_on_silence def handle(self, packedParameters: bytearray) -> Optional[bytes]: if packedParameters is None or len(packedParameters) == 0: return None parametersStr = packedParameters.decode("utf8") parameters = json.loads(parametersStr) optParameters = OptParameters() optParameters.AudioSegment = self.AudioSegment optParameters.split_on_silence = self.split_on_silence optParameters.sr = self.sr optParameters.imagesFolder = CommonUtils.getSolutionFolder().joinpath("CVServer").joinpath("main").joinpath("data").joinpath("sound") result = ConvertWavToTextCalculator.calculate(parameters, optParameters) try: response = json.dumps(result).encode("utf8") except: print('Error') return response

Ответ передается в виде словаря со структурой , где itemhex – байтовое представление файла с результатами распознавания, к которому применена функция hex().

Опишем структуру базы данных и остальные модули приложения.

Разработка базы данных

Используемая в ходе работы информация храниться в реляционной базе данных. На рисунке 2 приведена ER диаграмма (сущность-связь) спроектированной для системы базы данных.

Рисунок 2 – ER диаграмма базы данных системы

Модель данных, приведённая на рисунке 2, содержит 4 сущности – таблицы базы данных системы. Каждый экземпляр каждой сущности имеет идентификатор ID, представляющий собой уникальную строку символов, задаваемую с использованием текстового представления стандарта UUID.

Структура базы данных имеет две группы сущностей: 1) отвечающих за авторизацию и права пользователей; 2) отвечающих за хранение информации по распознаваемым файлам.

Опишем сущности первой группы, отвечающие за параметры безопасности и разграничение уровней доступа в системе. Сущность Users содержит информацию по личным данным пользователя: Name (имя), Surname (фамилия), Email (адрес почты), Username (логин), Password (пароль).

Сущность Roles содержит информацию о ролях, имеет поле Role, принимающие два значения: user и admin.

Связь между Users и Roles осуществляется с использованием таблицы UserRoles, в которой хранятся идентификаторы UserID и RoleID.

Вторая группа включает только лишь одну сущность Documents, содержащую поля UserID (идентификатор для связи с Users), название распознаваемого файла Title и его содержимого FilePDF и результата его расшифровки в текст FileTXT. FilePDF — это документ *.pdf или *.wav в виде массива байт, FileTXT — документ *.docx в виде массива байт.

Для создания тестового варианта базы данных использовался код на Python с использованием SQLAlchemy, позволяющий создать структуру и тестовое наполнения базы данных PostgreSQL. Код для генерации теста находится в папке Tools/ DBGenerate проекта
на Github.

Разработка остальных модулей

Структура клиентской и серверной частей, за исключением сервера проведения операций обработки изображений и аудио, приведена на рисунке 3.

Рисунок 3 – Структура модулей клиентской и серверной частей

Ядром модуля обработки операций является класс MainServlet, наследующий абстрактный класс HttpServlet. Он содержит экземпляры внутреннего класса HandlerInfo в виде структуры «словарь» handlers, хранящий все обработчики запросов и способ их распаковки, а также способ упаковки ответов клиенту. Код класса MainServlet:

import classes.RequestCode; import core.interaction.*; import handlers.DocumentsHandler; import handlers.SessionHandler; import handlers.UsersInfoHandler; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * Класс сервлета, через который осуществляется взаимодействие клиента и сервера в проекте. * */ @WebServlet(name = "MainServlet", urlPatterns = "/handler") @MultipartConfig( fileSizeThreshold = 1024 * 1024 * 8, maxFileSize = 1024 * 1024 * 8, maxRequestSize = 1024 * 1024 * 9 ) public class MainServlet extends HttpServlet < private static class HandlerInfo < RequestHandler requestHandler; RequestExtractor requestExtractor; ResponsePacker responsePacker; public HandlerInfo(RequestHandler handler, RequestExtractor requestExtractor, ResponsePacker responsePacker) < this.requestHandler = handler; this.requestExtractor = requestExtractor; this.responsePacker = responsePacker; >> private volatile Boolean isInitialized; private final Object isInitializedLock = new Object(); private final Map handlers; private MainServletEnvironment environment; public MainServlet() < handlers = new HashMap<>(); > @Override public void init(ServletConfig config) throws ServletException < super.init(config); //Запуск сессии if(isInitialized == null) < synchronized(isInitializedLock) < if(isInitialized == null) < isInitialized = initializeImpl(); >> > log("Method init =)"); > @Override protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException, IOException < String command = null; try < if (isInitialized) < command = httpServletRequest.getParameter("cmd"); HandlerInfo handlerInfo = handlers.get(command); if (handlerInfo != null) < Request request = handlerInfo.requestExtractor.extract(httpServletRequest); InnerResponseRecipient responseRecipient = new InnerResponseRecipient(); handlerInfo.requestHandler.executeRequest(responseRecipient, request); handlerInfo.responsePacker.pack(request, responseRecipient.response, httpServletRequest, httpServletResponse); >else < handleError(httpServletResponse, "templates/CommandNotSupported.html"); >> else < handleError(httpServletResponse, "templates/InitializationFailed.html"); >> catch (Exception e) < System.out.printf("Команда не поддерживается, код %s", command); handleError(httpServletResponse, "templates/ExceptionOccur.html"); e.printStackTrace(); >log("Method service =)"); > private void handleError(HttpServletResponse httpServletResponse, String errorTemplate) throws IOException < String pageText = environment.resourceManager.getResource(errorTemplate); HttpServletResponseBuilder.onStringResponse(httpServletResponse, HttpServletResponse.SC_BAD_REQUEST, HttpServletResponseBuilder.HTMLContentType, pageText); >@Override public void destroy() < super.destroy(); log("Method desctoy =)"); >private boolean initializeImpl() < boolean result; try < environment = MainServletEnvironment.create(); result = environment != null; if (result) < RequestHandler sessionHandler = new SessionHandler(environment.sessionManager, environment.securityManager); register(RequestCode.SESSION_OPEN, sessionHandler, environment.editContentRequestExtractor, environment.sessionOpenResponsePacker); register(RequestCode.SESSION_CLOSE, sessionHandler, environment.baseRequestExtractor, environment.sessionCloseResponsePacker); RequestHandler usersInfoHandler = new UsersInfoHandler(environment.sessionManager, environment.securityManager, environment.documentManager); register(RequestCode.USERS_INFO, usersInfoHandler, environment.editContentRequestExtractor, environment.objectResponsePacker); register(RequestCode.CURRENT_USER_INFO, usersInfoHandler, environment.baseRequestExtractor, environment.objectResponsePacker); register(RequestCode.REGISTRATION_USER_INFO, usersInfoHandler, environment.entityWithViolationsRequestExtractor, environment.sessionOpenResponsePacker); register(RequestCode.GET_DOCUMENTS_HISTORY, usersInfoHandler, environment.baseRequestExtractor, environment.objectResponsePacker); register(RequestCode.GET_DOCUMENT_BY_ID, usersInfoHandler, environment.editContentRequestExtractor, environment.objectResponsePacker); RequestHandler documentsHandler = new DocumentsHandler(environment.sessionManager, environment.documentManager); register(RequestCode.RECOGNIZE_DOCUMENT, documentsHandler, environment.requestWithAttachmentsExtractor, environment.objectResponsePacker); register(RequestCode.RECOGNIZE_AUDIO_DOCUMENT, documentsHandler, environment.requestWithAttachmentsExtractor, environment.objectResponsePacker); register(RequestCode.SAVE_DOCUMENT, documentsHandler, environment.requestWithAttachmentsExtractor, environment.baseResponsePacker); >> catch (Exception e) < e.printStackTrace(); result = false; >return result; > private void register(RequestCode code, RequestHandler handler, RequestExtractor requestExtractor, ResponsePacker responsePacker) < handlers.put(code.toString(), new HandlerInfo(handler, requestExtractor, responsePacker)); >>

Кроме того, MainServlet содержит экземпляр класса MainServlet Environment, содержащий экземпляры классов‑менеджеров для работы с базой данных, экземпляры классов для работы с запросами‑ответами и для работы с базой данных. MainServlet имеет следующие методы: initialize() — инициализирует единственный раз запуск метода initializeImpl(); initializeImpl() — запускает метод create() класса MainServlet Environment, и заполняет словарь handlers посредством метода register(); метод service() принимает запросы и отправляет ответы; метод handleError() нужен для обработки ошибок, когда приходит запрос, отсутствующий в словаре handlers.Код класса WebHandlerEnvironment:

import core.ResourceManager; import core.SessionManager; import core.documentManager.DocumentManager; import core.interaction.RequestExtractor; import core.interaction.ResponsePacker; import core.interaction.requestExtractors.BaseRequestExtractor; import core.interaction.requestExtractors.EditContentRequestExtractor; import core.interaction.requestExtractors.RequestWithAttachmentsExtractor; import core.interaction.requestExtractors.entityRequestExtractor.EntityRequestExtractor; import core.interaction.requestExtractors.entityRequestExtractor.EntityWithViolationsRequestExtractor; import core.interaction.responsePackers.BaseResponsePacker; import core.interaction.responsePackers.ObjectResponsePacker; import core.interaction.responsePackers.SessionCloseResponsePacker; import core.interaction.responsePackers.SessionOpenResponsePacker; import db.HibernateSessionFactory; import org.hibernate.SessionFactory; import core.securityManager.SecurityManager; public class MainServletEnvironment < final public SessionFactory hibernateSessionFactory; final public SessionManager sessionManager; final public SecurityManager securityManager; final public DocumentManager documentManager; final public ResourceManager resourceManager; final public RequestExtractor baseRequestExtractor; final public RequestExtractor editContentRequestExtractor; final public RequestWithAttachmentsExtractor requestWithAttachmentsExtractor; final public EntityRequestExtractor entityRequestExtractor; final public EntityWithViolationsRequestExtractor entityWithViolationsRequestExtractor; final public ResponsePacker sessionOpenResponsePacker; final public ResponsePacker sessionCloseResponsePacker; final public BaseResponsePacker baseResponsePacker; final public ObjectResponsePacker objectResponsePacker; private MainServletEnvironment(SessionFactory hibernateSessionFactory, SessionManager sessionManager, SecurityManager securityManager,DocumentManager documentManager) < this.hibernateSessionFactory = hibernateSessionFactory; this.sessionManager = sessionManager; this.securityManager = securityManager; this.resourceManager = new ResourceManager(); this.documentManager = documentManager; this.baseRequestExtractor = new BaseRequestExtractor(); this.editContentRequestExtractor = new EditContentRequestExtractor(); this.entityRequestExtractor = new EntityRequestExtractor(); this.entityWithViolationsRequestExtractor = new EntityWithViolationsRequestExtractor(); this.requestWithAttachmentsExtractor = new RequestWithAttachmentsExtractor(); this.baseResponsePacker = new BaseResponsePacker(); this.objectResponsePacker = new ObjectResponsePacker(resourceManager); this.sessionCloseResponsePacker = new SessionCloseResponsePacker(resourceManager); this.sessionOpenResponsePacker = new SessionOpenResponsePacker(resourceManager); >public static MainServletEnvironment create() < boolean result; SessionFactory hibernateSessionFactory = null; SessionManager sessionManager = null; SecurityManager securityManager = null; DocumentManager documentManager = null; try < hibernateSessionFactory = HibernateSessionFactory.getSessionFactory(); sessionManager = new SessionManager(); securityManager = new SecurityManager(hibernateSessionFactory, sessionManager); documentManager = new DocumentManager(hibernateSessionFactory, sessionManager); result = securityManager.init(); >catch (Exception e) < e.printStackTrace(); result = false; >return result ? new MainServletEnvironment(hibernateSessionFactory, sessionManager, securityManager, documentManager): null; > >

В качестве контейнера сервлетов используется Apache Tomcat. Обработка каждого запроса выделено в отдельные классы в пакете handlers, которые связываются с классами сервисов в пакете core. Работа с базой данных осуществляется с использованием классов пакетов dbclasses и db, где хранятся классы сущностей и сервисы работы с ними соответственно.

Рисунок 4 – Структурная схема приложения

Запросы от клиента к серверу проведения операций обработки изображений и аудио (CVServer на диаграмме рисунка 4) производятся через класс RecognizeTextClient пакета core. Запросы клиента и ответы сервера производятся по протоколу http. Код класса RecognizeTextClient:

import classes.RecognitionDocument; import core.recognitionClient.handlers.TextRecognitionHandler; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; /** * Класс для отправки на сервер распознавания запроса на распознавания текста с pdf документа */ public class RecognizeTextClient < private static final String SERVER_IP = "127.0.0.1"; private static final int SERVER_PORT = 10000; private static final int RequestCodeLength = 3; private static final int RequestBodyLength = 8; private static final int ResponseHeaderLength = 10; private final String requestHeaderMask; private final TextRecognitionHandler textRecognitionHandler; private Socket clientSocket; private InputStream in; private OutputStream out; public RecognizeTextClient()< requestHeaderMask = String.format("%%0%dd%%0%dd", RequestCodeLength, RequestBodyLength); textRecognitionHandler = new TextRecognitionHandler(); >private boolean connect() < try < clientSocket = new Socket(SERVER_IP, SERVER_PORT); in = clientSocket.getInputStream(); out = clientSocket.getOutputStream(); >catch (IOException ignored) < clientSocket = null; >return clientSocket != null; > private void disconnect() < if(clientSocket != null) < try< in.close(); out.close(); clientSocket.close(); >catch (IOException ignored) < >> clientSocket = null; > private String writeRequestReadResponse(int requestCode, String request) throws IOException < byte[] requestBody = request.getBytes(StandardCharsets.UTF_8); String requestHeaderStr = String.format(requestHeaderMask, requestCode, requestBody.length); byte[] requestHeader = requestHeaderStr.getBytes(StandardCharsets.UTF_8); byte[] responseHeader = new byte[ResponseHeaderLength]; byte[] responseBody; String response; out.write(requestHeader); out.write(requestBody); out.flush(); readNBytes(responseHeader, ResponseHeaderLength); String responseHeaderStr = new String(responseHeader, StandardCharsets.UTF_8); int responseBodyLength = Integer.parseInt(responseHeaderStr); if(responseBodyLength != 0 ) < responseBody = new byte[responseBodyLength]; readNBytes(responseBody, responseBodyLength); response = new String(responseBody, StandardCharsets.UTF_8); >else < response = ""; >return response; > private void readNBytes(byte[] b, int len) throws IOException < Objects.requireNonNull(b); if (len < 0 || len >b.length) throw new IndexOutOfBoundsException(); int n = 0; while (n < len) < int count = in.read(b, n, len - n); if (count < 0) break; n += count; >> private String executeOperation(int operationCode, String operationParameters) < String response = null; if(connect()) < try< response = writeRequestReadResponse(operationCode, operationParameters); >catch (IOException ignored) < >> disconnect(); return response; > public boolean recognitionText(List inputDocument, RecognitionDocument calculateResult) < String parameters = textRecognitionHandler.getRequestParameters(inputDocument); String response = executeOperation(CalculateServerRequestCode.RECOGNIZE_TEXT, parameters); if(!textRecognitionHandler.parseResponse(response)) return false; calculateResult.setValue(textRecognitionHandler.getInfos()); return textRecognitionHandler.getResult(); >public boolean recognitionAudio(List inputDocument, RecognitionDocument calculateResult) < String parameters = textRecognitionHandler.getRequestParameters(inputDocument); String response = executeOperation(CalculateServerRequestCode.RECOGNIZE_AUDIO, parameters); if(!textRecognitionHandler.parseResponse(response)) return false; calculateResult.setValue(textRecognitionHandler.getInfos()); return textRecognitionHandler.getResult(); >>

В пакете validators хранятся классы для проверки правильности заполнения полей формы при регистрации.

Для работы с базой данных, как отмечалось ранее, используется библиотека Hibernate. Ниже приведён код класса для инициализации SessionFactory (используется для получения объектов, необходимых для операции с базой данных). Код класса HibernateSessionFactory:

package db; import dbclasses.*; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; /** * Класс для создания SessionFactory. */ public class HibernateSessionFactory < private static volatile SessionFactory sessionFactory; //настройки и работа с сессиями (фабрика сессий) private static final Object sessionFactoryLock = new Object(); public static SessionFactory getSessionFactory() < if (sessionFactory == null) < synchronized(sessionFactoryLock) < if (sessionFactory == null) < try < Configuration configuration = new Configuration().configure(); configuration.addAnnotatedClass(User.class); configuration.addAnnotatedClass(Role.class); configuration.addAnnotatedClass(UserRole.class); configuration.addAnnotatedClass(Document.class); StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()); sessionFactory = configuration.buildSessionFactory(builder.build()); >catch (Exception e) < System.out.println("Исключение!" + e); >> > > return sessionFactory; > >

В приведённом выше классе HibernateSessionFactory находятся все классы-сущности для таблиц базы данных. Приведем цепочку классов для работы с сущностью Document. Класс-обработчик, используемый в MainServlet (рисунок 4), носит название DocumentsHandler:

package handlers; import classes.*; import core.SessionManager; import core.documentManager.DocumentManager; import core.interaction.Request; import core.interaction.RequestHandlerContainer; import core.interaction.Response; import core.interaction.ResponseRecipient; import core.interaction.requests.RequestWithAttachments; import core.interaction.responses.ObjectResponse; import core.recognitionClient.RecognizeTextClient; import dbclasses.Document; import java.nio.charset.StandardCharsets; import java.time.Clock; import java.util.List; import java.util.Objects; public class DocumentsHandler extends RequestHandlerContainer < private final SessionManager sessionManager; private final DocumentManager documentManager; public DocumentsHandler(SessionManager sessionManager, DocumentManager documentManager) < super(); this.sessionManager = sessionManager; this.documentManager = documentManager; register(RequestCode.RECOGNIZE_DOCUMENT.toString(), this::DocumentRecognize); register(RequestCode.RECOGNIZE_AUDIO_DOCUMENT.toString(), this::AudioDocumentRecognize); register(RequestCode.SAVE_DOCUMENT.toString(), this::DocumentSave); >private boolean DocumentRecognize(ResponseRecipient responseRecipient, Request requestBase) < RequestWithAttachments request = (RequestWithAttachments) requestBase; boolean result; Session session = sessionManager.getSession(request.sessionID); session.lastActivityTime = Clock.systemDefaultZone().instant(); RecognizeTextClient recognizeTextClient = new RecognizeTextClient(); RecognitionDocument calculateResult = new RecognitionDocument(); result = recognizeTextClient.recognitionText(request.attachments, calculateResult); Response response = new ObjectResponse(request.code, request.sessionID, result, calculateResult); if (responseRecipient != null) < responseRecipient.ReceiveResponse(response); >return result; > private boolean AudioDocumentRecognize(ResponseRecipient responseRecipient, Request requestBase) < RequestWithAttachments request = (RequestWithAttachments) requestBase; boolean result; Session session = sessionManager.getSession(request.sessionID); session.lastActivityTime = Clock.systemDefaultZone().instant(); RecognizeTextClient recognizeTextClient = new RecognizeTextClient(); RecognitionDocument calculateResult = new RecognitionDocument(); result = recognizeTextClient.recognitionAudio(request.attachments, calculateResult); Response response = new ObjectResponse(request.code, request.sessionID, result, calculateResult); if (responseRecipient != null) < responseRecipient.ReceiveResponse(response); >return result; > private boolean DocumentSave(ResponseRecipient responseRecipient, Request requestBase) < RequestWithAttachments request = (RequestWithAttachments) requestBase; boolean result = false; Session session = sessionManager.getSession(request.sessionID); session.lastActivityTime = Clock.systemDefaultZone().instant(); byte[] filepdf = request.attachments.get(0); byte[] filetext = request.attachments.get(1); String titleOfDocument = new String(request.attachments.get(2), StandardCharsets.UTF_8); String[] userIDs = new String[] ; Document document = new Document(); if(session != null) < for (String userID : userIDs) < if (Objects.equals(userID, session.currentUserID)) < document.setUserID(userID); document.setTitle(titleOfDocument); document.setFilepdf(filepdf); document.setFiletext(filetext); result = saveDocument(document, result); >> > Response response = new Response(request.code, request.sessionID, result); if (responseRecipient != null) < responseRecipient.ReceiveResponse(response); >return result; > private boolean saveDocument(Document entity, boolean result) < if (entity!=null)< try< documentManager.save(entity); result = true; >catch(Exception e) < e.printStackTrace(); result = false; >> return result; > private boolean DocumentInfoID(ResponseRecipient responseRecipient, Request request, String sessionID, String[] userIDs, String ID) < boolean result; Listdocuments = documentManager.getDocumentByID(sessionID, userIDs, ID); DocumentsInfo view = new DocumentsInfo(documents); result = documents.size() != 0; ObjectResponse response = new ObjectResponse(request.code, request.sessionID, result, view); if (responseRecipient != null) < responseRecipient.ReceiveResponse(response); >return true; > >

Класс-обработчик содержит методы для работы с поступающими запросами (RECOGNIZE_DOCUMENT, RECOGNIZE_AUDIO_DOCUMENT, SAVE_DOCUMENT), использует для операций класс RecognizeTextClient для выполнения детектирования текста и DocumentManager для операций с базой данных, находящийся в пакете core (рисунок 4):

package core.documentManager; import classes.Session; import core.CommonUtils; import core.SessionManager; import db.DocumentManagerService; import dbclasses.Document; import java.util.*; /** * Менеджер для работы с сущностью Document */ public class DocumentManager < private final DocumentManagerService service; private final SessionManager sessionManager; private final DocumentManagerData data; public DocumentManager(org.hibernate.SessionFactory hibernateSessionFactory, SessionManager sessionManager) < this.sessionManager = sessionManager; this.service = new DocumentManagerService(hibernateSessionFactory); this.data = new DocumentManagerData(); >public boolean init() < boolean result = true; service.getData(data); if (data==null)< result = false; >return result; > public List getDocuments(String currentSessionID, String[] ids) < Listresult = new ArrayList<>(); Session session = sessionManager.getSession(currentSessionID); List userIDs = new ArrayList<>(); if(ids == null || ids.length == 0) < userIDs.add(session.currentUserID); >else < Collections.addAll(userIDs, ids); >if(session != null) < for(String userID : userIDs) < if(Objects.equals(userID, session.currentUserID)) < service.getDataByUserID(data,userID); result.addAll(data.documents); >> > return result; > public List getDocumentByID(String currentSessionID, String[] ids, String id) < Listresult = new ArrayList<>(); Session session = sessionManager.getSession(currentSessionID); List userIDs = new ArrayList<>(); if(ids == null || ids.length == 0) < userIDs.add(session.currentUserID); >else < Collections.addAll(userIDs, ids); >if(session != null) < for(String userID : userIDs) < if(Objects.equals(userID, session.currentUserID)) < service.getDataByID(data,id); result.addAll(data.documents); >> > return result; > public void save(Document document) < document.setId(CommonUtils.createID()); service.createDocument(document); >> 

DocumentManager работает со списком объектов класса Document, собранных в класс DocumentManagerData:

package core.documentManager; import dbclasses.Document; import java.util.ArrayList; import java.util.List; /** * Класс для хранения списка документов */ public class DocumentManagerData < public final Listdocuments; public DocumentManagerData() < documents = new ArrayList<>(); > public void clear() < documents.clear(); >>

Кроме того, в DocumentManager используется класс-сервис DocumentManagerService для выполнения транзакций.

package db; import core.documentManager.DocumentManagerData; import dbclasses.Document; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.query.Query; import java.util.List; public class DocumentManagerService < private final SessionFactory sessionFactory; public DocumentManagerService(SessionFactory sessionFactory) < this.sessionFactory = sessionFactory; >public void getData(DocumentManagerData data) < data.clear(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); try < data.documents.addAll(session.createQuery("SELECT row FROM Document row", Document.class).list()); transaction.commit(); >catch (Exception e) < transaction.rollback(); throw e; >finally < session.close(); >> public void getDataByUserID(DocumentManagerData data, String id) < data.clear(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); try < data.documents.addAll(session.createQuery("SELECT row FROM Document row WHERE row.userID= : userID", Document.class). setParameter("userID", id). list()); transaction.commit(); >catch (Exception e) < transaction.rollback(); throw e; >finally < session.close(); >> public void getDataByID(DocumentManagerData data, String id) < data.clear(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); try < data.documents.addAll(session.createQuery("SELECT row FROM Document row WHERE row.id= : id", Document.class). setParameter("id", id). list()); transaction.commit(); >catch (Exception e) < transaction.rollback(); throw e; >finally < session.close(); >> public void createDocument(Document document) < Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); try < session.persist(document); transaction.commit(); >catch (Exception e) < transaction.rollback(); throw e; >finally < session.close(); >> >

Осталось привести код класса-сущности Document:

package dbclasses; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; @Entity @Table(name = "Documents") public class Document extends ObjectWithID < @Column(name = "userid") private String userID; @Column(name = "title") private String title; @Column(name = "filepdf") private byte[] filepdf; @Column(name = "filetext") private byte[] filetext; public String getUserID() < return userID; >public void setUserID(String userID) < this.userID = userID; >public String getTitle() < return title; >public void setTitle(String title) < this.title = title; >public byte[] getFilepdf() < return filepdf; >public void setFilepdf(byte[] filepdf) < this.filepdf = filepdf; >public byte[] getFiletext() < return filetext; >public void setFiletext(byte[] filetxt) < this.filetext = filetxt; >>

Весь фронтэнд находится в пакете web (рисунок 3) и состоит из пяти страниц HTML, за внешний вид отвечают ассоциированные с ними файлы CSS, за функционал естественно файлы на JavaScript. Обмен данных с сервером проходит с помощью технологии AJAX, реализованной в буферном классе NetworkClient, через который проходят все запросы со всех страниц (экземпляр NetworkClient присутствует на каждом классе для страницы).

Например, реализация отправки данных для авторизации в NetworkClient выполнена следующим образом:

export default class NetworkClient < constructor(parent) < this._parent = parent; this._serverUrl = 'handler'; this._defaultTimeout = 30000; //миллисек >#executeCommand(commandName, commandParameters, onSuccess, onError) < let query = < method: 'POST', url: this._serverUrl, timeout: this._defaultTimeout, context: this._parent, success: onSuccess, error: onError >; let parameters = commandParameters; if (parameters instanceof FormData) < parameters.append("cmd", commandName); query.data = parameters; query.processData = false; query.contentType = false; >else < parameters.cmd = commandName; query.data = parameters; >$.ajax(query); > commandLogin(username, password, onSuccess, onError) < let command = "SESSION_OPEN"; let commandParameters = ; this.#executeCommand(command, commandParameters, onSuccess, onError); >

Метод commandLogin принимает параметры от пользователя (логин и пароль), добавляет команду для сервера «SESSION_OPEN» и отправляет в метод executeCommand (принимающий и другие запросы). В executeCommand формируется POST запрос, который с использованием AJAX отправляется на сервер. В случае успеха информация далее поступает в соответствующий метод в контроллере страницы для авторизации, в случае ошибки данные уходят в другой метод, обычно показывающий соответствующее сообщение об ошибке.

В качестве итога — демонстрация работы приложения

Продемонстрирую кратко получившееся приложение в действии. При запуске приложения была настроена стартовая страница, startform.html (рисунок 5). Кроме запуска сервлета, необходимо параллельно запустить сервер на Python — файл cvserver.py.

Рисунок 5 – Настройки запуска приложения

Со стартовой страницы можно попасть либо на страницу для авторизации, либо на страницу регистрации (рисунок 6).

Рисунок 6 – Приветственное меню и форма для регистрации

После успешного прохождения авторизации или регистрации пользователь попадает на главную страницу личного кабинета (рисунок 7), где он может загружать файлы формата *.pdf или *.wav.

Рисунок 7 – Главное меню личного кабинета

При загрузке соответствующих файлов для распознавания, они отображаются в форме загрузки в центре, либо в виде PDF (рисунок 8), либо в виде проигрывателя звука (рисунок 9).

Рисунок 8 – Загруженный pdf документ и его распознавание

При нажатии на кнопку меню «Распознать» в случае успешной операции появляется сообщение о том, что документ распознан (рисунок 8). Текстовый документ с распознанным текстом можно скачать и сохранить в базу данных, задав при этом название файла.

Рисунок 9 – Загруженное аудио с возможностью воспроизведения

Кроме того, пользователь может зайти на страницу «История операций» и выполнить загрузку исходного документа, скачать его и скачать результат распознавания (рисунок 10).

Рисунок 10 – История сохранённых документов пользователя

Таким образом пользователь может не только выполнять распознавание текущих документов, но и обращаться к истории лично им использованных документов, связанных с его профилем.

Ссылки

  1. LeCun, Y. Gradient-based learning applied to document recognition / Y. LeCun, L. Bottou, Y. Bengio, P. Haffner // Proceedings of the IEEE. – 1998. – Vol. 86, Issue 11. – P. 2278-2323.
  2. . Rabiner, L.R. A tutorial on hidden Markov models and selected applications in speech recognition / L.R. Rabiner // Proceedings of the IEEE. – 1989. – Vol. 77, issue 2. – P. 257 — 286.
  3. Hochreiter S. Long Short-term Memory / S. Hochreiter, J. Schmidhuber // Neural Computation. – 1997. – Vol. 9, no. 8. – P. 1735-80.
  • обработка естественного языка
  • mvc
  • сервер
  • база данных
  • ocr-технологии
  • Сезон java one love

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *