如何将自定义滤镜应用于相机输出中的单帧,并显示它们。
到目前为止我尝试过:
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映射将如何工作?
答案 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,但也没有一个好的解决方案......