我复制粘贴了一些我在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 * wh字节的YUV格式转换为wh * 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上进行剩余的计算.
任何帮助都会被贬低……
最佳答案 转换CPU听起来很容易,但我相信问题是如何在GPU上做到这一点?
我最近在我的项目中做到了这一点,我需要获得非常快速的QR码检测,即使相机角度与打印代码的表面成45度,并且它具有很好的性能:
(以下代码被修剪为仅包含关键行,假设您对Java和OpenGLES都有扎实的了解)
>创建将包含存储的Camera图像的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类型,这将在下一步中使用.
>设置SurfaceTexture:
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
}
>设置相机:
Camera cam = Camera.open();
cam.setPreviewTexture(surfTex);
如果您定位Android 5.0,则不推荐使用此Camera API,因此如果您是,则必须使用新的CameraDevice API.
>在渲染管道中,使用以下块来检查相机是否有可用的帧,并使用它更新表面纹理.更新曲面纹理时,将填充与其链接的GL纹理.
if( surfTexNeedUpdate )
{
surfTex.updateTexImage();
surfTexNeedUpdate = false;
}
>绑定具有Camera的GL纹理 – > SurfaceTeture链接到,只需在渲染管道中执行此操作:
GLES20.glBindTexture(GLES20.GL_TEXTURE_EXTERNAL_OS, txt[0]);
不言而喻,您需要设置当前活动纹理.
>在你的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);
>一旦准备好渲染管道渲染具有上面纹理和着色器的四边形,只需启动相机:
cam.startPreview();
>你应该在GL屏幕上看到带有实时摄像头输入的四边形.现在你只需要使用glReadPixels获取图像:
GLES20.glReadPixels(0,0,width,height,GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bytes);
上面的行假设您的FBO是RGBA,并且该字节已经初始化byte []数组到适当的大小,并且该宽度和高度是您的FBO的大小.
瞧!您已从相机捕获RGBA像素,而不是转换onPreviewFrame回调中收到的YUV字节…
如果不需要,也可以使用RGB帧缓冲对象并避免使用alpha.
重要的是要注意摄像机将调用onFrameAvailable在它自己的线程中,而不是你的GL渲染管道线程,因此你不应该在该函数中执行任何GL调用.