Как создать видео с помощью avcodec из изображений jpeg типа OpenCV::Mat?

У меня есть цветные jpeg-изображения типа OpenCV::Mat, и я создаю из них видео, используя avcodec. Видео, которое я получаю, перевернутое, черно-белое, и каждая строка каждого кадра смещена, и у меня есть диагональная линия. В чем может быть причина такого вывода? Перейдите по этой ссылке, чтобы посмотреть видео, которое я получаю с помощью avcodec. Я использую функцию acpicture_fill для создания avFrame из cv::Mat кадра!

P.S. Каждый cv::Mat cvFrame имеет ширину = 810, высоту = 610, шаг = 2432. Я заметил, что avFrame (который заполняется acpicture_fill) имеет linesize[0]=2430. Я пытался вручную установить avFrame->linesizep0]=2432, а не 2430, но это все равно не помогло.

======== КОД ===================================== ===================

AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
AVStream *outStream = avformat_new_stream(outContainer, encoder);
avcodec_get_context_defaults3(outStream->codec, encoder);

outStream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
outStream->codec->width = 810;
outStream->codec->height = 610;
//...

SwsContext *swsCtx = sws_getContext(outStream->codec->width, outStream->codec->height, PIX_FMT_RGB24,
                                    outStream->codec->width, outStream->codec->height,  outStream->codec->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);

for (uint i=0; i < frameNums; i++)
{
    // get frame at location I using OpenCV
    cv::Mat cvFrame;
    myReader.getFrame(cvFrame, i); 
    cv::Size frameSize = cvFrame.size();    
    //Each cv::Mat cvFrame has  width=810, height=610, step=2432


1.  // create AVPicture from cv::Mat frame
2.  avpicture_fill((AVPicture*)avFrame, cvFrame.data, PIX_FMT_RGB24, outStream->codec->width, outStream->codec->height);
3avFrame->width = frameSize.width;
4.  avFrame->height = frameSize.height;

    // rescale to outStream format
    sws_scale(swsCtx, avFrame->data, avFrame->linesize, 0, outStream->codec->height, avFrameRescaledFrame->data, avFrameRescaledFrame ->linesize);
encoderRescaledFrame->pts=i;
avFrameRescaledFrame->width = frameSize.width;
    avFrameRescaledFrame->height = frameSize.height;

av_init_packet(&avEncodedPacket);
    avEncodedPacket.data = NULL;
    avEncodedPacket.size = 0;

    // encode rescaled frame
    if(avcodec_encode_video2(outStream->codec, &avEncodedPacket, avFrameRescaledFrame, &got_frame) < 0) exit(1);
    if(got_frame)
    {
        if (avEncodedPacket.pts != AV_NOPTS_VALUE)
            avEncodedPacket.pts =  av_rescale_q(avEncodedPacket.pts, outStream->codec->time_base, outStream->time_base);
        if (avEncodedPacket.dts != AV_NOPTS_VALUE)
            avEncodedPacket.dts = av_rescale_q(avEncodedPacket.dts, outStream->codec->time_base, outStream->time_base);

        // outContainer is "mp4"
        av_write_frame(outContainer, & avEncodedPacket);

        av_free_packet(&encodedPacket);
    }
}

ОБНОВЛЕНО

Как предложил @Alex, я изменил строки 1-4 с кодом ниже

int width = frameSize.width, height = frameSize.height; 
avpicture_alloc((AVPicture*)avFrame, AV_PIX_FMT_RGB24, outStream->codec->width, outStream->codec->height);
for (int h = 0; h < height; h++)
{
     memcpy(&(avFrame->data[0][h*avFrame->linesize[0]]), &(cvFrame.data[h*cvFrame.step]), width*3);
}

Видео (здесь) почти идеальное. Это НЕ перевернутое, НЕ черно-белое, НО кажется, что один из компонентов RGB отсутствует. Каждый коричневый/красный цвет стал синим (в исходных изображениях должно быть наоборот). В чем может быть проблема? Может ли это быть вызвано масштабированием (sws_scale) до формата AV_PIX_FMT_YUV420P?


person theateist    schedule 30.11.2012    source источник


Ответы (3)


Вкратце проблема: avpicture_fill() не ожидает заполнения между строками, т.е. шаг (шаг) будет равен width*sizeof(pixel), т.е. 810*3 = 2430. Фактический шаг данных в cv::Mat step, как вы говорите, равен 2432, что отличается, поэтому просто передать данные напрямую не получится. Невозможно указать avpicture_fill() использовать другой шаг для входных данных; это не часть API (можно сказать, что должно быть :)

Есть два возможных решения:

Создайте массив, в котором входные данные являются непрерывными, без заполнения между строками. Вам придется копировать каждую строку из cv::Mat в этот массив. Затем передайте его avpicture_fill().

int width, height; // get from mat
uint8_t* buf = malloc(width * height * 3); // 3 bytes per pixel
for (int i = 0; i < height; i++)
{
    memcpy( &( buf[ i*width*3 ] ), &( mat->data[ i*mat->step ] ), width*3 );
}
avpicture_fill(..., buf, ...)

Кстати, чтобы перевернуть видео по вертикали, вы можете сделать это, чтобы скопировать последнюю строку в первую и так далее:

...
    memcpy( &( buf[ i*width*3 ] ), &( mat->data[ (height - i - 1)*mat->step ] ), width*3 );
...

Или заполните AVPicture самостоятельно:

AVPicture* pic = malloc(sizeof(AVPicture));
avpicture_alloc(pic, PIX_FMT_BGR24, width, height);
for (int i = 0; i < height; i++)
{
    memcpy( &( pic->data[0][ i*pic->linesize[0] ] ),  &( mat->data[ i*mat->step ] ), width*3);
}

Нет необходимости выделять pic->data[0] или устанавливать pic->linesize[0], это должна делать avpicture_alloc(). Также нет необходимости заполнять данные [1] или данные [2], они должны быть нулевыми.

EDIT: Удален старый код, показывающий копирование R, G, B в разные плоскости. PIX_FMT_BGR24 не является плоским форматом.

Я недостаточно знаком с OpenCV C++ API, чтобы понять, как получить ширину и высоту (очевидно, это не мат-> ширина), но я думаю, вы понимаете, что я имею в виду.

P.S. Кстати, ваше видео на самом деле не черно-белое. Просто каждая последующая строка смещается на два байта, поэтому цвета чередуются: красный становится зеленым, зеленый становится синим и так далее. Результат имеет оттенки серого, но если вы внимательно посмотрите, отдельные строки окрашены.

person Alex I    schedule 01.12.2012
comment
Я так понимаю, что мне нужно выделить буфер для pic->data[0] и для pic->data[1] и pic->data[2] поровну на сколько? а pic->linesize[0] тоже 0, надо ли туда ставить 2430? - person theateist; 02.12.2012
comment
На самом деле я не совсем уверен, как avpicture_fill() заполняет изображение, когда формат BGR24. Пожалуйста, смотрите редактирование выше. - person Alex I; 02.12.2012
comment
Вам не нужно выделять pic-›data[0] или устанавливать размер строки, это должен сделать avpicture_alloc(). Это все для второго метода в любом случае. Сначала попробуйте первый метод (скопируйте данные в buf без заполнения и вызовите avpicture_fill). - person Alex I; 02.12.2012
comment
это не BGR24, а RGB24. Моя ошибка. Я обновил свой пост. попробую первый способ - person theateist; 02.12.2012
comment
Я попробовал то, что вы предложили, и это почти удалось. Проблема в том, что каждый коричневый/красный цвет становился синим (в исходных изображениях должно быть наоборот). У тебя есть идеи? Я обновляю свой пост и прикрепляю ссылку на полученное видео(picasaweb.google.com/103161760482140400348/< /а>) - person theateist; 02.12.2012
comment
@theateist: Хорошо, приближаемся :) Если вы вызываете avpicture_fill и sws_getContext с форматом RGB, вместо этого вызывайте оба с BGR (оставив все остальное без изменений). Если вы звоните с BGR, попробуйте RGB. Это поменяет местами красный и синий. - person Alex I; 02.12.2012
comment
Это помогло! Я изменил avpicture_alloc и sws_getContext на PIX_FMT_BGR24, и это помогло, но почему? Означает ли это, что изображение в формате jpeg было закодировано с помощью BGR, а не RGB? Второй вопрос: где вы прочитали, что avpicture_fill не ожидает отступов между строками? - person theateist; 03.12.2012
comment
@theateist: jpeg обычно хранится как YCbCr, но кажется, что когда вы создаете cv::Mat из jpeg, результатом является BGR. Для avpicture_fill в этот документ говорит всегда принимать выравнивание по размеру строки равное 1, что я интерпретировал как отсутствие обивка. Кроме того, нет возможности указать какой-либо конкретный размер строки/отступы при передаче данных в avpicture_fill, поэтому по умолчанию не будет отступов. Пожалуйста, не забудьте принять мой ответ и проголосовать за него :) Спасибо! - person Alex I; 03.12.2012
comment
Если бы я мог, я бы дал вам медаль за ваши объяснения! - person theateist; 03.12.2012

Рассматривали ли вы возможность использования функций OpenCV для создания видео? Это намного проще, так как ваши данные уже хранятся в файле cv::Mat.

Если вы хотите сохранить свой подход, вы можете просто повернуть cv::Mat.

person karlphillip    schedule 01.12.2012
comment
Я не могу использовать OpenCV для создания видео. Я должен использовать avcodec для этого. Я пытался использовать поворот, но это не исправило. Я все еще получаю черно-белое видео и диагональные линии. Я думаю, это что-то с шагом - person theateist; 01.12.2012

О проблеме с цветом в ОБНОВЛЕНИИ оригинального поста. Это вызвано тем,

OpenCV Mat (BGR) -> FFmpeg AVFrame (RGB)?

Если да, то попробуй

cvtColor( cvFrame , cvFrame , CV_BGR2RGB ) ; 

перед строкой 1.

person maythe4thbewithu    schedule 09.09.2013