2011-12-02 15 views
14

Primero - He estado trabajando en esto por algún tiempo y he intentado con varias soluciones, así que no me envíe el primer enlace que encuentre en google.Android: ¿cómo mostrar la vista previa de la cámara con devolución de llamada?

Lo que necesito hacer es bastante simple, quiero mostrar manualmente la vista previa de la cámara usando la devolución de llamada de la cámara y quiero obtener al menos 15 fps en un dispositivo real. Ni siquiera necesito los colores, solo necesito una vista previa de la imagen en escala de grises. Las imágenes de la cámara están en formato YUV y tiene que procesarlo de alguna manera, que es el principal problema de rendimiento. Estoy usando API 8.

En todos los casos estoy usando camera.setPreviewCallbackWithBuffer(), que es más rápido que camera.setPreviewCallback(). Parece que no puedo obtener unos 24 fps aquí, si no estoy mostrando la vista previa. Entonces no hay problema.

me han tratado estas soluciones:

1. Visualizar la cámara preliminar En un SurfaceView como un mapa de bits. Funciona, pero el rendimiento es de aproximadamente 6 fps.

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. Visualizar la cámara preliminar En un GLSurfaceView como una textura. Aquí estaba visualizando solo datos de luminancia (imagen en escala de grises), lo cual es bastante fácil, solo requiere una matriz de copias() en cada cuadro. Puedo obtener aproximadamente 12 fps, pero necesito aplicar algunos filtros a la vista previa y parece que no se puede hacer rápidamente en OpenGL ES 1. Por lo tanto, no puedo usar esta solución. Algunos detalles de esto en another question.


3. cámara de pantalla de vista previa en un SurfaceView (GL) usando NDK para procesar los datos YUV. Encuentro una solución here que usa alguna función C y NDK. Pero no logré usarlo, here some more details. Pero de todos modos, esta solución se hace para devolver a ByteBuffer como una textura en OpenGL y no será más rápido que el intento anterior. Así que tendría que modificarlo para devolver int [] array, que se puede dibujar con canvas.drawBitmap(), pero no entiendo lo suficiente C para hacer esto.


Entonces, ¿hay alguna otra manera que me falta o una mejora de los intentos que intenté?

Gracias por cualquier respuesta!

Respuesta

1

¿No es esto lo que quieres? Sólo tiene que utilizar un SurfaceView en su diseño, a continuación, en algún lugar de su inicio como onResume():

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

Simplemente envía los marcos directamente a la vista tan rápido como llegan.

Si desea una escala de grises, modifique los parámetros de la cámara con setColorEffect("mono").

+0

Gracias, pero realmente necesito usar la devolución de llamada y mostrar imágenes manualmente. No necesito solo la imagen en escala de grises, estoy usando algunos filtros más, que no mencioné en mi pregunta ... –

1

Para efectos muy básicas y sencillas, no es

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

me di cuenta de que este efecto hacer de manera diferente dependiendo del dispositivo. Por ejemplo, en mi teléfono (galaxy s II) parece un efecto cómico, ya que en contraste con la galaxia s 1 es "solo" un tono azul.

Es pro: funciona como live-preview.

Miré alrededor de algunas otras aplicaciones de cámara y obviamente también enfrentaron este problema. Entonces, ¿qué hicieron? Están capturando la imagen de cámara predeterminada, aplicando un filtro a los datos de mapa de bits y mostrando esta imagen en un simple ImageView. Es seguro que no es tan genial como en la vista previa en vivo, pero nunca enfrentarás problemas de rendimiento.

+0

Gracias, conozco estos efectos de la cámara, pero quería hacer un ajuste de contraste, la sustitución del color etc. Pero tiene razón, parece que realmente no hay una buena solución de trabajo para la vista previa en vivo, así que tendré que hacer algo como lo describió ...:/ –

7

Estoy trabajando exactamente en el mismo problema, pero no he llegado tan lejos como usted.

¿Ha considerado dibujar los píxeles directamente en el lienzo sin codificarlos primero a JPEG? Dentro del kit OpenCV http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3.1/OpenCV-2.3.1-android-bin.tar.bz2/download (que en realidad no usa opencv; no se preocupe), hay un proyecto llamado tutorial-0-androidcamera que muestra cómo convertir los píxeles YUV a RGB y luego escribirlos directamente en un mapa de bits.

El código en cuestión es esencialmente:

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(); 
} 

Por supuesto que lo tienes que adaptarlo a sus necesidades (Ej. No asignar RGBA cada cuadro), pero podría ser un comienzo. Me encantaría ver si funciona para ti o no, todavía estoy luchando con problemas ortogonales al tuyo en este momento.

+1

Lo intentaré mañana, pero estoy Estoy bastante seguro de que no habrá mejoría en el rendimiento, porque esto funciona más o menos igual que la versión con las clases YuvImage y BitmapFactory. Pero lo probaré y ya veremos. Gracias. –

+1

Así que, como esperaba, tiene el peor rendimiento de todos los casos, incluso cuando traté de optimizarlo un poco ... –

4

Creo que Michael está en el camino correcto. Primero puede probar este método para convertir de RGB a escala de grises. Está claro que está haciendo casi lo mismo que él, pero un poco más sucintamente para lo que quiere.

//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; 
    } 
} 

}

En segundo lugar, no crean un montón de trabajo para el recolector de basura. Sus mapas de bits y matrices van a tener un tamaño fijo. Crételos una vez, no en onFramePreview.

Hacer que va a terminar con algo que se parece a esto:

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(); 
    } 
}; 

Y luego el lienzo donde desea ponerlo ve así:

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); 
} 

Este ejemplo muestra una selección recortada y distorsionada de la vista previa de la cámara en tiempo real, pero se entiende la idea. Funciona a alta FPS en un Nexus S en escala de grises y debería funcionar para sus necesidades también.

+0

'Segundo, no crees una tonelada de trabajo para el recolector de basura. Su 'onDraw' nos dice lo contrario. –

0

Creo que leí en un blog que los datos de la escala de grises están en los primeros x * y bytes. Yuv debería representar la luminancia, por lo que los datos están allí, aunque no es una escala de grises perfecta. Es ideal para brillo relativo, pero no en escala de grises, ya que cada color no es tan brillante como el uno al otro en rgb. El verde generalmente recibe un mayor peso en las conversiones de luminosidad. ¡Espero que esto ayude!

0

¿Hay algún motivo especial por el que esté obligado a usar GLES 1.0?

Porque si no, ver la respuesta aceptada aquí: Android SDK: Get raw preview camera image without displaying it

Generalmente se menciona el uso de Camera.setPreviewTexture() en combinación con GLES 2.0. En GLES 2.0 puede representar un quad de pantalla completa en toda la pantalla y crear el efecto que desee.

Probablemente sea la forma más rápida posible.

+1

Ejemplo: https://github.com/google/grafika/blob/master/src/com/android/grafika/CameraCaptureActivity.java. Actualmente tiene un sombreador que coloca una imagen en blanco y negro en el canal de color rojo (busque "rosyFragment"). Al asignar el mismo valor a R/G/B se obtendría una imagen en blanco y negro. – fadden

Cuestiones relacionadas