簡體   English   中英

Android:如何使用回調顯示相機預覽?

[英]Android: how to display camera preview with callback?

我需要做的很簡單,我想使用相機回調手動顯示來自相機的預覽,我想在真實設備上獲得至少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中快速完成。所以我不能使用這個解決方案。 另一個問題中的一些細節。


3.使用NDK在(GL)SurfaceView上顯示相機預覽以處理YUV數據。 在這里找到了一個使用一些C函數和NDK的解決方案。 但我沒有設法使用它, 這里有更多細節 但無論如何,這個解決方案是為了返回ByteBuffer在OpenGL中將其顯示為紋理,並且它不會比之前的嘗試更快。 所以我必須修改它以返回int []數組,可以使用canvas.drawBitmap()繪制,但我不理解C足以執行此操作。


那么,我有沒有其他方法可以避免或嘗試一些改進?

我正在研究完全相同的問題,但還沒有達到你的目標。

您是否考慮過將像素直接繪制到畫布而不先將其編碼為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),但它可能是一個開始。 我很想知道它是否適合你 - 我現在還在與你的問題正交。

我認為邁克爾在正確的軌道上。 首先,您可以嘗試使用此方法將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運行,也可以滿足您的需求。

這不是你想要的嗎? 只需在布局中使用SurfaceView,然后在init中的某個位置使用onResume()

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

它只是在幀到達時將幀直接發送到視圖。

如果需要灰度,請使用setColorEffect("mono")修改相機參數。

對於非常基本和簡單的效果,有

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

我發現這種效果會根據設備的不同而有所不同。 例如,在我的手機(galaxy s II)上,它看起來有點像漫畫效果,與星系相比,它只是一個藍色的陰影。

它是專業的:它作為實時預覽工作。

我環顧了一些其他相機應用程序,他們顯然也遇到了這個問題。 那么他們做了什么? 他們捕獲默認的攝像機圖像,對位圖數據應用濾鏡,並在簡單的ImageView中顯示此圖像。 這肯定不像實時預覽那樣酷,但你不會遇到性能問題。

我相信我在博客中讀到灰度數據在第一個x * y字節中。 Yuv應該代表亮度,因此數據存在,盡管它不是完美的灰度。 它非常適合相對亮度,但不適合灰度,因為每種顏色在rgb中都不如彼此亮。 綠色通常在光度轉換中具有更強的重量。 希望這可以幫助!

您是否有任何特殊原因被迫使用GLES 1.0?

因為如果沒有,請在此處查看接受的答案: Android SDK:獲取原始預覽相機圖像而不顯示它

通常它會提到將Camera.setPreviewTexture()與GLES 2.0結合使用。 在GLES 2.0中,您可以在整個屏幕上渲染全屏四邊形,並創建您想要的任何效果。

這很可能是最快的方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM