Android opengl 着色器程序将图像从相机复制到 SSBO 以进行 TF-lite GPU 推理

标签 android shader tensorflow-lite opengl-es-3.1

Tensorflow lite gpu 委托(delegate)文档提供了一种在 Android 中使用 Opengl 和 SSBO 运行 tflite 推理的更快方法[3]。该文档提供了示例代码来创建 SSBO 并将其与 图像已在 GPU 中。我们如何从 Android 实时摄像头复制或转换图像,并使用 OpenGL 着色器代码将其复制到 SSBO?当我们只是将 CPU 内存转储到 SSBO 时,性能会比 正常的 GPU 委托(delegate)执行。那么将相机图像传递给 SSBO 以使 tflite 推理更快的正确或最有效的方法是什么?

在下面的代码中,我们尝试将相机帧转换为位图 然后将其转换为纹理,最后复制到SSBO。然而,此方法比普通 GPU 委托(delegate)执行管道(其中数据从 CPU 复制到 GPU 的开销)相对慢。目的是减少 通过使图像数据在 GPU 内存中可用,然后将其传递给模型,将图像数据从 CPU 复制到 GPU。 我们能够使用标准 GPU 委托(delegate)推理机制以 40-50 毫秒的速度运行模型[1];而需要 90-100 毫秒 使用上述SSBO方法(未优化)。上述时间是指 在tensorflow lite中运行interpreter.run()方法的时间。 而且看起来这个 SSBO 机制只适用于 OpenGL ES 3.1 或更高版本。

理想的用例(按照 tensorflow 的建议)如下[2]:

  1. 您以表面纹理的形式获取相机输入。
  2. 创建 OpenGL 着色器存储缓冲区对象 (SSBO)。
  3. 使用 GPUDelegate.bindGlBufferToTensor() 将该 SSBO 与输入张量关联。

  4. 编写一个小型着色器程序,将 [1] 的表面纹理有效地转储到 [2] 的 SSBO 中。

  5. 运行推理。

我们能够以原始字节的形式获取相机帧或将其转换为纹理,甚至将其渲染到 GLSurface View 。 但我们无法实现 tensorflow 建议的加速。

  1. https://github.com/tensorflow/tensorflow/issues/26297
  2. https://github.com/tensorflow/tensorflow/issues/25657#issuecomment-466489248
  3. https://www.tensorflow.org/lite/performance/gpu_advanced#android_2

Android 代码:

public int[] initializeShaderBuffer(){
        android.opengl.EGLContext eglContext = eglGetCurrentContext();
        int[] id = new int[1];
        GLES31.glGenBuffers(id.length, id, 0);
        GLES31.glBindBuffer(GL_SHADER_STORAGE_BUFFER, id[0]);
        GLES31.glBufferData(GL_SHADER_STORAGE_BUFFER, 257*257*3*4, null, GLES31.GL_STREAM_COPY);

        GLES31.glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);// unbind
        return id;
    }

@Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
.....
.....

mTextureDataHandle0 = TextureHelper.loadTexture(mActivityContext,
                R.drawable.srcim);//No error

}


@Override
    public void onDrawFrame(GL10 glUnused) {





        int inputSsboId = initializeShaderBuffer()[0];

        interpreter = new Interpreter(GLActivity.tfliteModel);

        Tensor inputTensor = interpreter.getInputTensor(0);
        GpuDelegate gpuDelegate = new GpuDelegate();
        gpuDelegate.bindGlBufferToTensor(inputTensor, inputSsboId);
        interpreter.modifyGraphWithDelegate(gpuDelegate);



final int computeShaderHandle = ShaderHelper.compileShader(
                GLES31.GL_COMPUTE_SHADER, fragmentShader);//No error
            mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle,
                    computeShaderHandle);//No error 

mTextureUniformHandle0 = GLES31.glGetUniformLocation(mProgramHandle,
            "u_Texture0");


/**
         * First texture map
         */
        // Set the active texture0 unit to texture unit 0.
        GLES31.glActiveTexture(GLES31.GL_TEXTURE0 );

        // Bind the texture to this unit.
        GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mTextureDataHandle0);

        // Tell the texture uniform sampler to use this texture in the shader by
        // binding to texture unit 0.
        GLES31.glUniform1i(mTextureUniformHandle0, 0);


        GLES31.glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, inputSsboId, 0, 257*257*3*4);

        GLES31.glUseProgram(mProgramHandle);
        if(compute==1)//Always set to 1
            GLES31.glDispatchCompute(16,16,1);

        GLES31.glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);  // unbind
        GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);  // unbind


        //Tflite code ...


        byte [][] outputArray = new byte [1][66049];//size based on model output
        Log.d("GPU_CALL_RUN","DONE");
        long oms1=System.currentTimeMillis();
        interpreter.run(null,outputArray);

        long cms1=System.currentTimeMillis();
        Log.d("TIME_RUN_MODEL",""+(cms1-oms1));

        Log.d("OUTVAL", Arrays.deepToString(outputArray));

}

计算着色器:-

#version 310 es
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0) uniform sampler2D u_Texture0;
layout(std430) buffer;
layout(binding = 1) buffer Output { float elements[]; } output_data;
void main() {
    ivec2 gid = ivec2(gl_GlobalInvocationID.xy);
    //if (gid.x >= 257 || gid.y >= 257) return;
    vec3 pixel = texelFetch(u_Texture0, gid, 0).xyz;
    int linear_index = 3 * (gid.y * 257 + gid.x);
    output_data.elements[linear_index + 0] = pixel.x;
    output_data.elements[linear_index + 1] = pixel.y;
    output_data.elements[linear_index + 2] = pixel.z;
}

最佳答案

没有简单的方法可以直接将 SurfaceTexture 转储到 SSBO。最简单的路径是 SurfaceTexture -> GlTexture -> SSBO。 TFLite GPU 团队还尝试引入另一个 API (bindGlTextureToTensor),但在此之前,这里是我用于 GlTexutre -> SSBO 转换的着色器程序:

    #version 310 es

    layout(local_size_x = 16, local_size_y = 16) in;
    layout(binding = 0) uniform sampler2D input_texture;
    layout(std430) buffer;
    layout(binding = 1) buffer Output { float elements[]; } output_data;

    void main() {
      ivec2 gid = ivec2(gl_GlobalInvocationID.xy);
      if (gid.x >= 224 || gid.y >= 224) return;
      vec3 pixel = texelFetch(input_texture, gid, 0).xyz;
      int linear_index = 3 * (gid.y * 224 + gid.x);
      output_data.elements[linear_index + 0] = pixel.x;
      output_data.elements[linear_index + 1] = pixel.y;
      output_data.elements[linear_index + 2] = pixel.z;
    }

请注意,这是针对输入张量大小为 224x224x3 的 MobileNet v1。

关于Android opengl 着色器程序将图像从相机复制到 SSBO 以进行 TF-lite GPU 推理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55165114/

相关文章:

android - 如何将布局中心放置在另一个布局的右上角?

android - ContentProvider 调用原子? onPause保存,加载OnActivityCreated,旧数据

javascript - 在 WebGL 循环中使用制服(或类似)的解决方法?

c++ - opengl着色器定向灯镜面反射随距离增加

swift - 如何不基于图像分类快速运行tflite模型

android - 在android kitkat中刷新画廊

android - 如何实现自定义 SearchView 布局?

javascript - webgl 试图画一个三 Angular 形

android - 将模型转换为 Tensorflow-lite 时出错

android - Tensorflow 图像分类 Android 示例无法构建