paint-brush
Ich habe generative KI verwendet, um Katzenrassen zu erkennen: So lief esvon@raymondcamden
657 Lesungen
657 Lesungen

Ich habe generative KI verwendet, um Katzenrassen zu erkennen: So lief es

von Raymond Camden9m2023/12/19
Read on Terminal Reader

Zu lang; Lesen

Für das Frontend habe ich mich entschieden, eine native Webplattformfunktion zu nutzen, um über ein einfaches HTML-Formularfeld auf die Kamera des Benutzers zuzugreifen. Durch die Verwendung von „capture="camera"“ auf einem Eingabe-Tag erhalten Sie direkten Zugriff auf die Gerätekamera. Es gibt fortgeschrittenere Möglichkeiten, dies zu tun, aber schnell und einfach funktioniert es gut. Noch besser: Auf einem Desktop fungiert es einfach als Dateiauswahl.
featured image - Ich habe generative KI verwendet, um Katzenrassen zu erkennen: So lief es
Raymond Camden HackerNoon profile picture

Mal ehrlich: Welchen anderen Nutzen hat generative KI als die Arbeit mit Katzen? Wenn Sie meinen vorherigen Beitrag zum Start von Googles Gemini AI gelesen haben, haben Sie vielleicht meine Testaufforderungen gesehen, in denen ich aufgefordert wurde, die auf einem Bild gezeigte Katzenart zu identifizieren.


Ich habe beschlossen, daraus eine richtige Webanwendung als echtes Beispiel für die API in Aktion zu machen. Hier ist, was ich mir ausgedacht habe.

Das Frontend

Für das Frontend habe ich mich entschieden, eine native Webplattformfunktion zu nutzen, um über ein einfaches HTML-Formularfeld auf die Kamera des Benutzers zuzugreifen. Durch die Verwendung von capture="camera" auf einem input -Tag erhalten Sie direkten Zugriff auf die Gerätekamera.


Es gibt fortgeschrittenere Möglichkeiten, dies zu tun, aber schnell und einfach funktioniert es gut. Noch besser: Auf einem Desktop fungiert es einfach als Dateiauswahl.


Mein Gedanke war, eine Möglichkeit bereitzustellen, ein Bild zu erhalten (entweder über die Kamera oder die Dateiauswahl), das Bild anzuzeigen und es an das Back-End zu senden. Während unglaublich einfaches und Vanilla-JS in Ordnung gewesen wäre, habe ich Alpine.js für die Interaktivität verwendet.


Zunächst muss der HTML-Code die Benutzeroberfläche für das Bild, einen Ort zum Anzeigen des Bildes und einen anderen Ort zum Anzeigen des Ergebnisses bereitstellen.


 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="app.css"> <title></title> </head> <body> <h2>🐈 Detector</h2> <div x-data="catDetector"> <input type="file" capture="camera" accept="image/*" @change="gotPic" :disabled="working"> <template x-if="imageSrc"> <p> <img :src="imageSrc"> </p> </template> <div x-html="status"></div> </div> <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script> <script src="app.js"></script> </body> </html>


Wenden wir uns nun JavaScript zu. Ich werde damit beginnen, die ursprüngliche Version vorzustellen, da dies einfacher ist, als zu erklären, warum sie fehlgeschlagen ist. Alles, was ich zunächst tun musste, war zu bemerken, wann eine Datei ausgewählt oder ein Bild aufgenommen wurde, und es im DOM zu rendern.


(Ich habe ein bisschen CSS verwendet, das hier nicht freigegeben ist, um die sichtbare Größe unter Kontrolle zu halten. Mehr dazu gleich.) Außerdem musste ich eine Base64-Version der Datei an den serverseitigen Code senden. Hier ist die erste Version:


 //const IMG_FUNC = 'http://localhost:8787/'; const IMG_FUNC = 'https://catdetector.raymondcamden.workers.dev'; document.addEventListener('alpine:init', () => { Alpine.data('catDetector', () => ({ imageSrc:null, working:false, status:'', async init() { console.log('init'); }, async gotPic(e) { let file = e.target.files[0]; if(!file) return; let reader = new FileReader(); reader.readAsDataURL(file); reader.onload = async e => { this.imageSrc = e.target.result; this.working = true; this.status = '<i>Sending image data to Google Gemini...</i>'; let body = { imgdata:this.imageSrc } let resp = await fetch(IMG_FUNC, { method:'POST', body: JSON.stringify(body) }); let result = await resp.json(); this.working = false; this.status = result.text; } } })) });


Die gotPic Methode wird immer dann ausgelöst, wenn das input ein onchange Ereignis auslöst. Ich nehme die verwendete Datei/das verwendete Bild, lese sie als Daten-URL (base64), ordne sie dann dem Bild im DOM zu und sende sie an den Server. Schön und einfach, oder?


Nun, auf dem Desktop hat alles gut funktioniert, aber als ich zu meiner Kamera, der Samsung S22 Ultra Magnus Extreme 200 Camera Lens Edition (nicht der richtige Name), wechselte, stieß ich auf Probleme, bei denen sich die Google-API darüber beschwerte, dass ich zu viele Daten sendete. Dann fiel mir ein, dass meine Kamera wirklich detaillierte Bilder macht und ich die Größe des Bildes ändern musste, bevor ich es weitersenden konnte.


Ich habe die Größe bereits in CSS geändert, aber das ist natürlich nicht dasselbe wie eine tatsächliche Größenänderung. Ich habe diesen hervorragenden Artikel auf der Website von ImageKit gefunden: Wie ändere ich die Größe von Bildern in Javascript? In diesem Artikel wird die Verwendung eines HTML- canvas Elements zur Größenänderung beschrieben.


Ich hatte Canvas wahrscheinlich seit fast einem Jahrzehnt nicht mehr verwendet, aber ich konnte ihren Code gut genug in mein Frontend umwandeln:


 //const IMG_FUNC = 'http://localhost:8787/'; const IMG_FUNC = 'https://catdetector.raymondcamden.workers.dev'; // Resize logic: https://imagekit.io/blog/how-to-resize-image-in-javascript/ const MAX_WIDTH = 400; const MAX_HEIGHT = 400; document.addEventListener('alpine:init', () => { Alpine.data('catDetector', () => ({ imageSrc:null, working:false, status:'', async init() { console.log('init'); }, async gotPic(e) { let file = e.target.files[0]; if(!file) return; let reader = new FileReader(); reader.readAsDataURL(file); reader.onload = async e => { let img = document.createElement('img'); img.onload = async e => { let width = img.width; let height = img.height; if(width > height) { if(width > MAX_WIDTH) { height = height * (MAX_WIDTH / width); width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width = width * (MAX_HEIGHT / height); height = MAX_HEIGHT; } } let canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; let ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); this.imageSrc = canvas.toDataURL(file.type); this.working = true; this.status = '<i>Sending image data to Google Gemini...</i>'; let body = { imgdata:this.imageSrc } let resp = await fetch(IMG_FUNC, { method:'POST', body: JSON.stringify(body) }); let result = await resp.json(); this.working = false; this.status = result.text; }; img.src = e.target.result; } } })) });


Sie werden feststellen, dass der Code sowohl für die Breite als auch für die Höhe eine maximale Abmessung verwendet und die Größenänderung unter Beibehaltung des gleichen Seitenverhältnisses korrekt handhabt. Auch hier kann ich mir das nicht zu eigen machen, vielen Dank an Manu Chaudhary für seinen Blogbeitrag.


Das Endergebnis dieser Änderung ist, dass ich jetzt ein viel kleineres Bild an den Back-End-Dienst sende, und es ist endlich Zeit, einen Blick darauf zu werfen.

Das Back-End

Für mein Backend habe ich mich erneut für den Einsatz von Cloudflare Workers entschieden. Ich war etwas zögerlich, da es zuvor einige Probleme mit NPM-Paketen und meinen Demos gab, aber dieses Mal gab es keine Probleme. Wenn Sie sich an meinen letzten Beitrag erinnern, können Sie mit dem AI Studio von Google ganz einfach Beispielcode aus Ihren Eingabeaufforderungen ausgeben. Ich musste diesen also nur in den Cloudflare Worker integrieren.


Hier ist der gesamte Code:


 const { GoogleGenerativeAI, HarmCategory, HarmBlockThreshold, } = require("@google/generative-ai"); const MODEL_NAME = "gemini-pro-vision"; export default { async fetch(request, env, ctx) { const API_KEY = env.GEMINI_KEY; console.log('begin serverless logic'); const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Access-Control-Max-Age": "86400", }; let { imgdata } = await request.json(); imgdata = imgdata.replace(/data:.*?;base64,/, ''); const genAI = new GoogleGenerativeAI(API_KEY); const model = genAI.getGenerativeModel({ model: MODEL_NAME }); const generationConfig = { temperature: 0.4, topK: 32, topP: 1, maxOutputTokens: 4096, }; const safetySettings = [ { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, }, { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, }, { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, }, { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, }, ]; const parts = [ {text: "Look at this picture and if you see a cat, return the breed of the cat."}, { inlineData: { mimeType: "image/jpeg", data: imgdata } } ]; console.log('calling google'); const result = await model.generateContent({ contents: [{ role: "user", parts }], generationConfig, safetySettings, }); const response = result.response; let finalResult = { text: response.text() }; return new Response(JSON.stringify(finalResult), { headers: {...corsHeaders}}); }, };


Der Großteil des Codes stammt aus dem AI Studio-Export, außer dass ich jetzt meine Bilddaten aus den an den Worker gesendeten Informationen erhalte. Besonders hervorheben möchte ich die Eingabeaufforderung:


 Look at this picture and if you see a cat, return the breed of the cat.


Anfangs habe ich versucht, ein Ergebnis in JSON zu erhalten und eine leere Zeichenfolge zurückzugeben, wenn das Bild keine Katze war. Aber dann ist mir etwas Cooles aufgefallen: Gemini hat es wirklich gut gemacht, mit Bildern umzugehen, die nicht von Katzen waren. Wie ... erschreckend gut.


Ich habe es wirklich sehr geschätzt, dass die App nicht nur „Keine Katze“ sagte, sondern stattdessen erklärte, was es war. Offensichtlich gibt es für eine Anwendung wie diese Platz für beide Stile, aber ich habe die ausführlichen und hilfreichen Antworten von Google beibehalten.


Nun zum spaßigen Teil: den Ergebnissen.

Die Ergebnisse

Beginnen wir mit ein paar Bildern von Katzen.


An erster Stelle steht Pig, meine Lieblingskatze, die nicht dick ist und überhaupt nicht wie Jabba the Hut aussieht:

Ein Bild einer richtig erkannten Kattunkatze


Als nächstes ein Bild von Luna. In diesem Fall ist die Rasse falsch – aber zumindest nah dran.


Ein Bild einer Maine Coon


Jetzt werfen wir Google einen Curveball zu:


Ein Bild einer Gießkanne richtig identifiziert.


Das hat mich wirklich überrascht. Die Beschreibung ist zu 100 % korrekt, und ehrlich gesagt, wenn ich dieses Bild gesehen hätte und es nicht gewusst hätte, hätte ich darin eine Skulptur einer Katze und nicht eine Gießkanne erkannt. Ich meine, ich schätze, es ist irgendwie offensichtlich, aber ich glaube ehrlich gesagt nicht, dass mir das selbst aufgefallen wäre.


Jetzt lasst uns völlig verrückt werden:


Ein Bild eines korrekt identifizierten Weihnachtsbaums.

Ja, das ist richtig, Google. Wie wäre es mit KI-generierten Katzenbildern?


Ein Bild einer Katze als DJ.


Ich denke, es hat das ziemlich gut gemeistert.


Nächste...

Ein Bild einer Bigfoot-Actionfigur


Ja, das ist Bigfoot in Ordnung. Stellen Sie sich vor, all diese Bigfoot-„Forscher“ könnten sich zurückziehen und ihre Wildkameras einfach an die KI anschließen!


Ein Bild einer Skeletor-Actionfigur

Ich muss sagen: Ich bin beeindruckt, dass Google nicht nur das Franchise, sondern auch den eigentlichen Charakter erkannt hat, aber um fair zu sein, Skeletor sieht für ihn ziemlich anders aus.


Und schließlich, da ich meine Katze (natürlich zu Unrecht) mit Jabba verglichen habe, wollen wir mal sehen, wie sie damit umgeht:


Jabba der Hutt


Oh, ich weiß, ich sagte, ich sei fertig, aber natürlich musste ich einen Hund testen:


Hund auf einer Couch


Verdammt gute Arbeit, Zwillinge. Leider werde ich diese Demo nicht live hosten (die „Live“-URL von Cloudflare weiter oben im Code funktioniert nicht), aber ich kann den Code teilen.