牟奕炫
在第14期的《基于MediaPipe的Python編程手勢識別應(yīng)用》一文中,我們借助MediaPipe實現(xiàn)了手部21個關(guān)鍵點的精準識別。MediaPipe不僅可以判斷識別各個獨立的關(guān)鍵點,如果結(jié)合某些點的區(qū)域劃分進行相關(guān)的邏輯運算,就能夠非常方便地進行很多手勢信息的“解讀”,比如識別0-9十個數(shù),進而在樹莓派中實現(xiàn)簡易“猜拳”游戲。
1.五個指尖關(guān)鍵點與“凸包”區(qū)域
為了對單手所表示的十個數(shù)進行手勢識別,判斷五個指尖關(guān)鍵點(4、8、12、16、20)與手掌心范圍的相互位置是非常重要的環(huán)節(jié)。手掌心范圍的界定可以通過“凸包”(convexhull)來實現(xiàn),建立列表變量round_points,其值為[0,1,2,3,6,10,14,19,18,17,10],表示從手腕根部0開始,向上沿大拇指依次經(jīng)過1、2、3點位,轉(zhuǎn)至食指的6、中指的10、無名指的14,最終從小拇指的19、18、17點位返回至手腕根部0,如此便構(gòu)建了一個包含手掌心在內(nèi)的閉合區(qū)域(如圖1)。
通過對五個指尖點是否在“凸包”內(nèi)的判斷(單獨的某個指尖或是幾個指尖的不同組合),就能夠表示出0-9這十個數(shù)字,并且將相關(guān)的代碼封裝成函數(shù)。
2.手勢數(shù)字的判斷函數(shù)
導(dǎo)入“mediapipeasmp”“cv2”“numpyasnp”“math”庫模塊。
計算兩個矢量角度finger_angle(point1,point2)函數(shù):借助numpy庫再通過兩次數(shù)學(xué)計算,建立變量two_angle,賦值為“np.dot(point1,point2)/(np.sqrt(np.sum(point1**2))*np.sqrt(np.sum(point2**2)))”,再賦值為“np.arccos(two_angle)/math.pi*180”,最后將該值返回即可。
判斷并返回手勢信息finger_sign(tip_finger,list_data)函數(shù)的編寫:1和9的共同點是均通過單根食指(直立或彎曲)來表示,判斷條件是“iflen(tip_finger)==1andtip_finger[0]==8:”(其中的tip_finger存儲的是“凸包”外的指尖關(guān)鍵點),意思是“凸包”外只檢測到有一個指尖(即一根手指)并且該指尖關(guān)鍵點是8(即食指指尖);建立兩個矢量point1、point2,賦值為“l(fā)ist_data[6]-list_data[7]”“l(fā)ist_data[8]-list_data[7]”,分別計算食指關(guān)鍵點6至7、8至7的矢量值;再通過調(diào)用函數(shù)為變量two_angle賦值:“finger_angle(point1,point2)”,并且進行“iftwo_angle<160:”的判斷,如果該角度值小于160度則認定為“彎曲的食指”,表示手勢識別的結(jié)果是數(shù)字9;條件不成立,則認定為“直立的食指”,變量finger_sign存儲的手勢識別結(jié)果即為數(shù)字1。
兩根手指可以表示2、6和8三種情況。對數(shù)字2的判斷條件為“eliflen(tip_finger)==2andtip_finger[0]==8andtip_finger[1]==12:”,意思是共有兩個指尖在“凸包”外,并且對應(yīng)的指尖關(guān)鍵點分別是8(食指指尖)和12(中指指尖),表示伸出了食指和中指,對應(yīng)的手勢識別數(shù)字為2。數(shù)字6對應(yīng)的兩個指尖關(guān)鍵點是拇指指尖4和小指指尖20:“eliflen(tip_finger)==2andtip_finger[0]==4andtip_finger[1]==20:”,而數(shù)字8對應(yīng)的則是拇指指尖4和食指指尖8。
數(shù)字3和7涉及三根手指。3的判斷條件為“eliflen(tip_finger)==3andtip_finger[0]==8andtip_finger[1]==12andtip_finger[2]==16:”,意思是共有三個指尖在“凸包”外,關(guān)鍵點是8(食指指尖)、12(中指指尖)和16(無名指指尖);數(shù)字7的判斷條件為“eliflen(tip_finger)==3andtip_finger[0]==4andtip_finger[1]==8andtip_finger[2]==12:”,對應(yīng)的指尖關(guān)鍵點除了8(食指指尖)和12(中指指尖)外,用4(拇指指尖)代替了16(無名指指尖)。
數(shù)字4的判斷條件為“eliflen(tip_finger)==4andtip_finger[0]==8andtip_finger[1]==12andtip_finger[2]==16andtip_finger[3]==20:”,即檢測到有四個指尖處于“凸包”外;
數(shù)字5的判斷條件為“eliflen(tip_finger)==5:”,表示檢測到五個指尖全部處于“凸包”外;
數(shù)字0的判斷條件為“eliflen(tip_finger)==0:”,表示在“凸包”外沒有檢測到任何一個指尖關(guān)鍵點。
如果以上if和elif十種可能性均不符合條件的話,則認定沒有檢測到有效的數(shù)字,變量finger_sign值為空("");最后打印輸出提示信息并將finger_sign返回:“print("檢測到的手勢數(shù)字為:",finger_sign)”、“returnfinger_sign”(如圖2)。(源代碼請至壹零社公眾號下載。)
3.編寫main()主程序代碼
參考第14期代碼,調(diào)用攝像頭進行檢測對象的定義等相關(guān)初始化操作:“camera=cv2.VideoCapture(0)”“mpHands=mp.solutions.hands”“hands=mpHands.Hands()”“mpDraw=mp.solutions.drawing_utils”;在“whileTrue:”循環(huán)結(jié)構(gòu)中,先讀取攝像頭所捕獲的畫面信息(包括畫面的寬度和高度值)、將BGR模式轉(zhuǎn)換為RGB模式等操作,再進行所有指尖關(guān)鍵點二維坐標值的采集:建立變量list_data(賦值為空列表),通過“foriinrange(21):”循環(huán),獲取對應(yīng)的(x,y)坐標值:“x,y=int(hand.landmark[i].x*w),int(hand.landmark[i].y*h)”,并將其追加至list_data中:“l(fā)ist_data.append([x,y])”。
接著進行“凸包”區(qū)域的界定,包括三個語句:“l(fā)ist_data=np.array(list_data,dtype=np.int32)”“round_points=[0,1,2,3,6,10,14,19,18,17,10]”和“hull_data=cv2.convexHull(list_data[round_points,:])”,再通過語句“cv2.polylines(img,[hull_data],True,(0,0,255),3)”實現(xiàn)“凸包”的紅色線框繪制;然后,進行“凸包”區(qū)域外指尖關(guān)鍵點的查找及畫面結(jié)果信息的顯示標注:變量tip_list值為“[4,8,12,16,20]”,對應(yīng)五個指尖的關(guān)鍵點編號,在“foriintip_list:”循環(huán)中先建立變量position,賦值為“(int(list_data[i][0]),int(list_data[i][1]))”,即點的坐標;建立變量dist,賦值為“cv2.pointPolygonTest(hull_data,position,True)”,作用是檢測這些點是否在“凸包”內(nèi),判斷條件“ifdist<0:”成立的話,說明關(guān)鍵點在“凸包”外,則將該數(shù)據(jù)追加:“tip_finger.append(i)”,循環(huán)結(jié)束后完成所有處于“凸包”外的點的收集。然后建立變量draw_finger_sign,調(diào)用函數(shù)“finger_sign(tip_finger,list_data)”,參數(shù)tip_finger和list_data分別表示“凸包”外關(guān)鍵點的列表和關(guān)鍵點的(x,y)坐標;語句“cv2.putText(img,'%s'%(draw_finger_sign),(530,120),cv2.FONT_HERSHEY_SIMPLEX,5,(0,0,255),4,cv2.LINE_AA)”實現(xiàn)的功能是在視頻畫面的右上角位置顯示輸出手勢識別的數(shù)字(紅色);下面的“foriintip_list:”循環(huán)作用是將五個指尖關(guān)鍵點進行二次描繪,先獲?。▁,y)坐標值:“int(hand.landmark[i].x*w),int(hand.landmark[i].y*h)”,再使用粉紅色繪制圓點:“cv2.circle(img,(x,y),7,(255,0,255),-1)”。
最后,進行視頻窗口名稱設(shè)置、熱鍵退出、攝像頭資源的釋放及窗口的關(guān)閉等操作。
4.測試
將程序保存為“[01]Recognize_Number.py”,按F5鍵運行測試;程序能夠快速準確地進行手勢識別——提示信息顯示有“檢測到的手勢數(shù)字為:X”,同時攝像頭畫面右上角也同步顯示有該數(shù)字。