击剑动作识别程序 - 基于 MediaPipe 和 KNN 的动作识别

本程序使用 MediaPipe 和 KNN 算法实现击剑动作识别,并提供可视化结果。支持视频文件输入,并可调整参数,方便用户进行自定义配置。

程序功能

  1. 视频文件输入: 支持 *.mp4 和 *.avi 格式的视频文件。
  2. 动作识别: 使用 MediaPipe 人体姿势模型识别击剑动作,并使用 KNN 算法进行分类。
  3. 可视化结果: 在视频帧上显示识别结果、角度值和可信度。
  4. 参数调整: 可调整 KNN 距离阈值和邻居数量。

使用说明

  1. 运行程序后,会弹出一个窗口,提示选择输入视频文件。
  2. 选择视频文件后,会显示选择的文件路径。
  3. 可以调整距离阈值和邻居数量参数,以优化识别结果。
  4. 点击“运行”按钮,开始处理视频文件。
  5. 视频处理完成后,会将识别结果输出到同目录下的 _OUT.mp4 文件。

代码示例

import cv2
import mediapipe as mp
import math

import numpy as np
import pandas as pd
from sklearn.neighbors import KNeighborsClassifier



# 一些全局变量
label = [' '] # 识别输出标签
poses = '' # 视频输出标签
i = 0
t = 30 # 后处理帧数
posess_a = ''
posess_b = ''
version = 'V7.0'
a = 1.2 # 辅助拟合精度
distance_threshold = 121 # 设置 KNN 距离阈值
neighbors = 2 # 邻居
input_path = ''

# 初始化 MediaPipe 的人体姿势模型
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

# # 打开输入视频文件
# cap = cv2.VideoCapture('5.mp4')
import tkinter as tk
from tkinter import filedialog

# 修改 distance_threshold
def set_distance_threshold(value):
    global distance_threshold
    distance_threshold = int(value)





# 创建窗口
root = tk.Tk()
root.withdraw()


# 显示使用说明信息
info = f'欢迎使用击剑动作识别程序 {version}\n作者:冯楠20201888\n指导教师:朱勇\n请选择输入视频文件\n支持格式:*.mp4, *.avi\n点击确认选择文件'
tk.messagebox.showinfo('使用说明', info)

# 定义选择文件函数
def select_file():
    global input_path
    root3 = tk.Tk()
    root3.title('选择文件')
    input_path = filedialog.askopenfilename(title='选择输入视频文件', filetypes=[('视频文件', '*.mp4;*.avi'), ('所有文件', '*.*')])
    root3.destroy()
    if not input_path:
        tk.messagebox.showerror('错误', '未选择输入视频文件!')
        root3.destroy()
        return
    # 更新标签显示选择的文件路径
    file_label.config(text=input_path)
def closeit():
    root2.destroy()

# 创建窗口 2
root2 = tk.Tk()
root2.title('选择文件,调整参数')
root2.geometry('400x600')

# 创建标题
file_titles = tk.Label(root2, text='击剑动作识别程序', font=('宋体', 18))
file_titles.pack(pady=20)
# 创建标签
file_label = tk.Label(root2, text='未选择文件')
file_label.pack(pady=20)
file_label.config(bg='#e9ccd3')

select_button = tk.Button(root2, text='选择文件', command=select_file)
select_button.pack(pady=10)
# 创建标题
file_titles = tk.Label(root2, text='参数调整', font=('微软雅黑', 13))
file_titles.pack(pady=20)
# 创建滑动条
scale = tk.Scale(root2, from_=0, to=200, orient=tk.HORIZONTAL, length=150, label='距离阈值', command=set_distance_threshold)
scale.set(distance_threshold)
scale.pack()



def set_neighbors(val):
    global neighbors
    neighbors = int(val)
# 创建滑动条
scale = tk.Scale(root2, from_=1, to=10, orient=tk.HORIZONTAL, length=150, label='邻居数量', command=set_neighbors)
scale.set(neighbors)
scale.pack()


# 创建按钮

run_button = tk.Button(root2, text='运行', command=closeit)
run_button.pack(pady=20)

# 创建标签
file_name = tk.Label(root2, text=f'作者:冯楠2020188 物联网2班\n指导教师:朱勇\n版本:{version}', font=('Arial', 14))
file_name.pack(pady=20)
# 关闭窗口
root.destroy()
# 运行窗口
root2.attributes('-topmost', True) # 窗口在最前
root2.mainloop()

# 打开输入视频文件
print('打开输入视频文件')
cap = cv2.VideoCapture(input_path)

# 获取输入视频的帧率和分辨率
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

# 创建输出视频文件
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(f'{input_path}_OUT.mp4', fourcc, fps, (width, height))

# 加载动作数据集



# 数据清洗
# 将每个小数据集中偏离平均值超过 2 倍标准差的样本删除
print('数据清洗')



# 将数据集合并为一个大的数据集





# 训练 KNN 分类器


root = tk.Tk()
root.withdraw()
info1 = '左上角为当前动作,绿色方块代表执剑手位置\n点击确认开始\n点击右上角 x 退出程序\n在视频处理过程中可以按 q 退出'
tk.messagebox.showinfo('即将开始', info1)
root.destroy()
# 处理视频文件中的每一帧
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.3) as pose:
    while cap.isOpened():
        # 读取一帧
        ret, frame = cap.read()
        # if ret is None:
        #     continue
        if not ret:
            break


        # 将帧转换为 RGB 格式
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # 处理人体姿势检测
        results = pose.process(image)

        # 判断是否检测到人体
        if results.pose_landmarks:
            # 绘制人体骨架
            mp_drawing.draw_landmarks(
                frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)



            # 计算腿与右手的角度


            # 获取左肩、左肘和左手腕


            # 获取左臀、左膝和左踝


            # 获取右臀、右膝和右踝


            # 获取右肩、右肘和右手腕

            # 获取左肩、左肘和左手腕 new


            # 获取左髋、左膝和左踝关键点的信息

            # 获取左髋、左膝和左踝的角度

            # 获取腰部、左髋和左肩的角度

            # 获取右髋、右膝和右踝关键点的信息

            # 获取右髋、右膝和右踝的角度

            # 获取腰部、右髋和右肩的角度

            # 获取鼻子、右肩和右手腕关键点的信息

            # 获取鼻子、右肩和右手腕的角度





            # 将数据输入 KNN 分类器进行预测
            distances, indices = knn.kneighbors([[angle, angle1, angle_dl, angle_dr, angle_tr, angle_tr, angle_tl,
                                                  angle_lka, angle_hls, angle_rka, angle_hrs, angle_nwr]])
            if distances[0][0] > distance_threshold:
                label = ['']
                confidence = 0
            else:
                label = knn.predict([[angle, angle1, angle_dl, angle_dr, angle_tr, angle_tr, angle_tl, angle_lka,
                                      angle_hls, angle_rka, angle_hrs, angle_nwr]])
                confidence = 1 - distances[0][0] / distance_threshold

            # label = knn.predict([[angle, angle1, angle_dl, angle_dr, angle_tr, angle_tr, angle_tl,angle_lka,angle_hls,angle_rka,angle_hrs,angle_nwr]])

            print([angle, angle1, angle_dl, angle_dr, angle_tr, angle_tr, angle_tl, angle_lka, angle_hls, angle_rka,
                   angle_hrs, angle_nwr])
posess_b = poses

            if label[0] == 'warm-up':
                i = t
                poses = 'warm-up'
            elif label[0] == 'hit':
                i = t
                if (angle > -20 * a and angle <= 5 * a) and ((angle_dr > 130.5 / a and angle_dr <= 155 * a) or (
                        angle_dr > 205 / a and angle_dr <= 212 * a)) and (
                        (angle_tr > 239 / a and angle_tr <= 260 * a) or (angle_tr > 147 / a and angle_tr <= 170 * a)):
                    poses = 'shi zhan poses'
                else:
                    poses = 'hit'
            elif label[0] == 'SHIZHAN POSE':
                i = t
                poses = 'shi zhan poses'
            elif label[0] == 'respect':
                i = t
                if (angle > -20 * a and angle <= 5 * a) and ((angle_dr > 130.5 / a and angle_dr <= 155 * a) or (
                        angle_dr > 205 / a and angle_dr <= 212 * a)) and (
                        (angle_tr > 239 / a and angle_tr <= 260 * a) or (angle_tr > 147 / a and angle_tr <= 170 * a)):
                    poses = 'shi zhan poses'
                else:
                    poses = 'respect'
            elif label[0] == 'gongbu':
                poses = 'gongbu'
                i = t
            else:
                if i != 0:
                    peses = poses
                    i = i - 1
                else:
                    poses = 'N/A'
                    i = t

            # 绘制头部关键点
            nose = results.pose_landmarks.landmark[mp_pose.PoseLandmark.NOSE]
            x, y = int(nose.x * width), int(nose.y * height)
            cv2.circle(frame, (x, y), 20, (220, 200, 100), -1)
            # 绘制执剑关键点
            zhijian = results.pose_landmarks.landmark[mp_pose.PoseLandmark.RIGHT_THUMB]
            x, y = int(zhijian.x * width), int(zhijian.y * height)
            cv2.rectangle(frame, (x - 20, y - 20), (x + 20, y + 20), (0, 255, 0), -1)

            # 显示可信度

            if confidence > 0:
                cv2.putText(frame, 'Current frame action:' + label[0] + ' ' + str(round(confidence, 2)), (5, 100), cv2.FONT_HERSHEY_SIMPLEX, 1,
                            (5, 200, 0), 2)
            else:
                cv2.putText(frame, 'Current frame action:' + 'N/A', (5, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (5, 200, 0), 2)



            if (possess_b != poses):
                print('dou')
            else:
                possess_a = poses



            # 在输出图片上显示角度值和动作类型
            cv2.putText(frame, 'Angle: {:.2f}'.format(angle), (5, 210),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)

            cv2.putText(frame, 'Angle_rka: {:.2f}'.format(angle_rka), (5, 225),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (100, 15, 255), 2)

            cv2.putText(frame, 'Angle_nwr: {:.2f}'.format(angle_nwr), (5, 240),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 100, 255), 2)
            cv2.putText(frame, 'angle_lka: {:.2f}'.format(angle_lka), (5, 255),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

            cv2.putText(frame, 'angle_tl: {:.2f}'.format(angle_tl), (5, 270),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 150, 255), 2)

            cv2.putText(frame, 'angle_hrs: {:.2f}'.format(angle_hrs), (5, 285),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (100, 0, 255), 2)

            cv2.putText(frame, 'Current action:' + possess_a, (5, 60),
                        cv2.FONT_HERSHEY_SIMPLEX, 1.1, (2, 2, 255), 2)


        else:
            # 如果未检测到人体,则跳过本帧处理
            cv2.putText(frame, 'No body detected', (5, 50),
                        cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

        # 将帧写入输出视频文件
        out.write(frame)

        # 显示当前帧的结果
        cv2.imshow('MediaPipe Pose Detection press q exit', frame)

        # 检测是否按下 q 键退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
# 显示使用说明信息
root = tk.Tk()
root.withdraw()
info = f'视频已输出至 {input_path}_OUT.mp4'
tk.messagebox.showinfo('处理完成', info)

# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
未来改进的思路和方法内容:1. 提高模型的准确性:可以尝试使用更复杂的神经网络结构,增加训练数据集的多样性,或者使用迁移学习的方法。

2. 实时性优化:可以使用 GPU 加速或者将模型部署在云端,减少本地计算压力,提高程序运行速度。

3. 动作类型扩展:可以增加更多的击剑动作类型,例如攻击、防守、移动等,以增加程序的实用性。

4. 改进交互界面:可以设计更友好的交互界面,例如添加音频提示、动画演示等,提高用户体验。

5. 实现多人姿势识别:可以使用多人姿势识别模型,实现同时识别多个人的击剑动作,以满足更多场景的需求。

未来改进方向

  1. 提高模型准确性: 可以尝试使用更复杂的神经网络结构,增加训练数据集的多样性,或者使用迁移学习的方法。
  2. 实时性优化: 可以使用 GPU 加速或者将模型部署在云端,减少本地计算压力,提高程序运行速度。
  3. 动作类型扩展: 可以增加更多的击剑动作类型,例如攻击、防守、移动等,以增加程序的实用性。
  4. 改进交互界面: 可以设计更友好的交互界面,例如添加音频提示、动画演示等,提高用户体验。
  5. 实现多人姿势识别: 可以使用多人姿势识别模型,实现同时识别多个人的击剑动作,以满足更多场景的需求。
击剑动作识别程序 - 基于 MediaPipe 和 KNN 的动作识别

原文地址: https://www.cveoy.top/t/topic/gRd0 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录