Android Camera预览YUV格式到GPU上的RGB

时间:2012-12-24 18:41:50

标签: java android gpu rgb yuv

我复制粘贴了一些我在stackoverflow上找到的代码,将默认的相机预览YUV转换为RGB格式,然后将其上传到OpenGL进行处理。 这很好用,问题是大多数CPU都忙着将YUV图像转换成RGB格式,然后变成了瓶颈。

我想将YUV图像上传到GPU中,然后在片段着色器中将其转换为RGB。 我使用了相同的Java YUV到RGB功能,我发现它在CPU上工作,并试图让它在GPU上工作。

它变成了一个小噩梦,因为在Java和GPU上进行计算时存在一些差异。 首先,预览图像在Java中以byte []形式出现,但字节是有符号的,因此可能存在负值。

此外,片段着色器通常处理[0..1]浮点值而不是字节。

我确信这是可以解决的,我几乎解决了它。但是我花了几个小时试图弄清楚我做错了什么并且无法使它发挥作用。

最重要的是,我要求有人写这个着色器功能并最好测试它。对我来说,这将是一个繁琐的猴子工作,因为我不太明白为什么这种转换按照它的方式工作,我只是尝试模仿GPU上的相同功能。

这与我在Java上使用的函数非常相似: Displaying YUV Image in Android

我在CPU上完成了一些工作,例如将1.5 * w h字节YUV格式转换为w h * YUV,如下所示:

static public void decodeYUV420SP(int[] rgba, byte[] yuv420sp, int width,
        int height) {
    final int frameSize = width * height;

    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (int) yuv420sp[yp]+127;
            if ((i & 1) == 0) {
                v = (int)yuv420sp[uvp++]+127;
                u = (int)yuv420sp[uvp++]+127;
            }
            rgba[yp] = 0xFF000000+(y<<16) | (u<<8) | v;
        }
    }
}

我添加了127因为字节已签名。 然后我将rgba加载到OpenGL纹理中并尝试在GPU上进行剩余的计算。

任何帮助都会被贬低......

3 个答案:

答案 0 :(得分:2)

转换CPU听起来很容易,但我相信问题是如何在GPU上做到这一点?

我最近在我的项目中做到了这一点,我需要获得非常快速的QR码检测,即使相机角度与打印代码的表面成45度,并且它具有很好的性能:

(以下代码被修剪为仅包含关键行,假设您对Java和OpenGLES都有扎实的理解)

  1. 创建一个包含存储的摄像机图像的GL纹理:

    int[] txt = new int[1];
    GLES20.glGenTextures(1,txt,0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,txt[0]);
    GLES20.glTextParameterf(... set min filter to GL_LINEAR );
    GLES20.glTextParameterf(... set mag filter to GL_LINEAR );
    GLES20.glTextParameteri(... set wrap_s to GL_CLAMP_TO_EDGE );
    GLES20.glTextParameteri(... set wrap_t to GL_CLAMP_TO_EDGE );
    
  2. 注意纹理类型不是GL_TEXTURE_2D。这很重要,因为SurfaceTexture对象仅支持GL_TEXTURE_EXTERNAL_OES类型,这将在下一步中使用。

    1. 设置SurfaceTexture:

      SurfaceTexture surfTex = new SurfaceTeture(txt[0]);
      surfTex.setOnFrameAvailableListener(this); 
      
    2. 上面假设&#39;这个&#39;是一个实现&#39; onFrameAvailable&#39;功能

          public void onFrameAvailable(SurfaceTexture st)
          {
                  surfTexNeedUpdate = true;
                  // this flag will be read in GL render pipeline
          }
      
      1. 设置摄像头:

        Camera cam = Camera.open();
        cam.setPreviewTexture(surfTex);
        
      2. 如果您定位Android 5.0,则不推荐使用此相机API,因此如果您是,则必须使用新的CameraDevice API。

        1. 在渲染管道中,使用以下块来检查相机是否有可用的帧,并使用它更新表面纹理。更新曲面纹理时,将填充与其链接的GL纹理。

          if( surfTexNeedUpdate )
          {
                  surfTex.updateTexImage();
                  surfTexNeedUpdate = false;
          }
          
        2. 绑定具有Camera的GL纹理 - &gt; SurfaceTeture链接到,只需在渲染管道中执行此操作:

          GLES20.glBindTexture(GLES20.GL_TEXTURE_EXTERNAL_OS, txt[0]);
          
        3. 不言而喻,您需要设置当前活动纹理。

          1. 在你的GL着色器程序中,它将使用上面的纹理片段部分,你必须有第一行:

            #extension GL_OES_EGL_imiage_external : require
            
          2. 以上是必须的。

            纹理均匀必须是samplerExternalOES类型:

                uniform samplerExternalOES u_Texture0;
            

            从中读取像素就像从GL_TEXTURE_2D类型,并且UV坐标在相同的范围内(从0.0到1.0):

                vec4 px = texture2D(u_Texture0, v_UV);
            
            1. 准备好渲染管道后,使用上面的纹理和着色器渲染四边形,只需启动相机:

              cam.startPreview();
              
            2. 您应该在GL屏幕上看到带有实时相机输入的四元组。现在你只需要用glReadPixels抓取图像:

              GLES20.glReadPixels(0,0,width,height,GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bytes);
              
            3. 上面的行假设您的FBO是RGBA,并且该字节已经初始化为byte []数组到适当的大小,并且该宽度和高度是您的FBO的大小。

              瞧!您已从相机捕获RGBA像素,而不是转换onPreviewFrame回调中收到的YUV字节...

              如果你不需要,你也可以使用RGB帧缓冲对象并避免使用alpha。

              重要的是要注意,相机会调用onFrameAvailable自己的线程,而不是你的GL渲染管道线程,因此你不应该在该函数中执行任何GL调用。

答案 1 :(得分:0)

我使用维基百科的这段代码来计算GPU上从YUV到RGB的转换:

private static int convertYUVtoRGB(int y, int u, int v) {
    int r,g,b;

    r = y + (int)1.402f*v;
    g = y - (int)(0.344f*u +0.714f*v);
    b = y + (int)1.772f*u;
    r = r>255? 255 : r<0 ? 0 : r;
    g = g>255? 255 : g<0 ? 0 : g;
    b = b>255? 255 : b<0 ? 0 : b;
    return 0xff000000 | (b<<16) | (g<<8) | r;
}

我将浮点数转换为0.0..255.0,然后使用上面的代码。 CPU上的部分是将原始YUV像素重新排列成YUV矩阵(也在wikipdia中显示)。 基本上我使用维基百科代码并做了最简单的float&lt; - &gt;字节对话来使它成功。 像向Y添加16或者不向U和V添加128这样的小错误会产生不良结果。所以你需要照顾它。 但是,一旦我使用维基百科代码作为基础,这并不是很多工作。

答案 2 :(得分:0)

2011年2月,Renderscript首次引入。自Android 3.0 Honeycomb(API 11)以来,并且肯定是自Android 4.2 JellyBean(API 17)以来,添加ScriptIntrinsicYuvToRGB时,最简单,最有效的解决方案是use renderscript for YUV to RGB conversion。我最近有generalized这个解决方案来处理设备旋转。