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,为什么呢?
如果这是一个坏主意,我该怎样才能更好地做到这一点?
答案 0 :(得分: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);
}
}
}
答案 1 :(得分:1)
不幸的是,您无法使用Bitmap.decodeByteArray将相机的预览输出转换为位图。
decodeByteArray用于将JPEG / PNG / etc图像转换为位图,它无法知道预览回调中的数据是什么样的,因为它是一个简单的像素值原始数组,没有标识头
您必须自己进行转换。有很多方法可以做到这一点,效率各不相同 - 我会在这里写出最简单的方法,但它也可能是最慢的。
来自摄像机的数据字节数组以某种特定的像素格式进行编码,该格式由Camera.Parameters.setPreviewFormat指定。如果您尚未调用此方法,则默认格式为NV21。 NV21保证可以在所有Android设备上运行;在Android版本&gt; = 3.0上,YV12格式也可以保证有效。
这两种都是YUV格式,意味着颜色被编码为亮度(亮度)通道和两个色度(颜色)通道。在Bitmap(主要是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 to RGB script intrinsic进行转换。
最后,您可以将数据和目标位图传递到JNI代码,您可以在其中直接访问Bitmap,并使用C或C ++编写转换函数。这需要很多脚手架,但效率很高。