如何使用avcodec从OpenCV :: Mat类型的jpeg图像创建视频?

时间:2012-11-30 23:46:28

标签: c++ opencv video ffmpeg libavcodec

我有OpenCV::Mat类型的彩色jpeg图片,我使用avcodec从他们创建视频。我得到的视频是颠倒的,黑色和白色,每帧的每一行都移动了,我得到了对角线。这样的输出可能是什么原因? 按照this链接观看我使用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);
}

我现在获得的视频(here)几乎是完美的。这不是颠倒的,不是黑色和白色,但似乎缺少一个RGB组件。每种棕色/红色都变成蓝色(在原始图像中应该是反之亦然)。 可能是什么问题呢?可以重新调整(sws_scale)到AV_PIX_FMT_YUV420P格式会导致这个吗?

3 个答案:

答案 0 :(得分:2)

问题简而言之: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-&gt; linesize [0],avpicture_alloc()应该这样做。也没有必要填写数据[1]或数据[2],那些应该是空的。

编辑:删除旧代码,显示将R,G,B复制到单独的平面。 PIX_FMT_BGR24不是平面格式。

我不熟悉OpenCV C ++ API来弄清楚如何获得宽度和高度(显然不是mat宽度),但我想你知道我的意思。

P.S。顺便说一句,您的视频实际上黑白。只是每个连续的行偏移两个字节,因此颜色会旋转:红色变为绿色,绿色变为蓝色,依此类推。结果是灰度等级,但如果仔细观察,各行都会被着色。

答案 1 :(得分:0)

您是否考虑过使用OpenCV's features为您制作视频?由于您的数据已存储在cv::Mat中,因此更加容易。

如果您想保留自己的方法,可以rotate the cv::Mat

答案 2 :(得分:0)

关于原始帖子的UPDATE中的颜色问题。这是由,

引起的

OpenCV Mat是(BGR) - &gt; FFmpeg AVFrame是(RGB)?

如果是,请尝试

cvtColor( cvFrame , cvFrame , CV_BGR2RGB ) ; 

在第1行之前。