通过GLSL将YV12转换为RGB的问题

我正在尝试使用GLSL着色器完成本文中提到的YV12到RGB转换

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

这是原始YUV框架的样子:

在此处输入图像描述

并且可以从此处下载应用程序加载的原始框架 。

这是我得到的输出:

在此处输入图像描述

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

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

这里的问题是图像实际上不是YV12,色度和亮度平面不是交错的,而是以块的forms排列。 这可以通过两种方式解决,在将其加载到纹理之前交错平面并按原样使用其余代码,或者可以在着色器中完成。 我删除了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(ty / 4.0);" " float CrY = ImgHeight + chromaHeight_Half + floor(ty / 4.0);" " float CbCrX = floor(tx / 2.0) + chromaWidth * floor(mod(ty, 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); // !! 

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

着色器中的交错(更快,更优先)
“C”中的手动交错

对不起快速结束,如果我不尽快离开我的工作场所,我将遇到问题:)。