工程筆記本 / AI 人工智慧

AI 人工智慧

微軟 LOBE ai - 離線訓練影像辨識模型!

鄭 建彥📅 2020-11-11👁 67
撰寫/攝影 曾吉弘
時間 1.5 - 2 hrs
成本 具備webcam的筆記型電腦或桌上型電腦
難度

★★★☆☆

Lobe ai

  Lobe ai 是微軟推出的離線版神經網路分類工具,相較於 Google Teachable Machine,離線就能訓練神經網路也許更適合教室使用。這股浪潮確實是資料驅動(data-driven)的時代,誰能掌握高品質的資料誰就能更快擁有合乎需求的神經網路。CAVEDU 2019 年所出版[實戰AI資料導向式學習]也有談到這個概念,使用 Raspberry Pi 搭配 Keras 訓練的視覺分類神經網路模型來實作路牌辨識機器人(讓我打一下書~嘛) 好,回歸正題,快開啟主頁面(https://lobe.ai)來看看! 根據主頁,目前可用的功能為影像分類,後續會推出物件偵測與資料分類等,非常令人期待。

下載與安裝

     請由主頁右上角的 Download 選項來下載,安裝包大約 380MB。依照預設設定安裝完畢,開啟主畫面如下,相當清爽。    點選 New Project 來開啟新專案,目前只有影像分類可用囉

建立專案

      空白專案畫面如下,我今天想要辨識兩種目標,都是玩具:薩克頭(zaku) 與 德姆頭 (DOM),因此總共需要三個類別,第三個類別是用來處理以上皆非的狀況,否則後續在辨識時就算看到別的東西,您的神經網路也只能薩克 / 德姆二選一。       Lobe 標榜每個類別只要5張照片就可以訓練,這真的是很厲害的數字啊! 點選右上角 Import,可以看到有三種建立資料集的方式, Images: 從電腦端上傳照片 Camera: 使用 webcam 拍照 Dataset: 上傳已經整理好的資料集   在此使用 Camera 方便我們快速驗證。

建立資料集

  在此總共建立三個類別:zaku、dom 與 other。 在上一步的 Import → Camera 之後,會出現即時的 webcam 畫面,點選中間的圓圈就會拍照,一直按著就會連拍。   本專案針對薩克與德姆各準備兩種不同顏色與略有不同的外型,希望能看看辨識的效果如何。可以看到左側 Unlabeled(未標註)的影片有 47張,後續再標註就好。   下圖左為沙漠型德姆,圖右為一般型德姆(在專業什麼..) [gallery columns="2" size="full" ids="40992,40993"] 把想要被標誌為 dom 的照片全部選起來,點選任何一張照片左下角標籤名稱,就可以一次全部修改好。如果發現哪張照片糊了或是不滿意,都可以馬上刪除。 再來是薩克,一樣有藍色與紅色的薩克兩種。類別的照片數量請盡量保持差不多的數量。 [gallery columns="2" size="full" ids="40995,40996"] 最後是 other,這裡我放了人臉、水壺以及背景。但是請注意,可能會出現但不希望被辨識錯誤的項目才要放在 other 類別中,否則就不需要放進去喔。

訓練

發現了嗎?只要建立了第二個類別並標註之後,Lobe 就會自動開始訓練了。訓練速度和您的電腦規格有關,以我的筆記型電腦 (i7, 10GB ram),192 張照片大約10分鐘,相較於 Google Teachable Machine 的一分鐘之內來說這實在有點久,但離線執行確實有其方便之處,請您多多評估囉。 [gallery size="full" columns="2" ids="40998,40999"]

測試

   測試分為 Images Camera 兩個選項,前者讓您從電腦端上傳照片,可以看到以下這張照片被分類為 dom,分類錯誤!但您可以點選右下角的紅色禁止符號來告訴 Lobe 他做錯了並給予正確的標籤(zaku),之後再次訓練時就會更好喔!   接著是 Camera 即時預覽畫面,正確辨識為 zaku!您可以點選右下角的綠色勾勾將本張照片加入 zaku 類別,這時 lobe 也會馬上開始訓練。 多多調整,盡量讓每個類別的辨識結果都愈高愈好。

匯出

   如果您滿意模型表現的話,就可以匯出(export)了,這也是這類工具最棒的地方。 請點選左上角的[]圖示,開啟選單: Preferences → Project Settings,有兩個選項,分別是針對準確度最佳化以及針對速度最佳化。在此選擇後者,您可以兩個都比較看看。點選之後 Lobe 會需要一些時間完成最佳化。 [gallery columns="2" size="full" ids="41003,41004"] 接著就可以匯出了,請點選左上角的[]圖示,開啟選單:Export,會看到 lobe 提供以下三個匯出選項:TensorFlow、TensorFlow Lite 與 Local API,請點選 TensorFlow Lite 就會開始匯出。 [gallery columns="2" size="full" ids="41005,41006"]   下載之後會出現一個與您專案同名並加上 TFLite 的資料夾(例如 DOM_ZAKU TFLite),會有 .example 資料夾,裡面有一個 python 程式可以跑跑看。另外就是 saved_model.tflite 與 signature.json 這兩個模型檔。 在此用 https://netron.app/ 這個神經網路視覺化工具來看看 .tflite 模型,層數約在80層左右,大量使用了 Conv2D 來進行卷積運算。   如果選擇 local API 會出現對應的語法與 parse result 說明

範例

馬上寫一個 python 來玩玩看。請先根據本篇文章在您的電腦上安裝好 Anaconda 與所需的套件。   執行語法: [pastacode lang="bash" manual="python%20LOBE_WEBCAM_tflite.py%20--model%20%22DOM_ZAKU%20TFLite%22" message="" highlight="" provider="manual"/]   注意 LOBE_WEBCAM_tflite.py 要與 DOM_ZAKU TFLite 資料夾同一層。   LOBE_WEBCAM_tflite.py 內容: [pastacode lang="python" manual="%23%0A%23%20%20-------------------------------------------------------------%0A%23%20%20%20Copyright%20(c)%20CAVEDU.%20%20All%20rights%20reserved.%0A%23%20%20-------------------------------------------------------------%0A%22%22%22%0ASkeleton%20code%20showing%20how%20to%20load%20and%20run%20the%20TensorFlow%20Lite%20export%20package%20from%20Lobe.%0A%22%22%22%0A%0Aimport%20argparse%0Aimport%20json%0Aimport%20os%0A%0Aimport%20numpy%20as%20np%0Afrom%20PIL%20import%20Image%0A%0Aimport%20cv2%0A%0Aimport%20tflite_runtime.interpreter%20as%20tflite%0A%0Adef%20get_prediction(image%2C%20interpreter%2C%20signature)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Predict%20with%20the%20TFLite%20interpreter!%0A%20%20%20%20%22%22%22%0A%20%20%20%20%23%20Combine%20the%20information%20about%20the%20inputs%20and%20outputs%20from%20the%20signature.json%20file%20with%20the%20Interpreter%20runtime%0A%20%20%20%20signature_inputs%20%3D%20signature.get(%22inputs%22)%0A%20%20%20%20input_details%20%3D%20%7Bdetail.get(%22name%22)%3A%20detail%20for%20detail%20in%20interpreter.get_input_details()%7D%0A%20%20%20%20model_inputs%20%3D%20%7Bkey%3A%20%7B**sig%2C%20**input_details.get(sig.get(%22name%22))%7D%20for%20key%2C%20sig%20in%20signature_inputs.items()%7D%0A%20%20%20%20signature_outputs%20%3D%20signature.get(%22outputs%22)%0A%20%20%20%20output_details%20%3D%20%7Bdetail.get(%22name%22)%3A%20detail%20for%20detail%20in%20interpreter.get_output_details()%7D%0A%20%20%20%20model_outputs%20%3D%20%7Bkey%3A%20%7B**sig%2C%20**output_details.get(sig.get(%22name%22))%7D%20for%20key%2C%20sig%20in%20signature_outputs.items()%7D%0A%0A%20%20%20%20%23%20process%20image%20to%20be%20compatible%20with%20the%20model%0A%20%20%20%20input_data%20%3D%20process_image(image%2C%20model_inputs.get(%22Image%22).get(%22shape%22))%0A%0A%20%20%20%20%23%20set%20the%20input%20to%20run%0A%20%20%20%20interpreter.set_tensor(model_inputs.get(%22Image%22).get(%22index%22)%2C%20input_data)%0A%20%20%20%20interpreter.invoke()%0A%0A%20%20%20%20%23%20grab%20our%20desired%20outputs%20from%20the%20interpreter!%0A%20%20%20%20%23%20un-batch%20since%20we%20ran%20an%20image%20with%20batch%20size%20of%201%2C%20and%20convert%20to%20normal%20python%20types%20with%20tolist()%0A%20%20%20%20outputs%20%3D%20%7Bkey%3A%20interpreter.get_tensor(value.get(%22index%22)).tolist()%5B0%5D%20for%20key%2C%20value%20in%20model_outputs.items()%7D%0A%20%20%20%20%23%20postprocessing!%20convert%20any%20byte%20strings%20to%20normal%20strings%20with%20.decode()%0A%20%20%20%20for%20key%2C%20val%20in%20outputs.items()%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(val%2C%20bytes)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20outputs%5Bkey%5D%20%3D%20val.decode()%0A%0A%20%20%20%20return%20outputs%0A%0A%0Adef%20process_image(image%2C%20input_shape)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Given%20a%20PIL%20Image%2C%20center%20square%20crop%20and%20resize%20to%20fit%20the%20expected%20model%20input%2C%20and%20convert%20from%20%5B0%2C255%5D%20to%20%5B0%2C1%5D%20values.%0A%20%20%20%20%22%22%22%0A%20%20%20%20width%2C%20height%20%3D%20image.size%0A%0A%20%20%20%20%23%20ensure%20image%20type%20is%20compatible%20with%20model%20and%20convert%20if%20not%0A%20%20%20%20if%20image.mode%20!%3D%20%22RGB%22%3A%0A%20%20%20%20%20%20%20%20image%20%3D%20image.convert(%22RGB%22)%0A%0A%20%20%20%20%23%20center%20crop%20image%20(you%20can%20substitute%20any%20other%20method%20to%20make%20a%20square%20image%2C%20such%20as%20just%20resizing%20or%20padding%20edges%20with%200)%0A%20%20%20%20if%20width%20!%3D%20height%3A%0A%20%20%20%20%20%20%20%20square_size%20%3D%20min(width%2C%20height)%0A%20%20%20%20%20%20%20%20left%20%3D%20(width%20-%20square_size)%20%2F%202%0A%20%20%20%20%20%20%20%20top%20%3D%20(height%20-%20square_size)%20%2F%202%0A%20%20%20%20%20%20%20%20right%20%3D%20(width%20%2B%20square_size)%20%2F%202%0A%20%20%20%20%20%20%20%20bottom%20%3D%20(height%20%2B%20square_size)%20%2F%202%0A%20%20%20%20%20%20%20%20%23%20Crop%20the%20center%20of%20the%20image%0A%20%20%20%20%20%20%20%20image%20%3D%20image.crop((left%2C%20top%2C%20right%2C%20bottom))%0A%0A%20%20%20%20%23%20now%20the%20image%20is%20square%2C%20resize%20it%20to%20be%20the%20right%20shape%20for%20the%20model%20input%0A%20%20%20%20input_width%2C%20input_height%20%3D%20input_shape%5B1%3A3%5D%0A%20%20%20%20if%20image.width%20!%3D%20input_width%20or%20image.height%20!%3D%20input_height%3A%0A%20%20%20%20%20%20%20%20image%20%3D%20image.resize((input_width%2C%20input_height))%0A%0A%0A%20%20%20%20%23%20make%200-1%20float%20instead%20of%200-255%20int%20(that%20PIL%20Image%20loads%20by%20default)%0A%20%20%20%20image%20%3D%20np.asarray(image)%20%2F%20255.0%0A%0A%20%20%20%20%23%20format%20input%20as%20model%20expects%0A%20%20%20%20return%20image.reshape(input_shape).astype(np.float32)%0A%0A%0Adef%20main()%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Load%20the%20model%20and%20signature%20files%2C%20start%20the%20TF%20Lite%20interpreter%2C%20and%20run%20prediction%20on%20the%20image.%0A%0A%20%20%20%20Output%20prediction%20will%20be%20a%20dictionary%20with%20the%20same%20keys%20as%20the%20outputs%20in%20the%20signature.json%20file.%0A%20%20%20%20%22%22%22%0A%20%20%20%20parser%20%3D%20argparse.ArgumentParser(%0A%20%20%20%20%20%20%20%20formatter_class%3Dargparse.ArgumentDefaultsHelpFormatter)%0A%20%20%20%20parser.add_argument(%0A%20%20%20%20%20%20%20%20'--model'%2C%20help%3D'Model%20path%20of%20.tflite%20file%20and%20.json%20file'%2C%20required%3DTrue)%0A%20%20%20%20args%20%3D%20parser.parse_args()%0A%20%20%20%20%0A%20%20%20%20with%20open(%20args.model%20%2B%20%22%2Fsignature.json%22%2C%20%22r%22)%20as%20f%3A%0A%20%20%20%20%20%20%20%20signature%20%3D%20json.load(f)%0A%0A%20%20%20%20model_file%20%3D%20signature.get(%22filename%22)%0A%0A%20%20%20%20interpreter%20%3D%20tflite.Interpreter(args.model%20%2B%20'%2F'%20%2B%20model_file)%0A%20%20%20%20interpreter.allocate_tensors()%0A%0A%20%20%20%20cap%20%3D%20cv2.VideoCapture(0)%0A%20%20%20%20%23%E6%93%B7%E5%8F%96%E7%95%AB%E9%9D%A2%20%E5%AF%AC%E5%BA%A6%20%E8%A8%AD%E5%AE%9A%E7%82%BA640%0A%20%20%20%20cap.set(cv2.CAP_PROP_FRAME_WIDTH%2C640)%0A%20%20%20%20%23%E6%93%B7%E5%8F%96%E7%95%AB%E9%9D%A2%20%E9%AB%98%E5%BA%A6%20%E8%A8%AD%E5%AE%9A%E7%82%BA480%0A%20%20%20%20cap.set(cv2.CAP_PROP_FRAME_HEIGHT%2C%20480)%0A%0A%20%20%20%20key_detect%20%3D%200%0A%20%20%20%20times%3D1%0A%20%20%20%20while%20(key_detect%3D%3D0)%3A%0A%20%20%20%20%20%20%20%20ret%2Cframe%20%3Dcap.read()%0A%0A%20%20%20%20%20%20%20%20image%20%3D%20Image.fromarray(cv2.cvtColor(frame%2Ccv2.COLOR_BGR2RGB))%0A%0A%20%20%20%20%20%20%20%20if%20(times%3D%3D1)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20prediction%20%3D%20get_prediction(image%2C%20interpreter%2C%20signature)%0A%0A%20%20%20%20%20%20%20%20print('Result%20%3D%20'%2B%20prediction%5B'Prediction'%5D)%0A%20%20%20%20%20%20%20%20print('Confidences%20%3D%20'%20%2B%20str(max(prediction%5B'Confidences'%5D))%20)%0A%20%0A%20%20%20%20%20%20%20%20cv2.putText(frame%2C%20prediction%5B'Prediction'%5D%20%2B%20%22%20%22%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20str(round(max(prediction%5B'Confidences'%5D)%2C3))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(5%2C30)%2C%20cv2.FONT_HERSHEY_SIMPLEX%2C%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(0%2C0%2C255)%2C%201%2C%20cv2.LINE_AA)%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20cv2.imshow('Detecting....'%2Cframe)%0A%0A%20%20%20%20%20%20%20%20times%3Dtimes%2B1%0A%20%20%20%20%20%20%20%20if%20(times%20%3E%3D%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20times%3D1%0A%0A%20%20%20%20%20%20%20%20read_key%20%3D%20cv2.waitKey(1)%0A%20%20%20%20%20%20%20%20if%20((read_key%20%26%200xFF%20%3D%3D%20ord('q'))%20or%20(read_key%20%3D%3D%2027)%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20key_detect%20%3D%201%0A%0A%20%20%20%20cap.release()%0A%20%20%20%20cv2.destroyAllWindows()%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20main()%0A" message="" highlight="" provider="manual"/]

執行效果

    執行效果如下,會開啟一個預覽畫面,並把辨識結果(label)與信心指數顯示出來。 [gallery size="full" ids="41009,41010,41011"]

與 Google Teachable Machine 比較與展望

最後免不了要與Google Teachable Machine (簡稱TM)比較一下,暑期的 AIGO 高中職生扎根營隊我們大量使用 TM來匯出視覺分類模型,也可以放在 Raspberry Pi、Jetson Nano 上執行,或者連動 7697 等這類MCU板來做到各種有趣的 AIoT 應用。   但Lobe標榜離線運作的話,教室的網路壓力就不會這麼高了,很期待各位後續的意見分享喔!  
Lobe.ai Teachable Machine
網路需求 下載安裝包才需要,之後都可離線使用 連線到網站使用,全程都須使用網路
訓練速度 於電腦端進行運算 根據該電腦等級而有明顯差異 200張約3-5分鐘(i7 / 16GB ram) 於電腦端進行運算 幾乎都可在1分鐘內完成
支援之神經網路 視覺分類 物件辨識(尚未) 資料分類(尚未) 視覺分類 聲音分類 姿勢分類
匯出模型格式 TensorFlow TensorFlow Lite TensorFlow TensorFlow Lite TensorFlow js
TFLite 神經網路層數 79 74
 

相關文章 📎

留言 💬 (0)

還沒有留言,來當第一個。