我需要做的很简单,我想使用相机回调手动显示来自相机的预览,我想在真实设备上获得至少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数据。我找到了一个使用一些C函数和NDK的解决方案here。但我没有设法使用它,here some more details。但无论如何,这个解决方案是为了返回ByteBuffer在OpenGL中将其显示为纹理,并且它不会比之前的尝试更快。所以我必须修改它以返回int []数组,可以使用canvas.drawBitmap()绘制,但我不理解C足以执行此操作。
<小时/>
那么,有没有其他方法可以让我失踪或者我尝试的尝试有所改善?
答案 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 :(得分:4)
我认为迈克尔走在正确的轨道上。首先,您可以尝试使用此方法将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运行,也可以满足您的需求。
答案 2 :(得分:1)
这不是你想要的吗?只需在布局中使用SurfaceView,然后在初始化的某个位置使用onResume()
:
SurfaceView surfaceView = ...
SurfaceHolder holder = surfaceView.getHolder();
...
Camera camera = ...;
camera.setPreviewDisplay(holder);
它只是在帧到达时将帧直接发送到视图。
如果您想要灰度,请使用setColorEffect("mono")
修改相机参数。
答案 3 :(得分:1)
对于非常基本和简单的效果,有
Camera.Parameters parameters = mCamera.getParameters();
parameters.setColorEffect(Parameters.EFFECT_AQUA);
我发现这种效果根据设备的不同而不同。 例如,在我的手机(galaxy s II)上,它看起来有点像漫画效果,与星系相比,它只是“蓝色”。
这是专业版:它正在进行实时预览。
我环顾了其他一些相机应用程序,他们显然也遇到了这个问题。 那么他们做了什么? 他们捕获默认的摄像机图像,对位图数据应用滤镜,并在简单的ImageView中显示此图像。它肯定不像现场预览那样酷,但你不会遇到性能问题。
答案 4 :(得分:0)
我相信我在blog中读到灰度数据在前x * y个字节中。 Yuv应该代表亮度,因此数据存在,尽管它不是完美的灰度。它非常适合相对亮度,但不适合灰度,因为每种颜色在rgb中都不如彼此亮。绿色通常在光度转换中具有更强的重量。希望这有帮助!
答案 5 :(得分:0)
您是否有任何特殊原因被迫使用GLES 1.0?
因为如果没有,请在此处查看接受的答案: Android SDK: Get raw preview camera image without displaying it
一般来说,它提到将Camera.setPreviewTexture()与GLES 2.0结合使用。 在GLES 2.0中,您可以在整个屏幕上渲染全屏四边形,并创建您想要的任何效果。
这很可能是最快的方式。