Создание микросервиса FastAPI OCR
19 декабря 2022 г.Иногда вам нужно настроить микрослужбу OCR, которая принимает изображения и возвращает текст. В этой статье я попытаюсь объяснить основные идеи о том, как бесплатно создать собственный сервис распознавания текста, используя python, fastAPI, tesseract, redis, celery
и docker
.< /p>
Что такое микросервисная архитектура?
Это один из вариантов серверной архитектуры, основанный на мельчайших независимых службах (веб-приложениях), которые взаимодействуют друг с другом с помощью протоколов на основе SOAP, REST, GraphQL и RPC. В нашем микросервисе мы будем использовать архитектурный стиль REST для взаимодействия микросервиса с одним основным узлом микросервиса (fastAPI_service
), но имейте в виду, что это не лучший вариант.
Наша микросервисная архитектура.
В нашей службе OCR у нас будет 9 микрослужб с дизайном архитектуры оркестровки, где у нас есть одна основная микрослужба, которая взаимодействует с другими. Я покажу вам, как создать каждую службу и заставить ее работать с другими.
Конечные точки FastAPI.
Служба FastAPI будет точкой входа в нашу службу OCR. Все коммуникации будут там. Он имеет python + fastAPI + uvicorn
. Он будет иметь 3 конечных точки. Конечная точка POST /api/v1/ocr/img
, GET /api/v1/ocr/status/{task_id}
и GET /api/v1/ocr/ текст/{идентификатор_задачи}код>.
Мы собираемся использовать конечную точку POST /api/v1/ocr/img
для загрузки изображений. Кроме того, он запустит задачу и даст нам идентификатор задачи. С помощью GET /api/v1/ocr/status/{task_id}
мы получим статус нашей задачи (процесс ocr — довольно «тяжелая» задача, и ее выполнение займет некоторое время), после получения статус успеха, который мы собираемся использовать, и конечную точку GET /api/v1/ocr/text/{task_id}
, чтобы увидеть окончательные результаты.
Во-первых, давайте создадим папку проекта и поместим следующую файловую структуру:
Для каждого сервиса у нас будет своя папка, я использую VS Code
для написания кода, а для создания virtualenv
я использую pipenv
упаковка. Для локального тестирования вне Docker я буду запускать службы в виртуальных средах.
Давайте посмотрим на main.py
.
from fastapi import FastAPI
from app.routers import ocr
app = FastAPI()
app.include_router(ocr.router)
@app.get("/")
async def root():
return {"message": "Hello Hackernoon.com!"}
Здесь мы определяем только одну корневую конечную точку GET. Он возвращает простое сообщение JSON. Кроме того, мы включаем маршрутизатор OCR, где у нас есть список конечных точек, принадлежащих OCR. Хорошей практикой является не помещать все ~~яйца в одну корзину~~ конечные точки в один файл, потому что это будет перегружено и не легко для понимания. Попробуйте разделить свой код на небольшие логически независимые части и соединить их короткими строками кода в один основной файл. Давайте посмотрим на наш маршрутизатор OCR, который мы включили в основной маршрутизатор FastAPI.
from fastapi import APIRouter
from model import ImageBase64
router = APIRouter(
prefix="/ocr",
tags=["ocr"],
)
@router.get("/status")
async def get_status():
return {"message": "ok"}
@router.get("/text")
async def get_text():
return {"message": "ok"}
@router.post("/img")
async def create_item(img: ImageBase64):
return {"message": "ok"}
В файле ocr.py
у нас есть маршрутизатор, который содержит три конечные точки: GET /ocr/status
, GET /ocr/text
и Конечная точка POST ocr/img
. Кроме того, мы импортируем модель данных для конечной точки POST. Я определил простую логику для проверки конечных точек. Мы можем запустить нашу службу через cmd.exe
.
«uvicorn app.main:app –reload»
Uvicorn
— это реализация веб-сервера ASGI для Python. Он запускает файл main.py
. – reload означает, что если мы изменим код внутри файлов, uvicorn
автоматически перезапустит новый код.
Базовое тестирование.
Для тестирования конечных точек мы будем использовать thunder client
в VS Code
. Сначала мы проверим наши конечные точки. GET http://127.0.0.1:8000
и GET http://127.0.0.1:8000/api/v1/ocr/status
конечной точки.
Оба работают нормально, теперь нам нужно написать логику. Мы получим наше изображение в формате строки base64 и получим сгенерированный task_id. Используя этот task_id, мы перейдем к GET /ocr/status
и получим наш статус обработки OCR, у нас будет три типа статуса: ожидание, успех и ошибка. После получения статуса успеха мы получим текст из конечной точки GET /ocr/text
, используя наш task_id.
Чтобы получить статус и отслеживать его, мы будем использовать пакет redis
. Для выполнения параллельного процесса мы будем использовать пакет celery
. Давайте установим докер и напишем код.
Установка Docker.
Перейдите на https://www.docker.com/
и установите Docker Desktop. После установки, если вы работаете в Windows, вам также необходимо установить пакет wsl
и получить образ системы Linux. Подробные инструкции описаны по адресу https://docs.docker.com/desktop/install/windows-install/.
После успешной установки запустите docker desktop, и вы увидите это окно.
Настройка контейнера FastAPI.
Теперь нам нужно создать Dockerfile
, в котором будут все необходимые команды docker
для запуска нашей службы fastAPI
внутри docker< /code> среда.
FROM python:3.11
WORKDIR /app
RUN apt-get update && apt-get install -y && apt-get clean
RUN pip install –upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt && rm -rf /root/.cache/pip
COPY . .
Короче говоря, мы создаем рабочий каталог с именем «app» и помещаем в него requirements.txt
, устанавливая все пакеты из этого файла с помощью python 3.11
. После всего удаляем кешированные данные pip и копируем все наши файлы в рабочий каталог внутри контейнера. Итак, наш первый контейнер с точкой входа готов. Теперь нам нужно создать еще один файл — docker-compose.yml
. Вкратце, это простой файл, содержащий все команды Docker, которые создают, развертывают и выполняют все контейнеры вместе с помощью простой однострочной команды.
version: '3.8'
services:
web:
build: ./fastapi_service
ports:
- 8001:8000
command: uvicorn app.main:app --host 0.0.0.0 –reload
Теперь у нас есть обновленная структура проекта.
Мы готовы разместить вашу первую службу fastAPI
в контейнере. Для этого нам нужно написать команды docker-compose
. Он создает и запускает все контейнеры.
Запустите cmd.exe
в папке, содержащей файл docker-compose.yml
, с помощью команды docker-compose up --build
и посмотрите .
Теперь наша служба fastAPI
работает внутри докера. Мы изменили номер порта на 8001 с помощью файла docker-compose
.
Все построить.
Сначала клонируйте https://github.com/abizovnuralem/ocr.
. Вы увидите эту структуру проекта.
Я разделил каждое приложение на собственную папку с собственным экземпляром celery
и redis
. Я решил использовать 3 экземпляра redis
и celery
, чтобы сделать каждый микросервис независимым. Эти приложения имеют собственный dockerfile
, куда мы устанавливаем виртуальную систему и все необходимые пакеты из requirements.txt. Каждое приложение содержит файл main.py, собственную точку входа и tasks.py, где мы выполняем задачи сельдерея. Папка routers содержит конечные точки, которые помогают обмениваться данными между контейнерами по протоколу REST API.
Служба FastAPI.
Основная логика всех наших сервисов находится в файле fastapi_service/app/tasks.py
. Он координирует все процессы, получает изображения, выполняет некоторую предварительную обработку и запускает процесс распознавания тессеракта.
import os
import time
import requests
import json
from routers.ocr.model import PreProsImgResponse
from celery import Celery
app = Celery('tasks', broker=os.environ.get("CELERY_BROKER_URL"))
def check_until_done(url):
attempts = 0
while True:
response = requests.get(url)
if response.status_code == 200 and response.json()['task_status'] == "PENDING" and attempts < 60:
time.sleep(1)
attempts+=1
elif response.status_code == 200 and response.json()['task_status'] == "SUCCESS":
return True
else:
return False
def convert_img_to_bin(img):
response = requests.post(url = "http://img_prepro:8000/api/v1/img_prep/img", json={"img_body_base64": img})
task = response.json()
if check_until_done("http://img_prepro:8000/api/v1/img_prep/status" + f"/{task['task_id']}"):
url = "http://img_prepro:8000/api/v1/img_prep/img" + f"/{task['task_id']}"
response = requests.get(url)
return response.json()['img']
raise Exception("Sorry, something went wrong")
def get_ocr_text(img):
response = requests.post(url = "http://tesseract:8000/api/v1/tesseract/img", json={"img_body_base64": img})
task = response.json()
if check_until_done("http://tesseract:8000/api/v1/tesseract/status" + f"/{task['task_id']}"):
url = "http://tesseract:8000/api/v1/tesseract/text" + f"/{task['task_id']}"
response = requests.get(url)
return response.json()['text']
raise Exception("Sorry, something went wrong")
@app.task(name="create_task")
def create_task(img: str):
try:
bin_img = convert_img_to_bin(img)
text = get_ocr_text(bin_img)
return text
except Exception as e:
print(e)
return {"text": "error"}
Мы используем всего две строки кода, чтобы выполнить все в нашем сервисе.
bin_img = convert_img_to_bin(img)
текст = get_ocr_text(bin_img)
Он выполняет некоторую предварительную обработку изображений, которая помогает tesseract распознавать изображения более точно и быстрее через REST API с помощью микросервиса img_prepro
, и запускает механизм tesseract внутри tesseract_service
через REST API и получает окончательные результаты.
Тестирование.
Для тестирования мы будем использовать это изображение.
Во-первых, нам нужно преобразовать его в строку base64. Мы собираемся использовать https://codebeautify.org/image-to-base64-converter
, чтобы получить строку изображения, а затем с помощью POST /http://localhost:8001/api. /v1/ocr/img
мы получим task_id.
Используя GET /http://localhost:8001/api/v1/ocr/status/2591ec33-11d2-4dec-8cf4-cea15e05517e
мы следим за статусом выполнения задачи, после получения статуса SUCCESS
мы получим текст от
GET /http://localhost:8001/api/v1/ocr/text/2591ec33-11d2-4dec-8cf4-cea15e05517e
Заключение
Мы создали 9 микросервисов в одном сервисе OCR. Настройте все контейнеры для запуска и взаимодействия друг с другом. Некоторые вещи, которые нам нужно сделать в ближайшем будущем:
- Ведение журнала;
- Тестирование;
- Мониторинг;
- Улучшить распознавание OCR;
Основная идея этой статьи состояла в том, чтобы показать вам, как создать простую микросервисную архитектуру, которая выполняет базовое распознавание OCR. Спасибо за внимание!
Оригинал