将 RGBA 二值图像转换为 YCbCr 但结果不符合预期

Converting RGBA binary image into YCbCr but the result is not as expected

我有一个二进制格式 (.raw) 的 RGBA 图像,我正在尝试使用 C++ 将图像转换为 YCbCr。然而,使用 ffplay 查看时转换后的图像给我一个绿色图像。我做错了什么?我有一个代码可以重现我面临的问题。输入图像如下所示:https://drive.google.com/file/d/1oDswYmmSV0pfNe-u8Do06WWVu2v1B-rg/view?usp=sharing and the snapshot of the converted image is https://drive.google.com/file/d/1G8Rut3CXILqbmlGrFQsnushy2CLKu40w/view?usp=sharing. The input RGBA .raw image can be obtained here: https://drive.google.com/file/d/19JhMjRdibGCgaUsE6DBGAXGTRiT2bmTM/view?usp=sharing

#include <fstream>
#include <iostream>
#include <vector>
#include <array>

typedef unsinged char byte;

int main(){

std::ifstream infile;
std::ofstream outfile;

const unsigned width = 1280;
const unsigned height = 720;

std::vector<std::array<byte, 4>> imageBuffer;
std::vector<std::array<byte, 3>> output;

imageBuffer.resize(width*height);
output.resize(width*height);

infile.open("input.raw", std::ios::binary);
if(infile){
    infile.read(reinterpret_cast<char*>(&imageBuffer[0]), width*height*4*sizeof(char));
}

for (unsigned y=0; y<height; ++y){
    for(unsigned x=0; x<width; ++x){
        byte R, G, B, A;
        R = imageBuffer[y*width + x][0];
        G = imageBuffer[y*width + x][1];
        B = imageBuffer[y*width + x][2];
        
        byte Y, Cb, Cr;

        Y = 0.257*R + 0.504*G + 0.098*B + 16;
        Cb = -0.148*R - 0.291*G + 0.439*B + 128;
        Cr = 0.439*R - 0.368*G - 0.071*B + 128;

        output[y*width + x][0] = Y;
        output[y*width + x][1] = Cb;
        output[y*width + x][2] = Cr;
    
    }

}

std::ofstream os("output444.yuv", std::ios::binary);
if(!os)
    return false;

os.write(reinterpret_cast<char*>(&output[0]), 1280*720*3*sizeof(char));}

您正在覆盖此处的相同字节:

    output[y*width + x][0] = Y;
    output[y*width + x][0] = Cb;
    output[y*width + x][0] = Cr;

您的代码适合 YUV_4_4_4 8-bit-Packed。 您可以使用 YUView 查看它:https://github.com/IENT/YUView/releases 和 select 设置:

会正常显示。

但是,如果您看到它 Green 或任何错误的颜色,这意味着读取它的程序需要不同的格式。很可能它期望 planar 格式,这意味着您需要先写入所有 Y 字节。然后写入 Cb 字节,然后写入 Cr 字节。

所以它看起来像 (YCbCr_4_4_4_Planar):

YYYY
YYYY
YYYY
CbCbCbCb
CbCbCbCb
CrCrCrCr
CrCrCrCr

而不是打包,看起来像(上面的代码 = YCbCr_4_4_4_Packed/Interleaved):

YCbCrYCbCrYCbCr
YCbCrYCbCrYCbCr
YCbCrYCbCrYCbCr
YCbCrYCbCrYCbCr

下面我写了一些可以处理多种格式的代码。它将采用 RAW 图像格式并将其转换为:

YUV_4_2_2_PLANAR,
YUV_4_2_2_PACKED,

YUV_4_4_4_PLANAR,
YUV_4_4_4_PACKED,
//
//  main.cpp
//  RAW-To-YUV-Conversion
//
//  Created by Brandon on 2021-08-06.
//

#include <iostream>
#include <fstream>
#include <utility>
#include <memory>
#include <vector>

void RGBToYUV(std::uint8_t R, std::uint8_t G, std::uint8_t B, std::uint8_t& Y, std::uint8_t& U, std::uint8_t& V)
{
  Y =  0.257 * R + 0.504 * G + 0.098 * B +  16;
  U = -0.148 * R - 0.291 * G + 0.439 * B + 128;
  V =  0.439 * R - 0.368 * G - 0.071 * B + 128;
}

//void RGBToYUV(std::uint8_t R, std::uint8_t G, std::uint8_t B, std::uint8_t &Y, std::uint8_t &U, std::uint8_t &V)
//{
//    #define RGB2Y(r, g, b) (uint8_t)(((66 * (r) + 129 * (g) +  25 * (b) + 128) >> 8) +  16)
//    #define RGB2U(r, g, b) (uint8_t)(((-38 * (r) - 74 * (g) + 112 * (b) + 128) >> 8) + 128)
//    #define RGB2V(r, g, b) (uint8_t)(((112 * (r) - 94 * (g) -  18 * (b) + 128) >> 8) + 128)
//
//    Y = RGB2Y((int)R, (int)G, (int)B);
//    U = RGB2U((int)R, (int)G, (int)B);
//    V = RGB2V((int)R, (int)G, (int)B);
//}

enum Format
{
    YUV_4_2_2_PLANAR,
    YUV_4_2_2_PACKED,
    YUV_4_4_4_PLANAR,
    YUV_4_4_4_PACKED,
};

class RawImage
{
private:
    std::unique_ptr<std::uint8_t> pixels;
    std::uint32_t width, height;
    std::uint16_t bpp;
    
public:
    RawImage(const char* path, std::uint32_t width, std::uint32_t height);
    ~RawImage() {}
    
    void SaveYUV(const char* path, Format format);
};

RawImage::RawImage(const char* path, std::uint32_t width, std::uint32_t height) : pixels(nullptr), width(width), height(height), bpp(32)
{
    std::ifstream file(path, std::ios::in | std::ios::binary);
    
    if (file)
    {
        std::size_t size = width * height * 4;
        
        file.seekg(0, std::ios::beg);
        pixels.reset(new std::uint8_t[size]);

        file.read(reinterpret_cast<char*>(pixels.get()), size);
    }
}

void RawImage::SaveYUV(const char* path, Format format)
{
    std::ofstream file(path, std::ios::out | std::ios::binary);
    
    if (file)
    {
        if (format == Format::YUV_4_2_2_PLANAR)
        {
            std::unique_ptr<std::uint8_t> y_plane{new std::uint8_t[width * height]};
            std::unique_ptr<std::uint8_t> u_plane{new std::uint8_t[(width * height) >> 1]};
            std::unique_ptr<std::uint8_t> v_plane{new std::uint8_t[(width * height) >> 1]};
            
            std::uint8_t* in = pixels.get();
            std::uint8_t* y_plane_ptr = y_plane.get();
            std::uint8_t* u_plane_ptr = u_plane.get();
            std::uint8_t* v_plane_ptr = v_plane.get();
            
            for (std::uint32_t i = 0; i < height; ++i)
            {
                for (std::uint32_t j = 0; j < width; j += 2)
                {
                    std::uint32_t offset = 4;
                    std::size_t in_pos = i * (width * offset) + offset * j;

                    std::uint8_t Y1 = 0;
                    std::uint8_t U1 = 0;
                    std::uint8_t V1 = 0;
                    
                    std::uint8_t Y2 = 0;
                    std::uint8_t U2 = 0;
                    std::uint8_t V2 = 0;
                    
                    RGBToYUV(in[in_pos + 0], in[in_pos + 1], in[in_pos + 2], Y1, U1, V1);
                    RGBToYUV(in[in_pos + 4], in[in_pos + 5], in[in_pos + 6], Y2, U2, V2);
                    
                    std::uint8_t U3 = (U1 + U2 + 1) >> 1;
                    std::uint8_t V3 = (V1 + V2 + 1) >> 1;
                    
                    *y_plane_ptr++ = Y1;
                    *y_plane_ptr++ = Y2;
                    *u_plane_ptr++ = U3;
                    *v_plane_ptr++ = V3;
                }
            }
            
            file.write(reinterpret_cast<char*>(y_plane.get()), width * height);
            file.write(reinterpret_cast<char*>(u_plane.get()), (width * height) >> 1);
            file.write(reinterpret_cast<char*>(v_plane.get()), (width * height) >> 1);
        }
        else if (format == Format::YUV_4_2_2_PACKED)
        {
            std::size_t size = width * height * 2;
            std::unique_ptr<std::uint8_t> buffer{new std::uint8_t[size]};
            std::uint8_t* in = pixels.get();
            std::uint8_t* out = buffer.get();
            
            for (std::uint32_t i = 0; i < height; ++i)
            {
                for (std::uint32_t j = 0; j < width; j += 2)
                {
                    std::uint32_t offset = 4;
                    std::size_t in_pos = i * (width * offset) + offset * j;

                    std::uint8_t Y1 = 0;
                    std::uint8_t U1 = 0;
                    std::uint8_t V1 = 0;
                    
                    std::uint8_t Y2 = 0;
                    std::uint8_t U2 = 0;
                    std::uint8_t V2 = 0;
                    
                    RGBToYUV(in[in_pos + 0], in[in_pos + 1], in[in_pos + 2], Y1, U1, V1);
                    RGBToYUV(in[in_pos + 4], in[in_pos + 5], in[in_pos + 6], Y2, U2, V2);
                    
                    std::uint8_t U3 = (U1 + U2 + 1) >> 1;
                    std::uint8_t V3 = (V1 + V2 + 1) >> 1;
                    
                    std::size_t out_pos = i * (width * 2) + 2 * j;
                    out[out_pos + 0] = Y1;
                    out[out_pos + 1] = U3;
                    out[out_pos + 2] = Y2;
                    out[out_pos + 3] = V3;
                }
            }
            
            file.write(reinterpret_cast<char*>(buffer.get()), size);
        }
        else if (format == Format::YUV_4_4_4_PLANAR)
        {
            std::size_t size = width * height * 3;
            std::unique_ptr<std::uint8_t> buffer{new std::uint8_t[size]};
            std::uint8_t* in = pixels.get();
            std::uint8_t* out = buffer.get();
            
            for (std::uint32_t i = 0; i < height; ++i)
            {
                for (std::uint32_t j = 0; j < width; ++j)
                {
                    std::uint32_t offset = 4;
                    std::size_t in_pos = i * (width * offset) + offset * j;

                    std::uint8_t Y = 0;
                    std::uint8_t U = 0;
                    std::uint8_t V = 0;
                    
                    RGBToYUV(in[in_pos + 0], in[in_pos + 1], in[in_pos + 2], Y, U, V);
                    
                    std::size_t y_pos = i * width + j;
                    std::size_t u_pos = y_pos + (width * height);
                    std::size_t v_pos = y_pos + (width * height * 2);
                    
                    out[y_pos] = Y;
                    out[u_pos] = U;
                    out[v_pos] = V;
                }
            }
            
            file.write(reinterpret_cast<char*>(buffer.get()), size);
        }
        else if (format == Format::YUV_4_4_4_PACKED)
        {
            std::size_t size = width * height * 3;
            std::unique_ptr<std::uint8_t> buffer{new std::uint8_t[size]};
            std::uint8_t* in = pixels.get();
            std::uint8_t* out = buffer.get();
            
            for (std::uint32_t i = 0; i < height; ++i)
            {
                for (std::uint32_t j = 0; j < width; ++j)
                {
                    std::uint32_t offset = 4;
                    std::size_t in_pos = i * (width * offset) + offset * j;

                    std::uint8_t Y = 0;
                    std::uint8_t U = 0;
                    std::uint8_t V = 0;
                    
                    RGBToYUV(in[in_pos + 0], in[in_pos + 1], in[in_pos + 2], Y, U, V);
                    
                    std::size_t out_pos = i * (width * 3) + 3 * j;
                    out[out_pos + 0] = Y;
                    out[out_pos + 1] = U;
                    out[out_pos + 2] = V;
                }
            }
            
            file.write(reinterpret_cast<char*>(buffer.get()), size);
        }
    }
}


int main(int argc, const char * argv[]) {

    RawImage img{"/Users/brandon/Downloads/input.raw", 1280, 720};
    img.SaveYUV("/Users/brandon/Downloads/output.yuv", Format::YUV_4_4_4_PACKED);
    
    return 0;
}