FFmpeg: Извлечь несколько отдельных кадров из видео, имея список временных меток?

Вопрос или проблема

Предположим, у меня есть список временных меток, например, 1.4, 5.6, 10.5 и т.д.

Для каждой временной метки я хочу извлечь ближайший кадр и сохранить его в файл формата jpeg.

Я могу сделать это по одному кадру за раз:

ffmpeg -i video.mp4 -ss 1.4 -frames 1 frame_1_4.jpg
ffmpeg -i video.mp4 -ss 5.6 -frames 1 frame_5_6.jpg
ffmpeg -i video.mp4 -ss 10.5 -frames 1 frame_10_5.jpg
...

Проблема с таким подходом заключается в том, что он требует много ресурсов по нескольким причинам: запуск ffmpeg снова и снова создает накладные расходы, а если видео имеет удаленные ключевые кадры (или, в худшем случае, вообще не имеет их), то декодер должен декодировать несколько кадров для каждого вызова. Если предположить худший случай (нет подходящих ключевых кадров), то наиболее эффективным способом извлечь кадры будет декодирование потока один раз и выбор каждого кадра по мере его появления. Для всего трех кадров это может быть не слишком сложно, но если я хочу извлечь список, скажем, из 100 или 200 кадров или более, это быстро становится неудобным, даже для файлов с хорошими ключевыми кадрами.

Очень примитивный тест показал, что для тестового файла длиной пять минут с ключевыми кадрами каждые 5 секунд каждый вызов ffmpeg занимает в среднем около 0.22 секунды, самый быстрый был 0.11, а самый медленный – 0.34 секунды. Тот же файл можно декодировать в ноль всего за 6 секунд. Следовательно, если я хочу, скажем, 40 кадров из этого файла, мне придется ждать максимум 12 секунд, в то время как общее время декодирования файла составляет половину от этого. Это говорит мне о том, что большая часть накладных расходов заключается в инициализации ffmpeg, открытии файла и т.д.

Очень наивный подход может заключаться в том, чтобы извлечь все кадры на диск, а затем выбрать нужные. Конечно, это даже медленнее, чем просто многократно запускать ffmpeg, поскольку кодировщик JPEG работает снова и снова, и необходимое дисковое пространство может быстро стать чрезмерным.

Существует ли, возможно, фильтр vf select или аналогичный фильтр, который может выбрать конкретный список временных меток, который я предоставляю, и извлечь только эти кадры?

(Для уточнения: имена файлов с временными метками не обязательно должны генерироваться с помощью ffmpeg; просто последовательные номера подойдут, поскольку вызываемое приложение/скрипт может просто переименовать файлы самостоятельно на основе того же списка временных меток)

Если вы можете преобразовать временные метки в номера кадров – т.е. 100, 184, 213 – тогда вы можете сделать:

ffmpeg -i video.mp4 -vf select="eq(n\,100)+eq(n\,184)+eq(n\,213)" -vsync 0 frame_%d.jpg

(Кажется, -vsync 0 важно для получения правильных кадров)

И потребуется какой-то скрипт для получения списка и генерации строки для select=.

Скрипт может даже получить временные метки и FPS для вычисления номеров кадров.


На основе вопроса из Stack Overflow: shell – Извлечение списка конкретных кадров с помощью ffmpeg


В конечном итоге я бы использовал Python и модуль cv (OpenCV), потому что он может считывать кадры по одному (вероятно, используя ffmpeg) из видеофайла (или веб-камеры, или потока) и сохранять кадр в виде изображения.


ИЗМЕНЕНИЕ:

Документация для select показывает, что вы можете использовать переменную t как timestamp
следовательно, это может работать непосредственно с 1.4, 5.6, 10.5

ffmpeg -i video.mp4 -vf select="eq(t\,1.4)+eq(t\,5.6)+eq(t\,10.5)" -vsync 0 frame_%d.jpg

Ответ или решение

Для извлечения нескольких отдельных кадров из видео по заданному списку временных меток с использованием FFmpeg можно воспользоваться фильтром select. Этот метод значительно более эффективен по сравнению с повторным запуском FFmpeg для каждой метки времени, так как позволит декодировать видео только один раз.

Шаг 1: Подготовка команд

Чтобы извлечь кадры из видео, вам нужно будет использовать фильтр select, который позволяет выбирать определенные кадры по их временным меткам. Вот пример команды для извлечения кадров по временным меткам 1.4 с, 5.6 с и 10.5 с:

ffmpeg -i video.mp4 -vf "select='eq(t\,1.4)+eq(t\,5.6)+eq(t\,10.5)'" -vsync 0 frame_%d.jpg

Пояснение параметров:

  • -i video.mp4: указывает входной файл, который мы хотим обрабатывать.
  • -vf "select='eq(t\,1.4)+eq(t\,5.6)+eq(t\,10.5)'": это основной фильтр, который выбирает кадры на основе заданных временных меток. Обратите внимание на экранирование запятой с помощью символа обратного слэша (\,).
  • -vsync 0: необходимый параметр, который обеспечивает корректное извлечение и сохранение кадров.
  • frame_%d.jpg: шаблон для именования выходных файлов, где %d будет заменен на номер кадра.

Шаг 2: Автоматизация процесса

Если у вас есть длинный список временных меток (например, 100 или более), разработка скрипта для автоматического формирования строки фильтра select будет крайне полезной.

Пример на Bash:

timestamps=(1.4 5.6 10.5)  # ваш список временных меток
select_filter=""

for ts in "${timestamps[@]}"; do
    select_filter+="eq(t\\,$ts)+"
done

# Удаление последнего '+' из строки
select_filter=${select_filter%+}

# Выполнение команды
ffmpeg -i video.mp4 -vf "select='$select_filter'" -vsync 0 frame_%d.jpg

Шаг 3: Вывод результатов

Файлы с изображениями будут сохраняться в текущем каталоге, и их имена будут начинаться с frame_, следуемые порядковым номером. Выбранные кадры будут представлять собой JPEG изображения, соответствующие указанным временным меткам.

Альтернативный подход с использованием OpenCV

Если вы предпочитаете программный подход, вы можете использовать Python и библиотеку OpenCV для извлечения кадров:

import cv2

video_path = 'video.mp4'
timestamps = [1.4, 5.6, 10.5]
frames = []

cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)

for ts in timestamps:
    frame_number = int(ts * fps)
    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
    ret, frame = cap.read()
    if ret:
        frames.append(frame)
        cv2.imwrite(f'frame_{int(ts * 10)}.jpg', frame)

cap.release()

Этот код позволяет легко извлекать кадры, используя временные метки и сохранять их в нужном формате.

Заключение

Использование фильтра select в FFmpeg или Python с OpenCV для извлечения кадров из видео по временным меткам значительно упрощает процесс и минимизирует использование ресурсов системы. Это эффективные методы, которые могут быть адаптированы для работы с большими объемами данных.

Оцените материал
Добавить комментарий

Капча загружается...