ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

虹软ArcFace离线人脸识别SDK部署与Python调用实战

虹软ArcFace离线人脸识别SDK部署与Python调用实战 做安防和门禁相关项目的人应该都听过虹软ArcFace这个名字。去年我接手一个离线环境下的刷脸考勤项目需要在纯内网部署一套人脸识别服务对比了一圈方案之后最终选定了虹软ArcFace SDK。这款离线SDK在业内口碑一直不错识别精度高、免费额度友好而且提供了Python接口对快速落地demo非常有利。这篇文章把我从SDK获取、环境配置、激活鉴权到核心接口调用的完整过程整理出来重点拆解了新版SDK的接口变化、Python调用时的数据格式处理以及我实际操作中踩过的坑。适合正在做门禁、考勤、人证比对或者想在本地快速实现一个人脸识别原型的开发者参考即使你之前没接触过ArcFace照着走也能跑通。1. 方案选型为什么用离线SDK而不是在线API1.1 离线识别的核心价值人脸识别方案市面上不少但大体分两类在线API和离线SDK。在线API胜在省事传一张图就能拿结果但问题也明显——每次识别都要走网络延迟在50ms到200ms之间波动数据要过第三方服务器在很多场景下直接劝退。我这次的需求是考勤机管理部署位置在企业内网摄像头通过局域网连到一台Windows主机。如果走在线API网络抖动会导致刷卡体验稀碎而且员工的照片和特征数据全部要上传这在数据安全层面就说不过去。ArcFace离线SDK的优势正好扎在这个痛点上所有计算都在本地完成人脸检测、特征提取、比对识别本地一把梭单次识别耗时在毫秒级运行库加载之后不会有什么日志偷偷外传的风险。另一个很关键的决策因素是成本。虹软的ArcFace在2019年后对开发者免费开放只需在官网申请Key并激活就能使用对个人学习和中小团队来说几乎等于零成本起步。相比商汤、旷视的私有化报价动辄十几万这个门槛低到几乎没有。1.2 版本变化与新环境的适配问题选了ArcFace之后第一步去官网下载SDK这时候就要注意版本问题了。新版SDK的安装包在界面上明确提示不再提供32位版本所以宿主机和Python解释器必须是64位的。这件事看起来不起眼但我见过有人装了64位的SDK结果Python环境还是32位一调用就报错两个小时的排查全耗在这上面。还有一个很隐蔽的变化新版SDK把官方网站列出的接口函数表砍掉了只在下载的文档包里有接口说明。这个调整对老用户不太友好之前对着官网示例写代码的习惯得改一改。我建议下载后第一时间把doc目录下的PDF和CHM文件完整看一遍特别是人脸检测这一章新版把ASFDetectFaces的输入参数从图片路径改成了图像数据缓冲区如果你按旧版写法传路径一定会踩坑。运行环境方面ArcFace SDK对Windows平台要求VS2015以上版本的运行库。官方推荐直接安装visualcppbuildtools_full或者vc_redist.x64.exe。我实际测试下来Windows 10/11系统上如果之前装过Visual Studio或大型软件很多软件会顺带装上运行库大概率不缺这个但Windows Server精简版或某些定制版系统就要手动装了。最稳的检测方法是在命令行跑pip install requests这种需要联网的操作如果报错提示缺MSVC运行库就先装运行库再继续。2. 环境准备与SDK安装激活鉴权是第一个大坑2.1 下载SDK与安装包内容解析虹软的开发者官网需要注册账号、填写应用信息审核通过后才能在控制台申请SDK包。这里有几个注意点一是申请时需要填写APPID它不是乱填的要在控制台里创建应用之后自动生成二是选择SDK版本时Windows版和Linux版别下错三是申请后SDK包会绑定你填写的APPID和激活Key后面激活时如果Key不匹配初始化阶段就会报错。下载下来的安装包是一个标准安装程序装到默认目录后整个SDK的文件结构大致如下文件/目录作用说明lib\win_x6464位动态库文件Python调用时主要依赖这里的dllincludeC/C头文件里面是接口类型定义和函数声明doc开发文档包含接口说明和示例代码必读examples官方示例工程有C和Python两种版本bin\win_x64部分版本的运行组件包含FreeType等依赖库安装过程中弹ActiveX控件注册的提示时不要慌那是虹软SDK自带的授权组件在尝试注册。如果系统报XXX.ocx无法注册也不要紧张一般不影响SDK核心功能我遇到过三次SDK照样能正常编解码和检测。如果安装到最后一步提示需要管理员权限记得右键安装包用管理员身份运行。2.2 Python依赖库与激活流程实测Python调用虹软SDK相对C来说少了很多胶水代码但需要先装几个辅助库。我这次用到的依赖库清单如下全部通过pip安装即可pip install requests pycryptodome win32gui pywin32 numpy opencv-pythonrequests激活SDK和拉取授权信息时用pycryptodomeSDK激活时对设备指纹做加密计算pywin32Windows系统API调用部分授权组件依赖opencv-python图像读取和预览非SDK必需但demo里基本都要用到numpy把图像数据转为ArcFace要求的数组格式这个必装激活是整个过程中最容易出问题的一环。虹软的激活流程分两步先在官网申请离线激活码或在线激活然后在本地跑官方提供的激活Python脚本。官方激活脚本一般在SDK包的tools目录下名字类似ArcFaceActivation.py。脚本会读取你本机的设备ID去虹软服务器换取激活码然后生成一个授权文件。这个过程中有两点必须提第一激活脚本请求服务器时必须保证网络通畅如果公司网络有防火墙很容易碰到超时或者INTERNET_TIME_OUT报错第二如果激活失败脚本会提示查看设备硬件ID官网申请离线激活码的时候要填入这个ID。我在实际操作中遇到过一次激活服务器连接超时的问题排查了半天最后发现是内网DNS解析不了虹软的域名手动把DNS改成公共DNS之后重新激活就成功了。所以遇到激活失败先ping一下激活服务器的域名再用tracert看路由基本能定位问题。激活完成后SDK会在指定目录生成授权文件通常是.lic结尾这个文件绑定了当前设备的机器码。如果后面你换了电脑或者重装系统这个授权文件就会失效需要重新去官网申请激活码做一次离线激活。我在项目上线前特意把授权文件备份到两个位置避免设备故障时授权丢失导致服务起不来。3. 核心接口调用Python人脸检测与特征提取实操3.1 SDK目录结构与关键数据结构解读激活完成之后先别急着写调用逻辑。对照SDK的include头文件把几个核心结构体搞清楚后面写代码会顺畅很多。ArcFace Python接口的核心数据结构和初始化引擎的代码如下所示。官方的Python封装在sdk目录下的arcsoft文件夹里这个文件把C接口重新包装了一遍我们直接import即可from ctypes import c_int, c_void_p, c_ubyte, POINTER, byref, create_string_buffer import numpy as np # 核心结构体定义来自arcsoft的API封装 class ASF_Detection(ctypes.Structure): _fields_ [ (face_id, c_int), # 人脸ID (left, c_int), # 人脸框左边缘坐标 (top, c_int), # 人脸框上边缘坐标 (right, c_int), # 人脸框右边缘坐标 (bottom, c_int), # 人脸框下边缘坐标 (score, c_float), # 置信度 (face_rect, c_void_p), # 人脸关键点坐标数组 (face_angle, c_int), # 人脸角度0为正脸1为左偏2为右偏 ]注意这个face_rect虽然是指针类型但底层实际指向一个包含106个关键点坐标的数组。官方Python封装里没有直接把这个数组解引用我写代码时都是通过numpy的frombuffer方法去读取效率很高后面会演示。3.2 引擎初始化参数选择与配置逻辑初始化引擎是整个SDK的入口函数是ASFInitEngine。这个函数的参数决定了SDK的运行模式。官方Python封装中的初始化代码如下def init_engine(app_id, sdk_key, detect_modeASF_DETECT_MODE_IMAGE, detect_face_angleASF_FACE_ANGLE_0, detect_face_num10): 初始化引擎 :param detect_mode: ASF_DETECT_MODE_IMAGE(静态图片) / ASF_DETECT_MODE_VIDEO(视频流) :param detect_face_angle: 检测人脸的姿态角度范围 :param detect_face_num: 单帧检测的最大人脸数 pEngine c_void_p() ret arcsoft.ASFInitEngine( c_int(detect_mode), c_int(detect_face_angle), c_int(detect_face_num), c_int(ASF_FACE_DETECT | ASF_FACE_RECOGNITION), # 启用检测识别功能 c_char_p(app_id.encode(utf-8)), c_char_p(sdk_key.encode(utf-8)), byref(pEngine) ) if ret ! 0: raise RuntimeError(fASFInitEngine failed, code{ret}) return pEngine参数里有几个细节值得展开说detect_mode的选择静态图片场景用ASF_DETECT_MODE_IMAGE视频流场景用ASF_DETECT_MODE_VIDEO。视频模式会利用帧间信息加速检测但要求连续调用DetectFace不能跳帧太多。我这个考勤项目用的是USB摄像头做实时识别所以用的VIDEO模式。如果只是批量处理图片比如做照片比对IMAGE模式就够了还更稳定。detect_face_angle取值ASF_FACE_ANGLE_0表示只检测正脸性能最好如果需要侧脸检测要选择ASF_FACE_ANGLE_30或ASF_FACE_ANGLE_90但这会显著提高CPU占用。我做门禁场景时用的是30度角既能保证正常刷脸通过率又不会因为侧脸误触发导致频繁检测。detect_face_num建议设10这是单帧最多返回的人脸数量。如果设成1多人同框时性能会好一点但容易丢检测框设成10在考勤场景足够用了反正后续比对会通过阈值过滤低质量脸。初始化之后记得调用ASFUninitEngine释放引擎Python的gc不会帮你管理这个C层对象写个上下文管理器来确保释放。3.3 图像数据预处理从OpenCV到ArcFace的格式对接ArcFace的DetectFace函数接收的不是文件路径也不是numpy数组直接传而是要一个原始像素缓冲区的指针。使用OpenCV读取图片后必须把BGR格式转成NV21格式同时把numpy数组的data_ptr传给SDK。这个步骤是Python调用中最容易翻车的地方。我先给出一个通用的图像预处理函数import cv2 import numpy as np IMAGE_WIDTH 640 IMAGE_HEIGHT 480 def img_to_nv21(img_bgr): 将OpenCV读取的BGR图像转为NV21格式返回bytes数据 NV21格式: Y分量全部在前UV交错排布 img_bgr cv2.resize(img_bgr, (IMAGE_WIDTH, IMAGE_HEIGHT)) img_yuv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2YUV_I420) # I420转为NV21NV21的UV顺序跟I420相反 height img_yuv.shape[0] width img_yuv.shape[1] y img_yuv[0:height, 0:width] u img_yuv[height:height height//4, 0:width//2] v img_yuv[height height//4:, 0:width//2] nv21 np.zeros((height * width * 3 // 2,), dtypenp.uint8) nv21[0:height*width] y.flatten() # UV交错V在前U在后 uv_plane np.empty((height*width//2,), dtypenp.uint8) uv_plane[0::2] v.flatten() uv_plane[1::2] u.flatten() nv21[height*width:] uv_plane return nv21.tobytes()这里有个细节ArcFace要求输入图像的宽高必须能被4整除否则有概率返回异常检测结果。所以上面代码里固定resize到640x480这个分辨率对考勤场景足够清晰同时完美满足对齐要求。用numpy的img_bgr.flatten()直接转bytes也行但务必确保内存连续性。经过cv2.resize和cv2.cvtColor之后numpy数组默认就是C连续内存布局直接tobytes()没问题。注意ArcFace对图像格式的要求不是BGR而是NV21这是Android相机常见的预览格式SDK官方支持。当初我第一次没转换格式直接传BGR数组进去结果人脸框位置全部偏移检测率极低排查了很久才发现是这个格式问题。3.4 完整人脸检测与特征提取代码实现下面给出一个完整的检测特征提取函数。这个函数输入是OpenCV的BGR图像数组输出是检测到的人脸框列表和对应的特征向量。def detect_and_extract(engine, img_bgr): 检测人脸并提取特征 :return: list of (face_rect, face_feature_bytes) nv21_data img_to_nv21(img_bgr) # 申请检测结果存储空间 face_num c_int(0) detect_result arcsoft.ASFDetectFaces( engine, c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), c_int(ASF_PIXEL_FORMAT_NV21), nv21_data, byref(face_num) ) if detect_result ! 0 or face_num.value 0: return [] # 获取检测框列表 face_info ASF_Detection * face_num.value p_face_info face_info() # 真正的人脸框信息通过ASFGetDetectedFaces获取 arcsoft.ASFGetDetectedFaces(engine, p_face_info, byref(face_num)) results [] for i in range(face_num.value): rect (p_face_info[i].left, p_face_info[i].top, p_face_info[i].right, p_face_info[i].bottom) # 提取人脸特征 feature ASF_FaceFeature() ret arcsoft.ASFFaceFeatureExtract( engine, c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), c_int(ASF_PIXEL_FORMAT_NV21), nv21_data, byref(p_face_info[i]), # 传入检测到的人脸框结构体 c_int(p_face_info[i].face_angle), byref(feature) ) if ret 0: # 特征提取成功 # feature.feature是一个指针featureSize是长度 feature_bytes ctypes.string_at( feature.feature, feature.featureSize ) results.append((rect, feature_bytes)) return results这段代码里需要特别解释两个地方第一ASFDetectFaces只是触发检测算法检测结果要通过ASFGetDetectedFaces取回来。我刚开始写的时候以为DetectFaces的返回值就是结果直接拿返回值判断人数结果永远是0后来翻了头文件才发现要调两次函数。官方的Python示例里就是这么做的新的封装接口也沿用了这个设计。第二特征提取时传入的ASF_Detection结构体必须和检测结果里的框信息一致。如果角度信息不准特征提取接口可能返回0但featureSize长度仍为0相当于没提取到有效特征。比较稳妥的做法是如果face_angle大于0可以尝试对图像做一次旋转变换后再提取或者直接丢弃该人脸框毕竟门禁场景下正脸识别是主流诉求。3.5 人脸比对余弦相似度与阈值设定特征提取之后比对算法就简单了。ArcFace的ASFFaceComparison接口接收两个特征结构体返回相似度值取值范围0~1。官方阈值一般推荐设成0.75以上才算同一个人但实际项目里要根据现场摄像头角度和光照微调。下面是人脸比对的调用示例def face_compare(feature_a, feature_b): 比对两个人脸特征返回相似度 c_feature_a ASF_FaceFeature() c_feature_a.feature ctypes.cast( ctypes.create_string_buffer(feature_a, len(feature_a)), ctypes.POINTER(c_ubyte) ) c_feature_a.featureSize c_int(len(feature_a)) c_feature_b ASF_FaceFeature() c_feature_b.feature ctypes.cast( ctypes.create_string_buffer(feature_b, len(feature_b)), ctypes.POINTER(c_ubyte) ) c_feature_b.featureSize c_int(len(feature_b)) score c_float(0) ret arcsoft.ASFFaceComparison( byref(c_feature_a), byref(c_feature_b), byref(score) ) if ret ! 0: return 0.0 return score.valueArcFace返回的相似度已经是归一化好的不需要再手动计算余弦距离。设计比对流程时建议把特征数据存到本地文件或数据库比对时不需要重新走检测流程直接把特征load进内存做计算效率会高很多。实际项目中我对10万级别的本地特征库做过一次性能测试用numpy矩阵乘法批量计算余弦相似度单次检索耗时在15ms左右完全够实时性要求。如果特征库超过百万级建议上用Faiss这类向量检索库但那是另一个话题了。3.6 年龄、性别检测与活体检测扩展ArcFace不仅支持人脸检测和识别还支持年龄、性别估计以及RGB活体检测。如果做门禁活体检测是必须加的不然一张照片就能骗过系统。年龄和性别检测的调用代码本质上和特征提取是同一套流程但要先激活对应的算法引擎# 激活年龄和性别检测能力 arcsoft.ASFInitEngine( c_int(ASF_DETECT_MODE_IMAGE), c_int(ASF_FACE_ANGLE_0), c_int(10), c_int(ASF_AGE | ASF_GENDER), # 启用年龄性别检测 app_id, sdk_key, byref(engine) ) # 检测之后调用年龄估计 age_info ASF_AgeInfo() arcsoft.ASFAgeEstimation( engine, c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), c_int(ASF_PIXEL_FORMAT_NV21), nv21_data, byref(face_info[i]), byref(age_info) )RGB活体检测的API名字是ASFLivenessDetection它需要专门的模型文件LivenessModel.bin并且需要在初始化引擎时通过ASFSetLivenessParam设置参数。活体检测返回的是一个分数一般阈值设在0.5左右分数越高表示活体概率越大。这个功能在照片翻拍场景下特别有效但要注意它只针对RGB摄像头对红外深度摄像头的支持需要专门的IR版本模型。4. 常见问题与排查技巧实录4.1 激活阶段的经典报错与处理方法激活失败是我在这套SDK上遇到最多的问题也是网上求助帖里最高频的一类。我把实际遇到的几种情况整理成了一个表格报错信息问题原因解决方案INTERNET_TIME_OUT激活服务器访问超时检查防火墙、DNS改用公共DNS重新激活ACTIVE_FAILED设备ID和申请Key不匹配查看设备硬件ID去官网重新申请离线激活码ASF_EX_ACTIVE_KEY_OVERDATE激活码过期重新生成激活码License无效授权文件路径不对确认授权文件和SDK包在同一目录或指定正确路径{ERRORCODE}1003SDK版本和激活码版本不匹配确认下载的SDK版本和申请时选择的版本一致激活时最头疼的问题是官网要求查看离线激活码时才让人眼识别输入不能直接复制。这个东西看起来繁琐其实是虹软为了防止机器人自动刷激活码做的验证保护只能忍着。如果出现激活服务器连不上的情况不要急着重装SDK。先排查本地网络策略特别是公司内网环境很多企业网络会屏蔽未知域名的HTTPS连接。我自己测试过用一个干净的家庭宽带环境激活成功率非常高而在公司网络下就经常超时。所以我的建议是激活这类和网络相关的操作尽量放在不受网络策略限制的环境下做激活完成后生成的授权文件拷回去用就行。4.2 运行时错误检测不到人脸与内存异常激活通过之后另一个高频报错是这个ASF_EX_FACELIB_NOT_ACTIVE or errorcode90111这个错误的意思是人脸库功能没有激活。ArcFace的人脸库管理ASFFaceLibrary和基础的人脸检测识别是分开授权的如果APPID申请的授权里没有勾选人脸库能力调用注册人脸到人脸库的接口就会报这个码。解决办法有两个一是去官网重新申请授权把人脸库能力勾上二是放弃人脸库接口自己用文件或数据库管理特征数据。我项目里直接选了后者因为自己的特征库做比对更灵活不受SDK单库容量限制。还有个常见运行时错误是未检测到人脸。这个原因就多了常见的有图像格式错了传了JPG的二进制数据而不是NV21原始像素数据图像尺寸不是4的倍数导致SDK内部内存对齐失败检测模式设置错误比如用IMAGE模式去处理视频流截帧也会降低召回率人脸角度超过检测范围侧脸或低头识别率大幅下降内存异常类问题在Python端不常见但如果长时间调用会产生句柄泄漏。建议设置一个定时重启机制比如每处理10万帧就重启一次引擎或者用进程池隔离保证长期运行的稳定性。4.3 视频流场景的连续识别优化考勤机的实际使用场景是连续视频流识别而不是一张一张的静态图。我实现视频识别时一开始是每帧都调用检测和特征提取结果CPU直接拉满掉帧严重。后来做了三个优化第一隔帧检测。摄像头帧率25fps实际门禁场景不用每帧都检测设置一个3帧的间隔检测频率降到8fps左右依然能流畅跟脸CPU占用直接降一半。第二检测和识别分离。不是每一个检测到的人脸都需要马上比对可以先做一次轻量的人脸质量评估——检测框太小或置信度太低就直接跳过质量达标才进入特征提取和比对流程。ArcFace检测结果里自带score字段官方建议0.7以上再提取特征我实际调下来0.65就够用太严格会漏检。第三跟踪机制。用检测框的中心点坐标做个简单最近邻匹配把当前帧的人脸框和上一帧的人脸框关联起来就避免了重复提取特征、重复比对。这个简单的帧间跟踪能减少80%以上的重复计算而且实现起来也就二十行。last_center None def track_and_compare(engine, img_bgr, target_feature): global last_center rects detect_faces(engine, img_bgr) if not rects: last_center None return False # 取置信度最高的框 best max(rects, keylambda r: r[4]) center ((best[0]best[2])//2, (best[1]best[3])//2) # 如果中心点和上一帧接近认为是同一张脸跳过特征提取 if last_center is not None: dist np.sqrt((center[0]-last_center[0])**2 (center[1]-last_center[1])**2) if dist 50: # 50像素以内的移动认为是同一张脸 last_center center return None # 表示正在跟踪中 last_center center # 提取特征并比对 feature extract_feature(engine, img_bgr, best) score face_compare(feature, target_feature) return score 0.75这样改写之后我在实机上测试Windows下CPU占用从35%降到了15%左右人脸识别响应时间从300ms缩短到80ms体验提升非常明显。5. 实操心得几个提升效率的小建议最后分享几个我在实际项目中总结出来的经验。优先跑通官方示例再改自己的场景。虹软的SDK包自带的examples是很好的起点。我之前图省事直接照着自己写的代码跑结果报错之后半天找不原因后来老老实实把官方示例完整跑了一遍再对照自己代码做增量修改效率反而更高。大多数定位问题其实都能通过先跑通最小可用示例这个笨办法解决。授权文件做好备份。虹软SDK的授权文件和机器码绑定一旦系统损坏或误删授权文件重新激活很麻烦。每次激活成功后立刻把.lic文件复制到一个不容易被覆盖的位置。我做项目时习惯在D盘专门建一个lic_backup目录顺手更新到git仓库里做历史版本管理这个习惯救过我一次。用PyCharm调试时注意Python解释器位数。PyCharm里配置的虚拟环境如果是32位ArcFace的64位DLL加载时会直接失败而且报错信息非常隐蔽只显示ModuleNotFoundError或者加载动态库失败的通用提示。创建虚拟环境时确认一下解释器版本在终端跑python -c import platform; print(platform.architecture())输出(64bit, WindowsPE)就没问题。虹软ArcFace这套离线SDK的Python接口整体做得比较规整文档齐全上手不难但细节坑也不少。希望这篇基于实际项目踩坑经历写出来的文章能帮你少走几段弯路。如果你正在做类似的人脸识别项目顺着我整理的流程走一遍应该能顺利跑通第一版。
返回列表