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

标签 c++ opencv video ffmpeg libavcodec

我有 OpenCV::Mat 类型的彩色 jpeg 图像,并使用 avcodec 从它们创建视频。我得到的视频是颠倒的,黑白的,每帧的每一行都会移动,我得到了对角线。这样的输出可能是什么原因? 关注 this观看我使用 avcodec 获得的视频的链接。 我正在使用 acpicture_fill 函数从 cv::Mat 帧创建 avFrame!

附注 每个 cv::Mat cvFrame 的宽度=810,高度=610,步长=2432 我注意到 avFrame (由 apicture_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 格式会导致此问题吗?

最佳答案

问题简而言之:avpicture_fill() 期望行之间没有填充,即步幅(step)等于width*sizeof(pixel),即810*3 = 2430。正如你所说,cv::Mat步骤中数据的实际步长是2432,这是不同的,所以直接传递数据是行不通的。无法告诉 avpicture_fill() 对输入数据使用不同的步幅;它不是 API 的一部分(你可能会说它应该是:)

有两种可能的解决方案:

创建一个数组,其中输入数据是连续的,行之间没有填充。您必须将 cv::Mat 中的每一行 memcopy 到该数组中。然后将其传递给 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() 应该这样做。 data[1]或data[2]也不需要填写,应该为空。

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

我对 OpenCV C++ API 不太熟悉,无法弄清楚如何获取宽度和高度(显然,它不是 mat->width),但我想你知道我的意思。

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

关于c++ - 如何使用 avcodec 从 OpenCV::Mat 类型的 jpeg 图像创建视频?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13654789/

相关文章:

c++ - Cuda 基本程序(将值写入矩阵和 std :cout does not work) ; Main function does not start

opencv - 我在哪里可以找到与OpenCV一起使用的面部基准点

c# - OpenCv:查找多个匹配项

animation - 无损视频压缩格式

c++ - 如何在 C++ 中的两个类之间定义(重载)对称二元运算符,同时考虑 r 值?

c++ - 绘图区 : fill area outside a region

c++ - 我在哪里可以学习有关C++编译器的 “what I need to know”?

c - 使用 openCv 和 c 保存图像

javascript - Video.js 5 中的 showTextTrack

android - 在 Android 中优化绘画视频编码