首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何将webrtc::VideoFrame转换为C++中的C++ Mat

如何将webrtc::VideoFrame转换为C++中的C++ Mat
EN

Stack Overflow用户
提问于 2022-03-24 17:53:42
回答 1查看 469关注 0票数 2

我正在尝试使用OpenCV imshow()显示接收到的OpenCV帧。WebRTC将帧作为webrtc::VideoFrame的对象交付,在我的例子中,我可以从它访问webrtc::I420Buffer。现在我的问题是如何将webrtc::I420Buffer中的数据转换为cv::Mat,以便将其交给imshow()

这就是webrtc::I420Buffer的定义

代码语言:javascript
运行
复制
namespace webrtc {

// Plain I420 buffer in standard memory.
class RTC_EXPORT I420Buffer : public I420BufferInterface {
 public:
  ...

  int width() const override;
  int height() const override;
  const uint8_t* DataY() const override;
  const uint8_t* DataU() const override;
  const uint8_t* DataV() const override;

  int StrideY() const override;
  int StrideU() const override;
  int StrideV() const override;

  uint8_t* MutableDataY();
  uint8_t* MutableDataU();
  uint8_t* MutableDataV();

  ...

 private:
  const int width_;
  const int height_;
  const int stride_y_;
  const int stride_u_;
  const int stride_v_;
  const std::unique_ptr<uint8_t, AlignedFreeDeleter> data_;
};
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-03-24 22:35:46

主要问题是从I420颜色格式转换为OpenCV使用的BGR (或BGRA)颜色格式。

两个很好的颜色转换选项:

  • 使用sws_scale - FFmpeg的C接口库的一部分。
  • 使用IPP的颜色转换功能,如P3C4R

我们还可以在简历:cvtColor参数中使用cv::COLOR_YUV2BGR_I420参数。

这是不太推荐,因为Y,U和V色通道必须在内存中顺序-在一般情况下,它需要太多的“深拷贝”操作。

在颜色转换之后,我们可以使用cv:Mat构造函数“包装”BGR (或BGRA)内存缓冲区(而不使用“深度复制”)。

示例(“步骤”、“大步”和“线条大小”等术语等效):

代码语言:javascript
运行
复制
cv::Mat bgra_img = cv::Mat(height, width, CV_8UC4, pDst, dstStep);

创建I420格式的原始图像示例,用于测试:

我们可以使用FFmpeg CLI来创建用于测试的输入文件:

代码语言:javascript
运行
复制
 ffmpeg -f lavfi -i testsrc=size=640x480:duration=1:rate=1 -pix_fmt yuv420p -f rawvideo I420.yuv

注意: FFmpeg yuv420p等同于I420格式。

代码示例包括两个部分:

第一部分使用sws_scale,第二部分使用IPP

选择其中之一(您不必同时使用两者)。

为了进行测试,我重新定义并向class I420Buffer添加了一些功能。

它看起来可能很奇怪,但只用于测试。

只需按照代码样本,看看这是有意义的.

下面是代码示例(请阅读注释):

代码语言:javascript
运行
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Use OpenCV for showing the image
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>


extern "C" {
//Required for using sws_scale
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
}

//We don't need both IPP and LibAV, the IPP solution is a separate example.
#include <ipp.h>
#include <ippi.h>



//I420 format:
//            <------ stride_y_------>
//            <------- width ------>
// data_y_ -> yyyyyyyyyyyyyyyyyyyyyy00
//            yyyyyyyyyyyyyyyyyyyyyy00
//            yyyyyyyyyyyyyyyyyyyyyy00
//            yyyyyyyyyyyyyyyyyyyyyy00
//            yyyyyyyyyyyyyyyyyyyyyy00
//            yyyyyyyyyyyyyyyyyyyyyy00
//
//            < stride_u_>
//            <-width/2->
// data_u_ -> uuuuuuuuuuu0
//            uuuuuuuuuuu0
//            uuuuuuuuuuu0
//            
//            < stride_v_>
//            <-width/2->
// data_v_ -> uuuuuuuuuuu0
//            uuuuuuuuuuu0
//            uuuuuuuuuuu0

 


// Plain I420 buffer in standard memory.
// Some extra functionality is added for testing
////////////////////////////////////////////////////////////////////////////////
class I420Buffer {
    public:
        //Constructor (for testing):
        //Allocate buffers, and read I420 image for binary file.
        explicit I420Buffer(int w, int h, const char *input_file_name) : width_(w), height_(h), stride_y_(w), stride_u_(w / 2), stride_v_(w / 2) 
        {
            //The example uses stride = width (but in the general case the stride may be larger than width).
            data_y_ = new uint8_t[w*h];
            data_u_ = new uint8_t[w*h / 4];
            data_v_ = new uint8_t[w*h / 4];

            FILE* f = fopen(input_file_name, "rb");
            fread(data_y_, 1, w*h, f);  //Read Y color channel.
            fread(data_u_, 1, w*h/4, f);  //Read U color channel.
            fread(data_v_, 1, w*h/4, f);  //Read V color channel.
            fclose(f);
        };

        //Destructor (for testing):
        ~I420Buffer()
        {
            delete[] data_y_;
            delete[] data_u_;
            delete[] data_v_;
        }

        int width() const { return width_; };
        int height() const { return height_; };
        const uint8_t* DataY() const { return data_y_; };
        const uint8_t* DataU() const { return data_u_; };
        const uint8_t* DataV() const { return data_v_; };

        int StrideY() const { return stride_y_; };
        int StrideU() const { return stride_u_; };
        int StrideV() const { return stride_v_; };

        //uint8_t* MutableDataY();
        //uint8_t* MutableDataU();
        //uint8_t* MutableDataV();

    private:
        const int width_;
        const int height_;
        const int stride_y_;
        const int stride_u_;
        const int stride_v_;
        //const std::unique_ptr<uint8_t, AlignedFreeDeleter> data_;
        uint8_t* data_y_;   //Assume data_ is internally divided into Y, U and V buffers.
        uint8_t* data_u_;
        uint8_t* data_v_;
};
////////////////////////////////////////////////////////////////////////////////



int main()
{
    //Create raw video frame in I420 format using FFmpeg (for testing):
    //ffmpeg -f lavfi -i testsrc=size=640x480:duration=1:rate=1 -pix_fmt yuv420p -f rawvideo I420.yuv
    int width = 640;
    int height = 480;

    I420Buffer I(width, height, "I420.yuv");


    //Create SWS Context for converting from decode pixel format (like YUV420) to BGR
    ////////////////////////////////////////////////////////////////////////////
    struct SwsContext* sws_ctx = NULL;

    sws_ctx = sws_getContext(I.width(),
                             I.height(),
                             AV_PIX_FMT_YUV420P,  //Input format is yuv420p (equivalent to I420).
                             I.width(),
                             I.height(),
                             AV_PIX_FMT_BGR24,    //For OpenCV, we want BGR pixel format.
                             SWS_FAST_BILINEAR,
                             NULL,
                             NULL,
                             NULL);

    if (sws_ctx == nullptr)
    {
        return -1;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Allocate frame for storing image converted to BGR.
    ////////////////////////////////////////////////////////////////////////////
    AVFrame* pBGRFrame = av_frame_alloc();  //Allocate frame, because it is more continent than allocating and initializing data buffer and linesize.

    pBGRFrame->format = AV_PIX_FMT_BGR24;
    pBGRFrame->width = I.width();
    pBGRFrame->height = I.height();

    int sts = av_frame_get_buffer(pBGRFrame, 0);    //Buffers allocation

    if (sts < 0)
    {
        return -1;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Convert from input format (e.g YUV420) to BGR:
    //Use BT.601 conversion formula. It is more likely that the input is BT.709 and not BT.601 (read about it in Wikipedia).
    //It is possible to select BT.709 using sws_setColorspaceDetails
    ////////////////////////////////////////////////////////////////////////////
    const uint8_t* const src_data[] = { I.DataY(), I.DataU(), I.DataV() };
    const int src_stride[] = { I.StrideY(), I.StrideU(), I.StrideV() };

    sts = sws_scale(sws_ctx,                //struct SwsContext* c,
                    src_data,               //const uint8_t* const srcSlice[],
                    src_stride,             //const int srcStride[],
                    0,                      //int srcSliceY, 
                    I.height(),             //int srcSliceH,
                    pBGRFrame->data,        //uint8_t* const dst[], 
                    pBGRFrame->linesize);   //const int dstStride[]);

    if (sts != I.height())
    {
        return -1;  //Error!
    }


    //Use OpenCV for showing the image (and save the image in JPEG format):
    ////////////////////////////////////////////////////////////////////////////
    cv::Mat img = cv::Mat(pBGRFrame->height, pBGRFrame->width, CV_8UC3, pBGRFrame->data[0], pBGRFrame->linesize[0]);    //cv::Mat is OpenCV "thin image wrapper".
    cv::imshow("img", img);
    //cv::waitKey();

    //Save the inage in PNG format using OpenCV
    cv::imwrite("rgb.png", img);
    ////////////////////////////////////////////////////////////////////////////


    //Free
    sws_freeContext(sws_ctx);
    av_frame_free(&pBGRFrame);



    // Solution using IPP:
    // The IPP sample use BT.709 conversion formula, and convert to BGRA (not BGR)
    // It is more likely that the input is BT.709 and not BT.601 (read about it in Wikipedia).
    // Using color conversion function: ippiYCbCr420ToBGR_709HDTV_8u_P3C4R
    //https://www.intel.com/content/www/us/en/develop/documentation/ipp-dev-reference/top/volume-2-image-processing/image-color-conversion/color-model-conversion/ycbcr420tobgr-709hdtv.html
    ////////////////////////////////////////////////////////////////////////////
    IppStatus ipp_sts = ippInit();

    if (ipp_sts < ippStsNoErr)
    {
        return -1;  //Error.
    }

    const Ipp8u* pSrc[3] = { I.DataY(), I.DataU(), I.DataV() };
    int srcStep[3] = { I.StrideY(), I.StrideU(), I.StrideV() };
    Ipp8u* pDst = new uint8_t[I.width() * I.height() * 4];
    int dstStep = I.width() * 4;
    IppiSize roiSize = { I.width(), I.height() };

    ipp_sts = ippiYCbCr420ToBGR_709HDTV_8u_P3C4R(pSrc,      //const Ipp8u* pSrc[3], 
                                                 srcStep,   //int srcStep[3], 
                                                 pDst,      //Ipp8u* pDst, 
                                                 dstStep,   //int dstStep, 
                                                 roiSize,   //IppiSize roiSize, 
                                                 255);      //Ipp8u aval)

    if (ipp_sts < ippStsNoErr)
    {
        return -1;  //Error.
    }

    cv::Mat bgra_img = cv::Mat(I.height(), I.width(), CV_8UC4, pDst, dstStep);    //cv::Mat is OpenCV "thin image wrapper".
    cv::imshow("bgra_img", bgra_img);
    cv::waitKey();

    delete[] pDst;
    ////////////////////////////////////////////////////////////////////////////

    return 0;
}

样本输出(调整大小):

注意:

实际使用WebRTC输入流似乎很困难。

假设已解码的原始视频帧已经存在于I420Buffer中,正如您在文章中所定义的那样。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71607268

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档