paint-brush
生成 AI を使用して猫の品種を検出してみた: その様子は次のとおりです@raymondcamden
804 測定値
804 測定値

生成 AI を使用して猫の品種を検出してみた: その様子は次のとおりです

Raymond Camden9m2023/12/19
Read on Terminal Reader

長すぎる; 読むには

フロントエンドでは、ネイティブ Web プラットフォーム機能を利用して、単純な HTML フォーム フィールドを介してユーザーのカメラにアクセスすることにしました。 input タグで Capture="camera" を使用すると、デバイスのカメラに直接アクセスできます。 これを行うにはさらに高度な方法がありますが、手早く簡単に行うには、これで問題なく機能します。さらに良いことに、デスクトップでは単にファイル セレクターとして機能します。
featured image - 生成 AI を使用して猫の品種を検出してみた: その様子は次のとおりです
Raymond Camden HackerNoon profile picture

正直に言うと、猫を扱うこと以外に生成 AI の用途はあるでしょうか? Google の Gemini AI の発表に関する私の前回の投稿を読んだ方は、写真に写っている猫の種類を識別するように求めるテスト プロンプトを見たことがあるかもしれません。


API の実際の動作例として、これを適切な Web アプリケーションに変換することにしました。これが私が思いついたものです。

フロントエンド

フロントエンドでは、ネイティブ Web プラットフォーム機能を利用して、単純な HTML フォーム フィールドを介してユーザーのカメラにアクセスすることにしました。 inputタグでcapture="camera"を使用すると、デバイスのカメラに直接アクセスできます。


これを行うにはさらに高度な方法がありますが、手早く簡単に行うには、これで問題なく機能します。さらに良いことに、デスクトップでは単にファイル セレクターとして機能します。


私の考えは、(カメラまたはファイル選択のいずれかを介して) 画像を取得し、画像を表示し、それをバックエンドに送信する方法を提供することでした。信じられないほどシンプルでバニラの JS でも問題ありませんでしたが、私は対話機能のためにAlpine.jsを使用しました。


まず、HTML は画像の UI、画像を表示する場所、結果を表示する別の場所を提供する必要があります。


 <!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>


さて、JavaScript の話に移りましょう。どのように失敗したかを説明するよりも簡単なので、最初のバージョンを共有することから始めます。最初に必要なのは、ファイルが選択されたとき、または写真が撮影されたときにそれを通知し、それを DOM にレンダリングすることだけでした。


(表示サイズを抑えるために、ここでは共有されていない CSS を少し使用しました。これについては後ほど詳しく説明します。) また、base64 バージョンのファイルをサーバー側のコードに送信する必要がありました。初期バージョンは次のとおりです。


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


gotPicメソッドは、 inputフィールドがonchangeイベントを起動するたびに起動されます。使用されるファイル/画像を取得し、データ URL (base64) として読み取り、DOM 内の画像に割り当ててサーバーに送信します。シンプルで素敵ですよね?


デスクトップではすべて問題なく動作していましたが、カメラを Samsung S22 Ultra Magnus Extreme 200 Camera Lens Edition (正式名ではありません) に切り替えると、送信するデータが多すぎると Google の API が警告するという問題に遭遇しました。そのとき、私のカメラは非常に詳細な写真を撮るので、送信する前に画像のサイズを変更する必要があることを思い出しました。


すでに CSS でサイズ変更していましたが、明らかに、それは実際にサイズを変更するのと同じではありません。 ImageKit のサイトで次の優れた記事を見つけました: Javascript で画像のサイズを変更するには?この記事では、HTML canvas要素を使用してサイズ変更を行う方法について説明します。


私はおそらく 10 年近く Canvas を使用していませんでしたが、そのコードをフロントエンドに十分に再利用することができました。


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


コードが幅と高さの両方に最大寸法を使用し、同じアスペクト比を維持しながらサイズ変更を正しく処理していることがわかります。繰り返しになりますが、 Manu Chaudharyのブログ投稿に感謝します。


この変更の最終的な結果として、はるかに小さいイメージをバックエンド サービスに送信するようになり、いよいよそれを確認する時が来ました。

バックエンド

バックエンドには、 Cloudflare Workers を再度使用することにしました。以前に NPM パッケージと私のデモでいくつかの問題が発生したため、少し躊躇していましたが、今回は問題はありませんでした。前回の投稿を覚えていると思いますが、Google の AI Studio を使用すると、プロンプトからサンプル コードを簡単に出力できるため、私がしなければならなかったのは、それを Cloudflare Worker に組み込むことだけでした。


コード全体は次のとおりです。


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


コードの大部分は AI Studio エクスポートからのボイラープレートです。ただし、ワーカーに POST された情報から画像データを取得する点は異なります。特に注意したいのは、次のプロンプトです。


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


最初は、JSON で結果を取得し、画像が猫でない場合は空の文字列を返すように努めました。しかし、その後、素晴らしいことに気づきました。ジェミニは猫以外の写真を非常にうまく処理しました。みたいな…衝撃的に良い。


実際、アプリが単に「猫ではありません」と言うだけでなく、それが何であるかを説明してくれるのは本当にありがたかったです。明らかに、このようなアプリケーションには両方のスタイルを採用する余地がありますが、私は Google の詳細で有益な回答をそのままにしました。


さて、お楽しみは結果です。

結果

まずは猫の写真から始めましょう。


まずは、太っていない、ジャバ・ザ・ハットに全く似ていない私のお気に入りの猫、ブタです。

三毛猫の写真が正しく認識されました


続いてルナちゃんの写真です。この場合、品種は間違っていますが、少なくともそれに近いものです。


メインクーンと思われる写真


さて、Google に変化球を投げてみましょう。


じょうろの写真は正しく識別されました。


これには本当に驚きました。この説明は 100% 正確で、正直に言うと、もし私がこの絵を知らずに見ていたら、それをじょうろではなく猫の彫刻だと認識したでしょう。つまり、それはある意味当然のことだと思いますが、正直なところ、自分では気づかなかったと思います。


さあ、完全に夢中になってみましょう:


正しく識別された XMas ツリーの写真。

はい、そうです、Googleです。 AIが生成した猫の画像はどうでしょうか?


DJになった猫の写真。


それをうまく処理できたと思います。


次...

ビッグフットのアクションフィギュアの写真


はい、ビッグフットですよ。考えてみてください、ビッグフットの「研究者」は全員引退して、トレイルカメラを AI に接続するだけで済むかもしれません。


スケルターのアクションフィギュアの写真

言わなければならないのは、Google がシリーズだけでなく実際のキャラクターも認識していたことに感心しているということですが、公平を期すために言うと、スケルターはかなり独特の見た目をしています。


そして最後に、私は(もちろん不公平ですが)私の猫をジャバと比較したので、それがどのように扱われるかを見てみましょう:


ジャバ・ザ・ハット


ああ、もう終わったと言いましたが、もちろん犬をテストしなければなりませんでした。


ソファの上の犬


頑張れ、ジェミニ。残念ながら、私はこのデモをライブでホストするつもりはありません(コード前半の Cloudflare の「ライブ」URL は機能しません)が、コードを共有することはできます。