LINUX.ORG.RU

Ищу пример кода как выдрать n-кадр из видео

 ,


6

6

Задача вроде примитивная: получить произвольный кадр из видео. Но не тут-то было.

1) Половина примеров из инета или не собираются или сыпят кучу варнингов о deprecated методах (у меня ffmpeg 3).

2) То, что работает - работает или неверно или медленно. Код из kde-ffmpegthumbs работает реактивно, но у него шаг огромный. Сделать более точное «перемещение» так и не удалось.

Нужны всего две функции: количество_кадров() и получить_кадр().

Сомневаюсь, что это уж такая титаническая задача, но на просторах инета ничего толкового не нашел.

Где уже смотрел: mpenkov/ffmpeg-tutorial, kde-ffmpegthumbs, qtffmpegwrapper, opencv (модуль CvCapture_FFMPEG, его пока и использую, но уж очень медленный он)

PS: нужен поиск именно по кадру, а не по времени.

★★★★★

Код из kde-ffmpegthumbs работает реактивно, но у него шаг огромный. Сделать более точное «перемещение» так и не удалось.

Вангую что они только I-кадры достают, чтобы сделать произвольный кадр придется раскодировать все кадры последовательно от ближайшего I-кадра

annulen ★★★★★
()
Ответ на: комментарий от annulen

Там и так и так можно. Меня устроят и ключевые. Но в том коде у них по секундам идёт отсчёт. То есть шаг в 1сек. Для меня это слишком много.

RazrFalcon ★★★★★
() автор топика
Последнее исправление: RazrFalcon (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

А там никак иначе. Насколько я знаю, там есть опорные кадры, т.е. те, которые грубо говоря картинкой сохранены, а потом некоторое количество кадров, записанных лишь в виде изменения. Т.е. если тебе нужен не опорный кадр, то придется с ближайшего опорного все изменения применять.

peregrine ★★★★★
()

Чисто догадка: Открываете формат контекст, берете видео поток читаете пакеты (кадры) и складываете их в очередь/список. При нахождении ключевого кадра (AV_PKT_FLAG_KEY) очередь можно смело дропать. При получении нужного кадра N декодируете всю очередь накопившихся пакетов - последний фрейм от декодера и будет вашей картинкой.

UPD - если нужен один большой файл со всего потока, то можно погуглить как через libav работать с индексами (если они присутствуют в контейнере) но лично я в этом направлении никогда не работал.

zaz ★★★★
()
Последнее исправление: zaz (всего исправлений: 1)
Ответ на: комментарий от zaz

UPD2 - в некоторых случаях вам предется «cкормить» декодеру больше пактов чтоб добраться до нужного (напрмемер вам нужен 10 фрейм после ключевого, в некоторых случаях декодер сможет его декодировать только после получения 20 паката после ключевого)

zaz ★★★★
()

То, что работает - работает или неверно или медленно.

Потому, что в видеопотоке нужно найти ключевой кадр, а потом от него построить требуемый кадр. А ключевые кадры в зависимости от кодировавшего могут быть редкими.

Код из kde-ffmpegthumbs работает реактивно, но у него шаг огромный.

Видимо потому, что он строит превьюхи по ключевым кадрам.

andreyu ★★★★★
()
Ответ на: комментарий от peregrine

Это да, но ffmpeg абстрагирует от этого, при желании.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от zaz

Это слишком низкоуровневый подход. av_seek_frame + av_read_frame работают уровнем выше.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от andreyu

Да, от кодека, настроек кодирование производительность очень зависит. Но тот же mpv листает как реактивный, а моя прога - нет.

RazrFalcon ★★★★★
() автор топика

Как я понимаю, нужно через av_seek_frame прыгнуть на нужный кусок видео, а потом через av_read_frame уже более точную позицию получить. То есть av_seek_frame + av_read_frame * n.

Проблема в том, что у меня av_seek_frame не работает, что ли, ибо он почти не «двигает» ткущий кадр, и в итоге av_read_frame вызывается почти n раз, из-за чего всё тормозит, ибо, как уже писали выше, нужно последовательно читать кадры.

В инете есть целые статьи чисто по av_seek_frame, ибо он слишком запутанный. Но так как у меня опыта работы с ffmpeg 0, оно мне мало что даёт.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

В идеале нужно нормальная перемотка. То есть если мне нужно получить предыдущий 100-й кадр, то мне не нужно с начала начинать, а можно просто откатится назад на n кадров.

Но я хз как работает av_seek_frame, начинает он с нуля или нет.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от peregrine

по дефолту не будет использовать, нужно попердолиться, чтоб его задействовать

Harald ★★★★★
()
Ответ на: комментарий от RazrFalcon

Меня устроят и ключевые. Но в том коде у них по секундам идёт отсчёт. То есть шаг в 1сек. Для меня это слишком много.

Ключевые кадры обычно ставят один на сотню, т.е. раз в 4 секунды. Это для потокового видео. Короче, тебе в любом случае придётся декодировать всё.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Разве? Или я путаю key-frames с i-frames? Насколько я помню, у h264 максимум 16 b-frames может быть, соответственно i-frame'ы должны быть довольно близко.

Нужно разбираться...

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от anonymous

У меня нет задач обработки видео. Мне нужно просто выдрать кадр.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Разве? Или я путаю key-frames с i-frames? Насколько я помню, у h264 максимум 16 b-frames может быть, соответственно i-frame'ы должны быть довольно близко.

I-frame - ключевой кадр. P-frame - неполный кадр, зависящий от предыдущего. B-frame - неполный кадр, зависящий от предыдущего I или P и следующего P.

hateyoufeel ★★★★★
()
Ответ на: комментарий от peregrine

Ещё раз: mpv листает как реактивный и не использует GPU.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Затупил. Тогда это всё объясняет.

Осталось до конца разобраться с av_seek_frame и всё.

RazrFalcon ★★★★★
() автор топика

У mplayer есть опция дампить каждый кадр в отделный файл. Можно ей воспользоваться, а потом взять нужный кадр.

Xenius ★★★★★
()
Ответ на: комментарий от RazrFalcon

av_seek_frame + av_read_frame * n

Можно же сразу спозиционироваться на время этого n-го кадра?

    time_base = (uint64_t)(video_dec_ctx->time_base.num) * AV_TIME_BASE / (uint64_t)(video_dec_ctx->time_base.den);
                                                                                
    /* seek */                                                                  
    if (av_seek_frame(fmt_ctx, -1, (uint64_t)FRAME_NUMBER * time_base, AVSEEK_FLAG_ANY) < 0) {
        printf("can't seek!\n");                                                
    }

    /* читаем кадр */
    if (av_read_frame(fmt_ctx, &pkt) >= 0) {
        /* ... */
    }

FRAME_NUMBER - нужный кадр.

Или так мимо попадает? Вроде бы должно работать. По крайней мере спозиционировался без ошибки, в кадре в зависимости от номера какие-то данные меняются (смотрел прямо на байтики, рендерить картинку лень).

Deleted
()
Ответ на: комментарий от Deleted

AVSEEK_FLAG_ANY выдаёт мусор. С AVSEEK_FLAG_BACKWARD нормально, но шаг сказочный. Оно моё видео пролистывает за 300 кадров, хотя AVStream::nb_frames выдаёт 5400 кадров. Видимо чисто по ключевым двигается, хз. time_base получается 16683 на тестовом видео.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

AVSEEK_FLAG_ANY выдаёт мусор

Жаль. Хотя да, это было бы слишком просто.

Получается, ты шел в правильном направлении, надо позиционироваться на ближайший фрейм и читать. Для твоего видео 5400/300 = 18 кадров нужно дочитывать в худшем случае. В общем случае может и больше, но все равно должно быть достаточно быстро

Deleted
()
Ответ на: комментарий от RazrFalcon

Делал простенький видеоплеер, который умеет играть .h264 видео и поддерживает перемотку. В том числе и покадровую.

Вот ссылка на функцию, которая прыгает к нужному кадру. Номер нужного кадра задается в качестве pts-а (это, насколько я помню, тупо порядковый номер кадра в видеопотоке).

Алгоритм тупой: прыгнуть на 2 секунды назад от требуемого кадра, найти опорный I-frame и от него додекодировать поток уже до нужного места. Не знаю, насколько оно правильно с точки зрения ffmpeg'a, но на практике работало.

P.S. я еще не погромист, поэтому сильно смеяться не нужно https://pastebin.com/8BFLyP0W

s3rjke
()
Ответ на: комментарий от s3rjke

Все правильно делал. Только ПТС - это Пресентейшен Таймштамп, т.е. время когда кадр показывать. Иначе не выйдет.

ruzisufaka
()

Задача вроде примитивная: получить произвольный кадр из видео. Но не тут-то было.

Хотя звучит примитивно, на самом деле решение в лоб невозможно: Provide an option to quickly seek by a number of frames. Разве что, как упомянули выше (и по ссылке), в видео-файле есть полный индекс.

Так что придётся ухищряться. По идее, если FPS - целое число, и случайно где-то не ошибиться с округлением, то даже после перемотки туда-сюда и декодирования всё равно можно быть уверенным в точном номере кадра. Интересно, а что когда FPS 23.976 или 29.97?

gag ★★★★★
()
Ответ на: комментарий от RazrFalcon

Но тот же mpv листает как реактивный

Я тоже поражаюсь - вот бы автор написал просмотрщик jpeg'ов. А то получается, что по видео можно значительно быстрее пробежаться, чем по куче jpeg'ов.

Вообще меня эта проблема с кадрами достаёт каждый раз, когда я сохраняю скриншот. Даже в документации к mpv написано: пользуйтесь временем, а не номерами кадров.

%p     Current playback time, in the same format as used in  the  OSD.
       The  result is a string of the form «HH:MM:SS». For example, if
       the video is at the time position 5 minutes and 34 seconds,  %p
       will be replaced with «00:05:34».

%P     Similar to %p, but extended with the playback time in millisec‐
       onds.  It is formatted as «HH:MM:SS.mmm», with «mmm» being  the
       millisecond part of the playback time.

         NOTE:
           This  is  a  simple  way  for getting unique per-frame time‐
           stamps. (Frame numbers would be more intuitive, but are  not
           easily  implementable  because container formats usually use
           time stamps for identifying frames.)
И вот непонятно: если используя время можно вычислить точный номер кадра, то почему это ещё не реализовано в mpv/ffmpeg. Может там какие-то подводные камни даже при целом значении FPS?

gag ★★★★★
()
Ответ на: комментарий от gag

Без понятия. Но mpv работает поверх ffmpeg, так что все претензии к ffmpeg.

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

RazrFalcon ★★★★★
() автор топика

Случайно нашел шикарнейший пример: https://docs.thefoundry.co.uk/nuke/80/ndkreference/examples/ffmpegReader.cpp

Всё по полочкам разложено. Переделал под свои нужды, но увы, этот метод ещё медленнее того, который я стырил из opencv. Зато типа самый надёжный.

Судя по дебагу этого алгоритма - всё происходит именно так, как тут многие и предполагали: прыгаем на ближайший ключевой кадр (это очень быстро), а затем декодируем нужное количество кадров до нужного нам кадра (очень медленно).

У меня на тестовом, h264, видео среднее время поиска кадра - 600мс, что ппц. И в худшем случае я декодирую до 40 кадров (пока не дойду до нужного).

gag vmx peregrine zaz

RazrFalcon ★★★★★
() автор топика
Последнее исправление: RazrFalcon (всего исправлений: 2)
Ответ на: комментарий от RazrFalcon

Потыкал mpv на этом же файле - он чисто по ключевым кадрам прыгает, поэтому и так быстро листает.

RazrFalcon ★★★★★
() автор топика

сыпят кучу варнингов о deprecated методах

А что вы хотели? FFmpeg пишется мудаками, у которых раз в полгода чешется задний проход и они меняют API.

Ну, уже о ключевых кадрах и дельте и без меня сказали.

a1batross ★★★★★
()
Ответ на: комментарий от RazrFalcon

Не, всё правда очень плохо.

Мне уже порой кажется, что в продуманные и стабильные API почти никто из опенсорца не умеет, зато умеют те, у кого каждая минимальная смена API обойдётся в некоторую сумму. :)

a1batross ★★★★★
()
Ответ на: комментарий от RazrFalcon

но увы, этот метод ещё медленнее того, который я стырил из opencv. Зато типа самый надёжный.

А насколько медленный метод из OpenCV в числах? А как проверяешь результат?

а затем декодируем нужное количество кадров до нужного нам кадра (очень медленно).

h264 1080p декодируется в реальном времени, и видно, что нагрузка на проц менее 100% (intel core 2nd gen. мобильный). Так что могу предположить, что в секунду можно осилить до 40 кадров (если только там битрейт не чересчур высокий). А на оборудовании поновее чем 5-летней давности и того больше. А менее 1-ой секунды, чтобы получить из любого видео любой кадр, кажется мне довольно быстрым. Зависит конечно от задачи, но чудо не может произойти только потому, что этого требует задача.

gag ★★★★★
()
Ответ на: комментарий от gag

А насколько медленный метод из OpenCV в числах?

Относительно чего? У них там кода раза в 5 меньше, чем по ссылке выше (реализация seek в opencv). Но я или криво её портировал или хз, но у меня она глючила немного. Не было чёткого позиционирования по кадрам.

А как проверяешь результат?

QElapsedTimer

h264 1080p декодируется в реальном времени

Я в курсе. Но это последовательно.

А менее 1-ой секунды, чтобы получить из любого видео любой кадр, кажется мне довольно быстрым.

Мне надо <20ms

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Относительно чего?

Найденного ffmpegReader.cpp

А как проверяешь результат?

QElapsedTimer

Я о том, что выдранный кадр имеет ровно нужный номер. Для 100% точности надо бы с самого начала видео покадрово декодировать и считать. А какая точность требуется: ничего если +-1 кадр?

Я в курсе. Но это последовательно.

Точно так же как и декодирование после найденного ключевого кадра. А именно в этом и бутылочное горлышко.

Мне надо <20ms

Т.е. в перерасчёте со скоростью не менее 50 кадров в секунду, игнорируя наличие промежуточных кадров, что на практике невозможно. Очень сомневаюсь, что это удастся на стандартном оборудовании.

gag ★★★★★
()
Ответ на: комментарий от RazrFalcon

Кстати, эта программка - работа и/или будет открыта?

gag ★★★★★
()
Ответ на: комментарий от gag

Найденного ffmpegReader.cpp

Я бенчей не делал. Чисто на глаз opencv быстрее. Но позиционирование немного хромает. У них там дельта используется, видимо из-за этого он иногда промахивается.

Для 100% точности надо бы с самого начала видео покадрово декодировать и считать.

Нет. Пример выше даёт 100% точность. По крайней мере у меня.

+-1 кадр - не страшно.

А именно в этом и бутылочное горлышко.

Да. Только плеер никуда не спешит, ему 30 кадров в секунду(условно) надо сделать, а мне нужно 10-60 за <20ms.

Очень сомневаюсь, что это удастся на стандартном оборудовании.

Заказчик не бум-бум. Ему нужно быстро.

Кстати, эта программка - работа и/или будет открыта?

Проприетарщина. Но часть связанная с ffmpeg почти полностью совпадает с примером выше. Разве что у меня игнорируется видео с переменным/не заданным fps. Ну и оно ничего не знаю о интерлейс-видео, анаморфном видео и тд (это, кстати, реализовано в kde-ffmpegthumbs).

Я думал запилить класс-либу и залить на гитхаб. Но универсальный вариант займет под 4к строк. Да и не спец я в этом вопросе.

Я молчу про то, что у меня большая часть методов помечено deprecated и портировать на новый api я не осилю.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon
ffprobe -select_streams v -show_frames -i file.avi

Выдаст все кадры, покажет оффсет в байтах, тип и много чего еще. Вот эти данные, раскурив исходники ффпробы, можно получать и сохранять в качестве индексов, тогда будет быстро. Этим же самым занимается xjadeo, но он как-то в два прохода делает.

deep-purple ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.