【问题标题】:Android: how to display camera preview with callback?Android:如何通过回调显示相机预览?
【发布时间】: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 的理解不足以做到这一点。


那么,我是否还有其他方法遗漏或改进了我尝试的尝试?

【问题讨论】:

    标签: java android camera android-camera preview


    【解决方案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 中,您可以在整个屏幕上渲染全屏四边形,并创建您想要的任何效果。

      这很可能是最快的方式。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-15
      • 2019-09-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多