基于mediapipe的姿态识别和简单行为识别

文章目录

  • 学习目标
  • 1、可以识别到人体姿态关键点
  • 2、可以通过角度识别的方法识别到人体的动作(自定义)
  • 一、mediapipe的安装
  • 二、使用mediapipe检测关键点
  • 1、mediapipe的介绍
  • 2、使用mediapipe检测人体
  • (0)检测前的准备工作
  • (1)检测图片
  • (2)检测视频
  • 三、使用mediapipe-BlazePose检测自定义简单行为
  • 1、原理介绍
  • 2、实现过程
  • 学习目标

    1、可以识别到人体姿态关键点

    2、可以通过角度识别的方法识别到人体的动作(自定义)

    源码地址:🚀🚀🚀🚀

    一、mediapipe的安装

    其实这部分很简单,直接在windows命令行的环境下

    pip install mediepipe
    

    就可以啦

    二、使用mediapipe检测关键点

    1、mediapipe的介绍

    Mediapipe是一个用于构建机器学习管道的框架,用户处理视频、音频等时间序列数据。这个跨平台框架适用于桌面/服务器、Android、ios和各类嵌入式设备。
    目前mediapipe包含16个solutions,分别为

    人脸检测
    Face Mesh
    虹膜
    手
    姿态
    人体
    人物分割
    头发分割
    目标检测
    Box Tracking
    instant Motion Tracking
    3D目标检测
    特征匹配
    AutoFlip
    MediaSequence
    YouTuBe_8M
    

    ![在这里插入图片描述](https://i3.wp.com/img-blog.csdnimg.cn/1fbcd4d624b14681995fdc90882f2006.png

    总的来说,mediapipe是一个很好的库,可以解决我们处理ML项目中面临的大部分麻烦,而且很适合做行为识别方向的小伙伴练手使用。

    2、使用mediapipe检测人体

    这里仅使用mediapipe关于人体识别的方法(solution),谷歌官方将这种人体姿态识别的方法叫做Blazepose。

    (0)检测前的准备工作

    '''导入一些基本的库'''
    import cv2
    import mediapipe as mp
    import time
    from tqdm import tqdm
    import numpy as np
    from PIL import Image, ImageFont, ImageDraw
    # ------------------------------------------------
    #   mediapipe的初始化
    # 	这一步是必须的,因为要使用到以下定义的几个类
    # ------------------------------------------------
    mp_pose = mp.solutions.pose
    mp_drawing = mp.solutions.drawing_utils
    pose = mp_pose.Pose(static_image_mode=True)
    

    (1)检测图片

    def process_frame(img):
        start_time = time.time()
        h, w = img.shape[0], img.shape[1]               # 高和宽
        # 调整字体
        tl = round(0.005 * (img.shape[0] + img.shape[1]) / 2) + 1
        tf = max(tl-1, 1)
        # BRG-->RGB
        img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # 将RGB图像输入模型,获取 关键点 预测结果
        results = pose.process(img_RGB)
        keypoints = ['' for i in range(33)]
        if results.pose_landmarks:
            mp_drawing.draw_landmarks(img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
            for i in range(33):
                cx = int(results.pose_landmarks.landmark[i].x * w)
                cy = int(results.pose_landmarks.landmark[i].y * h)
                keypoints[i] = (cx, cy)                                 # 得到最终的33个关键点
        else:
            print("NO PERSON")
            struction = "NO PERSON"
            img = cv2.putText(img, struction, (25, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.25, (255, 255, 0),
                              6)
        end_time = time.time()
        process_time = end_time - start_time            # 图片关键点预测时间
        fps = 1 / process_time                          # 帧率
        colors = [[random.randint(0,255) for _ in range(3)] for _ in range(33)]
        radius = [random.randint(8,15) for _ in range(33)]
        for i in range(33):
            cx, cy = keypoints[i]
            #if i in range(33):
            img = cv2.circle(img, (cx, cy), radius[i], colors[i], -1)
        '''str_pose = get_pos(keypoints)            #获取姿态
        cv2.putText(img, "POSE-{}".format(str_pose), (12, 100), cv2.FONT_HERSHEY_TRIPLEX,
                    tl / 3, (255, 0, 0), thickness=tf)'''
        cv2.putText(img, "FPS-{}".format(str(int(fps))), (12, 100), cv2.FONT_HERSHEY_SIMPLEX,
                    tl/3, (255, 255, 0),thickness=tf)
        return img
    

    如果需要执行代码,则在文末的主函数中使用

    if __name__ == '__main__':
    	# 读取图片
    	img0 = cv2.imread("./data/outImage--20.jpg")
    	# 因为有中文路径,所以加上此行
        image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
        img = image.copy()
        # 检测关键点,得到的image是检测过后的图片
        image = process_frame(img)
        # 使用matplotlib画图    
        fig, axes = plt.subplots(nrows=1, ncols=2)
        axes[0].imshow(img0[:,:,::-1])
        axes[0].set_title("原图")
        axes[1].imshow(image[:,:,::-1])
        axes[1].set_title("检测并可视化后的图片")
        plt.rcParams["font.sans-serif"] = ['SimHei']
        plt.rcParams["axes.unicode_minus"] = False
        plt.show()
        fig.savefig("./data/out.png")
    

    最后附上检测效果。

    (2)检测视频

    任何不涉及3D卷积的机器视觉方法,检测视频其实就是检测图片。因为视频是由多帧图片融合得来的。
    比如说一个30帧的视频,那么它的每一秒钟,就是由30张图片叠加而成。
    将这些分割出的图片分别进行检测,最后将检测的图片进行融合,得到的就是检测后的视频。
    有了这个依据,我们就可以把图片检测过程写成一个函数,在视频的每一帧中调用这个函数就可以啦
    一般使用opencv库将视频分解为图片帧的形式,示例代码如下:

    def video2image(videoPath="./video/demo1.mp4",
                    image_dir="./image"):
        '''videoPath是视频路径, image_dir是图片保存的文件夹路径'''
        cap = cv2.VideoCapture(videoPath)
        frame_count = 0
        while(cap.isOpened()):
            success,frame = cap.read()
            if not success:
                break
            frame_count += 1
        print("视频总帧数:", frame_count)
        cap.release()
        cap = cv2.VideoCapture(videoPath)
        count = 0
        with tqdm(total=frame_count-1) as pbar:
            try:
                while(cap.isOpened()):
                    success, frame = cap.read()
                    if not success:
                        break
                    #处理帧
                    try:
                        if count % 20 == 0:
                            cv2.imwrite("{}/outImage--{}.jpg".format(image_dir, count), frame)
                    except:
                        print("error")
                        pass
                    if success == True:
                        pbar.update(1)
                        count+=1
            except:
                print("中途中断")
                pass
        cv2.destroyAllWindows()
        cap.release()
        print("视频已经处理结束,进行下一步操作!!!")
    

    那么落实到本文想要实现的功能上,就可以在视频分解出的帧后面加上图片检测函数。
    代码如下所示:

    def process_video(video_path="./Data.mp4"):
        video_flag = False
        cap = cv2.VideoCapture(video_path)
        out_path = "./out_Data.mp4"
        print("视频开始处理……")
        frame_count = 0
        while (cap.isOpened()):
            success, frame = cap.read()
            frame_count += 1
            if not success:
                break
        cap.release()
        print("总帧数 = ", frame_count)
        cap = cv2.VideoCapture(video_path)
        if video_flag == False:
            frame_size = cap.get(cv2.CAP_PROP_FRAME_WIDTH), cap.get(cv2.CAP_PROP_FRAME_HEIGHT)  #处理图像的尺寸。
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')    #保存视频文件的格式为mp4
            fps = cap.get(cv2.CAP_PROP_FPS)
            out = cv2.VideoWriter(out_path, fourcc, fps, (int(frame_size[0]),int(frame_size[1])), ) #输出图像的句柄
        with tqdm(total=frame_count-1) as pbar:
            try:
                while cap.isOpened():
                    success, frame = cap.read()
                    if success:
                        pbar.update(1)
                        frame = process_frame(frame)					# frame就是视频截取的帧,process_frame表示对其检测。
                        cv2.namedWindow("frame", cv2.WINDOW_NORMAL)
                        cv2.imshow("frame", frame)
                        out.write(frame)
                        if cv2.waitKey(1) == 27:
                            break
                    else:
                        break
            except:
                print("中途中断")
                pass
        cap.release()
        cv2.destroyAllWindows()
        out.release()
        print("视频已保存至", out_path)
    

    有了视频的代码,那么就可以在主函数中对其进行调用,可视化效果就不展示了。

    三、使用mediapipe-BlazePose检测自定义简单行为

    1、原理介绍

    将Mediapipe用于行为检测是比较复杂的一件事;如果这样做,那么行为检测的精度就完全取决于Mediapipe关键点的检测精度。
    于是可以根据下图中人的关节夹角来对人的位姿进行检测。

    如举手的时候,大臂与水平方向夹角是一定大于0度的。
    叉腰的时候,双手垂下,大臂与小臂的夹角大于60度小于120度
    那么这样就可以完成一些基本动作的分类。
    我这里只列出几种比较简单的。
    (1)举双手(2)举左手(3)举右手(4)叉腰(5)比三角形
    先看一下效果图

    2、实现过程

    首先要知道,由坐标求得矢量的公式,其实就是两个坐标相减。
    那么求两个矢量之间的夹角公式:
    请添加图片描述
    那么到代码中就是:

    v1 = (x1, y1) - (x2, y2)
    v2 = (x0, y0) - (x2, y2)
    def get_angle(v1, v2):
        angle = np.dot(v1, v2) / (np.sqrt(np.sum(v1 * v1)) * np.sqrt(np.sum(v2 * v2)))
        angle = np.arccos(angle) / 3.14 * 180
    
        cross = v2[0] * v1[1] - v2[1] * v1[0]
        if cross < 0:
            angle = - angle
        return angle
    

    这样就可以得到两个矢量的夹角。
    之后,就可以通过夹角对行为进行判断,这里的规则是

    举双手				左手矢量小于0右手矢量夹角大于0
    举左手				左手矢量小于0右手矢量小于0
    举右手				左手矢量大于0右手矢量大于0
    比三角形				举双手的同时,大臂与小臂的夹角小于120度
    正常				左手矢量大于0右手矢量夹角小于0
    叉腰				正常情况下,左手肘夹角小于120度,右手肘夹角也小于0
    

    给出的代码示例如下:

    def get_pos(keypoints):
        str_pose = ""
        # 计算左臂与水平方向的夹角
        keypoints = np.array(keypoints)
        v1 = keypoints[12] - keypoints[11]
        v2 = keypoints[13] - keypoints[11]
        angle_left_arm = get_angle(v1, v2)
        #计算右臂与水平方向的夹角
        v1 = keypoints[11] - keypoints[12]
        v2 = keypoints[14] - keypoints[12]
        angle_right_arm = get_angle(v1, v2)
        #计算左肘的夹角
        v1 = keypoints[11] - keypoints[13]
        v2 = keypoints[15] - keypoints[13]
        angle_left_elow = get_angle(v1, v2)
        # 计算右肘的夹角
        v1 = keypoints[12] - keypoints[14]
        v2 = keypoints[16] - keypoints[14]
        angle_right_elow = get_angle(v1, v2)
    
        if angle_left_arm<0 and angle_right_arm<0:
            str_pose = "LEFT_UP"
        elif angle_left_arm>0 and angle_right_arm>0:
            str_pose = "RIGHT_UP"
        elif angle_left_arm<0 and angle_right_arm>0:
            str_pose = "ALL_HANDS_UP"
            if abs(angle_left_elow)<120 and abs(angle_right_elow)<120:
                str_pose = "TRIANGLE"
        elif angle_left_arm>0 and angle_right_arm<0:
            str_pose = "NORMAL"
            if abs(angle_left_elow)<120 and abs(angle_right_elow)<120:
                str_pose = "AKIMBO"
        return str_pose
    

    得到的str_pose就是行为字符串,在process_frame中可以在图片帧中可视化。
    到这里,关键点检测与简单行为检测已经全部介绍结束了,如果实在复现不成的,可以直接看我代码仓库中的源码

    计划:将在未来的博客里,把基于wxpython的UI设计与Mediapipe进行融合,实现可视化的交互过程,请持续关注。

    学习之路逆水行舟,加油加油!!!

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于mediapipe的姿态识别和简单行为识别

    发表评论