カメラ出力の単一フレームにカスタムフィルターを適用して表示するにはどうすればよいですか。
私がこれまでに試したこと:
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マッピングはどのように機能しますか?
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を使用していくつかのフィルターを作成できます。必要に応じて、これにさらにコードを送信できますが、ここに入力するには長すぎます...
いくつかの詳細については、私の 同様の質問 を見ることができますが、良い解決策もありません...