Как стать автором
Обновить
88.55
Smart Engines
Обработка изображений, распознавание в видеопотоке

Встраиваем распознавание документов от Smart Engines куда угодно за пять минут

Время на прочтение15 мин
Количество просмотров2.2K

Привет!

Мы, Smart Engines, многие годы занимаемся созданием ПО для распознавания документов, удостоверяющих личность, гибких форм, банковских карт, штрихкодов и так далее - всего более двух с половиной тысяч различных документов. С помощью нашего ПО клиенты решают самые разные задачи с различными сценариями использования (сканирование штрихкодов и банковских карт в мобильных приложениях и вебе, автоматизация заполнения шаблонов на основе распознанных ДУЛ, распознавание паспорта РФ). За всё время мы тысячу раз сталкивались с запросом “дайте какое-нибудь простое решение с API, которым нам можно было бы пользоваться”. Дело, конечно, хорошее, но функциональность у нашей системы очень богатая. Единый API, который подходил бы всем нашим заказчикам со своими разными задачами и разными сценариями использования, был бы переусложнен. В этой статье мы покажем пример того, как с помощью Docker, Python и нашего SDK самому реализовать простейшее решение для распознавания документов. 


Дисклеймер: в статье описан лишь пример использования библиотеки, предназначенный для оценки ресурсов на встраивание или быстрого развёртывания MVP!

Предлагаем такую схему - сервер внутри докера, реализующий некоторое API и линкующийся к нашей библиотеке распознавания, и клиент (не зависящий от нашей библиотеки) отправляющий на контролируемый вами сервер и получения результата распознавания. Это позволит иметь постоянно “живой” сервер распознавания, готовый к работе. Так, клиент отправляет серверу запрос в виде json, в котором лежат настройки для распознавания и сама картинка, потом ждёт ответ в виде json с полями распознанного документа. В самом простом случае всё сводится к отправке одной картинки на сервер.

Простейший запрос на сервер будет выглядеть так:

settings = {

  "mask": [mask], # тип распознаваемого документа, или "*" для режима автодетекции

  "input": input, # файл с картинкой в бинарном виде

  "signature": signature # персональная подпись клиента

}

В качестве “input” будет взятое из файловой системы изображение, передавать будем через питоновский asyncio:

reader, writer = await asyncio.open_connection(endpoint, port)

# Отправляем запрос

serialized_data = pickle.dumps(settings)

writer.write(struct.pack('<L', len(serialized_data)))

writer.write(serialized_data)

await writer.drain()

#Дальше ждём ответ и сохраняем его в файл:

request_size = struct.unpack('<L', await reader.readexactly(4))[0]

request_body = await reader.readexactly(request_size)

message = pickle.loads(request_body)

writer.close()

await save_result(message)

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

Для работы с модулем распознавания первым делом нужно его подключить:

sys.path.append(os.path.join(sys.path[0], './bindings/python/'))

sys.path.append(os.path.join(sys.path[0], './bin/'))

import pyidengine

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

global_engine = pyidengine.IdEngine.Create(bundle_path, lazy_init)

За само распознавание отвечает “сессия распознавания” - объект, в который нужно передать изображение, чтобы на выходе получить результат. Сессией это называется, потому что в один объект можно последовательно передать несколько изображений одного и того же документа. Тогда результат, получаемый после передачи каждого нового изображения, будет комбинированным по всем предыдущим распознаваниям из этой же сессии. Это можно использовать для повышения точности распознавания в случае, если у вас есть несколько фотографий одного и того же документа (особенно в случае неважного качества изображений), также на этом механизме строится распознавание из видеопотока. Для того чтобы настроить распознавание конкретных документов, существует отдельный механизм “опций сессии” - тоже объект, в котором хранятся настройки. 

# Создаём объект с опциями сессии

session_settings = global_engine.CreateSessionSettings()

# Сообщаем сессии, какие типы документов можно искать

for docmask in mask:

  session_settings.AddEnabledDocumentTypes(docmask)

# Создаём сессию, передавая контейнер настроек и подпись клиента

session = global_engine.SpawnSession(session_settings, signature)

  Теперь осталось передать изображение в сессию распознавания и получить результат:

# Картинка JPEG, переданная в виде base64

file = base64.b64encode(buffer).decode("utf-8")

image = pyidengine.Image.FromBase64Buffer(file)

# Отдаем картинку на распознавание и получаем результат

session.Process(image)

current_result = session.GetCurrentResult()

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

def RecognitionResult(recog_result):

  result = {}

  result['docType'] = recog_result.GetDocumentType()

  fields = {}

  # Извлекаем текстовые поля

  tf = recog_result.TextFieldsBegin()

  while(tf != recog_result.TextFieldsEnd()):

    info = tf.GetValue().GetBaseFieldInfo()

    field = {

      'name': tf.GetKey(),

      'value': tf.GetValue().GetValue().GetFirstString().GetCStr(),

      'isAccepted': info.GetIsAccepted()

    }

  # ... и извлекаем все остальное что нам нужно, если нужно ...
 

  return result

На выходе получим json, который и перешлём обратно к клиенту.

Теперь можно переходить к сборке Docker-образа с библиотекой, Python-обёрткой и скриптом сервера.

Пару слов об обёртках: поскольку мы поставляем основную библиотеку в виде shared library, обёртки представляют собой набор из интерфейса на том языке, под который собираем обёртку, и библиотеки-транслятора вызовов из приложения в нашу библиотеку. Соответственно, для использования нужно подключить интерфейс к приложению и правильно слинковать все библиотеки. Для того, чтобы пользователь мог собрать обёртки под именно ту систему, которую он использует у себя в проде, мы кладём в SDK все инструменты для автогенерации Python-обёртки.

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

# Берем образ Ubuntu и задаем его как образ для сборки

FROM ubuntu:22.04 AS builder

# Копируем наш SDK в этот образ

COPY "./IdEngineSDK" /home/idengine

# Зададим рабочий каталог

WORKDIR "/home/idengine/samples/idengine_sample_python/"

# Отключим интерактивный режим, иначе установка образа из Dockerfile не получится

ENV DEBIAN_FRONTEND noninteractive

# А теперь поставим необходимые зависимости

RUN set -xe </span>

	&& apt -y update  </span>

	&& apt -y install tcl </span>

	&& apt -y install build-essential </span>

	&& apt -y install make </span>

	&& apt -y install cmake </span>

	&& apt -y install python3-dev

# Эта команда компилирует модуль для питона для нашей библиотеки.

RUN ./build_python_wrapper.sh "../../bin" 3

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

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

Давайте протестируем на картинке из википедии. Отправим запрос вида:

 python3 client.py --image_path=/home/tolstov/Deutscher_Personalausweis_(1987_Version).jpg 

Получим ответ: 

JSON с данными
{
    "error": false,
    "response": {
        "docType": "deu.id.type2",
        "fields": {
            "birth_date": {
                "isAccepted": true,
                "name": "birth_date",
                "value": "12.08.1964"
            },
            "birth_place": {
                "isAccepted": true,
                "name": "birth_place",
                "value": "MÜNCHEN"
            },
            "expiry_date": {
                "isAccepted": true,
                "name": "expiry_date",
                "value": "07.10.2011"
            },
            "full_mrz": {
                "isAccepted": true,
                "name": "full_mrz",
                "value": "IDD<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<<1220001518D<<6408125<1110078<<<<<<<0"
            },
            "last_name": {
                "isAccepted": true,
                "name": "last_name",
                "value": "MUSTERMANN"
            },
            "last_name0": {
                "isAccepted": true,
                "name": "last_name0",
                "value": "MUSTERMANN"
            },
            "mrz_birth_date": {
                "isAccepted": true,
                "name": "mrz_birth_date",
                "value": "12.08.1964"
            },
            "mrz_cd_birth_date": {
                "isAccepted": true,
                "name": "mrz_cd_birth_date",
                "value": "5"
            },
            "mrz_cd_composite": {
                "isAccepted": true,
                "name": "mrz_cd_composite",
                "value": "0"
            },
            "mrz_cd_expiry_date": {
                "isAccepted": true,
                "name": "mrz_cd_expiry_date",
                "value": "8"
            },
            "mrz_cd_number": {
                "isAccepted": true,
                "name": "mrz_cd_number",
                "value": "8"
            },
            "mrz_doc_type_code": {
                "isAccepted": true,
                "name": "mrz_doc_type_code",
                "value": "ID"
            },
            "mrz_expiry_date": {
                "isAccepted": true,
                "name": "mrz_expiry_date",
                "value": "07.10.2011"
            },
            "mrz_gender": {
                "isAccepted": true,
                "name": "mrz_gender",
                "value": "<"
            },
            "mrz_issuer": {
                "isAccepted": true,
                "name": "mrz_issuer",
                "value": "D"
            },
            "mrz_last_name": {
                "isAccepted": true,
                "name": "mrz_last_name",
                "value": "MUSTERMANN"
            },
            "mrz_line1": {
                "isAccepted": true,
                "name": "mrz_line1",
                "value": "IDD<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<<"
            },
            "mrz_line2": {
                "isAccepted": true,
                "name": "mrz_line2",
                "value": "1220001518D<<6408125<1110078<<<<<<<0"
            },
            "mrz_name": {
                "isAccepted": true,
                "name": "mrz_name",
                "value": "ERIKA"
            },
            "mrz_nationality": {
                "isAccepted": true,
                "name": "mrz_nationality",
                "value": "D"
            },
            "mrz_number": {
                "isAccepted": true,
                "name": "mrz_number",
                "value": "122000151"
            },
            "mrz_opt_data_2": {
                "isAccepted": true,
                "name": "mrz_opt_data_2",
                "value": ""
            },
            "name": {
                "isAccepted": true,
                "name": "name",
                "value": "ERIKA"
            },
            "nationality": {
                "isAccepted": true,
                "name": "nationality",
                "value": "DEUTSCH"
            },
            "number": {
                "isAccepted": false,
                "name": "number",
                "value": "1946881314"
            }
        }
    }
}


Описанные выше скрипты для клиент-серверного взаимодействия, а также dockerfile для сборки и документацию мы предоставляем в составе отгружаемого SDK, как один из примеров использования нашей библиотеки. Посмотреть их можно также на гитхабе.

Теги:
Хабы:
+3
Комментарии0

Публикации

Информация

Сайт
smartengines.ru
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия