Применение пользовательских фильтров к выходу камеры

Как применить специальные фильтры к одиночным кадрам на выходе камеры и показать их.

Что я пробовал до сих пор:

mCamera.setPreviewCallback(new CameraGreenFilter());

public class CameraGreenFilter implements PreviewCallback {

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        final int len = data.length;
        for(int i=0; i<len; ++i){
            data[i] *= 2;
        }
    }
}
  • Хотя его имя содержит "зеленый", я на самом деле хочу просто изменить значения каким-то образом (в этом случае цвета немного активизировались). Короче говоря, это не работает.

  • Я понял, что данные массива байтов - это копия вывода камеры; но это действительно не помогает, потому что мне нужен "настоящий" буфер.

  • Я слышал, что вы можете реализовать это с помощью openGL. Это звучит очень сложно.

Есть ли более простой способ? Иначе, как будет работать это отображение openGL-surfaceView?

Ответы

Ответ 1

ОК, есть несколько способов сделать это. Но есть существенная проблема с производительностью. Байт [] с камеры находится в YUV format, который нужно преобразовать в какой-то формат RGB, если вы хотите его отобразить, Это преобразование довольно дорогостоящее и значительно снижает выходной сигнал fps.

Это зависит от того, что вы действительно хотите сделать с предварительным просмотром камеры. Потому что лучшим решением является нарисовать предварительный просмотр камеры без обратного вызова и внести некоторые эффекты в предварительный просмотр камеры. Это обычный способ использования аргументированных материалов для повторного использования.

Но если вам действительно нужно отображать вывод вручную, есть несколько способов сделать это. Ваш пример не работает по нескольким причинам. Во-первых, вы не показываете изображение вообще. Если вы вызываете это:

mCamera.setPreviewCallback(new CameraGreenFilter());
mCamera.setPreviewDisplay(null);

чем ваша камера не отображает предварительный просмотр вообще, вы должны отображать ее вручную. И вы не можете делать дорогие операции в методе onPreviewFrame, поскольку срок службы данных ограничен, он перезаписывается в следующем кадре. Один намек, используйте setPreviewCallbackWithBuffer, он быстрее, потому что он повторно использует один буфер и не должен выделять новую память для каждого кадра.

Итак, вы должны сделать что-то вроде этого:

private byte[] cameraFrame;
private byte[] buffer;
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    cameraFrame = data;
    addCallbackBuffer(data); //actually, addCallbackBuffer(buffer) has to be called once sowhere before you call mCamera.startPreview();
}


private ByteOutputStream baos;
private YuvImage yuvimage;
private byte[] jdata;
private Bitmap bmp;
private Paint paint;

@Override //from SurfaceView
public void onDraw(Canvas canvas) {
    baos = new ByteOutputStream();
    yuvimage=new YuvImage(cameraFrame, ImageFormat.NV21, prevX, prevY, null);

    yuvimage.compressToJpeg(new Rect(0, 0, width, height), 80, baos); //width and height of the screen
    jdata = baos.toByteArray();

    bmp = BitmapFactory.decodeByteArray(jdata, 0, jdata.length);

    canvas.drawBitmap(bmp , 0, 0, paint);
    invalidate(); //to call ondraw again
}

Чтобы выполнить эту работу, вам нужно вызвать setWillNotDraw (false) в конструкторе класса или где-нибудь.

В onDraw вы можете, например, применить paint.setColorFilter(фильтр), если хотите изменить цвета. Я могу опубликовать некоторые примеры этого, если вы хотите.

Итак, это сработает, но производительность будет низкой (менее 8 кадров в секунду), поэтому BitmapFactory.decodeByteArray работает медленно. Вы можете попробовать конвертировать данные из YUV в RGB с помощью собственного кода и андроида NDK, но это довольно сложно.

Другой вариант - использовать openGL ES. Вам нужен GLSurfaceView, где вы привязываете рамку камеры как текстуру (в GLSurfaceView реализуется Camera.previewCallback, поэтому вы используете onPreviewFrame так же, как и на обычной поверхности). Но есть одна и та же проблема, вам нужно преобразовать данные YUV. Есть один шанс - вы можете отображать только данные яркости из предварительного просмотра (изображение в оттенках серого) довольно быстро, потому что первая половина байтового массива в YUV - это только данные яркости без цветов. Итак, на onPreviewFrame вы используете arraycopy для копирования первой половины массива, и вы привязываете текстуру следующим образом:

gl.glGenTextures(1, cameraTexture, 0);
int tex = cameraTexture[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_LUMINANCE, 
    this.prevX, this.prevY, 0, GL10.GL_LUMINANCE, 
    GL10.GL_UNSIGNED_BYTE, ByteBuffer.wrap(this.cameraFrame)); //cameraFrame is the first half od byte[] from onPreviewFrame

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);

Вы не можете получить около 16-18 fps таким образом, и вы можете использовать openGL для создания некоторых фильтров. Я могу отправить вам еще один код, если вы захотите, но он слишком долго, чтобы войти сюда...

Для получения дополнительной информации вы можете увидеть мой simillar вопрос, но нет и хорошего решения...