2009-12-12 62 views
31

Tôi đã triển khai một ứng dụng đơn giản hiển thị hình ảnh camera trên màn hình. Những gì tôi muốn làm bây giờ là lấy một khung hình duy nhất và xử lý nó như là bitmap. Từ những gì tôi có thể tìm ra đến thời điểm này, nó không phải là một điều dễ dàng để làm.Lấy khung hình từ Video Image trong Android

Tôi đã thử sử dụng phương thức onPreviewFrame mà bạn lấy khung hiện tại làm mảng byte và cố gắng giải mã nó bằng lớp BitmapFactory nhưng nó trả về giá trị rỗng. Định dạng của khung là YUV không có tiêu đề có thể được dịch sang bitmap nhưng phải mất quá nhiều thời gian trên điện thoại. Ngoài ra tôi đã đọc rằng phương thức onPreviewFrame có các hạn chế về thời gian chạy, nếu nó mất quá nhiều thời gian, ứng dụng có thể gặp sự cố.

Vì vậy, cách phù hợp để làm điều này là gì?

+0

Sử dụng ffmpeg, chúng tôi có thể làm điều đó trong mã gốc. – Vinay

+0

Xin chào @Vinay, tôi đang cố gắng thử nghiệm từ rất nhiều ngày nhưng vẫn không có kết quả, nếu bạn bị vướng vào nó, bạn có thể tham khảo số này http://stackoverflow.com/questions/11322952/decoding-video-using-ffmpeg -cho-android tôi đã làm được điều này, nếu bạn muốn giới thiệu coe thì tôi sẽ đẩy nó vào git. –

Trả lời

7

Trong API 17+, bạn có thể chuyển đổi sang RGBA888 từ NV21 bằng RenderScript 'ScriptIntrinsicYuvToRGB'. Điều này cho phép bạn dễ dàng xử lý khung xem trước mà không cần mã hóa/giải mã khung theo cách thủ công:

@Override 
public void onPreviewFrame(byte[] data, Camera camera) { 
    Bitmap bitmap = Bitmap.createBitmap(r.width(), r.height(), Bitmap.Config.ARGB_8888); 
    Allocation bmData = renderScriptNV21ToRGBA888(
     mContext, 
     r.width(), 
     r.height(), 
     data); 
    bmData.copyTo(bitmap); 
} 

public Allocation renderScriptNV21ToRGBA888(Context context, int width, int height, byte[] nv21) { 
    RenderScript rs = RenderScript.create(context); 
    ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); 

    Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length); 
    Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); 

    Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height); 
    Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); 

    in.copyFrom(nv21); 

    yuvToRgbIntrinsic.setInput(in); 
    yuvToRgbIntrinsic.forEach(out); 
    return out; 
} 
34

Ok những gì chúng tôi đã kết thúc là sử dụng phương thức onPreviewFrame và giải mã dữ liệu trong một Chủ đề riêng biệt bằng cách sử dụng phương thức có thể tìm thấy trong nhóm trợ giúp Android.

decodeYUV(argb8888, data, camSize.width, camSize.height); 
Bitmap bitmap = Bitmap.createBitmap(argb8888, camSize.width, 
        camSize.height, Config.ARGB_8888); 

...

// decode Y, U, and V values on the YUV 420 buffer described as YCbCr_422_SP by Android 
// David Manpearl 081201 
public void decodeYUV(int[] out, byte[] fg, int width, int height) 
     throws NullPointerException, IllegalArgumentException { 
    int sz = width * height; 
    if (out == null) 
     throw new NullPointerException("buffer out is null"); 
    if (out.length < sz) 
     throw new IllegalArgumentException("buffer out size " + out.length 
       + " < minimum " + sz); 
    if (fg == null) 
     throw new NullPointerException("buffer 'fg' is null"); 
    if (fg.length < sz) 
     throw new IllegalArgumentException("buffer fg size " + fg.length 
       + " < minimum " + sz * 3/2); 
    int i, j; 
    int Y, Cr = 0, Cb = 0; 
    for (j = 0; j < height; j++) { 
     int pixPtr = j * width; 
     final int jDiv2 = j >> 1; 
     for (i = 0; i < width; i++) { 
      Y = fg[pixPtr]; 
      if (Y < 0) 
       Y += 255; 
      if ((i & 0x1) != 1) { 
       final int cOff = sz + jDiv2 * width + (i >> 1) * 2; 
       Cb = fg[cOff]; 
       if (Cb < 0) 
        Cb += 127; 
       else 
        Cb -= 128; 
       Cr = fg[cOff + 1]; 
       if (Cr < 0) 
        Cr += 127; 
       else 
        Cr -= 128; 
      } 
      int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5); 
      if (R < 0) 
       R = 0; 
      else if (R > 255) 
       R = 255; 
      int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1) 
        + (Cr >> 3) + (Cr >> 4) + (Cr >> 5); 
      if (G < 0) 
       G = 0; 
      else if (G > 255) 
       G = 255; 
      int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6); 
      if (B < 0) 
       B = 0; 
      else if (B > 255) 
       B = 255; 
      out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R; 
     } 
    } 

} 

Link: http://groups.google.com/group/android-developers/browse_thread/thread/c85e829ab209ceea/3f180a16a4872b58?lnk=gst&q=onpreviewframe#3f180a16a4872b58

+2

Hey Alexander, bạn đang làm bất kỳ ma thuật chế biến hoặc voodoo nào khác trong nền? Tôi đã thử điều này và kết thúc với một hình ảnh như thế này: https://dl.dropbox.com/u/6620976/image.jpg. Bất kỳ cái nhìn sâu sắc được đánh giá cao. –

+0

Nevermind! Tôi chỉ cần xác định chiều rộng và chiều cao bề mặt của tôi và tất cả đều hoạt động tốt. Cảm ơn bạn đã đăng bài này! –

+0

@Brian D: Tôi đang đối mặt với cùng một vấn đề, bạn có thể vui lòng cho tôi xem mã bề mặt của bạn không? – Mudassir

10

Tôi thực sự cố gắng mã cho các câu trả lời trước phát hiện ra rằng Colorvalues ​​không chính xác. Tôi đã kiểm tra nó bằng cách lấy cả bản xem trước và camera.takePicture trực tiếp trả về một mảng JPEG. Và màu sắc rất khác nhau. Sau khi tìm kiếm hơn một chút tôi thấy một ví dụ khác để chuyển đổi từ PreviewImage YCrCb để RGB:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) { 
    final int frameSize = width * height; 

    for (int j = 0, yp = 0; j < height; j++) { 
     int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; 
     for (int i = 0; i < width; i++, yp++) { 
      int y = (0xff & ((int) yuv420sp[yp])) - 16; 
      if (y < 0) y = 0; 
      if ((i & 1) == 0) { 
       v = (0xff & yuv420sp[uvp++]) - 128; 
       u = (0xff & yuv420sp[uvp++]) - 128; 
      } 
      int y1192 = 1192 * y; 
      int r = (y1192 + 1634 * v); 
      int g = (y1192 - 833 * v - 400 * u); 
      int b = (y1192 + 2066 * u); 

      if (r < 0) r = 0; else if (r > 262143) r = 262143; 
      if (g < 0) g = 0; else if (g > 262143) g = 262143; 
      if (b < 0) b = 0; else if (b > 262143) b = 262143; 

      rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); 
     } 
    } 
} 

Giá trị màu do này và takePicture() chính xác phù hợp. Tôi nghĩ tôi nên đăng nó ở đây. This is where I got this code from. Hy vọng điều này sẽ hữu ích.

+3

Cảm ơn - Tôi đã sử dụng mã trong [post # 37] (https://code.google.com/p/android/issues/detail?id=823#c37) thay vào đó (nó đã được đăng lâu sau câu trả lời này là , nhưng đối với những người trong chúng ta trong tương lai nó hoạt động độc đáo). – Matt

1

Giải pháp RenderScript của Tim rất tuyệt. Hai bình luận ở đây mặc dù:

  1. Tạo và sử dụng lại RenderScript rsAllocation in, out. Tạo chúng mỗi khung hình sẽ làm tổn thương hiệu suất.
  2. RenderScript support library có thể giúp bạn hỗ trợ lại cho Android 2.3.
Các vấn đề liên quan