2011-12-03 9 views
15

Jak zastosować niestandardowe filtry do pojedynczych klatek na wyjściu z kamery i wyświetlić je.Zastosuj filtry niestandardowe do wyjścia kamery

Co próbowałem dotąd:

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; 
     } 
    } 
} 
  • Choć jego nazwa zawiera „zielony” I rzeczywiście chcą po prostu jakoś zmodyfikować wartości (w tym przypadku, kolory zostaną zintensyfikowane trochę) . Krótko mówiąc, to nie działa.

  • Wyliczyłem, że tablica bajtów "dane" jest kopią wyjścia kamery; ale to naprawdę nie pomaga, ponieważ potrzebuję "prawdziwego" bufora.

  • Słyszałem, że można to zaimplementować z OpenGL. To brzmi bardzo skomplikowanie.

Czy jest łatwiejszy sposób? W przeciwnym razie, jak będzie wyglądało mapowanie OpenGL-surfaceView?

Odpowiedz

36

OK, jest na to kilka sposobów. Ale istnieje poważny problem z wydajnością. Bajt [] z kamery jest w YUV format, który musi zostać przekonwertowany do pewnego rodzaju formatu RGB, jeśli chcesz go wyświetlić. Ta konwersja jest dość kosztowna i znacznie obniża wyjściowe fps.

To zależy od tego, co faktycznie chcesz zrobić z podglądem kamery. Najlepszym rozwiązaniem jest narysowanie podglądu kamery bez oddzwaniania i wykonanie niektórych efektów na podglądzie kamery. To jest zwykły sposób na robienie sporów.

Ale jeśli naprawdę potrzebujesz wyświetlić wynik ręcznie, możesz to zrobić na kilka sposobów. Twój przykład nie działa z kilku powodów. Po pierwsze, w ogóle nie wyświetlasz obrazu. Jeśli to nazwiesz:

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

niż twój aparat nie wyświetla podglądu, musisz go wyświetlić ręcznie. I nie można wykonywać żadnych kosztownych operacji w metodzie onPreviewFrame, ponieważ czas życia danych jest ograniczony, jest on nadpisywany w następnej klatce. Jedna podpowiedź, użyj setPreviewCallbackWithBuffer, jest szybsza, ponieważ ponownie wykorzystuje jeden bufor i nie musi przydzielać nowej pamięci w każdej klatce.

Więc trzeba zrobić coś takiego:

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 
} 

Aby dokonać tej pracy, trzeba zadzwonić setWillNotDraw(false) w konstruktorze klasy lub gdzieś.

W onDraw można na przykład zastosować paint.setColorFilter(filter), jeśli chcesz zmodyfikować kolory. Mogę podać przykład tego, jeśli chcesz.

To zadziała, ale wydajność będzie niska (mniej niż 8 klatek na sekundę), ponieważ bitmapFactory.decodeByteArray jest powolny. Możesz spróbować przekonwertować dane z YUV na RGB z natywnym kodem i Androidem NDK, ale to dość skomplikowane.

Inną opcją jest użycie openGL ES. Potrzebujesz GLSurfaceView, gdzie bindujesz ramkę kamery jako teksturę (w GLSurfaceView implement Camera.previewCallback, więc używasz onPreviewFrame w taki sam sposób, jak w zwykłej powierzchni). Ale jest ten sam problem, musisz przekonwertować dane YUV.Jest jedna szansa - możesz szybko wyświetlić tylko dane luminancji z podglądu (obraz w skali szarości), ponieważ pierwsza połowa tablicy bajtów w YUV to tylko dane luminancji bez kolorów. Więc na onPreviewFrame użyć arraycopy skopiować pierwszą połowę tablicy, a nie zwiążesz teksturę tak:

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

mogę dostać około 16-18 fps w ten sposób i można go używać openGL aby niektóre filtry . Mogę wysłać ci więcej kodu, jeśli chcesz, ale tu jest za długo, aby je tu umieścić ...

Aby uzyskać więcej informacji, możesz zobaczyć moją simillar question, ale nie ma również dobrego rozwiązania ...

+0

Mówi się, że dane YUV muszą zostać przekonwertowane na dane RGB, aby można było je wyświetlić, a to dałoby około 8 fps. Co robi struktura systemu Android, aby uzyskać tak wysoką częstotliwość wyświetlania (bez zastosowanych filtrów)? Czy myślisz, że pakują wszystko, aby otworzyć ES, aby uzyskać około 20 klatek na sekundę? :) – poitroae

+0

Najprawdopodobniej nie używają Androida API :) Może być napisany natywnym kodem lub czymś w tym stylu (to tylko przypuszczenie) ... Ale Android to opensource, więc możesz spróbować znaleźć sposób, w jaki ich kod faktycznie działa :) –

+0

Jaa-c, zastanawiałem się, czy byłoby w ogóle możliwe opublikowanie przykładu za pomocą kodu, który napisałeś powyżej (nie OpenGL). Nie mogę wymyślić, jak to wszystko razem dodać, by działało poprawnie. Rozumiem, że jest to starsze pytanie, ale ich niewiele mogę znaleźć w Google na ten temat. Lub możesz zrobić mini samouczek na temat klas, które należy wprowadzić, aby to wdrożyć? –

Powiązane problemy