从标题就开始说大实话:5分钟,不是从拆快递开始算,也不是从给香橙派5刷系统开始算,而是指你已经把环境和模型文件准备妥当之后,从落地代码到摄像头前第一次成功识别,真实时间就是几分钟的量级。这篇文章拿香橙派5(RK3588)跑一套最简人脸识别系统,检测用RetinaFace,识别用insightface工具包里的rec模块,两个角色明确、分工干脆,解决的问题只有一个:“摄像头前出现一个人,他是谁,是不是我库里的人”。
这套方案不用训练自己的模型,不用搭客户端服务端,代码量也压得足够低,适合刚拿到RK3588开发板、想把“人脸识别”从PPT变成真实可用功能的同学参考,也适合已经跑通过Demo、想搞清楚RetinaFace和rec各自在系统里承担什么任务的读者。我会把从安装依赖到注册人脸、再到现场比对识别、最后调优的几个层次全部走一遍,所有代码贴在下面,直接能抄。
1. 项目概述与系统拆解
1.1 为什么选香橙派5 + RK3588 跑人脸识别
香橙派5这个系列最近在嵌入式圈子里讨论度很高,核心原因是它把RK3588这颗SoC的价格打了下来。RK3588的CPU部分是4核Cortex-A76加4核Cortex-A55,大核主频能到2.4GHz左右,这个算力跑传统的人脸检测识别链路完全够用。更关键的是它自带一个6 TOPS算力的NPU,虽然本文的“5分钟快速方案”先用CPU顶着跑,但后续如果要上NPU加速,同一个模型文件还有明确的升级路径,这点后文会专门讲。
相比之下,用树莓派4B跑同类方案,CPU推理一帧640分辨率的人脸检测,经常需要等上一两秒,而RK3588的A76大核跑onnxruntime,体感会流畅很多。实测在默认的640x640检测尺寸下,RetinaFace加rec特征提取的完整链路大约在2到4帧每秒,这已经足够做门禁验证、会员识别这类低频交互场景了。如果你只是想在开发板上拉起一个“能用的”人脸识别系统,RK3588是当前性价比和扩展性都比较均衡的选择。
1.2 RetinaFace和rec在系统里的分工
很多人看人脸识别项目会混淆检测和识别,其实这是两个完全不同的子任务,在工程上通常拆成两步。
RetinaFace负责的是“检测”,它在图像里把所有可能的人脸框出来,输出人脸边界框、关键点坐标、置信度这些信息。它不关心这个人是谁,只负责回答“哪里有人脸”。如果把整套系统比作一个安检流程,RetinaFace就是门口那位负责叫住每个来访者的保安:先确定有人进来了,而且把位置指出来。
rec模块负责的是“识别”,它接收RetinaFace裁出来的人脸区域,通过卷积骨干网络把这张脸压缩成一个固定维度的特征向量,也就是embedding。在insightface的模型包结构里,rec是recognition的缩写,buffalo_l这个预训练包里面实际包含det、rec、genderage、2d106、3d68几个子模块,rec就是专门负责特征提取的那一个。识别环节做的事情是:提取特征向量,再和你注册时保存的特征向量做相似度比对,相似度超过阈值,就判定是同一个人。
这两个模块合在一起,才算一个完整的人脸识别系统。单独用RetinaFace只能“找到脸”,单独用rec没有检测器给输入,也跑不起来。这也是我选择直接基于insightface的FaceAnalysis接口来做原因——它把检测和识别包成了一个统一对象,调用方不用关心内部先跑谁后跑谁,只要拿一张图进去,就能拿到人脸框、关键点、特征向量一套结果。
1.3 系统整体架构与“5分钟”的时间构成
整套系统的数据流非常简单:摄像头采集视频帧,送进FaceAnalysis对象,得到当前画面中的人脸列表;对每一张人脸,取出normed_embedding特征向量,和本地预存的JSON数据库做余弦相似度比对;得分最高的记录超过设定阈值,就认定身份匹配成功,在画面里画框标注姓名。
“5分钟”这个时间承诺,我拆成三块:第一块是代码编写,核心实现其实只有几十行,在我下面的示例里直接复制粘贴即可;第二块是初始化模型,首次运行FaceAnalysis时,程序会在用户目录下自动下载或加载buffalo_l模型包,这个过程取决于网络状况,一般33MB左右,网络好的话半分钟搞定;第三块是现场调试摄像头编号和检测阈值,这部分看运气,很多板子默认摄像头索引不是0,试一次就知道。这三块加起来,确实就是五分钟上下的体感时间。系统镜像烧录和模型手动下载这种一次性工作不计入这五分钟。
2. 环境准备:系统、依赖、模型文件
2.1 系统镜像烧录与基础设置
在香橙派5上建议直接使用官方提供的Ubuntu镜像,官方有桌面版和服务器版两种,我自己用的是服务器版,因为没有图形界面的牵绊,每次开机内存占用能省下不少。RK3588比较新的镜像已经适配到了较新内核,对USB摄像头、HDMI输出、NPU驱动的支持都比较完整,尽量别用那些来路不明的第三方精简镜像,省下后面排查驱动的时间。
烧录工具方面,Windows下用balenaEtcher或者官方配套的烧录软件,Linux下用dd命令直接写卡就行。需要注意一下,不要使用TF卡 + 读卡器组合时反复插拔导致的写入碎片问题。SD卡质量会影响系统稳定性,建议选至少A2速度等级的卡,实测同样环境下,垃圾卡和好卡在跑人脸识别时帧率差距并不大,但系统日志里I/O错误出现的概率会明显不同。烧录完开机,正常会看到串口或者通过HDMI接显示器能进到控制台,Ubuntu镜像默认账号一般会在官方文档里注明,登录后第一件事是执行apt update与apt upgrade。
如果想要无线连接或者查看局域网地址,连上网络之后用ip addr确认板子的IP,然后ssh进去操作。后续所有安装步骤都在ssh终端里进行,这是开发板最省事的模式。
2.2 Python环境与依赖安装
香橙派5官方Ubuntu镜像自带Python3,一般版本在3.8到3.10之间,够用。但在安装pip包之前,我建议先把基础编译链路补齐,防止后面装某些带C扩展的依赖包时直接编译报错:
sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-dev build-essential cmake libopencv-dev然后升级pip,并安装核心依赖:
pip install --upgrade pip setuptools wheel pip install numpy opencv-python onnxruntime这里有一个容易踩的坑:在ARM架构下,onnxruntime的安装会自己拉取对应的aarch64版本,绝大多数情况直接pip install就能成功。如果遇到pip死活找不到匹配版本的情况,可以先卸载重新装再试试。opencv-python装好后,在终端里执行python3 -c "import cv2; print(cv2.version)"验证一下,只要不报错,这一步就算过了。
接下来是重头戏,安装insightface:
pip install insightfaceinsightface在安装时会自动拉取onnx、albumentations、prettytable、requests等依赖。albumentations这个包偶尔会要求比较新的numpy版本,如果你前面已经装了numpy并且版本偏低,可以先单独升级一下numpy再回来装insightface,能省下不少报错时间。
2.3 模型文件准备:buffalo_l 包说明
insightface的FaceAnalysis在初始化时需要一个模型包名,常见的有buffalo_l、buffalo_s、buffalo_sc等。buffalo_l是综合表现最好的一个,包含检测、识别、还有两个关键点模型和性别年龄分类器。我们项目只需要detection和recognition,所以初始化时可以用allowed_modules参数限制加载范围,减少首次运行时的加载时间。
模型文件的保存位置默认在用户目录下的.insightface/models/文件夹。如果你所在网络环境访问GitHub不稳定,或者首次运行时模型自动下载失败,可以直接手动下载buffalo_l的zip包,解压后放到该目录下。建议直接把这个步骤当成标准操作,别依赖自动下载。
mkdir -p ~/.insightface/models cd ~/.insightface/models # 将buffalo_l.zip上传到此处,然后解压 unzip buffalo_l.zip目录结构大致如下:
~/.insightface/models/buffalo_l/ ├── det_10g.onnx ├── genderage.onnx ├── rec.w600k_r50.onnx ├── 2d106det.onnx └── 3d68.onnx上面文件里的det_10g就是RetinaFace检测模型,rec.w600k_r50是识别用的backbone。这两个文件名在你后续做NPU转换时也会用到,我先在这里标记一下。
验证模型是否放对位置的代码很简单:
import insightface from insightface.app import FaceAnalysis app = FaceAnalysis(name='buffalo_l', allowed_modules=['detection', 'recognition'], providers=['CPUExecutionProvider']) app.prepare(ctx_id=0, det_size=(640, 640)) print("模型初始化成功")能打印出“模型初始化成功”,环境准备就算彻底完成了。
3. 核心代码实现:检测、识别、注册一条龙
3.1 人脸检测模块(RetinaFace)的使用要点
我们把检测和识别封装在同一个FaceAnalysis对象里,实际使用中只需要调用app.get(img)这一个方法。传入的图像要求是BGR格式的numpy数组,OpenCV读出来的图像就是BGR顺序,直接传即可,不需要转成RGB。这一步经常有人搞错,转成RGB之后虽然图还是能出结果,但在关键点坐标映射的时候容易出偏差。
get方法返回的是一个Face对象的列表,每个Face对象里最常用的属性是bbox、kps、det_score和normed_embedding。bbox是四个人脸框坐标,kps是五个关键点坐标,det_score是检测置信度,normed_embedding是L2归一化后的特征向量,后面做相似度比对直接用这个就行。
官方FaceAnalysis默认的参数已经经过大量调优,直接使用通常可以获得很好的效果。如果你想针对RK3588的CPU性能做一些权衡,可以在prepare时调整det_size。更大的det_size会提高对小脸的检测能力,但推理耗时也会上升;更小的det_size比如320x320,检测速度快不少,但稍微远一点的人脸就可能检测不到。门禁闸机类场景,det_size=640是个比较中庸的选择。
3.2 人脸特征提取与相似度计算(rec)
rec模块在本项目里表现为Face对象里的normed_embedding属性。它是一串长度512的浮点型数组,代表一张人脸的“数学指纹”。两段特征向量是否来自同一个人,最常用的判断标准是余弦相似度,也就是计算两个向量的点积,因为normed_embedding已经做过归一化处理,点积结果直接就是余弦相似度,范围在-1到1之间。
在识别的工程实践中,阈值设定直接影响误识率和拒识率。常见做法是先设0.5作为初始阈值,然后在真实场景里采集几十个正样本和负样本调优。如果误识别情况多,把阈值调到0.55甚至0.6;如果经常把同一个人拒之门外,就适当降低到0.45。我用buffalo_l模型在室内光照环境下测下来,0.5到0.55这个区间相对均衡,你可以把它当起点往下调。
下面给出一段通用的人脸特征提取与比对代码:
import numpy as np import json import cv2 def extract_embedding(app, image_bgr): faces = app.get(image_bgr) if len(faces) == 0: return None return faces[0].normed_embedding def match_face(query_emb, db_embeddings, threshold=0.5): best_name = None best_score = -1.0 for name, emb in db_embeddings.items(): score = float(np.dot(query_emb, np.array(emb))) if score > best_score: best_score = score best_name = name if best_score >= threshold: return best_name, best_score return None, best_score注意这里查询时只取了faces[0],也就是画面里检测到的第一张人脸。如果场景里有同时出现多人的需求,要改成一个for循环遍历faces列表。
3.3 人脸数据库的注册与保存
识别系统需要一个“熟人库”,这个库在我们项目里就是一份Json文件。每张人脸注册的时候,把名字和512维特征向量存进去。Json文件本身具备可读性,调试的时候打开看一眼就知道数据格式对不对,比传统的二进制的存储方式更适合快速验证。
下面是注册人脸入库的完整代码,可以从摄像头拍照注册,也可以从图片文件注册:
import os import json import cv2 import numpy as np FACE_DB = "face_db.json" def load_db(): if os.path.exists(FACE_DB): with open(FACE_DB, "r") as f: return json.load(f) return {} def save_db(db): with open(FACE_DB, "w") as f: json.dump(db, f) def register_from_image(app, name, image_path): img = cv2.imread(image_path) if img is None: print("读取图片失败:", image_path) return False face = app.get(img) if len(face) == 0: print("图像中未检测到人脸") return False emb = face[0].normed_embedding.tolist() db = load_db() db[name] = emb save_db(db) print("已注册用户:", name, "特征维度:", len(emb)) return True def register_from_camera(app, name, camera_id=0): cap = cv2.VideoCapture(camera_id) if not cap.isOpened(): print("无法打开摄像头") return False ret, frame = cap.read() cap.release() if not ret: print("摄像头采集失败") return False face = app.get(frame) if len(face) == 0: print("画面中未检测到人脸,请调整位置后重试") return False emb = face[0].normed_embedding.tolist() db = load_db() db[name] = emb save_db(db) print("已注册用户:", name) return True注册的时候最好保证人脸在画面中央,并且正对摄像头,不要有大的侧脸或者遮挡。单张照片注册的特征向量鲁棒性一般,条件允许的话可以拍多张不同角度的照片,对同一个名字存多个特征向量,识别的时候分别比对取最高得分,这个技巧在实际部署里非常管用。
3.4 主识别循环与完整示例
当注册库准备好之后,启动识别系统的主循环就非常简单了。从OpenCV读取摄像头画面,每帧调用app.get,然后遍历检测到的人脸,每张脸取特征向量和fa_db比对,将结果画到画面里。完整代码如下:
import cv2 import numpy as np from insightface.app import FaceAnalysis FACE_DB = "face_db.json" def load_db(): import os, json if os.path.exists(FACE_DB): with open(FACE_DB, "r") as f: return json.load(f) return {} def main(): app = FaceAnalysis( name='buffalo_l', allowed_modules=['detection', 'recognition'], providers=['CPUExecutionProvider'] ) app.prepare(ctx_id=0, det_size=(640, 640)) db = load_db() print("已加载人脸库,人数:", len(db)) cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) if not cap.isOpened(): print("无法打开摄像头") return while True: ret, frame = cap.read() if not ret: break faces = app.get(frame) for face in faces: emb = face.normed_embedding best_name = "陌生人" best_score = -1.0 for name, saved_emb in db.items(): score = float(np.dot(emb, np.array(saved_emb))) if score > best_score: best_score = score best_name = name if best_score < 0.5: best_name = "陌生人" bbox = face.bbox.astype(int) x1, y1, x2, y2 = bbox cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) label = f"{best_name} ({best_score:.2f})" cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("Face Recognition", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == "__main__": main()这段代码已经是一套“最小可用”的人脸识别系统。启动后摄像头画面会实时显示每张人脸的框和名字,按q键退出。新手建议先跑通这个循环,再加日志、数据库、远程控制等额外功能,避免一开始就把复杂度堆起来。
4. 部署优化:NPU加速、摄像头接入与RTSP推流
4.1 RK3588 NPU加速:从ONNX到RKNN
前面主流程是基于CPU推理的,虽然能够正常工作,但在多路视频或者高帧率场景下CPU占用会偏高。RK3588最大的卖点是NPU,如果想要把人脸识别链路压到NPU上,需要把ONNX模型转成RKNN格式。这个过程需要额外一台x86主机,安装RKNN-Toolkit2工具包,然后在主机上完成模型转换,再把生成的rknn文件放到香橙派5上通过RKNN Runtime加载。
实际转换时只需要转换det_10g.onnx和rec.w600k_r50.onnx这两个文件,因为其他模块在这个项目里根本用不到。转换脚本的核心逻辑大致是:初始化RKNN配置对象,指定平台为rk3588,加载ONNX模型,设置输入尺寸,然后导出为rknn文件。这里有几个容易出问题的点:首先RetinaFace模型的输入是有动态batch维度的,转换前最好固定batch为1;其次模型里如果包含一些不支持的算子,比如某些自定义上采样操作,需要先简化onnx结构,或者改用RKNN-Toolkit2自带的一些预编译模型。
NPU加速的收益相当明显。CPU上跑一帧640x640检测加识别大约需要300到500毫秒,而NPU跑INT8量化后的模型,单纯推理时间能压缩到几十毫秒量级,整体帧率可以提升到十几帧每秒。不过量化的代价是精度会有一点损失,如果识别阈值卡得比较紧,建议在量化后重新标定一下阈值参数。
4.2 USB摄像头适配与RTSP推流
摄像头接入这件事看似简单,在开发板上却很容易卡十分钟。香橙派5的USB口识别常规UVC摄像头基本没压力,插入后执行ls /dev/video*,通常会出现video0,有的复合设备还会有video1或video2。如果OpenCV打开默认索引0失败,可以试着把VideoCapture参数改成1或者2。
从设备节点到RTSP流是另一个常见的需求。RK3588平台经常有人想把摄像头画面转成RTSP,给其他设备拉流。要高效编码,需要使用rk3588平台适配过的ffmpeg,它自带rkmpp硬件编码器,能用NPU旁边的VPU硬件单元做H.264编码,CPU占用极低。执行ffmpeg -encoders | grep rkmpp即可确认自己的ffmpeg是否支持rkmpp编码器,如果不支持,需要安装瑞芯微维护的ffmpeg分支。
基础的推流命令如下:
ffmpeg -f v4l2 -i /dev/video0 -c:v h264_rkmpp -b:v 2M -f rtsp rtsp://0.0.0.0:8554/live这样就把本地USB摄像头转成了RTSP流,其他设备用VLC或者ffmpeg拉流就能看到实时画面。更进一步,可以把识别结果画面叠加上人脸框后再推流,相当于一个带AI分析的监控摄像头流,不过这条路需要自己写编码pipeline,复杂度会高不少。
4.3 性能实测与参数调节
我在香橙派5上跑这套代码,几组实测参考数据如下(室内光照、单目USB摄像头、640x480输入):
| 配置 | CPU占用 | 帧率 | 备注 |
|---|---|---|---|
| buffulo_l默认640 det_size | 约70% | 2-3 FPS | 稳定识别无压力 |
| det_size降到320 | 约45% | 4-5 FPS | 小脸检测率下降 |
| 仅检测不识别 | 约30% | 8-10 FPS | 特征提取比较耗时 |
| NPU量化后双模型 | 约20% | 10-15 FPS | 需要额外转换工作量 |
从数据能看出来,rec特征提取环节在CPU上的耗时占比不小。如果只是需要一个“检测到有人”的提醒功能,可以把识别部分关掉,只跑RetinaFace,帧率提升明显。如果必须要实时人脸识别,最省事的优化方向是降低输入分辨率,比如把摄像头采集和det_size都调到320,而不是硬上NPU。对于现阶段大部分场景,2到3帧的识别速度已经足够用,一帧大约花300多毫秒,给人感觉是轻微延迟但可接受。
5. 常见问题与排错记录
5.1 依赖安装与import报错
问题A:pip install insightface时报错,提示编译某个依赖失败。
这个问题在ARM开发板上最常见的原因是缺少编译工具链和Python头文件。解决办法就一条,确保先执行了前面的apt install命令,尤其是build-essential和python3-dev。另外不要在conda环境里折腾,直接用系统的Python3环境最省事。
问题B:import insightface后调用FaceAnalysis时,提示No match found for buffalo_l。
这种情况绝大多数是模型文件没放到正确路径。确认~/.insightface/models/buffalo_l目录存在,并且里面至少有det_10g.onnx和rec.w600k_r50.onnx两个文件。如果是从Windows上传的压缩包,要注意解压时不要多套一层buffalo_l目录。
问题C:代码报错提示onnxruntime版本和onnx版本不兼容。
insightface对onnxruntime的版本要求不算苛刻,但如果之前手动装过较老版本的onnx,冲突概率会增加。处理方式是统一升级:
pip install --upgrade onnx onnxruntime5.2 摄像头不出图
打开摄像头失败是开发板上演出率最高的坑。先排查设备节点,执行ls /dev/video*,如果没有任何输出,说明系统没有识别到摄像头,检查USB线或者换个USB口试试。如果video设备存在,但cv2.VideoCapture依然失败,要么是权限问题,要么是索引不对。把代码里的摄像头索引从0依次换成1、2测试,或者直接写个小循环打印所有可用的video设备。
还可以用v4l2-ctl工具查看设备能力:
sudo apt install v4l-utils v4l2-ctl --list-devices这个命令会列出每个摄像头的详细信息,包括支持的格式和分辨率,方便确认摄像头是否处于可用状态。
5.3 识别率低与误判
如果你发现系统把自己人脸都识别成了“陌生人”,或者经常把两个人搞混,优先检查几个环节。第一,注册照片的质量,模糊、背光、侧脸的照片注册进库,识别效果会非常差。第二,摄像头分辨率是否太低,如果画面里的人脸只占几十个像素,检测都费劲,更别说提取有效特征了。第三,阈值是否合理,可以先把阈值降到0.3,打印出实际比对得分,看看正常匹配时的得分大概是多少,再据此调阈值。
误匹配的问题也值得留意,有些场景下两个人的特征相似度天然偏高,比如亲兄弟。这种问题单靠调阈值很难彻底解决,更实用的办法是给同一个人注册多个角度的特征,比对时以最高得分为准,相当于用多张样本来刻画一个人的特征分布。
6. 踩坑心得与扩展建议
整套系统跑下来,我个人最大的体会是:在RK3588平台上做AI应用,最难的不是算法,而是工程链路。模型选型、环境依赖、摄像头适配、权限配置,任何一环出问题都会让体验变得支离破碎。强烈建议在动手前先把系统镜像、模型文件这些静态准备一次性做好,然后把软件安装和代码调试当成一条独立流水线来处理,这样后面真正调试代码的时候才会顺畅。
从扩展角度说,这套基于insightface的人脸识别方案还有几个升级方向值得尝试。第一个方向是接入RKNN加速,把RetinaFace和rec模型都转成RKNN格式,帧率提升会非常明显;第二个方向是给系统加一个简单的Web管理界面,用Flask提供注册接口和识别记录查询,这样手机浏览器就能直接管理门禁设备;第三个方向是结合RK3588的硬件编解码能力,把识别结果画面推成RTSP流,做成一个真正的边缘AI摄像头。另外,同一块板子如果后续想跑YOLOv8目标检测,利用的也是当前这套RKNN转换和NPU部署思路,等于这次先把人脸识别链路走通,后面迁移到其他模型就水到渠成了。
如果你在照着步骤操作的过程中卡在某个环节,不妨把报错信息原样贴到搜索引擎里,RK3588现在用户群体很大,大多数问题都能找到前人踩坑后的解决办法。对于想快速在嵌入式设备上落地人脸识别的朋友,希望这篇文章能帮你把从零到一的路程缩短一些。