ArcFace

虹软人脸识别 - ArcFace 3.0中ArcSoftImag

2019-11-19  本文已影响0人  省油的灯_wsy

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

从虹软开放了2.0版本SDK以来,由于具有免费、离线使用的特点,我们公司在人脸识别门禁应用中使用了虹软SDK,识别效果还不错,因此比较关注虹软SDK的官方动态。近期上线了ArcFace 3.0 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中判断图像数据是否合法的校验代码:

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

四、步长的作用

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

以正确的步长解析 以错误的步长解析
以1000为步长解析 以996为步长解析

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

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

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

以上代码中便是Camera2 API回传的数据转换为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时对图像数据的了解更清晰。
上一篇 下一篇

猜你喜欢

热点阅读