Как мы научили локальную нейросеть «видеть» картинки — и почему она сначала видела только темноту
Недавно мы добавили поддержку компьютерного зрения в 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 указывает цвет фона. Поддерживаются:
- Именованные цвета:
white,black,gray - HEX-коды:
ffffff,ff0000 - RGB:
255,255,255
Код изменения — буквально 5 строк:
if (metadata.hasAlpha && bgColor) {
const background = parseBackgroundColor(bgColor)
img = img.flatten({ background })
}
Применяется только если:
- У изображения есть альфа-канал
- Передан параметр
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