2023年1月15日 星期日

[AI, Python, 深蹲, squat] Python 蹲的門面

 Python 蹲的門面

Line:ted2016.kpvs
Email:Lct4246@gmail.com
FBhttp://gg.gg/TedLeeFB/
Bloghttp://gg.gg/TedLeeBlog/

Jan. 15, 2023
[1]


在筆者的先前拙著《用變數傾印法玩 Python 蹲》(https://reurl.cc/LXk1qx)一文中,我們成功地在陳會安老師研發的 fChart 中的 Thonny IDE 測試了深蹲偵測及計次範例。本文將進一步地甞試以 fChart 內建的 Tinker 套件 [2] 為之再加上一套使用者介面(user interface,UI)來梳妝打扮一番


新增功能

在本文中,我們將為《用變數傾印法玩 Python 蹲》範例擴充下列兩個新功能:
  1.  改用攝影機即時取得的影像(而非事先預錄的影片)。
  2. 增設深蹲達標關卡(為了便於測試,暫訂為深蹲三下)。
 

UI 佈局(layout)

在圖 1 上方,我們設計了一個 3×2 大小的畫面,第一行顯示專案名稱(Python 蹲)與版次(V1.0),第二行顯示由攝影機取得的即時影像與相關訊息,第三行為留白及作者姓名(Ted Lee)。圖 1 下方為 Tinker 刻出來的畫面。

 
圖 1:UI 設計圖

在圖 2 中,我們先建立最外層的視窗 window 元件(component),再置入 frmTitle、frmVideo 和 frmTeam 三個框架元件(Frame)。緊接著,在 frmTitle 中擺放 lblTitle 和 lblVersion、在 frmVideo 中擺放 lblVideo 和 lblResult、在 frmTeam 中擺放 lblSpace 和 lblTeam。所有元件皆以 pack 佈局方式安排。詳如如圖 2 所示。

圖 2:UI 元件佈局

程式邏輯

我們將程式執行的流程畫成圖 3 的流程圖。此外,程式採模組化程式設計(modularized programming)的方法,將獨立的功能以下列三個副程式來達成:

  1. detectPose():偵測姿勢。
  2. updateUI():即時更新影像標籤 lblVideo。
  3. cleanUp():程式結束,釋放已配置資源(allocated resources)

圖 3:本文的流程圖

本文的執行結果請參考 https://youtu.be/Gm6o7m-QH6Y完整程式碼詳列如下(讀者亦可由 https://reurl.cc/rZ7aob 下載)。

import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image
    
from tkinter import *
from PIL import Image, ImageTk

from PoseModule import PoseDetector
dir = 0 
count = 0

def updateUI():
    global img, lblVideo
    
    cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
    img = Image.fromarray(cv2image)
    imgtk = ImageTk.PhotoImage(image = img)    

    lblVideo.imgtk = imgtk
    lblVideo.config(image = imgtk)
    lblVideo.after(100, detectPose)
    
    if count > 3:
       lblResult.config(text = '你已達標!')   
    
def detectPose():
    global cap, detector, dir, count, img

    success, img = cap.read()
    if success:
        img = cv2.resize(img, (640, 480))
        h, w, c = img.shape
        pose, img = detector.findPose(img, draw=True)
        
        if pose:
            lmList = pose["lmList"]
            angle, img = detector.findAngle(lmList[24], lmList[26], lmList[28], img)
            bar = np.interp(angle, (85, 180), (w//2-100, w//2+100))
            cv2.rectangle(img, (w//2-100, 50), (int(bar), 100), (0, 255, 0), cv2.FILLED)

            if angle <= 150:                
                if dir == 0:
                    count = count + 0.5
                    dir = 1
                    
            if angle >= 170: 
                if dir == 1:
                    count = count + 0.5
                    dir = 0
                    
            msg = str(int(count))        
            cv2.putText(img, msg, (100, 150), cv2.FONT_HERSHEY_SIMPLEX, 5, (255, 255, 255), 20)
            
        cv2.imshow("Pose", img)
        updateUI()

def cleanUp():
    window.destroy()
    cap.release()
    cv2.destroyAllWindows()

cap = cv2.VideoCapture(0) #####開啟攝影機
detector = PoseDetector()

#開 UI ---------------------------------------------------------------------------------------------------------   
#widow
window = Tk()
window.title('Python 蹲')
#window.geometry('1024x768')
window.attributes('-fullscreen', True)


#frame
frmTitle = Frame(window)
frmTitle.pack(side = TOP, fill = BOTH, expand = YES)

frmVideo = Frame(window)
frmVideo.pack(side = TOP, fill = BOTH, expand = YES)   

frmTeam = Frame(window)
frmTeam.pack(side = BOTTOM, fill = BOTH, expand = YES)


#Title
lblTitle = Label(frmTitle, text = "Python 蹲", fg = '#005E5E', bg = '#666666', font = ('標楷體', 70), borderwidth = 0, highlightthickness = 0, relief = "sunken") 
lblTitle.pack(side = LEFT, fill = BOTH, expand = YES)

lblVersion = Label(frmTitle, text = "V1.0", fg = '#005E5E', bg = '#666666', font = ('標楷體', 20), borderwidth = 0, highlightthickness = 0, relief = "sunken") 
lblVersion.pack(side = RIGHT, fill = BOTH, expand = YES)


#Video
lblVideo = Label(frmVideo, bg = '#005E5E', borderwidth = 0, highlightthickness = 0, relief = "ridge")
lblVideo.pack(side = LEFT, fill = BOTH, expand = YES)

lblResult = Label(frmVideo, text = "", fg = 'white', bg = '#005E5E', font = ('標楷體', 40), borderwidth = 0, highlightthickness = 0, relief = "sunken")
lblResult.pack(side = RIGHT, fill = BOTH, expand = YES)


#Team
lblSpace = Label(frmTeam, text = "", fg = '#005E5E', bg = '#666666', font = ('標楷體', 20), borderwidth = 0, highlightthickness = 0, relief = "sunken") 
lblSpace.pack(side = LEFT, fill = BOTH, expand = YES)

lblTeam = Label(frmTeam, text = "Ted Lee", fg = '#005E5E', bg = '#666666', font = ('標楷體', 20), borderwidth = 0, highlightthickness = 0, relief = "sunken") 
lblTeam.pack(side = RIGHT, fill = BOTH, expand = YES)

#姿勢識別 ---------------------------------------------------------------------------------------------------
detectPose()
    
window.bind('<Escape>', lambda e: cleanUp())
mainloop()


進階問題

  1. 建置一「分數雲端排行榜」來記錄玩家的深蹲分數。
  2. 修改程式,使得深蹲次數達標後出現是否「重新訓練」詢問。

[2] 請參考拙著《AI 成績查詢系統》(https://reurl.cc/aaWlKl)一文