Книга: Анализ данных с LLM. Текст, таблицы, изображения и аудио
Назад: 5. Анализ структурированных данных
Дальше: 7. Анализ аудиоданных

6. Анализ изображений и видеороликов

В этой главе

• Анализ изображений.

• Сравнение изображений.

• Анализ видеороликов.

В предыдущих главах вы узнали, как анализировать текст и структурированные данные. Все ли нюансы были учтены? Отнюдь. Подавляющая часть существующих данных представлена в виде изображений и видеороликов. Например, видео занимает две трети от общего объема данных, передаваемых через Интернет. В этой главе мы выясним, каким образом языковые модели могут помочь извлекать полезную информацию и из таких типов данных.

В следующих разделах представлено несколько небольших проектов по обработке изображений и видеоданных. Модель GPT-4o — изначально мультимодальная. Ее можно использовать для всех перечисленных задач.

• Сначала мы выясним, как с помощью GPT-4o получать ответы на произвольные вопросы (на естественном языке) об изображениях.

• Затем используем эту модель для создания приложения, которое будет автоматически отмечать изображенных на фотографиях людей, добавляя теги.

• Наконец, мы используем GPT-4o для автоматической генерации заголовков видеофайлов.

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

6.1. Настройка

Для запуска примеров кода потребуется установить еще один пакет Python, а именно библиотеку OpenCV для обработки изображений. Чтобы ее установить, выполните в терминале следующую команду:

pip install opencv-python==4.8.1.78

Мы будем использовать эту библиотеку, например, для чтения изображений с диска и разделения видео на кадры.

Затем нужно будет установить библиотеку, которая позволит отправлять запросы напрямую в веб-сервисы OpenAI (с ее помощью мы будем отправлять изображения, сохраненные на локальном компьютере, в OpenAI):

pip install requests==2.31.0

Отлично! Если при выполнении этих команд не появились сообщения об ошибках, значит, ваша система теперь настроена для анализа данных изображений и видеороликов с помощью языковой модели GPT-4o. Переходим к первому проекту.

6.2. Получение ответов на вопросы об изображениях

Нейронные сети, способные обнаруживать на изображениях объекты (скажем, автомобили), существуют уже много лет. Так в чем же особенность обработки изображений с помощью языковой модели GPT-4o?

Основное ограничение классических моделей для обработки изображений заключается в том, что их нужно обучать для выполнения конкретных задач анализа. Например, у вас есть нейронная сеть, которая очень хорошо выявляет изображения кошек. Ее можно использовать для фильтрации фотографий кошек из вашей личной коллекции. Однако, возможно, вам интересны не кошки в целом, а конкретно персидские кошки золотистого окраса. Если ваша модель не обучена обнаруживать именно эту породу кошек, то вам не повезло и придется самим тегировать достаточное количество примеров изображений. Это утомительно, и в итоге вы, скорее всего, перестанете пользоваться моделью и будете просматривать фотографии вручную. Особенность обработки изображений с помощью GPT-4o (и подобных моделей) заключается в том, что модель решает широкий спектр задач, касающихся изображений, используя лишь описание задачи (на естественном языке).

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

6.2.1. Определение мультимодального ввода

В этом разделе мы создадим систему, которая принимает два вида входных данных:

• URL изображения в Интернете;

• вопрос об изображении на естественном языке.

Выходные данные — это ответ на вопрос (в текстовом виде). Внутри система использует языковую модель GPT-4o для обработки вопроса применительно к поданному во входных данных изображению. Она генерирует мультимодальные промпты, комбинируя несколько типов данных (в данном случае — текст и изображения). На рис. 6.1 показан пример промпта: он содержит одно изображение (яблока) и вопрос об изображении (изображен ли здесь банан). В данном случае правильный ответ — «Нет».

Рис. 6.1. Мультимодальный промпт, содержащий изображение и текст. В промпт входит инструкция, указывающая языковой модели, что она должна определить, изображен ли на картинке банан. В данном случае ожидаемый результат — «Нет» (а если бы на картинке был банан, то «Да»)

Как составлять подобные промпты для модели GPT-4o? Можно повторно использовать эндпоинт chat.competions. Напомню, что он принимает в качестве входных данных список предыдущих сообщений, которыми обменялись пользователь и (потенциально) система. Для работы системы, отвечающей на вопросы об изображениях, требуется лишь одно сообщение — и оно исходит от пользователя.

В отличие от кода, с которым мы работали ранее, сообщения теперь могут содержать мультимодальный контент. В этом конкретном случае контент состоит из одного текстового фрагмента (вопроса, заданного пользователем) и одного изображения (на данный момент указанного как URL). Мы будем использовать это сообщение далее в коде (где question — переменная, содержащая текст вопроса, а image_url — URL изображения):

Прежде всего обратите внимание на атрибут role, идентифицирующий сообщение как сгенерированное пользователем. Далее содержимое сообщения указано как список словарей Python. Каждый из этих словарей описывает один элемент сообщения. Мы теперь рассматриваем мультимодальные данные (то есть изображения и текст), поэтому необходимо уточнить тип (или модальность) каждого входного элемента. Это достигается путем присвоения атрибуту type значения text или image_url. Фактическое содержимое указывается с помощью атрибута text или (в случае изображения) атрибута image_url . Языковая модель GPT-4o способна понимать, что вопрос относится к изображению, и может корректно обработать оба элемента.

СОВЕТ Здесь входные данные содержат одно изображение, однако оно может состоять из множественных элементов одного типа: например, нескольких изображений. Мы воспользуемся этой возможностью в проекте, с которым будем работать в следующем разделе.

6.2.2. Обсуждение кода

В листинге 6.1 показан полный код системы для получения ответов на вопросы об изображениях. После того как на вход поданы URL изображения и вопрос , все самое интересное (то есть получение ответов на вопросы об изображении) происходит в функции analyze_image .

Листинг 6.1. Получение ответов на вопросы об изображениях с помощью языковых моделей

import argparse

import openai

import time

 

client = openai.OpenAI()

 

def analyze_image(image_url, question):  

    """Использовать языковую модель для ответа на вопросы об изображении.

 

    Аргументы:

        image_url: URL, ведущий к изображению.

        question: вопрос от изображении.

 

    Выводит:

        Ответ языковой модели.

    """

    for nr_retries in range(1, 4):

        try:

            response = client.chat.completions.create(

                model='gpt-4o',

                messages=[               

                    {'role':'user', 'content':[

                        {'type':'text', 'text':question},

                        {'type':'image_url', 'image_url':{

                            'url':image_url

                            }

                        }]

                    }]

                )

            return response.choices[0].message.content

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

 

if __name__ == '__main__':

 

    parser = argparse.ArgumentParser()   

    parser.add_argument('imageurl', type=str, help='URL изображения')

    parser.add_argument('question', type=str, help='Вопрос об изображении')

    args = parser.parse_args()

 

    answer = analyze_image(args.imageurl, args.question)

    print(answer)

Как видите, для получения ответов на вопросы об изображениях требуется лишь несколько строк кода на Python! Функция analyze_image содержит всего один вызов языковой модели GPT-4o с использованием сообщения, описанного в предыдущем подразделе . Тот факт, что теперь мы предоставляем мультимодальные входные данные, не меняет формат ответа. Снова мы получаем объект, который содержит сообщение, сгенерированное языковой моделью. Хотя входные данные теперь могут быть мультимодальными, вывод представляет собой текст. Модель получила инструкцию сгенерировать ответ на заданный вопрос (то есть именно то, что нужно пользователю), поэтому выходные данные выводятся напрямую пользователю.

6.2.3. Проверка в работе

Пора протестировать нашу систему! Представьте, что вы снова в компании Banana, где вам предстоит разработать ее новый логотип. Вы хотите создать логотип на основе изображения банана. Поискав в Интернете, вы находите большие репозитории с изображениями фруктов. Но какие из них — бананы? Просматривать изображения вручную не хочется, лучше делегировать эту задачу языковой модели. К счастью, можно просто взять код из предыдущего подраздела, указав URL всех картинок с фруктами с вопросом: «Это банан (“Да”, “Нет”)?» По сути, вы будете использовать систему для получения ответов на вопросы об изображениях как метод классификации (это лишь один из множества возможных сценариев ее применения). Затем можно написать простой скрипт, который будет перебирать все соответствующие URL и сохранять те, на которые система ответит «Да».

На сайте книги доступны ссылки на файл с кодом из листинга 6.1 (Listing 1), а также на изображения фруктов (Fruit 1). Скачайте код, перейдите в терминале в каталог с ним и выполните следующую команду, требующую от системы ответить на вопрос, изображен ли на картинке банан:

py python [URL] 'Это банан ("Да", "Нет")?'

В этой команде замените [URL] URL изображения (подходящий URL можно получить, например скопировав адрес ссылки Fruit 1, доступной на сайте книги).

Классифицировать объекты относительно просто, особенно такие часто встречающиеся, как бананы. Поэтому для большинства примеров должны отобразиться точные результаты. Протестируйте систему, используя несколько разных фруктов, а может, и другие изображения на свой выбор. Диапазон вопросов, которые можно задавать, практически неисчерпаем (даже если не учитывать довольно щедрый лимит на длину ввода 128 000 токенов, что эквивалентно примерно 300 страницам текста).

Обращаю ваше внимание на один важный нюанс. Обработка изображений с помощью модели GPT-4o может быть дорогостоящей! Точная стоимость зависит от размера изображения и используемой степени детализации. Управлять детализацией можно с помощью параметра detail. Например, выберите низкую степень, используя следующую спецификацию URL изображения (во входных данных модели):

{'type':'image_url', 'image_url':{'url':image_url, 'detail':'low'}}

Установите значение атрибута detaillow, тогда стоимость будет эквивалентна 85 токенам за изображение (то есть стоимости обработки текста в 85 токенов с помощью языковой модели GPT-4o). Если задать значение степени детализации high (установлено по умолчанию), то стоимость будет состоять из фиксированной суммы за 85 токенов и переменной суммы, которая зависит от размера изображения. Чтобы рассчитать переменную составляющую стоимости, сначала масштабируем изображение до размера (в пикселах) 2048 × 2048 (сохраняя соотношение сторон). Этот этап масштабирования применяется только к изображениям размером более 2048 × 2048 пикселов. Второй этап масштабирования выполняется в любом случае. Он масштабирует меньшую сторону изображения до размера 768 пикселов. Теперь определим, какое минимальное количество квадратов 512 × 512 пикселов потребуется, чтобы покрыть изображение после второго этапа масштабирования. Переменные затраты рассчитываются пропорционально количеству квадратов: каждый квадрат умножается на 170 токенов (такова стоимость обработки одного квадрата, установленная OpenAI).

Например, мы хотим обработать изображение размером 1024 × 1024 пикселов, установив высокую степень детализации. В этом случае первый этап масштабирования можно пропустить, так как изображение помещается в квад­рат 2048 × 2048 пикселов. Однако второй этап масштабирования выполняется в любом случае. Он масштабирует изображение до размера 768 × 768 пикселов. Чтобы покрыть квадрат со стороной 768 пикселов, нам нужны четыре квадрата размером 512 × 512 пикселов. Это означает, что за обработку изображения мы заплатим:

• 85 токенов (фиксированная составляющая);

• 4 × 170 токенов = 680 токенов (переменная составляющая).

Итого мы заплатим 85 + 4 × 170 = 765 токенов. По текущим ценам это соответствует 0,003825 доллара (то есть менее одного цента). Хотя сумма и кажется приемлемой, всегда учитывайте стоимость обработки больших репозиториев изображений, выполняемой с помощью языковых моделей.

СОВЕТ Чтобы узнать стоимость обработки изображений с определенным разрешением, можно воспользоваться калькулятором цен OpenAI: https://openai.com/pricing.

6.3. Тегирование людей на изображениях

Представьте ситуацию: вы только что вернулись из (заслуженного) отпуска с друзьями и, конечно, сделали множество фотографий. Вы хотите отправить друзьям фото с ними. Но как сделать это эффективно? Можно просмотреть фотографии вручную и отметить каждого друга отдельно. Но вы только вернулись из отпуска, ваш электронный почтовый ящик переполнен, и на просмотр фото совсем нет времени. Как же быть?

6.3.1. Обзор

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

• путь к каталогу, содержащему фотографии, к которым нужно добавить теги;

• путь к каталогу, содержащему фото людей, которых нужно найти;

• путь к каталогу, куда будут сохраняться размеченные фотографии.

Для простоты мы будем использовать в качестве тегов имена файлов. Мы предполагаем, что фотографии с человеком, которого нужно найти, названы по имени этого человека. Например, в нашем каталоге с фотографиями людей, которых нужно найти, есть изображения с именами Joe.png и Jane.png. Чтобы тегировать нужное изображение, мы просто изменим имя файла, добавив в его начало имена людей, которые запечатлены на нем.

Предположим, у нас есть изображение beach.png, на котором есть и Джо, и Джейн. Тогда в выходном каталоге мы создадим два файла с именами Joebeach.png и Janebeach.png, показывая, что обе они есть на фотографии пляжа. Если нам нужно отправить все фотографии, на которых изображен один и тот же человек, например Джо, то можно найти все файлы, имя которых удовлетворяет регулярному выражению Joe*.png (где * обозначает произвольные строковые значения).

Сначала на этапе внутренней обработки требуется загрузить, во-первых, изображения людей, которых нужно найти, и, во-вторых, фото, которые нужно тегировать. Для каждого человека, которого мы ищем, и для каждой фотографии, которую нужно тегировать, будем перебирать все возможные пары. Например, если мы ищем пять человек и у нас есть десять фото для тегирования, то придется рассмотреть 50 пар. Для каждой из этих пар мы используем языковую модель GPT-4o, чтобы решить, запечатлен ли соответствующий человек на тегируемой фотографии.

Для этого понадобятся мультимодальные промпты, содержащие текст и два изображения. Первое — фото человека, которого нужно найти, а второе — фото, на котором его нужно отметить. С помощью текстового промпта можно дать языковой модели указание сравнить изображения и определить, есть ли этот человек на фотографии. При каждом совпадении — то есть сочетании человека и фото, на котором он есть, — мы будем копировать соответствующую фотографию в выходную папку, добавляя перед ее названием имя человека.

На рис. 6.2 показан пример промпта. Слева — фотография Джейн, одной из тех, кого мы ищем. Справа — фото для тегирования. В текстовой инструкции содержится указание для языковой модели сравнить две фотографии и дать ответ «Да», если на них изображен один и тот же человек (или «Нет», если люди разные). В данном случае на фотографиях изображены разные люди и правильный ответ должен быть «Нет».

Рис. 6.2. Мультимодальный промпт, содержащий два изображения и текст: языковая модель получает указание проверить, изображен ли на обеих фотографиях один и тот же человек (ожидаемый ответ: «Да») или нет (ожидаемый ответ: «Нет»)

6.3.2. Кодирование локально хранящихся изображений

В разделе 6.2 мы с помощью языковой модели GPT-4o анализировали интернет-изображения. Теперь речь идет о наших личных отпускных фотографиях. Допустим, нам не хочется выкладывать их в сеть и делать общедоступными. Каким образом передать их только GPT-4o?

Скорее всего, придется преобразовать изображения в формат, подходящий для GPT-4o. Языковая модель GPT-4o поддерживает широкий спектр форматов изображений, таких как PNG, JPEG, WEBP и GIF. Для любого формата размер файла изображения в данный момент ограничен 20 Мбайт. Чтобы загрузить изображения поддерживаемых типов в GPT-4o, сначала нужно закодировать их с помощью кодировки base64.

Что такое кодирование base64

Кодирование base64 — это способ представления двоичных данных в виде текстовой строки, состоящей из печатаемых символов. Как следует из названия base64, используемый для строки алфавит основан на 64 символах. Это означает, что каждый символ можно представить с помощью шести бит (поскольку они позволяют представить 26 = 64 возможных символа). Учитывая, что хранение данных в компьютерах осуществляется на уровне байтов (по восемь бит), кодирование целесообразно производить группами из трех байт (24 бита). Используя кодировку base64, три байта можно использовать для представления четырех символов (поскольку 24 / 6 = 4).

В Python кодирование двоичных данных в формат base64 выполняется с помощью модуля base64. Приведенный ниже код открывает файл изображения, путь к которому указан в переменной image_path, и кодирует его с использованием формата base64:

with open(image_path, 'rb') as image_file:

    encoded = base64.b64encode(image_file.read())

Мы преобразовали двоичные данные изображения в строку в формате base64. Прежде чем отправлять такие изображения языковой модели GPT-4o, необходимо выполнить еще одно (последнее) преобразование: представить строку в кодировке UTF-8.

Что такое кодировка UTF-8

UTF-8 — это способ представления строковых данных. Он чрезвычайно популярен и используется примерно на 98 % сайтов. UTF-8 поддерживает более миллиона символов из самых разных языков. Все эти символы можно представить с помощью фиксированного количества байтов: четыре байта для представления каждого символа. Однако это неэффективно, ведь не учитывается тот факт, что некоторые символы встречаются гораздо чаще других. Если закодировать распространенные символы меньшим количеством байтов, оставив представления с большим количеством байтов для более редких, то можно представить тот же текст, используя меньше байтов. Именно это и делает UTF-8, а поскольку для представления разных символов может потребоваться разное количество байтов, то UTF-8 также называют стандартом переменной длины. При этом он был разработан так, чтобы сохранять обратную совместимость со старым стандартом ASCII: первые 128 символов кодируются в нем точно так же.

Преобразовать нашу строку base64, кодирующую изображение, в формат UTF-8 в Python можно с помощью функции decode. При условии, что изображение все еще закодировано в переменной encoded, можно сделать это с помощью следующего кода:

image = encoded.decode('utf-8')

Полученное изображение, закодированное как текстовая строка UTF-8, подходит в качестве входных данных для языковой модели GPT-4o. Далее мы рассмотрим, как загрузить изображения в этом формате на платформу OpenAI. После их загрузки ссылки на эти изображения можно будет добавить в наши запросы. Изображения обычно указываются как компоненты запроса:

{'type':'image_url', 'image_url':{'url':image_url}}

Здесь image_url обозначает URL, ведущий к изображению, которое необходимо проанализировать. Ранее мы использовали для этого общедоступные URL. Теперь мы анализируем личные изображения, которые отправим компании OpenAI, чтобы использовать их только для обработки конкретных запросов. С учетом того, что элемент image по-прежнему представляет изображение, которое закодировано как строка, URL изображения можно задать так:

image_url = {'url':f'data:image/png;base64,{image}'}

В этом коде предполагается, что изображение хранится в формате PNG (если нет, то замените значение png на соответствующий идентификатор формата, например jpeg). URL объединяет метаданные об изображении (такие как тип изображения и кодировка) с суффиксом строки, представляющим саму фотографию.

6.3.3. Передача изображений из локального хранилища в OpenAI

Воспользуемся этим проектом как возможностью увидеть альтернативный способ взаимодействия с моделями GPT. Такой подход даст представление о внутренних механизмах работы библиотеки Python от OpenAI. До сих пор мы использовали обертки Python, которые в фоновом режиме отправляли запросы на платформу OpenAI. Для отправки своих локальных изображений языковой модели GPT-4o мы сами создадим такие запросы.

Мы используем библиотеку requests Python для создания HTTP-запросов, отправляем промпты (с текстом и изображениями) в GPT-4o и получаем ответ. Точнее говоря, мы будем отправлять HTTP-запросы методом POST. Это тип запроса, принимаемый платформой OpenAI. Такие запросы можно отправить с помощью метода requests.post.

Запросы будут содержать всю информацию, которая нужна GPT-4o для решения интересующей нас задачи (в данном случае проверки, изображен ли на двух фотографиях один и тот же человек). Прежде всего в запрос необходимо добавить заголовки (headers). Мы будем использовать следующие заголовки:

headers = {

    'Content-Type': 'application/json',

    'Authorization': 'Bearer ...'

}

Как видите, они заданы как словарь Python. Для нашего сценария использования требуется сохранить только два свойства: тип данных (мы планируем отправлять контент в формате JSON) и учетные данные доступа (три точки обозначают наш ключ доступа OpenAI).

Далее необходимо указать полезную нагрузку, то есть содержимое, которое следует отправить с помощью запроса в первую очередь:

Обратите внимание: полезная нагрузка содержит именно те поля, которые мы обычно указывали в вызовах метода completions.create. И это не случайно, поскольку второй метод внутри себя создает запросы с аналогичной полезной нагрузкой. Сначала полезная нагрузка указывает модель : gpt-4o (чтобы иметь возможность обрабатывать мультимодальные входящие промпты). Мы указываем список сообщений, в котором всего одна запись . Сообщение помечено как исходящее от пользователя ('role':'user'), а его содержимое, обозначенное тремя точками, будет содержать текстовые инструкции и изображения. Наконец, мы ограничиваем длину ответа одним токеном ('max_tokens':1) . Это логично, ведь нам нужен бинарный результат: один и тот же человек либо есть на нескольких входных изображениях (ожидаемый ответ: «Да»), либо нет (ожидаемый ответ: «Нет»).

Сформировав заголовки и полезную нагрузку, можно вызвать модель GPT-4o с помощью такого кода:

response = requests.post(

    'https://api.openai.com/v1/chat/completions',

    headers=headers, json=payload)

Первым параметром в вызове метода requests.post указан URL, на который нужно отправить запрос. В данном случае адрес https://api.openai.com/v1/chat/completions показывает, что требуется выполнить задачу типа Completion, используя одну из чат-моделей OpenAI (к ним относится и GPT-4o). Мы используем созданные ранее заголовки и полезную нагрузку.

В ответе содержится объект с результатом работы GPT-4o. Получить доступ к ответу (в котором говорится, изображен ли на двух фотографиях один и тот же человек) можно с помощью такого фрагмента кода:

response.json()['choices'][0]['message']['content']

6.3.4. Полный рабочий код для реализации

Теперь мы готовы обсудить полную реализацию от начала до конца! В листинге 6.2 представлен код, позволяющий отмечать людей на фотографиях.

Листинг 6.2. Тегирование  людей на фотографиях из локального хранилища

import argparse

import base64

import os

import requests

import shutil

 

def load_images(in_dir):                 

    """Загружает изображения из каталога.

 

    Аргументы:

        in_dir: путь к входному каталогу.

 

    Выводит:

        словарь сопоставлений имен файлов с PNG-изображениями.

    """

    name_to_image = {}

    file_names = os.listdir(in_dir)

    for file_name in file_names:

        if file_name.endswith('.png'):

            image_path = os.path.join(in_dir, file_name)

            with open(image_path, 'rb') as image_file:

                encoded = base64.b64encode(image_file.read())

                image = encoded.decode('utf-8')

                name_to_image[file_name] = image

 

    return name_to_image

 

def create_prompt(

    person_image, image_to_label):

    """ Создать промпт для сравнения изображений.

 

    Аргументы:

        person_image: изображение человека.

        image_to_label: изображение для назначения метки.

 

    Выводит:

        Промпт для проверки, изображен ли один и тот же человек

        на обеих иллюстрациях.

    """

    task = {'type':'text',

            'text':'На этих фотографиях один и тот же человек ("Да"/"Нет")?'}

    prompt = [task]

    for image in [person_image, image_to_label]:

        image_url = {'url':f'data:image/png;base64,{image}'}

        image_msg = {'type':'image_url', 'image_url':image_url}

        prompt += [image_msg]

 

    return prompt

 

def call_llm(ai_key, prompt):            

    """Вызвать языковую модель для обработки промпта с локальными изображениями.

 

    Аргументы:

        ai_key: ключ для доступа к OpenAI.

        prompt: промпт, объединяющий текст и локальные изображения.

 

    Выводит:

        Ответ языковой модели.

    """

    headers = {

        'Content-Type': 'application/json',

        'Authorization': f'Bearer {ai_key}'

    }

    payload = {

        'model': 'gpt-4o',

        'messages': [

            {'role': 'user', 'content': prompt}

            ],

        'max_tokens':1

        }

    response = requests.post(

        'https://api.openai.com/v1/chat/completions',

        headers=headers, json=payload)

    return response.json()['choices'][0]['message']['content']

 

if __name__ == '__main__':               

 

    parser = argparse.ArgumentParser()   

    parser.add_argument('peopledir', type=str, help='Изображения людей')

    parser.add_argument('picsdir', type=str, help='Изображения для тегирования')

    parser.add_argument('outdir', type=str, help='Выходной каталог')

    args = parser.parse_args()

 

    people_images = load_images(args.peopledir)

    unlabeled_images = load_images(args.picsdir)

 

    for person_name, person_image in people_images.items():

        for un_name, un_image in unlabeled_images.items():  

            prompt = create_prompt(person_image, un_image)

            ai_key = os.getenv('OPENAI_API_KEY')

            response = call_llm(ai_key, prompt)

            description = f'un_name versus person_name?'

            print(f'description -> response')

 

            if response == 'Да':        

                labeled_name = f'person_name[:-4]un_name'

                source_path = os.path.join(args.picsdir, un_name)

                target_path = os.path.join(args.outdir, labeled_name)

                shutil.copy(source_path, target_path)

Сначала рассмотрим основную функцию . Как обсуждалось ранее, в качестве параметров командной строки пользователи указывают три каталога : с фотографиями, которые требуется тегировать; с изображениями людей, которых нужно отметить на фотографиях; и каталог для вывода.

На первом этапе мы загружаем все изображения для тегирования, а также все изображения людей, которых нужно найти. Для этого используется функция load_images . Она получает список всех файлов во входном каталоге, а затем рассматривает те, которые заканчиваются суффиксом .png (то есть все изображения в формате PNG). Как обсуждалось ранее, изображения необходимо закодировать в виде строк (применив кодировку base64), которые в конечном счете представлены с помощью кодировки UTF-8. Результатом работы функции load_images является словарь Python, сопоставляющий имена файлов с соответствующими закодированными изображениями. Этот словарь возвращается как результат функции.

Применив функцию load_images к каждому из двух входных каталогов, мы получим два словаря Python. Один сопоставляет имена файлов, содержащих изображения людей (которые, как мы договорились ранее, являются именами этих людей), с соответствующим закодированным изображением. Другой сопоставляет имена файлов изображений, которые необходимо тегировать, с закодированными изображениями.

Наша цель — сопоставить каждую фотографию, предназначенную для тегирования, со всеми людьми, которые на ней запечатлены. Поскольку мы используем промпты, одномоментно сравнивающие только две фотографии, то придется просмотреть все сочетания людей и изображений, которые требуется тегировать. Именно поэтому мы используем двойной вложенный цикл for: один перебирает людей , а другой — размечаемые изображения .

Для каждого сочетания изображения и человека с помощью функции create_prompt создается мультимодальный промпт. Эта функция объединяет оба закодированных изображения вместе с текстовыми инструкциями в промпт. Текстовые инструкции («На фотографиях один и тот же человек (“Да”/“Нет”)?») определяют задачу, а также ожидаемый формат вывода («Да» или «Нет»). Каждый промпт отправляется в языковую модель GPT-4o через функцию call_llm. Как обсуждалось ранее, эта функция использует API запросов для отправки фотографий, находящихся в локальном хранилище, в GPT-4o. Если GPT-4o отвечает «Да», значит, рассматриваемый в данный момент человек есть на изображении, которое сейчас размечается.

Если человек есть на фотографии , то мы помечаем ее следующим образом. Берем имя человека (имя соответствующего файла изображения без расширения .png) и добавляем в начало имени файла изображения, которое требуется тегировать. Затем файл этого изображения копируем в выходной каталог, уже с новым именем (которое содержит тег).

6.3.5. Проверка в работе

Пробуем. Если у вас есть настоящие фотографии из отпуска, на которых можно кого-то отметить, то можете использовать их. Если нет, то подходящие тестовые данные доступны по ссылке Tagging, размещенной на сайте книги в разделе дополнительных материалов к главе. Скачайте заархивированный файл и распакуйте. После распаковки в полученной папке должно быть три подкаталога:

people — фотографии людей (в данном случае фото актеров из серии фильмов «Мстители»). Имена файлов содержат имена соответствующих актеров;

• pics — еще один набор фотографий (в данном случае дополнительные фото тех же актеров, что и в папке people), который нужно тегировать именами актеров;

processed — пустая папка, которую можно использовать в качестве выходного каталога.

Предположим, что путь к месту хранения распакованной папки — /tagging (так, путь /tagging/people будет вести к подкаталогу с фотографиями людей, которых нужно найти). Выполните код, запустив из терминала такую команду:

python listing2.py /tagging/people /tagging/pics /tagging/processed

СОВЕТ При запуске кода на платформе под управлением операционной системы Windows пути придется адаптировать. В частности, необходимо будет заменить символ / на \.

Во время обработки программа выводит информацию о том, есть ли конкретные люди на конкретных фотографиях. В примере данных есть фото двух человек, которых нужно найти, и четыре изображения, которые требуется тегировать. Это означает, что обработка должна занимать не более нескольких минут (обычно менее двух).

После завершения обработки зайдите в выходную папку. Там должны находиться размеченные изображения, к именам которых добавлены префиксы — имена людей, запечатленных на этих фото. Неплохо для нескольких строк кода на Python!

6.4. Генерация заголовков для видеороликов

Помимо множества фотографий (которые теперь можно автоматически тегировать благодаря коду, описанному в предыдущем разделе!), в отпуске вы сняли и немало видеороликов. Автоматически присвоенные имена файлов мало­информативны. В каком видеоролике показывается, как вы плаваете в океане? Было бы здорово присвоить видеороликам осмысленные названия, и это поможет быстрее находить нужные. Но у кого есть время вручную подписывать видеоролики? И снова для автоматического выполнения этой задачи можно использовать языковые модели.

6.4.1. Обзор

Мы разработаем систему, которая будет автоматически присваивать видеороликам подходящие заголовки. Эта система использует языковую модель GPT-4o в фоновом режиме. Чтобы присвоить заголовки видеороликам, необходимо отправить мультимодальный промпт, содержащий кадры видео (то есть изображения) и текст, в котором указание для языковой модели — придумать заголовок. На рис. 6.3 показан пример промпта.

Промпт состоит из нескольких видеокадров (на рис. 6.3 мы видим только первый и последний кадры; три точки обозначают, что между ними есть еще кадры) и текстовой инструкции «Придумай краткий заголовок для видеоролика». Обратите внимание, что мы платим за каждый видеокадр, который отправляем в языковую модель GPT-4o. Следовательно, обработка видеоданных через GPT-4o быстро станет дорогостоящей!

Рис. 6.3. Мультимодальный промпт для обработки видеоролика: языковая модель получает инструкцию сгенерировать подходящий заголовок на основе подборки видеокадров

В качестве ответа языковая модель GPT-4o должна отправить логичное название. В примере, показанном на рис. 6.3, это может быть отсылка к автомобилям и, возможно, даже к местоположению (на кадрах указано белым текстом).

6.4.2. Кодирование видеокадров

Сначала необходимо обсудить форматы видео. В предыдущем разделе вы узнали, как прописывать в коде изображения, находящиеся в локальном хранилище. Теперь мы расширим этот подход на работу с видеороликами. Наша цель — извлечь последовательность кадров. Однако видеоролики обычно хранятся не как последовательность кадров, а с использованием более эффективных кодировок. Для нас это означает, что сначала придется извлечь из видеоролика изображения.

Сделаем это с помощью библиотеки OpenCV. Это библиотека компьютерного зрения с открытым исходным кодом. Она предоставляет различные функции для компьютерного зрения, а также для обработки изображений и видеороликов в целом. Разумеется, функцию компьютерного зрения для нас выполнит модель GPT-4o. И все равно OpenCV будет полезна для извлечения кадров из видеороликов. Если вы еще не сделали этого, то самое время установить библиотеку OpenCV, следуя инструкциям в разделе 6.1.

Допустим, установка прошла успешно, и можно получить доступ к OpenCV из кода Python. Соответствующая библиотека Python называется cv2 (это название будет часто встречаться в виде префикса в приведенных ниже фрагментах кода).

Для работы с видеороликами, находящимися в локальном хранилище, сначала нужно открыть соответствующий файл. Запустите этот код, чтобы открыть видео, сохраненное по пути video_path:

video = cv2.VideoCapture(video_path)

Используя переменную video, теперь можно прочитать содержимое видеоролика, применив метод read:

success, frame = video.read()

Результат состоит из кортежей с двумя компонентами: индикатором успешности выполнения (success) и видеокадром (frame). Индикатор указывает, удалось ли нам прочитать еще один кадр. Он перестает работать при достижении конца видеоролика. В этом случае мы не получаем действительный кадр, и значение индикатора становится False.

Предположим, что нам удалось прочитать еще один кадр. В этом случае мы превратим кадр в изображение, которое можно отправить языковой модели GPT-4o. Библиотека OpenCV предоставляет соответствующую функциональность:

_, buffer = cv2.imencode('.jpg', frame)

Функция imencode превращает видеокадр в изображение соответствующего типа. Здесь мы преобразуем кадр в изображение в формате JPEG. В полученном кортеже нам интересен второй компонент (buffer). Он содержит двоичное представление соответствующего изображения.

Эта ситуация знакома нам по предыдущему разделу: есть двоичное представление изображения и его нужно превратить в формат, подходящий для языковой модели GPT-4o. И снова мы сначала кодируем изображение как строку с помощью кодировки base64, а затем представляем эту строку через UTF-8:

encoded = base64.b64encode(buffer)

frame = encoded.decode('utf-8')

Полученный кадр корректно закодирован для добавления в промпт для языковой модели GPT-4o. Завершив обработку видеоролика, закройте соответствующий объект захвата видео, используя такой код:

video.release()

Далее мы соберем фрагменты кода и генерируем заголовки для произвольных видеороликов.

6.4.3. Полный код реализации

В коде листинга 6.3 создаются названия для видеороликов, находящихся в локальном хранилище. Единственный входной параметр — путь к видеоролику. Получив его, программа извлекает несколько видеокадров , а затем генерирует промпт, дающий модели GPT-4o указание придумать заголовок для видеоролика на основе выборки кадров. После отправки этого промпта языковой модели ее ответ будет содержать предлагаемый заголовок видеоролика.

Листинг 6.3. Генерация заголовка видеоролика с помощью языковых моделей

import argparse

import cv2

import base64

import openai

import time

 

client = openai.OpenAI()

 

def extract_frames(video_path):               

    """ Извлекает кадры из видеоролика.

 

    Аргументы:

        video_path: путь к видеофайлу.

 

    Выводит:

        Список первых десяти видеокадров.

    """

    video = cv2.VideoCapture(video_path)

    frames = []

    while video.isOpened() and len(frames) <= 10:

        success, frame = video.read()

        if not success:

            break

 

        _, buffer = cv2.imencode('.jpg', frame)

        encoded = base64.b64encode(buffer)

        frame = encoded.decode('utf-8')

        frames += [frame]

 

    video.release()

    return frames

 

def create_prompt(frames):                    

    """ Составить промпт для генерации заголовка к видеоролику.

 

    Аргументы:

        frames: кадры видеоролика.

 

    Выводит:

        промпт, содержащий мультимодальные данные (список).

    """

    prompt = ['Придумай точное название для видеоролика.']

    for frame in frames[:10]:

        element = {'image':frame, 'resize':768}

        prompt += [element]

    return prompt

 

def call_llm(prompt):                         

    """ Отправить запрос большой языковой модели и вывести ответ.

 

    Аргументы:

        prompt: ввести запрос к языковой модели.

 

    Выводит:

        Ответ языковой модели.

    """

    for nr_retries in range(1, 4):

        try:

            response = client.chat.completions.create(

                model='gpt-4o',

                messages=[

                    {'role':'user', 'content':prompt}

                    ]

                )

            return response.choices[0].message.content

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

 

if __name__ == '__main__':

 

    parser = argparse.ArgumentParser()

    parser.add_argument('videopath', type=str, help='Путь к видеофайлу')

    args = parser.parse_args()

 

    frames = extract_frames(args.videopath)   

    prompt = create_prompt(frames)

    title = call_llm(prompt)

    print(title)

Код извлекает видеокадры с помощью функции extract_frames . Как обсуждалось ранее, она использует библиотеку OpenCV, чтобы открыть видеоролик для извлечения кадров, а затем последовательно считывает каждый кадр. Для генерации названия видео мы будем использовать не более десяти кадров. Вот почему извлечение заканчивается после максимум десяти кадров (или меньшего количества, если видео очень короткое). Каждый извлеченный кадр кодируется в соответствии с требованиями GPT-4o (то есть изображения в формате JPEG, закодированные как строки). Результатом функции является список закодированных кадров.

В процессе генерации промпта мы объединяем соответствующие текстовые инструкции («Придумай краткий заголовок для видео») с первыми десятью кадрами из видеоролика. Для отправки этих изображений вместе с инструкциями в модель GPT-4o снова воспользуемся оберткой Python . Как вариант, можно самостоятельно составлять запросы (как мы делали в предыдущем проекте). Ответ языковой модели должен содержать подходящий заголовок для нашего видеоролика.

Разумеется, мы отправляем только первые несколько кадров видеоролика. Если его содержание после этих нескольких кадров кардинально изменится, то заголовок может оказаться неоптимальным. Причина, по которой мы отправляем только десять кадров, — стоимость вычислений. Помните, что вы платите за каждое отправленное в промпт изображение! Отправка всех кадров длинных видеороликов, как правило, обходится непомерно дорого. Вот почему мы довольствуемся отправкой только небольшого подмножества видеокадров.

6.4.4. Проверка в работе

Протестируем наш генератор заголовков видеороликов! На сайте книги в разделе дополнительных материалов к этой главе есть ссылка Cars, по которой вы можете найти короткий видеоролик с камеры наблюдения, показывающий движение на оживленной дороге. Скачайте его на свой компьютер.

Откройте терминал и перейдите в каталог, содержащий код для этой главы. Предполагается, что видеоролик был загружен в тот же каталог (если нет, замените имя видеоролика — cars.mp4 — на полный путь к нему).

Запустите следующую команду:

python listing3.py cars.mp4

Спустя несколько секунд вычислений должен появиться предлагаемый вариант заголовка видеоролика: например, «Дорожная ситуация на шоссе I-5 у SR 516 и 188-й улицы» (точное название может меняться при разных запусках из-за рандомизации).

Обратите внимание, что название содержит информацию (указание местоположения), которая доступна в видеоролике только в виде текста. Использование GPT-4o для извлечения текста из изображений может быть полезно в различных сценариях: например, для извлечения данных из бланков.

Резюме

• Языковая модель GPT-4o обрабатывает как изображения, так и текст.

• В промптах можно объединять фрагменты текста и изображения.

• Модель GPT-4o поддерживает несколько форматов изображений.

• Изображения можно указывать через общедоступный URL.

• Изображения, находящиеся в локальном хранилище, можно загружать в OpenAI.

• Модель GPT-4o обрабатывает изображения, закодированные в виде строк.

• Стоимость обработки изображений выше, чем обработки текста, и может зависеть от размера изображения. Обработка изображений с низкой степенью детализации снижает затраты.

• Модуль base64 может кодировать изображения в строки.

• Для отправки в языковую модель GPT-4o видео нужно разделять на кадры.

• Для извлечения кадров из видео можно использовать библиотеку OpenCV.

Назад: 5. Анализ структурированных данных
Дальше: 7. Анализ аудиоданных