2021年12月1日 星期三

讓我們Py在一起

讓我們Py在一起

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

Dec. 1, 2021
88x31.png[1]
 

%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3Bhtml%3D1%3B%22%20edge%3D%221%22%20source%3D%223%22%20target%3D%228%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20value%3D%22cvzoneRSP_%E8%A9%B3%E8%A8%BB.py%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfillColor%3D%23d5e8d4%3BstrokeColor%3D%2382b366%3BfontSize%3D20%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22-170%22%20y%3D%2295%22%20width%3D%22210%22%20height%3D%22210%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%224%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3Bhtml%3D1%3BexitX%3D1%3BexitY%3D0.5%3BexitDx%3D0%3BexitDy%3D0%3BentryX%3D0%3BentryY%3D0.5%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20source%3D%225%22%20target%3D%223%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%225%22%20value%3D%22Hand%20Tracking.py%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfillColor%3D%23d5e8d4%3BstrokeColor%3D%2382b366%3BfontSize%3D20%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22-380%22%20y%3D%22115%22%20width%3D%22170%22%20height%3D%22170%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%226%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3Bhtml%3D1%3BentryX%3D0%3BentryY%3D0.5%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20source%3D%227%22%20target%3D%228%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%227%22%20value%3D%22PPT%20slideshow.py%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfillColor%3D%23ffe6cc%3BstrokeColor%3D%23d79b00%3BfontSize%3D20%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22-150%22%20y%3D%22320%22%20width%3D%22190%22%20height%3D%22190%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%228%22%20value%3D%22%26lt%3Bspan%20style%3D%26quot%3Bfont-size%3A%2020px%3B%26quot%3B%26gt%3BPPT%20controller.py%26lt%3B%2Fspan%26gt%3B%22%20style%3D%22whiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfontSize%3D20%3B%22%
在我們在先前的拙著《Python玩AI,你也可以》(https://reurl.cc/Kr1ADM)中,成功的解析了「剪刀石頭布RSP」實例(RSP.py)。緊接著,我們也測試了PowerPoint檔自動放映的範例(Slideshow.py)。
本文嘗試將這兩隻個別測妥的程式合併成可以「用手勢自動切換PowerPoint」的小專案(PPT controller.py),並以此過程來展示專案開發的技巧:

將大問題拆解為數個小問題後,再將各部份的解答回組成原解。


圖1詳盡地說明本文發展的過程:第一線,和陳會安老師學了CVzone的 ch6-5.py 範例後(https://reurl.cc/gzbqlb),我們先很用功的寫出每一行程式的註解 cvzoneRSP_詳註.py 。接著,再發想出用手勢來控制PowerPoint的播放切換。隨後,著手搜尋並測試了 PPT slideshow.py 這個程式片段。完成之後,我們將這兩隻程式合拼成 PPT controller.py。



圖1:手勢控制PowerPoint切換的演進過程。

cvzoneRSP_詳註.py

詳細說明請讀者參閱《Python玩AI,你也可以》(https://reurl.cc/Kr1ADM),以下僅列出完整程式碼:


from cvzone.HandTrackingModule import HandDetector #L5初始化用
import cv2
cap = cv2.VideoCapture(0) #攝影機的handler
detector = HandDetector(detectionCon=0.5, maxHands=1) #detectionCon:偵測的信心值(confidence)、maxHands:可測到幾隻手
while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像
    hands, img = detector.findHands(img) #從影像中中偵測手
    if hands: #如果有偵測到手
        hand = hands[0]
        bbox = hand["bbox"] #bbox: bounding box
        fingers = detector.fingersUp(hand) #fingers = [手指1, 手指2, 手指3, 手指4, 手指5]
        totalFingers = fingers.count(1) #fingers裡有個1
        print(totalFingers)
        msg = "None" #顯示剪刀、石頭、布
        
        if totalFingers == 5:
            msg = "Paper"
            
        if totalFingers == 0:
            msg = "Rock"
            
        if totalFingers == 2:
            if fingers[1] == 1 and fingers[2] == 1: #食指 + 中指
                msg = "Scissors"
                
        cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2) #顯示bbox框框 + 訊息
    cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
        break
    
cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗

PPT slideshow.py

這篇Satck Overflow中的文章(https://reurl.cc/NZKoYk)展示如下的程式碼,它是援用Python版的Win32擴充(extensions) pywin32https://reurl.cc/pxeRp8) 使用 COM(Component Object Model ,元件物件模型) 來控制PowerPoint(https://reurl.cc/Rb3Z66)。

import win32com.client #L4, 5用
import time #L8, 11, 14, 17用

app = win32com.client.Dispatch("PowerPoint.Application")
presentation = app.Presentations.Open(FileName=u'd:\\要播放的PowerPoint檔.pptx', ReadOnly=1)  #開啟PowerPoint檔

presentation.SlideShowSettings.Run()  #PowerPoint放映
time.sleep(1) #延遲一段時間

presentation.SlideShowWindow.View.Next()  #切換到下一頁
time.sleep(1)

presentation.SlideShowWindow.View.Next()  #切換到下一頁
time.sleep(1)

presentation.SlideShowWindow.View.Previous()  #切回到上一頁
time.sleep(1)

presentation.SlideShowWindow.View.Exit() #關閉PowerPoint檔
app.Quit() #關閉PowerPoint
  
          

讓我們Py在一起:PPT controller.py

cvzoneRSP_詳註.py 和 PPT slideshow.py 整併如下:

import win32com.client
import time

app = win32com.client.Dispatch("PowerPoint.Application")
presentation = app.Presentations.Open(FileName=u'd:\\Bit It.pptx', ReadOnly=1)

presentation.SlideShowSettings.Run()

delay_find = 0
flag = 0

import time

from cvzone.HandTrackingModule import HandDetector
import cv2

cap = cv2.VideoCapture(0) #使用第一台攝影機
detector = HandDetector(detectionCon=0.5, maxHands=1) #手部偵測

#攝影机已開啟
while cap.isOpened():
    success, img = cap.read() #讀入影像
    hands, img = detector.findHands(img) #找手
    
    if flag == 0:       
        #找到手
        if hands:
            hand = hands[0]
            bbox = hand["bbox"]
            
            #有幾根手指
            fingers = detector.fingersUp(hand)
            totalFingers = fingers.count(1)
            msg = "None"
            if totalFingers == 1:
               presentation.SlideShowWindow.View.Next()
            
            cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)                                       
            flag = 1
            delay_find = 0
    cv2.imshow("Image", img)
    
    delay_find = delay_find + 1    
    if delay_find > 30:
       flag = 0
       
#按q離開
    if cv2.waitKey(1) & 0xFF == ord("q"):
        presentation.SlideShowWindow.View.Exit() #關閉PowerPoint
        app.Quit() #關閉PowerPoint
        break
        
cap.release()
cv2.destroyAllWindows()

延伸應用發想

可以在控制PowerPoint放映時還能像新聞主播播報新聞那樣隨時圈畫重點嗎?也許將本文再結合這篇「AI虛擬畫家(https://tinyurl.com/y2jxsdo4 」就能達成。讀者們可以自行試試看。或者,電腦視覺特區(Computer Vision Zone)https://tinyurl.com/y5msr5pa)上也有許多使用AI的電腦視覺專案,像是「虛擬鍵盤(Virtual Keyboard)」,有興趣的讀者可以參考它們的程式碼再行整併出更有趣的實例。
期待!

後記:如何解題?

一般而言,解決問題的基本方法論(methodology)有兩種:由上而下(Top Down
由下而上(Bottom Up,如圖2和圖3所示。
前者是把大問題先拆解為各個功能獨立的小問題,個別求出其解後再將之組成原問題的解答;後者是從已知解答的各個小問題再發想出相關的應用,而這個新問題的答案就能快速地從已知合併(merge)出最終的結果。

圖2:由上而下求解。


圖3:由下而上求解。

然而,如果讀者已熟悉圖2及圖3的方法了,多練習幾個範例以建立直覺(intuition)。之後,再把這些方法都數忘記。不過,到那時,相信您定可信手拈來,而雖離師輔,道亦不遠矣!

圖4:全方位求解。

2021年11月23日 星期二

Python玩AI,你也可以

Python玩AI,你也可以

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

Nov. 23, 2021
88x31.png[1]
深度學習(Deep Learning)深幾許?中學生可以玩嗎?那要怎麼入門呢?

現今科技的進步,帶動了一個潮流:

原本入門門檻高的複雜技術,竟然被包裝成更簡化的流程了(甚至可以像填表一樣,基本資料填一填就好,連程式都不用寫半行)。

CVZone(https://reurl.cc/mvbmRY)就是在這樣一個概念下產生的Python開源套件(package)。它架構在OpenCVMediaPipe函式庫(libraries)上層(圖1),並提供影像處理(image processing)和AI的功能。

圖1:從原始碼(https://tinyurl.com/yxb5o6sg)中可以明確看到CVZone架構於OpenCV和MediaPipe的匯入敘述。


本文將從陳會安老師的剪刀石頭布手勢識別上課範例(https://reurl.cc/gzbqlb)(文後皆以RSP簡稱)[2] 出發,一步一步說明如何細細將這個例子拆解(decompose),以便我們得以通透的了解它整個完整編程的邏輯概念,進而能從中整理與歸納出一套可行的學習路徑(learning path)

學習路徑

懂和程式碼,再拆解出「基本元素」,然後發想自己的相關應用applications,動動手去改動範例程式試看看。之後,就可以甞試著從頭開始規畫寫一個中、小型專案(project)了。

RSP拆解

曹齊平老師(https://tinyurl.com/y29jruc2多年的教學經驗發現:

拆家電是學生活科技(living technologies)最好的活脫脫免費教材(https://tinyurl.com/yxacn8kh)。

然而,軟體學習又比硬體學習來的更低成品。但不變的是由大到小、由簡馭繁的基本原理、原則罷了。本小節即秉持此心法,動手將RSP細部拆解出兩個基礎程式範例:控制攝影機(camera) CVZoneRSP拆解_控攝影機.py 和手部的辨識 RSP拆解:手部辨識.py。本文所有程式都是使用陳會安老師包好的fChart_CVZonehttps://reurl.cc/gzbqlb)這包已裝好CVZone中的Thonny IDE。緊接著,我們將陳老師版的RSP全數加上註解(comments)以供讀者輕鬆地閱讀程式碼。最後,我們再把程式中手部辨識的程式片段獨立成一個函式(function)來管理。

1. RSP拆解:控攝影機 cap


我們以單行註解 # 及 多行註解 ''' ''' 的方式,採用試誤法(trail and error)逐步砍掉暫時用不到的指令來還原OpenCV控制攝影機的程式片段(snippet) CVZoneRSP拆解_控攝影機.py。成功執行的畫面如圖2所示。

圖2:「CVZoneRSP拆解_控攝影機.py」在陳老師版Thonny上的執行結果。

接著,我們在程式中插人適當的中斷點(break point)進入除錯模式(debugging mode)來追蹤出圖3的執行流程。

圖3:「CVZoneRSP拆解_控攝影機.py」的流程圖。

CVZoneRSP拆解_控攝影機.py
import cv2

cap = cv2.VideoCapture(0) #攝影機的handler

while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像
    
    cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
       break

cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗

2. RSP拆解:手部辨識 detector

承1,匯入圖4的 HandTrackingModule 模組(https://tinyurl.com/yxfn45et)就能引用 HandDetector 函式來識別左右手的骨架。執行結果如圖5所示。相關程式碼為「RSP拆解:手部辨識.py」。

圖4:。

圖5:「RSP拆解:手部辨識.py」的執行結果。

RSP拆解:手部辨識.py
from cvzone.HandTrackingModule import HandDetector #L5初始化用
import cv2

cap = cv2.VideoCapture(0) #攝影機的handler
detector = HandDetector(detectionCon=0.5, maxHands=1) #detectionCon:偵測的信心值(confidence)、maxHands:可測到幾隻手

while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像
    hands, img = detector.findHands(img) #從影像中偵測手

    cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
        break
    
cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗


3. RSP完整程式碼加註
承2,在 CVZoneRSP_詳註.py 中加入了手指識別 detector.fingersUp() 來判斷使用者是出剪刀(2隻手指)、石頭(無手指)和布(5隻手指)。程式的報行結果如圖6所示。

圖6:「CVZoneRSP_詳註.py」的執行結果。

CVZoneRSP_詳註.py
from cvzone.HandTrackingModule import HandDetector #L5初始化用
import cv2

cap = cv2.VideoCapture(0) #攝影機的handler
detector = HandDetector(detectionCon=0.5, maxHands=1) #detectionCon:偵測的信心值(confidence)、maxHands:可測到幾隻手

while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像
    hands, img = detector.findHands(img) #從影像中中偵測手
    
    if hands: #如果有偵測到手
        hand = hands[0]
        bbox = hand["bbox"] #bbox: bounding box
        fingers = detector.fingersUp(hand) #fingers = [手指1, 手指2, 手指3, 手指4, 手指5]
        totalFingers = fingers.count(1) #fingers裡有個1
        print(totalFingers)
        
        msg = "None" #顯示剪刀、石頭、布
        if totalFingers == 5:
            msg = "Paper"
        if totalFingers == 0:
            msg = "Rock"
        if totalFingers == 2:
            if fingers[1] == 1 and fingers[2] == 1: #食指 + 中指
                msg = "Scissors"
                
        cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2) #顯示bbox框框 + 訊息
    
cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
        break
    
cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗

4. RSP改寫:主/副程式版

承3,我們將之獨立出一個手指識別的副程式 detectHand() ,讓主程式更清爽而容易閱讀(readable)

CVZonRSP_主副程式版.py
from cvzone.HandTrackingModule import HandDetector #L5初始化用
import cv2

cap = cv2.VideoCapture(0) #攝影機的handler
detector = HandDetector(detectionCon=0.5, maxHands=1) #detectionCon:偵測的信心值(confidence)、maxHands:可測到幾隻手

def detectHand(img):
    hands, img = detector.findHands(img) #從影像中中偵測手
    
    if hands: #如果有偵測到手
        hand = hands[0]
        bbox = hand["bbox"] #bbox: bounding box
        fingers = detector.fingersUp(hand) #fingers = [手指1, 手指2, 手指3, 手指4, 手指5]
        totalFingers = fingers.count(1) #fingers裡有個1
        print(totalFingers)
        
        msg = "None" #顯示剪刀、石頭、布
        if totalFingers == 5:
            msg = "Paper"
            
        if totalFingers == 0:
            msg = "Rock"
            
        if totalFingers == 2:
            if fingers[1] == 1 and fingers[2] == 1: #食指 + 中指
                msg = "Scissors"
                
        cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2) #顯示bbox框框 + 訊息

while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像

    detectHand(img)
    
    cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
        break
    
cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗


以下是筆者加註的陳老師版RSP範例:

from cvzone.HandTrackingModule import HandDetector #L5初始化用
import cv2
cap = cv2.VideoCapture(0) #攝影機的handler
detector = HandDetector(detectionCon=0.5, maxHands=1) #detectionCon:偵測的信心值(confidence)、maxHands:可測到幾隻手
while cap.isOpened(): #當攝影機被開啟時
    success, img = cap.read() #回傳攝影機讀到的影像
    hands, img = detector.findHands(img) #從影像中中偵測手
    if hands: #如果有偵測到手
        hand = hands[0]
        bbox = hand["bbox"] #bbox: bounding box
        fingers = detector.fingersUp(hand) #fingers = [手指1, 手指2, 手指3, 手指4, 手指5]
        totalFingers = fingers.count(1) #fingers裡有個1
        print(totalFingers)
        msg = "None" #顯示剪刀、石頭、布
        if totalFingers == 5:
            msg = "Paper"
        if totalFingers == 0:
            msg = "Rock"
        if totalFingers == 2:
            if fingers[1] == 1 and fingers[2] == 1: #食指 + 中指
                msg = "Scissors"
        cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30),
                    cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2) #顯示bbox框框 + 訊息
    cv2.imshow("Image", img) #顯示圖片img,視窗標題為Image
    if cv2.waitKey(1) & 0xFF == ord("q"): #按q離開迴圈
        break
cap.release() #釋放攝影機handler cap
cv2.destroyAllWindows() #關閉視窗

註:CVZone給的原始手部辨識程式 HandTrackingExample.pyhttps://tinyurl.com/yxbpstwt)為:

from cvzone.HandTrackingModule import HandDetector
import cv2

cap = cv2.VideoCapture(0)
detector = HandDetector(detectionCon=0.8, maxHands=2)
while True:
    # Get image frame
    success, img = cap.read()
    # Find the hand and its landmarks
    hands, img = detector.findHands(img)  # with draw
    # hands = detector.findHands(img, draw=False)  # without draw

    if hands:
        # Hand 1
        hand1 = hands[0]
        lmList1 = hand1["lmList"]  # List of 21 Landmark points
        bbox1 = hand1["bbox"]  # Bounding box info x,y,w,h
        centerPoint1 = hand1['center']  # center of the hand cx,cy
        handType1 = hand1["type"]  # Handtype Left or Right

        fingers1 = detector.fingersUp(hand1)

        if len(hands) == 2:
            # Hand 2
            hand2 = hands[1]
            lmList2 = hand2["lmList"]  # List of 21 Landmark points
            bbox2 = hand2["bbox"]  # Bounding box info x,y,w,h
            centerPoint2 = hand2['center']  # center of the hand cx,cy
            handType2 = hand2["type"]  # Hand Type "Left" or "Right"

            fingers2 = detector.fingersUp(hand2)

            # Find Distance between two Landmarks. Could be same hand or different hands
            length, info, img = detector.findDistance(lmList1[8], lmList2[8], img)  # with draw
            # length, info = detector.findDistance(lmList1[8], lmList2[8])  # with draw
    # Display
    cv2.imshow("Image", img)
    cv2.waitKey(1)

RSP延伸:可自動判斷輸贏的RSP

RSP遊戲自2017年micro:bit發行以來一直是個經典教材(https://tinyurl.com/yyvo4jxa),它分成「出拳」和「判斷輸贏」兩個階段。其中,能讓板子自動判猜拳結果更是膾炙人口。因此,我們使用了深度學習的技術識別玩家的出拳之後,再搭配暴力法(brute force)的推演,便可以完成用電腦來當「公親」(台語)的任務了。我們將陳老師原程式「CVZoneRSP_詳註.py」 加上這個功能而改寫為「sleep 」和「旗標(flag) + 延時迴圈(delayed loop)」兩個新版本。其中,程式會不斷地去監測攝影機是否正常連線,會造成不斷的取得影像。所以,要想法子讓程式跑慢一點。然而,加了 sleep 指令會讓程式「打睏」,所以會有影像不及時之感。故而改用「空迴圈」的程式設計技巧來克服之。

1. sleep版:RSP自動判輸贏_sleep.py


RSP自動判輸贏_sleep.py
def judge(m, c):
    print('Computer = ' + str(c))
    print('      Me = ' + str(m))
    
    if (me == 0):
        if (c == 0):
            print('=> Tie')
            
        if (c == 1):
            print('   => Me won.')
            
        if (c == 2):
            print('   => Computer won.')
            
    if (me == 1):
        if (c == 0):
            print('   => Computer won.')
            
        if (c == 1):
            print('   => Tie')
            
        if (computer == 2):
            print('   => Me won.')           
        
    if (me == 2):
        if (c == 0):
            print('   => Me won.')
            
        if (c == 1):
            print('   => Computer won.')
            
        if (c == 2):
            print('   => Tie')
            
from random import randint
computer = randint(0, 2)
print('Computer: ' + str(computer))
print('   0: rock')
print('   1: scissors')
print('   2: paper')

me = 99
flag = 0

import time

from cvzone.HandTrackingModule import HandDetector
import cv2

cap = cv2.VideoCapture(0) #使用第一台攝影機
detector = HandDetector(detectionCon=0.5, maxHands=1) #手部偵測

#攝影机已開啟
while cap.isOpened():
    success, img = cap.read() #讀入影像
    hands, img = detector.findHands(img) #找手
#找到手
    if hands:
        hand = hands[0]
        bbox = hand["bbox"]
        
        #有幾根手指
        fingers = detector.fingersUp(hand)
        totalFingers = fingers.count(1)
        #print(totalFingers)
        
        msg = "None"
        if totalFingers == 5:
           msg = "Paper"
           me = 2
           judge(me, computer)
        
        if totalFingers == 0:    
           msg = "Rock"
           me = 0
           judge(me, computer)
        
        if totalFingers == 2:
           if fingers[1] == 1 and fingers[2] == 1:
              msg = "Scissors"
              me = 1
              judge(me, computer)
        
        cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)

        if me < 3:
            me = 99
            computer = randint(0, 2)
            print('----------------------------------------------------')
            print('Computer: ' + str(computer))
            print('   0: rock')
            print('   1: scissors')
            print('   2: paper')    

    cv2.imshow("Image", img)
    time.sleep(1)
    
#按q離開
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
        
cap.release()
cv2.destroyAllWindows()

2. 「旗標 + 延時迴圈」版:RSP自動判輸贏_delayed loop.py

承1,雖然我們努力調整了 sleep() 函式睡著的時間,但影像不即時看起來就是不順暢。所以,我們借用北商大資管系張隆君教授在研習時教授的延時程式技巧,讓程式執行起來較為流暢許多(圖7)。其中,讀者需先了解程式設計中常用的旗標跳脫法,在循環的迴圈中,埋下適當的越獄暗門。「舉旗待定.py

圖7:「舉旗待定.py」的執行結果。

舉旗待定.py
flag = 0
i = 0

while flag == 0:
   i = i + 1

   if (i < 50):
      if (i % 3 == 0): 
          print(str(i) + ' ', end = '')
   else:
      flag = 1


RSP自動判輸贏_delayed loop.py
def judge(m, c):
    print('Computer = ' + str(c))
    print('      Me = ' + str(m))
    
    if (me == 0):
        if (c == 0):
            print('=> Tie')
            
        if (c == 1):
            print('   => Me won.')
            
        if (c == 2):
            print('   => Computer won.')
            
    if (me == 1):
        if (c == 0):
            print('   => Computer won.')
            
        if (c == 1):
            print('   => Tie')
            
        if (computer == 2):
            print('   => Me won.')           
        
    if (me == 2):
        if (c == 0):
            print('   => Me won.')
            
        if (c == 1):
            print('   => Computer won.')
            
        if (c == 2):
            print('   => Tie')
            
from random import randint
computer = randint(0, 2)
print('Computer: ' + str(computer))
print('   0: rock')
print('   1: scissors')
print('   2: paper')

me = 99
delay_find = 0
flag = 0

import time

from cvzone.HandTrackingModule import HandDetector
import cv2

cap = cv2.VideoCapture(0) #使用第一台攝影機
detector = HandDetector(detectionCon=0.5, maxHands=1) #手部偵測

#攝影机已開啟
while cap.isOpened():
    success, img = cap.read() #讀入影像
    hands, img = detector.findHands(img) #找手
    
    if flag == 0:       
        #找到手
        if hands:
            hand = hands[0]
            bbox = hand["bbox"]
            
            #有幾根手指
            fingers = detector.fingersUp(hand)
            totalFingers = fingers.count(1)
            #print(totalFingers)
            
            msg = "None"
            if totalFingers == 5:
               msg = "Paper"
               me = 2
               judge(me, computer)
            
            if totalFingers == 0:    
               msg = "Rock"
               me = 0
               judge(me, computer)
            
            if totalFingers == 2:
               if fingers[1] == 1 and fingers[2] == 1:
                  msg = "Scissors"
                  me = 1
                  judge(me, computer)
            
            cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
            cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
            
            if me < 3:
                me = 99
                computer = randint(0, 2)
                print('----------------------------------------------------')
                print('Computer: ' + str(computer))
                print('   0: rock')
                print('   1: scissors')
                print('   2: paper')
                
                           
            flag = 1
            delay_find = 0
    #cv2.putText(img, msg, (bbox[0]+200,bbox[1]-30), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
    cv2.imshow("Image", img)
    
    delay_find = delay_find + 1    
    if delay_find > 30:
       flag = 0
       
#按q離開
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
        
cap.release()
cv2.destroyAllWindows()

RSP應用發想

技術問題搞定了,接下來就是教學發想的時刻。
RSP揭示的是手掌骨骼(skeleton)辨識技術,電腦能「看到」我們的手之後,可以怎麼玩呢?
  1. 體感(gestures)控制:投影片(slides)的控制或機器人前進/後退/停止控制、YouTube播放控制…。
  2. 電腦教九九乘乘表。
  3. 手語(sign language)辨識。
讀者們可能更想問:能辨識掌紋算命嗎?

手部辨識原理解說

在這篇 https://tinyurl.com/y4zwklz9 文章中講述的十分清楚,利用手骨估計模型(Hand Landmark Model),我們可以取得21個特徵點(feature points)送到機器學習(machine learning)中的深度學習去演算出手指的資訊。


  1. 六種授權條款
  2. 僅以此文向陳老師致上最深的啟蒙敬意。

2021年11月14日 星期日

邁向Python教學之路

 邁向Python教學之路

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

Nov. 14, 2021
88x31.png[1]


  1. JS積木 → Python:從JavaScript(JS)積木入門(https://makecode.microbit.org/#editor)可先不管文字式語法(syntax)的繁瑣規範。待有編程基礎後再進入Python的高階程式語言。
  2. JS積木 →  mP積木 → Python:在JS積木和Python中插人micro Python(官方的正式名稱叫static python,https://makecode.com/language。它是Pyhon語法的簡化版加上micro:bit開發版硬體控制程式的綜合體)為了方便起見,本文皆用mP來稱呼)做為中介的過渡站。在MakeCode平台上,mP的操作是拖曳出積木就會產生對應的語法程式,讓Python的初學者可以在進入純文字程式撰寫前有一稍稍的緩衝銜接。
  3. mP積木 → Python:由mP積木自動產生語法程式的輔助開始熟悉mP的語法規則後,再進入Python的編程世界。
  4. Blockly積木 → Python:此類較著名的Blockly積木工具有 Edu Blocks(https://app.edublocks.org/ )和Blockpy(https://think.cs.vt.edu/blockpy/blockpy/load )。其中,只有後者具積木和語法同時互轉的功能。
  5. Python:純文字語法的編程學習。
  6. 流程圖 → Python:國內已出版百餘




2021年11月6日 星期六

從想法(ideas)到實現(implementation):我是怎麼用程式解決問題的?

 從想法(ideas)到實現(implementation):

我是怎麼用程式解決問題的?

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

Nov. 6, 2021
88x31.png[1]

問題

在micro:bit(文後皆以小彼特稱之)官方MakeCode平台(https://makecode.microbit.org/)的5×5 LED點矩陣(dot matrix)中,畫出左下左上右上右下四個直角三角形(圖1)[2]。


圖1:在小彼特上畫出左下、左上、右上及右下四個直角三角形。

想法:觀察(observe)

我們先猜測:會畫一個方向就能畫出其他方向。所以,我們大膽的把問題縮小四分之一:「如何畫出一個左下的直角三角形?(圖2)」。我們將之攤到試算表(Spreadsheet)繪製成觀察圖(observation diagram)中以利後續觀察。

 

圖2:左下直角三角形的觀察圖。

接著,我們考慮小彼特上這25顆LED的定位方式,它是被擺放在直角座標軸的第一限象(正X、正Y)上,如圖3所示。

圖3:小彼特的LED定位系統。


最後,我們可以再細部構思出其他3個三角形的雷同結構(圖4)。

圖4:其他3個三角形的觀察圖。

想法:解析(Analyze)

先固定Y軸的一列,然後再從左到右掃瞄X軸。接著再往下移動一列,再掃瞄…再移動,再掃瞄…。

根據上面的策略,我們甞試採用「之字型掃瞄法」來推演左下直角三角形的點亮方法。

圖5:之字形掃瞄法。


步驟1:根據圖3的定位系統,逐一填入圖2的觀察圖。如下圖6所示,先填妥綠網底的各顆LED座標,然後將Y軸中i的變化情形填入黃網底處。最後,我們可以輕鬆地將i和j的變化情歸納到藍網底這兩格。其中,i表示X軸由左到右(0~4)依序掃瞄;j表示Y軸由上到下(0~4)位序掃瞄。而i與j交會處即點亮該LED。

圖6:左下直角三角形觀察圖。

步驟2:重複步驟1的方法,就能全數完成圖7的所有觀察圖。

圖7:其餘三個直角三角形的觀察圖。

想法:流程圖(Flowchart)

承上節,我們的破題策略是圖5的之字形掃瞄法:由上到下掃瞄,過程中,再由左到右掃。緊接著,我們從圖6中歸納出:
上到下掃瞄用j來控制,j = 0~4;左右掃瞄用i來控制,i = 0~j。
整理一下,就可畫出圖8的流程圖。其中,粉紅色網底包在無網底的內部,正好對應到巢狀for迴圈(nested for-loop),如圖9所示。

圖8:點亮左下直角三角形的流程圖。

圖9:流程圖對應到巢狀for迴圈結構。

實現

經由上述的觀察與歸納過程而導出的流程圖方是編程之母。有了流程圖,就好像孔明當年在隆中想出的木牛流馬設計圖一樣,離用程式語言寫出執行程序就不遠了。
為了程式簡潔起見,我們採用模組化(modualized)程式設計結構的方式寫成以下的程式碼:

#副程式:左下直角三角形
def show1():
    for j in range(04 + 1):  #由上到下掃
        for i in range(0, j + 1): #由左到右掃
            led.plot(i, j)  #點亮位置(i, j)的LED

#主程式
show1()

依此類推,四個直角三角形組成的完整程式碼如下所示,讀者可由 https://reurl.cc/gzxKAX 下載我們發佈(publish)到MakeCode上的專案(project)。其中,我們還加上了第三個最外層的迴圈(用times變數控制)來讓每個直角三角形依序閃爍三次。展示的GIF動畫如圖10所示。


圖10:完整的GIF專案動畫。

#副程式1:畫直角三角形(直角在左下)
def show1():
    basic.clear_screen()

    for times in range(01):
        for j in range(04 + 1):
            for i in range(0, j + 1):
                led.plot(i, j)
                basic.pause(50)

        #basic.pause(300)    
        #basic.clear_screen()
        #basic.pause(300) 

#副程式2:畫直角三角形(直角在右上)
def show2():
    basic.clear_screen()

    for times in range(01):
        for j in range(04 + 1):
            for i in range(j, 4 + 1):
                led.plot(i, j) #(4 - i + j, j)
                basic.pause(50)

        basic.pause(300)
        basic.clear_screen()
        basic.pause(300)    

#副程式3:畫直角三角形(直角在右下)
def show3():
    basic.clear_screen()

    for times in range(01):
        for j in range(04 + 1):
            for i in range(4 - j, 4 + 1):
                led.plot(i, j)
                basic.pause(50)

        basic.pause(300)
        basic.clear_screen()
        basic.pause(300)

#副程式4:畫直角三角形(直角在右上)
def show4():
    basic.clear_screen()

    for times in range(01):
        for j in range(05):
            for i in range(04 - j + 1):
                led.plot(i, j)
                basic.pause(50)

        basic.pause(300)
        basic.clear_screen()
        basic.pause(300)

#主程式
show1()
show2()
show3()
show4()

學習任務

筆者已將本文應用至國九資訊科技的Python教學上[2]。我們設計了以下的三節課135分鐘來引領學生走過一趟完整的編程思維之旅。我們設計的檢核表如圖11所示。

第1節:畫一,使用單層for迴圈。
第2節:畫左下直角三角形,使用雙層for迴圈。
第3節:畫左上、左下、右上、右下4個直角三角形並各別閃爍3欠,使用3層for迴圈。

圖11:學習任務檢核表。

教學翦影









結語

流程圖是編程邏輯之母(積木程式不是) ~陳會安老師[3]

一轉眼,筆在中學資訊領域已任教十年了,小彼特教學也逾4年餘[4]。有時候時常在想,當編程從專業走到通識,吾人到底在教什麼?
直到日前領受陳會安老師醍醐灌頂,日前又受到張隆君教授[7]啟發,遂完整的打通了圖12的編程通識教育流程。
問題解決過程是一連串的邏輯思維活動,從科學的觀察與歸納法則而得出解方的流程圖,之後才是由程式設計接手。現今雖然知識爆炸、科技日進千里,然而,一般的普羅大眾要培育是編程邏輯是還是建立能畫出圖示的前半部腦內思維活動。如何用電腦解決真實問題是一連串的why、why、why的邏輯思考力訓練。而程式只是實現的工具之一罷了!

圖12:
編程通識教育完整流程。

%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3Bdashed%3D1%3B%22%20edge%3D%221%22%20source%3D%224%22%20target%3D%225%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3Bhtml%3D1%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3BentryX%3D0%3BentryY%3D0.5%3BentryDx%3D0%3BentryDy%3D0%3BexitX%3D0.5%3BexitY%3D1%3BexitDx%3D0%3BexitDy%3D0%3B%22%20edge%3D%221%22%20source%3D%224%22%20target%3D%227%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%3E%3CArray%20as%3D%22points%22%3E%3CmxPoint%20x%3D%22249%22%20y%3D%22525%22%2F%3E%3C%2FArray%3E%3C%2FmxGeometry%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%224%22%20value%3D%22%E6%83%B3%E6%B3%95%EF%BC%88ideas%EF%BC%89%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22174%22%20y%3D%22320%22%20width%3D%22150%22%20height%3D%22150%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%225%22%20value%3D%22%E5%AF%A6%E7%8F%BE%EF%BC%88implementation%EF%BC%89%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22492%22%20y%3D%22315%22%20width%3D%22160%22%20height%3D%22160%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%226%22%20style%3D%22edgeStyle%3DorthogonalEdgeStyle%3Brounded%3D0%3BorthogonalLoop%3D1%3BjettySize%3Dauto%3Bhtml%3D1%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3BexitX%3D1%3BexitY%3D0.5%3BexitDx%3D0%3BexitDy%3D0%3BentryX%3D0.5%3BentryY%3D1%3BentryDx%3D0%3BentryDy%3D0%3B%22%20edge%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%3E%3CmxPoint%20x%3D%22460%22%20y%3D%22524%22%20as%3D%22sourcePoint%22%2F%3E%3CmxPoint%20x%3D%22572%22%20y%3D%22474%22%20as%3D%22targetPoint%22%2F%3E%3CArray%20as%3D%22points%22%3E%3CmxPoint%20x%3D%22572%22%20y%3D%22524%22%2F%3E%3C%2FArray%3E%3C%2FmxGeometry%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%227%22%20value%3D%22%E6%B5%81%E7%A8%8B%E5%9C%96%EF%BC%88flowchart%EF%BC%89%22%20style%3D%22ellipse%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3BfontFamily%3DTimes%20New%20Roman%3BfontSize%3D18%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22354%22%20y%3D%22470%22%20width%3D%22110%22%20height%3D%22110%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E

  1. 六種授權條款
  2. 編程邏輯之重複結構:巢狀for迴圈,https://reurl.cc/82zdqR
  3. 陳會安老師簡介:https://tinyurl.com/ykyvgeae,陳會安老師FB:https://tinyurl.com/yfk6ym82
  4. 筆者的小彼特教學:https://tinyurl.com/ygqm5rcu
  5. 張隆君教授FB:https://tinyurl.com/yh2zsrdy