2011-12-02 116 views
14

首先 - 我已经研究了一段时间,并尝试了几种解决方案,所以不要给我发现你在谷歌上找到的第一个链接。Android:如何使用回调显示相机预览?

我需要做的事很简单,我想用相机回调手动显示相机预览,并且我想在真实设备上至少获得15fps。我甚至不需要颜色,我只需要预览灰度图像。 来自相机的图像是YUV格式,您必须以某种方式处理它,这是主要的性能问题。我正在使用API​​ 8.

在所有情况下,我使用camera.setPreviewCallbackWithBuffer(),它比camera.setPreviewCallback()更快。如果我不显示预览,看起来我无法在这里获得大约24帧/秒的速度。所以没有问题。

我曾尝试以下解决方案:

1.显示相机预览上SurfaceView为位图。它的工作原理,但性能约为6fps。

baos = new ByteOutputStream(); 
yuvimage=new YuvImage(cameraFrame, ImageFormat.NV21, prevX, prevY, null); 

yuvimage.compressToJpeg(new Rect(0, 0, prevX, prevY), 80, baos); 
jdata = baos.toByteArray(); 

bmp = BitmapFactory.decodeByteArray(jdata, 0, jdata.length); // Convert to Bitmap, this is the main issue, it takes a lot of time 

canvas.drawBitmap(bmp , 0, 0, paint); 
上的GLSurfaceView


2.显示相机预览作为纹理。在这里,我只显示亮度数据(灰度图像),这是很容易的,它只需要在每个帧上的一个arraycopy()。我可以获得大约12fps,但我需要应用一些过滤器预览,看来,它不能在OpenGL ES 1中快速完成。所以我不能使用此解决方案。这个的一些细节在another question


3.使用NDK处理YUV数据一个(GL)SurfaceView显示相机预览。我找到一个解决方案here,它使用了一些C函数和NDK。但我没有设法使用它,here some more details。但无论如何,这个解决方案是为了返回ByteBuffer在OpenGL中将其显示为纹理,并且不会比以前的尝试更快。所以我将不得不修改它返回int []数组,可以用canvas.drawBitmap()绘制,但我不明白C足以做到这一点。


那么,有没有其他的方式,我失踪或尝试一些改进?

感谢您的回复!

回答

1

这不是你想要的吗?只需在您的init使用SurfaceView在你的布局,那么类似的地方onResume()

SurfaceView surfaceView = ... 
SurfaceHolder holder = surfaceView.getHolder(); 
... 
Camera camera = ...; 
camera.setPreviewDisplay(holder); 

它只是发送帧直奔视图一样快,他们到达。

如果您想要灰度,请使用setColorEffect("mono")修改相机参数。

+0

谢谢,但我真的需要手动使用回调和显示图像。我不需要灰度图像,我使用了一些更多的滤镜,我没有在我的问题中提到... –

1

对于非常基本的和简单的效果,有

Camera.Parameters parameters = mCamera.getParameters(); 
parameters.setColorEffect(Parameters.EFFECT_AQUA); 

我想通了,有什么不同取决于设备此效果。 例如,在我的手机(银河系列II)上,它看起来有点像漫画效果,与银河系1相反,它只是一个蓝色阴影。

它的专业:它的工作作为实时预览。

我环顾了一些其他相机应用程序,他们显然也面临这个问题。 那他们做了什么? 他们捕获默认的摄像头图像,对位图数据应用过滤器,并在简单的ImageView中显示该图像。在现场预览中肯定不会那么酷,但是你永远不会遇到性能问题。

+0

谢谢,我知道这些相机效果,但我想做一些对比度调整,颜色替换等等。但是你是对的,似乎真的没有一个好的工作解决方案来进行实时预览,所以我将不得不像你所描述的那样做......:/ –

7

我正在研究完全相同的问题,但还没有达到您的要求。

您是否曾考虑将像素直接绘制到画布上,而不是先将它们编码为JPEG?在OpenCV套件http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3.1/OpenCV-2.3.1-android-bin.tar.bz2/download(它实际上并不使用opencv;不用担心)中,有一个名为tutorial-0-androidcamera的项目,演示如何将YUV像素转换为RGB,然后将它们直接写入位图。

相关的代码基本上是:

public void onPreviewFrame(byte[] data, Camera camera, int width, int height) { 
    int frameSize = width*height; 
    int[] rgba = new int[frameSize+1]; 

    // Convert YUV to RGB 
    for (int i = 0; i < height; i++) 
     for (int j = 0; j < width; j++) { 
      int y = (0xff & ((int) data[i * width + j])); 
      int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0])); 
      int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1])); 
      y = y < 16 ? 16 : y; 

      int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128)); 
      int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128)); 
      int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128)); 

      r = r < 0 ? 0 : (r > 255 ? 255 : r); 
      g = g < 0 ? 0 : (g > 255 ? 255 : g); 
      b = b < 0 ? 0 : (b > 255 ? 255 : b); 

      rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r; 
     } 

    Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 
    bmp.setPixels(rgba, 0/* offset */, width /* stride */, 0, 0, width, height); 
    Canvas canvas = mHolder.lockCanvas(); 
    if (canvas != null) { 
     canvas.drawBitmap(bmp, (canvas.getWidth() - width)/2, (canvas.getHeight() - height)/2, null); 
     mHolder.unlockCanvasAndPost(canvas); 
    } else { 
     Log.w(TAG, "Canvas is null!"); 
    } 
    bmp.recycle(); 
} 

当然,你必须去适应它,以满足您的需求(当然不分配RGBA每一帧),但它可能是一个开始。我很想看看它是否适用于您 - 目前我仍然在与您的问题交战。

+1

明天我会试试这个,但我是相当肯定没有性能改进,因为这与YuvImage和BitmapFactory类的版本差不多。但我会试一试,我们会看到的。谢谢。 –

+1

所以,正如我所预料的那样,它在所有情况下都表现最差,即使我试图优化它一点...... –

4

我认为迈克尔在正确的轨道上。首先,您可以尝试使用此方法将RGB从RGB转换为灰度。显然它和他的做法几乎一样,但是对于你想要的更简洁一点。

//YUV Space to Greyscale 
static public void YUVtoGrayScale(int[] rgb, byte[] yuv420sp, int width, int height){ 
    final int frameSize = width * height; 
    for (int pix = 0; pix < frameSize; pix++){ 
     int pixVal = (0xff & ((int) yuv420sp[pix])) - 16; 
     if (pixVal < 0) pixVal = 0; 
     if (pixVal > 255) pixVal = 255; 
     rgb[pix] = 0xff000000 | (pixVal << 16) | (pixVal << 8) | pixVal; 
    } 
} 

}

其次,不要创建工作吨的垃圾收集器。你的位图和数组将会是一个固定的大小。创建一次,而不是在onFramePreview。

这样做,你会的东西,看起来像这样结束:

public PreviewCallback callback = new PreviewCallback() { 
    @Override 
    public void onPreviewFrame(byte[] data, Camera camera) { 
     if ((mSelectView == null) || !inPreview) 
      return; 
     if (mSelectView.mBitmap == null) 
     { 
      //initialize SelectView bitmaps, arrays, etc 
      //mSelectView.mBitmap = Bitmap.createBitmap(mSelectView.mImageWidth, mSelectView.mImageHeight, Bitmap.Config.RGB_565); 
      //etc 

     } 
     //Pass Image Data to SelectView 
     System.arraycopy(data, 0, mSelectView.mYUVData, 0, data.length); 
     mSelectView.invalidate(); 
    } 
}; 

然后,你想要把它看起来像这样的画布:

class SelectView extends View { 
Bitmap mBitmap; 
Bitmap croppedView; 
byte[] mYUVData; 
int[] mRGBData; 
int mImageHeight; 
int mImageWidth; 

public SelectView(Context context){ 
    super(context); 
    mBitmap = null; 
    croppedView = null; 
} 

@Override 
protected void onDraw(Canvas canvas){ 
    if (mBitmap != null) 
    { 
     int canvasWidth = canvas.getWidth(); 
     int canvasHeight = canvas.getHeight(); 
     // Convert from YUV to Greyscale 
     YUVtoGrayScale(mRGBData, mYUVData, mImageWidth, mImageHeight); 
      mBitmap.setPixels(mRGBData, 0, mImageWidth, 0, 0, mImageWidth, mImageHeight); 
      Rect crop = new Rect(180, 220, 290, 400); 
     Rect dst = new Rect(0, 0, canvasWidth, (int)(canvasHeight/2)); 
     canvas.drawBitmap(mBitmap, crop, dst, null); 
    } 
    super.onDraw(canvas); 
} 

这个例子显示了一个裁剪和扭曲的实时相机预览选择,但你明白了。它在Nexus S上以高灰度级以高FPS运行,并且应该可以满足您的需求。

+0

'其次,不要为垃圾收集器创建大量工作。你的'onDraw'告诉我们,否则。 –

0

我相信我读了blog灰度数据是在第一个x * y字节。 Yuv应该代表亮度,所以数据在那里,尽管它不是一个完美的灰度。它对于相对亮度很好,但不是灰度,因为每种颜色在rgb中都不像其他亮度那样明亮。在光度转换中,绿色通常被赋予更强的权重。希望这可以帮助!

0

有没有什么特别的理由让你被迫使用GLES 1.0?

因为如果没有,看到这里接受的答案: Android SDK: Get raw preview camera image without displaying it

一般来说,提到组合使用Camera.setPreviewTexture()与GLES 2.0。 在GLES 2.0中,您可以在整个屏幕上呈现全屏四合一,并创建您想要的任何效果。

这很可能是最快的方式。

+1

示例:https://github.com/google/grafika/blob/master/src/com/android/grafika/CameraCaptureActivity.java。它目前有一个着色器,将黑白图像放入红色通道(搜索“rosyFragment”)。为R/G/B分配相同的值将产生黑白图像。 – fadden