当我们使用活体检测检测完毕后,需要上传图片给后台进行人脸识别,在 Android 里面默认的 Camera 获取的图片格式是 NV21.
怎么知道这个默认格式呢?
1
2
| Camera.Parameters parameters = camera.getParameters();
parameters.getPreviewFormat();
|
这个格式的图片本身虹软是支持的,但是有个条件限制:SDK 对图形尺寸做了限制,宽高需要大于 0,宽度为 4 的倍数, YUYV/I420/NV21/NV12 格式的图片高度为 2 的倍数, RGB24 格式的图片高度不限制。如果你的图片是 NV21 格式不符合这个条件就需要进行转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| private static final int VALUE_FOR_4_ALIGN = 0b11;
private static final int VALUE_FOR_2_ALIGN = 0b01;
/**
* 确保传给引擎的NV21数据宽度为4的倍数,高为2的倍数
*
* @param bitmap 传入的bitmap
* @return 调整后的bitmap
*/
public static Bitmap alignBitmapForNv21(Bitmap bitmap) {
if (bitmap == null || bitmap.getWidth() < 4 || bitmap.getHeight() < 2) {
return null;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
boolean needAdjust = false;
//保证宽度是4的倍数
if ((width & VALUE_FOR_4_ALIGN) != 0) {
width &= ~VALUE_FOR_4_ALIGN;
needAdjust = true;
}
//保证高度是2的倍数
if ((height & VALUE_FOR_2_ALIGN) != 0) {
height--;
needAdjust = true;
}
if (needAdjust) {
bitmap = imageCrop(bitmap, new Rect(0, 0, width, height));
}
return bitmap;
}
|
如何从 onPreviewFrame(byte[] data, Camera camera)
回调中获取 Bitmap 对象呢?这个时候就需要用到 YuvImage
.
什么是 YuvImage
YuvImage 包含了 YUV 数据,并且提供了一个将 YUV 数据压缩成 Jpeg 数据的方法。
什么时候使用
相机 Camera 类的 PreviewCallback 回调中,这个接口回调的是相机的预览图片,是 YUV 格式的数据,这是,利用 YuvImage 对象的 compressToJpeg 方法生成 Jpeg 格式的图片保存在本地。
1
2
3
4
5
6
7
8
| Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
YuvImage image = new YuvImage(data, parameters.getPreviewFormat(),
size.width, size.height, null);
File file = new File(Environment.getExternalStorageDirectory()
.getPath() + "/check_face.jpg");
FileOutputStream filecon = new FileOutputStream(file);
image.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 90, filecon);
|
当然也可以使用如下方式转换为 Bitmap 对象:
1
2
3
4
5
6
7
8
9
10
11
| Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
YuvImage image = new YuvImage(data, parameters.getPreviewFormat(),
size.width, size.height, null);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 90, outputStream);
byte[] jpegData = outputStream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap nv21Bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
|
得到 Bitmap 对象后我们可以先进行旋转等处理,当然上面的 compressToJpeg
中的 Rect 最好是头像的轮廓,而不是整个画布,这样可以极大的缩小图片大小。
1
2
3
4
| Matrix matrix = new Matrix();
//matrix.postScale(1, 1);
matrix.postRotate(cameraOri);
Bitmap matrixedBitmap = Bitmap.createBitmap(nv21Bitmap, 0, 0, nv21Bitmap.getHeight(), nv21Bitmap.getWidth(), matrix, true);
|
最后进行 NV21 的宽高检测,确保传给引擎的 NV21 数据宽度为 4 的倍数,高为 2 的倍数,然后保存为文件上传给服务器。
1
2
3
4
| Bitmap checkedBitmap = ImageUtils.alignBitmapForNv21(matrixedBitmap);
File file = saveBitmapToFile(checkedBitmap, Environment.getExternalStorageDirectory()
.getPath() + "/check_face.jpg");
LogPlus.d("http", "开始上传头像");
|
最后再给出一些常用的工具方法:
保存Bitmap到文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| private File saveBitmapToFile(Bitmap bitmap, String filePath) {
if (bitmap == null || TextUtils.isEmpty(filePath)) {
return null;
}
try {
File file = new File(filePath);
if (!file.exists() && file.getParentFile().mkdirs() && !file.createNewFile()) {
return null;
}
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
return file;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
|
NV21 转换为 Bitmap 对象
1
2
3
4
5
6
7
8
9
| private Bitmap nv21ToBitmap(byte[] data, int width, int height) {
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 70, outputStream);
byte[] jpegData = outputStream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
}
|
确保传给引擎的 BGR24 数据宽度为4的倍数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public static Bitmap alignBitmapForBgr24(Bitmap bitmap) {
if (bitmap == null || bitmap.getWidth() < 4) {
return null;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
boolean needAdjust = false;
//保证宽度是4的倍数
if ((width & VALUE_FOR_4_ALIGN) != 0) {
width &= ~VALUE_FOR_4_ALIGN;
needAdjust = true;
}
if (needAdjust) {
bitmap = imageCrop(bitmap, new Rect(0, 0, width, height));
}
return bitmap;
}
|
Bitmap 转化为 RGB 数据(格式为 Bitmap.Config#ARGB_8888)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public static byte[] bitmapToBgr(Bitmap image) {
if (image == null) {
return null;
}
int bytes = image.getByteCount();
ByteBuffer buffer = ByteBuffer.allocate(bytes);
image.copyPixelsToBuffer(buffer);
byte[] temp = buffer.array();
byte[] pixels = new byte[(temp.length / 4) * 3];
for (int i = 0; i < temp.length / 4; i++) {
pixels[i * 3] = temp[i * 4 + 2];
pixels[i * 3 + 1] = temp[i * 4 + 1];
pixels[i * 3 + 2] = temp[i * 4];
}
return pixels;
}
|