Eine fotografierte Rechnung ist noch kein Text
Ein Laden fotografiert eine Papierrechnung und will die Summe in seiner Datenbank haben. Das Foto besteht nur aus Pixeln. Ein Leseprogramm muss zuerst die Wörter finden.
Es liefert jedes Textstück mit seinem Ort auf der Seite. Ihr Code bringt die Stücke dann in Ordnung und holt die Felder heraus.
Am Ende dieser Lektion
Notiz
Ich kann eine Seite aus Textfeldern in Zeilen in Lesereihenfolge bringen und ihre Felder mit der Sicherheit herausziehen.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Box | ein Textstück und sein Ort auf der Seite | |
| Lesereihenfolge | die Reihenfolge, in der ein Mensch die Boxen läse | |
| Feld | eine Beschriftung und der Wert daneben | Summe: 46,50 |
| Sicherheit | wie sicher sich der Leser ist, von 0 bis 100 | |
| Toleranz | wie weit zwei Boxen auseinander liegen dürfen und doch eine Zeile teilen |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Eine Seite ist eine Liste von Boxen
Ein Seitenleser gibt Ihnen keinen einzigen Textblock. Er gibt eine Liste von Boxen. Jede Box hat einen Text, ihren Ort (x quer, y nach unten) und eine Sicherheit von 0 bis 100.
Die Liste hat keine brauchbare Ordnung. Zwei Boxen, die wie eine Zeile aussehen, können einige Pixel auseinander liegen.
Vier Boxen einer Seite
| Text | x | y | Sicherheit |
|---|---|---|---|
| Summe: | 40 | 200 | 98 |
| 46,50 | 120 | 201 | 61 |
| Name: | 40 | 60 | 97 |
| Anja | 120 | 62 | 95 |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Die Lesereihenfolge braucht eine Toleranz
Um eine Seite zu lesen, sortieren Sie die Boxen von oben nach unten, gruppieren sie zu Zeilen und sortieren jede Zeile von links nach rechts.
Boxen einer Zeile haben selten genau dasselbe y. Eine Box gehört darum zu einer Zeile, wenn ihr y innerhalb einer Toleranz vom y der ersten Box liegt. Hier gelten 5 Pixel.
Ein häufiger Fehler
Warnung
Viele sortieren die Boxen nur nach y und lesen sie in dieser Reihenfolge. Ein Wert, der ein paar Pixel höher liegt als seine Beschriftung, kommt dann vor ihr. Gruppieren Sie die Boxen zuerst mit einer Toleranz zu Zeilen und sortieren Sie dann jede Zeile nach x.
Ein Feld ist eine Beschriftung mit ihrem Wert
In einem Formular folgt einer Beschriftung wie Summe: ihr Wert in derselben Zeile. Um ein Feld zu holen, suchen Sie eine Box, die mit einem Doppelpunkt endet, und nehmen die nächste Box der Zeile.
Behalten Sie die Sicherheit beim Wert. Ein niedriger Wert heißt, dass ein Mensch nachsehen sollte, denn eine falsch gelesene Ziffer sieht genauso aus wie eine richtige.
Modell-Helfer schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const basis = process.env.AI_URL || \"http://ai:4000\";' '// Die Token einer Antwort oder null, wenn sie keine nennt.' 'function zaehlen(daten, name) {' ' return daten.usage && typeof daten.usage[name] === \"number\" ? daten.usage[name] : null;' '}' > model.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Modell-Helfer schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function fragen(nachrichten, probe, max) {' ' try {' ' const res = await fetch(basis + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: nachrichten, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"Status \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, eingabe: zaehlen(d, \"prompt_tokens\"), ausgabe: zaehlen(d, \"completion_tokens\"), aufgezeichnet: false };' ' } catch (fehler) {' ' console.log(\"Aufgezeichnete Probe: Kein Modell hat geantwortet (\" + fehler.message + \").\");' ' return { text: probe, eingabe: null, ausgabe: null, aufgezeichnet: true };' ' }' '}' 'module.exports = { fragen };' >> model.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Seiten-Skript schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { fragen } = require(\"./model\");' 'const boxen = [' ' { text: \"46,50\", x: 120, y: 201, sicher: 61 },' ' { text: \"Summe:\", x: 40, y: 200, sicher: 98 },' ' { text: \"Rechnung\", x: 40, y: 10, sicher: 99 },' ' { text: \"Anja\", x: 120, y: 62, sicher: 95 },' ' { text: \"Name:\", x: 40, y: 60, sicher: 97 },' ' { text: \"12.03.2026\", x: 120, y: 99, sicher: 88 },' ' { text: \"Datum:\", x: 40, y: 100, sicher: 98 },' '];' > seite3.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Seiten-Skript schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function zeilen(liste, tol) {' ' const sortiert = liste.slice().sort(function (a, b) { return a.y - b.y || a.x - b.x; });' ' const aus = [];' ' for (const b of sortiert) {' ' const letzte = aus[aus.length - 1];' ' if (letzte && Math.abs(b.y - letzte[0].y) <= tol) letzte.push(b);' ' else aus.push([b]);' ' }' ' return aus.map(function (z) { return z.sort(function (a, b) { return a.x - b.x; }); });' '}' >> seite3.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Seiten-Skript schreiben, Teil 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function felder(zs) {' ' const gefunden = {};' ' for (const z of zs) {' ' for (let i = 0; i < z.length - 1; i++) {' ' if (z[i].text.endsWith(\":\")) gefunden[z[i].text.slice(0, -1).toLowerCase()] = { wert: z[i + 1].text, sicher: z[i + 1].sicher };' ' }' ' }' ' return gefunden;' '}' >> seite3.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Seiten-Skript schreiben, Teil 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const zs = zeilen(boxen, 5);' ' for (const z of zs) console.log(z.map(function (b) { return b.text; }).join(\" \"));' ' const f = felder(zs);' ' console.log(JSON.stringify(f));' ' const wenig = Object.keys(f).filter(function (k) { return f[k].sicher < 90; });' ' console.log(\"von Hand prüfen: \" + wenig.join(\", \"));' ' const frage = \"Schreiben Sie eine kurze Frage, die eine Person bittet, eine Summe zu bestätigen, die nur unsicher gelesen wurde.\";' ' const antwort = await fragen([{ role: \"user\", content: frage }], \"Bitte prüfen Sie diese Summe auf dem Papier: Sind es 46,50 Euro?\", 40);' ' console.log(\"Antwort: \" + antwort.text);' '}' 'main();' >> seite3.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Die Seite lesen und Felder holen
docker exec -w /home/node/project agent node seite3.jsHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Die Zeilen, die der Run druckt
| Zeile | Was sie zeigt |
|---|---|
| Name: Anja | Zwei Boxen zu einer Zeile verbunden, die Beschriftung zuerst |
| Datum: 12.03.2026 | y 99 und y 100 liegen innerhalb der Toleranz von 5 |
| {"name":{...},"datum":{...},"summe":{...}} | Jedes Feld mit Wert und Sicherheit |
| von Hand prüfen: datum, summe | Die zwei Werte unter 90 |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.