Преобразование двоичного изображения RGBA в YCbCr, но результат не такой, как ожидалось

У меня есть изображение RGBA в двоичном формате (.raw), и я пытаюсь преобразовать изображение в YCbCr с помощью C++. Однако преобразованное изображение при просмотре с помощью ffplay дает мне зеленое изображение. Что я делаю не так? У меня есть код для воспроизведения проблемы, с которой я столкнулся. Входное изображение выглядит так: https://drive.google.com/file/d/1oDswYmmSV0pfNe-u8Do06WWVu2v1B-rg/view?usp=sharing, а снимок преобразованного изображения - https://drive.google.com/. файл / d / 1G8Rut3CXILqbmlGrFQsnushy2CLKu40w / view? usp = совместное использование . Входное изображение RGBA .raw можно получить здесь: 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));}

2 ответа

Ваш код подходит для YUV_4_4_4 8-bit-Packed. Вы можете просмотреть это с помощью YUView: https://github.com/IENT/YUView/releases и выберите настройки:

Он будет отображаться нормально.

Однако, если вы это видите 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;
}

Здесь вы перезаписываете один и тот же байт:

          output[y*width + x][0] = Y;
    output[y*width + x][0] = Cb;
    output[y*width + x][0] = Cr;
Другие вопросы по тегам