Android虹软人脸识别V2.1接入

最近在做虹软的人脸识别接入介入过程中遇到了一些问题,下面记录一下,防止其他人踩坑

首选先讲下接入流程

1.权限

获取设备唯一标识,用于SDK激活授权

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

允许应用联网,用于SDK联网激活授权

<uses-permission android:name="android.permission.INTERNET" />

2.支持图片的颜色空间格式 (非常重要,如果自己本地的图片进行注册人脸这里会用到)

3.工程配置

1. 新建一个Android Project,切换到Project视图;

2. 将libarcsoft_face.so和libarcsoft_face_engine.so添加到src->main->jniLibs->armeabi-v7a路径下;

3. 将arcsoft_face.jar放入,并依赖进工程;

4.调用流程

Step1:调用FaceEngine的active方法激活设备,一个设备安装后仅需激活一次,卸载重新安装后需要重新激活。

Step2:调用FaceEngine的init方法初始化SDK,初始化成功后才能进一步使用SDK的功能。

Step3:调用FaceEngine的detectFaces方法进行图像数据或预览数据的人脸检测,若检测成功,则可得到一个人脸列表。(初始化时combineMask需要ASF_FACE_DETECT)

Step4:调用FaceEngine的extractFaceFeature方法可对图像中指定的人脸进行特征提取。(初始化时combineMask需要ASF_FACE_RECOGNITION)

Step5:调用FaceEngine的compareFaceFeature方法可对传入的两个人脸特征进行比对,获取相似度。(初始化时combineMask需要ASF_FACE_RECOGNITION)

Step6:调用FaceEngine的process方法,传入不同的combineMask组合可对Age、Gender、Face3Dangle、Liveness进行检测,传入的combineMask的任一属性都需要在init时进行初始化。

Step7:调用FaceEngine的getAge、getGender、getFace3Dangle、getLiveness方法可获取年龄、性别、三维角度、活体检测结果,且每个结果在获取前都需要在process中进行处理。

Step8:调用FaceEngine的unInit方法销毁引擎。在init成功后如不unInit会导致内存泄漏。

 5.按照官网的SDK说明中可以正常运行并使用demo但是,手动注册人脸照片时会遇到注册人脸失败的问题主要是图片注册需要转化为NV21的格式,直接将图片转换为byte是不能用的

下面是手动注册人脸的方法

/**

    * 将准备注册的状态置为{@link #REGISTER_STATUS_READY}

    *

    * @param view 注册按钮

    */

    public void test(View view) {

        Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bst_test);

        Bitmap bitmap = ImageUtil.alignBitmapForNv21(mBitmap);

//        byte[] bytes = bitmapToNv21(mBitmap, mBitmap.getWidth(), mBitmap.getHeight());

        byte[] bytes = ImageUtil.bitmapToNv21(bitmap, bitmap.getWidth(), bitmap.getHeight());

        boolean success = FaceServer.getInstance().register(MainActivity.this, bytes.clone(), bitmap.getWidth(), bitmap.getHeight(), "registered " + "bst_test");

        if (success){

            showToast("注册成功");

        }else {

            showToast("注册失败");

        }

    }

public boolean register(Context context, byte[] nv21, int width, int height, String name) {

    synchronized (this) {

        if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.length != width * height * 3 / 2) {

            return false;

        }

        if (ROOT_PATH == null) {

            ROOT_PATH = context.getFilesDir().getAbsolutePath();

        }

        boolean dirExists = true;

        //特征存储的文件夹

        File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);

        if (!featureDir.exists()) {

            dirExists = featureDir.mkdirs();

        }

        if (!dirExists) {

            return false;

        }

        //图片存储的文件夹

        File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);

        if (!imgDir.exists()) {

            dirExists = imgDir.mkdirs();

        }

        if (!dirExists) {

            return false;

        }

        //1.人脸检测

        List<FaceInfo> faceInfoList = new ArrayList<>();

        int code = faceEngine.detectFaces(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList);

        if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {

            FaceFeature faceFeature = new FaceFeature();

            //2.特征提取

            code = faceEngine.extractFaceFeature(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList.get(0), faceFeature);

            String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;

            try {

                //3.保存注册结果(注册图、特征数据)

                if (code == ErrorInfo.MOK) {

                    YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);

                    //为了美观,扩大rect截取注册图

                    Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect());

                    if (cropRect == null) {

                        return false;

                    }

                    File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);

                    FileOutputStream fosImage = new FileOutputStream(file);

                    yuvImage.compressToJpeg(cropRect, 100, fosImage);

                    fosImage.close();

                    Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());

                    //判断人脸旋转角度,若不为0度则旋转注册图

                    boolean needAdjust = false;

                    if (bitmap != null) {

                        switch (faceInfoList.get(0).getOrient()) {

                            case FaceEngine.ASF_OC_0:

                                break;

                            case FaceEngine.ASF_OC_90:

                                bitmap = ImageUtil.getRotateBitmap(bitmap, 90);

                                needAdjust = true;

                                break;

                            case FaceEngine.ASF_OC_180:

                                bitmap = ImageUtil.getRotateBitmap(bitmap, 180);

                                needAdjust = true;

                                break;

                            case FaceEngine.ASF_OC_270:

                                bitmap = ImageUtil.getRotateBitmap(bitmap, 270);

                                needAdjust = true;

                                break;

                            default:

                                break;

                        }

                    }

                    if (needAdjust) {

                        fosImage = new FileOutputStream(file);

                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);

                        fosImage.close();

                    }

                    FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);

                    fosFeature.write(faceFeature.getFeatureData());

                    fosFeature.close();

                    //内存中的数据同步

                    if (faceRegisterInfoList == null) {

                        faceRegisterInfoList = new ArrayList<>();

                    }

                    faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));

                    return true;

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

        return false;

    }

}

下面是官方工具类

package com.example.shine.util;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.Matrix;

import android.graphics.Rect;

import android.net.Uri;

import android.provider.MediaStore;

import java.io.IOException;

import java.nio.ByteBuffer;

//官方提供的工具类

public class ImageUtil {

    private static final int VALUE_FOR_4_ALIGN = 0b11;

    private static final int VALUE_FOR_2_ALIGN = 0b01;

    /**

    * Bitmap转化为ARGB数据,再转化为NV21数据

    *

    * @param src    传入的Bitmap,格式为{@link Bitmap.Config#ARGB_8888}

    * @param width  NV21图像的宽度

    * @param height NV21图像的高度

    * @return nv21数据

    */

    public static byte[] bitmapToNv21(Bitmap src, int width, int height) {

        if (src != null && src.getWidth() >= width && src.getHeight() >= height) {

            int[] argb = new int[width * height];

            src.getPixels(argb, 0, width, 0, 0, width, height);

            return argbToNv21(argb, width, height);

        } else {

            return null;

        }

    }

    /**

    * ARGB数据转化为NV21数据

    *

    * @param argb  argb数据

    * @param width  宽度

    * @param height 高度

    * @return nv21数据

    */

    private static byte[] argbToNv21(int[] argb, int width, int height) {

        int frameSize = width * height;

        int yIndex = 0;

        int uvIndex = frameSize;

        int index = 0;

        byte[] nv21 = new byte[width * height * 3 / 2];

        for (int j = 0; j < height; ++j) {

            for (int i = 0; i < width; ++i) {

                int R = (argb[index] & 0xFF0000) >> 16;

                int G = (argb[index] & 0x00FF00) >> 8;

                int B = argb[index] & 0x0000FF;

                int Y = (66 * R + 129 * G + 25 * B + 128 >> 8) + 16;

                int U = (-38 * R - 74 * G + 112 * B + 128 >> 8) + 128;

                int V = (112 * R - 94 * G - 18 * B + 128 >> 8) + 128;

                nv21[yIndex++] = (byte) (Y < 0 ? 0 : (Y > 255 ? 255 : Y));

                if (j % 2 == 0 && index % 2 == 0 && uvIndex < nv21.length - 2) {

                    nv21[uvIndex++] = (byte) (V < 0 ? 0 : (V > 255 ? 255 : V));

                    nv21[uvIndex++] = (byte) (U < 0 ? 0 : (U > 255 ? 255 : U));

                }

                ++index;

            }

        }

        return nv21;

    }

    /**

    * bitmap转化为bgr数据,格式为{@link Bitmap.Config#ARGB_8888}

    *

    * @param image 传入的bitmap

    * @return bgr数据

    */

    public static byte[] bitmapToBgr(Bitmap image) {

        if (image == null) {

            return null;

        }

        int bytes = image.getByteCount();

        ByteBuffer buffer = ByteBuffer.allocate(bytes);

        image.copyPixelsToBuffer(buffer);

        byte[] temp = buffer.array();

        byte[] pixels = new byte[(temp.length / 4) * 3];

        for (int i = 0; i < temp.length / 4; i++) {

            pixels[i * 3] = temp[i * 4 + 2];

            pixels[i * 3 + 1] = temp[i * 4 + 1];

            pixels[i * 3 + 2] = temp[i * 4];

        }

        return pixels;

    }

    /**

    * 裁剪bitmap

    *

    * @param bitmap 传入的bitmap

    * @param rect  需要被裁剪的区域

    * @return 被裁剪后的bitmap

    */

    public static Bitmap imageCrop(Bitmap bitmap, Rect rect) {

        if (bitmap == null || rect == null || rect.isEmpty() || bitmap.getWidth() < rect.right || bitmap.getHeight() < rect.bottom) {

            return null;

        }

        return Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), null, false);

    }

    public static Bitmap getBitmapFromUri(Uri uri, Context context) {

        if (uri == null || context == null) {

            return null;

        }

        try {

            return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);

        } catch (IOException e) {

            e.printStackTrace();

            return null;

        }

    }

    public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {

        if (b == null) {

            return null;

        }

        Matrix matrix = new Matrix();

        matrix.postRotate(rotateDegree);

        return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);

    }

    /**

    * 确保传给引擎的BGR24数据宽度为4的倍数

    *

    * @param bitmap 传入的bitmap

    * @return 调整后的bitmap

    */

    public static Bitmap alignBitmapForBgr24(Bitmap bitmap) {

        if (bitmap == null || bitmap.getWidth() < 4) {

            return null;

        }

        int width = bitmap.getWidth();

        int height = bitmap.getHeight();

        boolean needAdjust = false;

        //保证宽度是4的倍数

        if ((width & VALUE_FOR_4_ALIGN) != 0) {

            width &= ~VALUE_FOR_4_ALIGN;

            needAdjust = true;

        }

        if (needAdjust) {

            bitmap = imageCrop(bitmap, new Rect(0, 0, width, height));

        }

        return bitmap;

    }

    /**

    * 确保传给引擎的NV21数据宽度为4的倍数,高为2的倍数

    *

    * @param bitmap 传入的bitmap

    * @return 调整后的bitmap

    */

    public static Bitmap alignBitmapForNv21(Bitmap bitmap) {

        if (bitmap == null || bitmap.getWidth() < 4 || bitmap.getHeight() < 2) {

            return null;

        }

        int width = bitmap.getWidth();

        int height = bitmap.getHeight();

        boolean needAdjust = false;

        //保证宽度是4的倍数

        if ((width & VALUE_FOR_4_ALIGN) != 0) {

            width &= ~VALUE_FOR_4_ALIGN;

            needAdjust = true;

        }

        //保证高度是2的倍数

        if ((height & VALUE_FOR_2_ALIGN) != 0) {

            height--;

            needAdjust = true;

        }

        if (needAdjust) {

            bitmap = imageCrop(bitmap, new Rect(0, 0, width, height));

        }

        return bitmap;

    }

}

这样就可以手动注册成功了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容