《Android原生整合虹軟SDK開發uniapp外掛》

碼仔很忙發表於2021-08-05

1、專案背景

應公司要求,需要開發一套類似人臉打卡功能的app,但是因為我們公司沒有很強的原生android開發者,所以根據現狀選擇了第三方跨平臺的uniapp,想必目前大多人都瞭解這個平臺了,我也就不多贅述了,直接上uniapp官方網站,它有一個缺點就是很多複雜的功能實現不了,就比如今天我們所要說的基於虹軟開放平臺的人臉識別功能,那麼怎麼辦呢?當然有辦法,使用android原生整合虹軟SDK,然後做成外掛供uniapp使用,這就是我們們今天的主題。另外具體虹軟開放平臺是做什麼的,大家可以去官方做更深一步的瞭解,上官方連結:虹軟官方,為什麼要用虹軟,多了不說,我就說一點:免費、免費、免費,這個理由怎麼樣?!以下是虹軟開放平臺提供的解決方案:

微信圖片_20210803110554.png

$\color{DarkTurquoise}{溫馨提示}$

$\color{DarkTurquoise}{本篇就是針對小白寫的,小白不用怕
另外也需要一定的android原生基礎,入門能看懂程式碼即可,不需要精通}$

2、本篇用到的技術棧以及SDK

- 虹軟人臉識別SDK v3.0

- android

- vue

- uniapp

3、技術接入部分

1、去虹軟控制檯(要登入哦)下載人臉識別Demo,傳送陣

注意需要新建一個應用,如下圖,SDK中包含Demo

在這裡插入圖片描述

2、 將Demo匯入AndroidStudio,下圖就是Demo的樣子:

$\color{DarkTurquoise}{注意:AndroidStudio匯入的專案路徑一定不要有中文}$

*

3、如果不出意外的話,執行專案就會出現如下介面了,至此虹軟Demo也就跑起來了

如果出意外了,請檢視該文章的$\color{DarkTurquoise}{可能遇到的錯誤}$章節

4、接下來去跑uniapp的Demo,首先去uniapp官方下載Android平臺uni原生外掛開發Demo

5、將Demo匯入AndroidStudio,下圖就是Demo的樣子:

$\color{DarkTurquoise}{注意:AndroidStudio匯入的專案路徑一定不要有中文}$

在這裡插入圖片描述

6、跑專案,會出現$\color{DarkTurquoise}{未配置appkey或配置錯誤}$字樣,解決方法請參考:如何申請appkey傳送陣\

$\color{DarkTurquoise}{注意解決這個問題還是稍微比較複雜點的,請認真閱讀官方文件,不要懷疑官方文件的正確性}$


//過程中需要用到的一個生成 sha1 值得命令,在 C:\Program Files\Java\jre1.8.0_291\bin 路徑下執行 cmd
keytool.exe -list -v -keystore 【keystore檔案的絕對路徑】

7、拿到 appkey 之後,寫入 AndroidManifest.xml 檔案中的 meta-data 中,然後將申請 appkey 過程中申請的證照配置到專案中,再次跑專案,如果不出意外的話,執行專案就會出現如下介面了,至此uniapp的Demo也就跑起來了

在這裡插入圖片描述

8、兩個 Demo 都跑起來了,接下來就是整合兩個 Demo 了,首先在 uniapp 的 Demo 中右擊建立一個Module

在這裡插入圖片描述

9、選擇 Android Library ,在右側填寫如下圖幾個屬性,注意 Package name 儘量與虹軟Demo中的一致,因為之後會避免解決一些不必要的錯誤,下一步

在這裡插入圖片描述

10、將虹軟 Demo 中的如下 資料夾中的所有內容(包括資料夾)複製到剛才建立的 Module 中的同樣位置

libs 
java 
jniLibs
res

11、將 Module 中的 build.gradle 中的 dependencies 全部刪除,加入下面的

compileOnly fileTree(dir: '../app/libs', include: ['uniapp-v8-release.aar'])
implementation 'com.alibaba:fastjson:1.1.46.android'
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
compileOnly "com.android.support:recyclerview-v7:28.0.0"
compileOnly "com.android.support:support-v4:28.0.0"
compileOnly "com.android.support:appcompat-v7:28.0.0"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

12、截至目前步驟,我們所有的準備基本已經就緒,接下來我們需要建立一下三個檔案

FaceReco_AppProxy.java //用於初始化動態連結庫
FaceReco.java //用於啟用虹軟SDK 
FaceRecoView.java //用於人臉檢測檢視

在這裡插入圖片描述

13、我們找到 FaceAttrPreviewActivity 檔案,將關於人臉識別的核心程式碼拷貝到 FaceRecoView 檔案中,核心程式碼如下:

 * 初始化引擎
 */
private void initEngine() {
    faceEngine = new FaceEngine();
    afCode = faceEngine.init(getContext(), DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(getContext()),
            16, 20, FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
    Log.i(TAG, "initEngine:  init: " + afCode);
    if (afCode != ErrorInfo.MOK) {
        System.out.println(R.string.init_failed+":"+afCode);
    }
}

/**
 * 解除安裝引擎
 */
private void unInitEngine() {
    if (afCode == 0) {
        afCode = faceEngine.unInit();
        Log.i(TAG, "unInitEngine: " + afCode);
    }
}

/**
 * 初始化攝像頭
 */
private void initCamera() {
    DisplayMetrics metrics = new DisplayMetrics();
    Activity activity = (Activity)getContext();
    activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
    CameraListener cameraListener = new CameraListener() {

        @Override
        public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
            Log.i(TAG, "onCameraOpened: " + cameraId + "  " + displayOrientation + " " + isMirror);
            previewSize = camera.getParameters().getPreviewSize();
            drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
                    , cameraId, isMirror, false, false);
        }

        @Override
        public void onPreview(byte[] nv21, Camera camera) {
            if (faceRectView != null) {
                faceRectView.clearFaceInfo();
            }
            List<FaceInfo> faceInfoList = new ArrayList<>();
            long start = System.currentTimeMillis();
            int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
            if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
                code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask);
                if (code != ErrorInfo.MOK) {
                    return;
                }
            } else {
                return;
            }
            List<AgeInfo> ageInfoList = new ArrayList<>();
            List<GenderInfo> genderInfoList = new ArrayList<>();
            List<Face3DAngle> face3DAngleList = new ArrayList<>();
            List<LivenessInfo> faceLivenessInfoList = new ArrayList<>();
            int ageCode = faceEngine.getAge(ageInfoList);
            int genderCode = faceEngine.getGender(genderInfoList);
            int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
            int livenessCode = faceEngine.getLiveness(faceLivenessInfoList);
            // 有其中一個的錯誤碼不為ErrorInfo.MOK,return
            if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
                return;
            }
            System.out.println("檢測成功");
            if (faceRectView != null && drawHelper != null) {
                List<DrawInfo> drawInfoList = new ArrayList<>();
                for (int i = 0; i < faceInfoList.size(); i++) {
                    drawInfoList.add(new DrawInfo(drawHelper.adjustRect(faceInfoList.get(i).getRect()), genderInfoList.get(i).getGender(), ageInfoList.get(i).getAge(), faceLivenessInfoList.get(i).getLiveness(), RecognizeColor.COLOR_UNKNOWN, null));
                }
                drawHelper.draw(faceRectView, drawInfoList);
            }
        }

        @Override
        public void onCameraClosed() {
            Log.i(TAG, "onCameraClosed: ");
        }

        @Override
        public void onCameraError(Exception e) {
            Log.i(TAG, "onCameraError: " + e.getMessage());
        }

        @Override
        public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
            if (drawHelper != null) {
                drawHelper.setCameraDisplayOrientation(displayOrientation);
            }
            Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + "  " + displayOrientation);
        }
    };
    cameraHelper = new CameraHelper.Builder()
            .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
            .rotation(activity.getWindowManager().getDefaultDisplay().getRotation())
            .specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT)
            .isMirror(false)
            .previewOn(previewView)
            .cameraListener(cameraListener)
            .build();
      cameraHelper.init();
      cameraHelper.start(); 
 }

14、此時人臉檢測頁面就整合到 uniapp 中了,當然還不可以使用,為什麼呢?當然是還有兩個檔案沒做完呢,一個用於啟用SDK的,一個用於初始化載入動態連結庫檔案的,最重要的兩步,開搞~\

15、首先將初始化動態連結庫檔案程式碼寫入 FaceReco_AppProxy 檔案中

 * 檢查能否找到動態連結庫,如果找不到,請修改工程配置
 *
 * @param libraries 需要的動態連結庫
 * @return 動態庫是否存在
 */
private boolean checkSoFile(String[] libraries,Application application) {
    ApplicationInfo applicationInfo = application.getApplicationInfo();
    File dir = new File(applicationInfo.nativeLibraryDir);
    System.out.println("檔案路徑:"+dir.getAbsolutePath());
    File[] files = dir.listFiles();
    if (files == null || files.length == 0) {
        return false;
    }
    List<String> libraryNameList = new ArrayList<>();
    for (File file : files) {
        System.out.println("檔名字:"+file.getName());
        libraryNameList.add(file.getName());
    }
    boolean exists = true;
    for (String library : libraries) {
        exists &= libraryNameList.contains(library);
    }
    return exists;
}


16、然後啟用SDK檔案程式碼寫入 FaceReco 檔案中

/**
 * 啟用裝置
 */
private void active(){
    Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(ObservableEmitter<Integer> emitter) {
            RuntimeABI runtimeABI = FaceEngine.getRuntimeABI();
            Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI);
            int activeCode = FaceEngine.activeOnline(mUniSDKInstance.getContext(), CommonUtil.getAppId(), CommonUtil.getSdkKey());
            emitter.onNext(activeCode);
        }
    })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<Integer>() {
                @Override
                public void onSubscribe(Disposable d) {

                }
                @Override
                public void onNext(Integer activeCode) {
                    if (activeCode == ErrorInfo.MOK) {
                        showToast(getString(R.string.active_success));
                        mJsCallback.invokeAndKeepAlive("啟用成功");
                    } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
                        showToast(getString(R.string.already_activated));
                        mJsCallback.invokeAndKeepAlive("該裝置已啟用");
                    } else {
                        showToast(getString(R.string.active_failed)+":"+activeCode);
                        mJsCallback.invokeAndKeepAlive("啟用失敗,錯誤碼:"+activeCode);
                    }
                    ActiveFileInfo activeFileInfo = new ActiveFileInfo();
                    int res = FaceEngine.getActiveFileInfo(mUniSDKInstance.getContext(), activeFileInfo);
                    if (res == ErrorInfo.MOK) {
                        Log.i(TAG, activeFileInfo.toString());
                    }
                }
                @Override
                public void onError(Throwable e) {
                    showToast(e.getMessage());
                }
                @Override
                public void onComplete() {

                }
            });
}

17、將 AndroidManifest.xml 檔案替換如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.arcsoft.arcfacedemo">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

18、至此,我們們外掛的所有的配置基本完成,接下來刪除兩個資料夾,為什麼要刪除這兩個資料夾呢,因為這兩個資料夾都是安卓原生的 activity 檢視,因為目前我們們的檢視是 uniapp 來驅動的,所以用不到這些東西了

activity 
fragment

19、將 app 專案中引入我們們的外掛,在 app 專案中的 build.gradle 中配置

implementation project(':arcfacedemo')

20、將專案跑起來,沒有任何錯誤,漂亮,一切皆是那麼的完美,如下圖,呵呵,沒有任何變化,為什麼沒有變化呢?我們們繼續!

在這裡插入圖片描述

21、剛剛看到的是我們們的 uniapp 主介面,我們們目前只是把外掛部分做完了,接下來就是讓 uniapp 去調我們們的外掛,首先去寫一個介面,在這裡我就不寫介面了,我就直接說怎麼調外掛了,咦,對了,我們們的外掛還沒有打包,接下來打包外掛

22、在 Android Studio 中選擇 Build->Rebuild Project ,就將外掛打包好了,如圖:

在這裡插入圖片描述

23、怎麼用呢?在這裡我提供一下 package.json ,有了這個就不用我多說了吧!

{
    "name": "虹軟SDK人臉檢測",
    "id": "arc-face",
#### "version": "1.0.0",
    "description": "基於虹軟SDK開發的人臉檢測外掛,外掛永久維護,歡迎提需求(qq群:785919513)",
    "_dp_type":"nativeplugin",
    "_dp_nativeplugin":{
        "android": {
            "plugins": [
            	{
					"type": "module",
					"name": "arc-faceReco",
					"class": "com.arcsoft.arcfacedemo.FaceReco"
				},
				{
					"type": "component",
					"name": "arc-faceRecoView",
					"class": "com.arcsoft.arcfacedemo.FaceRecoView"
				}
            ],
            "hooksClass": "com.arcsoft.arcfacedemo.FaceReco_AppProxy",
            "integrateType": "aar",
            "abis": [
                "armeabi-v7a",
                "arm64-v8a"
            ],
            "minSdkVersion":23
        }
    }
}

24、至此外掛製作的全過程講解完畢\

25、最後附上原始碼:原始碼傳送陣

4、可能遇到的錯誤

這個怎麼說呢!一般遇到編譯不通過的錯誤大部分都是環境問題,或者業務問題,這個需要對症下藥,博主說一下自己在整合的時候遇到的一些問題吧

1.找不到動態連結庫(.so檔案)

解決方法:忘記把 .so 檔案拷貝過來

2.忘記這個錯誤了,稍後補上

解決方法:建立 Module 時選擇 Android Library ,而不是選擇 Phone & Tablet

3.忘記這個錯誤了,稍後補上

解決方法:專案路徑中不要有中文

5、完結

瞭解更多人臉識別產品相關內容請到虹軟視覺開放平臺

相關文章