计算机视觉——基于MediaPipe实现人体姿态估计与不良动作检测

概述

正确的身体姿势是个人整体健康的关键。然而,保持正确的身体姿势可能会很困难,因为我们常常会忘记。本博客文章将逐步指导您构建一个解决方案。最近,我们使用 MediaPipe POSE 进行身体姿势检测,效果非常好!

一、使用 MediaPipe Pose 进行身体姿势检测

MediaPipe Pose 是一种高保真度的身体姿势追踪解决方案,可以从 RGB 帧中渲染 33 个 3D 关键点和一个背景分割掩码(注意是 RGB 图像帧)。它利用了 BlazePose拓扑结构,这是 COCO、BlazeFace和 BlazePalm拓扑结构的超集。

1. 应用目标 —— MediaPipe 身体追踪

我们的目标是从完美的侧视图中检测一个人,并测量颈部和躯干相对于某个参考轴的倾斜角度。通过监控当人弯腰低于某个特定阈值角度时的倾斜角度来实现。

其他功能包括测量特定姿势的时间和相机对齐。我们必须确保相机能够正确地拍摄到侧面视角,因此我们需要对齐功能。

2. 身体姿势检测与分析应用工作流程

3. 准备工作

OpenCV 和 MediaPipe 是我们需要的主要包。使用代码文件夹中提供的 requirements.txt 文件来安装依赖项。

pip install -r requirements.txt

您需要具备 OpenCV Python 的基础知识才能理解代码。如果您是 OpenCV 的新手,这里有一些专门为初学者准备的 OpenCV 教程。

二、代码实现

1. 导入库

import cv2
import time
import math as m
import MediaPipe as mp

2. 计算偏移距离的函数

设置要求人处于正确的侧视图中。findDistance 函数帮助我们确定两点之间的偏移距离。它可以是臀部点、眼睛或肩膀。

这些点被选中是因为它们总是或多或少关于人体中心轴对称。通过这种方式,我们可以在脚本中加入相机对齐功能。

def findDistance(x1, y1, x2, y2):dist = m.sqrt((x2 - x1)**2 + (y2 - y1)**2)return dist

3. 计算身体姿势倾斜角度的函数

角度是判断姿势的主要决定性因素。我们使用颈部线躯干线与 y 轴之间的夹角。颈部线连接肩膀和眼睛,这里我们以肩膀为支点。

同样,躯干线连接臀部和肩膀,其中臀部被视为支点。

图:颈部倾斜角度测量

以颈部线为例,我们有以下点:

  • P1(x1, y1):肩膀
  • P2(x2, y2):眼睛
  • P3(x3, y3):通过 P1 的垂直轴上的任意一点

显然,对于 P3,x 坐标与 P1 相同。由于 y3 对所有 y 都有效,为了简单起见,我们取 y3 = 0。

我们采用向量方法来求三个点之间的夹角。向量 P****12P****13 之间的夹角由以下公式给出:

# 计算角度。
def findAngle(x1, y1, x2, y2):theta = m.acos((y2 - y1) * (-y1) / (m.sqrt((x2 - x1)**2 + (y2 - y1)**2) * y1))degree = int(180 / m.pi) * thetareturn degree

4. 发送不良姿势警报的函数

当检测到不良姿势时,使用此函数发送警报。我们将其保留为空,供您自行发挥创意并根据需要进行自定义。例如,您可以连接一个 Telegram Bot 来发送警报,这非常简单。链接在参考资料部分[6]。或者您可以更进一步,创建一个安卓应用。

def sendWarning(x):pass

5. 初始化

在这里初始化常量和方法。这些应该通过内联注释一目了然。

# 初始化帧计数器。
good_frames = 0
bad_frames = 0# 字体类型。
font = cv2.FONT_HERSHEY_SIMPLEX# 颜色。
blue = (255, 127, 0)
red = (50, 50, 255)
green = (127, 255, 0)
dark_blue = (127, 20, 0)
light_green = (127, 233, 100)
yellow = (0, 255, 255)
pink = (255, 0, 255)# 初始化 MediaPipe 姿势类。
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()

6.身体姿势检测

6.1 创建视频捕获和视频写入对象

为了演示,我们使用预先录制的视频样本。在实际应用中,您需要将网络摄像头定位以捕捉您的侧视图。在以下代码片段中,创建了视频捕获和视频写入对象。

正如您所看到的,我们正在获取视频元数据以创建视频捕获对象。如果您想以 mp4 格式写入,请将编码器改为 *’mp4v’。有关视频写入器和处理编码器的更直观指南,请查看有关 [OpenCV 视频写入器](https://learnopencv.com/
reading-and-writing-videos-using-opencv/)的文章。

# 对于网络摄像头输入,将文件名替换为 0。
file_name = 'input.mp4'
cap = cv2.VideoCapture(file_name)# 元数据。
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))
frame_size = (width, height)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')# 视频写入器。
video_output = cv2.VideoWriter('output.mp4', fourcc, fps, frame_size)

6.2 身体姿势检测主循环

Pose() 解决方案的可配置 API 不需要太多调整。默认值足以检测姿势关键点。然而,如果我们要生成分割掩码,则必须将 ENABLE_SEGMENTATION 标志设置为 True。以下是 MediaPipe 姿势解决方案中的一些可配置 API。

  • STATIC_IMAGE_MODE:这是一个布尔值。如果设置为 True,则会对每个输入图像运行人物检测。对于视频来说,这不是必要的,因为在视频中,检测运行一次后会跟随关键点跟踪。默认值为 False

  • MODEL_COMPLEXITY:默认值为 1。它可以是 0、1 或 2。如果选择更高的复杂性,则推理时间会增加。

  • ENABLE_SEGMENTATION:如果设置为 True,则解决方案会生成一个分割掩码以及姿势关键点。默认值为 False

  • MIN_DETECTION_CONFIDENCE:范围为 [0.0 – 1.0]。顾名思义,这是检测被认为有效的最低置信度值。默认值为 0.5。

  • MIN_TRACKING_CONFIDENCE:范围为 [0.0 – 1.0]。这是关键点被认为被跟踪的最低置信度值。默认值为 0.5。

通常,默认值表现良好。因此,我们没有在 mp_pose.Pose() 中传递任何参数。以下部分涉及处理 RGB 帧,以便我们稍后从中提取姿势关键点。最后,我们将图像转换回 OpenCV 友好的 BGR 颜色空间。

# 捕获帧。
success, image = cap.read()
if not success:print("Null.Frames")break
# 获取 fps。
fps = cap.get(cv2.CAP_PROP_FPS)
# 获取帧的高度和宽度。
h, w = image.shape[:2]# 将 BGR 图像转换为 RGB。
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 处理图像。
keypoints = pose.process(image)# 将图像转换回 BGR。
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

6.3 获取身体姿势关键点坐标

解决方案输出对象的 pose_landmarks 属性提供了关键点的归一化 x 和 y 坐标。因此,要获取实际值,我们需要分别将输出乘以图像的 宽度高度

关键点的 LEFT_SHOULDER‘RIGHT_SHOULDER’ 等是 PoseLandmark 类的属性。要获取归一化坐标,我们使用以下语法。

norm_coordinate = pose.process(image).pose_landmark.landmark[MediaPipe.solutions.pose.PoseLandmark.<SPECIFIC_LANDMARK>].coordinate

为了简化表示,我们使用以下简写方法。

# 使用 lm 和 lmPose 作为以下方法的简写。
lm = keypoints.pose_landmarks
lmPose = mp_pose.PoseLandmark
# 左肩膀。
l_shldr_x = int(lm.landmark[lmPose.LEFT_SHOULDER].x * w)
l_shldr_y = int(lm.landmark[lmPose.LEFT_SHOULDER].y * h)# 右肩膀。
r_shldr_x = int(lm.landmark[lmPose.RIGHT_SHOULDER].x * w)
r_shldr_y = int(lm.landmark[lmPose.RIGHT_SHOULDER].y * h)# 左耳。
l_ear_x = int(lm.landmark[lmPose.LEFT_EAR].x * w)
l_ear_y = int(lm.landmark[lmPose.LEFT_EAR].y * h)# 左臀部。
l_hip_x = int(lm.landmark[lmPose.LEFT_HIP].x * w)
l_hip_y = int(lm.landmark[lmPose.LEFT_HIP].y * h)

6.4 对齐相机

这是为了确保相机能够正确地拍摄到人的侧视图。我们测量左肩点和右肩点之间的水平距离。在正确对齐的情况下,左点和右点应该几乎重合。

请注意,偏移距离阈值是基于对具有与视频样本完全相同尺寸的数据集进行分析的结果得出的。如果您尝试使用更高分辨率的样本,这个值将会改变。它不需要非常精确,您可以根据自己的直觉设置一个阈值。

实际上,距离方法根本不是确定对齐的正确方式。它应该是基于角度的。

我们为了简化而使用距离方法。

# 计算左肩点和右肩点之间的距离。
offset = findDistance(l_shldr_x, l_shldr_y, r_shldr_x, r_shldr_y)# 协助对齐相机以拍摄人的侧视图。
# 偏移阈值 30 是基于对 100 个样本进行分析的结果得出的。
if offset < 100:cv2.putText(image, str(int(offset)) + ' Aligned', (w - 150, 30), font, 0.9, green, 2)
else:cv2.putText(image, str(int(offset)) + ' Not Aligned', (w - 150, 30), font, 0.9, red, 2)

6.5 计算身体姿势倾斜角度并绘制关键点

使用预先定义的 findAngle 函数获得倾斜角度。如下所示绘制关键点及其连接线。

# 计算角度。
neck_inclination = findAngle(l_shldr_x, l_shldr_y, l_ear_x, l_ear_y)
torso_inclination = findAngle(l_hip_x, l_hip_y, l_shldr_x, l_shldr_y)# 绘制关键点。
cv2.circle(image, (l_shldr_x, l_shldr_y), 7, yellow, -1)
cv2.circle(image, (l_ear_x, l_ear_y), 7, yellow, -1)# 为了显示的优雅,我们取 x1 的 y 坐标上方 100px 处。
# 虽然我们在计算 P1、P2、P3 之间的角度时取 y = 0。
cv2.circle(image, (l_shldr_x, l_shldr_y - 100), 7, yellow, -1)
cv2.circle(image, (r_shldr_x, r_shldr_y), 7, pink, -1)
cv2.circle(image, (l_hip_x, l_hip_y), 7, yellow, -1)# 同样地,这里我们取 x1 的 y 坐标上方 100px。注意
# 您可以取任意值作为 y,不一定是 100 或 200 像素。
cv2.circle(image, (l_hip_x, l_hip_y - 100), 7, yellow, -1)# 添加文本,姿势和角度倾斜。
# 文本字符串用于显示。
angle_text_string = 'Neck : ' + str(int(neck_inclination)) + '  Torso : ' + str(int(torso_inclination))

6.6 身体姿势检测条件语句

根据姿势是良好还是不良,显示结果。再次强调,阈值角度是基于直觉的。您可以根据需要设置阈值。每次检测时,分别增加良好姿势和不良姿势的帧计数器。

通过将帧数除以fps可以计算出特定姿势的时间。有关 fps 测量方法,请查看我们之前的博客文章。

# 判断是良好姿势还是不良姿势。
# 阈值角度是基于直觉设置的。
if neck_inclination < 40 and torso_inclination < 10:bad
_frames = 0good_frames += 1cv2.putText(image, angle_text_string, (10, 30), font, 0.9, light_green, 2)cv2.putText(image, str(int(neck_inclination)), (l_shldr_x + 10, l_shldr_y), font, 0.9, light_green, 2)cv2.putText(image, str(int(torso_inclination)), (l_hip_x + 10, l_hip_y), font, 0.9, light_green, 2)# 连接关键点。cv2.line(image, (l_shldr_x, l_shldr_y), (l_ear_x, l_ear_y), green, 4)cv2.line(image, (l_shldr_x, l_shldr_y), (l_shldr_x, l_shldr_y - 100), green, 4)cv2.line(image, (l_hip_x, l_hip_y), (l_shldr_x, l_shldr_y), green, 4)cv2.line(image, (l_hip_x, l_hip_y), (l_hip_x, l_hip_y - 100), green, 4)else:good_frames = 0bad_frames += 1cv2.putText(image, angle_text_string, (10, 30), font, 0.9, red, 2)cv2.putText(image, str(int(neck_inclination)), (l_shldr_x + 10, l_shldr_y), font, 0.9, red, 2)cv2.putText(image, str(int(torso_inclination)), (l_hip_x + 10, l_hip_y), font, 0.9, red, 2)# 连接关键点。cv2.line(image, (l_shldr_x, l_shldr_y), (l_ear_x, l_ear_y), red, 4)cv2.line(image, (l_shldr_x, l_shldr_y), (l_shldr_x, l_shldr_y - 100), red, 4)cv2.line(image, (l_hip_x, l_hip_y), (l_shldr_x, l_shldr_y), red, 4)cv2.line(image, (l_hip_x, l_hip_y), (l_hip_x, l_hip_y - 100), red, 4)# 计算保持特定姿势的时间。
good_time = (1 / fps) * good_frames
bad_time = (1 / fps) * bad_frames# 姿势时间。
if good_time > 0:time_string_good = 'Good Posture Time : ' + str(round(good_time, 1)) + 's'cv2.putText(image, time_string_good, (10, h - 20), font, 0.9, green, 2)
else:time_string_bad = 'Bad Posture Time : ' + str(round(bad_time, 1)) + 's'cv2.putText(image, time_string_bad, (10, h - 20), font, 0.9, red, 2)# 如果保持不良姿势超过 3 分钟(180 秒),发送警报。
if bad_time > 180:sendWarning()

结论

以上就是使用 MediaPipe Pose 构建姿势校正应用的全部内容。在本篇博文中,我们讨论了如何实现 MediaPipe Pose 以检测人体姿势。您了解了如何获取姿势关键点、可配置 API、输出等。希望本篇博文帮助您了解了 MediaPipe 姿势的基础知识,并为您的下一个项目带来一些新的想法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79284.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LSTM结合LightGBM高纬时序预测

1. LSTM 时间序列预测 LSTM 是 RNN&#xff08;Recurrent Neural Network&#xff09;的一种变体&#xff0c;它解决了普通 RNN 训练时的梯度消失和梯度爆炸问题&#xff0c;适用于长期依赖的时间序列建模。 LSTM 结构 LSTM 由 输入门&#xff08;Input Gate&#xff09;、遗…

六、adb通过Wifi连接

背景 收集是荣耀X40,数据线原装全新的&#xff0c;USB连上之后&#xff0c;老是断&#xff0c;电脑一直叮咚叮咚的响个不停&#xff0c;试试WIFI 连接是否稳定&#xff0c;需要手机和电脑用相同的WIFI. 连接 1.通过 USB 连接手机和电脑(打开USB调试等这些都略过) adb device…

如何理解前端开发中的“换皮“

"换皮"在前端开发中是一个常见的术语&#xff0c;通常指的是在不改变网站或应用核心功能和结构的情况下&#xff0c;只改变其外观和视觉表现。以下是关于前端"换皮"的详细理解&#xff1a; 基本概念 定义&#xff1a;换皮(Skinning)是指保持应用程序功能不…

从 Vue 到 React:深入理解 useState 的异步更新

目录 从 Vue 到 React&#xff1a;深入理解 useState 的异步更新与函数式写法1. Vue 的响应式回顾&#xff1a;每次赋值立即生效2. React 的状态更新是异步且批量的原因解析 3. 函数式更新&#xff1a;唯一的正确写法4. 对比 Vue vs React 状态更新5. React useState 的核心源码…

使用Redis实现分布式限流

一、限流场景与算法选择 1.1 为什么需要分布式限流 在高并发系统中&#xff0c;API接口的突发流量可能导致服务雪崩。传统的单机限流方案在分布式环境下存在局限&#xff0c;需要借助Redis等中间件实现集群级流量控制。 1.2 令牌桶算法优势 允许突发流量&#xff1a;稳定速…

快速搭建WordPress网站的主题

WP快主题(wpkuai.com )是一款由知名WordPress专业团队打造的专业化WordPress主题&#xff0c;旨在让用户使用该wordpress主题快速搭建网站。 WP快主题专注于快速搭建WordPress网站的主题解决方案。其主题设计注重简洁性与高效性&#xff0c;旨在帮助用户快速完成网站的搭建和部…

STM32江科大----------PID算法

声明&#xff1a;本人跟随b站江科大学习&#xff0c;本文章是观看完视频后的一些个人总结和经验分享&#xff0c;也同时为了方便日后的复习&#xff0c;如果有错误请各位大佬指出&#xff0c;如果对你有帮助可以点个赞小小鼓励一下&#xff0c;本文章建议配合原视频使用❤️ 如…

将JSON格式的SQL查询转换为完整SQL语句的实战解析

一、背景与需求 在现代数据处理中,JSON格式因其灵活性和可读性,常被用于定义SQL查询的结构。然而,直接编写JSON格式的SQL指令后,如何将其转换为可执行的SQL语句是开发者常遇到的挑战。本文将通过一个Python函数和多个实际案例,解析如何将JSON结构转换为完整的SQL语句,并…

java CountDownLatch用法简介

CountDownLatch倒计数锁存器 CountDownLatch&#xff1a;用于协同控制一个或多个线程等待在其他线程中执行的一组操作完成&#xff0c;然后再继续执行 CountDownLatch用法 构造方法&#xff1a;CountDownLatch(int count)&#xff0c;count指定等待的条件数&#xff08;任务…

Leetcode - 双周赛135

目录 一、3512. 使数组和能被 K 整除的最少操作次数二、3513. 不同 XOR 三元组的数目 I三、3514. 不同 XOR 三元组的数目 II四、3515. 带权树中的最短路径 一、3512. 使数组和能被 K 整除的最少操作次数 题目链接 本题实际上求的就是数组 nums 和的余数&#xff0c;代码如下&…

【后端】【python】利用反射器----动态设置装饰器

&#x1f4d8; Python 装饰器进阶指南 一、装饰器本质 ✅ 本质概念 Python 装饰器的本质是 函数嵌套 返回函数&#xff0c;它是对已有函数的增强&#xff0c;不修改原函数代码&#xff0c;使用语法糖 decorator 实现包裹效果。 def my_decorator(func):def wrapper(*args, …

Nodejs Express框架

参考&#xff1a;Node.js Express 框架 | 菜鸟教程 第一个 Express 框架实例 接下来我们使用 Express 框架来输出 "Hello World"。 以下实例中我们引入了 express 模块&#xff0c;并在客户端发起请求后&#xff0c;响应 "Hello World" 字符串。 创建 e…

Docker Swarm 集群

Docker Swarm 集群 本文档介绍了 Docker Swarm 集群的基本概念、工作原理以及相关命令使用示例&#xff0c;包括如何在服务调度中使用自定义标签。本文档适用于需要管理和扩展 Docker 容器化应用程序的生产环境场景。 1. 什么是 Docker Swarm Docker Swarm 是用于管理 Docker…

充电宝项目中的MQTT(轻量高效的物联网通信协议)

文章目录 补充&#xff1a;HTTP协议MQTT协议MQTT的核心特性MQTT vs HTTP&#xff1a;关键对比 EMQX项目集成EMQX集成配置客户端和回调方法具体接口和方法处理处理类 补充&#xff1a;HTTP协议 HTTP是一种应用层协议&#xff0c;使用TCP作为传输层协议&#xff0c;默认端口是80…

【iOS】UIPageViewController学习

UIPageViewController学习 前言创建一个UIPageViewController最简单的使用 UIPageViewController的方法说明&#xff1a;效果展示 UIPageViewController的协议方法 前言 笔者最近在写项目时想实现一个翻书效果&#xff0c;上网学习到了UIPageViewController今天写本篇博客总结…

Linux搭建环境:从零开始掌握基础操作(四)

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 软件测试第一步就是搭建测试环境&#xff0c;如何搭建好测试环境&#xff0c;需要具备两项的基础知识&#xff1a; 1、Linux 命令: 软件测试第一个任务, 一般都需要进行环境搭建, 一部分&#xff0c;环境搭建内容是在服…

一天一个java知识点----Tomcat与Servlet

认识BS架构 静态资源&#xff1a;服务器上存储的不会改变的数据&#xff0c;通常不会根据用户的请求而变化。比如&#xff1a;HTML、CSS、JS、图片、视频等(负责页面展示) 动态资源&#xff1a;服务器端根据用户请求和其他数据动态生成的&#xff0c;内容可能会在每次请求时都…

YOLOV8 OBB 海思3516训练流程

YOLOV8 OBB 海思3516训练流程 目录 1、 下载带GPU版本的torch(可选) 1 2、 安装 ultralytics 2 3、 下载pycharm 社区版 2 4、安装pycharm 3 5、新建pycharm 工程 3 6、 添加conda 环境 4 7、 训练代码 5 9、配置Ymal 文件 6 10、修改网络结构 9 11、运行train.py 开始训练模…

【深度学习】花书第18章——配分函数

直面配分函数 许多概率模型&#xff08;通常是无向图模型&#xff09;由一个未归一化的概率分布 p ~ ( x , θ ) \tilde p(\mathbf x,\theta) p~​(x,θ)定义。我们必须通过除以配分函数 Z ( θ ) Z(\pmb{ \theta}) Z(θ)来归一化 p ~ \tilde p p~​。以获得一个有效的概率分…

工作记录1

日常总结、灵感记录、学习要点。持续记录 学海无涯,再好的记性也比不过烂笔头,记录一下学习日常、灵感、要点。 前言:最近看见一个博文,很有感触,是某个大佬自己运营的网站,分享了他的各种经验文章和自身的一些笔记。本人还没有他这么屌,所以还是先在CSDN上小试牛刀吧…