MSPN50
1. 摘要
-
身份编号:fwb0042
-
类型与序号:MSPN50 方案1
-
进展:
item status detail 方案选择与硬件资源划分 1 模型导出 1 ~导出模型精度一致,提供python验证脚本 1 cpu处理部分c++工程移植 1 ~c++工程精度一致,提供验证脚本 1 ~c++工程性能优化 1 icraft编译通过 1 ~存在量化精度调优工作 1 ~完成基本pyrtcrt搭建与结果验证 1 ~完成pyrtcrt精度测试与对齐 1 完成上板耗时测试 1 ~使用运行时提供的api进行基本性能优化 1 ~存在整体方案各环节优化或优化方案的设计 1 提出bug数 0
2. 方案简介
2.1 算法流程介绍
模型或算法背景:
- MSPN(Multi-Stage Pose Network)是一种用于高精度多人人体姿态估计的框架,它通过多阶段、由粗到精的级联方式来逐步提升关键点定位的准确性。
- 源仓库链接:https://github.com/open-mmlab/mmpose (作为MMPOSE项目的一部分)
- 开发团队:微软亚洲研究院 (MSRA) (文方、李程、方慧芝等人主导开发)
- 发布时间:2019年
- 性能水平:MSPN在当时的COCO姿态估计挑战赛上取得了顶尖成绩。其核心思想——多阶段 refinement 和 跨阶段特征聚合——被证明是提升姿态估计精度的极其有效的方法,对后续的高精度姿态估计算法产生了深远影响。
在COCO val2017数据集上,使用ResNet-50作为主干的MSPN50与其他方法的性能对比:
| Model | Backbone | Input Size | GFLOPs | Params(M) | AP | AP50 | AP75 | AR | AR50 |
|---|---|---|---|---|---|---|---|---|---|
| Hourglass | Hourglass | 256x192 | 19.7 | 25.1 | 66.9 | - | - | - | - |
| CPN | ResNet-50 | 384x288 | 13.5 | 32.8 | 72.1 | 91.4 | 79.5 | 78.5 | 95.0 |
| Simple Baseline | ResNet-50 | 256x192 | 5.3 | 25.6 | 70.4 | 88.6 | 78.3 | 76.3 | 92.5 |
| MSPN50 (2-stage) | ResNet-50 | 256x192 | 12.2 | 29.1 | 74.1 | 90.0 | 81.6 | 79.5 | 93.8 |
| MSPN50 (4-stage) | ResNet-50 | 256x192 | 24.1 | 32.0 | 75.1 | 90.4 | 82.3 | 80.4 | 94.2 |
算法流程梳理:
模型结构上的创新与意义:
MSPN的核心是一个级联式的多阶段架构,每个阶段都包含一个编码器-解码器结构,并将前一阶段的预测结果作为后一阶段的先验知识,进行逐步精炼。
人体检测框输入 ——> Stage 1 ——> 粗略热力图 ——> Stage 2 ——> 精炼热力图 ——> ... ——> Final Stage ——> 最终高精度热力图
MSPN的架构可以分解为以下几个关键部分:
1. 多阶段级联 (Multi-Stage Cascade):
- 整个网络由多个(通常是2或4个)结构相似的子网络(即“阶段”)串联而成。
- 第一阶段:接收裁剪后的人像图片,进行一次完整的姿态估计,生成一个初步的、较为粗糙的关键点热力图。
- 后续阶段:每个后续阶段的输入不仅包含原始图像下采样后的特征,还融合了前一阶段输出的热力图。这相当于告诉当前阶段:“上一阶段认为关键点大概在这个位置,请你在这个基础上进行更精细的定位。”
2. 单阶段内的结构 (Intra-Stage Architecture):
- 每个阶段内部都是一个经典的编码器-解码器(Encoder-Decoder)结构。
- 编码器 (Downsample):通常采用标准的卷积神经网络(如ResNet-50)作为主干,负责从图像中提取多层次的特征。
- 解码器 (Upsample):采用一系列反卷积(Deconvolution)或上采样+卷积层,逐步将高层语义特征恢复到较高的分辨率,并最终生成与输入尺寸1/4大小的热力图。
3. 跨阶段连接 (Cross-Stage Connection):
- 这是MSPN设计的精髓。它并非简单地将前一阶段的热力图直接送入下一阶段,而是设计了精巧的跨阶段特征聚合单元。
- 在每个尺度上(编码和解码过程中),当前阶段的特征图都会与前一阶段对应尺度的特征图进行融合。这通常通过一个1x1卷积进行压缩,然后进行元素相加(Element-wise Sum)来实现。
- 这种设计确保了后一阶段不仅能看到前一阶段的最终预测,还能利用其所有中间层的丰富特征,从而实现更有效的信息传递和精炼。
4. 监督信号 (Supervision):
- 多阶段监督:与只监督最终输出不同,MSPN在每个阶段的末尾都计算损失并进行监督。这相当于为每个子网络都设置了明确的学习目标,避免了梯度消失问题,使得整个深层网络能够被有效训练。
基本组件:
ResNet-50:作为每个阶段编码器的强大主干网络,用于提取高质量的视觉特征。 反卷积层 (Deconvolution Layer):在解码器中用于上采样,恢复特征图的分辨率。 跨阶段特征融合单元:通过1x1卷积和元素相加,将前一阶段的中间特征高效地传递给当前阶段。
前后处理
前处理:
- 人体检测与裁剪:
- 首先,需要一个目标检测器(如YOLO, SSD)来检测出图像中的每个人。
- 根据检测框对每个人进行裁剪,并将裁剪出的人像区域作为MSPN50的输入。
- (为测试单模型部署效果,此处采用直接用裁剪后的图片作为输入)
- 尺寸调整与归一化:
- 将裁剪出的人像区域缩放到模型所需的固定输入尺寸(如 256x192),并保持固定的宽高比。
- 进行像素值的归一化(如减均值、除以标准差)。
# 读取原始图像 ori_img = cv2.imread(image_path) self.ori_height, self.ori_width = ori_img.shape[:2] # 调整尺寸 (保持宽高比填充到模型输入尺寸) input_height, input_width = self.input_shape[2], self.input_shape[3] scale = min(input_width / self.ori_width, input_height / self.ori_height) new_width = int(self.ori_width * scale) new_height = int(self.ori_height * scale) # 填充黑边使图像符合模型输入尺寸 resized_img = cv2.resize(ori_img, (new_width, new_height)) padded_img = np.zeros((input_height, input_width, 3), dtype=np.uint8) dx = (input_width - new_width) // 2 dy = (input_height - new_height) // 2 padded_img[dy:dy + new_height, dx:dx + new_width] = resized_img
后处理:
- 热力图解码:
- 模型输出一组热力图,每张图对应一个关键点。
- 在每张热力图中找到响应值(概率)最高的像素点位置
(x, y)。heatmap=np.array(output_tensors[0]).transpose(0,3,1,2) batch_size, num_kpts, H, W = heatmap.shape heatmap = heatmap[0] # 取batch中的第一个 keypoints = [] for i in range(num_kpts): hm = heatmap[i] max_loc = np.unravel_index(np.argmax(hm), hm.shape)
- 坐标映射:
- 将从热力图中解码出的坐标,从模型的输入尺寸(256x192)反向映射回原始图像的坐标系中。
score = hm[max_loc] x=max_loc[1]*(256/64) y=max_loc[0]*(192/48) # 从热图坐标转换到输入图像坐标 x = (x - pad_info['padding'][0]) / pad_info['scale'] y = (y - pad_info['padding'][1]) / pad_info['scale'] # 限制在图像范围内 x = np.clip(x, 0, ori_shape[0] - 1) y = np.clip(y, 0, ori_shape[1] - 1) keypoints.append([x, y, score])
- 将从热力图中解码出的坐标,从模型的输入尺寸(256x192)反向映射回原始图像的坐标系中。
2.2 硬件资源分配
psin模式:由电脑端传入测试图片
前处理:图片的resize和pad等由CPU完成,从PS->PL由FPGA完成
网络主体:npu
后处理热力图解码等:fpga
后处理坐标映射等:cpu
可视化:cpu
4. 模型导出说明
关于模型源码:
模型源码为OPENMMLAB的MMPOSE库,具体安装参考如下链接: https://mmpose.readthedocs.io/zh-cn/latest/installation.html
MMPOSE源码仓库链接为:https://github.com/open-mmlab/mmpose.git
从MMPOSE导出模型为ONNX需要安装OPENMMLAB的MMDEPLOY库,具体安装参考如下链接: https://mmdeploy.readthedocs.io/zh-cn/stable/get_started.html#mmdeploy
MMDEPLOY源码仓库链接为:https://github.com/open-mmlab/mmdeploy.git
关于模型导出: 确保文件夹结构如下所示:
├── mmdeploy >openmmlab用于部署导出文件的代码
├── mmpose >openmmlab关于关键点检测的代码
路径切到mmdeploy中,然后并下载好对应预训练模型,放在对应位置,执行下面代码,模型输出在rtmpose-ort文件夹下
python tools/deploy.py configs/mmpose/pose-detection_onnxruntime_static.py ../mmpose/configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_mspn50_8xb32-210e_coco-256x192.py ../mmpose/ckpt/mspn50_coco_256x192.pth demo/resources/human-pose.jpg --work-dir rtmpose-ort/rtmpose-m --device cpu --show --dump-info
不额外做处理,直接将pt模型导出为onnx
5. cpu处理部分c++工程移植说明
1. CPU环节主要实现的内容
- 1.读取并设置输入yaml文件中的相关参数,以及载入测试图片等功能
- 2.图片的resize和pad,以符合模型输入尺寸
- 3.在模型后处理部分,负责将热力图解码得到的坐标映射到原图中,并实现可视化
- 4.在MSPN50_task_queue.cpp中,还负责控制多线程实现并行计算
- 5.在16bit精度的工程中,由于无法使用硬算子实现热力图解码,这一部分也由CPU实现
2. 做了哪些测试验证移植后准确性
- 分别做了仿真阶段以及实际Socket上板阶段的精度测试,经测试精度一致,符合准确性
- 分别做了在pyrt和crt下的上板精度测试,经过测试精度一致,符合准确性
3.各环节耗时
在8bit条件下,耗时如下图所示

在16bit条件下,耗时如下图所示

在混合精度条件下,耗时如下图所示

6. ICRAFT编译
编译通过
-
16bit下推理测试结果:
框架动态图推理 导出静态模型推理 解析组件仿真 量化组件仿真 指令组件仿真 




-
8bit,16bit,混合精度的精度测试结果
-
8bit测试结果:
--- Evaluation Summary --- Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.570 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.851 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.631 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.558 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.590 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.699 Average Recall (AR) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.910 Average Recall (AR) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.771 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.665 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.748 ------------------------ -
16bit测试结果:
--- Evaluation Summary --- Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.582 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.856 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.646 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.561 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.620 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.709 Average Recall (AR) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.917 Average Recall (AR) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.781 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.676 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.758 ------------------------ -
混合精度测试结果:
--- Evaluation Summary --- Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.574 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.854 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.635 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.553 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.617 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 20 ] = 0.704 Average Recall (AR) @[ IoU=0.50 | area= all | maxDets= 20 ] = 0.916 Average Recall (AR) @[ IoU=0.75 | area= all | maxDets= 20 ] = 0.777 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets= 20 ] = 0.670 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets= 20 ] = 0.753 ------------------------ -
编译过程的一些特殊说明
- 量化校准集尽量选择经过目标识别后的裁剪图片,或者数据集中人物占图片比例较大的图片
7. 上板测试与优化
- 优化前各环节耗时统计(表格)
带imk,speedmode=false,compressftmp=false,fpga_argmax=false, socket上板

- 优化后耗时统计(表格)
带imk,speedmode=true,compressftmp=true,fpga_argmax=true, socket上板

- 优化项与效果
优化项1:合并算子
设置speedmode=true,compress=true实现算子合并,加速计算

可见总耗时由原来的200ms左右变为10ms左右
优化项2:后处理上,去除cast算子,并用FPGA算子替代CPU实现热力图解码
可见总耗时由10ms左右变为7ms左右
优化项3:采用多线程并行计算
采用多线程并行计算的方法,将整体runtime分为三个线程:
1.input_thread: 负责读取裁剪图片并输入imk算子完成搬数。
2.icore_thread[n]: 负责完成模型的前向推理工作。
3.post_thread:负责完成模型的后处理工作。
在多线程条件下,运行工程吞吐率约在60-70FPS,单线程条件下,运行工程吞吐率约在40FPS,提升约150%
优化项4:前处理部分采用imagemake硬算子,缩短数据输入时间
- 运行时工程的整体介绍
如上图流程图所示,整个流程展示了从模型部署、数据处理到硬件加速推理和结果生成的完整解决方案。程序首先通过解析 YAML 配置文件来初始化各项参数,包括模型路径、运行模式(硬件仿真或上板运行)、数据集位置以及后处理选项。随后,它加载预编译的神经网络模型(.json 和 .raw 文件),并根据配置创建与目标设备的会话(Session),完成模型的部署和资源分配。
在主处理循环中,程序逐一读取输入图像。对每张图像,它会执行一个保持纵横比的预处理操作:将图像缩放并填充(padding)至模型所需的输入尺寸,然后转换为框架所需的 Tensor 格式。该 Tensor 被送入 session.forward() 接口进行硬件加速推理。
推理完成后,程序进入后处理阶段。它从输出的热图(heatmap)中提取关键点坐标。此过程支持两种模式:一种是纯软件实现的 argmax 算法,在 CPU 上计算;另一种是高性能的硬件加速模式,调用 FPGA 上的 argmax 逻辑直接计算坐标。最后,程序将提取出的坐标经过反向变换,映射回原始图像尺寸,并将结果可视化(绘制关键点)或保存到指定目录。