← Alle berichten

OCR op edge compute

Op deze pagina

Geavanceerde technologie is gewoon geworden en computer vision is daardoor een onmisbaar gereedschap met veel toepassingen.

Je kunt het gebruiken om voorwerpen, patronen, mensen, planten en huisdieren te herkennen. Dat geeft gebruikers inzicht, helpt bij beslissingen en maakt het makkelijker om door enorme datasets te zoeken.

Daar komt de vraag naar realtime analyse en lage latency bij. Edge compute is daarom de beste plek geworden om je getrainde model te draaien.

Wat is OCR?

Optical Character Recognition (OCR) zet afbeeldingen van tekst om in door machines leesbare tekst. Je gebruikt het meestal om afbeeldingen van documenten om te zetten in tekst die je kunt doorzoeken en indexeren.

Je kunt hiermee documenten, bonnetjes, visitekaartjes en meer scannen. Bij Blinq snap je meteen waarom dat handig is.

Wat is edge compute?

Edge compute betekent dat je code zo dicht mogelijk bij de gebruiker draait. Meestal doe je dat op een CDN, zoals Cloudflare Workers of AWS Lambda@Edge.

Bij traditionele cloud compute draait je code in een datacenter dat meestal ver van de gebruiker staat.

Waarom edge compute?

Je kunt je model ook op het apparaat zelf draaien, maar op de edge heeft het een paar voordelen:

  • Lage latency: Het model staat dichter bij de gebruiker, dus de reactietijd is korter. Het voelt alsof het op het apparaat draait.
  • Lage kosten: Je betaalt niet voor de rekenkracht om het model te draaien, alleen voor de requests.
  • Makkelijk te updaten: Je kunt het model bijwerken zonder de app te updaten. Dat is vooral handig als je veel gebruikers op oude versies van je app hebt.

Bij Blinq onderhouden we een lange staart aan versies van de mobiele app. Het model kunnen bijwerken zonder de app te updaten scheelt ons dus enorm.

Je model op de edge draaien

Edge runtimes zoals Cloudflare Workers en Lambda@Edge zijn heel anders dan traditionele runtimes als Node.js en Golang. Ze zijn gebouwd om snel en licht te zijn, dus je hebt niet dezelfde libraries en API’s.

Het lijkt veel op code draaien in een browser. Je kunt je code dus niet zomaar kopiëren en plakken en verwachten dat het werkt.

Het model

Voor dit voorbeeld gebruik ik een eenvoudig OCR-model: Keras OCR. Het is een TensorFlow-model dat een afbeelding krijgt en de tekst teruggeeft die het erin vindt.

Ik gebruik ook Cloudflare’s Constellation (in beta). Daarmee draai je ONNX-modellen op de edge. Dat is een mooie nieuwe optie, want je kunt er modellen mee draaien die TensorFlow.js niet ondersteunt en die niet vastzitten aan de Worker-limiet van 10MB bij Cloudflare.

Lambda@Edge heeft veel ruimere limieten, maar het blijft verstandig om je model zo klein mogelijk te houden. Dan is de cold start korter.

Voor dit voorbeeld heb ik het crnn_kurapan-model gebruikt, dat je hier vindt.

Keras omzetten naar ONNX

De eerste stap is het Keras-model omzetten naar ONNX. Dat is vrij simpel, maar het kost een paar stappen. Ik ga ervan uit dat je Conda hebt geïnstalleerd en Python 3.10 gebruikt.

Gebruik je Apple Silicon, dan heb je ook dit nodig:

tensorflow-macos

Maak eerst een conda-omgeving aan.

$ conda create -n edge-onnx python=3.10 pip
$ conda activate edge-onnx

Installeer daarna de dependencies.

$ python -m tensorflow tf2onnx keras-ocr

Op Apple Silicon installeer je tensorflow-macos in plaats van tensorflow.

$ python -m pip install tensorflow-macos

Zet het model ten slotte om met dit fragment.

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)

Dit maakt een ONNX-model in de huidige map, als crnn_kurapan.onnx.

Het model op de edge draaien

Om het ONNX-model op edge compute te draaien gebruik ik Cloudflare Constellation, omdat het model veel te groot is voor Cloudflare Workers.

Je kunt dit doen via het Cloudflare-dashboard of de CLI. Ik gebruik de CLI, want die is makkelijker te automatiseren.

Eerst maken we een project aan.

$ npx wrangler constellation project create "ocr" ONNX
$ npx wrangler constellation project list

Noteer het project-ID.

Upload daarna het model:

$ wrangler constellation model upload ocr crnn_kurapan crnn_kurapan.onnx
$ npx wrangler constellation model list ocr

Noteer het model-ID.

Zet daarna het project op en initialiseer wrangler.

$ npm create cloudflare@2

Ik noemde het project ocr en koos de ‘Hello World’-template.

Voeg in het bestand wrangler.toml de constellation-binding toe en zet node_compat aan.

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 }}'},
]

Installeer ten slotte de constellation-client en de dependencies om afbeeldingen naar het model te streamen.

npm install --save @cloudflare/constellation string-to-stream pngjs

Nu kunnen we de code schrijven om het model te draaien.

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 });
    }
  },
};

Zolang Constellation in beta zit, moet je de constellation-waarschuwingen misschien uitzetten met export NO_CONSTELLATION_WARNING=true.

Ben je klaar om te deployen, voer dan gewoon wrangler deploy uit.

Het model testen

Nu kun je een HTTP-request naar de Worker sturen om het model te testen.

$ curl -X POST -F "
image=@/path/to/image.png" https://ocr.example.workers.dev

Dit geeft een JSON-respons terug met de uitvoer van het model.

{
  "output_1": [...]
}

De uitvoer van het model is een array met kansen per teken. Om dat om te zetten naar tekst kun je zoiets als dit fragment gebruiken.

const charset =
  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

const text = output
  .map((probabilities) => {
    const max = Math.max(...probabilities);
    const index = probabilities.indexOf(max);

    return charset[index];
  })
  .join('');

Waarom doe je dit?

Ik wilde weten of je een redelijk groot model op de edge kunt draaien. En ik wilde zien hoe goed het presteert.

Dit model komt waarschijnlijk niet in productie, maar het laat goed zien wat er kan met Cloudflare Workers en Constellation. Grotere OCR-modellen presteren meestal beter, maar kleinere modellen kunnen ook, als je wat nauwkeurigheid wilt inleveren. Daarvoor kun je ook altijd Tesseract.js gebruiken.

Prestaties

Het model doet er op de edge ongeveer 1,5 seconden over. Dat is een stuk trager dan lokaal, maar nog snel genoeg voor productie. In de praktijk kun je ook de diensten van Google en Amazon Web Services gebruiken. Die zijn veel nauwkeuriger, maar ook een stuk duurder.

Nauwkeurigheid

Het model is redelijk nauwkeurig, maar niet perfect. Het heeft moeite met sommige lettertypen en herkent tekst slecht in afbeeldingen met veel ruis. Het kan ook alleen kleine afbeeldingen aan, maar dat los je op met een sliding window.

Probeer het zelf

Cloudflare werkt actief aan Constellation en het zit nu in beta. Wil je het zelf proberen, dan kun je je hier aanmelden voor de beta.

Er is ook een handig Discord-kanaal waar je vragen kunt stellen en hulp krijgt van het Cloudflare-team.

Discussie op Hacker News