1、项目背景

应公司要求,需要开发一套类似人脸打卡功能的app,但是因为我们公司没有很强的原生android开发者,所以根据现状选择了第三方跨平台的uniapp,想必目前大多人都了解这个平台了,我也就不多赘述了,直接上uniapp官方网站,它有一个缺点就是很多复杂的功能实现不了,就比如今天我们所要说的基于虹软开放平台的人脸识别功能,那么怎么办呢?当然有办法,使用android原生整合虹软SDK,然后做成插件供uniapp使用,这就是咱们今天的主题。另外具体虹软开放平台是做什么的,大家可以去官方做更深一步的了解,上官方链接:虹软官方,为什么要用虹软,多了不说,我就说一点:免费、免费、免费,这个理由怎么样?!以下是虹软开放平台提供的解决方案:

$\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、完结

了解更多人脸识别产品相关内容请到虹软视觉开放平台哦

《Android原生整合虹软SDK开发uniapp插件》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. 怎么停掉或关闭运行的npm run dev

    可以直接Ctrl+C就会出现 输入是就可以了

  2. excel VBA根据一列的逗号隔开值分行

    Sub test1()    Dim h    Dim j As Integer    j = 0    Dim n1 As Integer '分行单元格在第几列    Dim m1 As Integ ...

  3. Apache Hudi在Hopworks机器学习的应用

    Hopsworks特征存储库统一了在线和批处理应用程序的特征访问而屏蔽了双数据库系统的复杂性.我们构建了一个可靠且高性能的服务,以将特征物化到在线特征存储库,不仅仅保证低延迟访问,而且还保证在服务时间 ...

  4. 限流神器Sentinel,不了解一下吗?

    概述 书接上回:你来说说什么是限流? ,限流的整体概述中,描述了 限流是什么,限流方式和限流的实现.在文章尾部的 分布式限流,没有做过多的介绍,选择了放到这篇文章中.给大伙细细讲解一下 Sentine ...

  5. 8、ITSM基本概念(1)

    ITSM即是信息技术服务管理: 8.1.什么是服务: 8.2.RACI模型: 谁负责(R =n Resposible),即负责执行任务的角色,他/她具体负责操控项目.解决问题. 谁批准(A = Acc ...

  6. 基于Yarp的http内网穿透库HttpMouse

    简介 前几天发表了<基于Yarp实现内网http穿透>,当时刚刚从原理图变成了粗糙的代码实现,项目连名字都还没有,也没有开放源代码.在之后几天的时间,我不断地重构,朝着"可集成. ...

  7. Mybatis学习(4)实现关联数据的查询

    有了前面几章的基础,对一些简单的应用是可以处理的,但在实际项目中,经常是关联表的查询,比如最常见到的多对一,一对多等.这些查询是如何处理的呢,这一讲就讲这个问题.我们首先创建一个Article 这个表 ...

  8. 大数据-Hadoop虚拟机的准备以及配置(三台机子)

    虚拟机的准备 修改静态IP(克隆的虚拟机) vim /etc/udev/rules.d/70-persistent-net.rules 配置网络: Vim /etc/sysconfig/network ...

  9. php混淆加密解密实战

    在查看别人的php源码的时候,我们经常会看到加密后的php代码.那么php加密原理是什么呢?怎么解密呢? 混淆加密 我们从百度随便搜索一个加密网站,例如:http://dezend.qiling.or ...

  10. mybatis框架的第二天

    一.mybatis的基础crud的操作 先在接口中,写对应的方法名称,返回类型,访问符. 之后在映射配置文件中,写具体的实现 二.mybati中crud的细节 1.模糊查询 这是接口中 这是xml中 ...