如何使用 python 接入虹软 ArcFace SDK


公司需要在项目中使用人脸识别SDK,并且对信息安全的要求非常高,在详细了解市场上几个主流人脸识别SDK后,综合来看虹软的Arcface SDK比较符合我们的需求,它提供了免费版本,并且可以在离线环境下使用,这一点非常符合我们对安全性的要求。但有个遗憾的事情,我们的项目主要使用了Python语言,虹软官方并没有提供Python版本的SDK,因此我自己使用Python封装了Arcface C++ SDK,便于在项目中使用,这里将主要过程写出来供大家探讨下。

1.环境说明

a.注意Win64环境的Python必须使用ArcFace C++(Win64) SDK,如果平台不一致, 否则可能会出现以下错误。

OSError: [WinError 193] %1 不是有效的 Win32 应用程序

b.由于SDK中涉及到内存操作,本文使用了ctypes包和cdll包提供的以下几种方式

c_ubyte_p = POINTER(c_ubyte)
memcpy = cdll.msvcrt.memcpy
malloc = cdll.msvcrt.malloc
malloc.restype = c_void_p
free = cdll.msvcrt.free

2.Arcface SDK基本数据结构封装

在封装数据结构时,一定要注意参数类型,否则可能会导致程序出错。

class MRECT(Structure):  # 人脸框
   _fields_ = [(u'left', c_int32),
               (u'top', c_int32),
               (u'right', c_int32),
               (u'bottom', c_int32)]


class ASFVersion(Structure):  # 版本信息     版本号 构建日期 版权说明
   _fields_ = [
       ('Version', c_char_p),
       ('BuildDate', c_char_p),
       ('CopyRight', c_char_p)]


class ASFSingleFaceInfo(Structure):  # 单人脸信息  人脸框 人脸角度
   _fields_ = [
       ('faceRect', MRECT),
       ('faceOrient', c_int32)]


class ASFMultiFaceInfo(Structure):  # 多人脸信息 人脸框数组 人脸角度数组 人脸数
   _fields_ = [
       (u'faceRect', POINTER(MRECT)),
       (u'faceOrient', POINTER(c_int32)),
       (u'faceNum', c_int32)]


class ASFFaceFeature(Structure):  # 人脸特征 人脸特征 人脸特征长度
   _fields_ = [
       ('feature', c_void_p),
       ('featureSize', c_int32)]


class ASFFace3DAngle(Structure):  # 人脸角度信息
   _fields_ = [
       ('roll', c_void_p),
       ('yaw', c_void_p),
       ('pitch', c_void_p),
       ('status', c_void_p),
       ('num', c_int32)]


class ASFAgeInfo(Structure):  # 年龄 
   _fields_ = [
       (u'ageArray', c_void_p),
       (u'num', c_int32)]


class ASFGenderInfo(Structure):  # 性别 
   _fields_ = [
       (u'genderArray', c_void_p),
       (u'num', c_int32)]


class ASFLivenessThreshold(Structure):  # 活体阈值
   _fields_ = [
       (u'thresholdmodel_BGR', c_float),
       (u'thresholdmodel_IR', c_int32)]


class ASFLivenessInfo(Structure):  # 活体信息
   _fields_ = [
       (u'isLive', c_void_p),
       (u'num', c_int32)]

3.Arcface SDK接口封装

a.接口封装之前需要加载dll库,Arcface SDK 提供的dll都需要加载。
b.本文中图片格式使用了ASVL_PAF_RGB24_B8G8R8。
c.每个接口都需要定义返回值以及参数类型,某些参数类型依赖前文所述的基本数据结构。

from arcsoft_face_struct import *
from ctypes import *
from enum import Enum

face_dll = CDLL("libarcsoft_face.dll")
face_engine_dll = CDLL("libarcsoft_face_engine.dll")

ASF_DETECT_MODE_VIDEO = 0x00000000
ASF_DETECT_MODE_IMAGE = 0xFFFFFFFF

ASF_NONE = 0x00000000
ASF_FACE_DETECT = 0x00000001
ASF_FACE_RECOGNITION = 0x00000004
ASF_AGE = 0x00000008
ASF_GENDER = 0x00000010
ASF_FACE3DANGLE = 0x00000020
ASF_LIVENESS = 0x00000080
ASF_IR_LIVENESS = 0x00000400

ASVL_PAF_RGB24_B8G8R8 = 0x201


class ArcSoftFaceOrientPriority(Enum):
    ASF_OP_0_ONLY = 0x1,
    ASF_OP_90_ONLY = 0x2,
    ASF_OP_270_ONLY = 0x3,
    ASF_OP_180_ONLY = 0x4,
    ASF_OP_0_HIGHER_EXT = 0x5,


activate = face_engine_dll.ASFActivation
activate.restype = c_int32
activate.argtypes = (c_char_p, c_char_p)


init_engine = face_engine_dll.ASFInitEngine
init_engine.restype = c_int32
init_engine.argtypes = (c_long, c_int32, c_int32, c_int32, c_int32, POINTER(c_void_p))


detect_face = face_engine_dll.ASFDetectFaces
detect_face.restype = c_int32
detect_face.argtypes = (c_void_p, c_int32, c_int32, c_int32, POINTER(c_ubyte), POINTER(ASFMultiFaceInfo))


extract_feature = face_engine_dll.ASFFaceFeatureExtract
extract_feature.restype = c_int32
extract_feature.argtypes = (c_void_p, c_int32, c_int32, c_int32, POINTER(c_ubyte),
                            POINTER(ASFSingleFaceInfo), POINTER(ASFFaceFeature))


compare_feature = face_engine_dll.ASFFaceFeatureCompare
compare_feature.restype = c_int32
compare_feature.argtypes = (c_void_p, POINTER(ASFFaceFeature),
                            POINTER(ASFFaceFeature), POINTER(c_float))


set_liveness_param = face_engine_dll.ASFSetLivenessParam
set_liveness_param.restype = c_int32
set_liveness_param.argtypes = (c_void_p, POINTER(ASFLivenessThreshold))


process = face_engine_dll.ASFProcess
process.restype = c_int32
process.argtypes = (c_void_p, c_int32, c_int32, c_int32, POINTER(c_ubyte),
                    POINTER(ASFMultiFaceInfo), c_int32)


get_age = face_engine_dll.ASFGetAge
get_age.restype = c_int32
get_age.argtypes = (c_void_p, POINTER(ASFAgeInfo))


get_gender = face_engine_dll.ASFGetGender
get_gender.restype = c_int32
get_gender.argtypes = (c_void_p, POINTER(ASFGenderInfo))


get_3d_angle = face_engine_dll.ASFGetFace3DAngle
get_3d_angle.restype = c_int32
get_3d_angle.argtypes = (c_void_p, POINTER(ASFFace3DAngle))


get_liveness_info = face_engine_dll.ASFGetLivenessScore
get_liveness_info.restype = c_int32
get_liveness_info.argtypes = (c_void_p, POINTER(ASFLivenessInfo))

4.封装接口调用

接下来按照下面的流程图介绍接口调用(此图使用 Microsoft Visio 2016自动生成)。
流程.png

下图是按照此流程处理得到的效果图,由于画面有限,只显示了年龄、性别、活体信息。
效果图.jpg
a.激活
需要注意app_id和sdk_key需要使用字节类型。

    app_id = b""
    sdk_key = b""
    ret = arcsoft_face_func.activate(app_id, sdk_key)  # 激活
    if ret == 0 or ret == 90114:
        print("激活成功")
    else:
        print("激活失败:", ret)

b.初始化
初始化需要将所有需要的功能参数一次性传入,本文使用了人脸检测、特征提取等功能。

    mask = arcsoft_face_func.ASF_FACE_DETECT | \
            arcsoft_face_func.ASF_FACE_RECOGNITION | \
            arcsoft_face_func.ASF_AGE | \
            arcsoft_face_func.ASF_GENDER | \
            arcsoft_face_func.ASF_FACE3DANGLE |\
            arcsoft_face_func.ASF_LIVENESS

    engine = c_void_p()
    ret = arcsoft_face_func.init_engine(arcsoft_face_func.ASF_DETECT_MODE_IMAGE,
                                        arcsoft_face_func.ArcSoftFaceOrientPriority.ASF_OP_0_ONLY.value[0],
                                   30, 10, mask, byref(engine))
    if ret == 0:
        print("初始化成功")
    else:
        print("初始化失败:", ret)

c.人脸检测
本文使用了opencv读图,兼容性更好,并且自定义的数据结构记录图片信息,注意 ArcFace C++ SDK 要求传入的图像宽度需要是4的倍数,下面做了裁剪。

class Image:
    def __init__(self):
        self.width = 0
        self.height = 0
        self.imageData = None

def load_image(file_path):
    img = cv2.imread(file_path)
    sp = img.shape
    img = cv2.resize(img, (sp[1]//4*4, sp[0]))# 四字节对齐

    image = Image()
    image.width = img.shape[1]
    image.height = img.shape[0]
    image.imageData = img
    return image

###################### 人脸检测 ##################################

    image1 = load_image(r"1.jpg")
    image_bytes = bytes(image1.imageData)
    image_ubytes = cast(image_bytes, c_ubyte_p)

    detect_faces = ASFMultiFaceInfo()
    ret = arcsoft_face_func.detect_face(
        engine,
        image1.width,
        image1.height,
        arcsoft_face_func.ASVL_PAF_RGB24_B8G8R8,
        image_ubytes,
        byref(detect_faces)
    )

    if ret == 0:
        print("检测人脸成功")
    else:
        print("检测人脸失败:", ret)

d.特征提取
特征提取只支持单人脸,因此做了人脸处理操作,并且需要及时将提取的人脸特征拷贝一份,否则会被覆盖。

    single_face1 = ASFSingleFaceInfo()
    single_face1.faceRect = detect_faces.faceRect[0]
    single_face1.faceOrient = detect_faces.faceOrient[0]

    face_feature = ASFFaceFeature()
    ret = arcsoft_face_func.extract_feature(
        engine,
        image1.width,
        image1.height,
        arcsoft_face_func.ASVL_PAF_RGB24_B8G8R8,
        image_ubytes,
        single_face1,
        byref(face_feature)
    )

    if ret == 0:
        print("提取特征1成功")
    else:
        print("提取特征1失败:", ret)

    feature1 = ASFFaceFeature()
    feature1.featureSize = face_feature.featureSize
    feature1.feature = malloc(feature1.featureSize)
    memcpy(c_void_p(feature1.feature),
           c_void_p(face_feature.feature),
           feature1.featureSize)

e.特征比对
按照前文所述再提取一张人脸的特征,即可以进行下面的人脸特征比对操作

    compare_threshold = c_float()
    ret = arcsoft_face_func.compare_feature(
        engine, feature1, feature2, compare_threshold
    )

    free(c_void_p(feature1.feature))
    free(c_void_p(feature2.feature))

    if ret == 0:
        print("特征比对成功,相似度:", compare_threshold.value)
    else:
        print("特征比对失败:", ret)

f.年龄、性别、3D Angle
process接口目前提供了 年龄、性别、3D Angle、活体检测, 但年龄、性别、3D Angle支持多人脸,而活体只支持单人脸,因此下面分别处理。

    process_mask = arcsoft_face_func.ASF_AGE | \
                   arcsoft_face_func.ASF_GENDER | \
                   arcsoft_face_func.ASF_FACE3DANGLE

    ret = arcsoft_face_func.process(
        engine,
        image1.width,
        image1.height,
        arcsoft_face_func.ASVL_PAF_RGB24_B8G8R8,
        image_ubytes,
        byref(detect_faces),
        c_int32(process_mask)
    )

    if ret == 0:
        print("process成功")
    else:
        print("process失败:", ret)

######################## Age ################################

    age_info = ASFAgeInfo()
    ret = arcsoft_face_func.get_age(engine, byref(age_info))

    if ret == 0:
        print("get_age 成功")
        age_ptr = cast(age_info.ageArray, POINTER(c_int))
        for i in range(age_info.num):
            print("face", i, "age:", age_ptr[i])
    else:
        print("get_age 失败:", ret)

####################### Gender #################################

    gender_info = ASFGenderInfo()
    ret = arcsoft_face_func.get_gender(engine, byref(gender_info))

    if ret == 0:
        print("get_gender 成功")
        gender_ptr = cast(gender_info.genderArray, POINTER(c_int))
        for i in range(gender_info.num):
            print("face", i, "gender:",
                  "女性" if (gender_ptr[i] == 1) else (
                      "男性" if (gender_ptr[i] == 0) else "未知"
                  ))
    else:
        print("get_gender 失败:", ret)

####################### 3D Angle #################################

    angle_info = ASFFace3DAngle()
    ret = arcsoft_face_func.get_3d_angle(engine, byref(angle_info))

    if ret == 0:
        print("get_3d_angle 成功")
        roll_ptr = cast(angle_info.roll, POINTER(c_float))
        yaw_ptr = cast(angle_info.yaw, POINTER(c_float))
        pitch_ptr = cast(angle_info.pitch, POINTER(c_float))
        status_ptr = cast(angle_info.status, POINTER(c_int32))
        for i in range(angle_info.num):
            print("face", i,
                  "roll:", roll_ptr[i],
                  "yaw:", yaw_ptr[i],
                  "pitch:", pitch_ptr[i],
                  "status:", "正常" if status_ptr[i] == 0 else "出错")

    else:
        print("get_3d_angle 失败:", ret)

g.RGB活体
在活体检测之前建议按照实际场景设置活体阈值,不设置即使用默认阈值,这里设置了RGB活体的阈值为0.75。并将检测的多人脸分别转为单张人脸的参数传到接口中。

######################### 活体阈值设置 ###############################
    threshold_param = ASFLivenessThreshold()
    threshold_param.thresholdmodel_BGR = 0.75
    ret = arcsoft_face_func.set_liveness_param(engine,threshold_param)

    if ret == 0:
        print("set_liveness_param成功")
    else:
        print("set_liveness_param 失败:", ret)

    temp_face_info = ASFMultiFaceInfo()
    temp_face_info.faceNum = 1
    LP_MRECT = POINTER(MRECT)
    temp_face_info.faceRect = LP_MRECT(MRECT(malloc(sizeof(MRECT))))
    LP_c_long = POINTER(c_long)
    temp_face_info.faceOrient = LP_c_long(c_long(malloc(sizeof(c_long))))

    for i in range(detect_faces.faceNum):
        temp_face_info.faceRect[0] = detect_faces.faceRect[i]
        temp_face_info.faceOrient[0] = detect_faces.faceOrient[i]

        ret = arcsoft_face_func.process(
            engine,
            image1.width,
            image1.height,
            arcsoft_face_func.ASVL_PAF_RGB24_B8G8R8,
            image_ubytes,
            byref(temp_face_info),
            c_int32(arcsoft_face_func.ASF_LIVENESS)
        )

        if ret == 0:
            print("process成功")
        else:
            print("process失败:", ret)
        ## RGB活体检测
        ret = arcsoft_face_func.process(
            engine,
            image1.width,
            image1.height,
            arcsoft_face_func.ASVL_PAF_RGB24_B8G8R8,
            image_ubytes,
            byref(temp_face_info),
            c_int32(arcsoft_face_func.ASF_LIVENESS)
        )

        if ret == 0:
            print("process成功")
        else:
            print("process失败:", ret)

        liveness_info = ASFLivenessInfo()
        ret = arcsoft_face_func.get_liveness_info(engine, byref(liveness_info))

        if ret == 0:
            print("get_liveness_info 成功")
            liveness_ptr = cast(liveness_info.isLive, POINTER(c_int))
            print("face", i, "liveness:",
                  "非真人" if (liveness_ptr[0] == 0) else (
                      "真人" if (liveness_ptr[0] == 1) else (
                          "不确定" if (liveness_ptr[0] == -1) else (
                              "传入人脸数>1" if (liveness_ptr[0] == -2) else
                              (liveness_ptr[0])
                          )
                      )
                  ))
        else:
            print("get_liveness_info 失败:", ret)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值