c++ 从I420帧ffmpeg计算rgb缓冲区简单方法

bihw5rsg  于 2023-03-14  发布在  其他
关注(0)|答案(1)|浏览(129)

我正在尝试从avframe直接计算rgb缓冲区。由于获得的图像错误,所以出现了一些问题。从AVFrame-〉data[0]提取灰色图像工作正常。但我无法提取彩色图像

inline void YCrCb_to_RGB8(int Y, int Cr, int Cb, int& R, int& G, int& B){

   R = (int)(Y + 1.402 *(Cr - 128));
   G = (int)(Y - 0.344136*(Cb-128) -0.71414*(Cr-128));
   B = (int)(Y + 1.772 *(Cb-128));

   if (R < 0) R = 0; else if (R > 255) R = 255;
   if (G < 0) G = 0; else if (G > 255) G = 255;
   if (B < 0) B = 0; else if (B > 255) B = 255;
}

int getRGB8buffer(AVFrame* pFrame, byte* buffer){

   const int width = pFrame->width, height = pFrame->height;
   int Y, Cr, Cb;
   int R, G, B;

   int pixel = 0;
   for (int y = 0; y < height; y++)
   {
    for (int x = 0; x < width; x++)
    {

        Y = pFrame->data[0][x + y * width];
        Cr = pFrame->data[1][x / 2 + ((int)(y / 2)) * pFrame->linesize[1]];
        Cb = pFrame->data[2][x / 2 + ((int)(y / 2)) * pFrame->linesize[2]];

        YCrCb_to_RGB8(Y, Cr, Cb, R, G, B);
        buffer[pixel * 3 + 0] = R;
        buffer[pixel * 3 + 1] = G;
        buffer[pixel * 3 + 2] = B;
        pixel++;
    }
   }
   return 0;
}

当我使用将获得的图像保存为ppm时

int save_RGB_frame(unsigned char* buf, int wrap, int xsize,int ysize, const char* filename){

   FILE* f;
   int i;
   f = fopen(filename, "w");
   // portable ppm format -> https://en.wikipedia.org/wiki/Netpbm#PPM_example
   fprintf(f, "P6\n%d %d\n%d\n", xsize, ysize, 255);

   // writing line by line
   for (i = 0; i < ysize; i++)
      fwrite(buf + i * wrap, 1, xsize*3, f);
   fclose(f);

   return 0;
 }

结果图像与结果图像https://github.com/hacenesh/ffmpeg_question/blob/main/img_2028144.ppm的链接错误

wwtsj6pe

wwtsj6pe1#

主要问题是使用fopen(filename, "w")而不是f = fopen(filename, "wb")
在Windows操作系统中,二进制文件和文本文件之间有一个重要的区别。
默认的"w"选项将文件作为文本文件打开。
写入文本文件时,每个新行字符\n转换为两个字符\r\n
额外的字符打乱了图像的整个结构。
注意:如果您使用的是Linux,"wb""w"应该是相同的。
纠正了save_RGB_frame的代码:

int save_RGB_frame(unsigned char* buf, int wrap, int xsize, int ysize, const char* filename)
{
    FILE* f;
    int i;
    //f = fopen(filename, "w");
    f = fopen(filename, "wb"); //In Windows OS, we must use "wb" for opening a binary file (by default "w" applies text file).
    // portable ppm format -> https://en.wikipedia.org/wiki/Netpbm#PPM_example
    fprintf(f, "P6\n%d %d\n%d\n", xsize, ysize, 255);

    // writing line by line
    for (i = 0; i < ysize; i++)
        fwrite(buf + i * wrap, 1, xsize*3, f);
    fclose(f);

    return 0;
}

保存到PPM图像文件:

save_RGB_frame(buffer, width*3, width, height, "img.ppm");

getRGB8buffer中的问题:
YUV 420 p格式的平面顺序是YUV
U应用CbV应用Cr,因此顺序为:一米十六英寸,一米十七英寸,一米十八英寸。
纠正了getRGB8buffer的代码:

int getRGB8buffer(const AVFrame* pFrame, byte* buffer)
{
    const int width = pFrame->width, height = pFrame->height;
    int Y, Cr, Cb;
    int R, G, B;

    int pixel = 0;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            //YUV420p planes ordering is Y, Cb, Cr (not Y, Cr, Cb).
            //Y = pFrame->data[0][x + y * width];
            //Cr = pFrame->data[1][x / 2 + ((int)(y / 2)) * pFrame->linesize[1]];
            //Cb = pFrame->data[2][x / 2 + ((int)(y / 2)) * pFrame->linesize[2]];
            Y = pFrame->data[0][x + y * pFrame->linesize[0]];   //Using pFrame->linesize[0] is prefered.
            Cb = pFrame->data[1][x / 2 + ((int)(y / 2)) * pFrame->linesize[1]];
            Cr = pFrame->data[2][x / 2 + ((int)(y / 2)) * pFrame->linesize[2]];

            YCrCb_to_RGB8(Y, Cr, Cb, R, G, B);
            buffer[pixel * 3 + 0] = R;
            buffer[pixel * 3 + 1] = G;
            buffer[pixel * 3 + 2] = B;
            pixel++;
        }
    }

    return 0;
}

YCrCb_to_RGB8中的问题:
您问题中的转换公式采用JPEG转换公式。
FFmpeg默认使用的换算公式采用BT.601“有限范围”换算公式。
在“有限范围”中,Y范围为[16,235],与“全范围”[0,255]相对。
与“全范围”(PC范围/ JPEG范围)相比,使用“有限范围”(“TV范围”)更为常见。
BT.601对于高清视频来说可能没有BT.709那么常见,但是BT.601是FFmpeg默认转换(我们将坚持使用BT.601)。
注:我们这里使用的换算公式与MATLAB函数ycbcr2rgb相同。

inline void YCrCb_to_RGB8(int Y, int Cr, int Cb, int& R, int& G, int& B)
{
    //Subtract offsets and cast to double.
    //Subtractin 16 from Y assuems "limited range" YCbCr format where Y range is [16, 235] (oppused to "full range" whenre Y range is [0, 255]).
    double y = (double)(Y - 16);
    double u = (double)(Cb - 128);
    double v = (double)(Cr - 128);

    //The folloiwng conversion applies BT.601 "limited range" conversion formula.
    //Getting the same results as MATLAB function ycbcr2rgb.
    //BT.601 "limited range" is also the default conversion used by FFmpeg.
    R = (int)std::round(1.1644*y + 1.5960*v);
    G = (int)std::round(1.1644*y - 0.3918*u - 0.8130*v);
    B = (int)std::round(1.1644*y + 2.0172*u);

    R = std::max(std::min(R, 255), 0);
    G = std::max(std::min(G, 255), 0);
    B = std::max(std::min(B, 255), 0);
}

相关问题