由于项目需求,对人体姿态识别这块做了一些学习调研,主流的落地方案是基于一种两阶段的思路。先利用一些算法计算人体骨架关键点,再基于关键点检测结果构造动作特征进行动作识别。这里也采用了这种思路来进行尝试。
一、关键点检测
目前开源的骨架关键点检测算法还比较多,经过比较,选择了以下三种算法进行测试。
1.Openpose
openpose应该是人体关键点检测比较经典的算法之一了,基于此落地的项目也不少。所以首先对这个项目进行尝试。openpose源码是基于c++编写的,并且已经有python版本基于pytorch、tensorflow等框架的移植项目,均在github上开源。我这里用的是pytorch版本进行测试。配置比较简单,主要是opencv和一些绘图工具,torch用来计算加速,没有的话在cpu也能跑。由于项目主要应用场景是从摄像头实时监测,所以直接运行demo_camera.py。这个文件可以检测两部分内容,一部分是身体关键点共18个,一部分是手部关键点共21个。检测结果精确度非常高,也支持多人,唯一缺点就是fps比较低,一帧一帧地显示,不流畅。我设备的显卡是A3000,跑的时候看了一下cpu与gpu都没满。后面换到另一张3060上测试,fps也没上去。在只检测身体,不检测手部的情况下,fps稍微高了一些,但还是不够。
2.SimDR
SimDR(现在项目应该叫SimCC)也是一个python开源项目。这个项目主要是看到网上一些大佬的实践经验,将这个算法与yolo系列算法相结合,先用yolo找出图像中的人物框,再对框中的人物做关键点检测,本来以为这种两阶段的会更慢,结果部署完跑通以后发现摄像头实时检测效果非常好,精确度也不错,也支持多人检测。但是这个项目只有身体部分的检测,而没有手部检测,项目当中有手部检测的需求。
3.Mediapipe
Mediapipe也是一个开源的人体关键点识别库,直接通过pip安装就能使用。经过安装测试,这个库有身体识别模型也有手部识别模型,且在同时检测时也能达到较高的fps,可能的原因是因为这个项目只支持单人的检测,计算量降低了。而项目场景中并没有多人的需求,所以初步采用这个作为关键点检测方案。
二、动作识别
有了较为精准的关键点检测结果,就能根据结果做动作识别映射。自然想到两种方法:一种是基于动作样式来设计判定规则,比如某些关键点之间的距离、形成的夹角等,为每种动作都设计这样的模板,检测结果符合哪种模版就是那个对应的动作。另一种是基于机器学习的方法,训练一个分类器(svm、knn等),这种方法需要采集若干动作数据,并打上对应动作标签。由于项目的具体动作暂时未知,且后期可能有增加动作的需求,采用第一种方法就需要每增加一种动作都要设计模板,第二种则只需要做动作采集一定数据在训练一次分类器,相比第二种更为简单。这里记录一下对第二种方案的实践过程。
项目的主要动作集中在手部,身体部分有一些简单动作,只要手部动作识别准确,基本就能满足要求。这里测试了两种任务,一是石头剪刀布手势识别任务,一种是从零到九手势识别任务。
1.数据采集
Mediapipe输出的landmarklist是关键点的三维度相对坐标,其中z轴表示景深信息。这里没有直接使用坐标值来作为特征,因为动作的判定与每个关键点在图像中的绝对位置是无关的,而与关键点之间的相对位置有关,考虑到手部动作的特点,采用其他20个关键点到掌心关键点(0号关键点)的距离构造特征向量。数据采集方法是写一个采集数据脚本,对着摄像头做分别做剪刀、石头、布的动作,在做对应的动作时,采集的数据就打上对应标签。做每个动作的时候尽量变换不同的角度姿势。贴上部分脚本。思路就是先从landmarks中取21个手部关键点相对坐标值,算出绝对坐标值,然后算出20个关键点到0号点的距离。最后打上标签得到长度为21的列表写进csv文件(0表示剪刀,1表示石头,2表示布)。
res_hand=[]
pix=[]
if hand.multi_hand_landmarks:
for hand_landmarks in hand.multi_hand_landmarks:
print('hand_landmarks:', hand_landmarks)
for item in hand_landmarks.landmark:
res_hand.append([item.x,item.y])
# 关键点可视化
mpDraw.draw_landmarks(
img, hand_landmarks, mphands.HAND_CONNECTIONS)
image_w, image_h, _ = img.shape
for xy in res_hand:
pix.append([xy[0]*image_w,xy[1]*image_h])
dis=[]
if pix:
for i in range(20):
dis.append(math.pow(pix[i+1][0]-pix[0][0],2)+math.pow(pix[i+1][1]-pix[0][1],2))
writer.writerow(dis + [0])
2.训练分类器
这里直接使用knn作为分类器就能够满足分类需求,最后要保存模型。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report
data = pd.read_csv('data.csv')
X = data.iloc[:, :-1].values
y = data.iloc[:, -1].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
report = classification_report(y_test, y_pred)
print(f'Accuracy: {accuracy * 100:.2f}%')
print('Classification Report:')
print(report)
# 保存模型
import joblib
joblib.dump(knn, 'knn_classifier.pkl')
3.手势识别
这里需要注意的就是,训练采用的特征是距离特征,使用模型预测的时候也要将坐标点转化为距离。
knn = joblib.load('knn_classifier.pkl')
predictions = knn.predict(predata)
labels = ['剪刀','石头','布']
for i, prediction in enumerate(predictions):
print(f'Sample {i + 1} prediction: {prediction}')
效果如下
数字手势识别实现过程基本相同,贴一张效果图
4.封装
写了个简单的界面,方便展示与后面使用。