基于YOLOv8-Pose的手部姿态估计实验
收藏回复举报
基于YOLOv8-Pose的手部姿态估计实验
发表于2025-07-16 11:11:26
0 查看

基于YOLOv8-Pose的手部姿态估计实验

姿态估计介绍

1.1 姿态估计

       姿态估计(Pose Estimation)是指检测图像和视频中的人物形象的计算机视觉技术,可以确定某人的某个身体部位出现在图像中的位置,也就是在图像和视频中对人体关节的定位问题,也可以理解为在所有关节姿势的空间中搜索特定姿势。人体姿态估计(Human Pose Estimation)是计算机视觉领域中的一个重要研究方向,也是计算机理解人类动作、行为必不可少的一步,人体姿态估计是指通过计算机算法在图像或视频中定位人体关键点,目前被广泛应用于动作检测、虚拟现实、人机交互、视频监控等诸多领域。

姿态估计可简单理解为从给定图像中识别提取人脸、手部、身体等部位的关键点。其可视化结果如下图所示。

cke_206248.png

1.2 YOLOv8-pose介绍

YOLO (You Only Look Once),由华盛顿大学的Joseph Redmon和Ali Farhadi开发的流行目标检测和图像分割模型,于2015年推出,由于其高速和准确性而迅速流行。

      YOLOv8在之前版本的成功基础上引入了新功能和改进,以提高性能、灵活性和效率。YOLOv8支持全范围的视觉AI任务,包括检测, 分割, 姿态估计, 跟踪, 和分类。这种多功能性使用户能够利用YOLOv8的功能应对多种应用和领域的需求。

GitHub: cid:link_4

    YOLOv8-Pose是在Yolov8的基础上进行改进与拓展的姿态估计模型。它通过在Yolov8的网络结构中引入额外的分支,实现对人体关节的定位与姿态估计。具体来说,YOLOv8-Pose在YOLOv8的最后一个卷积层后加入了一个新的卷积层,用于生成关节热图。这些关节热图表示了人体关节在图像上的分布情况。然后,通过对关节热图进行非极大值抑制(NMS)和插值,可以得到关节的准确位置和姿态。

YOLOv8-Pose的训练过程包括两个关键步骤:关节热图的生成和网络的训练。关节热图的生成是通过对标注好的人体关节位置进行高斯模糊得到的,这使得关节热图在关节点附近具有较高的响应,从而提高关节定位的准确性。在网格的训练过程中,YOLOv8-Pose采用了多任务学习的策略。网格同时进行了目标检测和姿态估计的训练,以使得网络能够学习到更多的语义信息和准确的关节位置。

有关YOLOv8-Pose的更多技术细节,可参见上述资料链接。

YOLOv8-Pose的精度如下图所示:

cke_846092.png

可以看到,目前YOLOv8-Pose的精度和实时性比较好的特别适合在边缘设备中部署应用

  • 数据集的制作,训练与验证

2.1 手部姿态估计数据集

22年Hagrid v1数据集同时具有检测框(bounding box)和关键点(keypoints)可作为YOLOv8-Pose训练数据集。

注意在官网GitHub上Hagrid具有v1和v2两个版本,其中只有v1版本的标注中同时具有人手的检测框和关键点,将Hagrid v1版本的源码git到本地。

cke_930524.png

其数据标注细节展示如下。

cke_992761.png

原始数据集中图片的尺寸大。建议下载低分辨率的全部数据进行训练,点击下方红框中的链接下载。

1下载下方图示Subsample中的图像数据集images和标注annotations。

cke_1050361.png

2将数据集转化为YOLOv8的格式YOLOv8-Pose的标注格式要求如下

# 以6个关键点为例

类别0 + bbox [cx cy w h] + 关键点 [k1, k2 ,k3 ,k4 ,k5 ,k6 ]

拿到标注和图片以后,使用Hagrid v1源码目录converters下的hagrid_to_yolo.py来将数据集转化成YOLO的格式。代码见附件中的hagrid_to_yolo.py和converter_config.yaml。

本实验对hagrid_to_yolo.py做了一定修改,原始数据集中有的标注中只有bbox没有keypoints,对这部分数据进行了清洗,执行以下命令转换成YOLO数据集。

# <PATH>即对应converter_config.yaml的路径

python -m hagrid_to_yolo –cfg <PATH>

3转换完成后会生成如下目录的图像和标注,然后将图片和数据集分别从各个类别中提取出来,生成一个包含所有图像的文件夹和包含所有标注的文件夹。

cke_1125725.png

4将图像和标签的文件夹制作好后,执行附件中的split.py进行数据集划分,按照85%训练集,15%测试集的比例划分(可以调整)。

最终得到形如如下树形目录的YOLO数据集:

dataset/
train/
	images/
		1.jpg
		…
	labels/
		1.txt
		…
val/
	images/
		1.jpg
		…
	labels/
		1.txt
		…

2.2 基于手部姿态估计数据集进行训练

1首先在Github官网上下载YOLOv8源码,通过以下指令安装Yolov8环境。

# 从GitHub安装ultralytics包

pip install git+cid:link_4.git@main

注:YOLOv8环境为Ultralytics YOLOv8.0.225 Python-3.8.0 torch-2.0.1+cu117 CPU

2环境全部安装完成后,通过以下指令测试YOLOv8-Pose是否可以训练。如果可以正常训练使用Ctrl+C终止进程,进行下一步内容。

yolo pose train

3下面配制训练YOLOv8-Pose所需的yaml文件,跟coco8-pose.yaml的内容基本一致,注意制成的kpt_shape只有x和y这两个坐标,可见性visible在数据集中未标注,内容如下。

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: /home/lx/model_deploy/hagrid-Hagrid_v1/dataset  # dataset root dir 
train: /home/lx/model_deploy/hagrid-Hagrid_v1/dataset/train  # train images (relative to 'path')	
val: /home/lx/model_deploy/hagrid-Hagrid_v1/dataset/val	# val images (relative to 'path')

# Keypoints
kpt_shape: [21, 2]  # number of keypoints, number of dims (2 for x,y or 3 for x,y,visible)
flip_idx: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]


# Classes
names:
0: hand

其中flip_idx,表示手部关键点与数据位置的对应关系,原始数据集中并没有标注具体的关系。具体可按下图确认各关键点之间关系

cke_1223469.png

4通过以下指令进行训练。因为要部署在华为边缘设备上,应尽量选择参数少且精度高的模型,实验采用Yolov8n-pose模型进行训练,采用官方的预训练模型yolov8n-pose.pt提供先验知识。

yolo pose train data=pose.yaml model=yolov8n-pose.pt pretrained=yolov8n-pose.pt epochs=100 imgsz=640 single_cls=true

在训练时开始时会打印很多关于标签的warning,这是因为数据集标签中的一些数据违规了,比如bounding box的取值大于1,而Yolov8会自动将这些违规数据清洗掉。

5训练完成后,可以在源码目录下的runs文件夹中看到训练结果。训练的模型权重保存在weights文件中。

cke_1459309.png

下面展示验证集的预测结果(左)和实际标注结果(右)的对比。

cke_1517241.png 

可以尝试用原始数据集中的所有图片和标注补充更多的数据进行训练,也可以调整优化YOLOv8-Pose的网络结构,获得更好的性能。

2.3 基于手部姿态估计数据集进行推理验证

模型训练成功后,可以通过附件中的predict-pose.py测试在pytorch框架下所训练的模型的性能。

执行成功后,就可以生成如下图的推理图片。

cke_1602115.png

  • 在开发板上部署模型

在华为Atlas200I DK A2上部署,需要先将模型由pt转化为onnx格式,再将onnx模型转化成om格式。

3.1 ONNX模型导出

1根据下面代码进行pt到onnx格式的转化。

yolo export model=yolov8n-pose.pt format=onnx imgsz=640  # 导出官方模型

yolo export model=path/to/best.pt format=onnx imgsz=640  # 导出自定义训练的模型

转化细节如下所示,onnx版本为1.12.0opset 17。

cke_1682231.png

目录中就会出现handpose.onnx(具体名字和onnx模型的生成路径与导入的pt模型路径有关)。

2onnx推理验证

参见onnx_test.py代码,对onnx模型进行测试验证

# 前处理模块细节
def preprocess_warpAffine(image, dst_width=640, dst_height=640):
scale = min((dst_width / image.shape[1], dst_height / image.shape[0]))
    ox = (dst_width  - scale * image.shape[1]) / 2
    oy = (dst_height - scale * image.shape[0]) / 2
    M = np.array([
        [scale, 0, ox],
        [0, scale, oy]
    ], dtype=np.float32)
    
    img_pre = cv2.warpAffine(image, M, (dst_width, dst_height), flags=cv2.INTER_LINEAR,
                             borderMode=cv2.BORDER_CONSTANT, borderValue=(114, 114, 114))
    IM = cv2.invertAffineTransform(M)

    img_pre = (img_pre[...,::-1] / 255.0).astype(np.float32)
    img_pre = img_pre.transpose(2, 0, 1)[None]
    #转化为tentor,用pytorch推理时使用
    #img_pre = torch.from_numpy(img_pre)
return img_pre, IM

# 后处理模块
def postprocess(pred, IM=[], conf_thres=0.25, iou_thres=0.45):
 # 输入是模型推理的结果,即8400个预测框
    # 1,8400,42 [cx,cy,w,h,conf,21*2]
    boxes = []
    for img_id, box_id in zip(*np.where(pred[...,4] > conf_thres)):
        item = pred[img_id, box_id]
        cx, cy, w, h, conf = item[:5]
        left    = cx - w * 0.5
        top     = cy - h * 0.5
        right   = cx + w * 0.5
        bottom  = cy + h * 0.5

        keypoints = item[5:].reshape(-1, 2)
        keypoints[:, 0] = keypoints[:, 0] * IM[0][0] + IM[0][2]
        keypoints[:, 1] = keypoints[:, 1] * IM[1][1] + IM[1][2]
        boxes.append([left, top, right, bottom, conf, *keypoints.reshape(-1).tolist()])

    #防止原始图像没有待测目标
    if boxes !=[]:
        boxes = np.array(boxes)
        lr = boxes[:,[0, 2]]
        tb = boxes[:,[1, 3]]
        boxes[:,[0,2]] = IM[0][0] * lr + IM[0][2]
        boxes[:,[1,3]] = IM[1][1] * tb + IM[1][2]
        boxes = sorted(boxes.tolist(), key=lambda x:x[4], reverse=True)
        
        return NMS(boxes, iou_thres)
    
    return []

前处理的代码包含修改图像大小,加灰条,图像归一化以及将图像从BGR转换为RGB步骤以及HWC到CHW的维度转换,返回预处理后的图像以及图像缩放比例系数。如果使用pytorch框架推理还要将图像从numpy格式转化为tensor格式。

后处理模块和YOLOv8源码后处理模块类似:当数据集的关键点特征只有x和y坐标没有v(代表关键点是否可见)时,默认所有关键点均为可见;当数据集的关键点特征有x、y及v时,进行更多的操作,先判断关键点是否可见再进行可视化绘图。

onnx模型的输入使用numpy格式。

# 代码片段

        # onnx模型推理,输入为numpy

        # 预测输出float32[1, 47, 8400]

        pred = self.session.run([self.label_name], {self.input_name: img.astype(np.float32)})[0]

        # 转化成[1,8400,47]

        pred = np.transpose(pred,(0,2,1))

cke_1762343.png

图片的推理预测结果

cke_1824720.png

摄像头实时推理预测结果

3.2 使用ATC转化工具将模型转化为OM模型

对于OM模型的转化在PC端和开发板上都可以完成,前提是配置安装CANN工具包以及其依赖包。开发板上的合设环境已经安装最新版本的CANN工具包可以直接转换模型,但其CPU性能及内存有限,推荐在PC端上进行模型的转化。

Ubuntu系统安装CANN工具包(选做)

1安装依赖项。

# 首先安装依赖项,PC端连接网络,执行如下命令检查源是否可用。

apt-get update



# 检查root用户的umask

umask

#如果umask不等于0022,执行以下操作

vi ~/.bashrc

#在文件最后一行后面添加umask 0022内容,按执行esc 以及 :wq!命令保存文件并退出

source ~/.bashrc

#安装CANN工具包完成后,记得执行上述步骤将umask改为0027



#安装以下包sudo apt-get install -y gcc g++ make cmake zlib1g \

zlib1g-dev openssl libsqlite3-dev \

libssl-dev libffi-dev libbz2-dev libxslt1-dev \

unzip pciutils net-tools libblas-dev gfortran libblas3



#在python环境中安装以下包,按照官网的教程,系统的python环境版本应该要大于等于3.7.5

pip3 install attrs

pip3 install numpypip3 install decorator

pip3 install sympy

pip3 install cffi

pip3 install pyyaml

pip3 install pathlib2

pip3 install psutil

pip3 install protobuf

pip3 install scipy

pip3 install requests

pip3 install absl-py

2然后下载CANN工具包,点击此链接下载,这是6.2.RC2版本的CANN。

3下载成功后,进入下载目录。

#增加对软件包的可执行权限

chmod +x Ascend-cann-toolkit_6.2.RC2_linux-x86_64.run


#执行以下命令安装软件

./Ascend-cann-toolkit_6.2.RC2_linux-x86_64.run –install

4安装完成后,若显示如下信息,则安装成功,xxx对应软件包名。

[INFO] xxx install success

5接下来还要配置环境变量,每次在使用CANN工具之前,都需要执行下面两句指令,可以直接写进~/.bashrc文件的环境变量中。

source CANN_INSTALL_PATH/ascend-toolkit/set_env.sh

export LD_LIBRARY_PATH=CANN_INSTALL_PATH/ascend-toolkit/latest/x86_64-linux/devlib/:$LD_LIBRARY_PATH

ATC命令转换OM模型

ONNX模型到OM模型的转化。参考链接

# model为要转化的onnx模型  framework表示源模型为onnx  output表示输出目录  soc_version表示开发板处理器信息
atc --model=handpose.onnx --framework=5 --output=handpose --soc_version=Ascend310B4

执行结果如下所示

cke_1909432.png

将Yolov8官方预训练的Yolov8-pose.pt一并转化成yolov8n-pose.om文件,Yolov8官方提供的是人体关键点检测,本实验训练的是手部关键点检测。

3.3 在开发板上部署人体关键点检测模型

部署Yolov8官方预训练的人体关键点检测OM模型。

前、后处理模块的实现与之前的onnx_test.py程序实现相同,具体见附件中om_test.py和det_utils.py相关代码

运行om_test.py在开发板上进行测试。

cke_1971861.png

3.4 在开发板上部署手部关键点检测模型

部署YOLOv8-Pose手部关键点检测模型。创建det_utils2文件,om_test.py开头导入的包由from det_utils import xxx改为from det_utils2 import xxx,进行手部关键点的推理预测。

cke_2043298.png

单张图片的推理效果

cke_2137015.png

摄像头视频帧的推理效果

推理摄像头视频帧能达到16,17帧的水平,可以进一步设计算子、优化程序来改进样例。

  • 使用AIPP算子优化模型的前处理过程

4.1 AIPP算子

AIPP(Artificial Intelligence Pre-Processing)人工智能预处理,用于在AI Core上完成数据预处理,包括改变图像尺寸、色域转换(转换图像格式)、减均值/乘系数(改变图像像素),数据预处理之后再进行真正的模型推理。官方资料

AIPP根据配置方式不同 ,分为静态AIPP和动态AIPP;如果要将原始图片输出为满足推理要求的图片格式,则需要使用色域转换功能;如果要输出固定大小的图片,则需要使用AIPP提供的Crop(抠图)、Padding(补边)功能。

实验所部署的Yolov8-pose姿态估计模型的预处理模块实现的功能包括图片缩放、缩放后图片边缘填充、BGR转RGB、像素值归一化、图片内存排布转换HWC转CHW。

设定尺寸缩放和图片边缘填充保留为OpenCV实现。采用静态AIPP实现色域转换和归一化处理。HWC到CHW的转换在AIPP下将由ATC工具自动完成。

参考官网样例,其配置文件(.cfg)大致如下所示。

aipp_op {
       aipp_mode : static		# 静态AIPP算子
       related_input_rank : 0		# 标识对第1个输入进行AIPP处理
       src_image_size_w : 608		# 输入图像宽度
       src_image_size_h : 608		# 输入图像高度
       crop : false			# 是否裁切	
       input_format : YUV420SP_U8	# 输入图像格式	
       csc_switch : true
       rbuv_swap_switch : false
       matrix_r0c0 : 298
       matrix_r0c1 : 0
       matrix_r0c2 : 409
       matrix_r1c0 : 298
       matrix_r1c1 : -100
       matrix_r1c2 : -208
       matrix_r2c0 : 298
       matrix_r2c1 : 516
       matrix_r2c2 : 0
       input_bias_0 : 16
       input_bias_1 : 128
       input_bias_2 : 128
       mean_chn_0 : 104
       mean_chn_1 : 117
       mean_chn_2 : 123
}

4.2 配置AIPP算子

1OpenCV库读取的图像为BGR格式,而模型需要RGB格式的图像,所以要对图像进行BGR到RGB的色域转换。华为官网对BGR转RGB的色域转换功能给出了如下配置。

cke_2235374.png

2关于归一化处理,官网给出了如下计算公式。

cke_2306935.png

下面展示代码中的归一化的实现方式。

# 归一化代码实现如下, img_pre代表图像
img_pre = (img_pre / 255.0)	#0 - 255 to 0.0 - 1.0

综上,可以得到如下配置结果。

# mean_chn_i = 0
# min_chn_i = 0
# var_reci_chn = 1/255
mean_chn_0 : 0
mean_chn_1 : 0
mean_chn_2 : 0
min_chn_0 : 0.0
min_chn_1 : 0.0
min_chn_2 : 0.0
var_reci_chn_0 : 0.003906
var_reci_chn_1 : 0.003906
var_reci_chn_2 : 0.003906

3AIPP算子配置文件handpose_aipp.cfg如下。

aipp_op { 
related_input_rank : 0
src_image_size_w : 640
src_image_size_h : 640
crop : false
padding : false
aipp_mode: static
input_format : RGB888_U8
csc_switch : false
rbuv_swap_switch : true
mean_chn_0 : 0
mean_chn_1 : 0
mean_chn_2 : 0
min_chn_0 : 0.0
min_chn_1 : 0.0
min_chn_2 : 0.0
var_reci_chn_0 : 0.003906
var_reci_chn_1 : 0.003906
var_reci_chn_2 : 0.003906
}

4.3 OM模型中加入AIPP算子并测试

1通过ATC工具重新生成新的带AIPP算子的om模型,执行如下指令。

# 转换模型
atc --model=handpose.onnx --framework=5  --output=handpose_aipp --soc_version=Ascend310B4 --insert_op_conf=./handpose_aipp.cfg

2模型转换成功后,将新的模型handpose_aipp.om放到开发板上,然后修改之前的样例程序。具体来说,打开det_utils2.py文件,删除前处理模块中的色域转换和归一化处理部分,只保留OpenCV中调整图像尺寸和填充的代码。改动如下所示。

# 模型前处理
def preprocess_warpAffine(image, dst_width=640, dst_height=640):
    scale = min((dst_width / image.shape[1], dst_height / image.shape[0]))
    ox = (dst_width  - scale * image.shape[1]) / 2
    oy = (dst_height - scale * image.shape[0]) / 2
    M = np.array([
        [scale, 0, ox],
        [0, scale, oy]
    ], dtype=np.float32)
    
    img_pre = cv2.warpAffine(image, M, (dst_width, dst_height), flags=cv2.INTER_LINEAR,
    borderMode=cv2.BORDER_CONSTANT, borderValue=(114, 114, 114))
    IM = cv2.invertAffineTransform(M)

    #img_pre = (img_pre[...,::-1] / 255.0).astype(np.float32)    # BGR to RGB, 0 - 255 to 0.0 - 1.0
    #img_pre = img_pre.transpose(2, 0, 1)[None]  # BHWC to BCHW (n, 3, h, w)
    #img_pre = torch.from_numpy(img_pre)	# 实测添加AIPP算子后好像不需要将图像从numpy转化成tentor了
return img_pre, IM

3改动完成后,在om_test.py中的模型替换为新添加AIPP算子的模型进行测试。

下面展示未加入AIPP算子的模型推理单张图片所用时间。

前处理

推理

后处理

画图

38ms

21ms

2.9ms

2.7ms

下面展示加入AIPP算子后模型推理单张图片所用时间。

前处理

推理

后处理

画图

10ms

13ms

2.9ms

2.7ms

下面展示加入AIPP算子后模型推理单张图片的效果。

cke_2387574.png

下面展示加入AIPP算子后模型推理摄像头视频帧的效果。

cke_2459358.png

可以看出得益于AIPP算子,模型的前处理和推理速度得到了极大提升,在640*640 30fps的摄像头下,达到了实时检测的效果。

我要发帖子