Эта статья изначально была опубликована в блоге Encord, который вы можете прочитать здесь.
Денис Гавриэлов
В основе предложения Encord лежит создание быстрых и интуитивно понятных инструментов обнаружения объектов, сегментации и классификации аннотаций для изображений и видео.
Точно так же, как элементы могут перемещаться по разным кадрам в видео, мы должны убедиться, что сохраняем правильные координаты отдельных меток, соответствующих правильному номеру кадра в видео.
Вы можете подумать «ага», но это серьезная проблема, которую нужно решить. У нас были клиенты, которые перешли на нашу платформу после того, как им пришлось выбросить многомесячные размеченные данные, созданные дорогостоящими командами по маркировке с помощью инструментов с открытым исходным кодом. Все это потому, что они поняли, что метки не совпадают с фактически соответствующими кадрами из-за множества проблем с рендерингом видео в современных веб-браузерах.
Вот как мы подошли к проблеме: мы встраиваем видео через HTML-элемент ‹video›. Когда пользователь приостанавливает видео, чтобы нарисовать метку, мы запрашиваем номер текущего кадра видео из соответствующего элемента DOM и сохраняем данные в нашем бэкэнде. Всякий раз, когда клиенту нужна метка для процесса обзора, для обучения или применения моделей машинного обучения или для загрузки меток для своих внутренних инструментов, он получает правильные данные.
Именно тогда мы поняли, что решили все проблемы наших клиентов и навсегда удалились на Карибы.
Хотя это то, что мы хотели, это, к сожалению, не так, как работает элемент ‹video›. Проблема в том, что он не позволяет искать конкретный кадр, а только конкретную временную метку.
Это не должно быть проблемой, если вы думаете, что поиск определенного фрейма будет поиском
timestamp_x = frame_x * (1 / frames_per_second)
который работает, если:
- В видео присутствует переменная частота кадров.
- Есть и другие неизвестные осложнения.
Переменная частота кадров в видео
Говоря о видео с переменной частотой кадров, можно подумать, что они могут понадобиться только путешественникам во времени или фокусникам.
Хотя они не очень распространены, похоже, есть вариант их использования, особенно с видеорегистраторами, производящими их. Мы предполагаем, что видеорегистраторы стараются сохранять кадры как можно быстрее; с различными процессами, привлекающими внимание ЦП видеорегистратора, это может означать, что иногда запись выполняется быстрее, а иногда медленнее.
Хорошо, они существуют, посмотрим позже, сможем ли мы их поддержать.
Другие неизвестные осложнения
Когда мы посмотрели, что еще может пойти не так, мы открыли банку червей настолько неприятных, что решили не добавлять сюда подходящую графическую гифку (пожалуйста).
Пока мы думали, что можем применить нашу волшебную формулу
timestamp_x = frame_x * (1 / frames_per_second)
когда нет переменной частоты кадров, мы быстро поняли, что разные медиаплееры в некоторых случаях могут отображать разное количество кадров или растягивать/укорачивать длину определенных кадров. Это особенно актуально для медиаплеера браузеров на базе Chromium (например, Google Chrome).
С помощью нашего друга FFmpeg мы можем проверить метаданные видео и метаданные каждого отдельного кадра в видео на нашем сервере. Таким образом, мы можем фактически проверить истинную временную метку, в которую должен воспроизводиться кадр. Однако мы не можем получить доступ к этим метаданным из элемента ‹video›. Мы также не можем воспроизвести ту же эвристику временных меток на нашем сервере, что и в браузере. Следовательно, существует проблема с синхронизацией кадров между внешним и внутренним интерфейсом.
Возможное решение
Что мы знаем на данный момент:
- В Chrome мы можем искать кадр видео только по отметке времени.
- Мы обнаружили, что Chrome иногда перемещается между кадрами с неправильными временными метками.
- Нам нужно убедиться, что метки хранятся с правильным номером кадра (как показывает FFmpeg на нашем сервере).
Короче, есть проблема, которую нужно решить. Рассмотрим возможные решения.
Распаковка видео в изображения
Одним из простых решений является использование нашего друга FFmpeg для распаковки видео в каталог изображений с номером кадра как частью изображения. Команда вроде
$ ffmpeg -i my-video.mp4 my-video-images/$filename%03d.jpg
сделает это возможным. Затем мы могли бы загружать изображения во внешний интерфейс, и когда пользователь перемещается по кадрам, мы отображаем соответствующее изображение.
Это может означать, что у нас может не быть надлежащей поддержки воспроизведения видео на нашем интерфейсе, из-за чего видео может казаться неуклюжим. Более серьезная проблема заключается в том, что мы взорвем размер хранилища видео на нашей платформе. Весь смысл видео заключается в сжатии изображений. 10-минутное видео, такое как этот в формате Full HD, занимает около 112 МБ памяти в виде видео, но требует 553 МБ памяти в виде извлеченных изображений.
Мы видим, что требования клиентов к хранилищу достигают терабайтов и продолжают расти, поэтому мы отказались от этого подхода.
Использование веб-функций, которые позволяют искать определенные кадры
Мы были не первой командой разработчиков, столкнувшейся с этой проблемой. Больше обсуждений вы можете прочитать здесь.
Учитывая разочарование других разработчиков, в настоящее время существуют экспериментальные веб-функции со своими преимуществами и недостатками. Некоторые из них
- Функция обратного вызова HTMLMediaElement.seekToNextFrame().
- Использование WebCodecs для более тонкого контроля.
- Функция обратного вызова HTMLVideoElement.requestVideoFrameCallback().
Если вы читаете эту статью и какая-либо из них получила надлежащую поддержку с гарантиями обратной совместимости API в браузере, мы предлагаем вам выполнить одно из следующих действий:
- Отпразднуйте, закройте эту статью и используйте одну из них для решения проблем с синхронизацией кадров.
- Отпразднуйте и продолжайте читать эту статью как урок истории.
Мы решили не использовать экспериментальные функции из-за возможных поломок API в новых версиях браузера. Выравнивание меток с правильными фреймами лежит в основе нашей платформы, и мы посчитали, что будет уместно более надежное решение.
Выяснение того, когда браузерный медиаплеер срабатывает, и соответствующая реакция
Спойлер: это то, что мы решили сделать, чтобы решить проблемы.
Мы чувствовали, что, выяснив, какой тип видео вызывает проблемы, мы а.) получим ценный опыт в мире видеоформатов и медиаплееров в нашей команде разработчиков и б.) сможем предложить информированное решение для наших клиентов, чтобы гарантировать синхронизация кадров в нашей платформе.
Чтобы быстрее находить проблемы, мы создали внутренний инструмент тестирования на основе браузера, который принимает два входа.
- количество кадров в секунду (fps) видео
- само видео
Затем он встраивает видео в элемент ‹video› и увеличивает временную метку очень маленькими шагами. На каждом шаге он делает снимок текущего отображаемого кадра и сравнивает его с предыдущим снимком экрана. Если изображения различаются, мы можем гарантировать, что браузер отображает новый кадр. Затем мы записываем отметку времени, когда это произошло.
Инструмент отмечает, соответствуют ли временные метки начала каждого кадра ожидаемым значениям частоты кадров видео.
Видео со звуком
Одна закономерность, которую мы постоянно наблюдали с помощью нашего инструмента тестирования, заключалась в том, что во многих видеороликах самый первый кадр отображался дольше, чем должен был.
Частота кадров около 23,98 кадров в секунду является распространенным видеостандартом. Это означает, что каждый кадр будет длиться около 0,0417 секунды, но во многих видео первый кадр длился 0,06305 секунды. Чуть более чем в 1,5 раза больше ожидаемой длины. Если первый кадр растянут, это означает, что все метки всего видео будут смещены на один кадр.
Мы проверили пакеты с помощью этой команды:
$ ffprobe -show_packets -select_streams v -of json video.mp4
и обнаружили, что для проблемных видео мы видим аудиокадры с отрицательной меткой времени. «Временная метка» здесь относится к времени, которое сообщается из метаданных видео из инструментов FFmpeg, а не к временной метке, которую мы ищем в элементе ‹video›. Если медиаплеер решит воспроизвести хотя бы часть негативных аудиокадров (и Chromium решит это сделать), он будет вынужден растянуть первый кадр на большее время, чем его обычное время отображения, чтобы отобразить кадр во время воспроизведения этих негативных аудиокадров.
Элемент ‹video› имеет атрибут muted, который мы пытались включить в нашем видеоплеере, чтобы Chrome не растягивал первый кадр. К сожалению, пока звук игнорировался, первый кадр все равно был растянут.
Затем мы попытались удалить звуковые кадры из видео с помощью
$ ffmpeg -i video-with-audio.mp4 -c:v copy -an video-no-audio.mp4
Все, что он делает, это копирует видеокадры и перетаскивает аудиокадры в новое видео с именем `video-no-audio.mp4`. Затем мы загрузили видео в наш инструмент тестирования и вуаля — проблема была исправлена, и кадры отображались с теми же временными метками, что и в строке «Ожидается».
Призрачные кадры
Мы придумали этот жуткий термин, когда наткнулись на видео, в метаданных которого мы нашли видеокадры с отрицательной меткой времени. Почему существуют такие видео? Это может произойти из-за обрезки видео, где ключевые кадры или инфракадры сохраняются от отрицательных временных меток. Подробнее о различных видах рамок можно прочитать в видео здесь.
При воспроизведении этого видео с помощью разных медиаплееров мы заметили, что все они решили отображать где-то между нулем и всеми кадрами с отрицательными временными метками.
Учитывая, что не было возможности детерминистически сказать, сколько из них было отображено, мы решили, что нам нужно будет удалить все эти кадры, перекодировав видео.
Перекодирование — это процесс распаковки видео во все его кадры изображения и повторной упаковки в видео. При этом вы также можете удалить поврежденные кадры, такие как кадры с отрицательными временными метками.
Для файлов mp4 эта команда может выглядеть так:
$ ffmpeg -err_detect aggressive -fflags discardcorrupt -i video.mp4 -c:v libx264 -movflags faststart -an -tune zerolatency re-encoded-video.mp4
- -err_detect Agressive сообщает нам обо всех ошибках с видео в целях отладки
- -fflags discardcorrupt удаляет поврежденные пакеты
- -c:v libx264 кодирует видео с помощью формата кодирования H.264 (используется для mp4)
- -movflags faststart рекомендуется для видео, воспроизводимых в браузере; он помещает метаданные видео в начало видео, поэтому воспроизведение может начаться сразу же после буферизации видео.
- -an для удаления звуковых кадров — на случай, если они будут проблематичными
- -tune zerolatency рекомендуется для быстрого кодирования.
Повторное кодирование видео с помощью этой команды удалит все кадры с отрицательными временными метками, устраняя эту проблему.
Переменная частота кадров
Возвращаясь к видео с переменной частотой кадров, мы увидели два варианта работы с ними:
- Передайте карту номера кадра в метку времени от бэкэнда к интерфейсу, чтобы найти правильное время.
- Перекодируйте видео, установив постоянную частоту кадров.
Учитывая наш опыт с некоторым неожиданным поведением в отношении видео со звуковыми кадрами или видео с фантомными кадрами, мы не хотели делать никаких предположений о том, что браузер не растягивает/сжимает длину кадров в видео с переменной частотой кадров. Поэтому мы остановились на варианте 2.
Мы использовали аналогичную команду перекодирования FFmpeg, описанную выше, только с добавленным флагом -vsync cfr. Это гарантирует, что FFmpeg вычислит разумную постоянную частоту кадров с учетом кадров, которые он видел ранее. Учитывая, что кадры с переменной скоростью необходимо сжать до постоянной, это означало, что некоторые кадры затем будут дублироваться или вообще отбрасываться. Компромисс, который мы сочли справедливым, учитывая, что мы можем обеспечить целостность данных.
Заключительные мысли
Напомним, что мы обнаружили проблему синхронизации кадров в том, как мы видим количество кадров в видео. В нашем бэкенде мы могли надежно просматривать метаданные видео с помощью FFmpeg, чтобы узнать точную временную метку кадра. В браузере мы могли искать кадр только по временной метке, но нам нужно было решить несколько проблем, когда браузер не мог надежно преобразовать временную метку в точный номер кадра в этом видео.
Оглядываясь назад, мы рады, что решили изучить проблемы синхронизации отдельных кадров, которые возникнут из разных видео, вместо того, чтобы просто распаковывать видео в изображения. Наша команда разработчиков теперь имеет четкое представление о стандартах кодирования видео, различном поведении медиаплееров, возможных проблемах с ними и о том, как найти правильные решения.
Теперь мы сообщаем клиентам о любых потенциальных проблемах заблаговременно и предлагаем им решение одним нажатием кнопки. Мы также можем дать им достаточно контекста о том, почему именно они должны перекодировать определенные видео.
Вы ищете надежную платформу для создания меток обучающих данных для ваших изображений или видео? Хотите уточнить что-то прочитанное в статье или поделиться собственным опытом работы с видео? В настоящее время вы ищете новую работу и хотели бы работать там, где будете решать такие же сложные задачи? Мы хотели бы, чтобы вы связались с нами через эту форму и пообщались с нами.