前端时间虹软免费开放了它的人脸检测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();
            }
        });