2
camera.setPreviewCallback(new Camera.PreviewCallback() {
    private long timestamp=0;
    public synchronized void onPreviewFrame(byte[] data, Camera camera) {
        Log.e("CameraTest","Time Gap = "+(System.currentTimeMillis()-timestamp));
        timestamp=System.currentTimeMillis();

        Bitmap mFaceBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
        if (mFaceBitmap!=null) FaceDetection.calculate(mFaceBitmap);

        camera.addCallbackBuffer(data);
        return;
    }
});

我有一个相机视图,在一个简单的视图前面(我可以在其中绘制一些东西)。当我能找到人的脸时,我想在 View 的正面画画。但是mFaceBitmap永远返回null,为什么?

如果这是一个坏主意,我怎样才能做得更好?

4

2 回答 2

2

设置相机时,您需要设置预览大小和预览格式。这是一些示例代码,可以给出粗略的想法:

int previewFormat = 0;
for (int format : parameters.getSupportedPreviewFormats()) {
  if (format == FORMAT_NV21) {
    previewFormat = FORMAT_NV21;
  } else if (previewFormat == 0 && (format == FORMAT_JPEG || format == FORMAT_RGB_565)) {
    previewFormat = format;
  }
}

// TODO: Iterate on supported preview sizes and pick best one
parameters.setPreviewSize(previewSize.width, previewSize.height);

if (previewFormat != 0) {
  parameters.setPreviewFormat(previewFormat);
} else {
  // Error on unsupported format
}

现在在回调中,您可以执行以下操作:

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
  Bitmnap bitmap;
  if (previewFormat == FORMAT_NV21) {
    int[] previewPixels = new int[previewSize.width * previewSize.height];
    decodeYUV420SP(previewPixels, data, previewSize.width, previewSize.height);
    bitmap = Bitmap.createBitmap(rgbPixels, previewSize.width, previewSize.height, Bitmap.Config.RGB_565);
  } else if (previewFormat == FORMAT_JPEG || previewFormat == FORMAT_RGB_565) {
    // RGB565 and JPEG
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inDither = true;
    opts.inPreferredConfig = Bitmap.Config.RGB_565;
    bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
  }
}

最后,转换

 static 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);
     }
   }
 }    
于 2013-01-25T19:47:31.837 回答
1

不幸的是,您不能使用 Bitmap.decodeByteArray 将相机的预览输出转换为位图。

decodeByteArray 设计用于将 JPEG/PNG/etc 图像转换为位图,它无法知道预览回调中的数据是什么样的,因为它是一个简单的像素值原始数组,没有标识头。

您必须自己进行转换。有很多方法可以做到这一点,不同程度的效率 - 我会在这里写出最简单的一种,但它也可能是最慢的。

来自相机的数据字节数组以某种特定的像素格式编码,由Camera.Parameters.setPreviewFormat指定。如果您还没有调用它,则默认格式为NV21。NV21 保证可以在所有安卓设备上运行;在 Android 版本 >= 3.0 上,YV12格式也保证可以工作。

这两种都是 YUV 格式,这意味着颜色被编码为一个亮度(亮度)通道和两个色度(颜色)通道。用于在位图上设置像素值的函数(主要是 setPixels)需要 RGB 颜色空间中的信息,因此需要进行转换。此外,NV21 和 YV12 都对色度通道进行二次采样 - 例如,如果您有 640x480 的图像,亮度通道中将有 640x480 像素,但两个色度通道中只有 320x240 像素。

这意味着您需要创建一个大小合适的新 int[] 数组,然后循环遍历 byte[] 数据数组,收集一组 Y、U 和 V 值,将它们转换为 RGB,然后将它们写入int[] 数组,然后在目标位图上调用 setPixels。您需要的颜色转换矩阵是 JPEG YCbCr->RGB 矩阵,例如,您可以在Wikipedia上找到它。您可以在fourcc上了解NV21或YV12的布局,例如

如果你真的不想搞砸这一切,你也可以使用YuvImage类,尽管是以迂回的方式。只要您使用 NV21 格式,您就可以从预览数据构造一个 YuvImage 实例,然后将其中的 JPEG 保存到ByteArrayOutputStream中。然后,您可以从流中获取 byte[],并使用 Bitmap.decodeByteArray 将其解码为位图。这是完全不必要的 JPEG 往返往返,因此效率非常低,并且可能导致质量损失,但它只需要几行代码。

在最新版本的 Android 中,您还可以使用 Renderscript 高效地进行此转换。您需要将数据复制到分配中,然后使用YUV 到 RGB 脚本内在进行转换。

最后,您可以将数据和目标位图传递到 JNI 代码中,您可以在其中直接访问位图,并在那里用 C 或 C++ 编写转换函数。这需要大量的脚手架,但非常有效。

于 2013-01-25T19:13:46.127 回答