2011-12-03 21 views
15

Làm cách nào để áp dụng bộ lọc tùy chỉnh cho các khung hình duy nhất trong đầu ra của máy ảnh và hiển thị chúng.Áp dụng các bộ lọc tùy chỉnh cho đầu ra máy ảnh

Những gì tôi đã cố gắng cho đến nay:

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; 
     } 
    } 
} 
  • Mặc dù tên của nó chứa "xanh" Tôi thực sự muốn chỉ cần sửa đổi các giá trị bằng cách nào đó (trong trường hợp này, màu sắc sẽ được tăng cường một chút) . Câu chuyện dài ngắn, nó không hoạt động.

  • Tôi đã tìm ra rằng mảng byte 'dữ liệu' là bản sao của đầu ra máy ảnh; nhưng điều này không thực sự hữu ích, bởi vì tôi cần bộ đệm 'thực'.

  • Tôi nghe nói bạn có thể triển khai điều này với openGL. Nghe có vẻ rất phức tạp.

Có cách nào dễ dàng hơn không? Khác, bản đồ openGL-surfaceView này sẽ hoạt động như thế nào?

Trả lời

36

OK, có một số cách để thực hiện việc này. Nhưng có một vấn đề đáng kể với hiệu suất. Các byte [] từ một máy ảnh là trong YUV format, mà phải được chuyển đổi sang một số loại định dạng RGB, nếu bạn muốn hiển thị nó. Chuyển đổi này là hoạt động khá tốn kém và làm giảm đáng kể fps đầu ra.

Tùy thuộc vào những gì bạn thực sự muốn thực hiện với bản xem trước máy ảnh. Vì giải pháp tốt nhất là vẽ bản xem trước máy ảnh mà không cần gọi lại và tạo một số hiệu ứng trên bản xem trước của máy ảnh. Đó là cách thông thường để thực hiện các công cụ thực tế.

Nhưng nếu bạn thực sự cần hiển thị đầu ra theo cách thủ công, có một số cách để thực hiện điều đó. Ví dụ của bạn không hoạt động vì nhiều lý do. Đầu tiên, bạn hoàn toàn không hiển thị hình ảnh. Nếu bạn gọi điện thoại này:

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

hơn máy ảnh của bạn không hiển thị xem trước, bạn phải hiển thị nó theo cách thủ công. Và bạn không thể thực hiện bất kỳ hoạt động đắt tiền nào trong phương thức onPreviewFrame, vì thời gian tồn tại của dữ liệu bị giới hạn, nó sẽ ghi đè lên khung tiếp theo. Một gợi ý, sử dụng setPreviewCallbackWithBuffer, nó nhanh hơn, bởi vì nó sử dụng lại một bộ đệm và không phải cấp phát bộ nhớ mới trên mỗi khung.

Vì vậy, bạn phải làm một cái gì đó như thế này:

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 
} 

Để thực hiện việc này, bạn cần phải gọi setWillNotDraw(false) trong constructor lớp hoặc ở đâu đó.

Trong onDraw, bạn có thể áp dụng ví dụ paint.setColorFilter(filter), nếu bạn muốn sửa đổi màu sắc. Tôi có thể đăng một số ví dụ về điều đó, nếu bạn muốn.

Vì vậy, điều này sẽ hiệu quả, nhưng hiệu suất sẽ thấp (dưới 8fps), khiến BitmapFactory.decodeByteArray chậm. Bạn có thể thử chuyển đổi dữ liệu từ YUV sang RGB bằng mã gốc và NDK android, nhưng điều đó khá phức tạp.

Tùy chọn khác là sử dụng OpenGL ES. Bạn cần GLSurfaceView, nơi bạn liên kết khung máy ảnh dưới dạng kết cấu (trong GLSurfaceView triển khai Camera.previewCallback, vì vậy bạn sử dụng onPreviewFrame giống như trong bề mặt thông thường). Nhưng có cùng một vấn đề, bạn cần phải chuyển đổi dữ liệu YUV.Có một cơ hội - bạn chỉ có thể hiển thị dữ liệu độ sáng từ bản xem trước (hình ảnh thang độ xám) khá nhanh, bởi vì nửa đầu của mảng byte trong YUV chỉ là dữ liệu độ sáng không có màu. Vì vậy, trên onPreviewFrame bạn sử dụng để sao chép arraycopy nửa đầu của mảng, và hơn bạn gắn kết cấu như thế này:

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

Bạn không thể nhận được khoảng 16-18 fps theo cách này và bạn có thể sử dụng OpenGL để làm cho một số bộ lọc . Tôi có thể gửi thêm một số mã cho bạn nếu bạn muốn, nhưng quá dài để đặt ở đây ...

Để biết thêm thông tin, bạn có thể xem simillar question, nhưng không có giải pháp tốt ...

+0

Bạn nói dữ liệu YUV cần được chuyển đổi thành dữ liệu RGB để hiển thị và điều này sẽ chỉ cung cấp khoảng 8fps. Khung làm việc android là gì để có được tốc độ hiển thị cao như vậy (không có bộ lọc nào được áp dụng)? Bạn có nghĩ rằng họ đang gói tất cả mọi thứ để OpenGL ES để có được khoảng 20 fps? :) – poitroae

+0

Nhiều khả năng, họ không sử dụng API Android :) Nó có thể được viết bằng mã gốc hoặc một cái gì đó tương tự (chỉ là phỏng đoán) ... Nhưng Android là mã nguồn mở, vì vậy bạn có thể thử tìm cách mã của họ thực sự công trình :) –

+0

Jaa-c, tôi đã tự hỏi nếu nó có thể được ở tất cả các bạn có thể đăng một ví dụ bằng cách sử dụng mã bạn đã viết ở trên (Không phải là OpenGL). Tôi không thể tìm ra cách để thêm tất cả lại với nhau để hoạt động chính xác và tôi hiểu đây là một câu hỏi cũ, nhưng không phải là tôi có thể tìm thấy nhiều thứ trên Google về điều này. Hoặc bạn có thể làm một hướng dẫn nhỏ về những gì các lớp học để thực hiện để thực hiện điều này? –

Các vấn đề liên quan