web-dev-qa-db-ja.com

カメラ出力にカスタムフィルターを適用する

カメラ出力の単一フレームにカスタムフィルターを適用して表示するにはどうすればよいですか。

私がこれまでに試したこと:

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マッピングはどのように機能しますか?

16
poitroae

OK、これを行うにはいくつかの方法があります。しかし、パフォーマンスには重大な問題があります。カメラからのbyte []は YUV形式 であり、表示する場合は、何らかのRGB形式に変換する必要があります。この変換は非常にコストのかかる操作であり、出力fpsを大幅に低下させます。

それはあなたが実際にカメラプレビューで何をしたいかによります。最善の解決策は、コールバックなしでカメラプレビューを描画し、カメラプレビューにいくつかの効果を加えることです。それは議論された現実のものをする通常の方法です。

ただし、実際に出力を手動で表示する必要がある場合は、いくつかの方法があります。あなたの例はいくつかの理由で機能しません。まず、画像をまったく表示していません。あなたがこれを呼ぶならば:

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

カメラがプレビューをまったく表示していない場合は、手動で表示する必要があります。また、onPreviewFrameメソッドでコストのかかる操作を行うことはできません。データの有効期間が制限されているため、次のフレームで上書きされます。ヒントの1つとして、 setPreviewCallbackWithBuffer を使用します。これは、1つのバッファーを再利用し、各フレームに新しいメモリを割り当てる必要がないため、高速です。

したがって、次のようなことを行う必要があります。

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(filter) を適用できます。必要に応じて、その例をいくつか投稿できます。

したがって、これは機能しますが、パフォーマンスが低くなり(8fps未満)、BitmapFactory.decodeByteArrayが遅くなります。ネイティブコードとAndroid NDKを使用して、データをYUVからRGBに変換することを試みることができますが、それは非常に複雑です。

もう1つのオプションは、openGLESを使用することです。カメラフレームをテクスチャとしてバインドするGLSurfaceViewが必要です(GLSurfaceViewではCamera.previewCallbackを実装するため、通常のサーフェスと同じようにonPreviewFrameを使用します)。しかし、同じ問題があります。YUVデータを変換する必要があります。 YUVのバイト配列の前半は色のない輝度データのみであるため、プレビュー(グレースケール画像)からの輝度データのみを非常に高速に表示できる可能性が1つあります。したがって、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を使用していくつかのフィルターを作成できます。必要に応じて、これにさらにコードを送信できますが、ここに入力するには長すぎます...

いくつかの詳細については、私の 同様の質問 を見ることができますが、良い解決策もありません...

38
Jaa-c