
简介本资源面向嵌入式AI开发者与计算机视觉工程师提供在海思Hi3516智能摄像机处理器上部署Arcface人脸识别算法的完整项目源码帮助解决资源受限平台下深度学习模型落地难的问题。压缩包共300个文件约42.81MB以137个C/C头文件与124个hpp文件构成算法与工程主体另含14个so动态库、若干jpg测试图、makefile构建脚本及说明文档覆盖模型适配、交叉编译与系统集成等环节。已有237人学习下载适合具备一定嵌入式与深度学习基础、希望掌握端侧人脸识别部署的开发者参考。项目围绕Arcface角度间隔损失带来的判别力优势结合Hi3516的AI加速模块进行算子替换与推理优化源码中可参考数据预处理、后处理及监控场景集成示例并附有性能监控与异常处理等工程实践思路便于快速搭建高效人脸识别系统。1. 海思 Hi3516 上跑 ArcFace从模型到板端的一次完整落地海思 Hi3516 这颗芯片在 IPC、门禁机、人脸抓拍盒子里出货量极大NNIE 硬件加速单元能跑卷积网络功耗和成本都压得很低。但很多人第一次把 ArcFace 人脸识别往 Hi3516 上搬的时候会发现一个尴尬的现实训练侧的 ArcFace 输出的是 512 维特征向量而 Hi3516 的 NNIE 对动态 shape、大分辨率输入、非标准算子支持有限直接拿 PyTorch 权重去转十有八九在转换阶段就翻车。这篇笔记讲的就是把 ArcFace 人脸识别算法真正部署到 Hi3516 上的完整路径——从模型裁剪、ONNX 导出、NNIE 量化编译到板端推理和比对阈值调优。适合已经跑通过 PC 端人脸识别、想往嵌入式端迁移的工程师也适合正在做人脸识别门禁机选型、需要评估 Hi3516 方案可行性的从业者。读完你应该能自己搭出一条可复现的部署链路知道每一步的参数边界和常见坑在哪。2. ArcFace 与 Hi3516 NNIE 的适配逻辑为什么不能直接转2.1 ArcFace 推理图里哪些部分必须改ArcFace 的训练图包含 backbone常见是 ResNet、MobileFaceNet 或 IR-SE 系列、BN 层、PReLU 激活、以及最后的 ArcFace margin 分类头。部署到 Hi3516 时分类头完全不需要——板端只做特征提取比对放在 CPU 上用余弦相似度算。所以第一步是把训练权重里的分类头摘掉只保留 backbone 到 embedding 输出。但摘掉分类头还不够。Hi3516 NNIE 对 PReLU 的支持在部分 SDK 版本里是通过 LeakyReLU 近似实现的如果模型里大量用 PReLU量化后精度掉得厉害。常见做法是把 PReLU 替换成 ReLU 或 LeakyReLU 再微调几个 epoch让精度回补。另一个问题是 BN 层推理阶段必须 fuse 进卷积否则 NNIE 编译时会多出大量无意义的 scale 算子拖慢速度。输入分辨率也要注意。ArcFace 训练常用 112x112Hi3516 NNIE 对 112x112 这种非 16 对齐的输入支持不好一般会 pad 到 128x128 或者直接改成 128x128 重新微调。我一般会选 128x128因为 NNIE 对 128 的整除对齐最友好且精度损失在 0.3% 以内。2.2 NNIE 的算子约束与量化策略NNIE 是定点推理单元支持 8bit 量化但它的量化不是训练后量化那种简单校准而是需要提供量化校准图片集通过 nnie_mapper 工具生成量化参数。校准集一般准备 200 到 500 张人脸图覆盖不同光照、角度、遮挡否则量化后特征分布偏移会导致比对阈值完全失效。NNIE 支持的算子列表在 SDK 文档里有明确说明常见的 Conv、Pooling、Eltwise、Concat、FC 都支持但像 Group Conv 在部分版本里支持不完整Depthwise Conv 需要确认 SDK 版本。MobileFaceNet 里大量用 Depthwise Conv如果 SDK 不支持就得换成普通卷积或者换 backbone。这一点在选型阶段就要确认不要等模型转完了才发现编译报错。量化策略上我一般会先用浮点 ONNX 在 PC 上跑一遍验证精度确认 backbone 本身没问题再走 NNIE 量化编译。量化后的模型在板端跑出来的特征向量和 PC 浮点模型的特征向量做余弦相似度对比如果同一人的相似度低于 0.95说明量化损失过大需要调整校准集或者换量化粒度。2.3 从 PyTorch 到 ONNX 再到 NNIE 的转换链路整个转换链路是PyTorch 权重 → 去掉分类头 → fuse BN → 替换 PReLU → 导出 ONNX → ONNX 简化 → nnie_mapper 编译 → 生成 .wk 文件 → 板端加载推理。导出 ONNX 时要注意 opset 版本Hi3516 SDK 一般支持 opset 9 到 11太高会引入 NNIE 不支持的算子。导出命令里要固定输入 shape不要用 dynamic axes。ONNX 简化用 onnx-simplifier把冗余的 Identity、Dropout 节点去掉。nnie_mapper 编译时需要提供 .prototxt 描述文件和 .caffemodel 或者 ONNX 模型以及校准图片列表。编译命令里要指定输入分辨率、量化模式、输出层名称。输出层名称要和后面板端代码里取的 blob 名称一致否则取不到特征向量。# 导出 ONNX固定输入 1x3x128x128 python export_onnx.py \ --weights arcface_backbone.pth \ --input-size 128 \ --opset 11 \ --output arcface_128.onnx # ONNX 简化 python -m onnxsim arcface_128.onnx arcface_128_sim.onnx # NNIE 编译生成 .wk nnie_mapper \ --model arcface_128_sim.onnx \ --proto arcface.prototxt \ --calibration calibration_list.txt \ --input_shape 1,3,128,128 \ --output arcface_128.wk \ --quantize 8bit导出脚本里关键参数是 input-size 和 opsetinput-size 决定板端预处理的分辨率opset 决定算子集。nnie_mapper 的 calibration_list.txt 每行是一张校准图的路径图片需要提前 resize 到 128x128 并归一化。量化模式选 8bit 是精度和速度的平衡点如果精度不够可以试 16bit但速度会降。3. 板端推理链路搭建从 .wk 加载到特征比对3.1 Hi3516 SDK 环境准备与交叉编译Hi3516 的开发环境一般是 Ubuntu 16.04 或 18.04SDK 里自带交叉编译工具链 arm-himix200-linux。先把 SDK 里的 mpp、nnie 相关库编译出来生成 libnnie.so、libmpi.so 等动态库。板端跑推理需要这些库交叉编译时链接路径要写对。我一般会在 SDK 根目录下先执行 source ./sdk.unpack 解压然后进入 mpp 目录 make再进入 nnie 目录 make。编译完成后把生成的库和头文件放到一个独立的 sysroot 目录方便后面应用程序引用。应用程序的编译用 CMake 或者直接写 Makefile链接时加上 -lnnie -lmpi -lpthread。注意 Hi3516 的内存布局NNIE 使用的内存需要从 MPP 的 mmz 里分配不能直接用 malloc否则会报物理地址映射失败。// 初始化 NNIE 和 MPP 系统 HI_MPI_SYS_Exit(); HI_MPI_VB_Exit(); VB_CONF_S vb_conf; memset(vb_conf, 0, sizeof(vb_conf)); vb_conf.u32MaxPoolCnt 1; vb_conf.astCommPool[0].u32BlkSize 1280 * 720 * 2; vb_conf.astCommPool[0].u32BlkCnt 4; HI_MPI_VB_SetConf(vb_conf); HI_MPI_VB_Init(); HI_MPI_SYS_Init(); // 加载 .wk 模型 SAMPLE_SVP_NNIE_MODEL_S model; HI_CHAR *pcModelName arcface_128.wk; SAMPLE_COMM_SVP_CheckSysInit(); SAMPLE_COMM_SVP_NNIE_LoadModel(pcModelName, model);这段代码先初始化 MPP 的 video buffer再加载 NNIE 模型。vb_conf 里的 blkSize 要根据实际输入图片大小调整1280x720 是常见 IPC 分辨率。加载模型后NNIE 会在内部完成网络构建和内存分配。3.2 预处理人脸对齐与归一化在板端的实现ArcFace 对输入人脸的对齐很敏感训练时用的是 5 点仿射变换对齐到 112x112 或 128x128。板端也要做同样的对齐否则特征分布和训练不一致比对精度会掉。常见做法是用人脸检测模型比如 RetinaFace 或 SCRFD先出 5 个关键点再用仿射变换把脸对齐到 128x128。Hi3516 上跑检测模型会额外占 NNIE 资源如果板子算力有限可以把检测和对齐放在 CPU 上用 OpenCV 做或者用轻量检测模型。对齐后的图片要做归一化ArcFace 训练时一般是 (pixel - 127.5) / 128板端也要用同样的归一化参数否则特征向量会偏。// 仿射变换对齐到 128x128 cv::Mat src_face ...; // 检测框裁剪的人脸 cv::Mat dst_face; cv::Point2f src_pts[5] {...}; // 检测到的 5 点 cv::Point2f dst_pts[5] { {38.2946, 51.6963}, {73.5318, 51.5014}, {56.0252, 71.7366}, {41.5493, 92.3655}, {70.7299, 92.2041} }; cv::Mat trans cv::estimateAffinePartial2D( cv::Mat(5, 1, CV_32FC2, src_pts), cv::Mat(5, 1, CV_32FC2, dst_pts)); cv::warpAffine(src_face, dst_face, trans, cv::Size(128, 128)); // 归一化 dst_face.convertTo(dst_face, CV_32FC3, 1.0/128.0, -127.5/128.0);dst_pts 是 ArcFace 标准 5 点模板对应 128x128 输入。estimateAffinePartial2D 算仿射矩阵warpAffine 做变换。归一化参数要和训练时完全一致差一点都会影响特征一致性。3.3 推理输出解析与余弦相似度比对NNIE 推理完成后输出的是一个 blob里面是 512 维 float 特征向量。取 blob 的时候要注意内存布局NNIE 输出的 blob 可能是 NCHW 或者 NHWC具体看编译时的配置。取到特征向量后做 L2 归一化然后和底库里的人脸特征算余弦相似度。底库一般存在板端的文件系统里每个人脸一个特征向量用二进制或者文本存。比对时遍历底库算相似度取最大值。阈值一般设 0.5 到 0.6具体看业务场景。门禁场景可以设高一点抓拍场景可以低一点。// 取 NNIE 输出 blob SAMPLE_SVP_NNIE_OUTPUT_S output; output.pstNnieBlob ...; // 从 NNIE 输出获取 float *feature (float *)output.pstNnieBlob-pu8VirAddr; // L2 归一化 float norm 0.0f; for (int i 0; i 512; i) { norm feature[i] * feature[i]; } norm sqrt(norm); for (int i 0; i 512; i) { feature[i] / norm; } // 和底库比对 float max_sim -1.0f; int match_id -1; for (int j 0; j gallery_size; j) { float sim 0.0f; for (int i 0; i 512; i) { sim feature[i] * gallery[j][i]; } if (sim max_sim) { max_sim sim; match_id j; } } if (max_sim 0.55f) { printf(Match: %d, similarity: %.4f\n, match_id, max_sim); }这段代码先取 NNIE 输出做 L2 归一化再遍历底库算余弦相似度。阈值 0.55 是经验值实际项目里要根据误识率和拒识率曲线调。底库特征也要提前做 L2 归一化否则相似度算出来不对。4. 避坑与排查Hi3516 部署 ArcFace 的 5 个血泪教训4.1 量化后特征向量分布偏移比对阈值完全失效现象浮点模型在 PC 上同一人相似度 0.98量化后板端同一人相似度只有 0.7不同人相似度反而 0.8阈值完全没法设。原因校准集覆盖不够NNIE 量化时对某些层的 scale 估计偏差大导致特征向量整体偏移。常见于 PReLU 层和 Depthwise Conv 层。解决扩充校准集到 500 张以上覆盖不同光照、角度、遮挡、年龄。如果还不行把 PReLU 换成 ReLU 重新微调或者对量化敏感层单独用 16bit 量化。我一般会在 nnie_mapper 里把最后几层设成高精度模式牺牲一点速度换精度。4.2 NNIE 编译报错 “unsupported layer type”现象nnie_mapper 编译时直接报某个算子不支持比如 Group Conv、Resize、Softmax。原因SDK 版本对算子支持有限或者 ONNX opset 太高引入了新算子。解决先查 SDK 文档里的算子支持列表确认哪些算子不支持。Group Conv 可以拆成多个普通 ConvResize 可以用反卷积替代Softmax 如果只是分类头可以直接去掉。opset 降到 9 或 10 再试。如果 backbone 里大量用不支持的算子考虑换 backboneMobileFaceNet 在部分 SDK 版本里支持不好可以换 ResNet18 裁剪版。4.3 板端推理时内存分配失败报 “mmz alloc failed”现象加载 .wk 模型或者推理时报内存分配失败程序直接退出。原因NNIE 需要从 MPP 的 mmz 里分配物理连续内存如果 vb_conf 里 blkSize 设太小或者系统里其他模块占用了太多 mmz就会分配失败。解决先查 /proc/media-mem 看 mmz 剩余情况然后调整 vb_conf 里的 blkSize 和 blkCnt。NNIE 模型加载需要的内存和网络大小相关128x128 的 MobileFaceNet 大概需要 20MB 到 30MB。如果 mmz 不够可以在内核启动参数里调大 mmz 大小比如 setenv mmz “mmzanonymous,0,0x8A000000,128M”。4.4 预处理归一化参数不一致导致特征向量完全对不上现象板端和 PC 端用同一个模型同一张图特征向量余弦相似度只有 0.3。原因预处理归一化参数不一致比如 PC 端用 (pixel - 127.5) / 128板端用 pixel / 255或者对齐模板不一致。解决把 PC 端预处理代码和板端预处理代码逐行对比确认归一化参数、对齐模板、通道顺序RGB vs BGR完全一致。我一般会写一个测试程序把同一张图在 PC 和板端分别跑一遍输出特征向量算余弦相似度确认大于 0.99 再继续。4.5 底库特征未归一化余弦相似度算出来大于 1现象比对时相似度算出来大于 1或者负数明显不对。原因底库特征没有做 L2 归一化或者推理输出的特征没有归一化导致余弦相似度公式失效。解决推理输出和底库特征都要做 L2 归一化确保模长为 1。归一化后再算点积结果就在 -1 到 1 之间。如果底库是提前存好的存之前就要归一化不要等比对时再算。5. 进阶技巧用多尺度特征融合提升 Hi3516 端侧识别率Hi3516 的 NNIE 算力有限单尺度 128x128 输入在复杂场景下识别率会掉尤其是侧脸、戴口罩、低照度。我一般会做一个轻量的多尺度融合在 backbone 的最后两个 stage 各取一个特征图分别做全局平均池化然后 concat 成一个 512256 维的特征向量再接一个 FC 降到 512 维。这样不增加太多计算量但特征表达能力明显提升。具体做法是在导出 ONNX 时把中间层的输出也暴露出来NNIE 编译时指定多个输出 blob。板端推理后把两个 blob 取出来 concat再做 L2 归一化。注意两个 blob 的量化 scale 可能不同concat 前要统一到同一量纲或者直接在浮点域做 concat。验证方法上我一般会准备一个 1000 对的正负样本集正样本同一人不同图负样本不同人。在 PC 浮点模型上跑一遍记录相似度分布再在板端量化模型上跑一遍对比 ROC 曲线。如果板端 AUC 比 PC 掉超过 2%就要回头查量化或者预处理。# PC 端验证脚本对比浮点和量化模型的特征一致性 import numpy as np import onnxruntime as ort sess_fp ort.InferenceSession(arcface_128.onnx) sess_qt ort.InferenceSession(arcface_128_quant.onnx) def get_feat(sess, img): inp img.transpose(2, 0, 1)[None].astype(np.float32) out sess.run(None, {sess.get_inputs()[0].name: inp})[0] return out / np.linalg.norm(out) feat_fp get_feat(sess_fp, img) feat_qt get_feat(sess_qt, img) sim np.dot(feat_fp, feat_qt) print(Feature similarity between FP and Quant: %.4f % sim)这个脚本用 onnxruntime 分别跑浮点和量化模型算特征余弦相似度。如果低于 0.95说明量化损失过大需要调校准集或者量化策略。我一般会把这个脚本挂在 CI 里每次换模型或者换 SDK 版本都跑一遍确保板端精度可控。最后说个习惯我每次部署完一个新模型都会在板端跑一个 24 小时稳定性测试记录内存占用、推理耗时、特征相似度波动。Hi3516 在长时间运行后NNIE 可能会有轻微的内存泄漏或者频率降频提前发现比上线后翻车强。希望帮到你。本文还有配套的精品资源点击获取