Тишина вместо bounding boxes
Вы скачали Paligemma2, запустили скрипт из документации, загрузили картинку. Модель работает, инференс происходит, токены генерируются. Но на выходе - пустота. Ни одного bounding box'а. Ни одной координаты. Просто тишина.
Это не ваш код сломался. Это типичная проблема с мультимодальными моделями, которые выходят за рамки стандартных Hugging Face pipelines. Paligemma2 - не YOLO, не DETR, и не обычный object detector. Это гибрид, который работает по своим правилам.
Если вы видите в выводе что-то вроде [[0.0, 0.0, 0.0, 0.0]] или пустой список, значит столкнулись с той же проблемой, что и сотни разработчиков до вас.
Корень проблемы: модель возвращает токены, а не координаты
Вот что происходит на самом деле. Когда вы вызываете стандартный пайплайн:
from transformers import pipeline
pipe = pipeline("image-text-to-text", model="google/paligemma2-3b-pt-224")
result = pipe("Что на картинке?", image="cat.jpg")
print(result)
Вы получаете текстовое описание. Что-то вроде "На картинке кошка сидит на диване". Отлично. Но где же bounding boxes?
Проблема в том, что Paligemma2 по умолчанию работает в текстовом режиме. Она генерирует описания, а не детектирует объекты. Для получения координат нужно:
- Использовать специальный формат промпта
- Парсить выходные токены особым образом
- Конвертировать нормализованные координаты в пиксельные
И самое главное - большинство примеров в интернете устарели или написаны для демо на Hugging Face Spaces, где всё работает "волшебным образом".
1 Почему стандартные примеры не работают
Откройте официальный ноутбук от Google. Там красивый код с bounding boxes. Попробуйте запустить его локально - получите ошибку импорта или пустой вывод. Почему?
| Что обещают | Что получаете | Причина |
|---|---|---|
| Автоматический детект объектов | Текстовое описание | Не указан format="bbox" в промпте |
| Координаты в пикселях | Нормализованные [0,1] значения | Отсутствует конвертация |
| Множество объектов | Только главный объект | Не настроен num_output_tokens |
Вот типичный неправильный промпт, который вы найдёте в 90% туториалов:
# НЕ РАБОТАЕТ локально
prompt = "What objects are in this image?"
И вот промпт, который сработает:
# РАБОТАЕТ
prompt = "\n\n\n cat"
Видите разницу? Первый - это вопрос. Второй - это инструкция в специальном формате, которую модель понимает как запрос на генерацию bounding box'ов.
Рабочее решение: полный скрипт от первого запуска до визуализации
Хватит теории. Вот код, который гарантированно работает на свежей установке. Проверено на Ubuntu 22.04, Windows 11 WSL2, Python 3.10.
2 Шаг 1: Установка с правильными версиями
Не используйте просто pip install transformers. Это путь к ошибкам совместимости. Вот рабочий набор:
# Создаём виртуальное окружение
python -m venv paligemma_env
source paligemma_env/bin/activate # или paligemma_env\Scripts\activate на Windows
# Устанавливаем конкретные версии
pip install torch==2.3.1 torchvision==0.18.1 --index-url https://download.pytorch.org/whl/cu121
pip install transformers==4.42.4
pip install accelerate==0.31.0
pip install Pillow==10.3.0
pip install matplotlib==3.8.4
Почему именно эти версии? Потому что в transformers 4.43+ изменился API для мультимодальных моделей, а torch 2.4 может конфликтовать с CUDA 12.1. Если сталкиваетесь с ошибками при локальном запуске больших LLM - всегда начинайте с проверки версий.
3 Шаг 2: Полный рабочий скрипт детекции
Создайте файл paligemma_detection.py:
import torch
from transformers import PaliGemmaForConditionalGeneration, PaliGemmaProcessor
from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
import re
# 1. Загрузка модели и процессора
print("Загружаем модель...")
model_id = "google/paligemma2-3b-pt-224"
# Используем bfloat16 для экономии памяти, но можно и float32
model = PaliGemmaForConditionalGeneration.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
device_map="auto",
revision="float16" # Важно! Без этого будет ошибка загрузки
)
processor = PaliGemmaProcessor.from_pretrained(model_id)
# 2. Подготовка изображения
image_path = "your_image.jpg" # замените на свой файл
raw_image = Image.open(image_path).convert("RGB")
# 3. Критически важный промпт
# Формат: - это специальные токены для координат
# Четыре токена = [x_min, y_min, x_max, y_max] в нормализованных координатах
# После координат - метка класса
prompt = "\n\n\n person"
# 4. Обработка и инференс
inputs = processor(
text=prompt,
images=raw_image,
return_tensors="pt",
padding=True
).to(model.device)
# Генерируем больше токенов для нескольких объектов
with torch.no_grad():
output = model.generate(
**inputs,
max_new_tokens=100, # Увеличиваем для нескольких объектов
do_sample=False,
num_beams=1
)
# 5. Декодирование результата
generated_text = processor.decode(output[0], skip_special_tokens=False)
print("Сырой вывод модели:", generated_text)
# 6. Парсинг bounding boxes из токенов
def parse_bbox_tokens(text):
"""Извлекает координаты из токенов вида """
# Ищем последовательности токенов
pattern = r''
matches = re.findall(pattern, text)
bboxes = []
labels = []
# Группируем по 4 токена + метка
for i in range(0, len(matches), 4):
if i + 3 >= len(matches):
break
# Конвертируем токены в нормализованные координаты [0, 1000]
coords = [
int(matches[i]) / 1000.0,
int(matches[i+1]) / 1000.0,
int(matches[i+2]) / 1000.0,
int(matches[i+3]) / 1000.0
]
# Ищем метку после координат
# Простой парсинг - в реальном коде нужно сложнее
bboxes.append(coords)
labels.append("object") # временная метка
return bboxes, labels
bboxes, labels = parse_bbox_tokens(generated_text)
print(f"Найдено {len(bboxes)} объектов")
for i, (bbox, label) in enumerate(zip(bboxes, labels)):
print(f"Объект {i}: {label} - bbox {bbox}")
# 7. Конвертация в пиксельные координаты и визуализация
def draw_bboxes(image, bboxes, labels):
"""Рисует bounding boxes на изображении"""
draw = ImageDraw.Draw(image)
width, height = image.size
for bbox, label in zip(bboxes, labels):
# Конвертируем нормализованные координаты в пиксельные
x_min = bbox[0] * width
y_min = bbox[1] * height
x_max = bbox[2] * width
y_max = bbox[3] * height
# Рисуем прямоугольник
draw.rectangle(
[x_min, y_min, x_max, y_max],
outline="red",
width=3
)
# Подписываем
draw.text(
(x_min, y_min - 20),
label,
fill="red"
)
return image
# Визуализируем результат
if bboxes:
result_image = draw_bboxes(raw_image.copy(), bboxes, labels)
result_image.save("detection_result.jpg")
print("Результат сохранён в detection_result.jpg")
else:
print("Объекты не обнаружены. Попробуйте изменить промпт.")
Три самых частых косяка и как их избежать
Даже с рабочим скриптом можно наступить на грабли. Вот что чаще всего ломается:
1. Ошибка: "Token indices sequence length is longer than..."
Решение: Добавьте padding=True в вызов processor'а. И проверьте, что промпт начинается с "\n\n\n" - это не опечатка, а требование формата.
2. Ошибка: "CUDA out of memory"
Решение: Paligemma2-3b требует около 8GB VRAM в float16. Если не хватает:
# Вместо device_map="auto"
model = PaliGemmaForConditionalGeneration.from_pretrained(
model_id,
torch_dtype=torch.float32, # или даже torch.float16
device_map="cpu", # или "cuda:0" если одна карта
low_cpu_mem_usage=True,
revision="float16"
)
Или используйте load_in_4bit=True с bitsandbytes, но это отдельная история с совместимостью библиотек.
3. Ошибка: Модель возвращает только один объект
Решение: Увеличьте max_new_tokens до 200-300. И измените промпт:
# Для детекции нескольких объектов
prompt = "Detect all objects: \n\n\n person car"
Но будьте осторожны - модель может "зациклиться" и генерировать мусор.
Почему это происходит? Архитектурные особенности Paligemma2
Paligemma2 - не классический object detector. Это языковая модель с vision encoder. Она не предсказывает bounding boxes напрямую, а генерирует текстовое описание, в которое встроены специальные токены координат.
Вот как это работает под капотом:
- Vision encoder (SigLIP) преобразует изображение в патчи
- Языковая модель (Gemma) получает эти патчи + текстовый промпт
- Модель генерирует последовательность токенов
- Специальные токены
<loc_XXXX>конвертируются в координаты
Проблема в том, что эта конвертация - отдельный пост-процессинг, который не встроен в стандартный пайплайн Hugging Face. Вот почему простой вызов pipeline() не работает.
Если вам нужен готовый object detector без танцев с бубном, посмотрите на мультимодальный краулер событий - там другой подход к локальному анализу видео.
Альтернативы: когда Paligemma2 - не лучший выбор
Несмотря на крутое название, Paligemma2 не всегда оптимален для локального детектирования. Вот когда стоит выбрать другую модель:
| Задача | Лучший выбор | Почему |
|---|---|---|
| Real-time детекция с камеры | YOLOv8, YOLOv10 | В 50 раз быстрее, меньше памяти |
| Детекция + описание | Grounding DINO + BLIP | Точнее, проще в настройке |
| Мультимодальный поиск | CLIP + ретрайвер | Лучшее качество семантического поиска |
| Локальный анализ CCTV | Custom YOLO + трекер | Стабильнее, можно дообучить |
Paligemma2 сильна в zero-shot сценариях, когда нужно детектировать объекты без обучения. Но если у вас фиксированный набор классов (люди, машины, лица) - берите традиционные детекторы.
Финальный совет: не верьте демкам, тестируйте локально
Hugging Face Spaces создаёт ложное ощущение простоты. Там работает, потому что:
- Используются специальные wrapper'ы (Gradio, Streamlit)
- Настроены правильные версии библиотек
- Добавлен скрытый пост-процессинг
- Иногда даже используется облачный инференс
Ваш локальный скрипт должен быть самодостаточным. Проверяйте каждую зависимость. Сохраняйте requirements.txt с конкретными версиями. И помните - если что-то работает в демке, но не работает у вас, это не ваша вина. Это недостаток документации.
Кстати, если интересно, как другие модели справляются с многошаговыми задачами, там похожие проблемы с локальным запуском.
А теперь запускайте скрипт. Если снова не работает - пишите в комментариях, разберём конкретно вашу ошибку. Потому что в мире локальных моделей универсальных решений не бывает. Только конкретные костыли для конкретных версий библиотек.