当前位置:   article > 正文

安卓CameraX基于虹软人脸识别程序开发_merr_asf_version_not_support

merr_asf_version_not_support

 ————————————————重要通知——————————————

Hello,本人的博客文章已更新至个人网站(www.jonexu.cn)

文章中有问题可以到网站联系博主,后续新的文章也将更新在个人网站

——————————————————————————————————

一、前言

 

需求:根据镜头内看到的人脸,获取其在系统中注册的用户信息

设计方案:

①注册:用户通过前端系统自拍图片发送给后端注册。后端对图片进行人脸特征数据分析,将特征数据保存到用户数据库。

②识别:用户正对设备镜头,通过前端识别到人脸时,对人脸分析获取特征数据,将特征数据发送给后端获取用户信息。后端比对特征数据,将相似度0.9以上中最高的用户返回。

准备:虹软注册账号获取APP_ID和SDK_KEY(付费版还要一个Active_Key)和下载SDK

二、初始SDK

要使用SDK,得先把sdk放到项目里,然后首次需要激活SDK,每次使用SDK需要初始化

1、先切换到Project视图,将下载的SDK 放到对应位置。然后在build.gradle中引入一下

2、到MainActive中先激活SDK,实际生产中不要每次都去激活,记录首次取激活就好了

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. toActiveFaceSDK(); // 激活
  5. }
  6. public void toActiveFaceSDK(){
  7. int status=FaceEngine.activeOnline(this, 你的APP_ID, 你的SDK_KEY);
  8. if(status == ErrorInfo.MOK || status == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED){
  9. Toast.makeText(this, "激活成功", Toast.LENGTH_LONG).show();
  10. initEngine();//激活成功那就去初始化SDK,准备使用
  11. }else if(status == ErrorInfo.MERR_ASF_VERSION_NOT_SUPPORT){
  12. Toast.makeText(this, "安卓版本不支持人脸识别", Toast.LENGTH_LONG).show();
  13. }else{
  14. Toast.makeText(this, "激活失败,状态码:" + status, Toast.LENGTH_LONG).show();
  15. }
  16. }

3、初始化SDK,准备使用,每次页面onDestroy的时候记得对应卸载SDK

  1. public void initEngine(){ // 初始化sdk
  2. faceObj = new FaceEngine();
  3. DetectFaceOrientPriority ftOrient = DetectFaceOrientPriority.ASF_OP_ALL_OUT; // 默认是对拿到的图像进行全角度检测
  4. // DetectFaceOrientPriority ftOrient = DetectFaceOrientPriority.ASF_OP_270_ONLY; // 270
  5. afCode = faceObj.init(that.getApplicationContext(),
  6. DetectMode.ASF_DETECT_MODE_VIDEO,ftOrient,16, 3,
  7. faceObj.ASF_FACE_DETECT | faceObj.ASF_AGE |faceObj.ASF_FACE3DANGLE
  8. |faceObj.ASF_GENDER | faceObj.ASF_LIVENESS| faceObj.ASF_FACE_RECOGNITION);
  9. if (afCode != ErrorInfo.MOK) {
  10. if(afCode == ErrorInfo.MERR_ASF_NOT_ACTIVATED){
  11. Toast.makeText(this, "设备SDK未激活", Toast.LENGTH_LONG).show();
  12. }else{
  13. Toast.makeText(this, "初始化失败"+afCode, Toast.LENGTH_LONG).show();
  14. }
  15. }
  16. }
  17. public void unInitEngine() { //卸载SDK
  18. if (afCode == 0) {
  19. afCode = faceObj.unInit();
  20. }
  21. }

三、配置相机

安卓Camera API到目前为止已有三代,ArcFace官方教程给的是Camera 1,但我在AS中引入的时候,会划删除线,提示Out并建议使用Camera2替代。于是乎,我使用Camera2去尝试替换官方的一代写法,虽然勉强成功,但是说实在的各种适配问题层出不穷,里面做适配写的代码占了一大半。So 最终我使用了CameraX,就这个预览自适应,就已经天下无敌了!推荐《为什么使用CameraX》。

1、CameraX需要在build.gradle中引入几个包,如下:

  1. def camerax_version = "1.1.0-alpha08"
  2. implementation("androidx.camera:camera-core:${camerax_version}")
  3. implementation("androidx.camera:camera-camera2:${camerax_version}")
  4. implementation("androidx.camera:camera-lifecycle:${camerax_version}")
  5. implementation("androidx.camera:camera-view:1.0.0-alpha25")
  6. implementation("androidx.camera:camera-extensions:1.0.0-alpha25")

2、创建预览View

  1. 页面定义变量
  2. public int rational;
  3. private Size mImageReaderSize;
  4. private FaceEngine faceObj; //sdk对象
  5. private int afCode = -1; // sdk状态
  6. private ProcessCameraProvider cameraProvider = null; // cameraX对象
  7. private Preview mPreviewBuild = null; //cameraX的preview
  8. private PreviewView viewFinder = null; // X的自带预览view
  9. private byte[] nv21 = null; // 实时nv21图像流
  10. protected void onCreate()(
  11. RelativeLayout layout=that.findViewById(R.id.camerax_preview);
  12. viewFinder =new PreviewView(that);//我是动态创建了个Preview,你也可以在页面直接写上
  13. layout.addView(viewFinder);
  14. Point screenSize = new Point(); // 获取屏幕总宽高
  15. getWindowManager().getDefaultDisplay().getSize(screenSize);//getRealSize就会包括状态栏
  16. rational = aspectRatio(screenSize.x, screenSize.y);
  17. startCameraX();
  18. }
  19. private double RATIO_4_3_VALUE = 4.0 / 3.0;
  20. private double RATIO_16_9_VALUE = 16.0 / 9.0;
  21. private int aspectRatio(int width, int height) {
  22. double previewRatio = Math.max(width, height) * 1.00 / Math.min(width, height);
  23. if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) {
  24. return AspectRatio.RATIO_4_3;
  25. }
  26. return AspectRatio.RATIO_16_9;
  27. }

3、初始化相机

  1. private void startCameraX() {
  2. ListenableFuture<ProcessCameraProvider> providerFuture = ProcessCameraProvider.getInstance(that);
  3. providerFuture.addListener(() -> {
  4. try { // 检测CameraProvider可用性
  5. cameraProvider = providerFuture.get();
  6. } catch (ExecutionException | InterruptedException e) {
  7. Toast.makeText(this, "相机X不可用", Toast.LENGTH_LONG).show();
  8. e.printStackTrace();
  9. return;
  10. }
  11. CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(
  12. CameraSelector.LENS_FACING_FRONT
  13. // CameraSelector.LENS_FACING_BACK
  14. ).build();
  15. //绑定预览
  16. mPreviewBuild = new Preview.Builder()
  17. // .setTargetRotation(Surface.ROTATION_180)//设置预览旋转角度
  18. .build();
  19. mPreviewBuild.setSurfaceProvider(viewFinder.getSurfaceProvider()); // 绑定显示
  20. // 图像分析,监听试试获取的图像流
  21. ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
  22. .setTargetAspectRatio(rational)
  23. .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
  24. //阻塞模式:ImageAnalysis.STRATEGY_BLOCK_PRODUCER (在此模式下,执行器会依序从相应相机接收帧;这意味着,如果 analyze() 方法所用的时间超过单帧在当前帧速率下的延迟时间,所接收的帧便可能不再是最新的帧,因为在该方法返回之前,新帧会被阻止进入流水线)
  25. //非阻塞模式: ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST (在此模式下,执行程序在调用 analyze() 方法时会从相机接收最新的可用帧。如果此方法所用的时间超过单帧在当前帧速率下的延迟时间,它可能会跳过某些帧,以便 analyze() 在下一次接收数据时获取相机流水线中的最新可用帧)
  26. .build();
  27. imageAnalysis.setAnalyzer(
  28. ContextCompat.getMainExecutor(this),
  29. new FaceAnalyze.MyAnalyzer() //绑定给我的图片分析类
  30. );
  31. // 绑定生命周期前先解绑
  32. cameraProvider.unbindAll();
  33. // 绑定
  34. Camera mCameraX = cameraProvider.bindToLifecycle(
  35. (LifecycleOwner) this
  36. ,cameraSelector
  37. ,imageAnalysis
  38. ,mPreviewBuild);
  39. }, ContextCompat.getMainExecutor(this));
  40. }

4、图片分析类,ImageUtil里的代码最后给出

  1. /**
  2. * 实时预览 Analyzer处理
  3. */
  4. private class MyAnalyzer implements ImageAnalysis.Analyzer {
  5. private byte[] y;
  6. private byte[] u;
  7. private byte[] v;
  8. private ReentrantLock lock = new ReentrantLock();
  9. private Object mImageReaderLock = 1;//1 available,0 unAvailable
  10. private long lastDrawTime = 0;
  11. private int timerSpace = 300; // 识别间隔
  12. @Override
  13. public void analyze(@NonNull ImageProxy imageProxy) {
  14. @SuppressLint("UnsafeOptInUsageError") Image mediaImage = imageProxy.getImage();
  15. // int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
  16. if(mediaImage != null){
  17. synchronized (mImageReaderLock) {
  18. /*识别频率Start 和状态*/
  19. long start = System.currentTimeMillis();
  20. if (start - lastDrawTime < timerSpace || !mImageReaderLock.equals(1)) {
  21. mediaImage.close();
  22. imageProxy.close();
  23. return;
  24. }
  25. lastDrawTime = System.currentTimeMillis();
  26. /*识别频率End*/
  27. //判断YUV类型,我们申请的格式类型是YUV_420_888
  28. if (ImageFormat.YUV_420_888 == mediaImage.getFormat()) {
  29. Image.Plane[] planes = mediaImage.getPlanes();
  30. if (mImageReaderSize == null) {
  31. mImageReaderSize = new Size(planes[0].getRowStride(), mediaImage.getHeight());
  32. }
  33. lock.lock();
  34. if (y == null) {
  35. y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
  36. u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
  37. v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
  38. }
  39. //从planes中分别获取y、u、v 变量数据
  40. if (mediaImage.getPlanes()[0].getBuffer().remaining() == y.length) {
  41. planes[0].getBuffer().get(y);
  42. planes[1].getBuffer().get(u);
  43. planes[2].getBuffer().get(v);
  44. if (nv21 == null) {
  45. nv21 = new byte[planes[0].getRowStride() * mediaImage.getHeight() * 3 / 2];
  46. }
  47. if (nv21 != null && (nv21.length != planes[0].getRowStride() * mediaImage.getHeight() * 3 / 2)) {
  48. return;
  49. }
  50. // 回传数据是YUV422
  51. if (y.length / u.length == 2) {
  52. ImageUtil.yuv422ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mediaImage.getHeight());
  53. }
  54. // 回传数据是YUV420
  55. else if (y.length / u.length == 4) {
  56. nv21 = ImageUtil.yuv420ToNv21(mediaImage);
  57. }
  58. //调用Arcsoft算法,获取人脸特征
  59. getFaceInfo(nv21);
  60. }
  61. lock.unlock();
  62. }
  63. }
  64. }
  65. //一定要关闭
  66. mediaImage.close();
  67. imageProxy.close();
  68. }
  69. }

5、进行人脸特征分析

  1. /**
  2. * 获取人脸特征
  3. */
  4. private int lastFaceID=-1;
  5. private void getFaceInfo(byte[] nv21) {
  6. List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
  7. FaceFeature faceFeature = new FaceFeature();
  8. //第一步是送数据给arcsoft sdk,检查是否有人脸信息。
  9. int code = faceObj.detectFaces(nv21, mImageReaderSize.getWidth(),
  10. mImageReaderSize.getHeight(),
  11. faceObj.CP_PAF_NV21, faceInfoList);
  12. //数据检查正常,并且含有人脸信息,则进行下一步的人脸识别。
  13. if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
  14. int newFaceID = faceInfoList.get(0).getFaceId();// 每次不离镜头的同一人脸,其ID均相同
  15. if (lastFaceID == newFaceID) { // 相同脸则return
  16. Log.i("人脸ID相同", "旧" + lastFaceID);// + "、新" + newFaceID
  17. return;
  18. }
  19. lastFaceID = newFaceID;
  20. // long start = System.currentTimeMillis();
  21. // 特征数据分析部分机型会卡
  22. code = faceObj.extractFaceFeature(nv21, mImageReaderSize.getWidth(), mImageReaderSize.getHeight(),faceObj.CP_PAF_NV21, faceInfoList.get(0), faceFeature);
  23. if (code != ErrorInfo.MOK) {
  24. return;
  25. }
  26. byte[] featureData=faceFeature.getFeatureData();//最终的特征数据!!!
  27. post(featureData); //把数据给后端去查信息
  28. // Log.e(TAG, "特征长度"+faceFeature.getFeatureData().length);
  29. // long end = System.currentTimeMillis();
  30. // Log.e(TAG+lastFaceID, "耗时: "+(end-start)+"ms特征数据---: "+featureData.length);
  31. }
  32. }

四、ImageUtil代码

  1. public class ImageUtil {
  2. /**
  3. * 将Y:U:V == 4:2:2的数据转换为nv21
  4. *
  5. * @param y Y 数据
  6. * @param u U 数据
  7. * @param v V 数据
  8. * @param nv21 生成的nv21,需要预先分配内存
  9. * @param stride 步长
  10. * @param height 图像高度
  11. */
  12. public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
  13. System.arraycopy(y, 0, nv21, 0, y.length);
  14. // 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算
  15. int length = y.length + u.length / 2 + v.length / 2;
  16. int uIndex = 0, vIndex = 0;
  17. for (int i = stride * height; i < length; i += 2) {
  18. nv21[i] = v[vIndex];
  19. nv21[i + 1] = u[uIndex];
  20. vIndex += 2;
  21. uIndex += 2;
  22. }
  23. }
  24. /**
  25. * 将Y:U:V == 4:1:1的数据转换为nv21
  26. *
  27. * @param y Y 数据
  28. * @param u U 数据
  29. * @param v V 数据
  30. * @param nv21 生成的nv21,需要预先分配内存
  31. * @param stride 步长
  32. * @param height 图像高度
  33. */
  34. public static void yuv420ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
  35. System.arraycopy(y, 0, nv21, 0, y.length);
  36. // 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算
  37. int length = y.length + u.length + v.length;
  38. int uIndex = 0, vIndex = 0;
  39. for (int i = stride * height; i < length; i++) {
  40. nv21[i] = v[vIndex++];
  41. nv21[i + 1] = u[uIndex++];
  42. }
  43. }
  44. public static byte[] yuv420ToNv21(Image image) {
  45. Image.Plane[] planes = image.getPlanes();
  46. ByteBuffer yBuffer = planes[0].getBuffer();
  47. ByteBuffer uBuffer = planes[1].getBuffer();
  48. ByteBuffer vBuffer = planes[2].getBuffer();
  49. int ySize = yBuffer.remaining();
  50. int uSize = uBuffer.remaining();
  51. int vSize = vBuffer.remaining();
  52. int size = image.getWidth() * image.getHeight();
  53. byte[] nv21 = new byte[size * 3 / 2];
  54. yBuffer.get(nv21, 0, ySize);
  55. vBuffer.get(nv21, ySize, vSize);
  56. byte[] u = new byte[uSize];
  57. uBuffer.get(u);
  58. //每隔开一位替换V,达到VU交替
  59. int pos = ySize + 1;
  60. for (int i = 0; i < uSize; i++) {
  61. if (i % 2 == 0) {
  62. nv21[pos] = u[i];
  63. pos += 2;
  64. }
  65. }
  66. return nv21;
  67. }
  68. public static Bitmap nv21ToBitmap(byte[] nv21,int w, int h) {
  69. YuvImage image = new YuvImage(nv21, ImageFormat.NV21, w, h, null);
  70. ByteArrayOutputStream stream = new ByteArrayOutputStream();
  71. image.compressToJpeg(new Rect(0, 0, w, h), 80, stream);
  72. Bitmap newBitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
  73. try{
  74. stream.close();
  75. }catch (Exception e){}
  76. return newBitmap;
  77. }
  78. }

That's all

Thanks !!!

附:

人脸路程中的坑《人脸识别坑

二进制的特征数据怎么发给后台,参考我的另外一篇《安卓form-data形式上传二进制

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/231668
推荐阅读
相关标签
  

闽ICP备14008679号