paint-brush
Я использовал генеративный искусственный интеллект для определения пород кошек: вот как все прошлок@raymondcamden
803 чтения
803 чтения

Я использовал генеративный искусственный интеллект для определения пород кошек: вот как все прошло

к Raymond Camden9m2023/12/19
Read on Terminal Reader

Слишком долго; Читать

Что касается внешнего интерфейса, я решил использовать встроенную функцию веб-платформы для доступа к камере пользователя через простое поле HTML-формы. Используя capture="camera" во входном теге, вы получаете прямой доступ к камере устройства. Есть более продвинутые способы сделать это, но если быстро и просто, то они работают нормально. Еще лучше, на рабочем столе он просто действует как выбор файлов.
featured image - Я использовал генеративный искусственный интеллект для определения пород кошек: вот как все прошло
Raymond Camden HackerNoon profile picture

Давайте будем честными: какая еще польза от генеративного ИИ, кроме работы с кошками? Если вы читали мой предыдущий пост о запуске Google Gemini AI, вы, возможно, видели мои тестовые подсказки с просьбой определить тип кошки, изображенной на картинке.


Я решил превратить это в полноценное веб-приложение в качестве реального примера API в действии. Вот что я придумал.

Передняя часть

Что касается внешнего интерфейса, я решил использовать встроенную функцию веб-платформы для доступа к камере пользователя через простое поле HTML-формы. Используя capture="camera" во input теге, вы получаете прямой доступ к камере устройства.


Есть более продвинутые способы сделать это, но если быстро и просто, то они работают нормально. Еще лучше, на рабочем столе он просто действует как выбор файлов.


Моя мысль заключалась в том, чтобы предоставить способ получить изображение (с помощью камеры или выбора файла), отобразить его и отправить на сервер. Хотя невероятно простой и ванильный JS был бы вполне подходящим, я пошел дальше и использовал Alpine.js для интерактивности.


Во-первых, HTML должен предоставить пользовательский интерфейс для изображения, место для отображения изображения и другое место для отображения результата.


 <!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 (имя изменено), я столкнулся с проблемами, когда API Google жаловался, что я отправляю слишком много данных. Затем я вспомнил, что моя камера делает очень подробные снимки, и мне нужно было изменить размер изображения, прежде чем отправлять его.


Я уже изменял размер в CSS, но, очевидно, это не то же самое, что реальное изменение размера. Я нашел эту отличную статью на сайте ImageKit: Как изменить размер изображений в Javascript? В этой статье они описывают использование элемента canvas HTML для изменения размера.


Я не использовал 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; } } })) });


Вы заметите, что код использует максимальный размер как для ширины, так и для высоты и правильно обрабатывает изменение размера, сохраняя при этом то же соотношение сторон. Опять же, я не могу приписать себе ничего из этого, спасибо Ману Чаудхари за его сообщение в блоге.


Конечным результатом этого изменения является то, что теперь я отправляю во внутреннюю службу изображение гораздо меньшего размера, и наконец пришло время взглянуть на это.

Задняя часть

В качестве серверной части я решил снова использовать 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% точное, и, честно говоря, если бы я увидел эту картину и не знал, я бы узнал в ней скульптуру кота, а не лейку. Я имею в виду, я думаю, это довольно очевидно, но я, честно говоря, не думаю, что сам бы это заметил.


Теперь давайте сойдем с ума:


Правильно идентифицировано изображение рождественской елки.

Да, это правда, Гугл. А как насчет изображений кошек, созданных ИИ?


Изображение кота в роли диджея.


Я думаю, что он с этим справился довольно хорошо.


Следующий...

Изображение фигурки снежного человека


Да, это снежный человек, хорошо. Подумайте только, все эти «исследователи» снежного человека могли бы уйти на пенсию и просто подключить свои камеры к ИИ!


Изображение фигурки Скелетора.

Я должен сказать - я впечатлен, что Google не только узнал франшизу, но и самого персонажа, но, честно говоря, Скелетор имеет довольно отчетливый вид.


И, наконец, раз уж я (несправедливо, конечно) сравнил своего кота с Джаббой, давайте посмотрим, как с этим справятся:


Джабба Хатт


О, я знаю, что сказал, что закончил, но, конечно, мне нужно было протестировать собаку:


Собака на диване


Черт возьми, хорошая работа, Близнецы. К сожалению, я не буду размещать эту демонстрацию в реальном времени («живой» URL-адрес Cloudflare, указанный ранее в коде, не будет работать), но я могу поделиться кодом.