Python程式打造Google MediaPipe 深蹲偵測互動遊戲

2022年南港瓶蓋工廠所舉行的Maker Faire 中,在公司的攤位上,最受到矚目與歡迎的,就是Google MediaPipe 的深蹲偵測互動遊戲了,達到一定的深蹲次數不僅買徽章可以折價,還可以免費獲得摩艾石像3D列印一個,吸引了很多大小朋友,一起來看看當時熱鬧的畫面吧!

 

 

 

原理及功能說明

本次的深蹲互動專題,以這篇[累累累] Google Mediapipe 深蹲偵測,結合 Arduino 首次接觸就上手所改編的,一樣著重於偵測身體大腿及小腿的角度小於一定的數值,將深蹲次數顯示出來外,並取消了和Arduino的連線。而MediaPipe已經將人體簡化成由點和線所構成的人體線條骨架的樣貌,如下圖所示。

 

由上圖所示,就是偵測左腿點23(left_hip)到點25(left _knee)的直線距離,和點25(left _knee)到點27(left _ankle)的直線距離,兩條直線所產生的夾角小於120;右腿則是點24(right_hip)到點26(right _knee)的直線距離,和點28(right _knee)到點27(right _ankle)的直線距離,兩條直線所產生的夾角小於120。

也就是說,當左右腳膝蓋彎曲角度小於120度時,字體顏色就會變綠色。只要螢幕上顯示的字體「Left Angle」、「Right Angle」、「Pose」皆為綠色時,次數會加1。

 

再來是遊戲互動體驗,筆者使用了一顆超大的Enter鍵,當程式開始執行時,畫面會出現指示「Press Enter To Start」,Enter鍵按下去後就可以開始深蹲遊戲,此時就會開始計算深蹲次數。再按一次Enter鍵,次數會歸零,同時又會回到「Press Enter To Start」的畫面。而筆者在每次計算深蹲次數後,都會加上音效,以確保動作完成,如以下所示。

 

再來是場地架設,以當時在Maker Faire南港瓶蓋工廠場地為例,筆者使用的是羅技C270的攝影鏡頭,鏡頭距離地面高度需2公尺,距離人的身體約3公尺,才能偵測得到全身骨架畫面,由下圖所示。

 

程式撰寫

由於程式過長,故說明重要片段,如下列程式所示。

以下程式為影像訊號來源。第1行為攝影機訊號;第2行可撥放影片。

cam = cv2.VideoCapture(3)
#cam=cv2.VideoCapture("POSE1.mp4")

 

以下程式為計算身體比率函式。

def get_body_ratio(landmarks):
    r_body = abs(landmarks[mppose.PoseLandmark["RIGHT_SHOULDER"].value].y
                 - landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y)

    l_body = abs(landmarks[mppose.PoseLandmark["LEFT_SHOULDER"].value].y
                 - landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y)

    avg_body = (r_body + l_body) / 2

    r_leg = abs(landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y
                - landmarks[mppose.PoseLandmark["RIGHT_ANKLE"].value].y)

    l_leg = abs(landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y
                - landmarks[mppose.PoseLandmark["LEFT_ANKLE"].value].y)

    if r_leg > l_leg:

        return r_leg / avg_body
    else:
        return l_leg / avg_body

 

以下程式為計算左右腳的角度函式。

def get_knee_angle(landmarks):

    r_hip = get_landmark(landmarks, "RIGHT_HIP")
    l_hip = get_landmark(landmarks, "LEFT_HIP")

    r_knee = get_landmark(landmarks, "RIGHT_KNEE")
    l_knee = get_landmark(landmarks, "LEFT_KNEE")

    r_ankle = get_landmark(landmarks, "RIGHT_ANKLE")
    l_ankle = get_landmark(landmarks, "LEFT_ANKLE")

    r_angle = calc_angles(r_hip, r_knee, r_ankle)
    l_angle = calc_angles(l_hip, l_knee, l_ankle)


    #print(r_hip)
    m_hip = (r_hip + l_hip)
    m_hip = [x / 2 for x in m_hip]
    m_knee = (r_knee + l_knee)
    m_knee = [x / 2 for x in m_knee]
    m_ankle = (r_ankle + l_ankle)
    m_ankle = [x / 2 for x in m_ankle]

    mid_angle = calc_angles(m_hip, m_knee, m_ankle)

    return [int(r_angle),int(l_angle) ,int(mid_angle)]

 

以下程式為計算按下Enter鍵的次數,可觸發遊戲開始。

        if (read_dir_key == 13):
            sport['count'] = 0
            run_flag += 1

        if (run_flag == 2):
            run_flag = 0

 

以下程式為左腳膝蓋彎曲角度之顏色顯示。小於120度綠色,介於120~130度黃色,大於130度紅色。

而檔案POSE_entercount_sound.py中的第226~248行則為顯示右腳膝蓋彎曲角度之顏色顯示。

if knee_angles[0] < 120:

cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (82, 169, 255), 4, cv2.LINE_AA)

cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 1, cv2.LINE_AA)


elif knee_angles[0] < 130:

cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (82, 169, 255), 4, cv2.LINE_AA)


cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 1, cv2.LINE_AA)

else:

cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (82, 169, 255), 4, cv2.LINE_AA)

cv2.putText(preview, "Left Angle: {:d}".format(knee_angles[0]), (400, 360)
, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 1, cv2.LINE_AA)

                               

 

決定深蹲的次數,如果次數已加1,則會撥放音效。

if status:
if avg_angle > 160:
status = False
pass_time = time.time() - start_time
start_time = 0

if 3000 > pass_time > 0.5:
sport['count'] = sport['count'] + 1

while (pygame.mixer.music.get_busy()!=1):
pygame.mixer.music.load('coin05.mp3')
pygame.mixer.music.play()

sport['calories'] = sport['calories'] + int(0.66 * pass_time)
logger(sport['count'], sport['calories'])
tmp = f"a{sport['count']}\n"
#ser.write(str.encode(tmp))
tmp = f"b{sport['calories']}\n"
#ser.write(str.encode(tmp))
#print(pass_time)


else:
if avg_angle < 120 and body_ratio < 1.2:
start_time = time.time()
status = True

 

 

在螢幕上顯示”Press Enter To Start”,以開始深蹲遊戲

cv2.putText(preview, "Press Enter To Start", (10, 80)
                               , cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 0), 5, cv2.LINE_AA)

cv2.putText(preview, "Press Enter To Start", (10, 80)
                                , cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 2, cv2.LINE_AA)

 

以上程式傳送門-連結

下載解壓縮後,電腦中需依本文安裝Python的執行環境(操作至圖 21)。

執行程式

cd pose_detection
python POSE_entercount_sound.py

 

影片展示

以下影片為腦波弱老闆在南港瓶蓋工廠所示範的深蹲畫面,一起來看看吧!

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *