正确的身体姿势是一个人整体健康的关键。然而,保持正确的身体姿势可能很困难,因为我们经常忘记它。这篇博文将引导你完成为此构建解决方案所需的步骤。最近,我们玩得很开心,进行身体姿势检测。它就像一个魅力!
-
进行身体姿势检测
-
申请目标
-
身体姿势检测和分析应用工作流程
-
要求
-
身体姿势检测代码解释
是一种高保真的身体姿势跟踪解决方案,可从 RGB 帧(注:RGB图像帧)在整个身体上渲染 33 个 3D 地标和背景分割蒙版。它利用了 BlazePose[1] 拓扑结构,它是 COCO[2]、BlazeFace[3] 和 BlazePalm[4] 拓扑结构的超集。
应用目标-Body Tracking
我们的目标是从完美的侧视图检测一个人,并测量颈部和躯干对某个参考轴的倾斜度。通过监测人弯曲到某个阈值角度以下时的倾斜角度。
其他功能包括测量特定姿势的时间和相机对齐。我们必须确保相机看向正确的侧视图。因此,我们需要对齐功能。
身体姿势检测和分析应用工作流程
先决条件
OpenCV是我们需要的主要软件包。使用代码文件夹中提供的requirements.txt文件安装依赖项。
pip install -r requirements.txt
身体姿势检测代码解释
1. 导入库
def findDistance(x1, y1, x2, y2):
dist = m.sqrt((x2-x1)**2+(y2-y1)**2)
return dist
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 的 x 坐标相同。由于 y3 对所有 y 都有效,为了简单起见,我们取 y3 = 0。
我们采用矢量法求三点的内角。两个向量之间的夹角P12 和P13 由下式给出,
# Calculate angle.
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)*theta
return degree
4.发送不良身体姿势警报的功能
使用此函数可在检测到不良姿势时发送警报。我们把它留空,供你使用。您可以在方便的时候随意发挥创意和定制。例如,您可以连接一个 Telegram Bot 来发出警报,这非常简单。参考部分 [6] 中的链接。或者,您可以通过创建一个 android 应用程序来提升它。
def sendWarning(x):
pass
5. 初始化
在此处初始化常量和方法。这些应该通过内联注释不言自明。
# Initialize frame counters.
good_frames = 0
bad_frames = 0
# Font type.
font = cv2.FONT_HERSHEY_SIMPLEX
# Colors.
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)
# Initialize mediapipe pose class.
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()
身体姿势检测主要功能
1. 创建 Video Capture 和 Video Writer 对象
为了进行演示,我们使用了预先录制的视频样本。在实践中,您需要定位网络摄像头以捕捉您的侧视图。在以下代码片段中,将创建视频捕获对象。
如您所见,我们正在获取视频元数据以创建视频捕获对象。如果要以 mp4 格式编写,请将编解码器更改为 *'mp4v'。有关视频编写器和处理编解码器的更直观指南,请查看有关的文章。
# For webcam input replace file name with 0.
file_name = 'input.mp4'
cap = cv2.VideoCapture(file_name)
# Meta.
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 writer.
video_output = cv2.VideoWriter('output.mp4', fourcc, fps, frame_size)
2. 身体姿势检测主循环
Pose() 解决方案的可配置 API 不需要太多调整。默认值足以检测姿态地标。但是,如果我们希望实用程序代码,则必须将 ENABLE_SEGMENTATION 标志设置为 True。以下是 姿势解决方案中的一些可配置 API。f
-
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。
通常,默认值做得很好。因此,我们不会在以下部分处理 RGB 帧的处理中传递任何参数,我们可以稍后从中提取姿态地标。最后,我们将图像转换回对 OpenCV 友好的 BGR 彩色空间。mp_pose.Pose().
# Capture frames.
success, image = cap.read()
if not success:
print("Null.Frames")
break
# Get fps.
fps = cap.get(cv2.CAP_PROP_FPS)
# Get height and width of the frame.
h, w = image.shape[:2]
# Convert the BGR image to RGB.
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Process the image.
keypoints = pose.process(image)
# Convert the image back to BGR.
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
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
使用如下所示的表示形式缩短了这些方法。
# Use lm and lmPose as representative of the following methods.
lm = keypoints.pose_landmarks
lmPose = mp_pose.PoseLandmark
# Left shoulder.
l_shldr_x = int(lm.landmark[lmPose.LEFT_SHOULDER].x * w)
l_shldr_y = int(lm.landmark[lmPose.LEFT_SHOULDER].y * h)
# Right shoulder.
r_shldr_x = int(lm.landmark[lmPose.RIGHT_SHOULDER].x * w)
r_shldr_y = int(lm.landmark[lmPose.RIGHT_SHOULDER].y * h)
# Left ear.
l_ear_x = int(lm.landmark[lmPose.LEFT_EAR].x * w)
l_ear_y = int(lm.landmark[lmPose.LEFT_EAR].y * h)
# Left hip.
l_hip_x = int(lm.landmark[lmPose.LEFT_HIP].x * w)
l_hip_y = int(lm.landmark[lmPose.LEFT_HIP].y * h)
4.对齐相机
这是为了确保摄像头捕捉到人物正确的侧视图。我们正在测量左右肩点之间的水平距离。正确对齐后,左点和右点应几乎重合。
请注意,偏移距离阈值基于对具有与视频样本完全相同尺寸的数据集的观察结果。如果尝试使用更高分辨率的样本,则此值将更改。它不必非常具体;您可以根据自己的直觉设置阈值。
实际上,距离法根本不是确定对齐方式的正确方法。它应该是基于角度的。
为简单起见,我们使用距离法。
# Calculate distance between left shoulder and right shoulder points.
offset = findDistance(l_shldr_x, l_shldr_y, r_shldr_x, r_shldr_y)
# Assist to align the camera to point at the side view of the person.
# Offset threshold 30 is based on results obtained from analysis over 100 samples.
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)
5.计算身体姿势倾斜度并绘制地标
倾角是使用预定义的函数 findAngle 获得的。地标及其连接如下图所示。
# Calculate angles.
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)
# Draw landmarks.
cv2.circle(image, (l_shldr_x, l_shldr_y), 7, yellow, -1)
cv2.circle(image, (l_ear_x, l_ear_y), 7, yellow, -1)
# Let's take y - coordinate of P3 100px above x1, for display elegance.
# Although we are taking y = 0 while calculating angle between P1,P2,P3.
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)
# Similarly, here we are taking y - coordinate 100px above x1. Note that
# you can take any value for y, not necessarily 100 or 200 pixels.
cv2.circle(image, (l_hip_x, l_hip_y - 100), 7, yellow, -1)
# Put text, Posture and angle inclination.
# Text string for display.
angle_text_string = 'Neck : ' + str(int(neck_inclination)) + ' Torso : ' + str(int(torso_inclination))
6. 身体姿势检测条件
根据姿势,无论好坏;将显示结果。同样,阈值角度是基于直觉的。您可以根据需要设置阈值。每次检测时,帧计数器都会分别针对良好姿势和不良姿势递增。
特定姿势的时间可以通过将帧数除以 fps 来计算。在我们之前的博客文章中查看 。
# Determine whether good posture or bad posture.
# The threshold angles have been set based on intuition.
if neck_inclination < 40 and torso_inclination < 10:
bad_frames = 0
good_frames += 1
cv2.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)
# Join landmarks.
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 = 0
bad_frames += 1
cv2.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)
# Join landmarks.
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)
# Calculate the time of remaining in a particular posture.
good_time = (1 / fps) * good_frames
bad_time = (1 / fps) * bad_frames
# Pose time.
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)
# If you stay in bad posture for more than 3 minutes (180s) send an alert.
if bad_time > 180:
sendWarning()
结论
这就是构建姿态校正器应用程序的全部内容。在这篇文章中,我们讨论了检测人体姿势的实现。您学习了如何获取姿势地标、可配置的 API、输出等。我希望这篇博文能帮助您了解 姿势的基础知识,并帮助您为下一个项目产生一些新的想法。