2011-12-02 6 views
14

Primo: ci sto lavorando da un po 'di tempo e ho provato diverse soluzioni, quindi non inviarmi il primo link che trovi su google.Android: come visualizzare l'anteprima della fotocamera con il callback?

Quello che devo fare è abbastanza semplice, voglio visualizzare manualmente l'anteprima dalla fotocamera utilizzando la funzione di richiamata della fotocamera e voglio ottenere almeno 15 fps su un dispositivo reale. Non ho nemmeno bisogno dei colori, ho solo bisogno di visualizzare l'immagine in scala di grigi. Le immagini dalla fotocamera sono in formato YUV e devi elaborarlo in qualche modo, che è il problema principale delle prestazioni. Sto usando l'API 8.

In tutti i casi sto usando camera.setPreviewCallbackWithBuffer(), che è più veloce di camera.setPreviewCallback(). Sembra che non riesca a ottenere circa 24 fps qui, se non visualizzo l'anteprima. Quindi non c'è il problema.

ho provato queste soluzioni:

1. Visualizzare anteprima della fotocamera su un SurfaceView come bitmap. Funziona, ma la prestazione è di circa 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. visualizzazione di anteprima della fotocamera su un GLSurfaceView come una texture. Qui stavo visualizzando solo i dati di luminanza (immagine in scala di grigi), che è abbastanza semplice, richiede solo un arraycopy() su ciascun fotogramma. Posso ottenere circa 12 fps, ma ho bisogno di applicare alcuni filtri per l'anteprima e sembra, che non può essere fatto velocemente in OpenGL ES 1. Quindi non posso usare questa soluzione. Alcuni dettagli su questo in another question.


3. Visualizzazione di anteprima della fotocamera su un (GL) SurfaceView utilizzando NDK per elaborare i dati YUV. Trovo una soluzione here che utilizza alcune funzioni C e NDK. Ma non sono riuscito a usarlo, here some more details. Ma comunque, questa soluzione è fatta per restituire ByteBuffer per visualizzarlo come una trama in OpenGL e non sarà più veloce del tentativo precedente. Quindi dovrei modificarlo per restituire l'array int [], che può essere disegnato con canvas.drawBitmap(), ma non capisco C abbastanza per farlo.


Quindi, c'è qualche altro modo che mi manca o qualche miglioramento rispetto ai tentativi che ho provato?

Grazie per qualsiasi risposta!

risposta

1

Non è questo che vuoi? Basta usare un SurfaceView nel layout, poi da qualche parte nel file init come onResume():

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

li invia i fotogrammi direttamente alla visualizzazione più veloce man mano che arrivano.

Se si desidera la scala di grigi, modificare i parametri della telecamera con setColorEffect("mono").

+0

Grazie, ma ho davvero bisogno di usare il callback e visualizzare le immagini manualmente. Non ho bisogno solo dell'immagine in scala di grigi, sto usando altri filtri, che non ho menzionato nella mia domanda ... –

1

Per gli effetti molto semplici e semplice, c'è

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

ho capito che questi effetti in modo diverso a seconda del dispositivo. Ad esempio, sul mio telefono (galaxy s II) sembra un po 'come un effetto comico, in contrasto con la galassia s 1 è' solo 'un'ombra blu.

È pro: funziona come anteprima dal vivo.

Ho guardato intorno ad altre app per fotocamere e ovviamente hanno anche affrontato questo problema. Quindi cosa hanno fatto? Catturano l'immagine della telecamera predefinita, applicano un filtro ai dati bitmap e mostrano questa immagine in un semplice ImageView. Non è certo così bello come nell'anteprima dal vivo, ma non dovrai mai affrontare problemi di prestazioni.

+0

Grazie, so di questi effetti fotocamera, ma volevo fare un po 'di regolazione del contrasto, sostituzione del colore ecc. Ma hai ragione, sembra che non ci sia davvero una buona soluzione di lavoro per vivere l'anteprima, quindi dovrò fare qualcosa come hai descritto ...:/ –

7

Sto lavorando esattamente allo stesso problema, ma non ho ottenuto il massimo.

Hai considerato di trascinare i pixel direttamente sulla tela senza prima codificarli in JPEG? All'interno del kit OpenCV http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3.1/OpenCV-2.3.1-android-bin.tar.bz2/download (che in realtà non usa opencv; non preoccuparti), c'è un progetto chiamato tutorial-0-androidcamera che dimostra la conversione dei pixel YUV in RGB e quindi li scrive direttamente in una bitmap.

Il codice in questione è essenzialmente:

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

Naturalmente sarebbe necessario adattarlo alle proprie esigenze (. Ex non attribuire RGBA ogni fotogramma), ma potrebbe essere un inizio. Mi piacerebbe vedere se funziona o meno per te - sto ancora combattendo problemi ortogonali ai tuoi al momento.

+1

Ci proverò domani, ma sono abbastanza sicuro che non ci saranno miglioramenti delle prestazioni, perché funziona più o meno come la versione con le classi YuvImage e BitmapFactory. Ma ci proverò e vedremo. Grazie. –

+1

Quindi, come mi aspettavo, ha le peggiori prestazioni di tutti i casi, anche quando ho provato ad ottimizzarlo un po '... –

4

Penso che Michael sia sulla buona strada. Innanzitutto puoi provare questo metodo per convertire da RGB a Scala di grigi. Chiaramente sta facendo quasi la stessa cosa della sua, ma un po 'più succintamente per quello che vuoi.

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

}

In secondo luogo, non creare un sacco di lavoro per il garbage collector. Le tue bitmap e gli array avranno dimensioni fisse. Creale una volta, non in onFramePreview.

Facendo che vi ritroverete con qualcosa che assomiglia a questo:

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

E poi la tela in cui si desidera mettere assomiglia a questo:

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

Questo esempio mostra un selezione ritagliata e distorta dell'anteprima della fotocamera in tempo reale, ma si ottiene l'idea. Funziona con FPS alti su un Nexus S in scala di grigi e dovrebbe funzionare anche per le tue esigenze.

+0

'Secondo, non creare un sacco di lavoro per il garbage collector. Il tuo 'onDraw' ci dice il contrario. –

0

Credo di aver letto in un blog che i dati della scala di grigi sono nei primi x * y byte. Yuv dovrebbe rappresentare la luminanza, quindi i dati sono lì, anche se non è una scala di grigi perfetta. È ottimo per la luminosità relativa, ma non per la scala di grigi, poiché ogni colore non è così luminoso come l'un l'altro in rgb. Di solito il verde ha un peso maggiore nelle conversioni di luminosità. Spero che questo ti aiuti!

0

C'è qualche motivo particolare per cui sei obbligato a utilizzare GLES 1.0?

Perché se no, si veda la risposta accettata qui: Android SDK: Get raw preview camera image without displaying it

Generalmente si parla usando Camera.setPreviewTexture() in combinazione con GLES 2.0. In GLES 2.0 è possibile eseguire il rendering di un quadrante a schermo intero su tutto lo schermo e creare l'effetto desiderato.

È molto probabilmente il modo più veloce possibile.

+1

Esempio: https://github.com/google/grafika/blob/master/src/com/android/grafika/CameraCaptureActivity.java. Attualmente ha uno shader che mette un'immagine in bianco e nero nel canale di colore rosso (cerca "rosyFragment"). Assegnando lo stesso valore a R/G/B si otterrebbe un'immagine in bianco e nero. – fadden