ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

香橙派RK3588边缘人脸识别实战:RetinaFace+rec模型NPU部署全流程

香橙派RK3588边缘人脸识别实战:RetinaFace+rec模型NPU部署全流程 说来也巧香橙派5RK3588刚拿到手那阵子我第一个想跑的项目就是人脸识别。原因很实在市面上的方案要么挂在云端要么绑在笨重的X86盒子上真正能塞进口袋、功耗只有几瓦、还带本地NPU算力的板子RK3588几乎就是当下的标准答案。这篇文章就围绕“香橙派RK3588 RetinaFace rec”这套组合把我从模型转换、NPU推理到后处理联调的完整过程写出来代码直接可抄希望对想在边缘设备上做AI落地的人有点帮助。这套系统的分工很清晰RetinaFace负责在画面中找到人脸输出人脸框和五个关键点rec模型insightface训练的人脸识别模型负责从对齐后的人脸区域提取512维特征向量后续靠向量距离判断是不是同一个人。两个模型我都转成了RKNN格式跑在香橙派的NPU上单帧检测加识别的耗时能控制在40毫秒级别应付门禁闸机、考勤打卡、课堂点名这类场景绰绰有余。1. 项目整体设计与方案选型1.1 为什么选香橙派RK3588算力、接口与生态人脸识别系统在边缘端最怕的就是算力不够。树莓派5的CPU虽然不弱但用CPU跑一个稍微像样的检测模型基本就是个位数帧率更别提再接一个识别模型。RK3588这颗芯片就完全不一样了8核CPU4个A76大核加4个A55小核集成Mali-G610 GPU最关键是自带一个6 TOPS算力的NPU。6 TOPS是什么概念它对标的是英伟达的Jetson Nano约0.5 TOPS的十几倍足够跑INT8量化的检测和识别模型了。我在实际测试中RetinaFace的MobileNet0.25版本量化后在NPU上单次推理大约15到25毫秒rec模型大约5到10毫秒这个速度放在门禁场景完全够用。而且RK3588的内存带宽和视频编解码能力都很强后续要接多路摄像头也有余量。香橙派5系列的另一个优势是接口齐全。USB 3.0可以接普通USB摄像头MIPI CSI可以接官方摄像头模组HDMI可以直接接显示器调试GPIO还能顺便控制电锁或者继电器。板子自带的Rockchip官方系统镜像里已经包含了NPU驱动和硬件编解码库省去了很多编译驱动的痛苦。1.2 算法选型RetinaFace做什么rec模型做什么人脸识别跟一般的目标检测不太一样它实际上包含两个阶段先检测再识别。检测阶段要在画面里把人脸找出来框出位置最好还能给出眼睛、鼻子、嘴巴这五个关键点因为后面做人脸对齐要用。识别阶段则把对齐后的人脸区域转成特征向量用特征向量的相似度来判断身份。我选RetinaFace做检测主要看中三点。一是检测加关键点的一体化输出框架里天然带了五个关键点的回归不需要额外再训练人脸关键点模型。二是模型体量小MobileNet0.25骨干的版本只有几兆字节非常适合边缘设备。三是它跟insightface生态是同一家出品前处理、后处理、关键点模板的坐标系约定完全一致后面串rec模型几乎没有对接成本。rec模型方面我直接用了insightface开源的人脸识别模型典型代表是w600k_r50输入112x112的RGB人脸图输出512维归一化特征向量。这套模型在光照变化、角度偏移、表情变化下都保持了不错的鲁棒性。识别判断的逻辑很简单把人脸特征与库中已注册的特征做余弦相似度计算超过阈值就认为是同一个人。1.3 整体数据流架构整个系统的数据流向是摄像头采集一帧图像先把图像缩放成RetinaFace期望的输入尺寸我用的640x640送到NPU做检测推理后处理得到若干个人脸框和关键点随后对每个人脸框裁剪出人脸区域利用五个关键点做仿射变换对齐到112x112对齐后的图像再送入rec模型提取512维特征最后与本地特征库逐条比对相似度输出识别结果。在代码结构上我把检测和识别封装成两个独立的类方便单独调试。实际联调的时候我建议先让检测单独跑起来在画面上看到人脸框和关键点再逐步加入识别部分。这样一旦出问题能快速定位是检测环节还是识别环节的锅。2. 环境准备与RKNN工具链2.1 板端系统与基础依赖板端我刷的是香橙派官方提供的Ubuntu 22.04镜像Debian也可以用Rockchip官方系统镜像的优点是把NPU驱动、GPU驱动、VPU编解码固件都预装好了。上电后用SSH连进板子第一步先确认NPU设备节点存在ls /dev/rknpu cat /proc/rknpu/version正常情况下能看到rknpu设备节点和版本信息。如果看不到说明系统镜像没带驱动或者需要在启动参数里打开。之后安装Python依赖我习惯用系统自带的Python 3.10直接pip安装就够pip install numpy opencv-python-headless pip install rknn-toolkit-lite2rknn-toolkit-lite2就是我们板端的推理库它会依赖系统的librknnrt.so运行时这个在官方镜像里已经带上了。实测opencv-python-headless版本就够了毕竟板子上不需要GUI。如果后续要显示画面调试再单独装完整版opencv和imshow的依赖。2.2 PC端模型转换环境模型转换必须在PC上进行因为RKNN-Toolkit2的完整版工具链在板端跑不流畅而且转换时内存和CPU占用都比较高。建议在Ubuntu 20.04或22.04的PC上用Python 3.8或3.10建一个干净的虚拟环境直接安装pip install rknn-toolkit2安装过程中最容易踩的坑是依赖版本冲突尤其是onnx、onnxruntime和protobuf。我的建议是安装完后先跑一下自带demo确认环境OK再开始转换自己的模型。另外注意PC上装的rknn-toolkit2和板上的rknn-toolkit-lite2是两个东西前者负责转换模型、仿真推理后者只负责在板端加载rknn模型做推理不要搞混了。2.3 模型文件准备与目录规划我习惯把所有东西放在一个固定目录方便管理和反复折腾rk3588-face/ ├── models/ │ ├── onnx/ │ │ ├── retinaface_mobile0.25.onnx │ │ └── w600k_r50.onnx │ ├── rknn/ │ │ ├── retinaface.rknn │ │ └── w600k_r50.rknn │ └── dataset.txt ├── convert_retinaface.py ├── convert_rec.py ├── test_recognition.py └── libs/ ├── detector.py ├── recognizer.py └── align.py模型文件的来源要说明一下。RetinaFace的onnx可以从insightface官方仓库拉取也有不少社区fork了带导出的版本。rec模型一般从insightface model zoo下载文件名通常叫w600k_r50.onnx。下载的时候注意看一下模型许可证如果是商用场景最好换成合规授权的模型部署逻辑是完全一样的。3. 模型转换与关键参数配置3.1 RetinaFace的ONNX导出与固定输出RetinaFace的原版PyTorch模型导成ONNX时有个关键点默认输入是动态的我建议在导出时直接固定batch为1输入尺寸固定为640x640这样转换RKNN时省去动态shape的麻烦推理性能也更稳定。onnx导出部分我用的导出代码大致是import torch from retinaface import RetinaFace model RetinaFace(mobilenet0.25) model.load_state_dict(torch.load(resnet50_softmax.pth, map_locationcpu)) model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, retinaface_mobile0.25.onnx, opset_version11, input_names[input], output_names[face_rpn_cls_prob, face_rpn_bbox_pred, face_rpn_landmark_pred], dynamic_axesNone )这里输出的三个tensor分别是对类别概率、边界框偏移、关键点偏移的原始预测。不同版本的RetinaFace输出节点名会有差异没关系转换的时候要记下来就行后处理的代码会用到。有些repo还会输出六个tensor那是把不同特征层的输出单独列出来了转换时要注意匹配。3.2 RetinaFace的RKNN转换实操拿到onnx之后转换脚本就很有讲究了。下面是我实际在用的转换脚本from rknn.api import RKNN rknn RKNN() rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588 ) print(-- Loading onnx) ret rknn.load_onnx(modelmodels/onnx/retinaface_mobile0.25.onnx) assert ret 0, load onnx failed print(-- Building model) ret rknn.build(do_quantizationTrue, datasetmodels/dataset.txt) assert ret 0, build failed rknn.export_rknn(models/rknn/retinaface.rknn)两个参数值得重点解释。mean_values和std_values对应的是原始模型训练时的预处理。RetinaFace的MobileNet版本用的是图像直接除以255做归一化也就是等价于mean为0、std为255转换成数值就是 (img - 0) / 255所以配置里写[[0,0,0]]和[[255,255,255]]。如果填错最典型的症状就是检测框大面积偏移或者干脆检不到人。dataset.txt是量化校准用的图片路径列表。量化不是随便给几张图就行选图很影响最终效果。我的建议是挑20到50张真实场景图覆盖不同的光照、角度和人物数量最好是目标设备将要部署的环境里拍的图。如果用的是纯背景图或者统一自拍图量化出来的模型在现场效果会很差。3.3 rec模型与RetinaFace模型的预处理差异rec模型的转换逻辑跟RetinaFace类似但有一个非常大的坑预处理参数完全不一样。insightface训练rec模型时输入图像做了这样几步resize到112x112RGB三通道然后做 (img / 255 - 0.5) / 0.5也就是归一化到-1到1。换算成RKNN的mean和std写法就是mean填127.5std也填127.5。from rknn.api import RKNN rknn RKNN() rknn.config( mean_values[[127.5, 127.5, 127.5]], std_values[[127.5, 127.5, 127.5]], target_platformrk3588 ) ret rknn.load_onnx(modelmodels/onnx/w600k_r50.onnx) assert ret 0, load onnx failed ret rknn.build(do_quantizationTrue, datasetmodels/dataset.txt) assert ret 0, build failed rknn.export_rknn(models/rknn/w600k_r50.rknn)很多新手在移植rec模型的时候会下意识把mean和std照抄RetinaFace的结果识别出来的特征向量全都不对相似度一片混乱。我建议把两个模型的预处理参数做成一张表贴在代码注释里防止自己以后再踩一遍模型输入尺寸通道顺序RKNN mean_valuesRKNN std_values数值范围RetinaFace检测640x640RGB0, 0, 0255, 255, 2550~1rec识别112x112RGB127.5, 127.5, 127.5127.5, 127.5, 127.5-1~13.4 量化精度相关int8、float16与混合量化RK3588的NPU对int8量化支持最好速度和吞吐都是最优的。但量化一定会带来精度损失rec模型尤其敏感。我的实测经验是RetinaFace量化后掉点不明显最多就是置信度稍微浮动而rec模型量化后特征向量的区分度会明显下降相似度阈值要重新调整。如果你的应用对精度要求高可以尝试两档方案。第一档是do_quantizationFalse转成float16的rknn模型精度基本无损但推理速度大约是int8的一半到三分之二。第二档是量化数据集里加入大量人脸样本把量化校准做得更充分再配合int8跑。如果还不行就用RKNN-Toolkit2的混合量化功能把rec模型最后的全连接层或特定层设定为float16其他层保持int8。后续章节我会把量化对识别的具体影响再展开讲。4. 推理代码与后处理实现4.1 RKNN推理类设计一个类管一个模型板端的推理代码我习惯封装成类。每个模型对应一个类加载rknn模型、初始化运行时、执行推理都放在类里面调用方只需要传图像进去拿结果出来就行。下面是一个检测类的骨架import cv2 import numpy as np from rknnlite.api import RKNNLite class FaceDetector: def __init__(self, model_path, input_size(640, 640), core_maskRKNNLite.NPU_CORE_0_1_2): self.input_size input_size self.rknn RKNNLite() assert self.rknn.load_rknn(model_path) 0, load rknn failed assert self.rknn.init_runtime(core_maskcore_mask) 0, init runtime failed def preprocess(self, bgr_img): img cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) img cv2.resize(img, self.input_size) img img.astype(np.float32) img img / 255.0 img img.transpose(2, 0, 1) return np.expand_dims(img, axis0) def infer(self, bgr_img): input_data self.preprocess(bgr_img) outputs self.rknn.inference(inputs[input_data]) return outputs这里有个容易忽略的点rknnlite的inference默认输入是NCHW格式也就是通道在前。我们传入的numpy数组形状需要是(1, 3, H, W)。如果你的模型是NHWC训练的可以通过rknn.config的input_format配置调整我这边统一用NCHW跟PyTorch导出的onnx保持一致。core_mask这个参数很实用它决定用几个NPU核心跑。RK3588的NPU内部有三个核心默认可能是单核我是直接传NPU_CORE_0_1_2全开推理速度能提升不少。4.2 RetinaFace后处理解码框、关键点与NMSRetinaFace的原始输出不是直接可用的坐标需要先解码。解码依赖anchoranchor是模型在训练时就预设好的一组框。不同特征层对应不同尺寸的anchor每个anchor会预测类别、框偏移和关键点偏移。后处理代码的核心逻辑是先为每个特征层生成anchor网格然后跟模型的偏移预测相加得到坐标最后合并所有层的候选框做NMS。我用的关键代码大致如下def decode_boxes(preds, anchors): # preds: 模型输出 [batch, num_anchors*4, h, w] # anchors: 预设 anchor 网格 boxes np.zeros_like(anchors) boxes[:, 0] anchors[:, 0] preds[:, 0] * 0.1 * anchors[:, 2] boxes[:, 1] anchors[:, 1] preds[:, 1] * 0.1 * anchors[:, 3] boxes[:, 2] anchors[:, 2] * np.exp(preds[:, 2] * 0.2) boxes[:, 3] anchors[:, 3] * np.exp(preds[:, 3] * 0.2) return boxes不同版本的RetinaFace在解码时的方差系数可能不同有的用0.1和0.2有的用1.0这个要看训练源码不能乱改。我的建议是把检测模型的锚点解码逻辑单独抽函数先用公开测试图片验证坐标是否正确再进入识别环节。NMS我用的是numpy手写版因为轻量且不依赖额外库。阈值方面分类置信度我习惯取0.5左右NMS的IoU阈值取0.4到0.5。如果你的场景人脸又小又多置信度阈值可以适当降低。关键的坐标还原也很容易踩坑。因为推理前我把原图resize到640x640所以解码出来的坐标都在640坐标系下要还原到原图必须记下resize的比例scale_x orig_w / 640 scale_y orig_h / 640 x1 int(box[0] * scale_x) y1 int(box[1] * scale_y) x2 int(box[2] * scale_x) y2 int(box[3] * scale_y)如果用了letterbox等比缩放加填充还原逻辑还要再加上偏移量。我当时图省事直接拉伸resize牺牲了一点宽高比但检测效果影响不大。4.3 关键点对齐与rec特征提取检测到人脸框之后不能直接把裁剪框扔给rec模型。因为人的头部会有倾斜和旋转直接裁出来的人脸角度不对识别精度会大幅下降。正确做法是利用检测出来的五个关键点做一次仿射变换把脸对齐到标准位置。标准模板用的是arcface的112x112对齐模板五个点的坐标如下REFERENCE_FACIAL_POINTS [ [38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041] ]对齐代码我封装成下面的函数def align_face(img, landmarks, output_size(112, 112)): import cv2 import numpy as np dst np.array(REFERENCE_FACIAL_POINTS, dtypenp.float32) src np.array(landmarks, dtypenp.float32) M, _ cv2.estimateAffinePartial2D(src, dst, methodcv2.LMEDS) aligned cv2.warpAffine(img, M, output_size, flagscv2.INTER_LINEAR) return aligned注意这里landmarks的顺序要跟训练模板一致通常是右眼、左眼、鼻尖、右嘴角、左嘴角。如果你的检测模型输出的关键点顺序不是这个需要先做索引重排否则对齐就错位了。对齐好的人脸图再经过RGB转换送进rec模型即可。rec模型推理代码同样封装成类输入之前记得做和转换时相同的预处理class FaceRecognizer: def __init__(self, model_path, core_maskRKNNLite.NPU_CORE_0_1_2): self.rknn RKNNLite() assert self.rknn.load_rknn(model_path) 0 assert self.rknn.init_runtime(core_maskcore_mask) 0 def get_embedding(self, aligned_bgr): rgb cv2.cvtColor(aligned_bgr, cv2.COLOR_BGR2RGB) rgb rgb.astype(np.float32) rgb (rgb - 127.5) / 127.5 rgb np.transpose(rgb, (2, 0, 1)) rgb np.expand_dims(rgb, axis0) outputs self.rknn.inference(inputs[rgb]) feat outputs[0].flatten() norm np.linalg.norm(feat) if norm 0: feat feat / norm return feat输出特征一定要做归一化。因为rec模型训练时就是用归一化后的特征向量计算余弦相似度的代码里顺手把归一化做了后面比对就只剩一个点积。4.4 5分钟跑通的最小可用系统把上面的零件串起来就得到了一套最小可用的识别系统。这里给一个完整的单文件示例我已经把注释写得比较细import cv2 import numpy as np import pickle from libs.detector import FaceDetector from libs.recognizer import FaceRecognizer from libs.align import align_face # 1. 加载模型 detector FaceDetector(models/rknn/retinaface.rknn) recognizer FaceRecognizer(models/rknn/w600k_r50.rknn) # 2. 加载特征库预先用 register.py 生成 with open(face_db.pkl, rb) as f: face_db pickle.load(f) # list of (name, feature) def recognize_name(feat, db, threshold0.45): best_name unknown best_score -1.0 for name, db_feat in db: score float(np.dot(feat, db_feat)) if score best_score: best_score score best_name name if best_score threshold: return unknown, best_score return best_name, best_score cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break # 3. 检测人脸 faces detector.detect(frame) # list of (box, landmarks) for box, landmarks in faces: x1, y1, x2, y2 [int(v) for v in box] # 4. 对齐并提取特征 aligned align_face(frame, landmarks) feat recognizer.get_embedding(aligned) # 5. 识别 name, score recognize_name(feat, face_db) # 6. 画框标名 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{name} {score:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(face recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这整套流程跑通如果仅仅是把代码运行起来在模型和特征库都准备好的前提下5分钟确实够了。不过首次从零开始要经历模型导出、转换、调参、建库这一套流程建议预留半天到一天的时间。我写这篇文章的目的一是给一个可以直接参考的完整示例二是帮大家把那几个最容易浪费时间的暗坑提前趟平。5. 性能实测与踩坑实录5.1 实测性能数据与瓶颈分析以下是我在香橙派5上跑这套系统的实测数据环境是Ubuntu 22.04、Python 3.10、RKNN Toolkit版本为1.6输入分辨率为640x640检测、112x112识别模型均为int8量化环节平均耗时毫秒说明图像预处理resize 归一化3~5numpy开销RetinaFace NPU推理15~25三核全开检测后处理解码NMS4~8纯numpy有优化空间单个人脸对齐rec推理8~15只在检测到人脸时才执行特征比对100人底库内1纯numpy点积整体来看画面中没有人脸时单帧耗时约25到35毫秒有人脸时加上识别环节约40到50毫秒大约20到28帧每秒。这个性能对门禁、考勤类应用已经足够但如果要跑实时视频监控就需要做一些优化了。瓶颈其实不在NPU而在Python侧的后处理层。RetinaFace的anchor数量比较多如果我用纯for循环去解码CPU占用会飙升。后来我把循环全部改成numpy矩阵运算耗时直接降到原来的三分之一。5.2 三板斧优化分辨率、多核、异步第一板斧是降低输入分辨率。检测模型的输入从640x640降到480x480NPU推理耗时能降低30%到40%但小尺寸人脸的检测率也会跟着下降。我的建议是先按640跑通确认系统稳定后再尝试降分辨率看效果能否接受。第二板斧是NPU多核全开。RK3588的NPU有三个核心默认可能只用了其中一个。初始化时通过core_mask参数可以指定使用哪几个核心我直接使用NPU_CORE_0_1_2。这一步几乎零成本推理速度能有明显提升尤其对多路并行任务友好。第三板斧是异步流水线。如果你需要同时处理摄像头采集、检测、识别可以用两个线程加两个queue一个线程只负责读帧和预处理另一个线程做推理和后处理把耗时隐藏起来。板子有四颗A76大核Python线程可以并行实测整体吞吐量能提高30%以上。另外一个小技巧是检测模型和rec模型可以同时加载在内存里但每次推理时尽量复用numpy缓冲区避免频繁分配大数组。这个优化对Python端的垃圾回收压力是个不小的缓解。5.3 常见问题速查表我在折腾这套系统的过程中踩了几个大坑周围朋友照着跑也踩了同样的坑整理成一个速查表现象可能原因解决办法load_rknn失败或初始化报错板端NPU驱动版本与rknn-toolkit-lite2不匹配用官方镜像自带的rknpu驱动并升级lite版本模型转换时报op不支持的错误onnx算子版本太高或RKNN-Toolkit2版本太旧升级RKNN-Toolkit2或尝试降低onnx opset版本推理输出全为0输入预处理mean/std配置错误或输入尺寸与模型不一致对应模型实际训练预处理检查输入shape检测框位置偏得离谱letterbox/缩放还原坐标没有换算正确检查坐标缩放比例和offset识别相似度普遍很低rec模型是float16被量化成int8导致掉点转float16或混合量化或者增加量化数据集识别误识别明显增多特征库质量参差或阈值设置过低提高余弦相似度阈值底库照片重新采集NPU推理速度比预期慢只用了单核或者输入分辨率过高初始化时开三核尝试降低输入分辨率摄像头调用失败权限问题或设备号错误检查/dev/video*并把用户加入video组5.4 量化掉点与阈值调参的独家经验量化掉点这个问题我单独拎出来说。rec模型对量化真的很敏感我第一次转int8版本时底库识别率从99%掉到80%出头肉眼可见的变差。后来我在量化数据集里加入了各种角度、各种光照下的人脸图片大概100张识别率回到了95%左右。如果你发现量化后精度还是不满意可以考虑转float16版本。RK3588的NPU对float16的支持也还可以推理速度虽然比int8慢一些但比CPU快得多精度几乎无损。我的建议是在需要严格保证识别准确率的场景里rec模型用float16检测模型用int8这样性能和精度能同时兼顾。阈值调整也要跟着模型精度走。int8版本下我把余弦相似度阈值从0.5降到0.42误识率和拒识率勉强平衡float16版本下阈值回到0.5也没问题。这个参数没有统一答案需要拿你自己的底库和现场图去试。6. 从工程落地到后续扩展6.1 从单帧推理到实时视频流前面演示的是最简单的单线程循环实际工程里不会这么干。接入实时视频流时我最常用的方案是双线程加队列采集线程持续读摄像头、做基础预处理把帧丢进一个有限长度的队列推理线程从队列里取帧做检测、识别和结果显示。如果帧率因为推理慢而掉帧队列能起到缓冲作用不至于把系统拖死。摄像头选型方面普通USB摄像头接上就能用cv2.VideoCapture(0)直接搞定。如果接MIPI CS摄像头就不能用OpenCV直接读了需要走Rockchip的rkmpp或rkcam接口代码复杂度会高一个台阶。首次调试建议先用USB摄像头把逻辑调通。6.2 特征库管理与检索特征库的存储方式取决于规模。如果就几十个人一个pkl文件存字符串加numpy数组就够了加载进内存后直接线性比对。如果扩展到几百上千人线性扫描的耗时虽然还在可接受范围但代码里依然建议按ID建立索引避免反复遍历整个列表。我实际用下来识别环节“注册新用户”的流程一定要单独写脚本不要跟识别主程序耦合。注册时用同一套检测、对齐、提特征逻辑保证跟识别时的特征分布一致。如果注册时用的是自拍大头贴识别时现场摄像头角度不同特征差异会非常大这是我踩过的一个很典型的坑。特征库更新也需要考虑。人员离职、新增、照片重拍这些操作都应该只改库文件不用重启主程序。我用的是pickle加一个小的更新函数每次保存时写临时文件再rename避免写一半断电导致整个库损坏。6.3 个人踩坑体会与最后建议玩这套系统最深刻的体会是边缘AI部署真正花时间的从来不是模型训练而是模型转换、后处理对齐和量化调试这一连串工程问题。RetinaFace加rec这套组合本身很稳定只要预处理参数对齐、anchor解码对路RK3588的NPU是完全能扛得住的。我建议所有想上手的同学拿到板子后先不要一上来就搞识别老老实实先把检测跑通能够稳定框出人脸并输出关键点再往下走半段路。检测阶段如果都能看到关键点稳稳搭在眼睛鼻子上后面的对齐和识别就是顺理成章的事了。要是跳步图快最后检出的框不对、特征提取结果一团乱排查起来反而更浪费时间。最后分享一个小技巧转换模型之前先用RKNN-Toolkit2自带的simulator在原PC上跑一遍onnx的推理结果跟PyTorch的原始输出对比一下确认预处理和模型本身没毛病再烧到板子上验证。这个习惯帮我省掉了大量在板端来回调试的折腾。香橙派这类国产开发板上手初期总会让人血压升高但一旦摸清了工具链的脾气后面跑什么模型都会顺手很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进