Android: как отобразить предварительный просмотр камеры с обратным вызовом? - PullRequest
14 голосов
/ 02 декабря 2011

То, что мне нужно сделать, довольно просто, я хочу вручную отобразить предварительный просмотр с камеры, используя обратный вызов камеры, и я хочу получить как минимум 15 кадров в секунду на реальном устройстве. Мне даже не нужны цвета, мне просто нужно предварительно просмотреть изображение в градациях серого. Изображения с камеры в формате YUV, и вы должны как-то обрабатывать их, что является основной проблемой производительности. Я использую API 8.

Во всех случаях я использую camera.setPreviewCallbackWithBuffer (), который работает быстрее, чем camera.setPreviewCallback (). Кажется, что я не могу получить около 24 кадров в секунду, если я не отображаю предварительный просмотр. Так что проблема не в этом.

Я пробовал эти решения:

1. Отображение предварительного просмотра камеры в SurfaceView в виде растрового изображения. Это работает, но производительность составляет около 6 кадров в секунду.

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 (). Я могу получить около 12 кадров в секунду, но мне нужно применить некоторые фильтры для предварительного просмотра, и кажется, что это не может быть сделано быстро в OpenGL ES 1. Поэтому я не могу использовать это решение. Некоторые детали этого в другой вопрос . <Ч />

3. Отобразите предварительный просмотр камеры в (GL) SurfaceView, используя NDK для обработки данных YUV. Я нахожу решение здесь , которое использует некоторую функцию C и NDK. Но мне не удалось его использовать, вот некоторые подробности . Но в любом случае, это решение сделано для возврата ByteBuffer для отображения его в качестве текстуры в OpenGL, и он не будет быстрее, чем предыдущая попытка. Поэтому мне нужно изменить его так, чтобы он возвращал массив int [], который можно нарисовать с помощью canvas.drawBitmap (), но я недостаточно понимаю C, чтобы сделать это. <Ч />

Итак, есть ли какой-то другой способ, который я пропускаю, или какое-то улучшение попыток, которые я пробовал?

Ответы [ 6 ]

7 голосов
/ 06 декабря 2011

Я работаю над точно такой же проблемой, но не так далеко, как вы.

Рассматривали ли вы рисование пикселей непосредственно на холсте без предварительного кодирования их в 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 для каждого кадра), но это может бытьНачните.Я хотел бы посмотреть, работает ли это для вас или нет - я все еще борюсь с проблемами, ортогональными вашей в данный момент.

4 голосов
/ 16 декабря 2011

Я думаю, Майкл на правильном пути. Сначала вы можете попробовать этот метод для преобразования из 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);
}

Этот пример показывает обрезанный и искаженный выбор предварительного просмотра камеры в режиме реального времени, но вы поняли идею. Он работает на высоких FPS на Nexus S в оттенках серого и должен работать для ваших нужд.

1 голос
/ 04 декабря 2011

Для очень простых и простых эффектов есть

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

Я понял, что эти эффекты работают РАЗНО в зависимости от устройства.Например, на моем телефоне (galaxy s II) это выглядит как комический эффект, поскольку в отличие от galaxy s 1 это «просто» синий оттенок.

Это про: он работает в режиме предварительного просмотра.

Я осмотрел некоторые другие приложения камеры, и они, очевидно, тоже столкнулись с этой проблемой.Так что они сделали?Они захватывают изображение с камеры по умолчанию, применяют фильтр к растровым данным и показывают это изображение в простом ImageView.Это точно не так круто, как в режиме предварительного просмотра, но вы никогда не столкнетесь с проблемами производительности.

1 голос
/ 02 декабря 2011

Разве это не то, что вы хотите?Просто используйте SurfaceView в вашей компоновке, а затем где-нибудь в вашей инициализации, например onResume():

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

. Он просто отправляет кадры прямо в представление так же быстро, как они поступают.

Если выдля изменения оттенков серого измените параметры камеры с помощью setColorEffect("mono").

0 голосов
/ 28 августа 2013

Есть ли какая-то особая причина, по которой вы вынуждены использовать GLES 1.0?

Потому что, если нет, посмотрите принятый ответ здесь: Android SDK: получите необработанный предварительный просмотр изображения камеры без его показа

Обычно упоминается использование Camera.setPreviewTexture () в сочетании с GLES 2.0.В GLES 2.0 вы можете визуализировать полноэкранный квад-квадратик по всему экрану и создать любой эффект, какой захотите.

Скорее всего, это самый быстрый способ из возможных.

0 голосов
/ 07 февраля 2013

Мне кажется, я прочитал в блоге , что данные в градациях серого находятся в первых x * y байтах. Yuv должен представлять яркость, поэтому данные есть, хотя они не являются идеальными оттенками серого. Он отлично подходит для относительной яркости, но не для оттенков серого, так как каждый цвет не такой яркий, как в RGB. Зеленым обычно дается более сильный вес в преобразованиях светимости. Надеюсь, это поможет!

...