Говорящая лисичка, Айвенго и немного оптического потока

Говорящая лисичка, Айвенго и немного оптического потока

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

С этого всё и началось.

Задача казалась простой: взять изображение персонажа и аудио — и получить видео, где он говорит. В эпоху ИИ, когда ChatGPT пишет диссертации и рисует котиков, такая технология должна существовать.

Она существовала. Но работала в десять раз медленнее реального времени — даже на игровой видеокарте.

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

Wav2Lip, SadTalker, FasterLivePortrait — всё это сильные модели, результат многолетних исследований, с впечатляющими демо. Но на практике возникают проблемы.

GPU обязателен. Без него — слайд-шоу. С ним — всё равно тормоза: десять секунд обработки на секунду видео. Мы хотели рендерить аудиокниги в реальном времени, а получали очередь на рендерферму.

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

Тогда возникла идея: а зачем генерировать мимику каждый раз? Что если записать её один раз — медленно, с GPU — а потом воспроизводить быстро?

Граммофон вместо синтезатора.

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

Как это работает

Снимаем человека на видео — несколько минут речи, фонетически разнообразной, чтобы покрыть все основные звуки языка. Нарезаем видео на короткие перекрывающиеся сегменты: по десять кадров (~0.4 секунды), шаг — два кадра. Для каждого сегмента вычисляем акустические признаки: 16-мерный вектор из MFCC-коэффициентов, энергии и спектрального центроида.

Получается библиотека: этот кусок видео соответствует такому звуку.

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

Проблема вторая: иногда человека нет

Лисичка из детской книжки не придёт на съёмку. Айвенго — персонаж романа двухсотлетней давности. А заказчику нужен именно он, а не актёр в доспехах.

Здесь на помощь приходит FasterLivePortrait — но уже в новой роли. Не как инструмент рендера (он слишком медленный), а как генератор обучающих данных.

Берём изображение персонажа. Запускаем FLP — он генерирует видео, где персонаж произносит специально подобранный фонетически насыщенный текст. Да, это долго. Да, нужен GPU. Но это происходит один раз.

Дальше — тот же пайплайн: нарезаем на сегменты, вычисляем признаки, строим библиотеку. И рендерим любое количество видео с любым аудио — уже без GPU.

FLP здесь — не костыль. Это поставщик данных. Медленный, дорогой, но одноразовый.

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

Как склеивать кусочки так, чтобы не было швов

Это главная техническая сложность. Два сегмента могут сильно отличаться по положению лица на стыке. Наивная склейка даёт рывок.

Выбор кандидата

Сначала — выбираем правильный сегмент. Планировщик смотрит вперёд на две секунды, берёт среднее по будущим акустическим признакам и ищет сегмент под будущую фонетику — не под текущую. Это lookahead, и он улучшает синхронизацию: сегмент подобран под то, что будет звучать.

Из всей библиотеки берём топ-20 по акустическому сходству. Затем выбираем лучший стык: у каждого сегмента хранятся миниатюры первого и последнего кадра (64×64 пикселя). Считаем пиксельное расстояние между концом текущего сегмента и началом каждого кандидата. Берём ближайший. Операция быстрая — используются только крошечные изображения.

Морф через оптический поток

Даже лучший кандидат даёт небольшой рывок. Чтобы сгладить переход, используем двунаправленный оптический поток Фарнебека.

Почему двунаправленный? Простой crossfade размывает кадры — пиксели усредняются, получается каша. Двунаправленный поток двигает каждый пиксель по траектории из A в B и из B в A, затем смешивает. Переход выглядит как движение, а не как растворение.

Длина морфа адаптивная — от 5 до 12 кадров, в зависимости от различий между кадрами. Планировщик минимизирует разницу, поэтому большинство переходов — 5–7 кадров — почти незаметны.

Что делать в тишине

Когда персонаж молчит, нужно показывать что-то естественное. Случайный сегмент из речевой библиотеки — рот двигается без звука, что выглядит жутковато. Заморозка последнего кадра — неестественно.

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

Почему это работает на CPU

Каждый шаг выбран так, чтобы не требовать GPU:

  • Косинусный поиск по тысячам сегментов — умножение 16-мерных векторов, выполняется мгновенно
  • Выбор лучшего стыка — MAD по 20 картинкам 64×64, ~80 тысяч операций
  • Оптический поток Фарнебека — классический алгоритм компьютерного зрения, OpenCV считает его на CPU
  • Декодирование JPEG и remap — пиксельные операции

Самый тяжёлый этап — предвычисление оптических потоков между соседними кадрами — можно вынести в подготовку модели. Тогда рендер просто применяет готовые карты.

Итог: работа в реальном времени на обычном ноутбуке. GPU не нужен.

Вместо выводов

Две проблемы — одно решение.

Нейросети медленные? Записываем мимику один раз, воспроизводим быстро. Человека нет? FasterLivePortrait генерирует обучающее видео из картинки — один раз, медленно, с GPU. Дальше — быстрый retrieval.

Retrieval вместо generation. Граммофон вместо синтезатора.

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

Читать оригинал