Android虹软人脸识别V2.1接入
最近在做虹软的人脸识别接入介入过程中遇到了一些问题,下面记录一下,防止其他人踩坑
首选先讲下接入流程
1.权限
获取设备唯一标识,用于SDK激活授权
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
允许应用联网,用于SDK联网激活授权
<uses-permission android:name="android.permission.INTERNET" />
2.支持图片的颜色空间格式 (非常重要,如果自己本地的图片进行注册人脸这里会用到)
3.工程配置
1. 新建一个Android Project,切换到Project视图;
2. 将libarcsoft_face.so和libarcsoft_face_engine.so添加到src->main->jniLibs->armeabi-v7a路径下;
3. 将arcsoft_face.jar放入,并依赖进工程;
4.调用流程
Step1:调用FaceEngine的active方法激活设备,一个设备安装后仅需激活一次,卸载重新安装后需要重新激活。
Step2:调用FaceEngine的init方法初始化SDK,初始化成功后才能进一步使用SDK的功能。
Step3:调用FaceEngine的detectFaces方法进行图像数据或预览数据的人脸检测,若检测成功,则可得到一个人脸列表。(初始化时combineMask需要ASF_FACE_DETECT)
Step4:调用FaceEngine的extractFaceFeature方法可对图像中指定的人脸进行特征提取。(初始化时combineMask需要ASF_FACE_RECOGNITION)
Step5:调用FaceEngine的compareFaceFeature方法可对传入的两个人脸特征进行比对,获取相似度。(初始化时combineMask需要ASF_FACE_RECOGNITION)
Step6:调用FaceEngine的process方法,传入不同的combineMask组合可对Age、Gender、Face3Dangle、Liveness进行检测,传入的combineMask的任一属性都需要在init时进行初始化。
Step7:调用FaceEngine的getAge、getGender、getFace3Dangle、getLiveness方法可获取年龄、性别、三维角度、活体检测结果,且每个结果在获取前都需要在process中进行处理。
Step8:调用FaceEngine的unInit方法销毁引擎。在init成功后如不unInit会导致内存泄漏。
5.按照官网的SDK说明中可以正常运行并使用demo但是,手动注册人脸照片时会遇到注册人脸失败的问题主要是图片注册需要转化为NV21的格式,直接将图片转换为byte是不能用的
下面是手动注册人脸的方法
/**
* 将准备注册的状态置为{@link #REGISTER_STATUS_READY}
*
* @param view 注册按钮
*/
public void test(View view) {
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bst_test);
Bitmap bitmap = ImageUtil.alignBitmapForNv21(mBitmap);
// byte[] bytes = bitmapToNv21(mBitmap, mBitmap.getWidth(), mBitmap.getHeight());
byte[] bytes = ImageUtil.bitmapToNv21(bitmap, bitmap.getWidth(), bitmap.getHeight());
boolean success = FaceServer.getInstance().register(MainActivity.this, bytes.clone(), bitmap.getWidth(), bitmap.getHeight(), "registered " + "bst_test");
if (success){
showToast("注册成功");
}else {
showToast("注册失败");
}
}
public boolean register(Context context, byte[] nv21, int width, int height, String name) {
synchronized (this) {
if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.length != width * height * 3 / 2) {
return false;
}
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
boolean dirExists = true;
//特征存储的文件夹
File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
if (!featureDir.exists()) {
dirExists = featureDir.mkdirs();
}
if (!dirExists) {
return false;
}
//图片存储的文件夹
File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
if (!imgDir.exists()) {
dirExists = imgDir.mkdirs();
}
if (!dirExists) {
return false;
}
//1.人脸检测
List<FaceInfo> faceInfoList = new ArrayList<>();
int code = faceEngine.detectFaces(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList);
if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
FaceFeature faceFeature = new FaceFeature();
//2.特征提取
code = faceEngine.extractFaceFeature(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList.get(0), faceFeature);
String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
try {
//3.保存注册结果(注册图、特征数据)
if (code == ErrorInfo.MOK) {
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
//为了美观,扩大rect截取注册图
Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect());
if (cropRect == null) {
return false;
}
File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);
FileOutputStream fosImage = new FileOutputStream(file);
yuvImage.compressToJpeg(cropRect, 100, fosImage);
fosImage.close();
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
//判断人脸旋转角度,若不为0度则旋转注册图
boolean needAdjust = false;
if (bitmap != null) {
switch (faceInfoList.get(0).getOrient()) {
case FaceEngine.ASF_OC_0:
break;
case FaceEngine.ASF_OC_90:
bitmap = ImageUtil.getRotateBitmap(bitmap, 90);
needAdjust = true;
break;
case FaceEngine.ASF_OC_180:
bitmap = ImageUtil.getRotateBitmap(bitmap, 180);
needAdjust = true;
break;
case FaceEngine.ASF_OC_270:
bitmap = ImageUtil.getRotateBitmap(bitmap, 270);
needAdjust = true;
break;
default:
break;
}
}
if (needAdjust) {
fosImage = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);
fosImage.close();
}
FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);
fosFeature.write(faceFeature.getFeatureData());
fosFeature.close();
//内存中的数据同步
if (faceRegisterInfoList == null) {
faceRegisterInfoList = new ArrayList<>();
}
faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
下面是官方工具类
package com.example.shine.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.MediaStore;
import java.io.IOException;
import java.nio.ByteBuffer;
//官方提供的工具类
public class ImageUtil {
private static final int VALUE_FOR_4_ALIGN = 0b11;
private static final int VALUE_FOR_2_ALIGN = 0b01;
/**
* Bitmap转化为ARGB数据,再转化为NV21数据
*
* @param src 传入的Bitmap,格式为{@link Bitmap.Config#ARGB_8888}
* @param width NV21图像的宽度
* @param height NV21图像的高度
* @return nv21数据
*/
public static byte[] bitmapToNv21(Bitmap src, int width, int height) {
if (src != null && src.getWidth() >= width && src.getHeight() >= height) {
int[] argb = new int[width * height];
src.getPixels(argb, 0, width, 0, 0, width, height);
return argbToNv21(argb, width, height);
} else {
return null;
}
}
/**
* ARGB数据转化为NV21数据
*
* @param argb argb数据
* @param width 宽度
* @param height 高度
* @return nv21数据
*/
private static byte[] argbToNv21(int[] argb, int width, int height) {
int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int index = 0;
byte[] nv21 = new byte[width * height * 3 / 2];
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int R = (argb[index] & 0xFF0000) >> 16;
int G = (argb[index] & 0x00FF00) >> 8;
int B = argb[index] & 0x0000FF;
int Y = (66 * R + 129 * G + 25 * B + 128 >> 8) + 16;
int U = (-38 * R - 74 * G + 112 * B + 128 >> 8) + 128;
int V = (112 * R - 94 * G - 18 * B + 128 >> 8) + 128;
nv21[yIndex++] = (byte) (Y < 0 ? 0 : (Y > 255 ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0 && uvIndex < nv21.length - 2) {
nv21[uvIndex++] = (byte) (V < 0 ? 0 : (V > 255 ? 255 : V));
nv21[uvIndex++] = (byte) (U < 0 ? 0 : (U > 255 ? 255 : U));
}
++index;
}
}
return nv21;
}
/**
* bitmap转化为bgr数据,格式为{@link Bitmap.Config#ARGB_8888}
*
* @param image 传入的bitmap
* @return bgr数据
*/
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;
}
/**
* 裁剪bitmap
*
* @param bitmap 传入的bitmap
* @param rect 需要被裁剪的区域
* @return 被裁剪后的bitmap
*/
public static Bitmap imageCrop(Bitmap bitmap, Rect rect) {
if (bitmap == null || rect == null || rect.isEmpty() || bitmap.getWidth() < rect.right || bitmap.getHeight() < rect.bottom) {
return null;
}
return Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), null, false);
}
public static Bitmap getBitmapFromUri(Uri uri, Context context) {
if (uri == null || context == null) {
return null;
}
try {
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {
if (b == null) {
return null;
}
Matrix matrix = new Matrix();
matrix.postRotate(rotateDegree);
return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);
}
/**
* 确保传给引擎的BGR24数据宽度为4的倍数
*
* @param bitmap 传入的bitmap
* @return 调整后的bitmap
*/
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;
}
/**
* 确保传给引擎的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;
}
}
这样就可以手动注册成功了