人脸检测模型部署实战
本文档根据《嘉楠K230开发手册》V1.0(2024-11-30)整理。正文、表格、示例代码与插图均来自原始手册。
针对Pytorch,基于K230的AI开发流程由训练和部署两个部分组成,其中训练包括Pytorch训练模型,部署包 括PyTorch到ONNX转换、使用ONNXRuntime进行推理、ONNX到kmodel转换、使用K230Runtime进行推理。
- PyTorch训练模型:
使用PyTorch框架定义并训练人脸检测和人脸识别模型。训练完成后,保存模型参数到.pth文件。
- PyTorch到ONNX转换:
利用PyTorch工具,将训练好的模型转换为ONNX格式。这一步会针对网络结构进行一些优化。
- 使用ONNXRuntime进行推理:
在PC上加载ONNX模型,并利用ONNXRuntime进行推理,以验证onnx模型的正确性和性能。ONNXRuntime推理的主要流程包括预处理、运行、后处理。
- ONNX到kmodel转换:
利用K230支持的转换工具,将ONNX模型转换为K230可用的kmodel格式。这一步会优化模型以适应K230,生成在K230上能高效运行的模型。
- 使用K230Runtime进行推理:
在K230上加载kmodel,使用K230Runtime进行推理。这确保模型在K230的运行效果。K230Runtime推理的主要流程包括预处理、运行、后处理。

整个流程通过将PyTorch模型经由ONNX中间格式,最终优化为适合K230的kmodel格式,实现了从PC端到K230的无缝部署。从pth/ckpt->onnx->kmodel,模型文件有3种文件格式,各种文件格式推理流程一一对应,因此转换完成后,我们需要在对应的推理流程下,验证转换模型的准确性。
#运行环境:常规pc环境
├── onnx_related
├── onnx_export #导出onnx
│ ├── face_detection_convert_to_onnx.py #对应3.1.1
│ ├── face_recognition_convert_to_onnx.py
│ └── readme.txt
└── onnx_inference #onnx推理流程
├── face_detection #人脸检测onnx推理流程 #对应3.1.2
└── face_recognition #人脸识别onnx推理流程
# 编译环境:k230的编译环境,运行环境:K230开发板
├── kmodel_related
│ ├── kmodel_export #导出kmodel,
│ │ ├── build_model.sh #生成kmodel脚本
│ │ ├── face_detection #对应3.1.3+3.1.4.1(两者都是python写的,为了写起来简单,把两者放在一起,逻辑上放到3.1.4更好)
│ │ ├── face_recognition
│ │ ├── k230_kmodel #生成kmodel
│ │ └── k230_utils #生成其它辅助文件,bin、图片等
│ └── kmodel_inference #kmodel推理
│ ├── build_app.sh #生成可执行文件脚本
│ ├── cmake
│ ├── CMakeLists.txt
│ ├── face_detection #人脸检测kmodel推理流程,对应3.1.4.2+3.1.4.3(两者都是c++写的)
│ ├── face_recognition #人脸识别kmodel推理流程
│ ├── k230_bin #生成的可执行文件、kmodel,上板执行脚本等
│ ├── main_nncase #kmodel上板验证工具
│ ├── shell
│ └── test_demo
人脸检测是指对于任意一幅给定的图像,采用一定的策略对其进行搜索以确定其中是否含有人脸,如果有则返回人脸检测框、五官关键点。参考人脸检测源码链接: https://github.com/biubug6/Pytorch_Retinaface

PyTorch到ONNX转换
- 模型转换流程
选择人脸检测模型时,一般应选择轻量化的模型,backbone一般小于resnet50参数量较好。因此我们选择基于MobileNetV1的RetinaFace 作为人脸检测模型。
- 加载pth或ckpt模型到cpu
- 构建随机模型输入
- 导出onnx模型
**注:**pth、onnx都支持动态输入,而K230的模型暂时不支持动态输入,所以导出onnx时,onnx输入shape固定。

- 模型转换执行步骤
- 在Ubuntu中新建终端,并激活conda的人脸相关环境
conda activate py39_mobilenet
- 进入人脸检测源码目录
cd k230_sdk/src/reference/Pytorch_Retinaface/
执行效果:

- 拷贝ONNX转换程序至当前目录
cp ../K230_AI_Demo_Development_Process_Analysis/onnx_related/onnx_export/face_detection_convert_to_onnx.py .

可根据Pytorch_Retinaface说明文档下载预训练模型,模型文件位于weights目录下。
- 执行转换程序
python face_detection_convert_to_onnx.py
执行完成后即可在当前目录看到生成的FaceDetector.onnx模型文件。执行效果如下所示:
使用ONNXRuntime进行推理
为了验证onnx正确性,我们需要使用ONNXRuntime对onnx进行推理,推理时保证读取图片、预处理、run、后处理、显示结果与pth/ckpt的推理流程一致。

- 读取图像

#ori_img(1024,624,3),opencv读入图片的默认格式为hwc,bgr
ori_img = cv2.imread('bin/test.jpg')
- 图像预处理
预处理构建(常用的方法:padding_resize,crop_resize,resize,affine、normalization):参考train.py,test.py、predict.py、现成的onnx推理脚本。
构建人脸检测预处理代码:

#face_detector.py
def pre_process(self,ori_img):
max_ori_img = max(ori_img.shape[1], ori_img.shape[0])
self.scale = [max_ori_img] * 4
self.scale1 = [max_ori_img] * 10
# (1) padding:将原图padding为正方形,pad_img(1024,1024,3)
pad_img = pad_to_square(ori_img,self.normalize_mean,True)
# (2) resize+tranpose+normalization:将padding之后的图像缩放到640,hwc转chw,并归一化,resize_img(3,640,640)
resize_img = resize_subtract_mean(pad_img,self.in_size,self.normalize_mean)
# (3) dequantize:将缩放的图像转换为float32,resize_img_float(3,640,640)
resize_img_float = np.float32(resize_img)
#(4)3维扩张为4维:input_data(1,3,640,640)
input_data = np.expand_dims(resize_img_float, 0)
return input_data
参考:(与pth预处理流程一致)人脸检测预处理代码参考train.py(k230模型的输入shape目前只支持固定输入,训练时都是批量固定输入的,因此可以借鉴)中调用的预处理,去掉不适合推理使用的crop、distort、mirror(数据增强),只留下onnx推理时必要的pad_to_square、resize_subact_mean处理,保证onnx与pth预处理一致。

- onnx推理
将预处理好的数据,喂给模型,得到onnx推理结果
#onnx_model.py
def forward(self, image_tensor):
'''
image_tensor = image.transpose(2, 0, 1)
image_tensor = image_tensor[np.newaxis, :]
onnx_session.run([output_name], {input_name: x})
:param image_tensor:
:return:
'''
input_feed = self.get_input_feed(image_tensor)
output = self.sess.run(self.out_names, input_feed=input_feed)
return output
#face_detector.py
loc,conf,landms = self.model.forward(input_data)
3.1后处理
后处理构建(常用的方法:softmax、loc解码、nms等):参考test.py或predict.py等测试脚本、现成的onnx推理脚本。
构建人脸检测后处理代码:包括解码、nms等,由于后处理较多,我们只截取部分代码进行说明,具体实现参考K230_AI_Demo_Development_Process_Analysis。
#face_detector.py
def post_process(self,loc,conf,landms):
loc, conf, landms = loc[0],conf[0],landms[0]
boxes = decode(loc, self.priors_numpy, self.cfg['variance'])
boxes = boxes * self.scale / 1 #右、下padding
......
**参考:**人脸检测repo的detect.py,对模型输入结果:loc(检测框)、conf(得分)、landms(关键点)进行后处理,进而得到人脸检测框、得分、五官点。

- 显示结果
显示结果:将后处理之后的结果画到原图,执行人脸检测推理流程,若是检测效果正确,则说明使用ONNXRuntime推理人脸检测的流程是正确的,转换的onnx也是正确的。
- 执行步骤
① 在Ubuntu中新建终端,并激活人脸相关环境,若已激活,请忽略此步骤
conda activate py39_mobilenet
② 进入ONNX模型推理源码目录
cd k230_sdk/src/reference/K230_AI_Demo_Development_Process_Analysis/onnx_related/onnx_inference/face_detection
③ 拷贝转换后ONNX模型至onnx文件夹中
cp ../../../../Pytorch_Retinaface/FaceDetector.onnx onnx/

④执行ONNX推理程序
python face_detector.py
执行效果图如下所示:
如果想退出显示,请按下q键退出。
ONNX到kmodel转换
- Kmodel模型转换源码解析
人脸检测onnx模型经过nncase编译之后,可以生成在k230上推理的模型kmodel,生成kmodel需要调用nncase的编译模型APIs(Python)。
1.1 配置生成kmodel参数
编译参数包括编译目标参数、预处理参数、后处理参数,编译目标参数指定编译目标, 如’cpu’, ‘k230’;常用预处理参数由Transpose参数、SwapRB参数、Dequantize参数、Normalization参数构成;后处理参数目前只支持Transpose参数。
| 参数类别 | 参数名称 |
|---|---|
| 编译目标参数 | target |
| 预处理参数 | input_shape、input_layout、 swapRB、input_type、input_range、mean、std等 |
| 后处理参数 | output_layout |
编译目标参数:
# 指定编译目标, 如'cpu', 'k230'
compile_options.target = args.target
- target = “cpu”,生成cpu上推理的kmodel,此时不进行量化;
- target = “k230”,生成在k230(kpu)上推理的kmodel,此时模型进行量化(默认uint8量化);
预处理参数:由于预处理参数比较复杂,接下来我们着重介绍下常用预处理参数。
# 是否开启前处理,默认为False
compile_options.preprocess = True
- 预处理参数(preprocess = False时,不进行任何预处理,kmodel ≈ onnx)
- 预处理参数(preprocess = True时,kmodel ≈ 预处理 + onnx,此时kmodel包含设置的预处理,这些预处理会在KPU计算,KPU计算较快,因此最好将尽可能多预处理放到kmodel上)
| 预处理操作类型 | 相关参数 |
|---|---|
| Transpose | input_shape、input_layout |
| SwapRB | swapRB |
| Dequantize | input_type、input_range |
| Normalization | mean、std |
【onnx输入数据】的格式决定了【新的输入】的格式;
【kmodel实际输入】的格式决定了【kmodel输入】的格式;

- Transpose参数:
# 指定输入数据的shape,input_shape的layout需要与input layout保持一致
compile_options.input_shape = [1, 3, 640, 640]
# 当按照字符串(`"NHWC"`、`"NCHW"`)形式配置 `input_layout`时,表示新的输入数据的layout
compile_options.input_layout = "NCHW"
相关参数:
- input_shape:输入数据的shape,input_shape的layout需要与“input_layout”保持一致;当 preprocess为 True时,必须指定。
- input_layout:支持字符串("NHWC"、"NCHW")和index。当按照字符串("NHWC"、"NCHW")形式配置 “input_layout”时,表示新的输入数据的layout;当按照index形式配置 “input_layout”时,表示输入数据会按照当前配置的 “input_layout”进行数据转置,即 “input_layout”为 “Transpose”的 “perm”参数;当 preprocess为 True时,必须指定。
- 分析说明(以字符串配置格式为例):
新的输入layout与input_layout一致;新的输入layout与onnx输入layout一致;因此input_layout与onnx输入layout一致;
当input_layout与kmodel输入layout一致时,kmodel输入经过transpose之后,生成的新的输入仍是与kmodel输入layout一致;
当input_layout与kmodel输入layout不一致时,kmodel输入经过transpose之后,变成与input_layout一致的新的输入。