Search code examples
c++imageimage-processingrgbaycbcr

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


I have a RGBA image in binary format(.raw) and I am trying to convert the image into YCbCr using C++. However the converted image when viewed using ffplay give me a green image. What could I be doing wrong? I have a code to reproduce the problem I am facing. The input image looks like this: 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));}

Solution

  • Your code is fine for YUV_4_4_4 8-bit-Packed. You can view it with YUView: https://github.com/IENT/YUView/releases and select the settings:

    YUView_4_4_4_Packed

    It will display just fine.

    However, if you are seeing it Green or whatever wrong colours, it means the program reading it is expecting a different format. Most likely it is expecting planar format which means you need to write all Y bytes first. Then write Cb bytes, then Cr bytes.

    So it'd look like (YCbCr_4_4_4_Planar):

    YYYY
    YYYY
    YYYY
    CbCbCbCb
    CbCbCbCb
    CrCrCrCr
    CrCrCrCr
    

    instead of packed which looks like (Your code above = YCbCr_4_4_4_Packed/Interleaved):

    YCbCrYCbCrYCbCr
    YCbCrYCbCrYCbCr
    YCbCrYCbCrYCbCr
    YCbCrYCbCrYCbCr
    

    Below I wrote some code that can handle multiple formats. It'll take a RAW image format and convert it to either:

    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;
    }