安卓CameraX基于虹软人脸识别程序开发
一、前言
需求:根据镜头内看到的人脸,获取其在系统中注册的用户信息
设计方案:
①注册:用户通过前端系统自拍图片发送给后端注册。后端对图片进行人脸特征数据分析,将特征数据保存到用户数据库。
②识别:用户正对设备镜头,通过前端识别到人脸时,对人脸分析获取特征数据,将特征数据发送给后端获取用户信息。后端比对特征数据,将相似度0.9以上中最高的用户返回。
准备:虹软注册账号获取APP_ID和SDK_KEY(付费版还要一个Active_Key)和下载SDK
二、初始SDK
要使用SDK,得先把sdk放到项目里,然后首次需要激活SDK,每次使用SDK需要初始化
1、先切换到Project视图,将下载的SDK 放到对应位置。然后在build.gradle中引入一下
2、到MainActive中先激活SDK,实际生产中不要每次都去激活,记录首次取激活就好了
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);toActiveFaceSDK(); // 激活
}
public void toActiveFaceSDK(){int status=FaceEngine.activeOnline(this, 你的APP_ID, 你的SDK_KEY);if(status == ErrorInfo.MOK || status == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED){Toast.makeText(this, "激活成功", Toast.LENGTH_LONG).show();initEngine();//激活成功那就去初始化SDK,准备使用}else if(status == ErrorInfo.MERR_ASF_VERSION_NOT_SUPPORT){Toast.makeText(this, "安卓版本不支持人脸识别", Toast.LENGTH_LONG).show();}else{Toast.makeText(this, "激活失败,状态码:" + status, Toast.LENGTH_LONG).show();}
}
3、初始化SDK,准备使用,每次页面onDestroy的时候记得对应卸载SDK
public void initEngine(){ // 初始化sdkfaceObj = new FaceEngine();DetectFaceOrientPriority ftOrient = DetectFaceOrientPriority.ASF_OP_ALL_OUT; // 默认是对拿到的图像进行全角度检测
// DetectFaceOrientPriority ftOrient = DetectFaceOrientPriority.ASF_OP_270_ONLY; // 270afCode = faceObj.init(that.getApplicationContext(),DetectMode.ASF_DETECT_MODE_VIDEO,ftOrient,16, 3,faceObj.ASF_FACE_DETECT | faceObj.ASF_AGE |faceObj.ASF_FACE3DANGLE|faceObj.ASF_GENDER | faceObj.ASF_LIVENESS| faceObj.ASF_FACE_RECOGNITION);if (afCode != ErrorInfo.MOK) {if(afCode == ErrorInfo.MERR_ASF_NOT_ACTIVATED){Toast.makeText(this, "设备SDK未激活", Toast.LENGTH_LONG).show();}else{Toast.makeText(this, "初始化失败"+afCode, Toast.LENGTH_LONG).show(); }}
}
public void unInitEngine() { //卸载SDKif (afCode == 0) {afCode = faceObj.unInit();}
}
三、配置相机
安卓Camera API到目前为止已有三代,ArcFace官方教程给的是Camera 1,但我在AS中引入的时候,会划删除线,提示Out并建议使用Camera2替代。于是乎,我使用Camera2去尝试替换官方的一代写法,虽然勉强成功,但是说实在的各种适配问题层出不穷,里面做适配写的代码占了一大半。So 最终我使用了CameraX,就这个预览自适应,就已经天下无敌了!推荐《为什么使用CameraX》。
1、CameraX需要在build.gradle中引入几个包,如下:
def camerax_version = "1.1.0-alpha08"implementation("androidx.camera:camera-core:${camerax_version}")implementation("androidx.camera:camera-camera2:${camerax_version}")implementation("androidx.camera:camera-lifecycle:${camerax_version}")implementation("androidx.camera:camera-view:1.0.0-alpha25")implementation("androidx.camera:camera-extensions:1.0.0-alpha25")
2、创建预览View
页面定义变量public int rational;private Size mImageReaderSize;private FaceEngine faceObj; //sdk对象private int afCode = -1; // sdk状态private ProcessCameraProvider cameraProvider = null; // cameraX对象private Preview mPreviewBuild = null; //cameraX的previewprivate PreviewView viewFinder = null; // X的自带预览viewprivate byte[] nv21 = null; // 实时nv21图像流protected void onCreate()(RelativeLayout layout=that.findViewById(R.id.camerax_preview);viewFinder =new PreviewView(that);//我是动态创建了个Preview,你也可以在页面直接写上layout.addView(viewFinder);Point screenSize = new Point(); // 获取屏幕总宽高 getWindowManager().getDefaultDisplay().getSize(screenSize);//getRealSize就会包括状态栏rational = aspectRatio(screenSize.x, screenSize.y);startCameraX();
}private double RATIO_4_3_VALUE = 4.0 / 3.0;private double RATIO_16_9_VALUE = 16.0 / 9.0;private int aspectRatio(int width, int height) {double previewRatio = Math.max(width, height) * 1.00 / Math.min(width, height);if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) {return AspectRatio.RATIO_4_3;}return AspectRatio.RATIO_16_9;}
3、初始化相机
private void startCameraX() {ListenableFuture<ProcessCameraProvider> providerFuture = ProcessCameraProvider.getInstance(that);providerFuture.addListener(() -> {try { // 检测CameraProvider可用性cameraProvider = providerFuture.get();} catch (ExecutionException | InterruptedException e) {Toast.makeText(this, "相机X不可用", Toast.LENGTH_LONG).show();e.printStackTrace();return;}CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT
// CameraSelector.LENS_FACING_BACK).build();//绑定预览mPreviewBuild = new Preview.Builder()
// .setTargetRotation(Surface.ROTATION_180)//设置预览旋转角度.build();mPreviewBuild.setSurfaceProvider(viewFinder.getSurfaceProvider()); // 绑定显示// 图像分析,监听试试获取的图像流ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().setTargetAspectRatio(rational).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
//阻塞模式:ImageAnalysis.STRATEGY_BLOCK_PRODUCER (在此模式下,执行器会依序从相应相机接收帧;这意味着,如果 analyze() 方法所用的时间超过单帧在当前帧速率下的延迟时间,所接收的帧便可能不再是最新的帧,因为在该方法返回之前,新帧会被阻止进入流水线)
//非阻塞模式: ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST (在此模式下,执行程序在调用 analyze() 方法时会从相机接收最新的可用帧。如果此方法所用的时间超过单帧在当前帧速率下的延迟时间,它可能会跳过某些帧,以便 analyze() 在下一次接收数据时获取相机流水线中的最新可用帧).build();imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this),new FaceAnalyze.MyAnalyzer() //绑定给我的图片分析类);// 绑定生命周期前先解绑cameraProvider.unbindAll();// 绑定Camera mCameraX = cameraProvider.bindToLifecycle((LifecycleOwner) this,cameraSelector,imageAnalysis,mPreviewBuild);}, ContextCompat.getMainExecutor(this));}
4、图片分析类,ImageUtil里的代码最后给出
/*** 实时预览 Analyzer处理*/private class MyAnalyzer implements ImageAnalysis.Analyzer {private byte[] y;private byte[] u;private byte[] v;private ReentrantLock lock = new ReentrantLock();private Object mImageReaderLock = 1;//1 available,0 unAvailableprivate long lastDrawTime = 0;private int timerSpace = 300; // 识别间隔@Overridepublic void analyze(@NonNull ImageProxy imageProxy) {@SuppressLint("UnsafeOptInUsageError") Image mediaImage = imageProxy.getImage();
// int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();if(mediaImage != null){synchronized (mImageReaderLock) {/*识别频率Start 和状态*/long start = System.currentTimeMillis();if (start - lastDrawTime < timerSpace || !mImageReaderLock.equals(1)) {mediaImage.close();imageProxy.close();return;}lastDrawTime = System.currentTimeMillis();/*识别频率End*///判断YUV类型,我们申请的格式类型是YUV_420_888if (ImageFormat.YUV_420_888 == mediaImage.getFormat()) {Image.Plane[] planes = mediaImage.getPlanes();if (mImageReaderSize == null) {mImageReaderSize = new Size(planes[0].getRowStride(), mediaImage.getHeight());}lock.lock();if (y == null) {y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];}//从planes中分别获取y、u、v 变量数据if (mediaImage.getPlanes()[0].getBuffer().remaining() == y.length) {planes[0].getBuffer().get(y);planes[1].getBuffer().get(u);planes[2].getBuffer().get(v);if (nv21 == null) {nv21 = new byte[planes[0].getRowStride() * mediaImage.getHeight() * 3 / 2];}if (nv21 != null && (nv21.length != planes[0].getRowStride() * mediaImage.getHeight() * 3 / 2)) {return;}// 回传数据是YUV422if (y.length / u.length == 2) {ImageUtil.yuv422ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mediaImage.getHeight());}// 回传数据是YUV420else if (y.length / u.length == 4) {nv21 = ImageUtil.yuv420ToNv21(mediaImage);}//调用Arcsoft算法,获取人脸特征getFaceInfo(nv21);}lock.unlock();}}}//一定要关闭mediaImage.close();imageProxy.close();}}
5、进行人脸特征分析
/*** 获取人脸特征*/private int lastFaceID=-1;private void getFaceInfo(byte[] nv21) {List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();FaceFeature faceFeature = new FaceFeature();//第一步是送数据给arcsoft sdk,检查是否有人脸信息。int code = faceObj.detectFaces(nv21, mImageReaderSize.getWidth(),mImageReaderSize.getHeight(),faceObj.CP_PAF_NV21, faceInfoList);//数据检查正常,并且含有人脸信息,则进行下一步的人脸识别。if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {int newFaceID = faceInfoList.get(0).getFaceId();// 每次不离镜头的同一人脸,其ID均相同if (lastFaceID == newFaceID) { // 相同脸则returnLog.i("人脸ID相同", "旧" + lastFaceID);// + "、新" + newFaceIDreturn;}lastFaceID = newFaceID;
// long start = System.currentTimeMillis();// 特征数据分析部分机型会卡code = faceObj.extractFaceFeature(nv21, mImageReaderSize.getWidth(), mImageReaderSize.getHeight(),faceObj.CP_PAF_NV21, faceInfoList.get(0), faceFeature);if (code != ErrorInfo.MOK) {return;}byte[] featureData=faceFeature.getFeatureData();//最终的特征数据!!!post(featureData); //把数据给后端去查信息
// Log.e(TAG, "特征长度"+faceFeature.getFeatureData().length);// long end = System.currentTimeMillis();
// Log.e(TAG+lastFaceID, "耗时: "+(end-start)+"ms特征数据---: "+featureData.length);}}
四、ImageUtil代码
public class ImageUtil {/*** 将Y:U:V == 4:2:2的数据转换为nv21** @param y Y 数据* @param u U 数据* @param v V 数据* @param nv21 生成的nv21,需要预先分配内存* @param stride 步长* @param height 图像高度*/public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {System.arraycopy(y, 0, nv21, 0, y.length);// 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算int length = y.length + u.length / 2 + v.length / 2;int uIndex = 0, vIndex = 0;for (int i = stride * height; i < length; i += 2) {nv21[i] = v[vIndex];nv21[i + 1] = u[uIndex];vIndex += 2;uIndex += 2;}}/*** 将Y:U:V == 4:1:1的数据转换为nv21** @param y Y 数据* @param u U 数据* @param v V 数据* @param nv21 生成的nv21,需要预先分配内存* @param stride 步长* @param height 图像高度*/public static void yuv420ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {System.arraycopy(y, 0, nv21, 0, y.length);// 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算int length = y.length + u.length + v.length;int uIndex = 0, vIndex = 0;for (int i = stride * height; i < length; i++) {nv21[i] = v[vIndex++];nv21[i + 1] = u[uIndex++];}}public static byte[] yuv420ToNv21(Image image) {Image.Plane[] planes = image.getPlanes();ByteBuffer yBuffer = planes[0].getBuffer();ByteBuffer uBuffer = planes[1].getBuffer();ByteBuffer vBuffer = planes[2].getBuffer();int ySize = yBuffer.remaining();int uSize = uBuffer.remaining();int vSize = vBuffer.remaining();int size = image.getWidth() * image.getHeight();byte[] nv21 = new byte[size * 3 / 2];yBuffer.get(nv21, 0, ySize);vBuffer.get(nv21, ySize, vSize);byte[] u = new byte[uSize];uBuffer.get(u);//每隔开一位替换V,达到VU交替int pos = ySize + 1;for (int i = 0; i < uSize; i++) {if (i % 2 == 0) {nv21[pos] = u[i];pos += 2;}}return nv21;}public static Bitmap nv21ToBitmap(byte[] nv21,int w, int h) {YuvImage image = new YuvImage(nv21, ImageFormat.NV21, w, h, null);ByteArrayOutputStream stream = new ByteArrayOutputStream();image.compressToJpeg(new Rect(0, 0, w, h), 80, stream);Bitmap newBitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());try{stream.close();}catch (Exception e){}return newBitmap;}
}
That's all
Thanks !!!
附:
人脸路程中的坑《人脸识别坑》
二进制的特征数据怎么发给后台,参考我的另外一篇《安卓form-data形式上传二进制》
安卓CameraX基于虹软人脸识别程序开发相关推荐
- 安卓Camera一代基于虹软人脸识别程序开发
如果不是CameraX不能使用,建议使用CameraX<基于CameraX实现人脸> 不推荐Camera2,适配难!还巨麻烦,要写的代码配置太多了! 一代我是直接新建的Class继承了Te ...
- 基于虹软人脸识别Web私有化服务(快速人脸服务集成二次开发)
基于虹软人脸识别微服务 生物智能识别服务开放接口(基于OAuth2.0) 完整文档 生物智能识别服务开放接口202000722v1.03.01.pdf 生物智能识别服务系统是一个将人脸识别.指纹识别. ...
- 基于虹软人脸识别,实现RTMP直播推流追踪视频中所有人脸信息(C#)
大家应该都知道几个很常见的例子,比如在张学友的演唱会,在安检通道检票时,通过人像识别系统成功识别捉了好多在逃人员,被称为逃犯克星:人行横道不遵守交通规则闯红灯的路人被人脸识别系统抓拍放在大屏上以示警告 ...
- 【javaCV基于虹软人脸识别demo添加电脑摄像头人脸识别(图片保存,视频保存,摄像头显示等 )(附源码)】
javaCV基于虹软人脸识别demo添加电脑摄像头人脸识别(图片保存,视频保存,摄像头显示等 )(附源码) 文章目录 javaCV基于虹软人脸识别demo添加电脑摄像头人脸识别(图片保存,视频保存,摄 ...
- unity接入实现人脸识别应用-基于虹软人脸识别算法4.0
一.准备工作 1.下载虹软人脸识别增值版SDK 4.0 1)注册并登录开发者中心 2)下载虹软人脸识别SDK 2.安装Unity3D及Visual Studio 2019开发环境 1)安装Unity ...
- Android相机预览,指定区域显示预览框,在区域内出现人脸进行人脸识别,并抓拍人脸照片存在本地,CameraX,虹软人脸识别
效果图: 第一种是使用camerax进行预览,android camerax预览官方文档,主要通过imageAnalysis,抓帧进行图片处理,然后通过android自带的图片人脸识别FaceDete ...
- 基于虹软人脸识别-iOS画框更改及前后摄像头的切换
公司项目使用过程中,为了配合市场需要,需要增加人脸识别+活体检测的功能.并且要求人脸识别的样式接近于主流产品的样式.所以选择了方便快捷的虹软人脸识别SDK. 1.项目逻辑流程图 根据下面的逻辑梳理,可 ...
- 基于虹软人脸识别API和Qt5的人脸识别
2019独角兽企业重金招聘Python工程师标准>>> 测试和使用了虹软的人脸API在QT5环境下设计了一个简单的人脸识别软件,实现了对人脸的跟踪和人脸识别.摄像头的控制以及图像格式 ...
- 基于虹软人脸识别,实现超市人脸支付
前言 随着计算机和网络技术的不断发达,人脸识别在我们的生活中也不断的被应用,而无人超市以及在超市中的人脸支付并没有普及,本文作为一个项目进行超市人脸支付的场景测试与探索. 实现功能 使用python语 ...
- android虹软人脸识别简书,基于虹软人脸识别API和Qt5的人脸识别
测试和使用了虹软的人脸API在QT5环境下设计了一个简单的人脸识别软件,实现了对人脸的跟踪和人脸识别.摄像头的控制以及图像格式的转换使用了Opencv,图像显示使用的是QT5的Qimage控件.下面是 ...
最新文章
- R构建径向核支持向量机分类器实战代码(Radial kernel Support Vector Classifier)
- 【转】如何使用应用日志(Application Log)
- 爨底下-双龙峡,凑合的一日游
- “达观杯”文本智能处理挑战赛,季军带你飞
- 函数式接口的概念函数式接口的定义
- 前端学习(1521):vue-cli工具介绍
- iOS Emoji表情编码/解码
- 整数区间(信息学奥赛一本通-T1324)
- jquery框架分析-构造jquery对象初步
- Spring 容器简介
- import time python_Python的import导入与时间
- HDFS集群常见异常及排查步骤
- Sea Battle<海战>(思路题)
- python 映射网络驱动器_用Delphi实现网络驱动器的映射和断开
- Android 退出app,后台推送的服务也停止了,怎么可以做到不停止后台服务呢?
- cad插件制作教程_站长私藏CAD插件分享(内附使用教程)
- Elasticsearch(ES)创建索引
- 数据库查询语句内连接外连接效率
- qq2007服务器中断,自动重启pubwin2007服务器脚本
- android模拟器横屏快捷键,Android模拟器横屏切换方法