6

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

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

这变成了一场噩梦,因为在 Java 和 GPU 上进行计算存在一些差异。首先,预览图像在Java中是字节[],但是字节是有符号的,所以可能有负值。

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

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

最重要的是,我要求有人编写这个着色器函数并最好对其进行测试。对我来说,这将是一项乏味的猴子工作,因为我真的不明白为什么这种转换会以这种方式工作,我只是尝试在 GPU 上模仿相同的功能。

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

我在CPU上做了一些工作,比如把1.5*w h bytes的YUV格式转成aw 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 上进行其余的计算。

任何帮助将不胜感激...

4

3 回答 3

1

我使用来自 wikipedia 的这段代码来计算 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<->byte conersions 来解决这个问题。像在 Y 上加 16 或在 U 和 V 上不加 128 这样的小错误会产生不良结果。所以你需要照顾它。但是,一旦我使用维基百科代码作为基础,这并不是很多工作。

于 2013-01-03T21:28:00.700 回答
1

在 CPU 上转换听起来很容易,但我相信问题是如何在 GPU 上进行转换?

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

(以下代码被剪裁以包含关键行,假设您对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 );
    

注意纹理类型不是 GL_TEXTURE_2D。这很重要,因为 SurfaceTexture 对象仅支持 GL_TEXTURE_EXTERNAL_OES 类型,将在下一步中使用该类型。

  1. 设置表面纹理:

    SurfaceTexture surfTex = new SurfaceTeture(txt[0]);
    surfTex.setOnFrameAvailableListener(this); 
    

以上假设“this”是一个实现“onFrameAvailable”功能的对象。

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

    Camera cam = Camera.open();
    cam.setPreviewTexture(surfTex);
    

如果您以 Android 5.0 为目标,则不推荐使用此 Camera API,因此如果您是,则必须使用新的 CameraDevice API。

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

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

    GLES20.glBindTexture(GLES20.GL_TEXTURE_EXTERNAL_OS, txt[0]);
    

不用说,您需要设置当前的活动纹理。

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

    #extension GL_OES_EGL_imiage_external : require
    

以上是必须的。

纹理统一必须是 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);
    

上面的行假设您的 FBO 是 RGBA,并且 bytes 已经初始化为适当大小的 byte[] 数组,并且宽度和高度是您的 FBO 的大小。

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

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

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

于 2015-10-23T06:41:12.003 回答
0

In February 2011, Renderscript was first introduced. Since Android 3.0 Honeycomb (API 11), and definitely since Android 4.2 JellyBean (API 17), when ScriptIntrinsicYuvToRGB was added, the easiest and most efficient solution has been to use renderscript for YUV to RGB conversion. I have recently generalized this solution to handle device rotation.

于 2020-08-25T08:51:19.687 回答