【问题标题】:Problems converting YV12 to RGB through GLSL通过 GLSL 将 YV12 转换为 RGB 的问题
【发布时间】:2012-02-17 03:33:22
【问题描述】:

我正在尝试使用 GLSL 着色器完成 YV12 到 RGB 的转换提到in this post。

我的应用程序从磁盘加载原始 YV12 帧并尝试使用 GLSL 着色器执行转换。但是,生成的图像是垂直翻转的,并且存在一些颜色问题。我认为问题可能是图像被读取为char(1 个字节)的数组,然后转换为GLushort(2 个字节)的数组。你怎么看?

这是原始 YUV 帧的样子:

and the raw frame loaded by the application can be downloaded from here.

这是我得到的输出:

我在下面分享应用程序的源代码:

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <GL/glew.h>
#include <GL/glut.h>
#include <GL/glu.h>

#include <iostream>

#include <fstream>

#ifndef SEEK_SET
#  define SEEK_SET 0
#endif

static GLfloat Xrot = 0, Yrot = 0, Zrot = 0;
static GLint ImgWidth, ImgHeight;
static GLushort *ImageYUV = NULL;


static void DrawObject(void)
{
   glBegin(GL_QUADS);

   glTexCoord2f(0, 0);
   glVertex2f(-1.0, -1.0);

   glTexCoord2f(ImgWidth, 0);
   glVertex2f(1.0, -1.0);

   glTexCoord2f(ImgWidth, ImgHeight);
   glVertex2f(1.0, 1.0);

   glTexCoord2f(0, ImgHeight);
   glVertex2f(-1.0, 1.0);

   glEnd();
}

static void Display( void )
{
   glClear( GL_COLOR_BUFFER_BIT );

   glPushMatrix();
      glRotatef(Xrot, 1.0, 0.0, 0.0);
      glRotatef(Yrot, 0.0, 1.0, 0.0);
      glRotatef(Zrot, 0.0, 0.0, 1.0);
      DrawObject();
   glPopMatrix(); 

   glutSwapBuffers();
}

static void Reshape( int width, int height )
{
   glViewport( 0, 0, width, height );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();

   // Vertical flip so texture appears right
   glFrustum( -1.0, 1.0, 1.0, -1.0, 10.0, 100.0 ); 
   //glFrustum( -1.0, 1.0, -1.0, 1.0, 10.0, 100.0 );

   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
   glTranslatef( 0.0, 0.0, -15.0 );
}

static void Key( unsigned char key, int x, int y )
{
   (void) x;
   (void) y;
   switch (key) {
      case 27:
         exit(0);
         break;
   }
   glutPostRedisplay();
}

static void SpecialKey( int key, int x, int y )
{
   float step = 3.0;
   (void) x;
   (void) y;

   switch (key) {
      case GLUT_KEY_UP:
         Xrot += step;
         break;
      case GLUT_KEY_DOWN:
         Xrot -= step;
         break;
      case GLUT_KEY_LEFT:
         Yrot += step;
         break;
      case GLUT_KEY_RIGHT:
         Yrot -= step;
         break;
   }
   glutPostRedisplay();
}        

bool CheckShader(int n_shader_object)
{
    int n_tmp;
    glGetShaderiv(n_shader_object, GL_COMPILE_STATUS, &n_tmp);
    bool b_compiled = n_tmp == GL_TRUE;
    int n_log_length;
    glGetShaderiv(n_shader_object, GL_INFO_LOG_LENGTH, &n_log_length);
    // query status ...

    if(n_log_length > 1) {
        char *p_s_temp_info_log;
        if(!(p_s_temp_info_log = (char*)malloc(n_log_length)))
            return false;
        int n_tmp;
        glGetShaderInfoLog(n_shader_object, n_log_length, &n_tmp,
            p_s_temp_info_log);
        assert(n_tmp <= n_log_length);

        fprintf(stderr, "%s\n", p_s_temp_info_log);
        free(p_s_temp_info_log);
    }
    // get/concat info-log

    return b_compiled;
}

static void Init( int argc, char *argv[] )
{
   GLuint texObj = 100;
   const char *file;

   printf("Checking GL_ARB_texture_rectangle\n");
   if (!glutExtensionSupported("GL_ARB_texture_rectangle")) {
      printf("Sorry, GL_ARB_texture_rectangle is required\n");
      exit(0);
   }

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texObj);
#ifdef LINEAR_FILTER
   /* linear filtering looks much nicer but is much slower for Mesa */
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#else
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#endif

    std::ifstream yuv_file("data.yv12", std::ios::in | std::ios::binary | std::ios::ate);
    if (!yuv_file.is_open())
    {
        std::cout << "> GLWidget::GLWidget !!! Failed to load yuv file";
        return;
    }
    int yuv_file_sz = yuv_file.tellg();

    ImgWidth = 1280;
    ImgHeight = 720;
    ImageYUV = new GLushort[yuv_file_sz];

    char* memblock = new char[yuv_file_sz];
    if (!memblock)
    {
        std::cout << "> GLWidget::GLWidget !!! Failed to allocate memblock";
        return;
    }

    yuv_file.seekg(0, std::ios::beg);
    yuv_file.read(memblock, yuv_file_sz);
    yuv_file.close();

    // A simple "memcpy(ImageYUV, memblock, yuv_file_sz);" 
    // won't work because the data read is stored as char (1 byte) and GLushort is 2 bytes.
    // So, doing a manual copy:
    for (int i = 0; i < yuv_file_sz; i++)
    {
        ImageYUV[i] = (GLushort)memblock[i];
    }
    delete[] memblock;

   printf("Image: %dx%d\n", ImgWidth, ImgHeight);

   glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
                GL_LUMINANCE_ALPHA, ImgWidth, ImgHeight, 0,
                GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, ImageYUV);

   assert(glGetError() == GL_NO_ERROR);

   glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
                   0, 0, ImgWidth, ImgHeight,
                   GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, ImageYUV);

   assert(glGetError() == GL_NO_ERROR);

   delete[] ImageYUV;

   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

   glEnable(GL_TEXTURE_RECTANGLE_ARB);

   glShadeModel(GL_FLAT);
   glClearColor(0.3, 0.3, 0.4, 1.0);

    static const char *p_s_vertex_shader =
        "varying vec2 t;"
        "void main()"
        "{"
        "    t = gl_MultiTexCoord0.xy;"
        "    gl_Position = ftransform();"
        "}";
    static const char *p_s_fragment_shader =
        "#extension GL_ARB_texture_rectangle : enable\n"
        "varying vec2 t;"
        "uniform sampler2DRect tex;"
        "void main()"
        "{"
        "    vec2 tcEven = vec2(floor(t.x * .5) * 2.0, t.y);"
        "    vec2 tcOdd = vec2(tcEven.x + 1.0, t.y);"
        "    float Cb = texture2DRect(tex, tcEven).x - .5;"
        "    float Cr = texture2DRect(tex, tcOdd).x - .5;"
        "    float y = texture2DRect(tex, t).w;" // redundant texture read optimized away by texture cache
        "    float r = y + 1.28033 * Cr;"
        "    float g = y - .21482 * Cb - .38059 * Cr;"
        "    float b = y + 2.12798 * Cb;"
        "    gl_FragColor = vec4(r, g, b, 1.0);"
        "}";

    int v = glCreateShader(GL_VERTEX_SHADER);
    int f = glCreateShader(GL_FRAGMENT_SHADER);
    int p = glCreateProgram();
    glShaderSource(v, 1, &p_s_vertex_shader, 0);
    glShaderSource(f, 1, &p_s_fragment_shader, 0);
    glCompileShader(v);
    CheckShader(v);
    glCompileShader(f);
    CheckShader(f);
    glAttachShader(p, v);
    glAttachShader(p, f);
    glLinkProgram(p);
    glUseProgram(p);
    glUniform1i(glGetUniformLocation(p, "tex"), 0);

   if (argc > 1 && strcmp(argv[1], "-info")==0) {
      printf("GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
      printf("GL_VERSION    = %s\n", (char *) glGetString(GL_VERSION));
      printf("GL_VENDOR     = %s\n", (char *) glGetString(GL_VENDOR));
      printf("GL_EXTENSIONS = %s\n", (char *) glGetString(GL_EXTENSIONS));
   }
}


int main( int argc, char *argv[] )
{
   glutInit( &argc, argv );
   glutInitWindowSize( 1280, 720 );
   glutInitWindowPosition( 0, 0 );
   glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE);
   glutCreateWindow(argv[0] );
   glewInit();

   Init( argc, argv );

   glutReshapeFunc( Reshape );
   glutKeyboardFunc( Key );
   glutSpecialFunc( SpecialKey );
   glutDisplayFunc( Display );

   glutMainLoop();
   return 0;
}

【问题讨论】:

    标签: c++ c opengl glsl yuv


    【解决方案1】:

    这里的问题是图像实际上不是YV12,色度和亮度平面不是交错的,而是分块布局的。这可以通过两种方式解决,要么在将平面加载到纹理之前交错平面并按原样使用其余代码,要么可以在着色器中完成。我删除了 iostream 并用 stdio 替换它(我使用的是相当旧的编译器)。这是我加载图像和交错的代码:

    GLubyte *memblock;
    {
        FILE *p_fr = fopen("data.yv12", "rb");
        if(!p_fr) {
            fprintf(stderr, "!!! Failed to load yuv file\n");
            return;
        }
        fseek(p_fr, 0, SEEK_END);
        int yuv_file_sz = ftell(p_fr);
        fseek(p_fr, 0, SEEK_SET);
        memblock = new GLubyte[yuv_file_sz];
        if(!memblock) {
            fprintf(stderr, "!!! Failed to allocate memblock\n");
            return;
        }
        fread(memblock, yuv_file_sz, 1, p_fr);
        fclose(p_fr);
    }
    // load .raw file
    
    ImgWidth = 1280;
    ImgHeight = 720;
    ImageYUV = new GLushort[ImgWidth * ImgHeight];
    // allocate an image
    
    int chromaWidth = ImgWidth / 2;
    int chromaHeight = ImgHeight / 2; // 2x2 luminance subsampling
    const GLubyte *pCb = memblock + ImgWidth * ImgHeight; // Cb block after Y
    const GLubyte *pCr = pCb + chromaWidth * chromaHeight; // Cr block after Cb
    // get pointers to smaller Cb and Cr blocks (the image is *not* interleaved)
    
    for(int i = 0; i < ImgWidth * ImgHeight; ++ i) {
        int x = i % ImgWidth;
        int y = i / ImgWidth;
        GLubyte cb = pCb[(x / 2) + (y / 2) * chromaWidth];
        GLubyte cr = pCr[(x / 2) + (y / 2) * chromaWidth];
        ImageYUV[i] = (memblock[i] << 8) | ((x & 1)? cr : cb);
    }
    // convert (interleave) the data to YV12
    

    这很简单,可以与上面的着色器一起使用。

    现在如果我们想跳过交错怎么办?首先,我要弄清楚这里的寻址是如何工作的(我们要表现得像图像是更高一点的单色图像,色度平面占据了亮度平面上方的空间):

    for(int y = 0; y < ImgHeight; ++ y) {
        for(int x = 0; x < ImgWidth; ++ x) {
            int CbY = ImgHeight + (y / 4);
            int CrY = ImgHeight + chromaHeight / 2 + (y / 4);
            int CbCrX = (x / 2) + chromaWidth * ((y / 2) & 1);
            // calculate x, y of cr and cb pixels in the grayscale image
            // where the Y, Cb anc Cr blocks are next to each other
    
            assert(&memblock[CbCrX + CbY * ImgWidth] == &pCb[(x / 2) + (y / 2) * chromaWidth]);
            assert(&memblock[CbCrX + CrY * ImgWidth] == &pCr[(x / 2) + (y / 2) * chromaWidth]);
            // make sure the addresses are correct (and they are)
    
            GLubyte cb = memblock[CbCrX + CbY * ImgWidth];
            GLubyte cr = memblock[CbCrX + CrY * ImgWidth];
            GLubyte Y = memblock[x + y * ImgWidth];
    
            ImageYUV[x + y * ImgWidth] = (Y << 8) | ((x & 1)? cr : cb);
        }
    }
    // convert (interleave) the data to YV12 (a little bit different way, use physical layout in memory)
    

    效果几乎相同。现在我们可以将计算位置的代码放入着色器中。

    static const char *p_s_fragment_shader =
        "#extension GL_ARB_texture_rectangle : enable\n"
        "uniform sampler2DRect tex;"
        "uniform float ImgHeight, chromaHeight_Half, chromaWidth;"
        "void main()"
        "{"
        "    vec2 t = gl_TexCoord[0].xy;" // get texcoord from fixed-function pipeline
        "    float CbY = ImgHeight + floor(t.y / 4.0);"
        "    float CrY = ImgHeight + chromaHeight_Half + floor(t.y / 4.0);"
        "    float CbCrX = floor(t.x / 2.0) + chromaWidth * floor(mod(t.y, 2.0));"
        "    float Cb = texture2DRect(tex, vec2(CbCrX, CbY)).x - .5;"
        "    float Cr = texture2DRect(tex, vec2(CbCrX, CrY)).x - .5;"
        "    float y = texture2DRect(tex, t).x;" // redundant texture read optimized away by texture cache
        "    float r = y + 1.28033 * Cr;"
        "    float g = y - .21482 * Cb - .38059 * Cr;"
        "    float b = y + 2.12798 * Cb;"
        "    gl_FragColor = vec4(r, g, b, 1.0);"
        "}";
    

    通过使用这个着色器,我们可以直接将原始数据上传到纹理中,只是它有点高,只有 GL_LUMINANCE:

    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
                    GL_LUMINANCE, ImgWidth, ImgHeight + ImgHeight / 2, 0, // !!
                    GL_LUMINANCE, GL_UNSIGNED_BYTE, memblock); // !!
    

    我会保留它。以下是完整的源代码:

    interleaving in shader (faster, preferrable)
    manual interleaving in "C"

    对不起,如果我不尽快离开工作场所,我会遇到问题:)。

    【讨论】:

    • +1 对于这样的高质量答案。这应该得到很多投票!
    • 在 Linux 上,使用 ATI 和 Intel 显卡result is yellow-ish。你知道会发生什么吗?
    • 顺便说一下,我们的代码呈现垂直翻转的结果(倒置)。要解决这个问题,只需将Reshape() 中的glFrustum() 调用更改为glFrustum( -1.0, 1.0, 1.0, -1.0, 10.0, 100.0 );。我在问题中解决了我的问题。
    • 在着色器中交换 Cr 和 Cb。那应该这样做。哦,对颠倒的事情感到抱歉,我完全忘记了,正在匆忙完成答案。
    • 谢谢,成功了!但是,为什么 Linux 上的 ATI/Intel 需要这种交换而 Windows 上的 NVIDIA 不需要这种交换是一个谜。我开始相信这可能与操作系统(驱动程序)有关,而不是视频卡本身。
    猜你喜欢
    • 2012-02-22
    • 1970-01-01
    • 2018-02-25
    • 1970-01-01
    • 2022-01-09
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多