Android:如何通过回调显示相机预览?
Posted
技术标签:
【中文标题】Android:如何通过回调显示相机预览?【英文标题】:Android: how to display camera preview with callback? 【发布时间】:2011-12-02 00:15:14 【问题描述】:我需要做的很简单,我想使用相机回调手动显示来自相机的预览,并且我想在真实设备上获得至少 15fps。我什至不需要颜色,我只需要预览灰度图像。 来自相机的图像是 YUV 格式,您必须以某种方式对其进行处理,这是主要的性能问题。我正在使用 API 8。
在所有情况下,我都使用 camera.setPreviewCallbackWithBuffer(),它比 camera.setPreviewCallback() 快。如果我不显示预览,我似乎无法在这里获得大约 24 fps。所以没有问题。
我已经尝试了这些解决方案:
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);
2。在 GLSurfaceView 上将相机预览显示为纹理。 这里我只显示亮度数据(灰度图像),这很容易,每帧只需要一个 arraycopy()。我可以获得大约 12fps,但我需要对预览应用一些过滤器,而且似乎在 OpenGL ES 1 中无法快速完成。所以我不能使用这个解决方案。 another question 中的一些细节。
3.使用 NDK 在 (GL)SurfaceView 上显示相机预览以处理 YUV 数据。我找到了一个解决方案here,它使用了一些 C 函数和 NDK。但我没有设法使用它,here some more details。但无论如何,这个解决方案是返回 ByteBuffer 以在 OpenGL 中将其显示为纹理,并且不会比之前的尝试快。所以我必须修改它以返回 int[] 数组,可以用 canvas.drawBitmap() 绘制,但我对 C 的理解不足以做到这一点。
那么,我是否还有其他方法遗漏或改进了我尝试的尝试?
【问题讨论】:
【参考方案1】:我正在处理完全相同的问题,但还没有达到你的水平。
您是否考虑过将像素直接绘制到画布上而不先将它们编码为 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),但这可能是一个开始。我很想看看它是否适合你——我目前仍在与与你正交的问题作斗争。
【讨论】:
我明天会试试这个,但我很确定不会有任何性能提升,因为它的工作原理或多或少与带有 YuvImage 和 BitmapFactory 类的版本相同。但我会试一试,我们会看到的。谢谢。 所以,正如我所料,它的性能在所有情况下都是最差的,即使我尝试对其进行一点优化...... 我在这一行得到 java.lang.ArrayIndexOutOfBoundsException int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0 ]));【参考方案2】:我认为迈克尔走在正确的轨道上。首先你可以试试这个方法将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 运行,应该也可以满足您的需求。
【讨论】:
Second, don't create a ton of work for the garbage collector.
。您的onDraw
告诉我们否则。【参考方案3】:
这不是你想要的吗?只需在您的布局中使用 SurfaceView,然后在您的 init 中的某个位置,例如 onResume()
:
SurfaceView surfaceView = ...
SurfaceHolder holder = surfaceView.getHolder();
...
Camera camera = ...;
camera.setPreviewDisplay(holder);
它只是在帧到达时将帧直接发送到视图。
如果要灰度,用setColorEffect("mono")
修改相机参数。
【讨论】:
谢谢,但我确实需要手动使用回调和显示图像。我不仅需要灰度图像,我正在使用更多过滤器,我在我的问题中没有提到......【参考方案4】:对于非常基本和简单的效果,有
Camera.Parameters parameters = mCamera.getParameters();
parameters.setColorEffect(Parameters.EFFECT_AQUA);
我发现这种效果因设备而异。 例如,在我的手机(galaxy s II)上,它看起来有点像漫画效果,而与 Galaxy s 1 相比,它“只是”一个蓝色阴影。
它是专业的:它可以作为实时预览。
我查看了其他一些相机应用程序,他们显然也遇到了这个问题。 那么他们做了什么? 他们正在捕获默认的相机图像,对位图数据应用过滤器,并在简单的 ImageView 中显示该图像。它肯定没有实时预览那么酷,但你永远不会遇到性能问题。
【讨论】:
谢谢,我知道这些相机效果,但我想做一些对比度调整,颜色替换等。但是你是对的,看起来实时预览真的没有一个好的工作解决方案,所以我必须做你描述的事情......:/【参考方案5】:我相信我在blog 中读到灰度数据位于前 x*y 字节中。 Yuv 应该代表亮度,所以数据就在那里,虽然它不是一个完美的灰度。它非常适合相对亮度,但不适用于灰度,因为在 rgb 中每种颜色都不像彼此那么亮。绿色通常在光度转换中被赋予更大的权重。希望这会有所帮助!
【讨论】:
【参考方案6】:有什么特殊原因迫使您使用 GLES 1.0 吗?
因为如果不是,请在此处查看接受的答案: Android SDK: Get raw preview camera image without displaying it
通常它提到将 Camera.setPreviewTexture() 与 GLES 2.0 结合使用。 在 GLES 2.0 中,您可以在整个屏幕上渲染全屏四边形,并创建您想要的任何效果。
这很可能是最快的方式。
【讨论】:
示例:github.com/google/grafika/blob/master/src/com/android/grafika/…。它目前有一个着色器,可将黑白图像置于红色通道中(搜索“rosyFragment”)。为 R/G/B 分配相同的值将产生黑白图像。以上是关于Android:如何通过回调显示相机预览?的主要内容,如果未能解决你的问题,请参考以下文章