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. 报错:vmnet8设置中出现错误。子网IP和子网掩码不一致

    报错:vmnet8设置中出现错误.子网IP和子网掩码不一致 设置子网IP时报错,如下图 同样的,写成192.168.0.0就没问题,如下图 总结: 这个虚拟网络编辑器是给添加网卡的,你添加vmnet8 ...

  2. SQL 利用存储过程实现对表数据有则更新无则添加(转)

    初学存储过程,发现这篇文章简单易懂,特意转载,地址 http://blog.csdn.net/luotuomianyang/article/details/52013144 如果某一操作包含大量的T- ...

  3. npm i安装命令中的-g -D -S的区别

    -g为全局安装 -D 对模块进行局部安装,模块写入到 devDependencies 对象 用于开发阶段,开发时用到的工具等 -S 局部安装,不同的是模块写入到 dependencies对象 用于生产 ...

  4. R语言--图形基本使用1

    1 使用图形 1.1 交互式绘图 使用的是内置数据集:mtcars 画出散点图:plot(mtcars$mpg,mtcars$wt) 给图形加标题:title("车辆耗油与重量之间的关系&q ...

  5. 时光卷轴,Microsoft大事记

    Our mission is to empower every person and every organization on the planet to achieve more. 2021年6月 ...

  6. 8.QSharedPointer

    QSharedPointer 是一个共享指针, 同时是引用计数型的智能指针 ,也就是说,QSharedPointer可以被自由地拷贝和赋值,在任意的地方共享它. QSharedPointer内部会对拥 ...

  7. 收集雪花(map)

    题目描述 不同的雪花往往有不同的形状.在北方的同学想将雪花收集起来,作为礼物送给在南方的同学们.一共有n个时刻,给出每个时刻下落雪花的形状,用不同的整数表示不同的形状.在收集的过程中,同学们不希望有重 ...

  8. layui动态创建Tab、Tab右键功能

      1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 & ...

  9. [心得体会]SpringMVC源码分析

    1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...

  10. challenge

    一.查壳 无壳的64位linux文件 二.拖入ida静态分析 F5反编译一下. 发现这段代码实际意义就是把字符串传进v7当中.v7的值就是flag值. 这个函数整体分析下来,各种递归,尝试用c语言来复 ...