1.项目背景
肩周炎又称肩关节周围炎,本病常发于40~60岁的中、老年患者,女性发病率略高于男性,多见于体力劳动者,50岁左右为高发,所以也称为“五十肩”。以肩部逐渐产生疼痛,夜间为甚,逐渐加重,肩关节活动功能受限而且日益加重,达到某种程度后逐渐缓解,直至最后完全复原为主要表现的肩关节囊及其周围韧带、肌腱和滑囊的慢性特异性炎症。肩周炎是以肩关节疼痛和活动不便为主要症状的常见病症。如得不到有效的治疗,有可能严重影响肩关节的功能活动。肩关节可有广泛压痛,并向颈部及肘部放射,还可出现不同程度的三角肌的萎缩。
其中肩关节的活动度评估是关节科医生很重要的一环,其不光体现在肩周炎的诊断中,同时在治疗的过程中肩关节的活动度变化情况仍然具有很重要的临床意义,这对于病情的变化情况以及治疗效果的评估中是不可缺少的一环。传统临床测量肩关节的活动度时往往比较麻烦,且不同的医生测量到的数值也不尽相同。通过使用AI测量在此时就派上了很大的用处,AI测量速度快,精度较高,同时数据重复性较强。
2.方案介绍
本次实践主要使用PaddleHub
中针对移动端设备优化的实时关键点检测模型PP-TinyPose
,该模型由PaddleDetecion
提供。TinyPose的运行环境有以下依赖要求:
PaddlePaddle>=2.2
模型介绍:
PaddleDetection推出的轻量级关键点检测算法PP-TinyPose,采用Top-Down的方式,先应用3.3M、150FPS的超轻量检测网络PP-PicoDet检测出人体,再用基于Lite-HRNet的移动端优化模型确保关键点检测的高精度,同时扩大数据集,减小输入尺寸,预处理与后处理加入AID、UDP和DARK等策略,保证模型的高性能。实现速度在FP16下122FPS的情况下,精度达到51.8%AP,比其他类似实现速度更快,同时精度提升了130%。
#####model
architecture: TopDownHRNet
TopDownHRNet:
backbone: LiteHRNet
post_process: HRNetPostProcess
flip_perm: *flip_perm
num_joints: *num_joints
width: &width 40
loss: KeyPointMSELoss
use_dark: true
LiteHRNet:
network_type: wider_naive
freeze_at: -1
freeze_norm: false
return_idx: [0]
KeyPointMSELoss:
use_target_weight: true
loss_scale: 1.0
3.实践教程
3.1 环境配置
PaddlePaddle==2.3.2
PaddleHub==2.3.1
PaddleDetection==2.5 (训练模型时使用,见3.4)
更多关于Paddle GPU版本的安装内容,请参考: https://www.paddlepaddle.org.cn/install/quick
首先,更新pip
,安装paddlehub
:
!pip install --upgrade pip # 更新pip
!pip install paddlehub==2.3.1 # 安装paddlehub
!python -m pip install --upgrade pip
!pip install paddlehub==2.3.1
安装pp-tinypose
模型
验证pp-tinypose
安装成功,会出现如下提示信息:
----------- Model Configuration -----------
Model Arch: GFL
Transform Order:
--transform op: Resize
--transform op: NormalizeImage
--transform op: Permute
--------------------------------------------
----------- Model Configuration -----------
Model Arch: HRNet
Transform Order:
--transform op: TopDownEvalAffine
--transform op: Permute
--------------------------------------------
keypoint visualize image saved to: pp_tinypose_output/1_vis.jpg
...
# 测试模型
%cd /home/aistudio/
!hub run pp-tinypose --input_path "/home/aistudio/pose/1.jpg"
3.2 推理
3.2.1 首先完成肩关节角度函数的定义:
import cv2
import numpy as np
# 定义肩关节角度函数
def frozen_shoulder(right_shoulder, left_shoulder, right_elbow, left_elbow):
"""返回:
left_frozen, right_frozen, (left_theta_rad, right_theta_rad), (left_theta_deg, right_theta_deg)
是否肩关节外展障碍,肩关节弧度,肩关节角度
"""
left_flag = 1 if left_elbow[1]-left_shoulder[1] > 0 else -1 # 添加钝角、锐角标志(np.arccos()计算结果为0-pi)
right_flag = 1 if right_elbow[1]-right_shoulder[1] > 0 else -1
# print("钝锐角标志:", (left_flag, right_flag))
if np.any(right_shoulder) and np.any(left_shoulder) and np.any(right_elbow) and np.any(left_elbow):
l_shoulder = left_shoulder - right_shoulder
# print("l_shoulder.shape:", l_shoulder.shape)
r_shoulder = right_shoulder-left_shoulder
shoulder_norm = np.linalg.norm(l_shoulder)
left_elbow_norm = np.linalg.norm(left_elbow-left_shoulder)
right_elbow_norm = np.linalg.norm(right_elbow-right_shoulder)
l_shoulder_dot_elbow = l_shoulder.dot(left_elbow-left_shoulder)
r_shoulder_dot_elbow = r_shoulder.dot(right_elbow-right_shoulder)
left_cos_theta = l_shoulder_dot_elbow/(shoulder_norm*left_elbow_norm)
# print("left_cos_theta:", left_cos_theta)
left_theta_rad = np.arccos(np.clip(left_cos_theta, -1, 1)) * left_flag
print("left_theta_rad:", left_theta_rad)
left_theta_deg = 90-np.rad2deg(left_theta_rad)
print("left_theta_deg:", left_theta_deg) # 角度
right_cos_theta = r_shoulder_dot_elbow/(shoulder_norm*right_elbow_norm)
# print("right_cos_theta:", right_cos_theta)
right_theta_rad = np.arccos(np.clip(right_cos_theta, -1, 1)) * right_flag
print("right_theta_rad:", right_theta_rad)
right_theta_deg = 90-np.rad2deg(right_theta_rad)
print("right_theta_deg:", right_theta_deg)
left_frozen = False
right_frozen = False
if left_theta_deg < 85: # 用大于85°估计肩关节外展良好
left_frozen = True
if right_theta_deg < 85:
right_frozen = True
return left_frozen, right_frozen, (left_theta_rad, right_theta_rad), (left_theta_deg, right_theta_deg)
3.2.2 定义展示肩关节角度函数
这一步我们需要解析模型返回值:
COCO keypoint Description:
0: "Nose",
1: "Left Eye",
2: "Right Eye",
3: "Left Ear",
4: "Right Ear",
5: "Left Shoulder,
6: "Right Shoulder",
7: "Left Elbow",
8: "Right Elbow",
9: "Left Wrist",
10: "Right Wrist",
11: "Left Hip",
12: "Right Hip",
13: "Left Knee",
14: "Right Knee",
15: "Left Ankle",
16: "Right Ankle"
其中我们使用到了5
,6
,7
,8
:左右肩关节及左右肘关节点位置。
import paddlehub as hub
import matplotlib.pyplot as plt
%matplotlib inline
import os
def show_shoulder_theta(img_path=None, visualization=False):
assert img_path, "图片路径不能为空"
model = hub.Module(name="pp-tinypose")
image = cv2.imread(img_path)
result = model.predict(image, visualization=visualization)
key_points = result[0][2][0]
left_shoulder = right_shoulder = left_elbow = right_elbow = np.zeros(
2, dtype=np.int32)
body_nums = 0
color_bgr = [(200, 0, 0), (0, 200, 0), (0, 0, 200)]
for key_point in key_points:
body_nums += 1
for i, k in enumerate(key_point):
if i == 5: # 此处对应上述关键点返回值,以下6,7,8同理
# print(k)
left_shoulder = np.asarray(k[:2], dtype=np.int32)
# print("left_shoulder:", left_shoulder)
if i == 6:
# print(k)
right_shoulder = np.asarray(k[:2], dtype=np.int32)
# print("right_shoulder:", right_shoulder)
if i == 7:
# print(k)
left_elbow = np.asarray(k[:2], dtype=np.int32)
# print("left_elbow:", left_elbow)
if i == 8:
# print(k)
right_elbow = np.asarray(k[:2], dtype=np.int32)
# print("right_elbow:", right_elbow)
l, r, theta_rad, theta_deg = frozen_shoulder(right_shoulder=right_shoulder,
left_shoulder=left_shoulder,
right_elbow=right_elbow,
left_elbow=left_elbow)
color_sign = body_nums % 3
color = color_bgr[color_sign]
l_base = left_shoulder + np.absolute(left_shoulder - left_elbow)
r_base= right_shoulder + np.absolute(right_shoulder - right_elbow)
cv2.line(image, (int(left_shoulder[0]), int(left_shoulder[1])),
(int(left_shoulder[0]), int(l_base[1])), color, 1)
cv2.line(image, (int(left_shoulder[0]), int(left_shoulder[1])),
(int(left_elbow[0]), int(left_elbow[1])), color, 2)
cv2.line(image, (int(right_shoulder[0]), int(right_shoulder[1])),
(int(right_shoulder[0]), int(r_base[1])), color, 1)
cv2.line(image, (int(right_shoulder[0]), int(right_shoulder[1])),
(int(right_elbow[0]), int(right_elbow[1])), color, 2)
cv2.putText(image, ("L: {:.0f}".format(theta_deg[0]) + " and " + "R: {:.0f}".format(theta_deg[1])),
(20, 100*(body_nums*2)), cv2.FONT_HERSHEY_SIMPLEX, 3, color, 12)
cv2.putText(image, ("L: {}".format("BAD" if l else "GOOD") + " and " + "R: {}".format("BAD" if r else "GOOD")),
(20, 100*(body_nums*2+1)), cv2.FONT_HERSHEY_SIMPLEX, 3, color, 12)
save_img_path = os.path.basename(img_path).split('.')[0] + "-pose.jpg"
cv2.imwrite(save_img_path, image)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.show()
3.2.3 展示肩关节角度
使用pose
文件夹下1.jpg
中人物为肩关节外展困难案例,2.jpg
中人物为肩关节外展正常案例。
运行如下代码:
import matplotlib.pyplot as plt
# 读取图片并显示
img_path="/home/aistudio/pose/1.jpg"
show_shoulder_theta(img_path=img_path, visualization=False)
结果如图,L代表左侧肩关节角度,R代表右侧肩关节角度,GOOD
代表肩关节外展良好,BAD
代表肩关节外展受限,图中的肩关节角度会用绿色线画出:
import matplotlib.pyplot as plt
# 读取图片并显示
img_path="/home/aistudio/pose/1.jpg"
show_shoulder_theta(img_path=img_path, visualization=False)
展示肩关节外展正常案例:
img_path="/home/aistudio/pose/2.jpg"
show_shoulder_theta(img_path=img_path, visualization=False)
结果如下:
img_path="/home/aistudio/pose/2.jpg"
show_shoulder_theta(img_path=img_path, visualization=False)
3.3 数据准备及PaddleDetection环境准备
3.3.1 数据准备
关键点检测模型使用COCO train2017
和AI Challenger trainset
作为训练集。本例中为演示方便,仅使用COCO train2017
验证集作为训练集。
关于pp-tinypose
模型更多内容可前往PP-tinypose github链接了解更多。
本次实践已经将该数据集放在data/data97273
目录:
--data/data97273
|-- annotations_trainval2017.zip
|-- train2017.zip
|-- val2017.zip
留下一个小坑,这里我们的数据仍未解压。
3.3.2 PaddleDetection安装
模型训练建议使用V100 32G的显卡环境。
依次执行下列代码安装PaddleDetection:
- 通过
gitee
加速获取
!git clone https://gitee.com/PaddlePaddle/PaddleDetection.git
结果如下:
正克隆到 'PaddleDetection'...
remote: Enumerating objects: 254853, done.
...
处理 delta 中: 100% (208408/208408), 完成.
检查连接... 完成。
- 安装依赖文件
%cd /home/aistudio/PaddleDetection
!pip install -r requirements.txt
- 编译安装
!python setup.py install
- PaddleDetection环境验证
!python3 ppdet/modeling/tests/test_architectures.py
- 如下提示安装成功:
.......
----------------------------------------------------------------------
Ran 7 tests in 3.194s
OK
# 安装paddledetection
# 如/home/aistudio路径下(进去AIStudio的默认路径)存在PaddleDetection文件夹可不用执行此命令
%cd /home/aistudio
!git clone https://gitee.com/PaddlePaddle/PaddleDetection.git
%cd /home/aistudio/PaddleDetection
!pip install -r requirements.txt
# 编译安装paddledet
%cd /home/aistudio/PaddleDetection
!python setup.py install
# 验证
%cd /home/aistudio/PaddleDetection
!python3 ppdet/modeling/tests/test_architectures.py
3.3.3 解压数据
执行命令:
!unzip -o /home/aistudio/data/data97273/val2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
!unzip -o /home/aistudio/data/data97273/train2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
!unzip -o /home/aistudio/data/data97273/annotations_trainval2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
将数据解压至/home/aistudio/PaddleDetection/dataset/coco
目录下
!unzip -o /home/aistudio/data/data97273/val2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
!unzip -o /home/aistudio/data/data97273/train2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
!unzip -o /home/aistudio/data/data97273/annotations_trainval2017.zip -d /home/aistudio/PaddleDetection/dataset/coco
3.3.4 添加配置文件
因为本次训练使用的数据没有测试集,使用默认配置进行模型导出时会出错,所以训练时使用pose/tinypose_256x192_t.yml
文件,将该文件复制到PaddleDetection/configs/keypoint/tiny_pose
文件夹中
执行!cp /home/aistudio/pose/tinypose_256x192_t.yml /home/aistudio/PaddleDetection/configs/keypoint/tiny_pose
!cp /home/aistudio/pose/tinypose_256x192_t.yml /home/aistudio/PaddleDetection/configs/keypoint/tiny_pose
3.4 模型优化
- 检测-跟踪模型优化
在PaddleDetection中,关键点检测能力支持Top-Down
、Bottom-Up
两套方案,Top-Down
先检测主体,再检测局部关键点,优点是精度较高,缺点是速度会随着检测对象的个数增加,Bottom-Up
先检测关键点再组合到对应的部位上,优点是速度快,与检测对象个数无关,缺点是精度较低。本例中使用的pp-tinypose
为 Top-Down
方案,可有效提示模型检测精度。
- 使用符合场景的数据迭代
目前发布的关键点检测算法模型主要在COCO/ AI Challenger等开源数据集上迭代,这部分数据集中可能缺少与实际任务较为相似的监控场景(视角、光照等因素)、体育场景(存在较多非常规的姿态)。使用更符合实际任务场景的数据进行训练,有助于提升模型效果。
- 使用预训练模型迭代
关键点模型的数据的标注复杂度较大,直接使用模型从零开始在业务数据集上训练,效果往往难以满足需求。在实际工程中使用时,建议加载已经训练好的权重,通常能够对模型精度有较大提升,例:
python tools/train.py -c configs/keypoint/hrnet/hrnet_w32_256x192.yml -o \
pretrain_weights=https://paddledet.bj.bcebos.com/models/keypoint/hrnet_w32_256x192.pdparams
在加载预训练模型后,可以适当减小初始学习率和最终迭代轮数, 建议初始学习率取默认配置值的1/2至1/5,并可开启–eval观察迭代过程中AP值的变化
- 遮挡数据增强
配置文件中:
- AugmentationbyInformantionDropping:
prob_cutout: 0.5
offset_factor: 0.05
num_patch: 1
trainsize: *trainsize
3.5 训练
3.5.1 修改配置
我们找到tinypose
下的配置文件:/home/aistudio/PaddleDetection/configs/keypoint/tiny_pose/tinypose_256x192.yml
# 此为原始配置的一部分,我们在3.3.4中复制过来的文件基于此修改
epoch: 420 # 本例中我们修改为50 各位开发者可以根据情况自行修改
#####data
TrainDataset:
!KeypointTopDownCocoDataset
image_dir: "" # 本例中为:train2017
anno_path: aic_coco_train_cocoformat.json # 本例中为:annotations/person_keypoints_train2017.json
dataset_dir: dataset # 本例中为:dataset/coco 前边步骤3.3.3中解压的文件夹
num_joints: *num_joints
trainsize: *trainsize
pixel_std: *pixel_std
use_gt_bbox: True
EvalDataset:
!KeypointTopDownCocoDataset # 详情可在PaddleDetection/configs/keypoint/tiny_pose/tinypose_256x192_t.yml中查看
image_dir: val2017
anno_path: annotations/person_keypoints_val2017.json
dataset_dir: dataset/coco
num_joints: *num_joints
trainsize: *trainsize
pixel_std: *pixel_std
use_gt_bbox: True
image_thre: 0.5
TestDataset:
!ImageFolder
anno_path: dataset/coco/keypoint_imagelist.txt
3.5.2 开始训练
使用以上优化参数
%cd /home/aistudio/PaddleDetection
!CUDA_VISIBLE_DEVICES=0
!python3 tools/train.py -c configs/keypoint/tiny_pose/tinypose_256x192_t.yml \
-o pretrain_weights=https://bj.bcebos.com/v1/paddledet/models/keypoint/tinypose_enhance/tinypose_256x192.pdparams
%cd /home/aistudio/PaddleDetection
!CUDA_VISIBLE_DEVICES=0
!python3 tools/train.py -c configs/keypoint/tiny_pose/tinypose_256x192_t.yml \
-o pretrain_weights=https://bj.bcebos.com/v1/paddledet/models/keypoint/tinypose_enhance/tinypose_256x192.pdparams
导出PP-Tinypose本次训练模型(模型位置为PaddleDetection/output_inference/tinypose_256x192
),我们会在模型导出位置看到如下文件:
├── tinypose_256x192
│ ├── infer_cfg.yml
│ ├── model.pdiparams
│ ├── model.pdiparams.info
│ └── model.pdmodel
%cd /home/aistudio/PaddleDetection
!python3 tools/export_model.py \
-c configs/keypoint/tiny_pose/tinypose_256x192.yml \
--output_dir=output_inference \
-o weights=output/tinypose_256x192_t/model_final
此时我们已经完成了模型的导出工作,现在我们需要将训练出来的模型复制到PaddleHub
中pp-tinypose
的模型目录(/home/aistudio/.paddlehub/modules/pp_tinypose/model/
)下:
# 替换paddlehub中tinypose 模型
!cp -r /home/aistudio/PaddleDetection/output_inference/tinypose_256x192 \
/home/aistudio/.paddlehub/modules/pp_tinypose/model/
3.6 测试训练结果
现在再次执行肩关节角度预测程序,不出意外,我们将得到类似下面的结果:
如若执行过程中出现所识别的肢体关键点效果不佳或者下述角度无法正常输出的情况:
left_theta_rad: nan
left_theta_deg: nan
right_theta_rad: nan
right_theta_deg: nan
这代表我们的模型仍不能准确识别出肢体关键点,需要进一步进行训练,那么,再次从3.2数据准备中开始调整数据集或者修改配置调整超参数吧。
%cd /home/aistudio/
show_shoulder_theta(img_path="/home/aistudio/pose/2.jpg")
4. 总结与展望
- 本案例使用
PP-tinypose
进行了肩关节角度检测分析,在使用过程中我们可以深深的体会到PaddleHub的便捷性,尤其是对于我们这种非专业出身的人来说,非常的友好,仅需简单修改配置就可一键启动。 - 因为
PP-tinypose
为PaddleDetection
下的模型,同时可以借助PaddleDetection
为模型进行进一步的优化,开发者也可以自己尝试更换其他检测模型。
此文章为搬运
原项目链接