将自定义滤镜应用于相机输出

时间:2011-12-03 21:09:29

标签: android opengl-es filter camera

如何将自定义滤镜应用于相机输出中的单帧,并显示它们。

到目前为止我尝试过:

mCamera.setPreviewCallback(new CameraGreenFilter());

public class CameraGreenFilter implements PreviewCallback {

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        final int len = data.length;
        for(int i=0; i<len; ++i){
            data[i] *= 2;
        }
    }
}
  • 虽然它的名字包含“绿色”但我实际上只是想以某种方式修改这些值(在这种情况下,颜色会稍微加强一些)。长话短说,它不起作用。

  • 我发现字节数组'data'是摄像头输出的副本;但这并没有多大帮助,因为我需要'真正的'缓冲区。

  • 我听说你可以用openGL实现这个。这听起来很复杂。

有更简单的方法吗?否则,这个openGL-surfaceView映射将如何工作?

1 个答案:

答案 0 :(得分:38)

好的,有几种方法可以做到这一点。但是性能存在严重问题。来自摄像机的byte []位于YUV format,如果要显示它,必须将其转换为某种RGB格式。这种转换操作非常昂贵,并且显着降低了输出fps。

这取决于您实际想要对相机预览做什么。因为最好的解决方案是在没有回调的情况下绘制相机预览,并对相机预览进行一些效果。这是做有争议的真实性的常用方法。

但是如果你真的需要手动显示输出,有几种方法可以做到这一点。您的示例由于多种原因不起作用。首先,您根本不显示图像。如果你这样称呼:

mCamera.setPreviewCallback(new CameraGreenFilter());
mCamera.setPreviewDisplay(null);

比你的相机根本没有显示预览,你必须手动显示它。并且你不能在onPreviewFrame方法中做任何昂贵的操作,因为数据的生命周期是有限的,它会在下一帧被覆盖。一个提示,使用setPreviewCallbackWithBuffer,它更快,因为它重用一个缓冲区而不必在每个帧上分配新的内存。

所以你必须做这样的事情:

private byte[] cameraFrame;
private byte[] buffer;
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    cameraFrame = data;
    addCallbackBuffer(data); //actually, addCallbackBuffer(buffer) has to be called once sowhere before you call mCamera.startPreview();
}


private ByteOutputStream baos;
private YuvImage yuvimage;
private byte[] jdata;
private Bitmap bmp;
private Paint paint;

@Override //from SurfaceView
public void onDraw(Canvas canvas) {
    baos = new ByteOutputStream();
    yuvimage=new YuvImage(cameraFrame, ImageFormat.NV21, prevX, prevY, null);

    yuvimage.compressToJpeg(new Rect(0, 0, width, height), 80, baos); //width and height of the screen
    jdata = baos.toByteArray();

    bmp = BitmapFactory.decodeByteArray(jdata, 0, jdata.length);

    canvas.drawBitmap(bmp , 0, 0, paint);
    invalidate(); //to call ondraw again
}

要使其工作,您需要在类构造函数或某处调用setWillNotDraw(false)

在onDraw中,如果要修改颜色,可以应用paint.setColorFilter(filter)。如果你愿意,我可以发布一些例子。

所以这会起作用,但性能会很低(小于8fps),因为BitmapFactory.decodeByteArray很慢。您可以尝试使用本机代码和android NDK将YUV中的数据转换为RGB,但这非常复杂。

另一种选择是使用openGL ES。您需要GLSurfaceView,将相机框架绑定为纹理(在GLSurfaceView中实现Camera.previewCallback,因此您使用onPreviewFrame的方式与常规表面相同)。但是存在同样的问题,需要转换YUV数据。有一次机会 - 您可以非常快速地显示预览(灰度图像)中的亮度数据,因为YUV中字节数组的前半部分只是没有颜色的亮度数据。所以在onPreviewFrame上你使用arraycopy来复制数组的前半部分,而不是像这样绑定纹理:

gl.glGenTextures(1, cameraTexture, 0);
int tex = cameraTexture[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_LUMINANCE, 
    this.prevX, this.prevY, 0, GL10.GL_LUMINANCE, 
    GL10.GL_UNSIGNED_BYTE, ByteBuffer.wrap(this.cameraFrame)); //cameraFrame is the first half od byte[] from onPreviewFrame

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);

你不能以这种方式获得大约16-18 fps,你可以使用openGL制作一些过滤器。如果你愿意的话,我可以给你发一些代码,但这里放的时间太长了......

有关更多信息,您可以看到我的simillar question,但也没有一个好的解决方案......