Bildanalyse und Erzeugung strukturierter Daten mit KI
Beitrag von Frank Seidel, Köln
Hier wird gezeigt, wie die Künstliche Intelligenz von Google Gemini AI in einer PHP-Applikation praktisch genutzt werden kann, um Artikelpositionen aus Handyfotos von Kassenbons auszulesen und in strukturierte Daten zu überführen.
Teilaspekte des Versuchs im Einzelnen:
- Erstellung einer PHP-Applikation, die KI nutzt
- Verwendung einer Client-Bibliothek für Google Gemini AI
- programmatische Kommunikation mit einer KI zur Automatisierung von Bildanalysen
- Einsatz des neuen Google Gemini 2.0 Flash Modells
- Prompt Engineering mit Google AI Studio
- Bildanalyse von Kassenbons (OCR)
- Erzeugung von CSV-Datei
- Code-Repository bei GitHub
Erste Versuche mit Tesseract
Mit dem Problem, Kassenbons zu digitalisieren und ihre Artikelpositionen in einem strukturierten Dateiformat wie CSV zu speichern, hatte ich mich schon einmal vor ca. 5 Jahren beschäftigt. Auf der Suche nach OCR-Lösungen, die das Lesen von Kassenbons ermöglichen würden, bin ich damals auf Tesseract gestossen und hatte einige Tage lang damit experimentiert. Die Ergebnisse waren nicht schlecht, die Datenqualität allerdings volatil. Zwar konnte man durch prozedurale Programmierung, Vor- und Nachbearbeitungen die Datenqualität anheben. Ohne weiteres Training des Modells und damit verbundener Einarbeitung war nach meinem Dafürhalten aber stets damit zurechnen, dass mit jedem neu hinzukommenden Händler und seinem spezifischen Kassenbon-Layout die Datenqualität wieder in Frage stehen würde und Feinabstimmungen erneut vorzunehmen wären. Ich nahm mir vor, einen weiteren Versuch hinsichtlich der Problemstellung zu einem späteren Zeitpunkt zu unternehmen. Dieser Zeitpunkt ist jetzt.
Versuche mit multimodalen KI-Modellen
Evaluiert habe ich nun, ob die neuen multimodalen KI-Modelle, über die man in den letzten Monaten einiges lesen konnte, auf Anhieb praxistaugliche Ergebnisse zu meiner speziellen Problemstellung liefern. Ausprobiert habe ich den Vorgang mit verschiedenen Google Gemini Modellen. Die Kassenbons habe ich mit meinem Smartphone fotografiert und mit der Android-eigenen Crop-Funktion zugeschnitten und als JPG-Dateien gespeichert. Sie waren zum Teil etwas faltig bzw. verknüllt. Teilweise habe ich sie zusammengefaltet, um die Informationsdichte kritischer Daten auf der Gesamtbildfläche nicht übermäßig zu verringern.
Die Fotos der Kassenbelege wurden mit einem Google Smartphone unter Verwendung der Android-eigenen Crop-Funktion ("Dokument scannen") aufgenommen.
Prompt optimieren mit Google AI Studio
Dann ging es darum, zu prüfen, ob es mithilfe eines geeigneten Prompts möglich sein würde, aus den Bilddaten strukturierte CSV-Daten in der gewünschten Qualität zu gewinnen. Den Prompt habe ich mit Google AI Studio ausgekundschaftet.
Dort kann man sich einen Developer-Key anlegen, um die Modelle zu testen und seine Prompts zu optimieren, auch multimodale Prompts mit Dateiuploads sind möglich. Zunächst habe ich mehrere Modelle grob getestet, schließlich mit dem Google's Gemini 2.0 Flash Modell gute erste Ergebnisse erreicht und damit die Arbeit an dem Prompt fortgesetzt.
Strukturierte Ausgaben ohne JSON-Schema erzeugen
Gewünscht war eine Ausgabe der Daten aus den Fotos in CSV-Format. Verwendet wurde dafür ein multimodaler Prompt, die geforderte Struktur wurde natürlichsprachlich beschrieben.
Auf dem Eingabebild befindet sich ein eingescannter Kassenbon bzw. Quittung.
Ziel ist es, aus den abrechnungsrelevanten Positionen auf dem Bon eine CSV-Ausgabedatei zu erstellen.
Sowohl die Firmendaten des Händlers, als auch die Daten der Artikelpositionen sollen in jeder Zeile der CSV-Ausgabedatei enthalten sein.
Als Trennzeichen wird Semikolon verwendet.
Alle Werte werden mit doppelten Anführungsstrichen eingeschlossen.
Die CSV-Ausgabedatei enthält auch eine Kopfzeile mit den Feldnamen.
...
Der verwendete Prompt (Ausschnitt). Die Entwicklung des Prompts aus einem ersten Ansatz über mehrere Versionen bis hin zum funktionierenden Prompt wird in 'Prompt Engineering.md' im Verzeichnis 'docs' des Repositories gezeigt.
Die KI antwortet auf Prompts ohne einschränkende Spezifizierung in natürlichsprachlicher Weise, d.h. die formatierten CSV-Daten erscheinen als einer von manchmal mehreren Textabschnitten innerhalb einer Fließtextantwort, werden dabei aber durch Delimiter vom restlichen Fließtext abgegrenzt, was die prozedurale Auswertung der Antwort vereinfacht.
Gemini ermöglicht es auch, ein Antwortverhalten in spezifiziertem JSON zu erzwingen, worauf in diesem Versuch jedoch verzichtet wurde.
... da ein strukturiertes Antwortformat im Prompt nicht erzwungen wird,
kann die Response der AI auch ergänzenden Fließtext enthalten ...
```csv
"Geschäftsunternehmen";"Straße und Hausnummer";"PLZ und Stadt";"Datum";"Uhrzeit";"Artikelname";"Artikelmenge";"Preis";"Mehrwertsteuerklasse"
"Firma";"Beispielstraße 1";"50000 Ort";"01.03.2025";"10:13";"Produkt 1";;"14,99";"7.00"
"Firma";"Beispielstraße 1";"50000 Ort";"01.03.2025";"10:13";"Produkt 1";;"2";"19.00"
```
In meinen Versuchen hat die AI oberhalb der Delimiter manchmal weitere Informationen zur Antwort mitgeteilt. Damit ist also zu rechnen und Responses aus der API sollten deshalb zunächst normalisiert und typsicher gemacht werden. Als Alternative bietet es sich an, mit Schemata zu arbeiten - die Gemini-API bietet die Möglichkeit, strukturierte Antworten in einem vorher definierten JSON-Format zu erzwingen.
Optimierung des Prompts
Für eine Einordnung als korrektes Ergebnis hat es mir ausgereicht, wenn im CSV-Abschnitt der Antwort zu einem Kassenbon alle abrechnungsrelevanten Artikelpositionen und ihre Preise kombiniert mit den Stammdaten und dem Belegdatum ausgegeben wurden. Letzteres erwähne ich insbesondere deshalb, weil es eine gewisse Herausforderung dargestellt hat, einen Prompt so zu formulieren, dass man damit der KI vermittelt, die einmalig auf dem Kassenbon vorkommenden Stammdaten (Firma) und Belegdatum in jeder Ausgabezeile mit der jeweiligen Artikelposition und dem Preis zu kombinieren. Außerdem musste der Prompt daraufhin ausgerichtet werden, dass stets alle Spalten (auch leere) wiederzugeben sein würden.
Den Prompt habe ich schließlich solange optimiert, bis er die für die Testmenge der fünf Kassenbons korrekten Ergebnisse geliefert hat.
Automatisierung des produktiven Vorgangs
Danach habe ich ein PHP-Script geschrieben, das diesen Vorgang automatisiert für alle JPG-Dateien, die sich in einem ausgewählten Verzeichnis auf der lokalen Entwicklungsmaschine befinden (Programmschleife). Im ersten Ansatz habe ich dabei die Gemini API im Programm nativ mit curl angesprochen (Google API Studio bietet für Prompts Code-Snippets für verschiedene Umgebungen an, u.a. für curl). In der gezeigten Programmversion wurde ein Gemini API Client für PHP über composer installiert und im Code verwendet. Es gibt mehrere Gemini Clients, die von privaten Autoren für PHP geschrieben wurden, Google selbst stellt keine offizielle API bzw. SDK für PHP zur Verfügung. Die Verwendung eines API-Clients macht die Ansprache der Schnittstelle etwas bequemer und weniger fehleranfällig als mit curl.
Fazit
Das Script normalisiert die Antworten der KI, d.h. die CSV-Daten werden aus dem Antworttext extrahiert, zusätzliche Textausgaben, die fallweise zusätzlich in der KI-Antwort erfolgen, werden ignoriert. Als Ausgabe liefert das Script eine CSV-Datei, die gespeichert wird und die Artikelpositionen für alle Kassenbons enthält. In meinem Versuch mit 21 Kassenbons von 7 verschiedenen Händlern (Druckbildern/Layouts) und 118 Artikelpositionen erreicht die hier gezeigte Lösung ein korrektes Ergebnis für 100% der erwarteten Ausgaben.
Nachbemerkung
Cave: Das spezifische Setting ist vermutlich nicht unwesentlich für das hier dokumentierte Ergebnis. Dazu können neben dem Prompt verschiedene Parameter gehören:
- Cropping der Bilder (Pre-Processing)
- evtl. weitere technische Eigenschaften der Bilddateien wie Embeddings von Farbpaletten, Bildauflösung oder Dateiformat
- GenerationConfig - Konfiguration von Gemini-spezifischen Parametern, welche beim Prompt Engineering mit Google AI Studio angepasst und von dort übernommen werden können
Um den Vorgang mit dem gezeigten Code durchspielen zu können, habe ich dem Repository einige Belege beigefügt, die wie oben beschrieben fotografiert und vorbearbeitet wurden.
Mehr dazu:
- Repository zu diesem Versuch https://github.com/entwicklungen/receipt-analyzer
- Google AI Studio https://aistudio.google.com
- Gemini API Key erstellen https://aistudio.google.com/apikey
- Gemini Developer API Documentation https://ai.google.dev/gemini-api/docs?hl=de
- Alternative Gemini PHP API Clients https://github.com/search?q=gemini+php+client&type=repositories
- Tesseract bei Wikipedia https://de.wikipedia.org/wiki/Tesseract_(Software)
Autor: Frank Seidel, Webentwickler aus Köln. Webentwicklung und -konzeption unter Verwendung von PHP, MySQL, modernen Frameworks wie Symfony und Laravel und verwandten Technologien und Werkzeugen. Schwerpunkt im Bereich Unternehmensanwendungen und Online-Tools. https://www.webentwickler.de
php-programmierer.de ist ein IT-Jobboard, das auf PHP Programmierung fokussiert ist.
Ohne aufwändige Registrierung und in wenigen Augenblicken können aktuelle Stellenanzeigen oder Entwickler-Profile durchgesehen werden.
Ebenso können Stellenangebote oder ein eigenes Profil hinzugefügt werden.