虹软人脸识别3.0 - 图像数据结构介绍(Android)

从虹软开放了2.0版本SDK以来,由于具有免费、离线使用的特点,我们公司在人脸识别门禁应用中使用了虹软SDK,识别效果还不错,因此比较关注虹软SDK的官方动态。近期上线了ArcFace 3.0 SDK版本,确实做了比较大的更新。首先本篇介绍一下关于Android平台算法的更新内容,下一篇将针对Windows平台的算法更新展开介绍。

  • 特征比对支持比对模型选择,有生活照比对模型人证比对模型
  • 识别率、防***效果显著提升
  • 特征值更新,升级后人脸库需重新注册
  • Android平台新增64位的SDK
  • 图像处理工具类
  • 人脸检测同时支持全角度及单一角度
  • 新增了一种图像数据传入方式

在实际开发过程中使用新的图像数据结构具有一定的难度,本文将从以下几点对该图像数据结构及使用方式进行详细介绍

  1. SDK接口变动
  2. ArcSoftImageInfo类解析
  3. SDK相关代码解析
  4. 步长的作用
  5. 将Camera2回传的Image转换为ArcSoftImageInfo

一、SDK接口变动

在接入3.0版SDK时,发现FaceEngine类中的detectFacesprocessextractFaceFeature等传入图像数据的函数都有重载函数,重载函数的接口均使用ArcSoftImageInfo对象作为入参的图像数据,以人脸检测为例,具体接口如下:

原始接口:

public int detectFaces(byte[] data, int width, int height, int format, List<FaceInfo> faceInfoList)

新增接口:

public int detectFaces(ArcSoftImageInfo arcSoftImageInfo, List<FaceInfo> faceInfoList)

可以看到,重载函数传入ArcSoftImageInfo对象作为图像数据进行检测,arcSoftImageInfo替代了原来的data, width, height, format

二、ArcSoftImageInfo类解析

在我实际使用后发现,ArcSoftImageInfo不只是简单封装一下,它还将一维数组data修改为二维数组planes,还新增了一个与planes对应的步长数组strides

步长概念介绍:
步长可以理解为一行像素的字节数。

类结构如下:

public class ArcSoftImageInfo {
    private int width;
    private int height;
    private int imageFormat;
    private byte[][] planes;
    private int[] strides;
    ...
}

官方文档中对该类的介绍:

  • 成员描述
类型 变量名 描述
int width 图像宽度
int height 图像高度
int imageFormat 图像格式
byte[][] planes 图像通道
int[] strides 每个图像通道的步长
  • 组成方式介绍
// arcSoftImageInfo组成方式举例:

// NV21格式数据,有两个通道,
// Y通道步长一般为图像宽度,若图像经过8字节对齐、16字节对齐等操作,需填入对齐后的图像步长
// VU通道步长一般为图像宽度,若图像经过8字节对齐、16字节对齐等操作,需填入对齐后的图像步长
ArcSoftImageInfo arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_NV21, new byte[][]{planeY, planeVU}, new int[]{yStride, vuStride});

// GRAY,只有一个通道,
// 步长一般为图像宽度,若图像经过8字节对齐、16字节对齐等操作,需填入对齐后的图像步长
arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_GRAY, new byte[][]{gray}, new int[]{grayStride});

// BGR24,只有一个通道,
// 步长一般为图像宽度的三倍,若图像经过8字节对齐、16字节对齐等操作,需填入对齐后的图像步长
arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_BGR24, new byte[][]{bgr24}, new int[]{bgr24Stride});

// DEPTH_U16,只有一个通道,
// 步长一般为图像宽度的两倍,若图像经过8字节对齐、16字节对齐等操作,需填入对齐后的图像步长
arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_DEPTH_U16, new byte[][]{depthU16}, new int[]{depthU16Stride});

可以看到,ArcSoftImageInfo用于存储分离的图像数据,以NV21数据为例,NV21数据有两个通道,那二维数组planes存储的就是两个数组:y数组和vu数组。以下是NV21数据的排列方式:

NV21图像格式属于 YUV颜色空间中的YUV420SP格式,每四个Y分量共用一组U分量和V分量,Y连续存储,U与V交叉存储。

排列方式如下(以8x4的图像为例):

Y Y ? Y Y ? Y Y ? Y Y
Y Y ? Y Y ? Y Y ? Y Y
Y Y ? Y Y ? Y Y ? Y Y
Y Y ? Y Y ? Y Y ? Y Y
V U? V U ? V U? V U
V U ?V U ? V U? V U

以上数据分为两个通道,首先是连续的Y数据,然后是交叉存储的VU数据。如果我们使用的是Camera API,那基本用不到ArcSoftImageInfo类,因为Camera API回传的NV21数据是连续的,直接使用旧版接口即可;而当我们使用的是其他API时,拿到的数据可能是不连续的,例如使用Camera2 APIMediaCodec拿到的android.media.Image类对象,其图像数据也是分通道的,我们可以根据其通道内容,获取Y通道数据和VU通道数据,组成NV21格式的ArcSoftImageInfo对象用于处理。

三、SDK相关代码解析

我们来看下SDK中判断图像数据是否合法的校验代码:

注:原始代码由于被编译器修改过,阅读体验不佳,以下代码是我修改过的,将常量值替换回常量名,更便于阅读。

  • 校验分离的图像信息数据

        private static boolean isImageDataValid(byte[] data, int width, int height, int format) {
            return
            (format == CP_PAF_NV21 && (height & 1) == 0 && data.length == width * height * 3 / 2)||
            (format == CP_PAF_BGR24 && data.length == width * height * 3)||
            (format == CP_PAF_GRAY && data.length == width * height) ||
            (format == CP_PAF_DEPTH_U16 && data.length == width * height * 2);
        }

    解读:
    各个图像数据的要求如下:

    1. NV21格式图像数据的高度是偶数,数据大小是:宽x高x3/2
    2. BGR24格式图像数据的大小是:宽x高x3
    3. GRAY格式图像数据的大小是:宽x高
    4. DEPTH_U16格式图像数据的大小是:宽x高x2
  • 校验ArcSoftImageInfo对象
        private static boolean isImageDataValid(ArcSoftImageInfo arcSoftImageInfo) {
            byte[][] planes = arcSoftImageInfo.getPlanes();
            int[] strides = arcSoftImageInfo.getStrides();
            if (planes != null && strides != null) {
                if (planes.length != strides.length) {
                    return false;
                } else {
                    byte[][] var3 = planes;
                    int var4 = planes.length;
    
                    for(int var5 = 0; var5 < var4; ++var5) {
                        byte[] plane = var3[var5];
                        if (plane == null || plane.length == 0) {
                            return false;
                        }
                    }
    
                    switch(arcSoftImageInfo.getImageFormat()) {
                    case CP_PAF_BGR24:
                    case CP_PAF_GRAY:
                    case CP_PAF_DEPTH_U16:
                        return planes.length == 1 && planes[0].length == arcSoftImageInfo.getStrides()[0] * arcSoftImageInfo.getHeight();
                    case CP_PAF_NV21:
                        return (arcSoftImageInfo.getHeight() & 1) == 0 && planes.length == 2 && planes[0].length == planes[1].length * 2 && planes[0].length == arcSoftImageInfo.getStrides()[0] * arcSoftImageInfo.getHeight() && planes[1].length == arcSoftImageInfo.getStrides()[1] * arcSoftImageInfo.getHeight() / 2;
                    default:
                        return false;
                    }
                }
            } else {
                return false;
            }
        }

    解读:

    1. 每个通道数据的大小是:高度x每个通道的步长
    2. BGR24GRAYDEPTH_U16格式图像数据都只有一个通道,但上述示例组成方式说明中提到它们的步长不同,关系如下:
      • BGR24格式图像数据步长一般为3 x width
      • GRAY格式图像数据步长一般为width
      • DEPTH_U16格式图像数据步长一般为2 x width
    3. NV21格式图像数据的高度是偶数,有两个通道,且第0个通道的数据大小是第1个通道数据大小的2倍。

    四、步长的作用

    • 具体踩坑举例

    如下图,这是在某台手机上使用Camera2 API时,指定了以1520x760分辨率进行预览时获取的数据。虽然指定的分辨率是1520x760,但是预览数据的实际大小却是1536x760,解析存下的图像数据,发现右边填充的16像素内容均为0,此时若我们以1520x760的分辨率去将这组YUV数据取出并转换为NV21,并在进行人脸检测时传入的宽度是1520,SDK将无法检测到人脸;若我们以1536x760的分辨率去解析,生成的NV21传给SDK,并且传入的宽度是1536时,SDK能够检测到人脸。

  • 步长的重要性

只是差了这几个像素,为什么就导致人脸检测不到了呢?之前说到过,步长可以理解为一行像素的字节数。如果第一行像素的读取有偏差,那后续像素的读取也会受到影响。<br>

以下是对一张大小为1000x554NV21图像数据,以不同步长进行解析的结果:

以正确的步长解析 以错误的步长解析

可以看到,对于一张图像,如果使用了错误的步长去解析,我们可能就无法看到正确的图像内容。

结论:通过引入图像步长能够有效的避免高字节对齐的问题。

五、将Camera2回传的Image转换为ArcSoftImageInfo

  • Camera2 API回传数据处理
    对于以上场景,我们可提取android.media.Image对象的YUV通道数据,组成NV21格式的ArcSoftImageInfo对象,传入SDK处理。示例代码如下:

    • 取出Camera2 API回传数据的YUV通道数据

              private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener{
                  private byte[] y;
                  private byte[] u;
                  private byte[] v;
      
                  @Override
                  public void onImageAvailable(ImageReader reader) {
                      Image image = reader.acquireNextImage();
                      // 实际结果一般是 Y:U:V == 4:2:2
                      if (camera2Listener != null && image.getFormat() == ImageFormat.YUV_420_888) {
                          Image.Plane[] planes = image.getPlanes();
                          // 重复使用同一批byte数组,减少gc频率
                          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);
                              camera2Listener.onPreview(y, u, v, mPreviewSize, planes[0].getRowStride());
                          }
                      }
                      image.close();
                  }
              }
    • 转换为ArcSoftImageInfo对象

    注意: 拿到的YUV数据可能是YUV422,也可能是YUV420,需要分别实现两者转换为NV21格式的ArcSoftImageInfo对象的函数。

            @Override
            public void onPreview(final byte[] y, final byte[] u, final byte[] v, final Size previewSize, final int stride) {
                if (arcSoftImageInfo == null) {
                    arcSoftImageInfo = new ArcSoftImageInfo(previewSize.getWidth(), previewSize.getHeight(), FaceEngine.CP_PAF_NV21);
                }
                // 回传数据是YUV422
                if (y.length / u.length == 2) {
                    ImageUtil.yuv422ToNv21ImageInfo(y, u, v, arcSoftImageInfo, stride, previewSize.getHeight());
                }
                // 回传数据是YUV420
                else if (y.length / u.length == 4) {
                    ImageUtil.yuv420ToNv21ImageInfo(y, u, v, arcSoftImageInfo, stride, previewSize.getHeight());
                }
                // 此时的arcSoftImageInfo数据即可传给SDK使用
                if (faceEngine != null) {
                    List<FaceInfo> faceInfoList = new ArrayList<>();
                    int code = faceEngine.detectFaces(arcSoftImageInfo, faceInfoList);
                    if (code == ErrorInfo.MOK) {
                        Log.i(TAG, "onPreview: " + code + "  " + faceInfoList.size());
                    } else {
                        Log.i(TAG, "onPreview: no face detected , code is : " + code);
                    }
                } else {
                    Log.e(TAG, "onPreview: faceEngine is null");
                    return;
                }
                ...
            }

以上代码中便是Camera2 API回传的数据转换为ArcSoftImageInfo对象并检测的具体实现。以下是将YUV数据组成ArcSoftImageInfo对象的具体实现。

  • YUV数据组成ArcSoftImageInfo对象

    对于Y通道,直接拷贝即可,对于U通道和V通道,需要考虑这组YUV数据的格式是YUV420还是YUV422,再获取其中的UV数据

            /**
             * YUV420数据转换为NV21格式的ArcSoftImageInfo
             *
             * @param y                YUV420数据的y分量
             * @param u                YUV420数据的u分量
             * @param v                YUV420数据的v分量
             * @param arcSoftImageInfo NV21格式的ArcSoftImageInfo
             * @param stride           y分量的步长,一般情况下,由于YUV数据的对应关系,Y分量步长确定了,U和V也随之确定
             * @param height           图像高度
             */
            public static void yuv420ToNv21ImageInfo(byte[] y, byte[] u, byte[] v, ArcSoftImageInfo arcSoftImageInfo, int stride, int height) {
                if (arcSoftImageInfo.getPlanes() == null) {
                    arcSoftImageInfo.setPlanes(new byte[][]{new byte[stride * height], new byte[stride * height / 2]});
                    arcSoftImageInfo.setStrides(new int[]{stride, stride});
                }
                System.arraycopy(y, 0, arcSoftImageInfo.getPlanes()[0], 0, y.length);
                // 注意,vuLength 不能直接通过步长和高度计算,实测发现Camera2 API回传的数据有数据丢失,需要使用真实数据长度
                byte[] vu = arcSoftImageInfo.getPlanes()[1];
                int vuLength = u.length / 2 + v.length / 2;
                int uIndex = 0, vIndex = 0;
                for (int i = 0; i < vuLength; i++) {
                    vu[i] = v[vIndex++];
                    vu[i + 1] = u[uIndex++];
                }
            }
            /**
             * YUV422数据转换为NV21格式的ArcSoftImageInfo
             *
             * @param y                YUV422数据的y分量
             * @param u                YUV422数据的u分量
             * @param v                YUV422数据的v分量
             * @param arcSoftImageInfo NV21格式的ArcSoftImageInfo
             * @param stride           y分量的步长,一般情况下,由于YUV数据的对应关系,Y分量步长确定了,U和V也随之确定
             * @param height           图像高度
             */
            public static void yuv422ToNv21ImageInfo(byte[] y, byte[] u, byte[] v, ArcSoftImageInfo arcSoftImageInfo, int stride, int height) {
                if (arcSoftImageInfo.getPlanes() == null) {
                    arcSoftImageInfo.setPlanes(new byte[][]{new byte[stride * height], new byte[stride * height / 2]});
                    arcSoftImageInfo.setStrides(new int[]{stride, stride});
                }
                System.arraycopy(y, 0, arcSoftImageInfo.getPlanes()[0], 0, y.length);
                byte[] vu = arcSoftImageInfo.getPlanes()[1];
                // 注意,vuLength 不能直接通过步长和高度计算,实测发现Camera2 API回传的数据有数据丢失,需要使用真实数据长度
                int vuLength = u.length / 2 + v.length / 2;
                int uIndex = 0, vIndex = 0;
                for (int i = 0; i < vuLength; i += 2) {
                    vu[i] = v[vIndex];
                    vu[i + 1] = u[uIndex];
                    vIndex += 2;
                    uIndex += 2;
                }
            }

六、ArcSoftImageInfo优点总结

  1. 在获取的图像数据源是分通道的数据时,使用ArcSoftImageInfo对象传入分离的图像数据可避免数据拼接所需的额外内存消耗。
  2. 引入了步长的概念,在使用时传入了各个通道的步长,使开发者在使用SDK时对图像数据的了解更清晰。

Android Demo可在虹软人脸识别开放平台下载

原文地址:https://blog.51cto.com/14633836/2457798

时间: 2024-10-27 14:30:55

虹软人脸识别3.0 - 图像数据结构介绍(Android)的相关文章

虹软人脸识别3.0 - 图像数据结构介绍(C++)

从虹软开放了2.0版本SDK以来,由于具有免费.离线使用的特点,我们公司在人脸识别门禁应用中使用了虹软SDK,识别效果还不错,因此比较关注虹软SDK的官方动态.近期上线了ArcFace 3.0 SDK版本,确实做了比较大的更新.上一篇主要介绍了关于Android平台算法的改进,本篇将介绍一下关于Windows平台算法的更新. 特征比对支持比对模型选择,有生活照比对模型和人证比对模型 识别率.防***效果显著提升 特征值更新,升级后人脸库需重新注册 人脸检测同时支持全角度及单一角度 新增了一种图像

虹软人脸识别ArcFace2.0 Android SDK使用教程

一.获取SDK 1.进入ArcFace2.0的申请地址 https://ai.arcsoft.com.cn/product/arcface.html 2.填写信息申请并提交 申请通过后即可下载SDK,查看APP_ID和SDK_KEY 二.功能介绍 虹软ArcFace 2.0 Android包含人脸检测.年龄信息检测.性别信息检测.人脸三维角度检测.活体检测.人脸特征提取.人脸特征比对功能. 其中暴露对外的功能方法有:active(激活) init(初始化) detectFaces(人脸检测) p

Android 实现人脸识别教程[运用虹软人脸识别SDK]

基于虹软人脸识别引擎,在Android平台上实现人脸识别功能,即使在离线的情况下依旧运行,不被人采集个人照片的感觉,还是爽爽的.经过整个测试过来,虹软的人脸识别还是很强大的,人脸检测可以控制在20ms之内,人脸识别大概在200ms左右.今天就来分享一下开发经验 项目的目标 我们需要实现一个人脸识别功能.简单来说,就是机的后置摄像头,识别摄像头中实时拍到的人脸信息,如果人库注册过,则显示识别后的人脸信息,如登记的名字:如果不在,提示未注册. 这个功能具有多个应用场景,比如,火车站或者打卡和门禁系统

虹软人脸识别应用开发过程

趁空闲的一点点时间向大家分享一个好用的人脸识别的应用--来自虹软公司的人脸识别推荐这家的产品主要有以下几个理由~1.免费!免费!免费!它家比较良心.人脸识别.人证核验.活体检测等等一切的sdk都是免费下载使用的- -即使商用也可以~非常适合我这种小穷人2.根据不同操作平台提供不同的SDK目前可下载Windows x86,x64平台(c++/Java语言).Linux x64平台(c++/Java语言).iOS平台(Objective-C语言)与Android arm32平台(Java语言)3.官

虹软人脸识别SDK在网络摄像头中的实际应用

目前在人脸识别领域中,网络摄像头的使用很普遍,但接入网络摄像头和人脸识别SDK有一定门槛,在此文章中有介绍过虹软人脸识别SDK的接入流程,本文着重介绍网络摄像头获取视频流并处理的流程(红色框内),以下内容仅供参考. 1.海康SDK接入基本流程 a.初始化并登录验证 NET_DVR_Init(); NET_DVR_DEVICEINFO_V30 struDeviceInfo = { 0 }; long lUserID = NET_DVR_Login_V30(m_cameraIp, m_cameraP

记C# 调用虹软人脸识别 那些坑

上一个东家是从事安防行业的,致力于人工智能领域,有自主人脸识别.步态识别的算法.C++同事比较称职有什么问题都可以第一时间反馈,并得到合理的处理,封装的DLL 是基于更高性能的GPU算法,可支持更多线路的运算,接口调用简单,只需要传入图片即可得到特征特征值.对于公司的项目,更多的是与各类接口进行交互.包括建立任务.上传视频.截取片段,抽取特征,学习特征,步态比对等对接接口的复杂业务逻辑.由于长期和C++接口对接,包括Kafka消息队列传输等,对于公司项目支撑的业务流程比较了解.至于底层算法,只是

虹软人脸识别 - faceId及IR活体检测的介绍

前几天虹软推出了 Android ArcFace 2.2版本的SDK,相比于2.1版本,2.2版本中的变化如下: ? VIDEO模式新增faceId(类似于之前文章中提到的trackId)? 新增IR活体检测功能? 新增IR.RGB的活体阈值设置 一.faceId介绍 1. 定义 在连续的视频帧中,当一个人脸进入视频画面直到离开,其faceId不变. 2. 应用场景举例 在门禁应用场景下,若一个人长时间停留在画面中,借助faceId的功能,在此人的人脸特征提取成功后,可不再对此人的后续人脸信息进

虹软人脸识别的应用开发过程分享

虹软的人脸识别是应用与离线开发的,因为不需要网络,所以它的识别速度较快.好了,废话不多说,接下来就开始教大家怎样使用了. 1.首先就是去官网申请APPKEY,各种密匙,然后在下载jar包,这些就不一一给大家讲解了.注意一下,要在app的gradle里面加上 sourceSets { main { jniLibs.srcDirs = ['libs'] } } 这句话,不然可能会造成so库加载不了的错误. 2.接下里就需要进行开发了.就拿人脸检测的功能来说吧,首先需要对引擎初始化, AFD_FSDK

如何使用虹软人脸识别门禁应用套件

导语:虹软ArcFaceGo智慧门禁应用套件包括门禁软件APK.PC管理客户端和云端服务三部分,最快3分钟即可完成智慧门禁系统的部署,帮助中小型企业降低开发成本,加快实现产品智能化落地.本文来自开发者投稿. 应公司需求,最近开发了一款有考勤功能的人脸门禁应用. 相比传统密码门禁操作不便,指纹和刷卡门禁容易冒用和代打卡,刷脸门禁采用生物识别技术,必须本人刷脸才能起效,在原有门禁的基础上的改造也比较容易. 刷脸门禁虽好,然而最大问题是,我并没有从零开发一款人脸门禁的能力.多方研究之下,我选择了虹软A