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]:
- 您以表面纹理的形式获取相机输入。
- 创建 OpenGL 着色器存储缓冲区对象 (SSBO)。
使用
GPUDelegate.bindGlBufferToTensor()
将该 SSBO 与输入张量关联。编写一个小型着色器程序,将 [1] 的表面纹理有效地转储到 [2] 的 SSBO 中。
运行推理。
我们能够以原始字节的形式获取相机帧或将其转换为纹理,甚至将其渲染到 GLSurface View 。 但我们无法实现 tensorflow 建议的加速。
- https://github.com/tensorflow/tensorflow/issues/26297
- https://github.com/tensorflow/tensorflow/issues/25657#issuecomment-466489248
- 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/