概述
正確的身體姿勢是個人整體健康的關鍵。然而,保持正確的身體姿勢可能會很困難,因為我們常常會忘記。本博客文章將逐步指導您構建一個解決方案。最近,我們使用 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****12 和 P****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 姿勢的基礎知識,并為您的下一個項目帶來一些新的想法。