基于虹软SDK,适配Camera1、Camera2、CameraX,实现人脸识别(Android)

这篇文章主要介绍基于虹软人脸识别SDK,适配Camera1、Camera2、CameraX。文章分下面几点展开。

一、应用设计流程图
二、应用界面展示
三、虹软SDK介绍
四、代码实现

ArcSoft官方的demo目前是采用的Camera1接口,我前面也写过一篇单独Camera2 接口集成Arcsoft接口的文章。(https://www.jianshu.com/p/cb2f65ead747

一、应用设计流程图

如下图所示,应用流程比较简单,分别从不同的API接口获取到Camera数据流数据,然后送到ArcSoft人脸识别算法库中进行识别,最终将识别结果绘制到界面上。

应用数据流处理流程图

二、应用界面

CameraX需要和界面生命周期进行绑定,所以主界面设计成了2个Button入口,一个入口时Camera1和Camera2共用,一个是CameraX独立的入口。


应用主界面

如下图所示:Camera1和Camera2之间可以互相切换。


Camera1 API
Camera2 API

CameraX是单独的界面。


CameraX API

三、虹软SDK介绍

本应用基于虹软人脸识别SDK 3.0版本,目前有更新的4.1版本,支持更多的功能。
虹软视觉开放平台: https://ai.arcsoft.com.cn/

虹软视觉开放平台

我们点击选择进入“开发者中心”。
进入开发者中心


如上图所示,登录进入开发者中心后,我们可以创建我们的应用,进而获取到对应的APP_ID 和SDK_KEY,并且可以下载对应的SDK。


我们根据自己需要下载对应的sdk,sdk里面包含了需要用的jar包、so库,还有详细的集成说明文档,已经samplecode供我们参考集成,可谓是集成起来是非常的方便了。

四、代码实现

1) 虹软SDK关键代码

i、我们在开发者平台上申请的APP_ID和SDK_KEY,在激活人脸识别引擎的时候需要用到。

 public static final String APP_ID = "JDk8rV3BBGnToh6HNYzCFpxYXMowitBjFuCjPGQr4CcC";
 public static final String SDK_KEY = "EBH3RG2D55ayfVrGK7SeTivMWxjzsskwNt1RyfVPVbg9";

ii、初始化SDK引擎

private void initEngine() {
        faceEngine = new FaceEngine();
        afCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
                16, 20,
         FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
      
        if (afCode != ErrorInfo.MOK) {
            showToast( getString(R.string.init_failed, afCode));
        }
    }

iii、将实时的nv21数据送到sdk中进行识别。detectFaces方法显示检测是否有人脸。检测到人脸后,process方法是获取识别的人脸详细信息(年龄、性别...)。

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);
                   
2) Camera1 API的使用:
 private void startCameraByApi1() {
        CameraListener cameraListener = new CameraListener() {
            @Override
            public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
                 ......
            }

            @Override
            public void onPreview(byte[] nv21, Camera camera) {
               //接收到实时预览数据回调,送入虹软sdk进行人脸识别
                drawFaceInfo(nv21);
            }
                ......
        };
3) Camera2 API的使用:
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireLatestImage();

            if(image == null){
                return;
            }

            synchronized (mImageReaderLock) {
                if(!mImageReaderLock.equals(1)){
                    Log.v(TAG, "--- image not available,just return!!!");
                    image.close();
                    return;
                }
                if (ImageFormat.YUV_420_888 == image.getFormat()) {
                    Image.Plane[] planes = image.getPlanes();

                    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()];
                    }

                    if (image.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() * mPreviewSize.getHeight() * 3 / 2];
                        }

                        if(nv21 != null && (nv21.length != planes[0].getRowStride() * mPreviewSize.getHeight() *3/2)){
                            return;
                        }

                        // 回传数据是YUV422
                        if (y.length / u.length == 2) {
                            ImageUtil.yuv422ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mPreviewSize.getHeight());
                        }
                        // 回传数据是YUV420
                        else if (y.length / u.length == 4) {
                            ImageUtil.yuv420ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mPreviewSize.getHeight());
                        }

                        //调用Arcsoft算法,绘制人脸信息
                        drawFaceInfo(nv21);
                    }
                    lock.unlock();
                }
            }
            image.close();
        }
    };
3) CameraX API的使用:
 private void startCameraX() {
        Log.v(TAG,"--- startCameraX();");
        mPreviewSize = new Size(640,480);
        setPreviewViewAspectRatio();
        initArcsoftDrawHelper();

        Rational rational = new Rational(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        // 1. preview
        PreviewConfig previewConfig = new PreviewConfig.Builder()
                .setTargetAspectRatio(rational)
                .setTargetResolution(mPreviewSize)
                .build();

        Preview preview = new Preview(previewConfig);
        preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
            @Override
            public void onUpdated(Preview.PreviewOutput output) {
                previewView.setSurfaceTexture(output.getSurfaceTexture());
                configureTransform(previewView.getWidth(),previewView.getHeight());
            }
        });

        // 2. capture
        ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder()
                .setTargetAspectRatio(rational)
                .setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
                .build();
        final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig);

        // 3. analyze
        HandlerThread handlerThread = new HandlerThread("Analyze-thread");
        handlerThread.start();

        ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
                .setCallbackHandler(new Handler(handlerThread.getLooper()))
                .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
                .setTargetAspectRatio(rational)
                .setTargetResolution(mPreviewSize)
                .build();

        ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
        imageAnalysis.setAnalyzer(new MyAnalyzer());

        CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);
    }
private class MyAnalyzer implements ImageAnalysis.Analyzer {
        private byte[] y;
        private byte[] u;
        private byte[] v;
        private byte[] nv21;
        private ReentrantLock lock = new ReentrantLock();
        private Object mImageReaderLock = 1;//1 available,0 unAvailable

        @Override
        public void analyze(ImageProxy imageProxy, int rotationDegrees) {
            Image image =  imageProxy.getImage();

            if(image == null){
                return;
            }

            synchronized (mImageReaderLock) {
                if(!mImageReaderLock.equals(1)){
                    image.close();
                    return;
                }
                if (ImageFormat.YUV_420_888 == image.getFormat()) {
                    Image.Plane[] planes = image.getPlanes();
                    if(mImageReaderSize == null){
                        mImageReaderSize = new Size(planes[0].getRowStride(),image.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()];
                    }

                    if (image.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() * image.getHeight() * 3 / 2];
                        }

                        if(nv21 != null && (nv21.length != planes[0].getRowStride() * image.getHeight() *3/2)){
                            return;
                        }

                        // 回传数据是YUV422
                        if (y.length / u.length == 2) {
                            ImageUtil.yuv422ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), image.getHeight());
                        }
                        // 回传数据是YUV420
                        else if (y.length / u.length == 4) {
                            nv21 = ImageUtil.yuv420ToNv21(image);
                        }

                        //调用Arcsoft算法,绘制人脸信息
                        drawFaceInfo(nv21,mPreviewSize);
                    }
                    lock.unlock();
                }
            }
        }
    }

四、遇到的问题

1)预览变形

这个是由于设置Camera预览的size和TextureView的size比例不一致导致。

我们一般会根据当前设备屏幕的size,遍历camera支持的preview size,找到适合当前设备的预览size,再根据当前预览size,动态调整textureView的显示。

2)Arcsoft sdk code异常

中间遇到的关于Arcsoft sdk code异常的,可以在Arcsoft开发中心,帮助界面,输入对应的error code,根据提示信息,可以帮忙我们快速定位排查问题。


五、附录:

1)Demo地址:

链接:https://pan.baidu.com/s/1rKyncLbxPfH5ajDTINBhCg
提取码:xcbj

2)ArcSoft官网sdk下载地址:

https://ai.arcsoft.com.cn/


*本人从事Android Camera相关开发已有5年,
*目前在深圳上班,
*小伙伴记得点我头像,看【个人介绍】进行关注哦,希望和更多的小伙伴一起交流 ~
** 为了方便大家沟通交流,我建了个wx交流群,想加入的同学,欢迎私信我加入~*

-------- 2021.09.11 深圳

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

推荐阅读更多精彩内容