在最近的工作中要用到人脸识别的功能,当然市面上有很多成熟的框架,比如虹软、旷视等,但是由于公司这方面的经费有限,所以就没有用,而是用的公司合作的一个摄像头厂商提供的一个人脸识别sdk,相对来说比较便宜😂
接下来我从下面几方面来记录下我开发过程中所积攒的经验~~(所遇到的坑)
一、人脸识别sdk环境、算法初始化;
二、人脸识别步骤过程;
三、人脸识别开发遇到的问题。
一、人脸识别sdk环境、算法初始化
- SDK 简介
- 提取人脸特征值;
- 输入:byte[]格式或者Bitmap格式图片数据
- 输出类:FaceFeature
- 计算人脸特征值之间的相似度;
- 输入:两个特征值数组
- 输出:分数(0-1)分数越大表示越相似;
- SDK集成步骤
- 将sdk目录下的所有aar依赖到工程中
- 将model目录内的模型文件拷贝到assets根目录(若为了减少apk大小,可使用设备绝对路径加载,则在程序运行时需要将model中的文件放置在设备中)
- 初始化SDKEnv运行环境
-
初始化FaceIDDetector(人脸检测)、FaceExtractor(人脸识别)
到此为止,准备工作结束了,接下来就是初始化算法环境。
/**
* 初始化 人脸识别sdk环境及算法
*/
private void initFaceSdk() {
//初始化需要申请Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_WIFI_STATE 三个权限
Disposable subscribe = new RxPermissions(this)
.request(Manifest.permission.READ_PHONE_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_WIFI_STATE)
.subscribe(aBoolean -> {
if (aBoolean) {
SDKRuntimeEnv.init(null, () -> {
LogUtils.d("初始化环境成功");
// SDK 初始化成功,初始化算法实例
FaceRecognitionManager.getInstance().init(false, new FaceRecognitionManager.InitListener() {
@Override
public void onSucceed() {
LogUtils.dTag(TAG, "人脸识别初始化算法成功");
}
@Override
public void onError(int code, String msg) {
LogUtils.dTag(TAG, "人脸识别初始化算法失败");
}
});
});
}
});
}
/**
* 谷歌SDK初始化销毁包装类
*/
public class SDKRuntimeEnv {
private static final String TAG = "SDKRuntimeEnv";
/**
* 初始化方法
*
* @param modelDir 模型目录(model的父目录绝对路径) 如果模型放在assets中,此参数传空即可
*/
public static void init(String modelDir, Runnable successedRunnable) {
checkKey(Constant.SDK_KEY, modelDir, successedRunnable, () -> {
LogUtils.dTag(TAG,"初始化人脸识别环境失败");
});
}
// 销毁
public static void destroy() {
SDKEnv.destroy();
}
/**
* 核验初始化的授权码
* @param key 授权码
* @param modelDir model模型存放的绝对路径
* @param successedRunnable 授权码验证成功回调
* @param failedRunnable 授权码验证失败回调
*/
private static void checkKey(String key, String modelDir, Runnable successedRunnable, Runnable failedRunnable) {
if (TextUtils.isEmpty(key) || key.startsWith("---")) {
if (failedRunnable != null) {
failedRunnable.run();
}
return;
}
SDKEnv.init(MyApplication.getApplication(), key, modelDir, new SDKEnv.OnSDKInitListener() {
@Override
public void onInitSuccess(String s) {
LogUtils.d(TAG, "onInitSuccess");
if (successedRunnable != null) {
successedRunnable.run();
}
}
@Override
public void onInitError(int errorCode, String message) {
LogUtils.d(TAG, "onInitError");
if (failedRunnable != null) {
failedRunnable.run();
}
}
});
}
}
/**
* 此类包装了算法实现
*/
public class FaceRecognitionManager {
private static final String TAG = "FaceRecognitionManager";
// 单例实现
private static final FaceRecognitionManager sInstance = new FaceRecognitionManager();
/**
* 是否支持活体检测
*/
private boolean enableLiveNess;
public static FaceRecognitionManager getInstance() {
return sInstance;
}
private FaceRecognitionManager() {
}
// 人脸特帧提取
private FaceRecognition faceRecognition;
// 人脸跟踪检测
private FaceIDDetector faceRectSDK;
// 相机流场景人脸识别时的回调方法
private ResultCallback resultCallback;
// 工作线程相关
private HandlerThread workThread;
private Handler workHandler;
// 是否正在异步提取特帧值
private volatile boolean isAsyncExtracting;
/**
* 初始化
*
* @param initListener 初始化监听器
*/
public synchronized void init(boolean enableLiveness, InitListener initListener) {
Log.d(TAG, "init ");
this.enableLiveNess = enableLiveness;
release();
openSubThread();
boolean ret = runOnSubThread(() -> {
int errorCode;
String errorMsg = null;
do {
FaceRecognition faceExtractor = new FaceRecognition();
errorCode = faceExtractor.init(enableLiveness);
if (errorCode != ErrorCode.API_RET_SUCCESS) {
errorMsg = "FaceExtractor.init失败!!!errorCode=" + errorCode;
errorCode += 100;
break;
}
FaceIDDetector faceTrackerDetector = new FaceIDDetector();
errorCode = faceTrackerDetector.init();
if (errorCode != ErrorCode.API_RET_SUCCESS) {
errorMsg = "FaceDetector.init失败!!!errorCode=" + errorCode;
errorCode += 300;
faceExtractor.destroy();
break;
}
FaceRecognitionManager.this.faceRecognition = faceExtractor;
FaceRecognitionManager.this.faceRectSDK = faceTrackerDetector;
} while (false);
if (errorCode != ErrorCode.API_RET_SUCCESS) {
if (null != initListener) {
initListener.onError(errorCode, errorMsg);
}
} else {
if (null != initListener) {
initListener.onSucceed();
}
}
});
if (!ret) {
if (null != initListener) {
initListener.onError(-1, "内部错误");
}
}
}
/**
* 销毁
*/
public synchronized void release() {
if (faceRecognition != null) {
faceRecognition.destroy();
faceRecognition = null;
}
if (faceRectSDK != null) {
faceRectSDK.destroy();
faceRectSDK = null;
}
closeWorkThread();
}
/**
* 获取bitmap中人脸信息以及特征值,用于底库录入和图片比对
*/
public synchronized List<FaceRectAndFeatureInfo> execBitmap(Bitmap bitmap) {
List<FaceRectAndFeatureInfo> faceIdAndFeatureInfos = new ArrayList<>();
if (null != faceRectSDK && null != faceRecognition) {
faceRectSDK.setModel(FaceIDDetector.Model.MODEL_PICTURE);
List<FaceInfo> faceInfos = faceRectSDK.execBitmap(bitmap, SdkImageOrientation.IMAGE_UP);
if (faceInfos.size() != 0) {
if (enableLiveNess) {
faceInfos = getBigFaceInfo(faceInfos);
}
float[][] points = getPointFromFaceDetectInfo(faceInfos);
FaceFeature[] faceFeatures = faceRecognition.execBitmap(bitmap, points);
faceIdAndFeatureInfos = generateFaceRecognitionInfos(faceInfos, faceFeatures);
}
}
return faceIdAndFeatureInfos;
}
private List<FaceInfo> getBigFaceInfo(List<FaceInfo> faceInfos) {
List<FaceInfo> result = new ArrayList<>();
if (faceInfos == null) {
return result;
}
FaceInfo bigestFaceFromFaceList = FaceInfoUtil.getBigestFaceFromFaceList(faceInfos);
if (bigestFaceFromFaceList != null) {
result.add(bigestFaceFromFaceList);
}
return result;
}
/**
* 相机连续帧异步提取特帧值,提取的特帧值以及人脸信息会在AsyncFrameCallback.onResult回调出去
*/
public synchronized void execFrameBytes(byte[] bytes, int width, int height, SdkImageFormat format, SdkImageOrientation orientation, boolean flipx) {
FaceIDDetector faceDetector = this.faceRectSDK;
if (faceDetector != null) {
faceDetector.setModel(FaceIDDetector.Model.MODEL_VIDEO);
// 先提取当前这一帧图像的人脸信息
List<FaceInfo> faceInfos = faceDetector.execBytes(bytes, width, height, format, orientation);
final ResultCallback resultCallback = this.resultCallback;
if (resultCallback != null) {
// 特帧值和人脸信息组合后回调出去
resultCallback.onFaceDetectorCallBack(bytes, width, height, format, orientation, flipx, faceInfos);
}
if (faceInfos == null || faceInfos.size() == 0) {
return;
}
// 如果有人脸并且当前没有在做异步特帧提取时
if (!isAsyncExtracting) {
isAsyncExtracting = true;
runOnSubThread(() -> {
List<FaceInfo> faceInfosExt = new ArrayList<>(faceInfos);
if (enableLiveNess) {
faceInfosExt = getBigFaceInfo(faceInfosExt);
}
// 人脸信息转化为float二维数组,因为提取特帧值需要用到人脸的点位信息
float[][] points = getPointFromFaceDetectInfo(faceInfosExt);
float[][] rectFromFaceDetectInfo = getRectFromFaceInfo(faceInfosExt);
FaceFeature[] faceFeatures = null;
FaceRecognition faceExtractor = this.faceRecognition;
if (faceExtractor != null) {
// 提取人脸特帧值
faceFeatures = this.faceRecognition.execBytes(bytes, width, height, format, points, orientation, rectFromFaceDetectInfo);
}
ResultCallback extractCallback = this.resultCallback;
if (extractCallback != null) {
// 特帧值和人脸信息组合后回调出去
List<FaceRectAndFeatureInfo> faceIdAndFeatureInfos = generateFaceRecognitionInfos(faceInfosExt, faceFeatures);
extractCallback.onFaceRecognitionCallback(bytes, width, height, format, orientation, flipx, faceIdAndFeatureInfos);
}
isAsyncExtracting = false;
});
}
}
}
// 设置提取人脸特帧值结束回调,用于相机流连续提取特帧值的场景
public synchronized void setResultCallback(ResultCallback callback) {
this.resultCallback = callback;
}
/**
* 计算两个人脸特征的相似度
*
* @return 相似度得分 0~1
*/
public static float compare(@NonNull float[] features1, @NonNull float[] features2) {
return FaceRecognition.compare(features1, features2);
}
// 初始化工作线程
private void openSubThread() {
workThread = new HandlerThread("Work Thread");
workThread.start();
workHandler = new Handler(workThread.getLooper());
}
// 销毁工作线程
private void closeWorkThread() {
if (null != workThread) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
workThread.quitSafely();
} else {
workThread.quit();
}
workThread = null;
}
workHandler = null;
}
// 使runnable运行在工作线程
private boolean runOnSubThread(Runnable runnable) {
if (null == runnable) {
return false;
}
if (null != workThread && null != workHandler) {
if (Thread.currentThread().getId() == workThread.getThreadId()) {
runnable.run();
} else {
workHandler.post(runnable);
}
} else {
Log.w(TAG, "工作线程已销毁");
return false;
}
return true;
}
private List<FaceRectAndFeatureInfo> generateFaceRecognitionInfos(List<FaceInfo> faceInfos, FaceFeature[] faceFeatures) {
List<FaceRectAndFeatureInfo> faceIdAndFeatureInfos = new ArrayList<>();
for (int i = 0; i < faceInfos.size(); ++i) {
FaceRectAndFeatureInfo faceIdAndFeatureInfo = new FaceRectAndFeatureInfo();
faceIdAndFeatureInfo.setFaceInfo(faceInfos.get(i));
if (null != faceFeatures) {
faceIdAndFeatureInfo.setFaceFeature(faceFeatures[i]);
}
faceIdAndFeatureInfos.add(faceIdAndFeatureInfo);
}
return faceIdAndFeatureInfos;
}
private float[][] getPointFromFaceDetectInfo(List<FaceInfo> FaceDetectInfos) {
if (null == FaceDetectInfos) {
return new float[0][];
}
float[][] pointss = new float[FaceDetectInfos.size()][];
for (int i = 0; i < FaceDetectInfos.size(); i++) {
pointss[i] = FaceDetectInfos.get(i).getPoints();
}
return pointss;
}
public static float[][] getRectFromFaceInfo(List<FaceInfo> faceDetectInfos) {
if (null == faceDetectInfos) {
return new float[0][];
}
float[][] pointss = new float[faceDetectInfos.size()][];
for (int i = 0; i < faceDetectInfos.size(); i++) {
RectF faceRect = faceDetectInfos.get(i).getRect();
float[] rectInfo = new float[4];
rectInfo[0] = faceRect.left;
rectInfo[1] = faceRect.top;
rectInfo[2] = faceRect.width();
rectInfo[3] = faceRect.height();
pointss[i] = rectInfo;
}
return pointss;
}
@WorkerThread
public interface ResultCallback {
void onFaceRecognitionCallback(byte[] data, int width, int height, SdkImageFormat format, SdkImageOrientation orientation, boolean flipx, List<FaceRectAndFeatureInfo> faceIdAndFeatureInfos);
void onFaceDetectorCallBack(byte[] data, int width, int height, SdkImageFormat format, SdkImageOrientation orientation, boolean flipx, List<FaceInfo> faceInfos);
}
public interface InitListener {
void onSucceed();
void onError(int code, String msg);
}
}