简介ArcSoft_ArcFace_Linux_x64_V3.0.zip 是虹软面向 Linux x64 平台发布的开源人脸识别 SDK 发行包适合需要在服务端或嵌入式 Linux 环境中集成人脸检测、关键点定位、人脸比对与属性分析的开发者可服务于门禁考勤、安防监控、无人零售支付等场景。压缩包共 19 个文件约 36.75MB以头文件、动态库、说明文档和示例代码为主头文件用于声明 API 接口so 动态库供项目链接调用txt 与 pdf 文档给出开发指南和版本说明另有示例工程与测试图片便于对照验证。资源内附开发者指南、版本更新日志以及可直接编译的示例代码读者能据此快速理解接口调用方式、完成环境配置与工程搭建并借助示例排查链接与运行问题。目前已有 207 人学习下载适合具备一定 C 与 Linux 开发基础、希望低成本接入人脸识别能力的技术人员参考。1. 从一张 Linux 服务器上的证件照比对说起上周有个做门禁系统的朋友找我说他们在一台 Ubuntu 服务器上跑人脸比对用 Python 调了半天第三方库结果要么识别率飘忽要么一并发就崩。我问他用的什么他说是某个开源模型自己封装的接口。我让他把ArcSoft_ArcFace_Linux_x64_V3.0.zip解压出来看看——这是虹软 ArcFace 引擎的 Linux x64 V3.0 版本一个本地离线的人脸识别 SDK不依赖网络请求不挑 GPU纯 CPU 就能跑。它解决的核心问题很明确在 Linux 服务器上做 1:1 人脸比对和 1:N 人脸检索给出稳定的人脸检测、特征提取和比对分数。适合谁做门禁、考勤、人证核验、照片去重这类需要本地化部署的开发者。如果你正在找一套能在 x64 Linux 上直接编译、不折腾环境的人脸识别方案这份资源值得拆开看。2. 拆包看结构动态库、头文件与激活机制2.1 压缩包里到底有什么解压ArcSoft_ArcFace_Linux_x64_V3.0.zip之后目录结构通常长这样不同小版本可能略有差异但核心文件一致ArcSoft_ArcFace_Linux_x64_V3.0/ ├── lib/ │ ├── libarcsoft_face.so │ ├── libarcsoft_face_engine.so │ └── libarcsoft_face_engine_jni.so ├── inc/ │ ├── arcsoft_face_sdk.h │ ├── amcomdef.h │ ├── asvloffscreen.h │ └── merror.h ├── samplecode/ │ ├── C/ │ │ └── sample.cpp │ └── Java/ │ └── ArcFaceDemo.java ├── doc/ │ └── 开发文档.pdf └── readme.txt关键文件就三类.so动态库、.h头文件、samplecode示例代码。libarcsoft_face.so负责人脸检测和特征提取libarcsoft_face_engine.so是引擎主库JNI 那个是给 Java 层用的。头文件里arcsoft_face_sdk.h是核心接口定义amcomdef.h定义了基础类型asvloffscreen.h是图像数据结构merror.h是错误码表。注意V3.0 的库文件是编译好的二进制不提供源码。你只能通过头文件暴露的接口来调用不能改内部实现。2.2 激活与授权SDK 的“黑匣子”入口ArcFace Linux SDK 不是解压就能无限用的它有一套激活机制。你需要在代码里调用ArcFaceEngine::ActiveDevice或者 C 接口的AFActivate传入从官方获取的 AppId 和 SDKKey。常见做法是在程序启动时激活一次激活成功后把激活文件通常叫ArcFaceActive.dat或类似名字写到磁盘后续启动直接读激活文件不再重复联网激活。这里有个血泪经验激活文件是和设备绑定的。如果你在虚拟机里激活然后把镜像克隆到另一台机器激活会失效。我一般会在 Docker 里跑但 Docker 容器的 MAC 地址每次重建都会变所以要么固定 MAC要么把激活文件挂载到宿主机持久化目录。# 查看动态库依赖确认没有缺库 ldd libarcsoft_face_engine.so # 如果提示 not found把 lib 目录加入 LD_LIBRARY_PATH export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/your/path/ArcSoft_ArcFace_Linux_x64_V3.0/libldd用来检查.so依赖的共享库是否齐全。如果缺libarcsoft_face.so说明LD_LIBRARY_PATH没设对或者你把库文件放错了位置。export那行是临时生效写进~/.bashrc或 systemd 的Environment里才能持久。2.3 图像格式与内存对齐的坑ArcFace 的 C 接口接收的是ASVLOFFSCREEN结构体里面包含图像宽高、像素格式和指向图像数据的指针。像素格式常见的有ASVL_PAF_RGB24_B8G8R8即 BGR 排列和ASVL_PAF_GRAY。如果你从 OpenCV 读图cv::Mat默认是 BGR正好对应ASVL_PAF_RGB24_B8G8R8但要注意Mat的step可能不等于width * 3因为 OpenCV 会对齐行边界。// 假设 img 是 cv::Mat类型 CV_8UC3 ASVLOFFSCREEN offscreen {0}; offscreen.u32PixelArrayFormat ASVL_PAF_RGB24_B8G8R8; offscreen.i32Width img.cols; offscreen.i32Height img.rows; offscreen.pi32Pitch[0] img.step; // 关键用 step 而不是 cols*3 offscreen.ppu8Plane[0] img.data;pi32Pitch[0]必须填img.step否则图像会错位检测出来的人脸框会偏移甚至检测不到。这是新手最容易翻车的地方之一。如果你手动分配内存那就让pitch width * 3并且确保内存是连续且按行紧密排列的。3. 从编译到跑通一个最小可用的 C 比对程序3.1 环境准备与编译命令先确认系统里有 g 和 make然后写一个最简单的比对程序。假设你已经把inc和lib放到了/opt/arcface下。# 安装编译工具Ubuntu/Debian sudo apt update sudo apt install -y build-essential cmake # 设置库路径 export LD_LIBRARY_PATH/opt/arcface/lib:$LD_LIBRARY_PATH编译时用-I指定头文件目录-L指定库目录-l指定库名。g -stdc11 -o face_compare face_compare.cpp \ -I/opt/arcface/inc \ -L/opt/arcface/lib \ -larcsoft_face_engine -larcsoft_face \ -lpthread -ldl-lpthread和-ldl是 ArcFace 库内部依赖的不加会报未定义符号。-stdc11是因为示例代码里用了nullptr和auto。3.2 初始化引擎与激活#include arcsoft_face_sdk.h #include merror.h #include cstdio int main() { // 1. 激活 MRESULT res ArcFaceEngine::ActiveDevice( 你的AppId, 你的SDKKey, nullptr); if (res ! MOK) { printf(激活失败错误码%d\n, res); return -1; } // 2. 初始化引擎指定检测模式 res ArcFaceEngine::InitEngine( ASF_DETECT_MODE_IMAGE, // 图片模式视频流用 ASF_DETECT_MODE_VIDEO ASF_OP_0_ONLY, // 仅检测 0 度人脸可选 0/30/45/90 等 16, // 最小人脸尺寸单位像素 5, // 最大人脸数 nullptr); if (res ! MOK) { printf(引擎初始化失败%d\n, res); return -1; } // ... 后续处理 ArcFaceEngine::UninitEngine(); return 0; }ActiveDevice的第三个参数是激活文件路径传nullptr表示默认在当前目录生成。InitEngine的第二个参数ASF_OP_0_ONLY表示只检测正脸如果你的场景里人脸会旋转改成ASF_OP_0_HIGHER_EXT或ASF_OP_0_90等组合。第三个参数16是最小人脸宽度设太小会误检设太大漏检一般 16 到 32 之间。第四个参数是单张图最多检测几张脸门禁场景设 1 到 5 就够。3.3 人脸检测与特征提取// 假设已经准备好 ASVLOFFSCREEN offscreen ASF_MultiFaceInfo faces {0}; MRESULT res ArcFaceEngine::FaceDetection( offscreen, faces); if (res ! MOK || faces.faceNum 0) { printf(未检测到人脸\n); return -1; } // 取第一张人脸的特征 ASF_FaceFeature feature {0}; res ArcFaceEngine::FaceFeatureExtract( offscreen, faces.faceRect[0], feature); if (res ! MOK) { printf(特征提取失败%d\n, res); return -1; }FaceDetection返回的ASF_MultiFaceInfo里包含faceNum和faceRect数组每个faceRect是一个矩形框。FaceFeatureExtract需要传入原图和某一张人脸的矩形框输出ASF_FaceFeature里面是 1024 字节V3.0 通常是 1024 维的特征向量。3.4 比对与分数解读ASF_FaceFeature feature1 ...; // 第一张脸的特征 ASF_FaceFeature feature2 ...; // 第二张脸的特征 MFloat confidence 0.0f; MRESULT res ArcFaceEngine::FaceCompare( feature1, feature2, confidence); if (res ! MOK) { printf(比对失败%d\n, res); return -1; } printf(比对分数%.2f\n, confidence); // 一般阈值 0.8 认为是同一人 0.6 认为不是FaceCompare输出的是相似度分数范围通常在 0 到 1 之间。实际用的时候阈值要根据业务调。门禁场景建议 0.8 以上考勤可以 0.75照片去重可以 0.9。不要迷信固定阈值最好拿一批真实数据跑 ROC 曲线选一个误识率和拒识率平衡的点。注意特征向量是二进制数据存数据库时用 BLOB 类型不要转成字符串再存否则精度会丢。4. 避坑排查激活失败、检测不到脸、内存泄漏4.1 激活返回错误码 90114 或 90115现象调用ActiveDevice返回 90114 或 90115程序直接退出。原因90114 通常是 AppId 和 SDKKey 不匹配或者 SDKKey 对应的平台不是 Linux x64。90115 是激活文件写入失败常见于目录没有写权限或者磁盘满了。解决先确认你拿到的 SDKKey 是 Linux x64 版本的不是 Windows 或 Android 的。然后在ActiveDevice第三个参数里指定一个可写目录比如/tmp/arcface_active并确保运行用户有写权限。4.2 图片明明有人脸却返回 faceNum 0现象用 OpenCV 读了一张清晰的正面照FaceDetection返回faceNum 0。原因最常见的是pi32Pitch填错导致图像数据错位。其次是像素格式不对比如你把 RGB 数据当成 BGR 传进去。还有一种可能是最小人脸尺寸设得太大比如设了 100而图片里人脸只有 80 像素宽。解决先打印img.step和img.cols * 3看是否相等。如果不相等pi32Pitch[0]必须用img.step。然后确认u32PixelArrayFormat和你的数据排列一致。最后把最小人脸尺寸调到 16 再试。4.3 长时间运行后内存持续增长现象程序跑几个小时内存占用从几十兆涨到几个 G最后被 OOM Killer 杀掉。原因ASF_FaceFeature里的feature指针指向引擎内部分配的内存如果你在循环里反复调用FaceFeatureExtract但不释放就会泄漏。另外FaceDetection返回的ASF_MultiFaceInfo里的faceRect数组也是引擎内部分配的虽然多数版本会自动管理但如果你手动拷贝了指针就要小心。解决每次提取完特征如果不再使用调用ArcFaceEngine::FaceFeatureRelease如果头文件里有或者确保在下次调用前不再持有旧指针。更稳妥的做法是把特征拷贝到你自己的std::vectorunsigned char里然后让引擎自己回收。// 安全拷贝特征 std::vectorunsigned char my_feature( feature.feature, feature.feature feature.featureSize); // 后续用 my_feature.data() 和 my_feature.size()4.4 多线程并发时崩溃现象单线程跑得好好的一开多线程就 segfault。原因ArcFace 引擎实例不是线程安全的。如果你在多个线程里共用一个引擎句柄同时调用FaceDetection或FaceFeatureExtract内部状态会冲突。解决每个线程单独InitEngine一个实例或者加互斥锁串行化调用。但加锁会降低吞吐推荐每个线程独立初始化。初始化开销不大几十毫秒的事。4.5 比对分数忽高忽低现象同一组人脸两次比对分数差很多。原因人脸检测框的微小偏移会导致特征提取结果不同。如果两次传入的faceRect不完全一致分数就会有波动。解决对于 1:1 比对尽量用同一张图检测出来的人脸框或者用对齐后的人脸。ArcFace 有提供人脸对齐接口FaceAlignment但 V3.0 的 C 接口里可能没有直接暴露需要自己根据关键点做仿射变换。常见做法是检测到人脸后取两眼中心点做旋转对齐再提取特征。5. 进阶技巧把 ArcFace 塞进 Docker 并做批量比对5.1 构建一个最小化 Docker 镜像FROM ubuntu:20.04 RUN apt update apt install -y libgomp1 libstdc6 COPY ArcSoft_ArcFace_Linux_x64_V3.0/lib /opt/arcface/lib COPY ArcSoft_ArcFace_Linux_x64_V3.0/inc /opt/arcface/inc COPY face_compare /app/face_compare ENV LD_LIBRARY_PATH/opt/arcface/lib WORKDIR /app ENTRYPOINT [./face_compare]libgomp1是 OpenMP 运行时ArcFace 内部可能用了并行计算。libstdc6是 C 标准库。基础镜像用ubuntu:20.04是因为它的 glibc 版本和大多数编译环境兼容。如果你在更新的系统上编译可以换成对应的基础镜像。5.2 批量比对脚本import subprocess import json import os def compare_faces(img1, img2): result subprocess.run( [./face_compare, img1, img2], capture_outputTrue, textTrue ) if result.returncode ! 0: return None # 假设 face_compare 输出 JSON 格式的分数 return json.loads(result.stdout)[confidence] # 遍历目录做 1:N 检索 def search_face(query_img, gallery_dir): query_feat extract_feature(query_img) best_score 0.0 best_match None for fname in os.listdir(gallery_dir): path os.path.join(gallery_dir, fname) score compare_features(query_feat, extract_feature(path)) if score best_score: best_score score best_match fname return best_match, best_score这个 Python 脚本只是壳实际比对还是调 C 程序。subprocess.run的capture_outputTrue会捕获标准输出textTrue让输出是字符串。json.loads解析分数。批量检索时先把底库所有人脸的特征提取出来存成文件查询时只提取查询图的特征然后逐个比对避免重复提取底库特征。5.3 特征向量存储与检索优化V3.0 的特征是 1024 字节的浮点数组。如果底库有 10 万张人脸全部加载到内存是 100MB 左右可以接受。但逐个比对 10 万次每次计算余弦相似度在单核上大概需要几百毫秒。如果要求实时可以用 SIMD 指令加速或者用 Faiss 建索引。// 余弦相似度计算简化版 float cosine_similarity(const float* a, const float* b, int dim) { float dot 0.0f, norm_a 0.0f, norm_b 0.0f; for (int i 0; i dim; i) { dot a[i] * b[i]; norm_a a[i] * a[i]; norm_b b[i] * b[i]; } return dot / (sqrt(norm_a) * sqrt(norm_b)); }但注意ArcFace 的FaceCompare内部可能不是简单的余弦相似度它有自己的归一化和映射逻辑。所以如果你自己算相似度分数和 SDK 返回的分数可能对不上。稳妥做法还是调FaceCompare或者用 SDK 返回的特征做你自己的距离度量但阈值要重新标定。5.4 一个我踩过的坑激活文件在容器重启后失效Docker 容器每次docker run都会生成新的容器 ID如果激活文件写在容器内部重启后容器被删除激活文件就丢了。我一开始把激活文件写在/tmp结果每次重启都要重新激活而激活接口有频率限制频繁调用会被临时封禁。后来我改成把激活文件写到挂载卷里docker run -v /host/arcface_data:/data \ -e ARCFACE_ACTIVE_PATH/data/ArcFaceActive.dat \ face_compare然后在代码里读环境变量ARCFACE_ACTIVE_PATH传给ActiveDevice的第三个参数。这样容器重建激活文件还在不用重复激活。从那以后我每次部署带激活机制的 SDK都强制走一遍“激活文件外挂”的流程再也没被频率限制坑过。希望帮到你。本文还有配套的精品资源点击获取