作者 | 李秋键
出品 | AI科技大本营
引言:随着计算机视觉领域中视频动作识别技术的发展,体育动作识别研究在统计运动动作特点、运动学研究、体育教学展示等方面的应用越来越广泛。对于各种球类比赛,依据比赛类型, 可以将它们的结构特征分为时间和比分两种类型。时间类型的体育项目如篮球、足球和橄榄球等,在比赛过程中没有属于某一方球员专门的区域,双方球员在位置上处于混合交错状态,在一定时间间隔内通过团队合作来取得比赛的胜利。比分类型的项目包括网球、羽毛球、乒乓球等,比赛时双方球员始终在属于自己的区域内运动,和对手在位置上处于对峙状态,这种类型通常是球员经过自身水平的发挥来赢取比赛。观看该类比赛时,观众往往会关注球员的动作特点。
在羽毛球比赛中,运动员的动作姿态信息可为理解比赛过程、发现球员动作特点提供重要线索。羽毛球运动与排球、网球和乒乓球运动特点相似,均满足马尔可夫过程条件,比赛中运动员的每次击球动作在瞬间完成。为了更好地辅助教练或观众理解并把握羽毛球视频中球员动作等关键信息,实现对羽毛球运动员的动作智能识别是有意义的。
目前计算机视觉技术在视频动作识别方向的相关研究已经取得重大突破,但大多是针对不同日常动作的广义性动作识别,缺乏针对羽毛球视频动作识别的相关研究。若能对羽毛球视频中的击球动作进行时序定位并且能比较准确地判断出羽毛球视频中的击球动作类型,则可为观众提供各类击球动作类型的视频集锦。此外,在体育视频分析领域中,也可根据羽毛球的动作分类迁移至网球等项目,因它们的比赛形式与羽毛球有许多相同之处,更容易进行运动特征的迁移。
故今天我们将使用torch搭建LSTM实现对羽毛球动作的实时训练并预测,本文将其分为数据集制作、数据处理、模型搭建以及可视化几个步骤,模型在训练2000轮实现效果如下如下(左侧为当前动作,右侧为预测出的未来10帧后的羽毛球动作):
羽毛球动作识别发展介绍
针对羽毛球的击球动作识别,Chu 等人采用了基于姿态识别的方法,从球员的边界框提取方向梯度直方图 HOG,并在 HOG 基础上基于支持向量机 SVM对击球动作进行分类,但其使用的训练和测试数据是击球瞬间的单个图像,而对于击球姿态十分相似的不同击球动作很可能会混淆,如杀球与高远球,平抽与吊球。Careelmont对压缩羽毛球视频的镜头进行分类,并通过检测羽毛球的移动轨迹来识别击球动作。Ramasinghe 等提出了一种基于密集轨迹和轨迹对齐的 HOG 特征的羽毛球视频动作识别方法,将球员击球动作分为正手击球、反手击球、杀球和其他类型,但 HOG 本身不具有尺度不变性,且由于梯度的性质,HOG 对噪点相当敏感。杨静等人在体育视频时常具有像素品质欠佳、非静态视频及图像的分辨率较低的问题背景下,提出一种基于光流的运动描述符,并通过检测关键音频元素捕获球员的挥拍击球图像,最后采用支持向量机,对运动员的三种典型挥拍动作——上挥拍、左挥拍、右挥拍进行分类。Wang 等提出了一种基于身体传感器网络的双层隐马尔可夫模型分类算法来识别羽毛球击球类型,但其针对于传感器捕获的击球状态数据,并不适用于对视频中的羽毛球动作进行有效识别。Rahmad 等人比较了 AlexNet、GoogLeNet、VggNet-16 和 VggNet-19 四种不同的深度卷积预训练模型在对羽毛球比赛图像进行分类时的表现,以识别运动员的不同动作,最终表明 GoogLeNet 的分类准确率最高,但其针对的仍是羽毛球比赛击球瞬间的静态图像,未能对羽毛球动作元视频进行动作分类识别。
羽毛球动作预测搭建
为了更好的研究对羽毛球视频动作识别,我们这里实现对羽毛球视频球员击球动作进行时域定位。
这里程序的设计分为以下几个步骤,分别为数据集制作、数据处理、模型搭建以及可视化几个步骤。
2.1 骨骼数据集提取
这里我们将准备好的视频素材放置项目文件下,使用data_deal.py提取骨骼点存储。针对2.mp4视频文件使用openpose逐帧提取骨骼数据并存入txt文件中。代码如下:
代码语言:javascript复制parser = argparse.ArgumentParser(description='Action Recognition by OpenPose')
parser.add_argument('--video', help='Path to video file.')
args = parser.parse_args()
# 导入相关模型
estimator = load_pretrain_model('VGG_origin')
# 参数初始化
realtime_fps = '0.0000'
start_time = time.time()
fps_interval = 1
fps_count = 0
run_timer = 0
frame_count = 0
# 读写视频文件
cap =cv.VideoCapture("2.mp4")
#video_writer = set_video_writer(cap, write_fps=int(7.0))
# 保存关节数据的txt文件,用于训练过程(for training)
f = open('origin_data.txt', 'a ')
num=0
while cv.waitKey(1) < 0:
has_frame, show = cap.read()
if has_frame:
fps_count = 1
frame_count = 1
# pose estimation
humans = estimator.inference(show)
# get pose info
pose = TfPoseVisualizer.draw_pose_rgb(show, humans) # return frame, joints, bboxes, xcenter
#video_writer.write(show)
if len(pose[-1])==36:
num =1
print(num)
# 采集数据,用于训练过程(for training)
joints_norm_per_frame = np.array(pose[-1]).astype(np.str)
f.write(' '.join(joints_norm_per_frame))
f.write('n')
cv.imshow("tets",show)
cv.waitKey(1)
else:
break
cap.release()
f.close()
2.2 数据处理
通过对数据观察发现,由于拍摄的视频遮挡较多,部分肢体提取为0会较大的影响模型效果,这里将这几个部位去除。代码如下:
代码语言:javascript复制f=open('origin_data.txt')
text=f.read()
f.close()
datasets=[]
text=text.split("n")
for i in text:
temp=i.split(" ")
temp1=[]
state=True
for j in range(len(temp)):
try:
temp1.append(float(temp[j]))
except:
pass
if len(temp1) == 36:
temp1.pop(28)
temp1.pop(28)
temp1.pop(30)
temp1.pop(30)
for t in temp1:
if t==0.:
state=False
if state:
datasets.append(temp1)
flap=30#
x_data = datasets[:-1-flap]
y_data=datasets[flap:-1]
n=len(x_data)
2.3 LSTM模型搭建和训练
这里设置LSTM层神经元64,设置损失函数为为MSE误差函数,优化器为adam优化器,迭代次数为100轮,并将其损失图动态绘制。代码如下:
代码语言:javascript复制times=[]
losss=[]
nums=0
Epoch=100
correct=0
for k in range(Epoch):
for i in range(n):
x_np=np.array(x_data[i],dtype='float32')#此时x的维度为1维
y_np=np.array(y_data[i],dtype='float32')
#需要把x维度扩充到三个维度,[batch,time_step,input_size]
x=variable(torch.from_numpy(x_np[np.newaxis,:,np.newaxis]))
y=variable(torch.from_numpy(y_np[np.newaxis,:,np.newaxis]))
prediction=rnn(x)
if prediction.flatten().data.numpy().any==y.flatten().data.numpy().any:
correct =1
loss=loss_func(prediction,y)
optim.zero_grad()
loss.backward()
optim.step()
nums = 1
accuracy=float(correct/nums)
print("|Epoch:",k,"|step:",nums,"|loss:",loss.data.numpy(),"|accuracy:%.4f"�curacy)
times.append(nums)
losss.append(float(loss.data))
plt.plot(times,losss)
plt.pause(0.05)
2.4 模型可视化
根据预测出的骨骼坐标,定义基本骨骼连接方法和颜色,同时这里还要考虑到已经去除的骨骼,最终代码如下:
代码语言:javascript复制import cv2
def draw(test):
back=cv2.imread("back.jpg")
image_h, image_w ,c= back.shape
centers = {}
CocoColors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0],
[0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255],
[170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85], [255, 0, 85]]
CocoPairs = [
(1, 2), (1, 5), (2, 3), (3, 4), (5, 6), (6, 7), (1, 8), (8, 9), (9, 10), (1, 11),
(11, 12), (12, 13), (1, 0), (0, 14), (14, 15), (5, 15)
]#修改了
for pos in range(0,16):
center = (int((test[2*pos] * (image_w//2) 0.5)), int((test[2*pos 1] * (image_h//2) )))
centers[pos] = center
cv2.circle(back, center, 3, CocoColors[pos], thickness=3, lineType=8, shift=0)
for pair_order, pair in enumerate(CocoPairs):
cv2.line(back, centers[pair[0]], centers[pair[1]], CocoColors[pair_order], 3)
完整代码:
https://download.csdn.net/download/qq_42279468/72398200
李秋键,CSDN博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap竞赛获奖等。