OCR na edge
Nesta página
À medida que a tecnologia avançada se torna comum, a visão por computador passou a ser uma ferramenta que parece essencial, com muitas aplicações.
Serve para identificar objetos, padrões, pessoas, plantas e animais de estimação, dá informação útil a quem a usa, apoia decisões e facilita a triagem de conjuntos de dados enormes.
Junta a isto a procura crescente de análise em tempo real e de respostas com baixa latência, e a edge compute tornou-se o melhor sítio para pores o teu modelo treinado a funcionar.
O que é o OCR?
O reconhecimento ótico de caracteres (OCR, de Optical Character Recognition) é o processo de converter imagens de texto em texto codificado para máquinas. Costuma usar-se para transformar imagens de documentos em texto que se pode pesquisar e indexar.
Podes usá-lo para digitalizar documentos, recibos, cartões de visita e mais. Percebe-se logo porque é útil na Blinq.
O que é a edge compute?
A edge compute é a prática de correr o teu código o mais perto possível do utilizador. Normalmente faz-se correndo o código numa CDN, como os Cloudflare Workers ou o AWS Lambda@Edge.
É diferente da cloud tradicional, em que o código corre num centro de dados que costuma estar longe do utilizador.
Porquê a edge compute?
Embora seja possível correr o modelo no dispositivo, deixá-lo na edge tem algumas vantagens:
- Baixa latência: O modelo está mais perto do utilizador, por isso a resposta é mais rápida. Parece que está no dispositivo.
- Baixo custo: Não pagas os recursos de computação para manter o modelo a correr, só pagas os pedidos.
- Fácil de atualizar: Podes atualizar o modelo sem atualizar a app. Ajuda sobretudo se tiveres muitos utilizadores em versões antigas da app.
Na Blinq, mantemos uma cauda longa de versões da app para telemóvel, por isso poder atualizar o modelo sem atualizar a app é uma grande vantagem para nós.
Como correr o teu modelo na edge
Os runtimes de edge, como os Cloudflare Workers e o Lambda@Edge, são muito diferentes dos runtimes tradicionais, como o Node.js e o Golang. Foram feitos para serem rápidos e leves, por isso não têm acesso às mesmas bibliotecas e APIs.
Parecem-se muito com correr o código num browser, por isso não adianta copiar e colar o código à espera de que funcione.
O modelo
Neste exemplo uso um modelo de OCR simples, o Keras OCR. É um modelo TensorFlow que recebe uma imagem e devolve o texto que lá encontra.
Também uso a funcionalidade Constellation da Cloudflare (em beta), que permite correr modelos ONNX na edge. É uma ótima novidade, porque deixa correr modelos que o TensorFlow.js não suporta e que não ficam limitados pela quota de 10MB dos Workers da Cloudflare.
O Lambda@Edge tem limites muito mais altos, mas continua a ser boa ideia manter o modelo o mais pequeno possível para reduzir o tempo de arranque a frio.
Neste exemplo usei o modelo crnn_kurapan, disponível aqui.
Converter Keras em ONNX
O primeiro passo é converter o modelo Keras em ONNX. É um processo bastante simples, mas exige alguns passos. Parto do princípio de que tens o Conda instalado e usas Python 3.10.
Se estiveres num Mac com Apple Silicon, precisas também de:
tensorflow-macos
Primeiro, cria um ambiente conda.
$ conda create -n edge-onnx python=3.10 pip
$ conda activate edge-onnx
A seguir, instala as dependências.
$ python -m tensorflow tf2onnx keras-ocr
Se estiveres num Mac com Apple Silicon, tens de instalar o tensorflow-macos em vez do tensorflow.
$ python -m pip install tensorflow-macos
Por fim, converte o modelo com este excerto.
import tf2onnx
import onnx
import keras_ocr
recognition = keras_ocr.recognition.Recognizer(
alphabet='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', weights='kurapan')
output_path = 'crnn_kurapan.onnx'
model_proto, _ = tf2onnx.convert.from_keras(
recognition.model, opset=None, output_path=output_path)
onnx.save(model_proto, output_path)
Isto cria um modelo ONNX na pasta atual, em crnn_kurapan.onnx.
Correr o modelo na edge
Para correr o modelo ONNX na edge compute, usei o Cloudflare Constellation, porque o modelo é demasiado grande para os Cloudflare Workers.
Podes fazê-lo pelo painel da Cloudflare ou pela CLI. Eu uso a CLI, porque é mais fácil de automatizar.
Primeiro, temos de criar um projeto.
$ npx wrangler constellation project create "ocr" ONNX
$ npx wrangler constellation project list
Toma nota do ID do projeto.
A seguir, carrega o modelo:
$ wrangler constellation model upload ocr crnn_kurapan crnn_kurapan.onnx
$ npx wrangler constellation model list ocr
Toma nota do ID do modelo.
Depois, prepara o projeto e inicializa o wrangler.
$ npm create cloudflare@2
Chamei ocr ao projeto e usei o template “Hello World”.
No ficheiro wrangler.toml, acrescenta a ligação ao constellation e ativa o node_compat.
name = "ocr"
main = "src/index.js"
node_compat = true
workers_dev = true
compatibility_date = "2023-07-01"
constellation = [
{binding = 'OCR', project_id = '{{ project ID from earlier }}'},
]
Por fim, instala o cliente do constellation e as dependências para enviar imagens em stream para o modelo.
npm install --save @cloudflare/constellation string-to-stream pngjs
Agora podemos escrever o código que corre o modelo.
import str from 'string-to-stream';
import { PNG } from 'pngjs/browser';
import { Tensor, run } from '@cloudflare/constellation';
const MODEL_ID = '{{ model ID from earlier }}';
function normalizeImage(data) {
return new Promise(async (resolve, reject) => {
const stream = str(data);
const png = new PNG({ filterType: 4 });
stream
.pipe(png)
.on('parsed', function () {
const [r, g, b] = new Array(3).fill([]);
for (let i = 0; i < this.data.length; i += 4) {
r.push(this.data[i] / 255.0);
g.push(this.data[i + 1] / 255.0);
b.push(this.data[i + 2] / 255.0);
}
resolve({
input: [...r, ...g, ...b],
shape: [3, this.height, this.width, 3],
});
})
.on('error', function (error) {
reject({ err: error.toString() });
});
});
}
export default {
async fetch(request, env) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const formData = await request.formData();
const image = formData.get('image');
if (!image) {
return new Response('No image found', { status: 400 });
}
const buffer = await image.arrayBuffer();
try {
const normalized = await normalizeImage(buffer);
if (!normalized) {
return new Response('Unable to normalize image', { status: 500 });
}
const input = new Tensor(
'float32',
normalized.shape,
normalized.input,
'input_1'
);
const output = await run(env.OCR, MODEL_ID, input);
return new Response(JSON.stringify(output), { status: 200 });
} catch (err) {
return new Response(err, { status: 500 });
}
},
};
Enquanto estiver em beta, talvez tenhas de desativar os avisos do constellation com export NO_CONSTELLATION_WARNING=true.
Quando estiveres pronto para fazer deploy, basta correr wrangler deploy.
Testar o modelo
Agora podes fazer um pedido HTTP ao Worker para testar o modelo.
$ curl -X POST -F "
image=@/path/to/image.png" https://ocr.example.workers.dev
Isto devolve uma resposta JSON com o resultado do modelo.
{
"output_1": [...]
}
O resultado do modelo é um array de probabilidades para cada caractere. Para o converter em texto, podes usar algo parecido com este excerto.
const charset =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const text = output
.map((probabilities) => {
const max = Math.max(...probabilities);
const index = probabilities.indexOf(max);
return charset[index];
})
.join('');
Porquê fazer isto?
Queria ver se era possível correr um modelo grande-ish na edge. Queria também ver que desempenho teria.
Embora seja pouco provável que este modelo vá para produção, é um bom exemplo do que se consegue com os Cloudflare Workers e o Constellation. Os modelos de OCR maiores costumam ter melhor desempenho, mas dá para usar modelos mais pequenos se aceitares perder precisão. Ainda assim, podes sempre recorrer ao Tesseract.js para isso.
Desempenho
O modelo demora cerca de 1,5 segundos a correr na edge. É muito mais lento do que correr localmente, mas ainda é rápido que chegue para produção. Mesmo assim, num cenário real também podes recorrer às ofertas da Google e da Amazon Web Services, que são muito mais precisas. Em contrapartida, são também muito mais caras.
Precisão
O modelo é bastante preciso, mas não é perfeito. Tem dificuldades com algumas fontes e não é bom a reconhecer texto em imagens com muito ruído. Só lida com imagens pequenas, mas isso melhora com uma janela deslizante.
Experimenta tu
A Cloudflare está a desenvolver ativamente o Constellation, que de momento está em beta. Se o quiseres experimentar, podes inscrever-te no beta aqui.
Há também um canal de Discord onde podes fazer perguntas e pedir ajuda à equipa da Cloudflare.