Как мы научили локальную нейросеть «видеть» картинки — и почему она сначала видела только темноту

Недавно мы добавили поддержку компьютерного зрения в KMS-Agent — наш open-source проект автономного AI-агента. Задача казалась простой: отправляем картинку — получаем описание. Но на практике столкнулись с неожиданной проблемой, решение которой оказалось элегантным и полезным для всех, кто работает с компьютерным зрением.

Задача

KMS-Agent — это платформа для создания автономных AI-агентов с собственной идентичностью, базой знаний и инструментами. Нам нужно было добавить агенту возможность «видеть» — анализировать изображения локально, без облачных API.

Конкретный кейс — распознавание графиков математических функций из школьных задач ОГЭ. Модель должна была смотреть на график параболы и определять знаки коэффициентов.

Требования:

  • Работа полностью локально, без облачных API
  • Минимальные требования к железу (обычная игровая видеокарта)
  • Совместимость с OpenAI API для простой интеграции в существующую архитектуру агента

Выбор технологии

Остановились на связке:

  • llama.cpp — быстрый движок для запуска нейросетей на обычном железе
  • Qwen3.5-4B — свежая мультимодальная модель от Alibaba, которая умеет и текст, и картинки

Qwen3.5 — это уже весьма умная модель даже в версии на 4 миллиарда параметров. Она не просто описывает что видит, а реально «думает»: анализирует, строит рассуждения, делает выводы. При этом работает на обычной RTX 3060/4060 со скоростью около 40 слов в секунду.

Первый тест — и первая проблема

Отправили модели картинку с графиком параболы. Вопрос простой: «Что на картинке?»

Ответ модели нас озадачил:

«На изображении представлен очень тёмный фрагмент... Фон — глубокий чёрный цвет... едва заметные серые линии...»

Модель видела почти чёрную картинку с какими-то штрихами. Хотя на самом деле там был чёткий белый график на прозрачном фоне.

Расследование

Начали разбираться. Картинка — PNG-файл из базы школьных задач. Открываем в браузере — всё отлично видно. Отправляем модели — она видит темноту.

Проверили размер: 183×185 пикселей, 750 байт. Маленькая, но не критично.

Потом обратили внимание: PNG с прозрачным фоном.

Корень проблемы

Оказалось, что vision-модели обучаются на обычных RGB-изображениях (три канала: красный, зелёный, синий). А PNG с прозрачностью имеет четыре канала — добавляется альфа-канал, который отвечает за прозрачность.

Когда изображение конвертируется из RGBA в RGB, прозрачные пиксели должны куда-то «лечь». По умолчанию они становятся чёрными. Формула простая:

новый_пиксель = (1 - прозрачность) × фон + прозрачность × старый_пиксель

Если прозрачность = 0 (полностью прозрачный пиксель) и фон = чёрный, то результат = чёрный.

Вот почему модель видела «тёмный фрагмент с едва заметными линиями» — это были чёрные линии графика на чёрном фоне. Контраста почти нет.

Решение

Решение оказалось простым: перед отправкой в модель нужно «положить» прозрачное изображение на белый фон.

У нас уже был сервис для ресайза картинок на Node.js с библиотекой Sharp. Добавили туда один GET-параметр:

/images/resized/middle/path/to/image.png?bg=ffffff

Параметр bg указывает цвет фона. Поддерживаются:

  • Именованные цвета: whiteblackgray
  • HEX-коды: ffffffff0000
  • RGB: 255,255,255

Код изменения — буквально 5 строк:

if (metadata.hasAlpha && bgColor) {
  const background = parseBackgroundColor(bgColor)
  img = img.flatten({ background })
}

Применяется только если:

  1. У изображения есть альфа-канал
  2. Передан параметр bg

Без параметра — картинка отдаётся как есть.

Результат

Отправили ту же картинку, но с ?bg=ffffff:

«На этой картинке представлен график квадратичной функции (параболы) на системе координат. Парабола направлена вверх. Вершина находится ниже оси X. График пересекает ось X в двух точках...»«Это классический график функции вида y = ax² + c, где a > 0 и c < 0»

Модель не просто описала картинку — она правильно решила математическую задачу, определив знаки коэффициентов по виду графика.

Выводы

1. Прозрачность PNG — подводный камень для vision-моделей

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

  • Схем и графиков
  • Логотипов
  • Скриншотов с прозрачностью
  • Любой графики, созданной в редакторах

2. Решение простое — добавить фон

Можно делать на стороне сервера (как мы), на клиенте, или даже в пайплайне подготовки данных. Главное — не забыть.

3. Локальные модели уже достаточно умные

Qwen3.5-4B — это 4 гигабайта весов, работает на обычной видеокарте. При этом она:

  • Корректно распознаёт геометрические фигуры
  • Понимает математическую нотацию
  • Строит логические рассуждения
  • Отвечает на русском языке

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

Технические детали

Всё это уже реализовано в KMS-Agent v1.8.0. Для включения vision достаточно добавить в docker/.env:

LLAMA_MODEL=unsloth/Qwen3.5-4B-GGUF/Qwen3.5-4B-Q8_0.gguf
LLAMA_MMPROJ=unsloth/Qwen3.5-4B-GGUF/mmproj-F16.gguf

Модели скачиваются автоматически при первом запуске.

Стек:

  • Модель: unsloth/Qwen3.5-4B-GGUF
  • Vision encoder: mmproj-F16.gguf (672 MB)
  • Движок: llama.cpp с CUDA
  • API: OpenAI-совместимый /v1/chat/completions
  • Обработка PNG: Sharp (Node.js) с методом .flatten()

Изображения передаются в base64 формате — llama.cpp не умеет сам ходить по URL.

Документация: wiki/computer-vision