ANN手勢辨識猜拳機器人
最近工作單位的老師希望可以實作一個機器人範例,其主要是可以整合機電實做和機器學習,做一個可以與人類猜拳的機器人
起初老師的構想是可以用影像辨識的方式來讓機器人可以偵測人出拳的手勢(石頭,剪刀或布).但考量到我本身的影像辨識熟練度不高,樣本蒐集和機器學習的時間也都很花時間. 雖然我也覺得影像辨識類的範例可能比較容易激起學生的興趣,但如果省略掉樣本蒐集和機器學習這些比較枯燥的步驟,似乎課堂上知識面內容又過於薄弱. 因此我的想法是初版的機器人範例先不要用影像,而是以感知機為基礎建立一個多層的人工神經網路,將每隻手指的彎曲度(彎曲感測器)做為輸入,用來判斷可能的手勢.這個題目本身在資料蒐集和機器學習的時間可以大幅化簡,但卻還是保留.
系統流程:
大致上來說,整個遊戲的流程會由使用者按下"開始"鈕開始.經過3秒鐘的倒數計時,機器人本身會用亂數決定一個手勢(剪刀,石頭,或布)並將機器手移動成該手勢.此時機器人也會檢查使用者的手勢,並決定遊戲勝負,最後則更新記分板.
系統架構:
Arduino Uno: 機器人運作控制核心
Servo:帶動機械運動的伺服機馬達
FlexSensor: 用來偵測手指彎曲程度的感測器
User Interface: 遊戲啟動按鈕,分數提示...等 輸入及顯示裝置
記分板, 左邊數字為機器人勝場數,右方為玩家的勝場數
機器手機構設計:
為了簡化設計,雖然人有5支手指,但對於猜拳遊戲來說,我們可以把"食指中指"及"無名指小指"視為兩個群組,而大拇指獨立一組.所以我們可以用3個伺服機就能表現出猜拳的三種動作.
硬體完成:
ANN人工神經網路-Arduino實做:
我們在Arduino中實做出上圖的網路架構,以食指,無名指和大拇指的彎曲感測做為網路輸入,最終輸出石頭,剪刀,布三種手勢的輸出權重.
使用陣列資料結構描述網路架構, 陣列中將每個節點輸入權重紀錄器來,關係如下圖(也可參考上圖,如果你不覺得圖太小的話)
Layer0:
float Layer0_W[4][4]={{-3.35796,-1.126993,0.00737043,1.9779116}, {-2.4028397,-1.3854587,-0.7931496,2.080642}, {1.0243597,2.1095006,1.6874604,-2.316983}, {1.8379576,1.5571779,1.2571335,-2.14945}};
Layer1:
float Layer1_W[4][5]={{3.109413,2.1515133,-1.7762052,-2.118841,1.3897169}, {-0.54771924,-2.6526208,3.062316,2.4938545,0.12920415}, {1.0274583,1.9144325,-3.6218135,-2.1290317,0.1106161}, {-3.6488092,-3.56605,1.812533,1.8006505,-1.1013095}};
輸出層:
float LayerOutputs_W[3][5]={{-3.0573914,-0.33590198,-2.9392657,3.5195374,-0.66786194}, {1.2544978,2.7321491,-2.4654458,-39482572,-1.2988576}, {1.7912973,-3.1768422,1.5621092,-1.8792809,-0.85067666}};
在使用" Layer0_W", "Layer1_W", LayerOutput_W"描述完網路架構後,接下來就是實際執行ANN的部份了.神經網路雖然看似複雜,但執行階段其實就是在計算各層,各節點的加權總合,我在這個範例的做法是使用2層迴圈來進行每一層的加權總和,第一層代表層的節點,第二層代表每個節點的輸入.
//Layer0 for(i=0;i<4;i++) { for(j=0;j<3;j++) { Layer0_Outputs[i]=Layer0_Outputs[i]+(ANNinputs[j]*Layer0_W[i][j]); } Layer0_Outputs[i]=Layer0_Outputs[i]+Layer0_W[i][3]; Layer0_Outputs[i]=HardSigmoid(Layer0_Outputs[i]); } //Layer1 for(i=0;i<4;i++) { for(j=0;j<4;j++) { Layer1_Outputs[i]=Layer1_Outputs[i]+(Layer0_Outputs[j]*Layer1_W[i][j]); } Layer1_Outputs[i]=Layer1_Outputs[i]+Layer1_W[i][4]; Layer1_Outputs[i]=HardSigmoid(Layer1_Outputs[i]); } //LayerOutputs for(i=0;i<3;i++) { for(j=0;j<4;j++) { ANNOutputs[i]=ANNOutputs[i]+(Layer1_Outputs[j]*LayerOutputs_W[i][j]); } ANNOutputs[i]= ANNOutputs[i]+LayerOutputs_W[i][4]; ANNOutputs[i]=HardSigmoid(ANNOutputs[i]); }
激勵函數HardSigmoid( )
float HardSigmoid(float x0) { float retHS=0.0; if(x0 < -2.5) { retHS=0.0; } else if(x0 > 2.5) { retHS=1.0; } else if((-2.5 <= x0)&&(x0 <= 2.5)) { retHS=0.2 * x0 + 0.5; } return retHS; }
機器學習-PC端實作:
雖然機器人我們以Arduino為核心,但機器學習的部分會在PC上進行.我們先將監督式學習的樣本準備在csv檔中,交由"gestureML.py"進行機器學習,最後輸出該網路的權重
與在Arduino上使用ANN一樣,在PC上的使用Keras,也是需要描述網路架構.Keras本身Sequential物件可以用"add"方法來依序建立各層,以前述的網路架構來說.我們會建立Layer0,Layer1及輸出層 (輸入層本身就是Layer0的輸入值,所以不需要特別建立)
model = keras.Sequential() model.add(keras.layers.Dense(4, input_shape=(InuputNum,), activation="hard_sigmoid", kernel_initializer='glorot_uniform')) model.add(keras.layers.Dense(4, activation="hard_sigmoid")) model.add(keras.layers.Dense(3, activation="hard_sigmoid"))
在學習完成後,可以使用"model.get_weights()"將整個網路學習出來的結果印在畫面上.並將這些資料做為Arduino中"Layer0_W", "Layer1_W", LayerOutput_W"三個陣列的資料內容.
"model.get_weights()"的輸出
完整的PC端程式及說明,請參考本範例的 google colab:
https://colab.research.google.com/drive/1dDi9V4d4cpNsRSTb1DHKLPFErb-c9lrD?usp=sharing
測試影片:
後記:
-其實這個專題本身當然是可以不用類神經網路就能達到所需的功能
-這個專題在機器學習的作法是直接建立手指動作非常明確的真質表,後續實驗可改成蒐集不同人帶感應手套的數據並直接將這些數據標籤後做機器學習
-原設計有蜂鳴器在倒數完畢後會發出聲響,但後來發現直接放在木板上,似乎因為共振或其他不明原因,聲音幾乎發不出來
相關連結:
手勢機器學習google colab:https://colab.research.google.com/drive/1dDi9V4d4cpNsRSTb1DHKLPFErb-c9lrD#scrollTo=g_mzKQkSZc-J
彎曲Sensor實做動態手套:https://madeinti.blogspot.com/2020/04/blog-post.html
參考資料:
HardSigmoid的定義:https://keras.io/zh/activations/
Arduino實做ANN即到傳遞:http://robotics.hobbizine.com/arduinoann.html
中文講解Arduino ANN實做: https://makerpro.cc/2019/05/self-learning-series2-of-ai/
深度學習入門教室 臉譜出版
留言
張貼留言