人脸识别模块(一)

在最近的工作中要用到人脸识别的功能,当然市面上有很多成熟的框架,比如虹软、旷视等,但是由于公司这方面的经费有限,所以就没有用,而是用的公司合作的一个摄像头厂商提供的一个人脸识别sdk,相对来说比较便宜😂
接下来我从下面几方面来记录下我开发过程中所积攒的经验~~(所遇到的坑)

一、人脸识别sdk环境、算法初始化;

二、人脸识别步骤过程;

三、人脸识别开发遇到的问题。

一、人脸识别sdk环境、算法初始化

  1. SDK 简介
  • 提取人脸特征值;
    • 输入:byte[]格式或者Bitmap格式图片数据
    • 输出类:FaceFeature
  • 计算人脸特征值之间的相似度;
    • 输入:两个特征值数组
    • 输出:分数(0-1)分数越大表示越相似;
  1. SDK集成步骤
    • 将sdk目录下的所有aar依赖到工程中
    • 将model目录内的模型文件拷贝到assets根目录(若为了减少apk大小,可使用设备绝对路径加载,则在程序运行时需要将model中的文件放置在设备中)
    • 初始化SDKEnv运行环境
    • 初始化FaceIDDetector(人脸检测)、FaceExtractor(人脸识别)


      image.png

      image.png

      image.png

      image.png

      到此为止,准备工作结束了,接下来就是初始化算法环境。

  /**
     * 初始化 人脸识别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);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容