Android虹軟人臉識別2.0

最近研究了一下比較熱門的人臉識別技術,經過一番調研,選取了虹軟AI開放平臺。

人臉識別的應用也是相對廣泛,借用官網一張圖來說明。


應用場景.png

在Android App也有不少應用場景,比如:刷臉打卡、身份驗證等。

爲什麼選擇虹軟人臉識別?

  • 虹軟公司是一傢俱有硅谷背景的圖像處理公司,虹軟在人臉相關領域裏的研究成果及技術應用不論是其深度和廣度,都是全球領先者。虹軟的人臉相關技術廣泛應用於智能手機、DSC、平板、IP Camera, 機器人、智能家居、智能終端等領域,超過十億臺的設備在使用該技術。此外虹軟人臉相關技術更被廣泛用於專業的垂直領域比如公安,安防和醫療等。

  • 虹軟人臉識別等SDK是免費的,且支持離線環境使用

虹軟功能簡介

人臉檢測
對傳入圖像數據進行人臉檢測,返回人臉位置信息和人臉在圖像中的朝向信息,可用於後續的人臉分析、人臉比對操作,支持圖像模式和視頻流模式。支持單人臉、多人臉檢測,最多支持檢測人臉數爲50

人臉追蹤
捕捉視頻流中的人臉信息,精確定位並跟蹤面部區域位置

人臉比對
將兩個人臉進行比對,來判斷是否爲同一個人,返回比對相似度值,如果相似度值在0.8以上,一般可認爲是同一個人

人臉查找
在人臉庫中查找相似的人臉

人臉屬性
對檢測到的人臉進行屬性分析,支持性別、年齡,3d等屬性分析,支持圖像模式和視頻流模式。

活體檢測
檢測是否爲真人,防止惡意***。針對視頻流/圖片,通過採集人像的破綻來判斷目標對象是否爲活體,可有效防止照片、屏幕二次翻拍等作弊***。只有視頻模式下才能檢測成功。

人臉三維角度檢測
檢測輸入圖像數據指定區域人臉的三維角度信息,包含人臉三個空間角度:俯仰角(pitch), 橫滾角(roll), 偏航角(yaw), 支持圖像模式和視頻流模式。

三維


人臉註冊和識別的流程如下

人臉識別流程


準備工作

首先去虹軟官網的新手幫助查看SDK配置和使用的方法,也可以下載官方Demo來體驗一下。

V2.0相較於V1.0的區別

1、按照官方的說法,V2.0比V1.0在速度上有所提升,具體多少,我沒測過;
2、V2.0在人臉檢測中增加了人臉3D角度值和活體檢測值,以前活體檢測是單獨的一項,是收費的,在V2.0中變成免費的了(親測可行)
3、SDK變化很大,整體更簡潔了,很多操作需要通過FaceEngine對象來操作,初始化也只需要1個就行,比以前簡單了。

SDK的調用流程圖


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)通常相似度在0.8以上可認爲是同一個人。

Step6:調用 FaceEngine 的 process 方法,傳入不同的 combineMask 組合可對 Age、 Gender、Face3Dangle、Liveness 進行檢測,傳入的 combineMask 的任一屬性都需要在 init 時進行初始化。

Step7:調用 FaceEngine 的 getAgegetGendergetFace3DanglegetLiveness 方法可獲 取年齡、性別、三維角度、活體檢測結果,且每個結果在獲取前都需要在 process 中進行 處理。

Step8:調用 FaceEngine 的unInit 方法銷燬引擎。在 init 成功後如不 unInit 會導致內存 泄漏。

注意:調用FaceEngine中的任何一個方法時都會返回一個int的結果信息。如果成功的話會返回ErrorInfo.MOK(0),否則就是失敗。你可以根據返回的errorCode去官網查詢。

激活人臉識別引擎SDK

mFaceEngine = new FaceEngine();
        boolean isActive = SharedPrefUtils.getBoolean(mContext, Constants.IS_ACTIVE, false);
        if (!isActive) {
            int activeCode = mFaceEngine.active(this,
                    Constants.APP_ID,
                    Constants.SDK_KEY);
            if (activeCode == ErrorInfo.MOK) {
                Log.i(TAG, "人臉引擎激活成功");
                SharedPrefUtils.writeBoolean(mContext, Constants.IS_ACTIVE, true);
            } else {
                Log.i(TAG, "人臉引擎激活失敗 " + activeCode);
            }
        }

注意:激活引擎的active()方法,需要將APP_ID和SDK_KEY替換成你自己的(官網獲取)。一個設備安裝後僅需激活一次,卸載重 新安裝後需要重新激活。

初始化人臉識別引擎

/**
     * 初始化圖片識別引擎(視頻、拍照)
     * 調用FaceEngine的init方法初始化SDK,初始化成功後才能進一步使用SDK的功能。
     *
     * @param context
     * @param detectFaceMaxNum 最大檢測人數
     */
    public boolean initVideoEngine(Context context, int detectFaceMaxNum) {
        mFaceEngine = new FaceEngine();
        int faceEngineCode = mFaceEngine.init(context,
                FaceEngine.ASF_DETECT_MODE_VIDEO,
                FaceEngine.ASF_OP_270_ONLY,
                16,
                detectFaceMaxNum,
                FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
        if (faceEngineCode == ErrorInfo.MOK) {
            Log.i(TAG, "人臉引擎初始化成功");
        } else {
            Log.i(TAG, "人臉引擎初始化失敗 " + faceEngineCode);
        }
        return faceEngineCode == ErrorInfo.MOK;
    }

V2.0的初始化還是簡潔了不少,以前是多個對象初始化,2.0只需要FaceEngine 初始化就行了。

參數2:檢測模式,分別是視頻模式ASF_DETECT_MODE_VIDEO和圖像模式ASF_DETECT_MODE_IMAGE,如果需要活體檢測的話只能使用視頻模式。

參數3:人臉檢測方向,如果使用前置攝像頭的話設置爲270度即可;如果是檢測圖片中的人臉的話要使用ASF_OP_0_HIGHER_EXT 。

參數4:人臉相對於所在圖片的長邊的佔比,在視頻模式ASF_DETECT_MODE_VIDEO下有效值範圍[2,16],在圖像模式ASF_DETECT_MODE_IMAGE下有效值範圍[2,32]

參數5:人臉引擎最多能檢測出的人臉數,有效值範圍[1,50]

參數6:初始化引擎所需的功能,可以是以下單個或者多個,用 | 運算符拼接
ASF_NONE
ASF_FACE_DETECT
ASF_FACE_RECOGNITION
ASF_AGE
ASF_GENDER
ASF_FACE3DANGLE
ASF_LIVENESS

人臉識別

通過照片或者相機獲取頭像,圖片不用說了,打開相冊獲取圖片數據即可,相機的話,需要打開前置攝像頭,在相機預覽回調的方法中獲取頭像數據

Camera.PreviewCallback mPreViewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(final byte[] data, Camera camera) {
            if (startFaceCheck) {
              int detectFacesCode = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
if (detectFacesCode== ErrorInfo.MOK&&faceInfoList!=null&&faceInfoList.size()>0){
                    Log.i(TAG, "onPreview 人臉檢測成功 : ");
                }else {
                    Log.i(TAG, "onPreview 人臉檢測失敗 : "+detectFacesCode);
                }
            }
        }
    };

在相機預覽的回調方法中的data數據就是NV21格式,可以在這裏處理人臉檢測

注意:只有code爲ErrorInfo.MOK且檢測到的人臉數>=1纔算檢測成功,否則就是失敗。

參數1:圖像數據,也就是Camera中onPreviewFrame回調的數據
參數2:圖像的寬度,也就是Camera的setPreviewSize的寬度
參數3:圖像的高度,也就是Camera的setPreviewSize的高度
參數4:圖像的顏色空間格式,支持NV21(CP_PAF_NV21)、BGR24(CP_PAF_BGR24)
參數5:人臉列表,List<FaceInfo> faceInfoList,如果調用detectFaces成功之後,它就會有數據。

根據相機獲取人臉數據成功以後,需要繪製一個人臉框的自定義View來把臉的輪廓給框起來。這一步你以爲很複雜,其實一點也不難,因爲虹軟SDK已經幫你獲取到了人臉位置座標了,你只需要簡單調用一下繪製一個矩形框就好。
簡單一點的,只畫一個純實線的矩形,你可以調用這個方法。

 /**
     * 繪製人臉框
     *
     * @param bitmap
     */
    public Bitmap drawFaceRect(Bitmap bitmap) {
        //繪製bitmap
        bitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(10);
        paint.setColor(Color.YELLOW);

        for (int i = 0; i < faceInfoList.size(); i++) {
            //繪製人臉框
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawRect(faceInfoList.get(i).getRect(), paint);
            //繪製人臉序號
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            paint.setTextSize(faceInfoList.get(i).getRect().width() / 2);
            canvas.drawText("" + i, faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
        }
        return bitmap;
    }

如果需要更復雜的自定義人臉框,你可以參考demo中的DrawHelper類更改

檢測人臉成功以後,你可以繼續檢測人臉年齡、性別等信息和提取人臉特徵。

檢測人臉年齡、性別等信息

注意:這一步調用前需要進行人臉檢測detectFaces才行。

/**
     * 檢測人臉年齡、性別等信息
     *
     * @param bgr24
     * @param width
     * @param height
     * @param format             圖像的格式
     * @param onFaceDetectResult
     */
    public void detectFaceInfo(byte[] bgr24, int width, int height, int format, IOnFaceDetectResult onFaceDetectResult) {
        /**
         * 4.上一步已獲取到人臉位置和角度信息,傳入給process函數,進行年齡、性別、三維角度檢測
         */
        int faceProcessCode = mFaceEngine.process(bgr24, width, height, format, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);
        if (faceProcessCode != ErrorInfo.MOK) {
            onFaceDetectResult.detectFaceInfos(faceProcessCode, "獲取人臉年齡、性別等信息失敗", null);
        } else {
            Log.i(TAG, "processImage 獲取年齡、性別等信息成功 : ");
        }


        //年齡信息結果
        List<AgeInfo> ageInfoList = new ArrayList<>();
        //性別信息結果
        List<GenderInfo> genderInfoList = new ArrayList<>();
        //人臉三維角度結果
        List<Face3DAngle> face3DAngleList = new ArrayList<>();
        //活體檢測結果
        List<LivenessInfo> livenessInfoList = new ArrayList<>();
        //獲取年齡、性別、三維角度、活體結果
        int ageCode = mFaceEngine.getAge(ageInfoList);
        int genderCode = mFaceEngine.getGender(genderInfoList);
        int face3DAngleCode = mFaceEngine.getFace3DAngle(face3DAngleList);
        int livenessCode = mFaceEngine.getLiveness(livenessInfoList);

        if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
            Log.i(TAG, "detectFaceInfo 獲取部分信息失敗: ageCode: " + ageCode + " genderCode:" + genderCode + " face3DAngleCode:" + face3DAngleCode + " livenessCode:" + livenessCode);
            return;
        }


        /**
         * 5.年齡、性別、三維角度已獲取成功,添加信息到提示文字中
         */
        List<FaceDetectInfo> mFaceDetectInfos = new ArrayList<>();
        for (int i = 0; i < faceInfoList.size(); i++) {
            FaceInfo faceInfo = faceInfoList.get(i);
            FaceFeature faceFeature = extractFaceFeature(bgr24, width, height, format, faceInfo);

            FaceDetectInfo faceDetectInfo = new FaceDetectInfo();
            faceDetectInfo.setFaceAge(ageInfoList.get(i).getAge());
            faceDetectInfo.setFaceGender(genderInfoList.get(i).getGender());
            faceDetectInfo.setFace3DAngle(face3DAngleList.get(i));
            faceDetectInfo.setLivenessInfo(livenessInfoList.get(i));
            faceDetectInfo.setFaceInfo(faceInfo);
            faceDetectInfo.setFaceFeature(faceFeature);
            mFaceDetectInfos.add(faceDetectInfo);
        }

        onFaceDetectResult.detectFaceInfos(faceProcessCode, "獲取人臉年齡、性別等信息失敗", mFaceDetectInfos);

    }

人臉特徵提取

注意:這一步調用前需要進行人臉檢測detectFaces才行。

/**
     * 提取人臉特徵
     *
     * @param bgr24
     * @param width
     * @param height
     * @param faceInfo
     * @param format   圖像的格式
     */
    public FaceFeature extractFaceFeature(byte[] bgr24, int width, int height, int format, FaceInfo faceInfo) {
        FaceFeature faceFeature = new FaceFeature();
     
        int extractFaceFeatureCode = mFaceEngine.extractFaceFeature(bgr24, width, height, format, faceInfo, faceFeature);
        if (extractFaceFeatureCode != ErrorInfo.MOK) {
            Log.i(TAG, "extractFaceFeature:extract face feature failed,code= " + extractFaceFeatureCode);
            faceFeature = null;
        } else {
            Log.i(TAG, "extractFaceFeature 提取人臉特徵成功 : " + faceFeature.toString());
        }
        return faceFeature;
    }

FaceFeature就是人臉特徵對象,它可以用來比對2張人臉的相似度。如果提取成功,這個FaceFeature對象就有數據,否則爲空。

人臉比對

這一步思路比較簡單,通過照相機獲取人臉特徵對象FaceFeature,再從數據庫中查詢要對比的人的FaceFeature,如果相似度在0.8以上就可以認爲是同一個人。

/**
     * 人臉比對
     *
     * @param faceFeature1 第一張人臉特徵
     * @param faceFeature2 第二張人臉特徵
     * @return
     */
    public float compare(FaceFeature faceFeature1, FaceFeature faceFeature2) {
        if (faceFeature1 == null || faceFeature2 == null) {
            return 0;
        }
        FaceSimilar faceSimilar = new FaceSimilar();
        //比對兩個人臉特徵獲取相似度信息
        int compareFaceFeature = mFaceEngine.compareFaceFeature(faceFeature1, faceFeature2, faceSimilar);
        if (compareFaceFeature == ErrorInfo.MOK) {
            //獲取相似度
            float score = faceSimilar.getScore();
            Log.d(TAG, "人臉比對成功 " + faceSimilar.getScore());
            return score;
        } else {
            Log.d(TAG, "人臉比對失敗 " + compareFaceFeature);
        }
        return 0;
    }

人臉識別系統的思路大致如下:打開前置攝像頭獲取人臉數據,如果獲取成功,則從數據庫中去查詢匹配這個人的人臉相似度,如果沒有查詢到數據或者相似度特別低可以認爲人臉庫中沒有這個人,那就可以走註冊流程;如果相似度在0.8以上可以視爲查找到本人,那就是打卡成功。注意:打卡必須要檢測到這個人是活體才能進行下一步比較,否則用一張照片也可以矇混過關。

虹軟底活體檢測,我測過多次,真心nb,基本別想用其他手段代替真人。


歡迎大家批評指正!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章