Как я могу управлять предварительным просмотром камеры? - PullRequest
36 голосов
/ 25 июня 2011

Существует несколько учебных пособий, в которых объясняется, как настроить простой предварительный просмотр камеры на устройстве Android. Но я не смог найти ни одного примера, который объясняет, как манипулировать изображением до его рендеринга.
Я хочу реализовать собственные цветовые фильтры для имитации, например, красный и / или зеленый дефицит.

Ответы [ 3 ]

53 голосов
/ 03 июля 2011

Я провел некоторое исследование по этому вопросу и собрал рабочий (ish) пример.Вот что я нашел.Получить исходные данные с камеры довольно просто.Он возвращается как байтовый массив YUV.Вам нужно будет нарисовать его вручную на поверхности, чтобы иметь возможность изменить его.Для этого вам понадобится SurfaceView, с которым вы можете вручную запускать вызовы отрисовки.Для этого можно установить несколько флагов.

Для того, чтобы выполнить вызов отрисовки вручную, вам необходимо преобразовать массив байтов в какое-то растровое изображение.На этом этапе битовые карты и BitmapDecoder не очень хорошо обрабатывают байтовый массив YUV.Для этого была подана ошибка, но не надо знать, какой у нее статус.Поэтому люди пытаются самостоятельно декодировать байтовый массив в формат RGB.

Похоже, что ручное декодирование было довольно медленным, и люди имели разные степени успеха с ним.Нечто подобное должно, вероятно, действительно быть сделано с нативным кодом на уровне NDK.

Тем не менее, можно заставить его работать.Кроме того, моя небольшая демонстрация - это только то, как я провожу пару часов вместе, занимаясь хакерством (думаю, это слишком сильно поразило мое воображение;)).Таким образом, есть вероятность, что с некоторыми изменениями вы сможете значительно улучшить то, что мне удалось получить.

Этот небольшой фрагмент кода содержит пару других драгоценных камней, которые я также нашел.Если все, что вам нужно - это рисовать поверх поверхности, вы можете переопределить функцию onDraw поверхности - вы могли бы потенциально проанализировать возвращенное изображение с камеры и нарисовать наложение - это было бы намного быстрее, чем пытаться обрабатывать каждый кадр.Кроме того, я изменил SurfaceHolder.SURFACE_TYPE_NORMAL от того, что было бы необходимо, если вы хотите, чтобы отображался предварительный просмотр камеры.Итак, пара изменений в коде - закомментированный код:

//try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
//  { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }

И:

SurfaceHolder.SURFACE_TYPE_NORMAL //SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS - for preview to work

Должно позволять накладывать кадры на основе предварительного просмотра камеры поверхреальный предварительный просмотр.

В любом случае, вот рабочий фрагмент кода. Должен дать вам кое-что для начала.

Просто поместите строку кода в один из ваших видов, например:

<pathtocustomview.MySurfaceView android:id="@+id/surface_camera"
    android:layout_width="fill_parent" android:layout_height="10dip"
    android:layout_weight="1">
</pathtocustomview.MySurfaceView>

И включите этот класс в ваш источник где-нибудь:

package pathtocustomview;

import java.io.IOException;
import java.nio.Buffer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView implements Callback,
    Camera.PreviewCallback {

    private SurfaceHolder mHolder;

    private Camera mCamera;
    private boolean isPreviewRunning = false;
    private byte [] rgbbuffer = new byte[256 * 256];
    private int [] rgbints = new int[256 * 256];

    protected final Paint rectanglePaint = new Paint();

    public MySurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
        rectanglePaint.setARGB(100, 200, 0, 0);
        rectanglePaint.setStyle(Paint.Style.FILL);
        rectanglePaint.setStrokeWidth(2);

        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(new Rect((int) Math.random() * 100,
            (int) Math.random() * 100, 200, 200), rectanglePaint);
        Log.w(this.getClass().getName(), "On Draw Called");
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    }

    public void surfaceCreated(SurfaceHolder holder) {
        synchronized (this) {
            this.setWillNotDraw(false); // This allows us to make our own draw
                                    // calls to this canvas

            mCamera = Camera.open();

            Camera.Parameters p = mCamera.getParameters();
            p.setPreviewSize(240, 160);
            mCamera.setParameters(p);


            //try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
            //  { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }

            mCamera.startPreview();
            mCamera.setPreviewCallback(this);

        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        synchronized (this) {
            try {
                if (mCamera != null) {
                    mCamera.stopPreview();
                    isPreviewRunning = false;
                    mCamera.release();
                }
            } catch (Exception e) {
                Log.e("Camera", e.getMessage());
            }
        }
    }

    public void onPreviewFrame(byte[] data, Camera camera) {
        Log.d("Camera", "Got a camera frame");

        Canvas c = null;

        if(mHolder == null){
            return;
        }

        try {
            synchronized (mHolder) {
                c = mHolder.lockCanvas(null);

                // Do your drawing here
                // So this data value you're getting back is formatted in YUV format and you can't do much
                // with it until you convert it to rgb
                int bwCounter=0;
                int yuvsCounter=0;
                for (int y=0;y<160;y++) {
                    System.arraycopy(data, yuvsCounter, rgbbuffer, bwCounter, 240);
                    yuvsCounter=yuvsCounter+240;
                    bwCounter=bwCounter+256;
                }

                for(int i = 0; i < rgbints.length; i++){
                    rgbints[i] = (int)rgbbuffer[i];
                }

                //decodeYUV(rgbbuffer, data, 100, 100);
                c.drawBitmap(rgbints, 0, 256, 0, 0, 256, 256, false, new Paint());

                Log.d("SOMETHING", "Got Bitmap");

            }
        } finally {
            // do this in a finally so that if an exception is thrown
            // during the above, we don't leave the Surface in an
            // inconsistent state
            if (c != null) {
                mHolder.unlockCanvasAndPost(c);
            }
        }
    }
}
10 голосов
/ 30 ноября 2012

Я использовал решение walta , но у меня были некоторые проблемы с конвертацией YUV, размерами кадров фотокамеры и сбоем при выпуске камеры.

Наконец-то у меня сработал следующий код:

public class MySurfaceView extends SurfaceView implements Callback, Camera.PreviewCallback {

private static final String TAG = "MySurfaceView";

private int width;
private int height;

private SurfaceHolder mHolder;

private Camera mCamera;
private int[] rgbints;

private boolean isPreviewRunning = false; 

private int mMultiplyColor;

public MySurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mHolder = getHolder();
    mHolder.addCallback(this);
    mMultiplyColor = getResources().getColor(R.color.multiply_color);
}

// @Override
// protected void onDraw(Canvas canvas) {
// Log.w(this.getClass().getName(), "On Draw Called");
// }

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    synchronized (this) {
        if (isPreviewRunning)
            return;

        this.setWillNotDraw(false); // This allows us to make our own draw calls to this canvas


        mCamera = Camera.open();
        isPreviewRunning = true;
        Camera.Parameters p = mCamera.getParameters();
        Size size = p.getPreviewSize();
        width = size.width;
        height = size.height;
        p.setPreviewFormat(ImageFormat.NV21);
        showSupportedCameraFormats(p);
        mCamera.setParameters(p);

        rgbints = new int[width * height];

        // try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
        // { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }

        mCamera.startPreview();
        mCamera.setPreviewCallback(this);

    }
}


@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    synchronized (this) {
        try {
            if (mCamera != null) {
                //mHolder.removeCallback(this);
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
                isPreviewRunning  = false;
                mCamera.release();
            }
        } catch (Exception e) {
            Log.e("Camera", e.getMessage());
        }
    }
}

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    // Log.d("Camera", "Got a camera frame");
    if (!isPreviewRunning)
        return;

    Canvas canvas = null;

    if (mHolder == null) {
        return;
    }

    try {
        synchronized (mHolder) {
            canvas = mHolder.lockCanvas(null);
            int canvasWidth = canvas.getWidth();
            int canvasHeight = canvas.getHeight();

            decodeYUV(rgbints, data, width, height);

            // draw the decoded image, centered on canvas
            canvas.drawBitmap(rgbints, 0, width, canvasWidth-((width+canvasWidth)>>1), canvasHeight-((height+canvasHeight)>>1), width, height, false, null);

            // use some color filter
            canvas.drawColor(mMultiplyColor, Mode.MULTIPLY);

        }
    }  catch (Exception e){
        e.printStackTrace();
    } finally {
        // do this in a finally so that if an exception is thrown
        // during the above, we don't leave the Surface in an
        // inconsistent state
        if (canvas != null) {
            mHolder.unlockCanvasAndPost(canvas);
        }
    }
}



/**
 * Decodes YUV frame to a buffer which can be use to create a bitmap. use
 * this for OS < FROYO which has a native YUV decoder decode Y, U, and V
 * values on the YUV 420 buffer described as YCbCr_422_SP by Android
 * 
 * @param rgb
 *            the outgoing array of RGB bytes
 * @param fg
 *            the incoming frame bytes
 * @param width
 *            of source frame
 * @param height
 *            of source frame
 * @throws NullPointerException
 * @throws IllegalArgumentException
 */
public void decodeYUV(int[] out, byte[] fg, int width, int height) throws NullPointerException, IllegalArgumentException {
    int sz = width * height;
    if (out == null)
        throw new NullPointerException("buffer out is null");
    if (out.length < sz)
        throw new IllegalArgumentException("buffer out size " + out.length + " < minimum " + sz);
    if (fg == null)
        throw new NullPointerException("buffer 'fg' is null");
    if (fg.length < sz)
        throw new IllegalArgumentException("buffer fg size " + fg.length + " < minimum " + sz * 3 / 2);
    int i, j;
    int Y, Cr = 0, Cb = 0;
    for (j = 0; j < height; j++) {
        int pixPtr = j * width;
        final int jDiv2 = j >> 1;
    for (i = 0; i < width; i++) {
        Y = fg[pixPtr];
        if (Y < 0)
            Y += 255;
        if ((i & 0x1) != 1) {
            final int cOff = sz + jDiv2 * width + (i >> 1) * 2;
            Cb = fg[cOff];
            if (Cb < 0)
                Cb += 127;
            else
                Cb -= 128;
            Cr = fg[cOff + 1];
            if (Cr < 0)
                Cr += 127;
            else
                Cr -= 128;
        }
        int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5);
        if (R < 0)
            R = 0;
        else if (R > 255)
            R = 255;
        int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5);
        if (G < 0)
            G = 0;
        else if (G > 255)
            G = 255;
        int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6);
        if (B < 0)
            B = 0;
        else if (B > 255)
            B = 255;
        out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R;
    }
    }

}

private void showSupportedCameraFormats(Parameters p) {
    List<Integer> supportedPictureFormats = p.getSupportedPreviewFormats();
    Log.d(TAG, "preview format:" + cameraFormatIntToString(p.getPreviewFormat()));
    for (Integer x : supportedPictureFormats) {
        Log.d(TAG, "suppoterd format: " + cameraFormatIntToString(x.intValue()));
    }

}

private String cameraFormatIntToString(int format) {
    switch (format) {
    case PixelFormat.JPEG:
        return "JPEG";
    case PixelFormat.YCbCr_420_SP:
        return "NV21";
    case PixelFormat.YCbCr_422_I:
        return "YUY2";
    case PixelFormat.YCbCr_422_SP:
        return "NV16";
    case PixelFormat.RGB_565:
        return "RGB_565";
    default:
        return "Unknown:" + format;

        }
    }
}

Чтобы использовать его, запустите из своей деятельности onCreate следующий код:

            SurfaceView surfaceView = new MySurfaceView(this, null);
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        surfaceView.setLayoutParams(layoutParams);
        mRelativeLayout.addView(surfaceView);
3 голосов
/ 09 июня 2014

Вы смотрели на GPUImage?

Первоначально это была библиотека OSX / iOS, созданная Брэдом Ларсоном, которая существует как оболочка Objective-C для OpenGL / ES.

https://github.com/BradLarson/GPUImage

Сотрудники CyberAgent создали порт Android (который не имеет полной четности функций), который представляет собой набор оболочек Java поверх содержимого OpenGLES.Это относительно высокий уровень и довольно простой в реализации, с множеством функций, упомянутых выше ...

https://github.com/CyberAgent/android-gpuimage

...