paint-brush
我使用生成式人工智能来检测猫的品种:它是这样的经过@raymondcamden
803 讀數
803 讀數

我使用生成式人工智能来检测猫的品种:它是这样的

经过 Raymond Camden9m2023/12/19
Read on Terminal Reader

太長; 讀書

对于前端,我决定利用本机 Web 平台功能通过简单的 HTML 表单字段访问用户的相机。通过在输入标签上使用 capture="camera",您可以直接访问设备摄像头。 有更高级的方法可以做到这一点,但为了快速和简单,它工作得很好。更好的是,在桌面上,它只是充当文件选择器。
featured image - 我使用生成式人工智能来检测猫的品种:它是这样的
Raymond Camden HackerNoon profile picture

老实说,除了与猫一起工作之外,生成式人工智能还有什么其他用途呢?如果您阅读了我之前关于 Google Gemini AI 发布的文章,您可能已经看到我的测试提示,要求它识别图片中显示的猫的种类。


我决定将其转变为一个合适的 Web 应用程序,作为 API 实际应用的真实示例。这就是我的想法。

前端

对于前端,我决定利用本机 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; } } })) });


每当input字段触发onchange事件时,都会触发gotPic方法。我获取使用的文件/图像,将其读取为数据 URL (base64),然后将其分配给 DOM 中的图像并将其发送到服务器。又好又简单,对吧?


嗯,在桌面上一切正常,但是当我切换到我的相机,三星 S22 Ultra Magnus Extreme 200 相机镜头版(不是真名)时,我遇到了 Google API 抱怨我发送了太多数据的问题。然后我想起我的相机可以拍摄非常详细的照片,我需要在发送之前调整图像的大小。


我已经在 CSS 中调整了大小,但显然,这与真正的调整大小不同。我在 ImageKit 的网站上找到了这篇优秀的文章: How to resize images in Javascript?在本文中,他们描述了使用 HTML canvas元素来调整大小。


我可能有近十年没有使用过 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 导出的样板文件,但现在,我从发布给工作人员的信息中获取图像数据。我想特别提示一下:


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


最初,我努力尝试获取 JSON 格式的结果,如果图片不是猫,则让它返回一个空白字符串。但后来我注意到了一些很酷的事情——双子座在处理非猫的图片方面做得非常好。就像...令人震惊的好。


事实上,我真的很感激该应用程序不会只说“不是猫”,而是解释它是什么。显然,对于这样的应用程序来说,两种风格都有空间,但我保留了谷歌的详细而有用的回复。


现在,有趣的部分......结果。

结果

让我们从几张猫的照片开始。


首先是小猪,我最喜欢的猫,它不胖,而且看起来一点也不像小屋贾巴:

正确识别的三色猫图片


接下来是露娜的照片。在这种情况下,品种是不正确的——但至少很接近。


一张被认为是缅因猫的照片


现在,让我们向 Google 抛出一个曲线球:


正确识别喷壶的图片。


这真的让我惊讶。这个描述是100%准确的,老实说,如果我看到这张照片并且不知道的话,我会认出它是一只猫的雕塑,而不是一个喷壶。我的意思是,我想这有点明显,但老实说我认为我自己不会注意到这一点。


现在,让我们彻底疯狂:


正确识别的圣诞树图片。

是的,没错,就是谷歌。人工智能生成的猫图像怎么样?


一张猫当 DJ 的照片。


我认为它处理得很好。


下一个...

大脚人偶的图片


是的,这就是大脚怪。试想一下,所有那些大脚怪“研究人员”都可以退休,只需将他们的追踪摄像机连接到人工智能即可!


骷髅可动人偶的图片

我不得不说 - 令我印象深刻的是,谷歌不仅认可了这个系列,还认可了实际角色,但公平地说,骷髅有着非常独特的外观。


最后,由于我(当然不公平)将我的猫与贾巴进行了比较,让我们看看它是如何处理的:


赫特人贾巴


哦,我知道我说我已经完成了,但是当然,我必须测试一只狗:


狗在沙发上


干得好,双子座。不幸的是,我不会实时托管此演示(代码中前面的 Cloudflare“实时”URL 不起作用),但我可以共享代码。