嘿!您似乎在 United States,您想使用我们的 English 网站吗?
Switch to English site
Skip to main content

在Raspberry Pi 4設計GUI介面,匯入機器學習模型實現商品結帳應用

 

作者

许钰莨/曾俊霖

難度

普通

 

00_022a13d0eb82d255f809e3dc92f3ec3c1711e62a.jpg

本文為前篇『使用Google Teachable Machine 來實現Raspberry Pi 4 的影像分類推論』的延伸,所以本文主要是分享如何更換商品之模型檔,讀者可以沿用前篇所訓練好的模型相關檔案作為商品,直接匯入本文所設計好的GUI介面,同時攝像頭會將照到的人臉和商品拍照,作為資料庫,可進一步優化下次在網頁訓練時的模型。

 

本專題將商品結帳系統設計成GUI介面,讓使用者方便操作,商品結帳系統分辨人臉及商品兩種不同的類別,功能使用如下:

  • 「分辨商品視窗」顯示到商品後,當使用者按下「增加購買商品」鍵,「結帳台」上會出現該商品名稱,反之,若「結帳台」沒有商品,或「分辨商品視窗」顯示非商品(即不是在Teachable Machine所訓練的物件),便會播放"結帳台上沒有商品"的聲音。

 

  • 當使用者反悔不想購買商品時,可按下「刪除購買商品」鍵,「結帳台」上會出現『-----------』的刪除符號,當商品全數刪除後,若又按下「商品結帳」鍵時,會播放"沒有可結帳商品,請先選購商品後再結帳"的聲音。

 

  • 「結帳台」上有商品,使用者按下「商品結帳」鍵時,會播放"結帳完成謝謝光臨"的聲音。

0123_18c8a030123a9956cca492289b8ce8a9c5638eab.jpg

 

本文將分成幾個部分來介紹:

一、介紹 Tkinter 模組。

二、將商品結帳系統之相關檔案上傳至RPi4。

三、匯入訓練商品之模型檔。

四、安裝商品結帳系統播放音訊檔和圖像PIL之套件。

五、接上硬體設備之須知

六、開啟商品結帳系統程式。

七、程式說明。

 

一、介紹Tkinter模組

使用Tkinter ,是因Python 裡已經內建的標準模組,且具有以下兩項優點:

  • 可以跨平台,如: Linux/MAC OS/Windows 可以執行Python的作業系統,而Windows則是安裝Python時會一併安裝Tkinter。即可先在Windows 作業系統中設計介面再到其他作業系統執行,如本文使用的RPi4 。

 

  • 程式碼簡潔易懂,對於剛接觸人機互動介面的初學者很容易學習,且也很快可以設計出人機介面。

 

因為RPi4的Python中已內建Tkinter ,我們可進一步查詢Tkinter版本,和呼叫內建的測試視窗。

 

(1)Tkinter版本查詢,查詢指令步驟如下:

先開啟RPi4 終端機,並輸入

Python 3 

0220_baa69733b8ce3684ead91b4f16516f3b8870f090.jpg

 

輸入 import tkinter ,再輸入

tkinter.Tcl().eval('info patchlevel')

0318_8973ce64a6e322ae7c3bce8451fdf1963159002c.jpg

即可知道本文的Tkinter版本為8.6.9版。

 

(2)呼叫出內建的測試視窗,執行測試函數_test()即可顯示測試視窗

輸入

import tkinter

 

再輸入

tkinter._test()

 

0419_e01c23928432660d83f781ca802a91f9e60790e0.jpg

 

若按下「Click me!」,會顯示中括號,按下「QUIT」則退出

0513_0665ed7d4bc48726c36db19f4dba8ea1a77255c8.jpg

 

二、將商品結帳系統之相關檔案上傳至RPi4

 本文準備了Store資料夾,相關的檔案讀者可以從本文提供的連結下載後上傳至RPi4

0612_95c48e40fa617748c3094fc4963407364eb20989.jpg

 

三、匯入訓練商品之模型檔

 如果讀者想重新訓練商品的模型,請參考前篇文章使用Google Teachable Machine  來實現Raspberry Pi 4 的影像分類推論所訓練的模型檔案,將商品的模型檔及標籤檔改名成labels_goods.txt和model_goods.tflite 。

0710_e63c9eeda8f062206f8b78db65bfb7e68a9e7bfe.jpg

 

將商品的模型檔及標籤檔透過遠端連線軟體傳送至RPi4中本文已經創建好的Store資料夾中

0810_7c7232c5ea8d84b936a625d8bfceb236f050f79a.jpg

 

四、安裝商品結帳系統播放音訊檔和圖像之套件

本文的人機互動介面除可以分辨人臉及商品功能外,也可以播放音訊檔來得知結果,播放音訊檔的套件是使用pygame,故須先安裝此套件:

$pip3 install pygame

0910_994c81b10d6e71e35e0b09d3c81e555a427ef372.jpg

 

本文pygame套件的版本,可利用以下指令查詢:

$pip3 list

1035_1aa84b14d541142309e796ddaa836392c3012bd0.jpg

 

可以得知pygame套件的版本為1.9.6版

1145_b50b225645401aeb9a0974f46bd4d6aa73e67eea.jpg

 

還需安裝圖像PIL套件

$  sudo apt-get install python3-pil.imagetk

11-2_fbc635bc76cd8e03fcaec61b7c0a6080d30ec0a6.jpg

 

如果讀者想更換音訊檔,本文是使用文字轉語音的人工語音合成網站,輸入文字後可以依照喜好如:男生、女生、語速、音高,進行調整後下載。當然,讀者有找到不錯的人工語音合成網站也可嘗試使用。

網址連結: https://www.toolfk.com/tool-online-text2video?type=base

 

要注意的一點,本文所下載的音訊語速和音高皆是調到最低值,主要的原因是pygame套件會加速原本音訊檔的語速,這裡請讀者需耐心測試。

1237_05bab02b4ee0f055deef9cf983c696604d987f17.jpg

 

本文整理了商品結帳系統所需的音訊檔名稱,及音訊內容

 

音訊檔名稱

(不可隨意更改檔名)

音訊內容

thanks.mp3

結帳完成謝謝光臨

no_goods.mp3

沒有可結帳商品

請    請先選購商品後再結帳

no_goods_class.mp3

結帳台上沒有商品

 

以上為商品結帳系統所有音訊檔案的說明,音訊內容的語句可以自行設計,但是音訊檔檔名請照原本的名稱,因為tk_cv_goods.py檔案需要和以上音訊檔檔名一致,故不可隨意更改,否則執行時會顯示找不到檔案的錯誤。

 

五、接上硬體設備之須知

        開啟程式前,請先確認攝像頭是否插入RPi4的USB3.0孔(藍色USB插孔)

1335_0dc8d777232fc1b761b735e6a85138a941547612.jpg

 

並將喇叭插入3.5mm 音源孔來播出聲音

1431_423e323e49c368fbebf1b6be8bef656ba33b6324.jpg

 

開啟商品結帳系統。

首先,移動到Store資料夾中

$ cd Store/ 

1530_0c22111de6949301bfe1d833d5a088d5b3781271.jpg

 

匯入需要執行影像的檔案

$ ln -s /usr/local/python/cv2/python-3.7/cv2.cpython-37m-arm-linux-gnueabihf.so cv2.so

1629_8715dff722f4bd17cfbf2f855e154d73c38eb2ef.jpg

 

六、執行商品結帳系統程式

$python3 tk_cv_goods.py

1730_e7e3c44ab87185cea8098868c3a1b04a84e2e31f.jpg

 

執行畫面如下:

1828_022a13d0eb82d255f809e3dc92f3ec3c1711e62a.jpg

執行程式影片

 

七、程式說明

匯入相關函式庫,本文所使用的框架為Tensorflow Lite ,優點在於若部屬在像RPi4的邊緣裝置,可以使模型優化,執行的效率非常快速,而且也可部屬於Android手機。

import tkinter
import cv2
import PIL.Image, PIL.ImageTk 

import pygame

from tflite_runtime.interpreter import Interpreter

 

開啟視訊鏡頭

self.vid_0 = MyVideoCapture(self.video_source_0)

 

Tkinter的GUI視窗的像素大小設為500*400

 self.window.geometry('500x400')
 self.window.resizable(False, False)

1927_85948d0ccb38388f35b0b0e5564c2f29d3e199ce.jpg

 

設置畫布尺寸,為240*180。

self.canvas_goods = tkinter.Canvas(window, width = 240, height = 180)

 

設置畫布尺寸視窗中的於第0行第1列,在網格中使用"sticky"參數來指定對齊方式,可指定n、s、e、w,分別為上、下、左、右對齊,這剛好可用指北針的方位圖來表示位置。

self.canvas_goods.grid(row=0, column=1, sticky="w")

20_22_b55c74ae4c35aa9eca2168b7a06738af2a8c6928.jpg

 

設置按鈕尺寸,分別為"增加購買商品"於第1行第1列、"刪除購買商品"於第2行第1列、"商品結帳"於第3行第1列

 tkinter.Button(window, text="增加購買商品", command=self.add_goods).grid(row=1, column=1)
 tkinter.Button(window, text="刪除購買商品", command=self.delete_goods).grid(row=2, column=1)
 tkinter.Button(window, text="商品結帳", command=self.check_out).grid(row=3, column=1)

2125_7344c4c388be0a753e5bb4bbfff27cc47602b0e9.jpg

 

攝影機還有另一項功能,就是做完影像推論後,會將商品的圖像擷取到goods_collect資料夾中作為資料庫,往後再重新訓練時能提高商品的精確度。

    def add_goods(self):
        ret_0, frame_0 = self.vid_0.get_frame()
        if (ret_0):
            cv2.imwrite("goods_collect/" + "goods-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg", cv2.cvtColor(frame_0, cv2.COLOR_RGB2BGR))
            self.is_goods = Goods_class(frame_0,self.item)    
        self.item=self.item+1

 

當按下"刪除購買商品"時,會將商品刪除,標籤元件顯示於第self.item+1行,第二列。

def delete_goods(self):
        self.item = self.item -1
        if (self.item <= 0):
            self.item=0
        labelExample = tkinter.Label(text="____________________")
        labelExample.grid(row=self.item+1, column=2)

2224_e30da0dfdfe40d12c37d952e8d6bede7adddd765.jpg

 

接下來說明如何將影像顯示在GUI視窗上,攝像頭原本的影像大小為320*240

  self.vid = cv2.VideoCapture(video_source)
  self.vid.set(cv2.CAP_PROP_FRAME_WIDTH,320)
  self.vid.set(cv2.CAP_PROP_FRAME_HEIGHT,240)

 

但前面已設置畫布尺寸為240*180,必須符合畫布尺寸,所以將影像尺寸縮放成240*180

frame_0_small=cv2.resize(frame_0,(240,180))

 

最後在Goods_class的函式裡,開始進行圖像分類,最後推論出來的結果,可以顯示商品標籤及信心指數(即預測值)

#做圖像分類
class Goods_class:
    def load_labels(self,path):
        with open(path, 'r') as f:
            return {i: line.strip() for i, line in enumerate(f.readlines())}

    def set_input_tensor(self, interpreter, image):
        tensor_index = interpreter.get_input_details()[0]['index']
        input_tensor = interpreter.tensor(tensor_index)()[0]
        input_tensor[:, :] = image
    
    
    def classify_image(self, interpreter, image, top_k=1):
        self.set_input_tensor(interpreter, image)
        interpreter.invoke()
        output_details = interpreter.get_output_details()[0]
        output = np.squeeze(interpreter.get_tensor(output_details['index']))
        
        if output_details['dtype'] == np.uint8:
            scale, zero_point = output_details['quantization']
            output = scale * (output - zero_point)

        ordered = np.argpartition(-output, top_k)
        return [(i, output[i]) for i in ordered[:top_k]]


    def __init__(self,image_src,item):
        labels = self.load_labels('/home/pi/Store/labels_goods.txt')
        interpreter = Interpreter('/home/pi/Store/model_goods.tflite')
        interpreter.allocate_tensors() 
        _, height, width, _ = interpreter.get_input_details()[0]['shape']
        
        #驗證畫面尺寸為224X224,改變尺寸會驗證錯誤
        image=cv2.resize(image_src,(224,224))

        results = self.classify_image(interpreter, image)
        label_id, prob = results[0]
        
        #顯示商品標籤id及信心指數
        print(label_id, prob)

 

筆者在最後加上"print(label_id, prob)",可以使讀者了解圖片是如何被分類出來,其中紅框表示商品標籤id,黃框為預測值

2321_a877099192c1cf9e8065349195324016054986e0.jpg

CAVEDU Education is devoted into robotics education and maker movement since 2008, and is intensively active in teaching fundamental knowledge and skills. We had published many books for readers in all ages, topics including Deep Learning, edge computing, App Inventor, IoT and robotics. Please check CAVEDU's website for more information: http://www.cavedu.com, http://www.appinventor.tw
DesignSpark Electrical Logolinkedin