基于虹软SDK的人脸检测方案(2)
前端时间虹软免费开放了它的人脸检测SDK,速度还可以,比openCV快一些,主要openCV太臃肿,集成也不方便,调研了几种本地算法之后,最终选择了虹软的。
它现在免费开放的SDK中有5个功能:
- 人脸识别(AFD)
- 人脸跟踪(AFT)
- 人脸比对(AFR)
- 年龄
- 性别
年龄和性别,一般来说没什么用,顶多也就做一些娱乐向的东西,因为它居然把我鉴定为XX岁。。。手动再见这个功能。
主要说一下人脸识别和人脸跟踪,因为他们刚开放的SDK,所以文档和资料比较少,这两个功能是一样的,主要的区别是跟踪更适合用在动态识别上,比如Camera的Preview中的data,而识别更适合用于静态的图片,当然两者是通用的,比如下面我故意在相机预览中使用AFD来做检测,并没什么问题,用法差不多,检测速度大概在20MS,还是很快的
更新:
Detection 一个data检测时间大概在200ms,哪怕没有脸的图,但是检测到最小的人脸更小,大概在40像素左右,60度左右的摄像头480P的模式下大概在3米,人脸越大检测时间越短,前后两张脸距离超过大约50CM左右,会检测不到小的脸
Tracking 对单个data检测的速度更快,但是能识别的人脸需要比较大,最小需要60多,60度左右的摄像头480P的模式下大概在1.5米,识别时间在全志V40上9-50多ms,似乎跟人脸面积关系不大
RECT坐标点以左上角为00点,最右和最下为data宽高尺寸
人脸对比的速度,主要取决于CPU的运算速度了,我用的板子比较差,一张脸取特征点需要1700MS以上,I5在600MS左右。
代码放最后,主要在Camera.PreviewCallback中,贴出一些使用示例
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//输入的data数据为NV21格式(如Camera里NV21格式的preview数据),其中height不能为奇数,人脸检测返回结果保存在result。
afd_fsdkError = afd_fsdkEngine.AFD_FSDK_StillImageFaceDetection(data, mPreviewWidth, mPreviewHeight, AFD_FSDKEngine.CP_PAF_NV21, afd_fsdkFaces);
if (afd_fsdkError.getCode() != AFD_FSDKError.MOK) {
// 错误处理
Logger.w("人脸识别ErrorCode =" + afd_fsdkError.getCode() + " Face=" + afd_fsdkFaces.size());
// 错误日志上传服务器
}
int size = afd_fsdkFaces.size();
if (mFaceNum != size) {
mFaceNum = size;
if (size != 0) {
for (AFD_FSDKFace face : afd_fsdkFaces) {
AFR_FSDKFace result = new AFR_FSDKFace();
// 本地人脸对比 START --------------------------------------------------
if (openLocal) {
long time = System.currentTimeMillis();
afr_fsdkError = afr_fsdkEngine.AFR_FSDK_ExtractFRFeature(data, mPreviewWidth, mPreviewHeight, AFR_FSDKEngine.CP_PAF_NV21, face.getRect(), face.getDegree(), result);
// 开始比对 FaceDB.FaceRegist,本地没有再网络检索
AFR_FSDKMatching score = new AFR_FSDKMatching();
for (FaceDB.FaceRegist faceRegist : mFaceDB.mRegister) {
for (AFR_FSDKFace afr_fsdkFace : faceRegist.mFaceList) {
afr_fsdkError = afr_fsdkEngine.AFR_FSDK_FacePairMatching(result, afr_fsdkFace, score);
if (score.getScore() > 0.6f) {
// 置信度大于60%认为通过
// 拿名字去播报
String[] nameAndId = faceRegist.mName.split(NameIdSeparator);
Logger.e("本地搜索结果: " + faceRegist.mName);
speakAndUpload(currentTimeMillis, nameAndId[0], nameAndId[1], null, nameAndId[2]);
return;
}
}
}
Logger.w("人脸提取 + 本地识别比对 cost :" + (System.currentTimeMillis() - time) + "ms" +
"\n" + "Face=" + result.getFeatureData()[0] + "," + result.getFeatureData()[1] + "," + result.getFeatureData()[2] + "," + afr_fsdkError.getCode());
}
// 本地人脸对比 END -------------------------------------
// 裁剪出人脸
YuvImage yuv = new YuvImage(data, ImageFormat.NV21, mPreviewWidth, mPreviewHeight, null);
KeyByteArrayOutputStream ops = new KeyByteArrayOutputStream();
yuv.compressToJpeg(face.getRect(), 100, ops);
//Bitmap bitmap = BitmapFactory.decodeByteArray(ops.getByteArray(), 0, ops.getByteArray().length);
// 转BASE64
//ops.reset();
//bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ops);
// 转换为字符串
// ops.toByteArray()精准长度 ops.getByteArray()固定长度
String base64Bitmap = Base64.encodeToString(ops.toByteArray(), Base64.DEFAULT);
mSearchFaceOnNetExecutor.execute(() -> search(base64Bitmap, currentTimeMillis, result));
try {
ops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//clear result. 还是用原来那个数量变化的思路解决重复问题
afd_fsdkFaces.clear();
}
});
人脸识别的方案现在选择比较多
想问一下还有哪些比较好的方案啊?可以分享一下吗?