Quantcast
Channel: CAVEDU教育團隊技術部落格
Viewing all 678 articles
Browse latest View live

Pytorch深度學習框架X NVIDIA JetsonNano應用-torch.nn實作

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 嘉鈞
難度

★★★☆☆(理論困難,實作普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

上次的教學教到如何刻一個線性回歸,這次的教學將使用 torch.nn 由官方提供的各種模組,來加速刻程式的速度,也因為 torch 將各種模型、演算法都包好,使用者在開發上也會更輕鬆,我們將完成。

  • 透過nn建置 Linear Regression
  • 激勵函數(Activation Function)
  • 建置一個神經網路

透過torch.nn建置 Linear Regression

我們將使用上一次的數據圖來改建Linear Regression,第一步我們將學習使用nn.Linear,這個模組其實就是之前的線性方程 ,其中有兩個引數各別代表是輸入維度、輸出維度,使用它將可以避免掉手動建置權重跟偏差,torch會自動幫你算好對應的矩陣形狀,可以注意到W的形狀是 [4, 3],主要是因為其實 Linear的函數會先針對 W做轉置才會與x進行點積,為了符合y的大小。

嘗試改造過後你會發現其實沒減少太多程式碼 (如下圖):

因為 weights跟bias 都要「個別」更新,所以就佔掉很多行數,torch的nn.Module可以透過 .parameters() 跟 .zero_grad() 來一起搞定參數更新,首先需要先建立一個類別並且繼承nn.Module,標準格式是 init 裡面通常會放神經網路的架構,而forward就是向前傳遞該怎麼傳遞(哪一層接到哪一層的概念),宣告完模型的類別記得要實例化該模型

原本的程式 更改後的程式

你會發現在更新參數的時候更簡潔了,但是其實還可以更簡潔一點,這時候就要用到optim優化器去協助我們模型訓練,它將可以取代掉我們原先「手動更新」的部分,首先需要先導入函式,挑選你要的優化器並且告知那些參數是要訓練的,還要告知學習率是多少。

宣告完之後原本落落長的程式碼只要兩行就搞定了:

原本的程式 更改後的程式

接下來是訓練流程的比較,你看,是不是更清晰了?

原本的程式 更改後的程式

完整程式碼如下:

那在程式端我們很直觀的理解到優化器可以減少程式碼的量,但是為什麼要用優化器呢?這邊要稍微帶一點基礎觀念,已經很了解的讀者們可以先跳過~

我們之前在對Loss做偏導數求梯度再乘上學習率去更新參數的方法叫做梯度下降 ( gradient descent ),使用各種參數的梯度值去最小化或最大化損失函數的數值,而optimizer就是梯度下降的優化方法;主要有兩個面向,第一個是計算梯度下降的方向 (偏導數),第二個是找到適合的學習步長 (學習率,Learning Rate,以下簡稱 lr );你會發現我們的程式都是固定的學習率,而這又會有什麼問題呢?下圖是所有有可能出現的參數對應出的loss示意圖,而我們的目的是要走到山谷谷底,計算梯度等於是告訴你谷底的方向,學習率則是你每次跨出的步長,你可以注意到如果步長過大或不變的時候,都有可能走不到最谷底,而步長太小又會走太慢,甚至迭代次數跑完了還沒到達目的地。

圖片來源:李弘毅-ML Lecture 3-1: Gradient Descent

所以控制學習率是梯度下降很重要的環節,目前常出現的演算法有SGD (Stochastic gradient descent)、Adagrad、RMSprop、Adam,詳細的差別就不多說了,大家可以自己去查資料,以下簡單做個測試,epochs為100次,lr設0.1,我們來觀察看看3種不同的優化器的結果:

雖然Adam是目前看似最強大的演算法,但是在這個僅50次迭代的簡單問題中 SGD收斂是最較快且正確的;所以挑選優化器也是一門學問,有時候不是用上最強的演算法就能符合自己的需求,不過如果你覺得太複雜的話還是可以直接套用 Adam啦,畢竟他簡單或複雜的問題都可以獲得不錯的結果!

激勵函數(Activation Function)

通常在處理較複雜 ( 非線性 ) 問題的時候就需要用到激勵函數,文章前面提到的Linear Regresion就是去解決線性的問題,可以用一條直線預測出數據的分布或趨向,而遇到非線性的則稱為 Logstic (non-linear) Regression,下圖為簡單的差別,除了第一個可以用線性回歸解決其餘都不太可行。

神經網路模型遇到的問題通常都是比較複雜的,所以大部分必須採用激勵函數來協助模型解決複雜問題,下圖是常見的激勵函數,x軸是輸入y軸是輸出;

圖片來源:Tim Wang-【ML09】Activation Function 是什麼?

Pytorch使用激勵函數一樣在torch.nn中呼叫即可,程式碼如下圖可以再對照上面的圖做確認,像是原本的數值是[ -1. , 1. , 0. ],經過 Sigmoid函數就變成了 [ 0.26 , 0.73 , 0.5 ] ,因為Sigmoid會將數值 ( x ) 縮限在 [ -1 , 1 ] 之間,最初我在理解激勵函數就是直觀的認為目標是要將輸出縮限到對應的需求,之後才去理解線性與非線性、運算量的問題

建置一個神經網路

我們先創造一個稍微複雜一點的數據,然後嘗試建置一個沒有激勵函數的神經網路模型並迭代6000次看看效果,接著建置含有激勵函數的神經網路模型再比較一次。

建置多層的神經網路其實非常簡單,我們一樣使用nn.Module來建置,主要的差別在於__init__的部分需多宣告幾層,而forward的部分需要將其連接在一起;這邊我將激勵函數寫成方便改變的方式,並且將是否使用激勵函數、使用哪個激勵函數寫在一起,而我的範例提供ReLU、Tanh兩種訓練結果:

訓練的程式碼如下:

第一次訓練的時候,我不使用激勵函數來訓練神經網路,也就是說現在的神經網路只能解決線性的問題,可以從下圖觀察到該模型沒辦法收斂到正確答案:

 

接著我使用ReLU、Tanh來嘗試,你會發現在曲線的地方有成功的收斂了,你們可以利用前面提供的激勵函數圖表來嘗試猜猜看哪一個ReLU?

 

答案是左邊是ReLU,你可以注意到沒有y值是負的,原因就是ReLU會將輸出其收斂在 [ 0 , n ] 之間 ( n表示任意數 ),所以在我們這個案例中使用 ReLU可能就不是個好選擇,反觀Tanh是將數值收斂在 [ -1, 1 ] 之間,所以在我們的案例中使用它就不會遇到任何問題,由此可知選擇激勵函數也是一門大學問了,如果你的輸出要收斂到哪裡就該選擇哪個又或者是多個選項輸出的話就該選擇maxout、輸出對與錯就使用sigmoid等等。

結語

看完了這篇你已經學會線性回歸 ( linear regression) 與邏輯斯回歸 (logistic regression),利用PyTorch建置神經網路的基礎也已經都摸透了,下一篇將稍微進階一些來玩捲積神經網路並做一個小專案。

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 (本篇文章完整範例程式請至原文下載)


Pytorch深度學習框架X NVIDIA JetsonNano應用-貓狗分類器

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 嘉鈞
難度

★★★☆☆(理論困難,實作普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

本篇文章將使用Jetson Nano做出一個非常簡單的貓狗分類器,其中用到PyTorch的ImageFolder做數據集並且用DataLoader將數據集載入的技術,並且學會用自己建置的CNN來訓練,最後取出測試圖片來做預測 ( 如上圖 )。

Jetson Nano遠端、確認環境

今天我們要在Jetson nano這塊開發版上運作,而我們公司已經有許多Jetson nano的相關介紹、應用,所以就不多作介紹了,連結放在這裡給大家參考https://blog.cavedu.com/tag/jetson-nano/,遠端連線的部份我們使用Wireless Network Watcher查看Nano的IP位址,在選項 > 進階選項 > 使用下列網路卡 > 選擇乙太網路,點擊確定進行掃描就可以找到Jetson nano的虛擬IP

接著可使用MobaXterm或Jupyter Notebook進行內網的遠端,兩者都有檔案系統可以直接進行檔案傳輸,相當好用!在MobaXterm的部份,直接上網安裝點擊Session輸入IP位址、使用者帳號、密碼即可使用;而Jupyter Notebook的部份,Jetson Nano已經有幫我們設定好Notebook的遠端功能,所以只要在PC端開啟瀏覽器輸入http://{你的JetsonNano的IP位址}:8888/,輸入密碼即可登入;本次範例將使用Jupyter Notebook來運作。

以下為使用MobaXterm操作的介面:

以下為使用Jupyter Notebook操作的介面,建議先新增一個資料夾當作工作區,新增後可以開始一個Terminal跟Jupyter Notebook;Termial用來安裝套件、Jupyter用來撰寫與執行程式:

Kaggle

介紹

今天使用的數據集會從Kaggle上面下載下來,這是一個數據建模和數據分析競賽平台,每年都會辦機器學習、深度學習的比賽,也有勵志成為機器學習工程師的人會反覆地在上面刷各種數據集取得好成績。

數據下載的連結https://www.kaggle.com/c/dogs-vs-cats/overview/description

關於Kaggle數據集下載的方法有兩種:第一種是直接下載,第二種則是透過Kaggle提供的API進行下載,今天也教大家如何使用API下載。

使用API下載Kaggle數據集

第一步、安裝Kaggle API

可在Jupyter Notebook中執行:

!pip3 install -U -q kaggle

也可以在Terminal中執行

pip3 install -U -q kaggle

兩者差別只在於Jupyter中要使用命令需要加上驚嘆號來與程式碼作區別!

第二步、取得認證JSON檔

右上方大頭貼 > My Account > 下拉至API欄位並點擊 Create New API Token就可以取得認證的JSON檔

注意存放位置!

這邊下載後會提醒你要放到使用裝置的 ~/.kaggle 位置,等等新增到Jetson Nano中要特別注意!

第三步、將認證檔新增到Jetson Nano

輸入以下程式碼,將 {usr}、{API key} 修改成自己的名稱、金鑰即可,執行之後Jetson Nano中就有kaggle中你的認證檔了,接下來直接進行下載。

第四步、下載dataset到特定資料夾

這段的程式碼是從Kaggle數據集的網頁中Data的資訊中可以找到,其中 -p的引數是下載到指定目錄:

 

第五步、確認資料並解壓縮

資料處理

提供的資料是train跟test1兩個資料夾,在train這個資料夾中總共有25000筆資料,我們將前九張照片顯示出來看看

可以注意到資料的名稱是 {label}.{id}.jpg,我們的目標是要將貓跟狗兩種不同的資料分到不同的資料夾當中,所以們我會先新建一個屬於貓跟狗的資料夾,在透過檔名來做分類。

先導入函式庫以及宣告基本的目錄位址:

確認目錄是否存在,如果沒有就創建一個:

接下來就是主要整理的程式碼了:

整理完可以發現貓跟狗都已經到各自的資料夾了,各有12500張照片:

透過torch製作數據集

在PyTorch當中客製化數據集是必要的,因為有時候你的數據是「一張照片配一個標籤」有時候是「一張照片配多個標籤」甚至還有個多元的標籤形式,所以這邊會提到怎麼去製作自己的數據集。

Dataset、DataLoader之間的關係

PyTorch將所有數據打包在torch.utils.data.Dataset 當中,這邊可以選擇你要怎麼提取你的數據集 ( 單筆 ),像是前面所說的一張照片配一個標籤或者搭配更複雜標籤,也可以在建置數據集的時候使用數據增強,透過變形、裁切來增加數據量、神經網路模型的強健度;宣告完之後再將 Dataset 打包進 torch.utils.data.DataLoader 當中去推送,這邊你可以選擇一次要丟幾張照片出來進行平行運算。目前我比較常用的有兩種定義數據集的方式,如果是已經用資料夾分類好的就可以使用ImageFolder 來製作數據集,如果要取得更多訊息就會客製化一個數據集。

使用ImageFolder建置數據集

這是torch提供最一般的數據集整理方式,我們先前已經將貓跟狗都丟進各自的資料夾了,所以等等直接使用這種簡單粗暴的建制方式。

程式碼如下,在建置數據集的同時我們會先定義transform,這個目的通常用於數據處理、增強,可以做裁切、變形、轉檔等功能,也是很重要的環節:

我們將數據集拿出來看可以發現貓跟狗已經幫我們分類成0跟1了:

再來會注意到一個問題,我將前五張圖片拿出來查看大小,發現每一張圖片的大小都不一樣,這時候必須去處理維度的問題,不然捲積神經網路沒辦法跑

通常遇到這種問題最簡單粗暴的方式是直接在transform的地方加上resize,當然會有更好的解法但這邊暫時先不講述,我們將所有的圖片都先縮放到 224 * 224並且轉換成Tensor後做一下正規化。

使用DataLoader批次丟出數據

這邊我為了防止跑太久將迴圈限制在10次,可以注意到每一次都出去都是16張 ( 維度為 [16, 3, 224, 224] ),並且從label那邊可以看出來都是打亂的。

建立一個捲積神經網路

捲積神經網路的概念就不贅述,主要流程就是 捲積( Conv ) > 池化 ( MaxPool ) > 攤平 ( view ) > 全連接 ( linear ) > 輸出 (維度要注意),這邊我在最後一層加了softmax讓兩者加起來為1比較好看,除此之外要注意的地方是必須要自己去計算每經過一層捲積池化,圖片會變成多大,因為最後攤平的時候必須要先宣告資料輸入的維度是多少,計算公式為:

所以的會發現全連階層的第一層輸入是 128 * 28 * 28,其中128就是最後一層conv的kernel數量,28*28則是自己算出來的。

建立一個捲積神經網路

捲積神經網路的概念就不贅述,主要流程就是 捲積( Conv ) > 池化 ( MaxPool ) > 攤平 ( view ) > 全連接 ( linear ) > 輸出 (維度要注意),這邊我在最後一層加了softmax讓兩者加起來為1比較好看,除此之外要注意的地方是必須要自己去計算每經過一層捲積池化,圖片會變成多大,因為最後攤平的時候必須要先宣告資料輸入的維度是多少,計算公式為:

所以的會發現全連階層的第一層輸入是 128 * 28 * 28,其中128就是最後一層conv的kernel數量,28*28則是自己算出來的。

可以將神經網路打印出來或者導入一筆Data看看

最後輸出是二維的因為有貓跟狗兩個類別,如果位置0的數值比較大代表神經網路判斷為貓,反之位置1的數值比較大則是判斷為狗:

開始訓練

先將基本的設定好,這邊要注意的是我將模型丟到GPU中去訓練,因為是分類問題所以使用CrossEntropy當作損失函數,最後使用Adam來當優化器。

訓練之前記得先將張量也丟到GPU中,其餘的訓練流程皆與之前教學雷同,我特別將每次迭代的Loss存下用來視覺化用,這邊我看到網路上很多人會使用model.train()、model.eval(),這是torch自動會將 BatchNorm、Dropout在驗證的時候關掉,由於我們現在自己建的神經網路沒有這兩層所以其實是可以不用寫的,不過礙於之後會陸續增加技術,BatchNorm是一定會寫入的,所以現在大家可以先習慣寫這兩行,訓練開始前將模型設成訓練模式,結束的時候設成驗證模式。

訓練完的結果,在一般電腦使用RTX 1080訓練耗費了約545秒,而JetsonNano上使用gpu訓練每一次epoch約1300~1350秒,5個回合訓練完大概是6500秒左右也就是幾乎快兩個小時了,看似效能差距很大,但是其實考量到它的價位、體積以及運算能力,其實是相當的不錯了!

我有先將loss儲存起來,所以這邊可以直接調用並視覺化:

測試數據

驗證集的資料型態比較不同,我為了使用ImageFolder在test1資料夾中又新增了一個test資料夾並且將圖片都丟入其中,最後用ImageFolder打包。

當初有做什麼樣的圖片變化,測試的時候一樣要做,否則預測就不準確了:

接著就可以進行預測了,這邊我們只取第一個batch的圖片也就是16張圖片,將其丟進神經網路模型後會獲得一組數據 [ 16, 1 ],代表16張圖片的預測結果,我們可以透過程式碼取得較大數值的維度,用來判斷貓( 0 ) 還是狗 ( 1 )。

預測的程式碼如下:

最後結果可以看到,這次預測16張圖片只有錯2張圖片~

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 (本篇文章完整範例程式請至原文下載)

 

Pytorch深度學習框架X NVIDIA JetsonNano應用-cDCGAN生成手寫數字

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 嘉鈞
難度

★★★★☆(中偏難)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

DCGAN 到 cDCGAN

先來稍微複習一下DCGAN (深度捲積生成對抗網路),字面上就是利用捲積網路的架構來做生成對抗,主要由生成器與鑑別器所構成,如下圖所示:

生成器會將一組雜訊或稱做潛在空間的張量轉換成一張照片,這張照片再經由鑑別器去判斷圖片是否夠真實,越接近0越假;越接近1越真。

 

由於我們在訓練的時候其實是沒有載入標籤的!所以他生成的時候都是隨機生成,為了能限制特定的輸出我們必須載入標籤,概念圖就會變成下面這張:

透過標籤的導入,讓生成器知道要生成的對象是哪一個數字,並且鑑別器訓練的目標變成「圖像是否真實」加上「是否符合該類別」,cDCGAN跟DCGAN相比,訓練的結果通常會比較好,因為DCGAN神經網路是盲目的去生成,而cDCGAN則是會將生成的範圍縮小,整體而言會收斂更快且更好。

 

將標籤合併於資料中

首先我們要先了解如何加入標籤,對於DCGAN來說有兩種加入標籤的方法,第一個是一開始就將圖片或雜訊跟標籤合併;另一個方法則是在深層做合併,讀者們在實作的時候可以自行調整看看差異,那較常見的做法是深層合併,而我寫的也是!

潛層合併,先合併再輸入網路 深層合併:各別輸入後再合併

 

其中詳細的差別我還沒涉略到,不過選定了深層合併接著就可以先來實作生成器跟鑑別器了。首先先來建構生成器,可以參考上一篇DCGAN的程式碼,這邊幫大家整理了一張概念圖:

輸入的z是維度為 ( 100, 1, 1) 的雜訊,為了將標籤跟雜訊能合併,必須轉換到相同大小也就是 (1, 1),可以看到這邊 y 的維度是 ( 10, 1, 1 ) 原因在於我們將原先阿拉伯數字的標籤轉成 onehot 編碼格式,如下圖所示。

OneHot編碼主要在於讓標籤離散,如果將標籤都用阿拉伯數字表示,對於神經網路而言他們屬於連續性的數值或許會將前後順序、距離給考慮進去,但是用onehot之後將可以將各類標籤單獨隔開並且對於彼此的距離也會相同。

 

建立Generator

接下來是程式的部分,如何在神經網路中做分流又合併,其實對於PyTorch而言非常的簡單只要在forward的地方做torch.cat就可以了。首先一樣要先定義網路層,我們定義了三個 Sequential,其中input_x是給圖像用的所以第一層deconv的輸入維度是z_dim;而input_y則是標籤用所以deconv的輸入是label_dim,可以對照上面的圖片看看:

        def __init__(self, z_dim, label_dim):
        super(Generator, self).__init__()
        self.input_x = nn.Sequential(
            # input is Z, going into a convolution
            nn.ConvTranspose2d(z_dim, 256, 4, 1, 0, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # image size =  (1-1)*1 - 2*0 + 4 = 4
        )
        self.input_y = nn.Sequential(
            # input is Z, going into a convolution
            nn.ConvTranspose2d( label_dim, 256, 4, 1, 0, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # image size =  (1-1)*1 - 2*0 + 4 = 4
        )
        self.concat = nn.Sequential(
            
            # 因為 x 跟 y 水平合併所以要再乘以 2
            nn.ConvTranspose2d(256*2, 128, 4, 2, 1, bias=False),    
            nn.BatchNorm2d(128),
            nn.ReLU(True),
             # image size =  (4-1)*2 - 2*1 + 4 = 8

            nn.ConvTranspose2d( 128, 64, 4, 2, 1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            # image size =  (8-1)*2 - 2*1 + 4 = 16
            
            nn.ConvTranspose2d( 64, 1, 4, 2, 3, bias=False),
            nn.Tanh()
            # image size =  (16-1)*2 - 2*3 + 4 = 28
        )

接下來看 forward的部分,可以看到我們在向前傳遞的時候要丟入兩個數值,雜訊跟標籤,將x跟y丟進各自的Sequential中,接著我們使用torch.cat將x, y從橫向 ( dim=1 ) 合併後再進到concat中。

    def forward(self, x, y):
        x = self.input_x(x)
        y = self.input_y(y)
        out = torch.cat([x, y] , dim=1)
        out = self.concat(out)
        return out

接下來可以試著將網路架構顯示出來,我們直接使用print也使用torchsummary來顯示,你可以發現其實你沒辦法看出網路分支再合併的狀況

 

def print_div(text):
    
    div='\n'
    for i in range(60): div += "="
    div+='\n'
    print("{} {:^60} {}".format(div, text, div))
    
""" Define Generator """
G = Generator(100, 10)
 
""" Use Print"""
print_div('Print Model Directly')
print(G)
 
""" Use Torchsummary """
print_div('Print Model With Torchsummary')
test_x = (100, 1, 1)
test_y = (10, 1, 1)
summary(G, [test_x, test_y], batch_size=64)

所以我決定使用更圖像化一點的方式來視覺化我們的網路架構,現在有不下10種的圖形化方式,我舉兩個例子:Tensorboard、hiddenlayer。

 

視覺化模型

Tensorboard 是Google 出的強大視覺化工具,一般的文字、數值、影像、聲音都可以動態的紀錄在上面,一開始只支援Tensorflow 但是 PyTorch 1.2 之後都包含在其中 ( 但是要使用的話還是要先安裝tensorboard ) ,你可以直接從 torch.utils.tensorboard 中呼叫 Tensorboard,首先需要先實體化 SummaryWritter,接著直接使用add_graph即可將圖片存到伺服器上

""" Initial Parameters"""
batch_size = 1
test_x = torch.rand(batch_size, 100, 1, 1)
test_y = torch.rand(batch_size, 10, 1, 1)
 
print_div('Print Model With Tensorboard')
print('open terminal and input "tensorboard --logdir=runs"')
print('open browser and key http://localhost:6006')
writer = SummaryWriter()
writer.add_graph(G, (test_x, test_y))
writer.close()

接下來要開啟伺服器,在終端機中移動到與程式碼同一層級的位置並且輸入:

> tensorboard –logdir=./runs

一開始就可以看到 input > Generator 的箭頭有寫 2 tensor,而這些方塊都可以打開:

開啟後你可以看到更細部的資訊,也很清楚就可以看到支線合併的狀況。

每一次捲積後的形狀大小也都有顯示出來:

接下來簡單介紹一下hiddenlayer ,它不能用來取代高級API像是tensorboard之類的,它僅僅就是用來顯示神經網路模型,但是非常的輕巧所以我個人蠻愛使用它的,首先要先透過pip安裝hiddenlayer、graphviz:

> pip install hiddenlayer
> Pip install graphviz

如果是用Jetson Nano的話,建議用 apt去裝 graphviz

$ sudo apt-get install graphviz

接著用 build_graph就能產生圖像也能直接儲存:

""" Initial Parameters"""
batch_size = 1
test_x = torch.rand(batch_size, 100, 1, 1)
test_y = torch.rand(batch_size, 10, 1, 1)
 
print_div('Print Model With HiddenLayer')
g_graph = hl.build_graph(G, (test_x, test_y))
g_graph.save('./images/G_grpah', format="jpg")
g_graph

因為太長了所以我截成兩半方便觀察,這邊就可以注意到前面的ConvTranspose、BatchNorm、ReLU是分開的,之後才合併這邊還特別給了一個Concat的方塊,我喜歡使用它的原因是簡單明瞭,捲積後的維度也都有寫下來,並且直接執行就可以看到結果,不用像Tensorboard還要再開啟服務。

建立Discriminator

跟建立Generator的概念相似,我們要個別處理輸入的圖片跟標籤,所以依樣宣告兩個 Sequential 個別處理接著再將輸出 concate 在一起,主要要注意的是 y 的輸入為度為 (10, 28, 28):

import torch
import torch.nn as nn
from torchsummary import summary
 
class Discriminator(nn.Module):
    
    def __init__(self, c_dim=1, label_dim=10):
        
        super(Discriminator, self).__init__()
 
        self.input_x = nn.Sequential(
            
            # Input size = 1 ,28 , 28
            nn.Conv2d(c_dim, 64, (4,4), 2, 1),
            nn.LeakyReLU(),
        )
        self.input_y = nn.Sequential(
            
            # Input size = 10 ,28 , 28
            nn.Conv2d(label_dim, 64, (4,4), 2, 1),
            nn.LeakyReLU(),
        )
        
        self.concate = nn.Sequential(
            
            # Input size = 64+64 ,14 , 14
            nn.Conv2d(64*2 , 64, (4,4), 2, 1),
            nn.LeakyReLU(),
            
            # Input size = (14-4+2)/2 +1 = 7
            nn.Conv2d(64, 128, 3, 2, 1),
            nn.LeakyReLU(),
            
            # Input size = (7-3+2)/2 +1 = 4
            nn.Conv2d(128, 1, 4, 2, 0),
            nn.Sigmoid(),
            
            # output size = (4-4)/2 +1 = 1
        )
        
    def forward(self, x, y):
        
        x = self.input_x(x)
        y = self.input_y(y)
        out = torch.cat([x, y] , dim=1)
        out = self.concate(out)
        return out
    
D = Discriminator(1, 10)
test_x = torch.rand(64, 1,28,28)
test_y = torch.rand(64, 10,28,28)
 
writer = SummaryWriter()
writer.add_graph(D, (test_x, test_y))
writer.close()
 
hl.build_graph(D, (test_x, test_y))

 

視覺化的結果如下:

數據處理

神經網路都建置好就可以準備來訓練啦!當然第一步要先將數據處理好,那我個人自學神經網路的過程我覺得最難的就是數據處理了,這次數據處理有2個部分:

  1. 宣告固定的雜訊跟標籤用來預測用
  2. 將標籤轉換成onehot格式 ( scatter )

 

Onehot數據處理,在torch中可以直接使用scatter的方式,我在程式註解的地方有推薦一篇文章大家可以去了解scatter的概念,至於這邊我先附上實驗的程式碼:

""" OneHot 格式 之 scatter 應用"""
""" 超好理解的圖形化教學 https://medium.com/@yang6367/understand-torch-scatter-b0fd6275331c """
 
label =torch.tensor([1,5,6,9])
print(label, label.shape)
 
 
a = torch.zeros(10).scatter_(0, label, 1)
print(a)
 
print('\n\n')
label_=label.unsqueeze(1)
print(label_, label_.shape)
b = torch.zeros(4,10).scatter_(1, label_, 1)
print(b)

接下來我們將兩個部分分開處理,先來處理測試用的雜訊跟標籤,測試用圖片為美個類別各10張,所以總共有100張圖片代表是100組雜訊及對應label:

""" 產生固定資料,每個類別10張圖(雜訊) 以及 對應的標籤,用於視覺化結果 """
temp_noise = torch.randn(label_dim, z_dim)                   # (10, 100) 10張圖
fixed_noise = temp_noise                          
fixed_c = torch.zeros(label_dim, 1)                          # (10, 1 ) 10個標籤
 
for i in range(9):
    fixed_noise = torch.cat([fixed_noise, temp_noise], 0)    #將每個類別的十張雜訊依序合併,維度1會自動boardcast
    temp = torch.ones(label_dim, 1) + i                      #依序將標籤對應上 0~9
    fixed_c = torch.cat([fixed_c, temp], 0)                  #將標籤也依序合併
 
fixed_noise = fixed_noise.view(-1, z_dim, 1, 1)              #由於是捲積所以我們要將形狀轉換成二維的
print('Predict Noise: ', fixed_noise.shape)
print('Predict Label (before): ', fixed_c.shape, '\t\t\t', fixed_c[50])    
 
""" 針對 lael 做 onehot """
fixed_label = torch.zeros(100, label_dim)                    #先產生 [100,10] 的全0張量,100個標籤,每個標籤維度是 10
fixed_label.scatter_(1, fixed_c.type(torch.LongTensor), 1)   #轉成 onehot編碼 (1, ) -> (10, )
fixed_label = fixed_label.view(-1, label_dim, 1, 1)          #轉換形狀 (10, 1, 1 ) 
print('Predict Label (onehot): ',fixed_label.shape, '\t\t', fixed_label[50].view(1,-1), '\n')

我在顯示的時候有將形狀從 (10,1)變成(1,10) 來方便做觀察:

接下來要幫訓練的數據做前處理,處理方式跟前面雷同,主要差別在要餵給鑑別器的標籤 ( fill ) 處理方式比較不同,從結果圖就能看的出來彼此不同的地方:

""" 幫標籤做前處理,onehot for g, fill for d """
                    # 產生 (10,10) 10個標籤,維度為10 (onehot)
 
print('Train G label:',onehot[1].shape, '\n', onehot[1], '\n')                # 假設我們要取得標籤 1 的 onehot (10,1,1),直接輸入索引 1
 
fill = torch.zeros([label_dim, label_dim, image_size, image_size])    # 產生 (10, 10, 28, 28) 意即 10個標籤 維度都是 (10,28,28)
for i in range(label_dim):
    fill[i, i, :, :] = 1                                     # 舉例 標籤 5,第一個[]代表標籤5,第二個[]代表onehot為1的位置 
print('Train D Label: ', fill.shape)                             
print('\n', fill[1].shape, '\n', fill[1])                    # 假設我們要取得標籤 1 的 onehot (10,28,28)

開始訓練-起手式

一樣從基本的參數開始宣告起,流程個別是:基本參數、數據載入、建立訓練相關的東西(模型、優化器、損失)、開始訓練。

""" 基本參數 """
epoch = 10
lr = 1e-5
batch = 4
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
z_dim = 100        # latent Space
c_dim = 1          # Image Channel
label_dim = 10     # label 
 
 
""" 取得數據集以及DataLoader """
transform = trans.Compose([
    trans.ToTensor(),
    trans.Normalize((0.5,),(0.5,)),
])
 
train_set = dset.MNIST(root='./mnist_data/', 
                       train=True, transform=transform, 
                       download=True)
 
test_set = dset.MNIST(root='./mnist_data/',
                      train=False, 
                      transform=transform, 
                      download=False)
 
train_loader = torch.utils.data.DataLoader(
    dataset = train_set,
    batch_size = batch,
    shuffle=True,
    drop_last=True
)
 
test_loader = torch.utils.data.DataLoader(
    dataset = test_set,
    batch_size = batch,
    shuffle=False        
)
 
""" 訓練相關 """
 
D = Discriminator(c_dim, label_dim).to(device)
G = Generator(z_dim, label_dim).to(device)
loss_fn = nn.BCELoss()
D_opt = optim.Adam(D.parameters(), lr= lr)
G_opt = optim.Adam(G.parameters(), lr= lr)
D_avg_loss = []
G_avg_loss = []
 
img = []
ls_time = []

開始訓練 – 手動更新學習率

會手動更新主要原因在於其實GAN的訓練並不是那麼的順利,如果速度太快會導致震盪嚴重訓練生成效果極差,所以GAN普遍的學習率都會更新並且都蠻低的,這邊我也稍微調整一下:

    """ 看到很多範例都有手動調整學習率 """
    if epoch == 8:
        G_opt.param_groups[0]['lr'] /= 5
        D_opt.param_groups[0]['lr'] /= 5

開始訓練 – 訓練D、G

一樣參考上一篇的DCGAN來改良,主要差別在於需要引入label,並且需要將label轉換成onehot格式,其中

鑑別器 (D) 的訓練步驟一樣先學真實圖片給予標籤1  再學生成圖片給予標籤 0,生成圖片的部分要產生對應的亂數label,丟入G的時候是從先前寫的 onehot 中提取對應的onehot格式標籤而丟入D的時候是從 fill 中提取~

生成器 (G) 的訓練方式就是把D的後半段拿出來用,但是標籤需要改成 1,因為它的目的是要騙過D!

""" 訓練 D """
 
D_opt.zero_grad()
 
x_real = data.to(device)
y_real = torch.ones(batch, ).to(device)
c_real = fill[label].to(device)
 
y_real_predict = D(x_real, c_real).squeeze()        # (-1, 1, 1, 1) -> (-1, )
d_real_loss = loss_fn(y_real_predict, y_real)
d_real_loss.backward()
 
noise = torch.randn(batch, z_dim, 1, 1, device = device)
noise_label = (torch.rand(batch, 1) * label_dim).type(torch.LongTensor).squeeze()
noise_label_onehot = onehot[noise_label].to(device)   #隨機產生label (-1, )
 
x_fake = G(noise, noise_label_onehot)       # 生成假圖
y_fake = torch.zeros(batch, ).to(device)    # 給予標籤 0
c_fake = fill[noise_label].to(device)       # 轉換成對應的 10,28,28 的標籤
 
y_fake_predict = D(x_fake, c_fake).squeeze()
d_fake_loss = loss_fn(y_fake_predict, y_fake)
d_fake_loss.backward()
D_opt.step()
 
""" 訓練 G """
 
G_opt.zero_grad()
 
noise = torch.randn(batch, z_dim, 1, 1, device = device)
noise_label = (torch.rand(batch, 1) * label_dim).type(torch.LongTensor).squeeze()
noise_label_onehot = onehot[noise_label].to(device)   #隨機產生label (-1, )
 
x_fake = G(noise, noise_label_onehot)
#y_fake = torch.ones(batch, ).to(device)    #這邊的 y_fake 跟上述的 y_real 一樣,都是 1  
c_fake = fill[noise_label].to(device)
 
y_fake_predict = D(x_fake, c_fake).squeeze()
g_loss = loss_fn(y_fake_predict, y_real)    #直接用 y_real 更直觀
g_loss.backward()
G_opt.step()
 
D_loss.append(d_fake_loss.item() + d_real_loss.item())
G_loss.append(g_loss.item())

成果

起初我在第五次迭代的時候調整了學習率結果原本 1 到 5 學習的都不錯,到第 6次的時候開始有了偏差,所以真的不能亂調學習率阿~

下面是迭代15次的成果,感覺上比參考的gihub還要差了一些,仔細看了一下應該是D的結構跟learning rate的調整有差,大家可以在自己調整看看。

訓練時間比較

一樣都是 10 個 epoch ,Jetson Nano所需要的時間大約是 1 小時 40 分鐘,其實還算是蠻快的,大家可以試試看 CPU 去跑跑看就可以知道差異了。

 

結語

最後相信大家到看完這篇以及上一篇DCGAN已經對生成對抗網路有一定的熟悉度了,接下來我們可以找些GAN的github的範例來玩玩看並且增加應用。

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 (本篇文章完整範例程式請至原文下載)

LOBE connect –本機端 api 呼叫

$
0
0
撰寫/攝影   曾吉弘 
難度

★★★★☆ (要理解 python 與基本網路呼叫,難度1~10)

時間

約1小時

材料表
  • 電腦,具備 python 環境
  • 要辨識的物體(例如兩種不同的扭蛋)

  微軟在2020下半年推出了 LOBE.ai 神經網路訓練工具,我們馬上寫了這篇文章 [微軟 LOBE ai – 離線訓練影像辨識模型!],裡面有一個即時影像推論的範例,在一般電腦或是單板電腦(Rasbperry Pi 或 Jetson Nano) 上都可以執行,如下圖:

最近一次更新之後,可以匯出的神經網路模型格式更豐富了,也提供了 iOS / Android 的 app 樣板讓您可以更快完成一個視覺辨識 app。

    本篇文章是另一個小範例,LOBE 在訓練完神經網路之後就會產生一個本地端的 REST API 端點,可以自己呼叫自己,或是讓別台電腦透過 ip 來呼叫,並根據所訓練好的神經網路模型來回傳結果。適合不強調執行速度的情境下使用,程式也比較簡潔,基本上就對 LOBE API url 進行一次 POST 呼叫來送出一個 base64 影像,接著 LOBE 就會回傳預測結果,包含標籤與信心指數。

    請根據 [微軟 LOBE ai – 離線訓練影像辨識模型!] 快速訓練一個模型吧!完成之後請在 Use 中找到 Lobe Connect in the Use tab to get started integrating your model.

  點選之後會跳出一個選單,點選下拉式選單選到 [Python],可以看到所產生的端點與範例程式碼(本文末)其他選項還有 C#、Node.js、,方便不同技術背景的使用者快速使用。端點會長這樣:

http://localhost:38101/v1/predict/8658343e-1545-4354-b3ea-093f83ed9a4c

當然也可以把 localhost 改成您電腦的 IP,例如 192.168.x.x,就可以讓同一個網段的齊它電腦來呼叫這個 LOBE 端點了!

 延續上一篇教學 中的 DOM 與 ZAKU,在此一樣使用這兩個相當具代表性的機器人來進行辨識,測試圖片如下圖:

 

    由於這個範例只進行 http 呼叫,只要有 Python 環境並且不需要安裝AI框架就可以執行,請新增以下 lobe_connect.py (或任意檔名),在終端機中執行就可以看到結果了。下圖進行了三次呼叫,辨識結果分別為zaku、zaku、dom:

 

import base64
import requests

# 請設定正確的檔名與路徑
img_filepath = "./dom.jpg"

# Create base64 encoded string
with open(img_filepath, "rb") as f:
    image_string = base64.b64encode(f.read()).decode("utf-8")

# 取得 POST 呼叫的回應
response = requests.post(
    url="http://localhost:38101/v1/predict/8658343e-1545-4354-b3ea-093f83ed9a4c",
    json={"image": image_string},
)
data = response.json()
top_prediction = data["predictions"][0]  #取得預測值(信心指數)最高的項目

# 顯示信心最高的標籤名稱與信心值
print("predicted label:\t{}\nconfidence:\t\t{}".format(top_prediction["label"], top_prediction["confidence"]))
< lobe_connect.py >

相關文章:

也來做電子火把

$
0
0

「出來混,總是要還的」~ 無間道2

家裡兩個小孩長期受華中童軍團-青商總會第170團(FB連結)的照顧與訓練,總是要回饋一下,就來做做看營火活動中的要角「火把」。

撰寫/攝影 腦波弱老闆
難度

★☆☆☆☆ (我腦波弱都做得出來,是能有多難)

時間

組裝大約10分鐘,備料比較花時間。

材料表
  • 火焰燈泡 AC/DC12-24V 7W T60 E27
  • E27燈座
  • 9V電池
  • 9V電池扣
  • PVC電管 28mm(約50cm長)
  • PVC電管 50 X 28mm轉接
  • PVC電管 35 X 28mm轉接
  • PVC電管 35mm塞頭
  • 電線兩條,長度約60cm
  • 開關一個
  • 夾線豆或是接線器四個(也可以用焊的或是電線纏起來,再電工膠帶包一包)

 

參考了網路上的產品,大多是以風扇吹動紅色的三角形布片,加上黃色燈光,看起來像是迎運動會聖火用的。

而且,看起來沒有很難做。

「像我這樣專業的Maker,大概幾天就會搞定了」~ OpenLab Taipei創辦人,現任台灣自造者協會理事長鄭鴻旗

當然是要來自己弄一套啊!

首先要來找有火焰效果的LED燈泡(不是會燒起來的燈泡),雖然這顆燈泡是吃12V的電,但9V就可以亮(我也試過用行動電源的5V,也是可以用的),而且交流或是直流電都可以,所以也不用管正負極方向問題,還蠻方便的。

零號機是使用之前童軍活動製作的竹竿火把來改裝,步驟如下:

  1. 準備一個空的伯朗咖啡罐,把底部用開罐器切掉,切口用銼刀修一下,並用電工膠帶貼起來。
  2. 上面的拉環去掉,再鎖上E27燈座,讓電線從喝飲料那個孔走下來。
  3. 接上有開關的9V電池盒,並放入9V電池。
  4. 把電池盒塞回去。

為了讓光線看起來比較柔和,所以在燈泡外面再包一圈白色PE(厚度約8mm,從包材回收來用的),順便可以遮醜。

 

但是呢,臨時沒有這麼多支竹竿可以給整團的童軍們使用。

沒關係,我是專業的Maker,一定可以找出辦法的。

團員家長中,有專業水電技師,就由專家以PVC電管製作量產型,規格一致,方便加工。(其實是開外掛,感謝林爸爸)

設計圖簡圖如下:

強者家長手繪的。

安全護具:

  • 護目鏡
  • 工作手套(3M工作手套不錯用)
  • 製作過程中會接觸到揮發性接著劑跟噴漆,請帶口罩或防毒面具。

 

材料清單:

  • PVC電管 28mm 約50公分(一根PVC管長度為4公尺,買的時候可以請店家先裁好)
  • 35X28mm異徑接頭
  • 50X28mm異徑接頭
  • 35mm管頭
  • 電線:兩條,各約60公分左右,建議準備紅色跟黑色。
  • 開關
  • 熱縮套管
  • E27燈座(塑膠製,我是用斜口剪把旁邊螺絲固定座修掉)

 

工具:

  • 鋸子或裁管器
  • 電鑽與鑽頭(開孔安裝開關)
  • 剝線鉗、斜口剪
  • 銲槍(開關接線用)
  • 熱風槍(縮熱縮套管用)
  • 熱熔膠(固定開關時,可能會用到)
  • 黏合管子有專門的接著劑,如:南亞硬質膠合劑,採購PVC管的時候一起買就好,還要買搭配的毛刷,圖片中黃色的那種是可以當蓋子用的刷子,覺得方便。

修飾用具:

膠帶、砂紙、噴漆。

 

製作步驟:

1.先組合PVC管子,並開孔給開關用。開孔大小請以您取得的開關尺寸為準

2.開關焊上電線。一端長度約60cm,稍後接燈座正極,另一端約10cm,要接電池扣的正極。因為當天是在戶外進行工作坊,不方便用電焊,所以這段我們是先做好再帶去的。

3.把步驟2做好的線,從開關孔裝進去,並拉到定位

4.把燈座跟電池扣的線路接起來。

接燈座。

 

5.裝上燈泡跟電池,試試看會不會亮。

6.把線路收好,火把下端的管塞裝上去,就完成了。

幫火把加上裝飾,如果像我們一樣用噴漆的,建議先用砂紙磨過外表後再噴,增加漆的附著性。

雖然晚上只看的到燈光,但自己開心很重要。

 

附記:

製作那天,忘記帶E27燈座去,只好直接把電線用膠帶固定在燈泡上,也是可以用,塞在管子內,外觀也分不出來。

下次改裝的話,會考慮用行動電源模組加上USB升壓模組,看看12V的亮度效果如何。

 

利用Jetson Nano、Google Colab實作CycleGAN:將拍下來的照片、影片轉換成梵谷風格 – 訓練、預測以及應用篇

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 

作者/攝影 嘉鈞
難度

★★★★☆(中偏難)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

訓練CycleGAN

首先先取得訓練資料:

from tqdm import tqdm
import torchvision.utils as vutils

total_len = len(dataA_loader) + len(dataB_loader)

for epoch in range(epochs): 
    progress_bar = tqdm(enumerate(zip(dataA_loader, dataB_loader)), total = total_len) 
    for idx, data in progress_bar: 
        ############ define training data & label ############ 
        real_A = data[0][0].to(device)    # vangogh image
        real_B = data[1][0].to(device)    # real picture

我們要先訓練G,總共有三個標準要來衡量生成器:

1.是否能騙過鑑別器 (Adversial Loss ):

對於G_B2A來說,將A轉換成B之後給予1的標籤,並且計算跟real_B 之間的距離。

        ############ Train G ############ 
        optim_G.zero_grad()
        
        ############  Train G - Adversial Loss  ############
        fake_A = G_B2A(real_B)
        fake_out_A = D_A(fake_A) 
        fake_B = G_A2B(real_A)
        fake_out_B = D_B(fake_B)

        real_label = torch.ones( (fake_out_A.size()) , dtype=torch.float32).to(device)
        fake_label = torch.zeros( (fake_out_A.size()) , dtype=torch.float32).to(device) 
        adversial_loss_B2A = MSE(fake_out_A, real_label)
        adversial_loss_A2B = MSE(fake_out_B, real_label)
        adv_loss = adversial_loss_B2A + adversial_loss_A2B

2.是否能重新建構 (Consistency Loss):

舉例 G_B2A(real_B) 產生風格A的圖像 (fake_A) 後,再丟進 G_A2B(fake_A) 重新建構成B風格的圖像 (rec_B),並且計算 real_B 跟 rec_B之間的差距。

        ############  G - Consistency Loss (Reconstruction)  ############
        rec_A = G_B2A(fake_B)
        rec_B = G_A2B(fake_A) 
        consistency_loss_B2A = L1(rec_A, real_A)
        consistency_loss_A2B = L1(rec_B, real_B) 
        rec_loss = consistency_loss_B2A + consistency_loss_A2B

3.是否能保持一致 (Identity Loss):

以G_A2B來說,是否在丟入 real_B的圖片後,確實能輸出 B風格的圖片,是否能保持原樣?

        ############  G - Identity  Loss ############
        idt_A = G_B2A(real_A)
        idt_B = G_A2B(real_B) 
        identity_loss_A = L1(idt_A, real_A)
        identity_loss_B = L1(idt_B, real_B) 
        idt_loss = identity_loss_A + identity_loss_B

接著訓練D,它只要將自己的本份顧好就好了,也就是「能否分辨得出該風格的成像是否真實」。

        ############ Train D ############ 
        optim_D.zero_grad()
        
        ############ D - Adversial D_A Loss ############  
        real_out_A = D_A(real_A)
        real_out_A_loss = MSE(real_out_A, real_label) 
        fake_out_A = D_A(fake_A_sample.push_and_pop(fake_A))
        fake_out_A_loss = MSE(real_out_A, fake_label)
        
        loss_DA = real_out_A_loss + fake_out_A_loss
        
        ############  D - Adversial D_B Loss  ############
        
        real_out_B = D_B(real_B)
        real_out_B_loss = MSE(real_out_B, real_label) 
        fake_out_B = D_B(fake_B_sample.push_and_pop(fake_B))
        fake_out_B_loss = MSE(fake_out_B, fake_label)
        
        loss_DB = ( real_out_B_loss + fake_out_B_loss )
        
        ############  D - Total Loss ############
        
        loss_D = ( loss_DA + loss_DB ) * 0.5
        
        ############  Backward & Update ############
        
        loss_D.backward()
        optim_D.step()

最後我們可以將一些資訊透過tqdm印出來

        ############ progress info ############
        progress_bar.set_description(
            f"[{epoch}/{epochs - 1}][{idx}/{len(dataloader) - 1}] "
            f"Loss_D: {(loss_DA + loss_DB).item():.4f} "
            f"Loss_G: {loss_G.item():.4f} "
            f"Loss_G_identity: {(idt_loss).item():.4f} "
            f"loss_G_GAN: {(adv_loss).item():.4f} "
            f"loss_G_cycle: {(rec_loss).item():.4f}")

接著訓練GAN非常重要的環節就是要記得儲存權重,因為說不定訓練第100回合的效果比200回合的還要好,所以都會傾向一定的回合數就儲存一次。儲存的方法很簡單大家可以上PyTorch的官網查看,大致上總共有兩種儲存方式:

1.儲存模型結構以及權重

torch.save( model )

2.只儲存權重

torch.save( model.static_dict() )

而我採用的方式是只儲存權重,這也是官方建議的方案:

       
        if i % log_freq == 0:
            
            vutils.save_image(real_A, f"{output_path}/real_A_{epoch}.jpg", normalize=True)
            vutils.save_image(real_B, f"{output_path}/real_B_{epoch}.jpg", normalize=True)
            
            fake_A = ( G_B2A( real_B ).data + 1.0 ) * 0.5
            fake_B = ( G_A2B( real_A ).data + 1.0 ) * 0.5
            
            vutils.save_image(fake_A, f"{output_path}/fake_A_{epoch}.jpg", normalize=True)
            vutils.save_image(fake_B, f"{output_path}/fake_A_{epoch}.jpg", normalize=True)
        
        
    torch.save(G_A2B.state_dict(), f"weights/netG_A2B_epoch_{epoch}.pth")
    torch.save(G_B2A.state_dict(), f"weights/netG_B2A_epoch_{epoch}.pth")
    torch.save(D_A.state_dict(), f"weights/netD_A_epoch_{epoch}.pth")
    torch.save(D_B.state_dict(), f"weights/netD_B_epoch_{epoch}.pth")

    ############ Update learning rates ############
    lr_scheduler_G.step()
    lr_scheduler_D.step()

############ save last check pointing ############
torch.save(netG_A2B.state_dict(), f"weights/netG_A2B.pth")
torch.save(netG_B2A.state_dict(), f"weights/netG_B2A.pth")
torch.save(netD_A.state_dict(), f"weights/netD_A.pth")
torch.save(netD_B.state_dict(), f"weights/netD_B.pth")   

測試

其實測試非常的簡單,跟著以下的步驟就可以完成:

1.導入函式庫

import os
import torch
import torchvision.datasets as dsets
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from tqdm import tqdm
import torchvision.utils as vutils

2.將測試資料建一個數據集並透過DataLoader載入:

這邊我創了一個Custom資料夾存放我自己的數據,並且新建了一個output資料夾方便察看結果。

  batch_size = 12
  device = 'cuda:0' if torch.cuda.is_available() else 'cpu'

  transform = transforms.Compose( [transforms.Resize((256,256)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])])

  root = r'vangogh2photo'
  targetC_path = os.path.join(root, 'custom')
  output_path = os.path.join('./', r'output')

  if os.path.exists(output_path) == False:
    os.mkdir(output_path)
    print('Create dir : ', output_path)

  dataC_loader = DataLoader(dsets.ImageFolder(targetC_path, transform=transform), batch_size=batch_size, shuffle=True, num_workers=4)

3.實例化生成器、載入權重 (load_static_dict)、選擇模式 ( train or eval ),如果選擇 eval,PyTorch會將Drop給自動關掉;因為我只要真實照片轉成梵谷所以只宣告了G_B2A:

  # get generator
  G_B2A = Generator().to(device)

  # Load state dicts
  G_B2A.load_state_dict(torch.load(os.path.join("weights", "netG_B2A.pth")))

  # Set model mode
  G_B2A.eval()

4.開始進行預測:

取得資料>丟進模型取得輸出>儲存圖片

progress_bar = tqdm(enumerate(dataC_loader), total=len(dataC_loader))

  for i, data in progress_bar:
   # get data
      real_images_B = data[0].to(device)

      # Generate output
      fake_image_A = 0.5 * (G_B2A(real_images_B).data + 1.0)

      # Save image files
      vutils.save_image(fake_image_A.detach(), f"{output_path}/FakeA_{i + 1:04d}.jpg", normalize=True)

      progress_bar.set_description(f"Process images {i + 1} of {len(dataC_loader)}")

5.去output察看結果:

可能是因為我只有訓練100回合,梵谷風格的細節線條還沒學起來,大家可以嘗試再訓練久一點,理論上200回合就會有不錯的成果了!

 

ORIGINAL

 

TRANSFORM

 

好的,那現在已經會建構、訓練以及預測了,接下來我們來想個辦法應用它!講到Style Transfer的應用,第一個就想到微軟大大提供的Style Transfer Azure Website。

 

Azure 的Style Transfer

網站連結  https://styletransfers.azurewebsites.net/

 

這種拍一張照片就可以直接做轉換的感覺真的很棒!所以我們理論上也可以透過簡單的opencv程式來完成這件事情,再實作之前先去體驗看看Style Transfer 。

按下Create就能進來這個頁面,透過點擊Capture就可以拍照進行轉換也可以點擊Upload a picture上傳照片,總共有4種風格可以選擇:

感覺真的超級酷的!所以我們也來試著實作類似的功能。

 

在JetsonNano中進行風格轉換

 

1.首先要將權重放到Jetson Nano中

我新增了一個weights資料夾並且將pth放入其中,此外還在同一層級新增了jupyter book的程式:

2.重建生成器並導入權重值

這邊可能會有版本問題,像我就必須升級成Torch 1.6版本,而安裝PyTorch的方法我會放在文章結尾補述,回歸正題,還記得剛剛我儲存的時候只有儲存權重對吧,所以我們必須建一個跟當初訓練一模一樣的模型才能匯入哦!所以來複製一下之前寫的生成器吧!

import torch
from torch import nn
from torchsummary import summary


def conv_norm_relu(in_dim, out_dim, kernel_size, stride = 1, padding=0):
    
    layer = nn.Sequential(nn.Conv2d(in_dim, out_dim, kernel_size, stride, padding),
                          nn.InstanceNorm2d(out_dim), 
                          nn.ReLU(True))
    return layer

def dconv_norm_relu(in_dim, out_dim, kernel_size, stride = 1, padding=0, output_padding=0):
    
    layer = nn.Sequential(nn.ConvTranspose2d(in_dim, out_dim, kernel_size, stride, padding, output_padding),
                          nn.InstanceNorm2d(out_dim), 
                          nn.ReLU(True))
    return layer

class ResidualBlock(nn.Module):
    
    def __init__(self, dim, use_dropout):
        super(ResidualBlock, self).__init__()
        res_block = [nn.ReflectionPad2d(1),
                     conv_norm_relu(dim, dim, kernel_size=3)]
        
        if use_dropout:
            res_block += [nn.Dropout(0.5)]
        res_block += [nn.ReflectionPad2d(1),
                      nn.Conv2d(dim, dim, kernel_size=3, padding=0),
                      nn.InstanceNorm2d(dim)]

        self.res_block = nn.Sequential(*res_block)

    def forward(self, x):
        return x + self.res_block(x)

class Generator(nn.Module):
    
    def __init__(self, input_nc=3, output_nc=3, filters=64, use_dropout=True, n_blocks=6):
        super(Generator, self).__init__()
        
        # 向下採樣
        model = [nn.ReflectionPad2d(3),
                 conv_norm_relu(input_nc   , filters * 1, 7),
                 conv_norm_relu(filters * 1, filters * 2, 3, 2, 1),
                 conv_norm_relu(filters * 2, filters * 4, 3, 2, 1)]

        # 頸脖層
        for i in range(n_blocks):
            model += [ResidualBlock(filters * 4, use_dropout)]

        # 向上採樣
        model += [dconv_norm_relu(filters * 4, filters * 2, 3, 2, 1, 1),
                  dconv_norm_relu(filters * 2, filters * 1, 3, 2, 1, 1),
                  nn.ReflectionPad2d(3),
                  nn.Conv2d(filters, output_nc, 7),
                  nn.Tanh()]

        self.model = nn.Sequential(*model)    # model 是 list 但是 sequential 需要將其透過 , 分割出來

    def forward(self, x):
        return self.model(x)

接下來要做實例化模型並導入權重:

def init_model():
    
    device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
    G_B2A = Generator().to(device)
    G_B2A.load_state_dict(torch.load(os.path.join("weights", "netG_B2A.pth"), map_location=device ))
    G_B2A.eval()
    
    return G_B2A

3.在Colab中拍照

我先寫了一個副函式來進行模型的預測,丟進去的圖片記得也要做transform,將大小縮放到256、轉換成tensor以及正規化,這部分squeeze目的是要模擬成有batch_size的格式:

def test(G, img): 
    device = 'cuda:0' if torch.cuda.is_available() else 'cpu' 
    transform = transforms.Compose([transforms.Resize((256,256)),
                                    transforms.ToTensor(),
                                    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])])

    data = transform(img).to(device) 
    data = data.unsqueeze(0) 
    out = (0.5 * (G(data).data + 1.0)).squeeze(0)    return out

我們接著使用OpenCV來完成拍照,按下q離開,按下s進行儲存,那我們可以在按下s的時候進行風格轉換,存下兩種風格的圖片,這邊要注意的是PyTorch吃的是PIL的圖檔格式,所以還必須將OpenCV的nparray格式轉換成PIL.Image格式:

if __name__=='__main__':
    
    G = init_model()
    
    trans_path = 'test_transform.jpg'
    org_path = 'test_original.jpg'
    
    cap = cv2.VideoCapture(0)

    while(True):

        ret, frame = cap.read()
        
        cv2.imshow('webcam', frame)

        key = cv2.waitKey(1)

        if key==ord('q'):
            cap.release()
            cv2.destroyAllWindows()
            break

        elif key==ord('s'):
            
            output = test(G, Image.fromarray(frame))
            style_img = np.array(output.cpu()).transpose([1,2,0])
            org_img = cv2.resize(frame, (256, 256))
            
            cv2.imwrite(trans_path, style_img*255)
            cv2.imwrite(org_path, org_img)
            break
    
    cap.release()
    cv2.destroyWindow('webcam')
    

執行的畫面如下:

最後再將兩種風格照片合併顯示出來:

    res = np.concatenate((style_img, org_img/255), axis=1)
    cv2.imshow('res',res )

    cv2.waitKey(0)
    cv2.destroyAllWindows()

在Jetson Nano中做即時影像轉換

概念跟拍照轉換雷同,這邊我們直接在取得到攝影機的圖像之後就做風格轉換,我額外寫了一個判斷,按下t可以進行風格轉換,並且用cv2.putText將現在風格的標籤顯示在左上角。

if __name__=='__main__':
    
    G = init_model() 
    cap = cv2.VideoCapture(0) 
    change_style = False
    save_img_name = 'test.jpg'
    cv2text = ''

    while(True): 
        ret, frame = cap.read()
        # Do Something Cool 
        ############################ 
        if change_style:
            style_img = test(G, Image.fromarray(frame))
            out = np.array(style_img.cpu()).transpose([1,2,0])
            cv2text = 'Style Transfer'
        else:
            out = frame
            cv2text = 'Original'
            
        out = cv2.resize(out, (512, 512))
        out = cv2.putText(out, f'{cv2text}', (20, 40), cv2.FONT_HERSHEY_SIMPLEX ,  
                   1, (255, 255, 255), 2, cv2.LINE_AA) 

        ########################### 

        cv2.imshow('webcam', out) 
        key = cv2.waitKey(1) 
        if key==ord('q'):
            break
        elif key==ord('s'):
            if change_style==True:
                cv2.imwrite(save_img_name,out*255)
            else:
                cv2.imwrite(save_img_name,out) 
        elif key==ord('t'):
            change_style = False if change_style else True
    cap.release()
    cv2.destroyAllWindows()

即時影像風格轉換成果

結語

這次GAN影像風格轉換的部分就告一段落了,利用Colab來訓練風格轉換的範例真的還是偏硬了一點,雖然我們只有訓練100回合但也跑了半天多一點了,但是!GAN就是個需要耐心的模型,不跑個三天兩夜他是不會給你多好的成效的。

至於在Inference的部分,Jetson Nano還是擔當起重要的角色,稍微有一些延遲不過還算是不錯的了,或許可以考慮透過ONNX轉換成TensorRT再去跑,應該又會加快許多了,下一次又會有什麼GAN的範例大家可以期待一下,或者留言跟我說。

 

補充 – Nano 安裝Torch 1.6的方法

首先,JetPack版本要升級到4.4哦!不然CUDA核心不同這部分官網就有升級教學所以就不多贅述了。

將PyTorch等相依套件更新至1.6版本:

$ wget https://nvidia.box.com/shared/static/yr6sjswn25z7oankw8zy1roow9cy5ur1.whl -O torch-1.6.0rc2-cp36-cp36m-linux_aarch64.whl
$ sudo apt-get install python3-pip libopenblas-base libopenmpi-dev 
$ pip3 install Cython
$ pip3 install torch-1.6.0rc2-cp36-cp36m-linux_aarch64.whl

將TorchVision更新至對應版本:

$ sudo apt-get install libjpeg-dev zlib1g-dev
$ git clone --branch v0.7.0 https://github.com/pytorch/vision torchvision
$ cd torchvision
$ export BUILD_VERSION=0.7.0  # where 0.x.0 is the torchvision version  
$ sudo python3 setup.py install     # use python3 if installing for Python 3.6
$ cd ../  # attempting to load torchvision from build dir will result in import error

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 

Pytorch深度學習框架X NVIDIA JetsonNano應用-cDCGAN風格轉換,圖片自動填色

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 嘉鈞
難度

★★★★☆(中偏難)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

Pix2pix

說到風格轉換第一個想到的就是 pix2pix,他算是很早期透過 cGAN 的方式去完成圖像與圖像之間的翻譯,是風格轉換的經典作品之一,風格轉換有很多種有趣的應用,像是從語意分析圖轉換成實體圖片,又或者將衛星地圖轉換成簡化地圖,也有給輪廓去填顏色,這些都算是風格轉換也算是 pix2pix 提供範例的範疇。

 

風格轉換 Style Transform

風格轉換的概念其實很簡單,只要將輸入跟輸出改變一下就可以完成風格轉換了。原本圖片生成的部分生成器的輸入是一組雜訊、輸出是圖片,就像下圖一樣:

現在只要把他改成輸入是一個風格的圖片、輸出是另一種風格的圖片,那我們就完成風格轉換了~不過在這邊我們的數據必須是對應的,如下圖來說A風格的數字5跟B風格的數字5就是對應的:

Pix2pix重點技術簡介

 

1.運用cGAN的架構 ( 以訓練鑑別器為例 )

下圖可以看到輸入是A風格而經過生成器後會產生B風格的圖片,這時候A風格跟B風格都要丟進鑑別器,主要用意是在輪廓都是A的狀況下,鑑別器要去區分是真實顏色還是由生成器所生成出來的。

2.生成器採用 U-Net結構

論文中有提到生成器的架構有兩種,一種是AutoEncoder的架構;另一種是U-Net的架構,作者提出來的論點主要在輸入與輸出的差別只有在「表面外觀」不同,實際的「基礎結構」是相同的,如果常看捲積神經網路的讀者應該知道,捲積神經網路的淺層主要都在色塊而深層特徵比較多是在輪廓等結構上,所以使用U-Net結構可以讓結構的訊息被共享,白話一點就是可以讓輪廓這類型的特徵在神經網路中更突出。

3.鑑別器使用 PatchGAN的技術

鑑別器運用了PatchGAN的技術,一般的GAN都是在輸出的時候給予一個數值( 0或1 ),但是PatchGAN的技術是給予N*N的矩陣 (每一個位置也是 0或1),每一個數值都代表一個Patch,可以想像把圖片切成很多個區塊去判斷成像是否真實,因為判斷的區域較小可以顧及到的細節更多,而相對的整體上細節也就會更好,特別是Patch數量越多的時候。

4.損失函數加入了L1正規化

主要是L1會讓數值區間更窄,表示讓圖像均值化,當RGB都相近的時候會越接近灰階,而 cGAN則是目標要讓圖片有更多顏色。

 

實現 pix2pix

畢竟是經典之作,現在github上有提供很多PyTorch去實作pix2pix的程式,我看了幾個覺得這個整合度比較高,他將pix2pix跟CycleGAN整合在一起,此外也寫了PyTorch跟Tensorflow的版本,所以我們就用這個 github來體驗一下pix2pix吧!

 

1.下載github

如果沒安裝過git則需先安裝,下載完後檔案結構如下圖:

pip install git
git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix

2.安裝需求套件

接著在檔案目錄中可以找到 requirements.txt,我們只需要執行下列程式進行套件安裝:

pip install -r requirements.txt

套件如下,其中torch跟torchvision就是必備套件,dominate跟visdom是用於可是化的套件,跟tensorboard一樣需要在開啟server才能進行觀察。

 

3.下載數據集

根據論文提供的數據集,總共有五種可以下載!今天我們想要讓電腦嘗試去填顏色,所以我們可以選擇 edges2shoes來玩玩看,下載後解壓縮到pytorch-CycleGAN-and-pix2pix/datasets當中:

數據集連結:https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/

Edges2shoes中有兩個資料夾 ( train, val),每張圖片大小為 ( 256 , 512 ) ,代表是將輪廓圖與原始圖合併在一起,左邊為輪廓右邊為原始圖,所以如果之後我們要預測自己的圖也必須符合這個形式:

 

4.下載欲訓練模型

接下來就要下載欲訓練模型了,一樣提供5種,請找到跟數據集對應的下載:

欲訓練模型載點:http://efrosgans.eecs.berkeley.edu/pix2pix/models-pytorch/

下載完後放置到pytorch-CycleGAN-and-pix2pix\checkpoints\edges2shoes_pretrained底下,並且更名為 latest_net_G.pth,通常需要自己新增資料夾checkpoints、edges2shoes_pretrained。

這邊可以注意到.pth檔是PyTorch的其中一種模型檔,它包含了神經網路模型以及權重,儲存跟讀取都很方便,缺點就是自由度不高,官方也比較傾向只儲存權重的方式,不過這部分就不是今天探討的範圍了。

 

5.進行預測

首先,需要先將數據集中的val 更名為 test,接著執行程式:

python test.py --dataroot ./datasets/edges2shoes --name edges2shoes_pretrained --model pix2pix --direction BtoA

–dataroot 就是數據集的位置

–name 是checkpoint的資料夾,也就是模型、權重的資料夾名稱

–model 有 cycleGAN與pix2pix可選擇

–direction 是風格轉換方向;要將輪廓填滿還是將填滿的轉成輪廓

 

6.執行結果

最後結果將會輸出在pytorch-CycleGAN-and-pix2pix的results當中,有個別的圖檔也有作者整理在網頁上的比較圖,下圖擷取部分html上的結果。可以看到效果還蠻有趣的,大致的輪廓其實掌握得很好,但顏色都會稍微有一點色偏。

玩轉 pix2pix

我在找pix2pix的時候找到一個很厲害的大神,他自己做了貓咪數據集並且放在互動式網頁上https://affinelayer.com/pixsrv/,我就在思考自己或許也能做一個陽春版的。

既然有了決斷就只差執行了!我就直接拿預訓練好的edges2shoes來嘗試,要做到這個首先需要做一個手寫繪圖版,這次我使用的是 pyqt5 ,它是 Python 用來撰寫 GUI的套件之一,可以取代內建的TKinter,我個人蠻喜歡它是它有一個Qt Designer可以像 Visual Studio 拉視窗程式那樣處理,相對來說方便許多。

不過今天我們要製作極簡手繪版就不需要這個Qt Designer了,一切從簡~

 

首先記得沒安裝pyqt5的要先安裝一下

pip install PyQt5

先在那個github中建立一個新的 Jupyter Notebook,我們可以透過終端機開啟 Notebook,輸入:

jupyter notebook

開啟後,在右上角new的地方新增 python3 檔案即可,這邊我命名為 drawpad。

首先要先定義視窗程式,設定標題、視窗顯示的位置 ( 起始座標x,起始座標y,寬,高)

self.setWindowTitle("Paint with PyQt5")
merge = 40
size = canvas_size - merge
self.setGeometry(merge, merge, size, size)

接著建立一個全白的圖片用於繪圖

self.image = QImage(self.size(), QImage.Format_RGB32) 
self.image.fill(Qt.white)

繪圖的相關設定,狀態變數、筆刷大小顏色以及滑鼠最後的位置

self.drawing = False 
self.brushSize = brush_size
self.brushColor = Qt.black
self.lastPoint = QPoint()

定義菜單以及對應功能,鑒於觸碰螢幕的菜單有點難按所以我沒增加到菜單中,不過有設定快捷鍵所以使用上還是很方便的,總共有三個功能,儲存、清除、離開:

mainMenu = self.menuBar() 
fileMenu = mainMenu.addMenu("File")

saveAction = QAction("Save", self) 
saveAction.setShortcut(QKeySequence('Ctrl+s'))
fileMenu.addAction(saveAction)
saveAction.triggered.connect(self.save)

clearAction = QAction("Clear", self) 
clearAction.setShortcut(QKeySequence('Ctrl+c')) 
fileMenu.addAction(clearAction) 
clearAction.triggered.connect(self.clear)

exitAction = QAction("Exit", self)
exitAction.setShortcut(QKeySequence('Ctrl+q'))
fileMenu.addAction(exitAction)
exitAction.triggered.connect(self.exitapp)

接下來就是繪圖的關鍵,pyqt5在觸碰上面好像有另外的寫法,不過這邊我就直接採用滑鼠點擊的方式來寫,所以要先定義滑鼠按下、拖曳、放開三個動作事件。首先是按下的時候,我們要先將狀態設定成True並且紀錄按下的位置:

    def mousePressEvent(self, event): 
        if event.button() == Qt.LeftButton: 
            self.drawing = True
            self.lastPoint = event.pos()

在滑鼠拖曳的時候,要先實例化畫布功能,實現在image上並且設定筆刷參數,接著劃一條線從上一個位置到現在位置,最後更新位置資訊並且刷新畫布:

   def mouseMoveEvent(self, event): 
          
        if (event.buttons() & Qt.LeftButton) & self.drawing: 
              
            painter = QPainter(self.image) 
            painter.setPen(QPen(self.brushColor, self.brushSize,  
                            Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 
              
            painter.drawLine(self.lastPoint, event.pos()) 
              
            self.lastPoint = event.pos() 

            self.update()

最後放開左鍵的時候就將狀態設為False,也就不會畫線跟刷新了:

    def mouseReleaseEvent(self, event): 
  
        if event.button() == Qt.LeftButton:
            self.drawing = False

各種功能設定,第一個是畫圖的事件,需要先將畫圖功能打開,對象是主視窗,利用drawImage將剛剛的self.image繪製上去;第二跟第三個是為了綁定菜單跟快捷鍵而設計的副函式:

    # 建立 painter 後還須建立畫圖的事件 
    def paintEvent(self, event): 
        canvasPainter = QPainter(self) 
        canvasPainter.drawImage(self.rect(), self.image, self.image.rect()) 
   
    # 清除畫布
    def clear(self): 
        
        self.image.fill(Qt.white)
        self.update() 
    
    # 離開視窗
    def exitapp(self):
        self.close()

接下來是儲存圖片的部分,儲存圖片的大小我是預設為畫布大小所以會隨著你的螢幕大小而改變,但是訓練、測試資料維度大小為 (256, 256) 所以必須先重新朔形,此外還需增加Ground Truth的部分,不過因為是自己畫得所以Ground Truth為空白,上述說的全都再initImage 這個副函式中執行,接著再儲存的時候先進行儲存 (self.image.save) 再進行前處理 ( iniImage):

    def initImage(self, filepath):

        trg_size = 256

        new_img = np.zeros([256,512,3], dtype=np.uint8)

        new_img.fill(255)

        org_img = cv2.imread('{}'.format(filepath))
        src_img = cv2.resize(org_img, (trg_size, trg_size) )

        print('org_img {} > src_img {}'.format(org_img.shape, src_img.shape))

        for h in range(trg_size):
            for w in range(trg_size*2):
                if w< 256:
                    new_img[h][w] = src_img[h][w]
                else:
                    break

        cv2.imwrite(filepath, new_img)

        # 儲存畫布
    def save(self):
        filePath = {your path}

        self.image.save(filePath) 
        self.initImage(filePath)
        print('save image: ', filePath)
        self.close()     

以上宣告完,就是主要執行的階段了:

# 實例化 app 這支程式
if not QtWidgets.QApplication.instance():
    app = QtWidgets.QApplication(sys.argv)
else:
    app = QtWidgets.QApplication.instance() 

# 獲得螢幕可使用範圍
screen = app.primaryScreen()
rect = screen.availableGeometry()
print('Available: %d x %d' % (rect.width(), rect.height()))

# 宣告畫布大小以及筆刷大小
canvas_size = rect.width() if rect.width() < rect.height() else rect.height()
brush_size = 10

# 實例化視窗
window = Window(canvas_size, brush_size) 
  
# 顯示視窗
window.show() 
  
# 執行 app
sys.exit(app.exec())

接著下一個block的目標就是執行pix2pix並且查看成果,這邊直接寫了指令 (有點懶得整合),執行完成後再透過opencv來開啟成果圖,按任意鍵即可退出。

!python test.py --dataroot datasets\edges2shoes --model pix2pix --name edges2shoes_pretrained
        
ImPath =  {your path} + ‘custom_edge_fake_B.png’
im = cv2.imread(ImPath)
cv2.imshow('result', im)
cv2.waitKey(0)
cv2.destroyAllWindows()

玩轉pix2pix的執行成果

我有嘗試用PC以及Jetson Nano都可以成功運行,當程式開始執行會跳出一個手繪板,

畫完之後按 ctrl + s 就會自動儲存到 test,接下來可以執行下一個 block的程式來利用
pix2pix 生成圖片。

這邊也提供了 PC 、Jetson Nano的DEMO影片:

結語

這次帶大家認識了pix2pix,是不是很好玩?GAN很多應用都是相當有趣的~不過就是訓練起來要人命,所以可以先嘗試別人做好的預訓練模型,來看能不能完成自己想要的結果。像是今天這個案例,如果我要做一個狗狗自動填色的其實就是在datasets中換成自己的數據就可以了。 下一次將帶大家認識另個經典之作 CycleGAN,它與pix2pix都是屬於風格轉換,不過它的數據是可以不用成對的,下一篇會再仔細說明~

 

參考文章

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 (本篇文章完整範例程式請至原文下載)

Intel 深度影像攝影機應用- 在NVIDIA Jetson Nano上使用RealSense D435-辨識人臉與距離

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 

作者/攝影 郭俊廷
難度

★★★☆☆

材料表

RK-2020新版NVIDIA® Jetson Nano™ Developer Kit B01(簡配套件)

之前的文章跟大家介紹深度攝影機RealSense D435相關資訊與應用還有OpenCV的基礎應用。本次的分享如何在Jetson Nano上改寫RealSense D435的Python範例,OpenCV搭配深度資訊辨識畫面中人臉與攝影機的距離。對Jetson Nano、Python、OpenCV不太熟悉有的讀者可以看先前的基礎文章。

 

相關文章請參考:

 

需要把RealSense D435相關的套件軟體安裝完成,在執行本篇文章的所有功能,相關安裝與設定請參考先前的]文章。

 

本篇文章使用MobaXterm遠端連線操作範例,您也可以直接使用螢幕鍵盤滑鼠編輯相關內容

執行以下指令移動到RealSense的python/example資料夾

cd ~/librealsense/wrappers/python/examples

執行以下指令測試OpenCV是否成功安裝,如安裝成功會顯示OpenCV版本

python -c "import cv2; print(cv2.__version__)"

沒有安裝OpenCV時可以執行以下指令安裝OpenCV

sudo apt-get install python3-opencv

STEP1解析範例程式

執行以下指令可以看到RGB影像與深度影像

python3 opencv_viewer_example.py

本範例按下CTRL+C中斷程式並關閉攝影機畫面,根據下方增加的程式修改成在畫面上按Esc或q鍵關閉攝影機畫面

以下解析官方範例程式。

這個程式碼使用常見的OpenCV及NumPy套件,並且處理與顯示彩色影像資訊及深度影像資訊。是可以快速入門且方便的程式碼。

首先來看看程式碼的內容及解析。

第一段為匯入函式庫,包含PyRealSense2、NumPy、以及OpenCV。

import pyrealsense2 as rs
import numpy as np
import cv2

第二段中設定彩色影像串流以及深度影像串流,解析度設定為640×480。兩個影像資料格式不同:z16為16bit線性深度值,深度比例乘上該像素儲存的值可以得到以公尺為單位的測量深度值;bgr8很直觀為8bit的藍綠紅三顏色通道資訊,與OpenCV格式互通。對其他格式有興趣可以參考此文件

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

接下來就是開始影像串流

pipeline.start(config)

主要段落當中,使用了try/finally。我們之前的文章,大多數都是介紹try/except。except為try區塊中有發生例外則執行的區塊,而finally則是無論try區塊有無發生例外,finally區塊一定會被執行。常常製作影像相關專題的使用者可能會有過以下類似的經驗,如果沒有正確關閉資源,就會導致程式結束後,鏡頭模組資源被佔用,而無法順利執行下一個要使用鏡頭模組資源的程式。所以finally區塊可以放入「無論什麼狀況都需要關閉資源的程式碼」。在這個範例中,finally放入了關閉影像串流的程式碼。

try:
    while True:
        ……
finally:
    # 停止影像串流
    pipeline.stop()

在while True迴圈中的第一段落為,等待同一幀的彩色跟深度影像才繼續執行後續影像處理,兩者缺一不可。

frames = pipeline.wait_for_frames()
depth_frame = frames.get_depth_frame()
color_frame = frames.get_color_frame()
if not depth_frame or not color_frame:
    continue

迴圈中第二段落將兩種影像資訊都轉換成NumPy陣列。

depth_image = np.asanyarray(depth_frame.get_data())
color_image = np.asanyarray(color_frame.get_data())

迴圈中第三段將深度影像資訊上假彩色 (影像必須事先轉換成每像素8bit)。假彩色很有趣,可以套用OpenCV不同的假彩色設定。除了cv2.COLORMAP_JET這個最常見的設定之外,也可以試試 cv2.COLORMAP_SUMMER、cv2.COLORMAP_OCEAN等不一樣的假彩色設定。

depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)

第四段中,使用hstack將彩色影像及深度影像兩張影像水平方向結合在一起,你也可以改成用vstack將兩張圖垂直結合在一起。能結合在一起的前提是影像要結合的邊像素數目要對上。

images = np.hstack((color_image, depth_colormap))

第五段為顯示影像。

cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
cv2.imshow('RealSense', images)

第六段為設定按 esc 鍵或是 q 鍵就關閉顯示影像的視窗,也就是我們要增加的程式碼,可以使用nano編輯相關程式或是使用螢幕鍵盤滑鼠直接到目錄底下開啟該程式編輯

        key = cv2.waitKey(1)
 
        if key & 0xFF == ord('q') or key == 27:
            cv2.destroyAllWindows()
            break

輸入以下指令編輯opencv_viewer_example.py檔案

nano ~/librealsense/wrappers/python/examples/opencv_viewer_example.py

完整程式碼如下:

## License: Apache 2.0. See LICENSE file in root directory.
## Copyright(c) 2015-2017 Intel Corporation. All Rights Reserved.

###############################################
##      Open CV and Numpy integration        ##
###############################################

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)

try:
    while True:
        frames = pipeline.wait_for_frames()
        depth_frame = frames.get_depth_frame()
        color_frame = frames.get_color_frame()
        if not depth_frame or not color_frame:
            continue

        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())

        depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)

        images = np.hstack((color_image, depth_colormap))

        cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('RealSense', images)
  
        key = cv2.waitKey(1)
        if key & 0xFF == ord('q') or key == 27:
            cv2.destroyAllWindows()
            break

finally:
    pipeline.stop()

左邊為彩色影像資訊、右邊為深度影像資訊。

這時按 Esc 鍵或是 q 鍵就可以關閉顯示影像的視窗

 

STEP2取得單點深度資訊

知道如何取得影像及如何影像串流之後,下一步就是練習取得像素單點的深度資訊。

我們要在上述範例中加入一小段程式碼來顯示取得的單點深度資訊。

首先,讓彩色影像跟深度影像對齊很重要!沒有對齊取得彩色資訊跟深度資訊就會搭不上,下列為兩組影像對齊的程式碼:

align_to = rs.stream.color
align = rs.align(align_to)
...
aligned_frames = align.process(frames)   
depth_frame = aligned_frames.get_depth_frame() 
color_frame = aligned_frames.get_color_frame()

使用下列程式碼取得影像中像素(x, y)的深度資訊:

depth_frame.get_distance(x, y)

為了方便示範,接下來的範例中會試圖取得影像正中央的像素,也就是點(320,240)的深度資訊。並將深度資訊做成字串。 深度值太長,所以使用np.round,取前幾個數值即可。

text_depth = "depth value of point (320,240) is "+str(np.round(depth_frame.get_distance(320, 240),4))+"meter(s)"
 

如果你不是很確定你取得的影像大小,可以使用下列的程式來進行確認。

print("shape of color image:{0}".format(color_image.shape))

接下來使用OpenCV的circle函式在彩色影像上用黃色標記我們要取值的點

color_image = cv2.circle(color_image,(320,240),1,(0,255,255),-1)

並在彩色影像上加上剛剛深度資訊的文字,範例中用紅色顯示。

color_image=cv2.putText(color_image, text_depth, (10,20),  cv2.FONT_HERSHEY_PLAIN, 1, (0,0,255), 1, cv2.LINE_AA)

輸入以下指令新增並編輯貼上以下程式碼

nano opencv_singlepoint_viewer_example.py   

完整程式碼如下:

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
 
pipeline.start(config)

align_to = rs.stream.color
align = rs.align(align_to)

try:
    while True:
        frames = pipeline.wait_for_frames()
        aligned_frames = align.process(frames)   
        depth_frame = aligned_frames.get_depth_frame() 
        color_frame = aligned_frames.get_color_frame()
        
        if not depth_frame or not color_frame:
            continue
     
        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())
 
        depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_WINTER)

#################加入這段程式#####################

        #print("shape of color image:{0}".format(color_image.shape))
        #print("shape of depth image:{0}".format(depth_colormap.shape))
        #print("depth value in m:{0}".format(depth_frame.get_distance(320, 240)))


        text_depth = "depth value of point (320,240) is "+str(np.round(depth_frame.get_distance(320, 240),4))+"meter(s)"
        color_image = cv2.circle(color_image,(320,240),1,(0,255,255),-1)
        color_image=cv2.putText(color_image, text_depth, (10,20),  cv2.FONT_HERSHEY_PLAIN, 1, (0,0,255), 1, cv2.LINE_AA)

##################################################

        images = np.hstack((color_image, depth_colormap))
        
        cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('RealSense', images)
 
 
        key = cv2.waitKey(1)
        if key & 0xFF == ord('q') or key == 27:
            cv2.destroyAllWindows()
            break
 
 
finally:
    pipeline.stop()

執行成果如下:

可以看到深度距離訊息在彩色畫面左上角,且畫面正中間有黃色標記的點。這個範例程式可以做很多距離相關的延伸應用,例如放在百米衝刺終點線前,偵測哪位選手最快通過終點、安裝在空拍機上做田間或果園巡視、或是搭配大型螢幕做成展場與參觀者互動的遊戲裝置都很有趣。

 

STEP3人臉辨識並取得臉部距離

想要做人臉辨識的話,把上一範例中插入的程式碼改成以下內容,就可以偵測人臉,並在人臉的方框左上方顯示人臉距離!

加入的第一行為取得人臉資料集,根據教學經驗,雖然設定相對路徑比較簡潔優雅,但設定絕對路徑比相對路徑的成功率高,不需要確認目前資料夾位置,任意移動檔案時也不會出問題。有興趣的人可以玩看看haarcascades資料夾下的其他資料集。

face_cascade = cv2.CascadeClassifier('/home/your_user_name/opencv/data/haarcascades/haarcascade_frontalface_default.xml')

下一步將影像灰階化後比較方便偵測

gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

接著設定人臉偵測的參數

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(50,50))

每一張人臉都畫方框後標記深度距離。在這段範例程式中,我們設定偵測人臉方框正中央代表人臉與鏡頭的距離。顯示深度的字串位置如果直接設(x, y)會跟方框重疊在一起,所以將y修改為y-5讓文字高於方框。有任何顏色、粗細、字型、字體等喜好都可以自行做調整。

for (x, y, w, h) in faces:
    cv2.rectangle(color_image, (x, y), (x+w, y+h), (255, 0, 0), 2)
    text_depth = "depth is "+str(np.round(depth_frame.get_distance(int(x+(1/2)*w), int(y+(1/2)*h)),3))+"m"            
    color_image = cv2.putText (color_image, text_depth,(x, y-5), cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),1,cv2.LINE_AA) 
 

完整程式碼如下:

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)
 
align_to = rs.stream.color
align = rs.align(align_to)

try:
    while True:
        frames = pipeline.wait_for_frames()
        aligned_frames = align.process(frames)   
        depth_frame = aligned_frames.get_depth_frame() 
        color_frame = aligned_frames.get_color_frame()
        if not depth_frame or not color_frame:
            continue
 
        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())

        depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)

        ################加入這段程式##################
        face_cascade = cv2.CascadeClassifier('/home/dlinano/opencv/data/haarcascades/haarcascade_frontalface_default.xml')

        gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(50,50))

        for (x, y, w, h) in faces:
            cv2.rectangle(color_image, (x, y-5), (x+w, y+h), (255, 0, 0), 2)
            text_depth = "depth is "+str(np.round(depth_frame.get_distance(int(x+(1/2)*w), int(y+(1/2)*h)),3))+"m"
            color_image=cv2.putText(color_image,text_depth,(x,y-5),cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),1,cv2.LINE_AA)
        ###############################################

        images = np.hstack((color_image, depth_colormap))
        
        cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('RealSense', images)
 
 
        key = cv2.waitKey(1)
        
        if key & 0xFF == ord('q') or key == 27:
            cv2.destroyAllWindows()
            break
 
 
finally:
    pipeline.stop()

首先我們要先下載OpenCV人臉資料集

輸入以下指令回到家目錄並且下載OpenCV資料集

cd ~
git clone https://github.com/opencv/opencv.git

下載完之後移動回範例資料夾並執行以下指令新增並編輯貼上剛剛的程式碼

cd ~/librealsense/wrappers/python/examples
nano opencv_facedistance_viewer_example.py

執行人臉辨識及距離偵測的程式

python3 opencv_facedistance_viewer_example.py

執行成果如下:

戴口罩也可以偵測得到人臉,並且可以偵測到該臉離鏡頭的距離,可是只有某些特定角度時比較容易偵測的到,五官的輪廓度會影響到偵測的效果,不戴口罩的偵測效果較佳。

一人版本:

多人版本:

由於這篇的教學是入門教學,僅取人臉偵測方框的中間點深度值代表整臉。想要進一步練習的人,可以試著將整個方框的所有像素深度值去極值後取平均,會有更精確的效果。或是去除背景及偵測失敗的像素後再取平均值也是好方法。

以上是Intel 深度影像攝影機應用-在NVIDIA Jetson Nano上使用RealSense D435-單點深度資訊和人臉辨識及距離偵測的範例應用改寫,大家有沒有執行成功呢?是不是很有趣呢? 如果你也有做出其他有趣的範例歡迎跟我們分享。

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結 


【Arduino首次接觸就上手】使用Arduino網頁編輯器竟然也會通!

$
0
0

「Arduino首次接觸就上手」套件也能透過網頁編輯器寫程式互動喔,如此一來,就算電腦裡面沒有安裝Arduino IDE,只要可以上網,就可以隨時隨地寫程式了呢!

撰寫/攝影 郭皇甫
難度

★☆☆☆☆

時間

30min

材料表

 

在本篇文章中,我們要來教大家如何使用Arduino網頁編輯器上傳程式至「Arduino首次接觸就上手」套件。其實相當容易,我們只需要註冊帳號、安裝Arduino Agent程式、選擇開發板型號,就可以使用囉!接下來,就請你跟著我們一步一步進行吧。

 

Step1。進入Arduino網頁版並註冊帳號。

在Google頁面搜尋Arduino後,進入Arduino的首頁,會看到右上角有「SIGN IN」的字樣,點擊後可以開始註冊。

圖1.在Arduino首頁右上角點擊「SIGN IN」註冊。

Arduino提供了新帳號的註冊,以及如果你已有Google帳號或是Apple的帳號,也可以直接登入使用,真的是相當方便。

圖2.建立一個帳號或是使用現有的Google或Apple帳號登入。

 

Step2.下載安裝Arduino Agent。

登入帳號後,我們會回到Arduino的首頁,你可以看到在網頁右上角會有登入帳號的頭像,在頭像的左手邊有選單按鈕,點擊後可以選擇「Web Editor」。

圖3.點擊右上角進入網頁編輯器。

進入網頁編輯器後,會看到熟悉的介面,基本上與Arduino IDE類似,不過你會發現上方多了一條黃色的警告標語,這是因為我們還未安裝Arduino Agent。點擊LEARN MORE進行下一步。

圖4.點擊LEARN MORE

這裡就會出現一些提示,如果Agent無法正常運作,有可能是你的防火牆或是防毒軟體把它在外面,你可能要從防護的清單中把它移除掉,或是允許它可以執行。

圖5.點擊INSTALL THE AGENT進入下載頁面。

進入下載頁面時,Arduino也告訴你這個Agent安裝程式主要提供了哪些功能。

圖6.按下START。

 

在這裡可以根據個人電腦的位元數來選擇適合自己電腦的版本。

圖7.選擇適合自己電腦的位元版本。

下載完成後,點擊執行開始安裝,可以自訂安裝的資料夾路徑,後續都是點擊Next。

圖8、9、10、11執行安裝步驟

到這裡按下Finish後,整個程式就已經安裝完畢。

圖12.按下Finish完成安裝。

當安裝完成後,在你的電腦工作列右下角會出現Arduino Agent的圖示,這表示它已經在正常運作。

圖13.按下NEXT進行下一步。

接下來就可以跳回一開始的網頁編輯器介面。

圖14.點擊返回網頁編輯器頁面。

 

你可以看到原本的黃色警告標語已經不見了,這就表示已經可以正常運作。

圖15.安裝完成無黃色警告標語。

 

 

Step3.選擇開發板型號。

接下來請使用microUSB線連接開發板與電腦,這時候我們就要先選擇開發板的型號與序列埠。

圖16.點擊選擇開發板與連接埠。

在這裡的開發板型號選擇「Arduino UNO」後,確認連接埠是正確的,就可以按下OK。

圖17.選擇Arduino UNO。

 

Step4.測試上傳第一個範例。

一切準備就緒後,我們就可以打開範例程式並上傳囉!網頁版的Arduino範例其實跟Arduino IDE是差不多的,你可以從左方的Example->01.Basics裡找到Blink程式。開啟並上傳後,你就可以看到開發板左下方內建的LED每隔一秒閃爍。

圖18.上傳範例。

 

是不是很簡單又方便呢?如果要使用「Arduino首次接觸就上手」套件上的感測器,例如我們要使用「Blink」讓D4腳位的紅色LED閃爍,那麼只需要將程式內所有的LED_BUILTIN更改為4即可。

 

好啦,今天的介紹就到這邊,趕快試著動手讓感測器都動起來吧!

【開箱評測】Jetson Nano Wi-Fi dongle多款實測結果

$
0
0

「Arduino首次接觸就上手」套件也能透過網頁編輯器寫程式互動喔,如此一來,就算電腦裡面沒有安裝Arduino IDE,只要可以上網,就可以隨時隨地寫程式了呢!

撰寫/攝影 郭俊廷
難度

★★☆☆☆

材料表

 

有在使用Jetson Nano的朋友要上網時大部分都會使用到 Wi-Fi dongle,這裡我們就測試了幾隻Wi-Fi dongle並把可以在Jetson Nano B01上使用的Wi-Fi dongle紀錄起來並寫出各個廠牌的數據和比較。

如果各位有推薦的WIFI dongle可以分享給我們或是寄給我們幫忙測試歐!(求贊助實測)

我們共測試了約八款WIFI dongle其中發現以下幾款是可以使用的,尋找的依據是根據之前可用的WIFI dongle網卡晶片去尋找同型號晶片的WIFI dongle來測試。

以下共列出了可以使用的七款WIFI dongle。

我們使用Jetson Nano B01 Jetpack 4.4.1原生的映象檔來測試。

載點如下:https://developer.nvidia.com/jetson-nx-developer-kit-sd-card-image-441

如使用Jetpack 4.4.1以外的映象檔會導致部分Wi-Fi dongle無法使用或是另外安裝驅動才能使用的問題。

測試的另一個目的是為了讓我們Jetson Nano找到有5GHz的Wi-Fi dongle來使用

目前常用的Wi-Fi網路設備通常是使用Wi-Fi 4跟Wi-Fi 5,目前測試的這幾個Wi-Fi dongle也只支援到Wi-Fi 5,如有適合的Wi-Fi 6 dongle我們也會盡快測試。或是有人發現可以用在Jetson Nano的Wi-Fi 6 dongle麻煩各位留言通知我們或是寫信聯絡我們歐。

至於Wi-Fi 4、5、6的差別是甚麼可以看下表大概簡述一下。

Wi-Fi差別表

 

首先先來說一下測試結果,詳細資料可以根據下方Wi-Fi dongle測試列表來查看。

編號 廠牌&型號 可否在Jetson Nano上使用 價格(NTD) 實際測試網路速度
1 D-Link N150(DWA-127) 可直接使用 319 下載45M

上傳33M

2 TOTOLINK N150UA 可直接使用 279 下載46M

上傳34M

3 TOTOLINK N300UM 可直接使用 249 下載5M

上傳0.49M

4 EDIMAXN300 長距離高速USB無線網路卡 EW-7822UAn 可直接使用

(長時間使用會斷線)

499 下載23M

上傳21M

5 ASUS AC1200 USB Nano雙頻無線網卡 可(Jetpack 4.4.1要額外安裝網路上非原廠

驅動)

699 2.4GHz

下載94M

上傳93M

5GHz

下載207M

上傳109M

6 TP-LinkAC600高增益 USB 無線雙頻網路卡Archer T2U Plus 可(Jetpack 4.4.1以上可直接使用,Jetpack4.3需額外安裝網路上非原廠驅動) 399 2.4GHz

下載57M

上傳52M

5GHz

下載241M

上傳108M

7 TP-LINK TL-WN722N 150M高增益USB無線網路卡 可直接使用 279 下載:46M

上傳:26M

以上價格以PChome24h購物網站20210330價格為基準,日後如有變動恕不更新

Wi-Fi dongle測試列表

 

編號1、2號D-Link N150跟TOTOLINK N150UA價錢跟速度上都差不多,也是我們套件包之前配置的兩隻網卡。

編號3號TOTOLINK N300UM可以發現實際測試網路速度很慢,測試時發現在WIN10電腦測速時速度是正常的但在Jetson Nano裡測試速度明顯慢上許多,這可能是他在ubuntu上的軟體部分沒有寫好導致速度無法正常使用。

編號4號EDIMAXN300長距離高速USB無線網路卡則是速度稍微慢一點且他的dongle本體天線部分稍微短一點,這個盒子裡還有一個延長線可以根據天線訊號延長或轉向使Wi-Fi dongle更方便使用。但我長時間在Jetson Nano上使用會發生很容易斷線的問題,所以不推薦這支在Jetson Nano上使用,這個部分麻煩也有使用這支天線的朋友可以幫忙在Jetson Nano上測試看看。

編號5號可以發現網路速度最快的是ASUS AC1200 USB Nano雙頻無線網卡,但價格也最高,而且還需要另外安裝驅動才可以使用,對於初學者來講不適合使用。

CP值最高推薦使用的是編號6號TP-Link AC600高增益 USB 無線雙頻網路卡,有雙頻可以使用價位也不會太高,實測時發現Jetpack 4.4.1可直接使用,如果想在Jetpack4.3使用需額外安裝網路上非原廠驅動才可以使用。

最後是TP-LINK TL-WN722N 150M高增益USB無線網路卡,這支直接插入Jetson Nano上即可使用,不用另外安裝驅動。但此款只支援2.4GHz頻段,所以價格也比較低,另外要注意的是此款dongle寬度較寬使用時可能會擋住另一個USB裝置,所以需要接上一個USB 90度轉接頭會比較方便使用。

對外連線速度測試我們統一使用NTU Speed 台大測速這個網站來測速(網址如下)

http://speed5.ntu.edu.tw/speed5/

 

進入網頁,按下中間的開始即會開始測試下載及上傳的速度(如下圖紅框處)

底下我們分享幾個實際利用這網站測試的速度圖

TP-LinkAC600 2.4GHz的速度 下載57M上傳52M

TP-LinkAC600 5GHz的速度下載241M上傳108M

 

今天的測試就在這邊告一段落,如有其他可以在Jetson Nano上用的Wi-Fi dongle,歡迎在下方留言一起討論分享歐。

Arduino首次接觸就上手 –藍牙4.0篇01_控制LED亮滅

$
0
0
撰寫/攝影 曾吉弘
難度

★★★☆☆

時間

2hrs

材料表

【雙A計劃】是 CAVEDU blog 上非常熱門的文章之一,使用 Arduino 搭配 HC05/06 等傳統藍牙通訊模組來與 App Inventor 通訊。但隨著藍牙4.0逐漸普及,後續的開發板例如 LinkIt 7697 ,甚至 Raspberry Pi 等單板電腦也直接在板子上整合了藍牙BLE晶片。

本篇將比照【雙A計劃】Part1App Inventor 經由藍牙控制 Arduino LED 亮滅,但改用[Arduino 首次接觸就上手]電子教學套件,搭配 HC10 藍牙模組來介紹如何用 Android 手機控制 Arduino的LED 亮滅。

關於[Arduino 首次接觸就上手]電子教學套件,我們已經寫了非常完整的基礎教學,您可以選用 Arduino 類C語法或專用的 HangeekDuino 圖形介面來開發。本系列文章的範例程式請由 Github 下載 (EX01_controlLED)

中文系列文章

English version

範例影片

 

硬體

HC10藍牙模組包含了以下腳位:

請把 HC10 藍牙模組搭配 Grove 接頭接上 [Arduino首次接觸就上手] 的 D7 Grove port,實際上裏面包含了GND, Vcc, D7, D8 等四支腳位,它一樣是一片 Arduino UNO,只是把常見的電路模組整合在同一片電路板上,無須再使用麵包板。但也正因如此,多數腳位已被占用,例如LED已連接到D4,除非將 LED 從板子上拆下來,否則無法作為其他用途。因此本範例使用 [Arduino首次接觸就上手] 尚未被占用的腳位D7與D8做為通訊腳位。

如果您使用傳統獨立運作的 Arduino,請把 HC10 的RX, TX 依序接到 Arduino 的 D7, D8 腳位 (或其他腳位也可以,但請記得修改 Arduino 程式碼),並完成電源接腳VCC與GND的連接。

App Inventor 編寫簡易的藍牙訊息發送程式

萬丈高樓平地起,先從最簡單的LED亮滅開始吧。這個由 App Inventor 編寫的 app 可以搜尋鄰近的藍牙BLE裝置、進行藍牙連線/斷線,並運用兩個按鈕發送不同的字元給 Arduino,Arduino 就會根據接收到的字元執行對應的動作。別小看這個 app,可以延伸出非常多應用呢!

Designer畫面編排

畫面元件很簡單,點選 [Scan] 之後,會掃描手機附近的藍牙BLE裝置,並顯示於 ListView 中。本範例的 HC10 的預設名稱會叫做 HMSoft XX:XX:XX ,後面12碼為藍牙位址。

點選 [Connect] 連線,連線成功之後畫面會顯示相關訊息。這時可以點選 [LED ON] 與 [LED OFF],這時候  [Arduino首次接觸就上手] 的 D4 LED 應該會隨之亮暗。

操作完畢之後請點選 [Disconnect] 斷開藍牙連線。

blocks程式設計

STEP1:

在 ButtonScan.Click 事件中,使用 BluetoothLE.StartScanning 來掃描鄰近的藍牙裝置。如果有掃描到任何藍牙BLE裝置,會呼叫 BluetoothLE.DeviceFound事件,並把 DeviceList 放到 ListView (已改名 ListBLE中)

在 ButtonStopScan.Click 事件中,使用 BluetoothLE.StopScanning 來停止掃描

SERVICE_UUID / CHARACTERISTIC_UUID 維持預設值就好,無須修改。

STEP2:

按下 [ButtonConnect] 按鈕時,使用 BluetoothLE.Connect 來對所選擇的藍牙裝置連線,這是一種常見的作法。您也可以改用 ConnectWithAddress 來直接對已知位址的藍牙裝置來連線。

連線成功會自動呼叫 BluetoothLE.Connected 事件並執行其內容。

STEP3:

按下 [Button_on] 按鈕時,會透過 BluetoothLE.WriteBytes 送出一個字元 “o”;另外則是在按下 [Button_off] 按鈕時送出一個字元 “f”。

這裡的做法其實相當多元,您可以改用 WriteInteger / WriteString 等語法試試看,當然也要 修改 Arduino 端的程式碼。

STEP4:

按下 ButtonDisconnect 斷線按鈕則中止藍牙連線,並將個畫面元件回復到初始狀態等候下一次連線。

斷線完成,會自動呼叫 BluetoothLE.Disconnected 事件並執行其內容。

Arduino的CODE

Arduino 程式碼也相當簡單易懂,設定好 HC10 模組所連接的腳位(D7與D8)之後,就會等候是否有資料送進來,並執行對應的動作。

您可以將本範例視為基礎樣板延伸出更多功能,快點試試看吧!

#include <SoftwareSerial.h>
SoftwareSerial HM10(7, 8); // RX, TX
char appData;
String inData = "";
void setup(void)
{
  Serial.begin(9600);
  Serial.println("HM10 serial started at 9600");
  HM10.begin(9600); // set HM10 serial at 9600 baud rate
  pinMode(4, OUTPUT); // onboard LED
  digitalWrite(4, LOW); // switch OFF LED
}

void loop(void)
{
  HM10.listen();  // listen the HM10 port
  while (HM10.available() > 0) {   // if HM10 sends something then read
    appData = HM10.read();
    inData = String(appData);  // save the data in string format
    Serial.println(appData);
  }

  if (Serial.available()) {  // Read user input if available.
    delay(10);
    HM10.write(Serial.read());
  }
  if ( inData == "f") {
    Serial.println("LED OFF");
    digitalWrite(4, LOW); // switch OFF LED
    delay(500);
  }
  if ( inData == "o") {
    Serial.println("LED ON");
    digitalWrite(4, HIGH); // switch OFF LED
    delay(500);
  }
}

實際執行

請先確認 HC10 模組已經接好,燒錄上述 arduino 程式碼到 [Arduino首次接觸就上手] 套件,再安裝 app inventor 到您的 Android 裝置上。根據上述影片來操作,即可根據 app 按鈕來控制 D4 LED 亮暗。

注意:App Inventor 已於 2021 年上半年登陸 iOS 平台,但到本文編寫日期,BLE 功能還在測試中。

 

相關文章

TP-Link Archer T2U Plus AC600無線雙頻網路卡,在JetsonNano JetPack 4.3驅動安裝教學

$
0
0

由於NVIDAI的Getting Started with DeepStream for Video Analytics on Jetson Nano課程,使用的映像檔是使用JetPack 4.3,如果要使用我們附上的WIFI dongle:TP-Link Archer T2U Plus AC600需要另外安裝驅動,以下是安裝教學。

撰寫/攝影 CAVEDU教育團隊 技術團隊
難度

★☆☆☆☆

STEP1.下載驅動相關程式

這裡使用的映象檔是 DeepStream課程所提供的映象檔

首先我們要先去我們的dropbox下載我們整合好的AC600需要的相關程式及驅動

https://www.dropbox.com/sh/v0486l28665pldr/AAAmkMPRc4zIblcft62-Xy9La?dl=0

 

這裡介紹接上螢幕、鍵盤滑鼠的操作方法

下載後插入USB將AC600複製放入家目錄(如下圖)

 

AC600資料夾裡的檔案內容如下圖

如果不是使用螢幕、鍵盤滑鼠的操作方法可以使用Micro USB連線

利用mobaxterm的SSH連線至192.168.55.1可以使用dlinano帳號登入(密碼一樣是dlinano)

接著透過拖曳的方式利用mobaxterm直接傳送檔案至JetsonNano裡面

STEP2.刪除dpkg設定檔

接著我們要刪除dpkg的設定檔,好讓我們等等安裝驅動不會發生錯誤,如沒有刪除時安裝rtl8812au驅動時會發生設定檔鎖住無法設定等訊息。

錯誤訊息如下
E: Could not get lock /var/lib/dpkg/lock-frontend – open (11: Resource temporarily unavailable)

E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?

 

執行以下指令刪除兩個設定檔,如遇到需輸入密碼的提示請輸入dlinano

sudo rm /var/lib/dpkg/lock-frontend
sudo rm /var/lib/dpkg/lock

指令操作畫面如下圖

STEP3.安裝DKMS相關套件

先移動到AC600資料夾

接著執行指令sudo dpkg -i *.deb 安裝DKMS的相關套件

cd AC600/
sudo dpkg -i *.deb

STEP4.安裝AC600相關驅動

接著要安裝AC600的驅動rtl8812au

首先先移動至AC600資料夾裡面的rtl8812au資料夾

接著執行指令sudo make dkms_install安裝rtl8812au相關驅動

cd rtl8812au
sudo make dkms_install

STEP5.重開機,檢查有無安裝成功

最後重開機,查看相關驅動有無安裝成功

執行reboot重開機

執行dkms status檢查8812au等相關驅動有無安裝成功,成功者如下圖

會出現以下訊息:8812au, 5.6.4.2_35491.20191025, 4.9.140-tegra, aarch64: installed

reboot
dkms status

這時候就可以將TP-Link Archer T2U Plus AC600的WIFI dongle接上你JetsonNano了。

可以至系統右上角查看有無可連線的WIFI訊號,連線至你想連線的WIFI。

有正常連線至你的WIFI代表你的驅動安裝成功,恭喜你可以使用此WIFI dongle了。

 

【分享】防疫不停學,CAVEDU線上資源總整理

$
0
0
撰寫/攝影 怡婷/圖片來源:各網站截圖

隨著疫情逐漸升溫,雙北市政府發布了從5/18起高中以下全面停課,並且啟動遠距教學的機制,而在停課不停學的條件之下,讓在家的孩子們能夠防疫兼具學習是首要的課題之一,小編彙整了關於學習程式、AI、物聯網相關的教學資源,提供給大家做為參考喔~

初學程式設計、物聯網

LinkIt 7697搭配洞洞么教學材料包自學套件

使用BlocklyDuino的軟體以拖拉程式積木來寫程式,讓學習寫程式變得輕鬆無負擔,並且能夠再加上LinkIt Remote app與MCS物聯網平台來做更多延伸進階的聯網專題喔!大家來一起自學吧!

  • 電子互動專題
  • 圖形化程式介面好上手
  • 手機藍牙遙控
  • MCS 雲端物聯網應用
  • 套件感測器評測把關

洞洞么教學材料包相關線上平台:

 

Arduino首次接觸就上手

在學習新事起步總是相當困難,對於Arduino初學者來說也是一樣。需要學習硬體知識、程式設計、了解各種接線的連接方式,甚至需掌握焊接技巧;在開始學習Arduino程式設計之前,需要準備的事情很多……

現在只需要擁有《 Arduino首次接觸就上手》就能夠解決您的前期準備的問題喔!

  • 只需先專注程式設計與Arduino的學習,不須在學習初期就要應付繁雜的準備工作。
  • Arduino為控制核心,帶您由基礎學習入門到專題延伸,
  • 操作簡單且無需使用麵包板即可完成專題應用。
  • 讓您輕鬆自學,不卡關!!

Arduino相關線上平台:

BOSON電子積木

不需看著螢幕,就能學數位邏輯。

BOSON將AND、OR、NOT等邏輯運算子實體化,只要接接線、就能體驗學習寫程式最重要的基礎概念之一;可與樂高積木完美搭配,也可以透過螺絲固定在您的作品上。

AI人工智慧

 

LinkIt 7697

NVIDIA Jetson Nano

小尺寸。優惠價格。重大的人工智慧發現。

NVIDIA® Jetson Nano™ 開發套件是學習、打造和教授人工智慧與機器人的理想選擇。價格平易近人,專為創作者所打造。開發套件是邊做邊學的最佳工具,不僅提供熟悉的 Linux 環境和容易遵循的教學流程,還請活躍社群特別設計,具備可立即建置的開放原始碼專案。

 

樹莓派 RASPBERRY PI 4

Intel RealSense 深攝影機

 

Jetbot AI自駕車

希望以上收集的線上資源能夠幫助到您,如對線上課程有興趣的朋友,可協助填寫「線上課程問券」,有利於後續安排與規劃課程~

線上課程問券調查

使用NVIDIA Jetson 機器學習專案,結合Intel RealSense D435 景深攝影機進行物件偵測與距離偵測

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

Jetson Inference範例是NVIDIA 提供給Jetson系列的邊緣裝置進行視覺影像識別的範例,主要的特色在於這些範例都特別強調以NVIDIA Jetson系列邊緣裝置內的GPU進行運算,在實測上也的確發揮了很好的影像識別效果,尤其在物件偵測(Object Detection)所使用的Detectnet範例更有效率的辨識效能,CAVEUD在本網站有詳細的安裝與測試教學文章(請參考連結)。

RealSense D435則是intel推出的RealSense系列產品之一,這系列的產品主要是以輸出影像深度資訊(測距)的應用為主,RealSense D435透過光學測距的方式進行深度資訊的探測,在應用上有著不錯的探測效果,在CAVEDU 亦有相關的安裝與測試教學(請參考連結)。

作者/攝影 曾俊霖
難度

★★☆☆☆(普通)

材料表
  • NVIDIA Jetson Nano 4GB版(建議64GB SD卡記憶體)
  • Intel RealSense D435(或D435i)
  • 筆電(以遠端操控時需要)
  • 螢幕、鍵盤、滑鼠(直接操作時需要)

影像物件偵測一直都是影像深度學習的重要應用之一,透過神經網路對於物件(Object)的種類進行辨識進而確認物件在影像中的直角座標位置,在辨識上僅能確認物件的影像方位,這樣的影像物件偵測由於缺少了物件與攝影機的相對距離,因此在更進一步的應用上便會受到一些侷限,例如:透過影像資訊控制機械手臂,自動夾取指定的物品、自駕車或無人飛行器精確的迴避障礙物或跟蹤控制。

因此本文將著重在物件與攝影機的相對距離應用在影像物件偵測的實作教學上,希望能夠透過這樣的實作教學讓每個使用者可以進一步的針對影像物件偵測進行測量距離,這次採用的微電腦平台是NVIDIA Jetson Nano 4GB嵌入式系統,與Intel的影像深度攝影機Realsense D435兩者進行整合,本次教學亦針對NVIDIA的GStreamer技術進行簡介,主要是因為NVIDIA的Jetson Inference相關應用,都是透過GStreamer進行影像資料的交換傳輸,透過GStreamer可以有效提升影像處理的效能。

 

一、Jetson Inference應用程式操作環境的安裝與設定

Jetson Inference程式操作環境的設定主要是會完成三個環境更新安裝與設定,分別是:

  1. Python程式的相依套件程式安裝(如:Pytroch等)
  2. Python程式的編譯與路徑參數設定
  3. 影像識別相關預訓練神經模型的安裝

有關Jetson Inference程式的安裝與設定,請參閱CAVEDU在本網站的網路教學文章進行安裝即可(請參考連結)。

CAVEDU的Jetson Inference教學文章(請參考連結)

本文所採用的主要的是detectnet這個範例應用為主,因此若使用者對於安裝空間有所斟酌的話,請優先完成這部分的安裝,請注意,本文建議盡可能將detectnet所需要用到的預訓練神經網路模型全部安裝,這樣可以避免未來在應用時產生補充安裝模型的問題。此外這裡的安裝會需要從網路下載大量程式與檔案,因此請務必確認網路傳輸環境在長時間運作下,能夠正常傳輸。

Jetson Inference的範例程式運作,主要是透過CUDA連結GPU的方式進行Tensor-RT的運算,也由於是透過GPU進行運算,在操作影像識別的神經網路運算時有著較佳的運作效能。透過GStreamer進行影像串流的處理,可以有效提升影像傳輸的效能,在Jetson Inference的神經網路運算時,也都是以GStreamer的方式進行影像資料的傳遞。

透過上述這兩方面的技術的整合,對於影像物件偵測是有非常重要的貢獻,根據實測在進行detectnet程式運作的時候,整體約莫會有25FPS的操作效能,這歸功於CUDA有效連結了GPU進行運算產生的效益。

 

二、intel Realsense D435深度影像應用程式操作環境的安裝與設定

Realsense相關應用程式操作環境的安裝與設定主要是針對以下三個事項進行設定,分別是:

  1. Python相依套件的安裝
  2. Realsense應用程式的編譯與路徑設定
  3. Realsense-viewer或Python程式的安裝

有關Realsense程式的安裝與設定,請參閱CAVEDU在本網站的網路教學文章進行安裝即可(請參考連結)。

CAVEDU的RealSense教學文章(請參考連結)

Realsense的影像資訊其格式必須經過numpy套件程式的轉換處理才能呈現即時影像,這部分的介紹將會在後續的介紹當中,透過片段程式的進行重點說明,Realsense在運作的時候基本上會有兩種不同內容的影像輸出,一個是標準RGB的影像輸出,另一個具有深度資訊的影像輸出,這兩個影像在應用時必須要注意「影像資訊對齊」,這主要的原因在於兩種影像在擷取的時候可能會有兩種不同的解析度的設定。在實際的硬體規格當中亦可以看出這兩種影像解析度的極限的確有所不同。

Intel Realsense D435原廠網站資料(請參考連結)

 

三、系統架構圖

從上圖可知,Realsense D435要透過Numpy套件程式才能將影像資訊轉換成OpenCV格式進行後續的影像處理,如:繪邊界框、標註文字與即時影像顯示等,而要進行Jetson Inference的detectnet運算之前,亦必須先透過CUDA轉換資料將OpenCV格式轉換成GStreamer格式才能進行detectnet神經網路運算,之後再將detectnet辨識後的結果,如:邊界框位置資訊、物件種類名稱,加入至RGB影像訊息內容中,最後與深度影像資訊進行對齊,如此便可以OpenCV進行整合後的影像呈現。

 

四、程式設計說明

<匯入相關套件>

  1. Jetson Inference 相關套件
    主要有inference、jetson.utils
  2. Realsense相關套件
    主要是pyrealsense2
  3. OpenCV相關套件
    主要是cv2
  4. Numpy數值運算相關套件
    主要是numpy
#!/usr/bin/python3

#

# Copyright (c) 2021, Cavedu. All rights reserved.

#


import jetson.inference
import jetson.utils

import argparse
import sys
import os

import cv2
import re
import numpy as np

import io
import time
import json
import random

import pyrealsense2 as rs

<程式外部參數設定>

  1. –network指定預訓練神經網路模型
  2. –threshold設定影像辨識閥值(多少以上才進行顯示)
  3. –width 設定影像寬度
  4. –height設定影像高度
# parse the command line

parser = argparse.ArgumentParser(description="Locate objects in a live camera stream using an object detection DNN.",
 formatter_class=argparse.RawTextHelpFormatter, epilog=jetson.inference.detectNet.Usage() +
 jetson.utils.logUsage())

parser.add_argument("--network", type=str, default="ssd-mobilenet-v2",
		help="pre-trained model to load (see below for options)")

parser.add_argument("--threshold", type=float, default=0.5,
		help="minimum detection threshold to use")

parser.add_argument("--width", type=int, default=640,
		help="set width for image")

parser.add_argument("--height", type=int, default=480,
		help="set height for image")

opt = parser.parse_known_args()[0]

設定jetson.inference的神經網路運算為detectnet,並且透過opt.network的外部參數進行設定預訓練神經網路模型。

# load the object detection network

net = jetson.inference.detectNet(opt.network, sys.argv, opt.threshold)

設定Realsense套件的起始條件,並且啟動Realsense套件程式。

# Configure depth and color streams

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, opt.width, opt.height, rs.format.z16, 30)
config.enable_stream(rs.stream.color, opt.width, opt.height, rs.format.bgr8, 30)

# Start streaming

pipeline.start(config)

由於啟動本程式會進行大量運算,而使微電腦的核心產生高熱,因此透過本行程式啟動散熱風扇進行降溫。

os.system("sudo sh -c 'echo 128 > /sys/devices/pwm-fan/target_pwm'")

讀取Realsense D435影像內容並且透過Numpy將資料轉成OpenCV格式。

depth_image:深度影像資訊

show_img:BGR影像資訊

press_key = 0
while (press_key==0):
	
# Wait for a coherent pair of frames: depth and color

	frames = pipeline.wait_for_frames()
	depth_frame = frames.get_depth_frame()
	color_frame = frames.get_color_frame()
	if not depth_frame or not color_frame:
		continue

	
# Convert images to numpy arrays

	depth_image = np.asanyarray(depth_frame.get_data())
	show_img = np.asanyarray(color_frame.get_data())

透過CUDA進行OpenCV的BGR影像格式轉換,並且將影像格式調整適用於神經網路運算的GStreamer格式(img)。

# convert to CUDA (cv2 images are numpy arrays, in BGR format)

	bgr_img = jetson.utils.cudaFromNumpy(show_img, isBGR=True)

	
# convert from BGR -> RGB

	img = jetson.utils.cudaAllocMapped(width=bgr_img.width,height=bgr_img.height,format='rgb8')

	jetson.utils.cudaConvertColor(bgr_img, img)

將img影像資訊傳入神經網路進行運算,並將結果存在detections中。

# detect objects in the image (with overlay)

	detections = net.Detect(img)

取得所有detections中所有已辨識出來的物件種類與位置資訊。

box_XXXXX:邊界框座標

score:辨識信心分數

label_name:物件種類名稱

	for num in range(len(detections)) :
		score = round(detections[num].Confidence,2)
		box_top=int(detections[num].Top)
		box_left=int(detections[num].Left)
		box_bottom=int(detections[num].Bottom)
		box_right=int(detections[num].Right)
		box_center=detections[num].Center
		label_name = net.GetClassDesc(detections[num].ClassID)

透過OpenCV進行每個被辨識出來的物件其邊界框繪製、中心準線繪製與標註辨識結果內容文字。

		point_distance=0.0
		for i in range (10):
			point_distance = point_distance + depth_frame.get_distance(int(box_center[0]),int(box_center[1]))

		point_distance = np.round(point_distance / 10, 3)

		distance_text = str(point_distance) + 'm'

		cv2.rectangle(show_img,(box_left,box_top),(box_right,box_bottom),(255,0,0),2)

		cv2.line(show_img,
			(int(box_center[0])-10, int(box_center[1])),
			(int(box_center[0]+10), int(box_center[1])),
			(0, 255, 255), 3)
		cv2.line(show_img,
			(int(box_center[0]), int(box_center[1]-10)),
			(int(box_center[0]), int(box_center[1]+10)),
			(0, 255, 255), 3)

		cv2.putText(show_img,
			label_name + ' ' + distance_text,
			(box_left+5,box_top+20),cv2.FONT_HERSHEY_SIMPLEX,0.4,
			(0,255,255),1,cv2.LINE_AA)

透過net.GetNetworkFPS擷取神經網路辨識的FPS值,並且透過OpenCV在即時影像畫面中標註其FPS數值內容。

	cv2.putText(show_img,
		"{:.0f} FPS".format(net.GetNetworkFPS()),
		(int(opt.width*0.8), int(opt.height*0.1)),
		cv2.FONT_HERSHEY_SIMPLEX,1,
		(0,255,255),2,cv2.LINE_AA)

透過OpenCV的resize函數進行顯示畫面的調整,並且進行即時影像的顯示。

	display = cv2.resize(show_img,(int(opt.width*1.5),int(opt.height*1.5)))

	cv2.imshow('Detecting...',display)

	keyValue=cv2.waitKey(1)
	if keyValue & 0xFF == ord('q'):
		press_key=1

關閉即時影像畫面,並且結束Realsense的串流影像傳送。

# 關閉所有 OpenCV 視窗

cv2.destroyAllWindows()
pipeline.stop()

五、程式執行方式

1.可透過MobaXterm以SSH方式連線進入Jetson Nano作業系統操作終端機,或是直接以HDMI螢幕、USB鍵盤滑鼠直接操作亦可。

2.操作本文測試程式之前,請務必要先將Jetson Inference (請參考連結)與Realsense(請參考連結),兩個相關程式安裝完成,缺一不可。

3.進入終端機指定資料夾中(本文為Realsense_Jetson_Inference)

cd ~
cd Realsense_Jetson_Inference

4.請確定測試程式存於本資料夾中,並請執行以下指令(預設為ssd-mobilenet-v2模型):

python3 detectnet_realsense.py

執行結果如下:

5.或指定其他預訓練神經網路模型,請執行以下指令(本文例為pednet模型):

python3 detectnet_realsense.py --network=pednet

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結(本篇文章完整範例程式請至原文下載)

NVIDAI Jetson Nano深度學習應用-使用OpenCV處理YOLOv4即時影像辨識

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

 

詳解darknet.py

首先我們先來詳解一下 darknet.py,由於他是由C去做封裝的所以比較難理解一些,但是他已經幫我們整理好一些Python的副函式可以使用,這邊將會一一介紹。

取得神經網路的輸入寬高 ( network_width、network_height )

如同標題所示,其中 lib會連結到darknetlib.so,是作者用c封裝好的函式庫,最後面會稍微介紹一下怎麼樣去查看各函式,這邊功能簡單就不多說了:

def network_width(net):
    return lib.network_width(net)

def network_height(net):
    return lib.network_height(net)

邊界框座標與標籤色彩 ( bbox2point、class_color)

由於神經網路模型輸出的是中心點的位置以及物件的寬高大小,這邊需要一個副函式來做轉換;接著通常物件辨識會變是一種以上的物件,所以通常會使用不同的顏色來做區隔,所以也提供了一個隨機色彩的副函式:

def bbox2points(bbox):
    """
    From bounding box yolo format
    to corner points cv2 rectangle
    """
    x, y, w, h = bbox
    xmin = int(round(x - (w / 2)))
    xmax = int(round(x + (w / 2)))
    ymin = int(round(y - (h / 2)))
    ymax = int(round(y + (h / 2)))
    return xmin, ymin, xmax, ymax

def class_colors(names):
    """
    Create a dict with one random BGR color for each
    class name
    """
    return {name: (
        random.randint(0, 255),
        random.randint(0, 255),
        random.randint(0, 255)) for name in names}

載入神經網路模型 ( load_network )

在執行命令的時候可以發現每一次都需要給予 data、cfg、weight,原因就在這個副函式上拉,在load_net_custom的部分會透過config ( 配置檔 )、weight ( 權重檔 ) 導入神經網路模型,這邊是他寫好的liberary我也不再深入探討;接著metadata存放 .data 檔案後就可以取得所有的標籤,這邊用Python的簡寫來完成,將所有的標籤都存放在陣列裡面 ( class_names ),metadata 的部分可以搭配下一列的coco.data內容去理解:

def load_network(config_file, data_file, weights, batch_size=1):
    """
    load model description and weights from config files
    args:
        config_file (str): path to .cfg model file
        data_file (str): path to .data model file
        weights (str): path to weights
    returns:
        network: trained model
        class_names
        class_colors
    """
    network = load_net_custom(
        config_file.encode("ascii"),
        weights.encode("ascii"), 0, batch_size)
    metadata = load_meta(data_file.encode("ascii"))
    class_names = [metadata.names[i].decode("ascii") for i in range(metadata.classes)]
    colors = class_colors(class_names)
    return network, class_names, colors
 

這邊可以稍微帶一下各個檔案的內容,下列是coco.data,這邊就不多作介紹了,應該都可以看得懂:

classes= 80
train  = /home/pjreddie/data/coco/trainvalno5k.txt
valid  = coco_testdev
#valid = data/coco_val_5k.list

names = data/coco.names
backup = /home/pjreddie/backup/
eval=coco

將辨識結果顯示出來 ( print_detections )

將推論後的結果顯示在終端機上面,如果要學習怎麼提取資料可以參考這個部分,在推論後的結果 ( detection ) 中可以解析出三個內容 標籤 ( labels)、信心指數 ( confidence )、邊界框 ( bbox ),取得到之後將所有內容顯示出來,這邊提供了一個變數是coordinates讓使用者自己確定是否要顯示邊界框資訊:

def print_detections(detections, coordinates=False):
    print("\nObjects:")
    for label, confidence, bbox in detections:
        x, y, w, h = bbox
        if coordinates:
            print("{}: {}%    (left_x: {:.0f}   top_y:  {:.0f}   width:   {:.0f}   height:  {:.0f})".format(label, confidence, x, y, w, h))
        else:
            print("{}: {}%".format(label, confidence))

將邊界框繪製到圖片上 ( draw_boxes )

將邊界框繪製到圖片上面,針對 bbox進行轉換 ( 使用 bbox2point ) 取得四個邊角座標,方便繪製邊界框使用 ( cv2.rectangle );接著要將標籤資訊給繪製上去 ( cv2.putText ),最後將繪製完的圖片回傳:

def draw_boxes(detections, image, colors):
    import cv2
    for label, confidence, bbox in detections:
        left, top, right, bottom = bbox2points(bbox)
        cv2.rectangle(image, (left, top), (right, bottom), colors[label], 1)
        cv2.putText(image, "{} [{:.2f}]".format(label, float(confidence)),
                    (left, top - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
                    colors[label], 2)
    return image

解析辨識結果並回傳 ( decode_detection )

這部分是待會加上GPIO互動最重要的環節了,他會解析detection並將各個職做好處理後回傳讓使用者去做後續的應用;這邊比較不常看到的是round( x, 2 ) 為取小數點後2位,乘以100則是轉換成百分比:

def decode_detection(detections):
    decoded = []
    for label, confidence, bbox in detections:
        confidence = str(round(confidence * 100, 2))
        decoded.append((str(label), confidence, bbox))
    return decoded

取得乾淨的辨識結果 ( remove_negatives )

這邊的目的是因為coco dataset有91種類別,但辨識出來的東西可能僅有3個,這樣就會有88個0,資料稍微肥大不好查看,所以她這邊提供一個副函式將所有有信心指數為0的給除去,留下有辨識到的物件:

def remove_negatives(detections, class_names, num):
    """
    Remove all classes with 0% confidence within the detection
    """
    predictions = []
    for j in range(num):
        for idx, name in enumerate(class_names):
            if detections[j].prob[idx] > 0:
                bbox = detections[j].bbox
                bbox = (bbox.x, bbox.y, bbox.w, bbox.h)
                predictions.append((name, detections[j].prob[idx], (bbox)))
    return predictions

進行辨識回傳結果 ( detect_image )

最重要的環節來了,這邊是主要推論的地方,將模型、標籤、圖片都丟進副函式當中就可以獲得結果了,這邊比較多C函式庫的內容,如果不想深入研究的話只需要知道透過這個副函式可以獲得predict的結果,知道這個點我們就可以客製化程式了:

def detect_image(network, class_names, image, thresh=.5, hier_thresh=.5, nms=.45):
    """
        Returns a list with highest confidence class and their bbox
    """
    pnum = pointer(c_int(0))
    predict_image(network, image)
    detections = get_network_boxes(network, image.w, image.h,
                                   thresh, hier_thresh, None, 0, pnum, 0)
    num = pnum[0]
    if nms:
        do_nms_sort(detections, num, len(class_names), nms)
    predictions = remove_negatives(detections, class_names, num)
    predictions = decode_detection(predictions)
    free_detections(detections, num)
    return sorted(predictions, key=lambda x: x[1])

進行辨識回傳結果 – 進階

這裡多是用C的函式庫內容,在程式的最頂端有導入了ctypes這個函式庫,這是個與C兼容的數據類型,並且可以透過這個函式庫調用DLL等用C建置好的函式庫,如果想要了解更多可以去include資料夾中尋找C的函式,並且在 darknet/src當中找到對應的內容。

舉例來說,我現在想了解get_network_boxes,先開啟 darknet/include/darknet.h的標頭檔進行搜尋:

接著可以看到一系列的副函式上方有 // network.h 的字樣,代表要去 /darknet/src/network.c中找到這個副函式的內容,注意程式內容是放在 .c 哦:

自己撰寫一個最易理解的yolov4即時影像辨識

礙於原本github提供的程式碼對於一些新手來說還是不太好理解,因為新手也比較少用到 Threading跟Queue,除此之外原本的darknet_video.py我在Jetson Nano上執行非常的卡頓 ( 原因待查證 ),所以我決定來帶大家撰寫一個較好理解的版本,使用OpenCV就可以搞定。

這個程式需要放在darknet的資料夾當中,並且確保已經有build過了 ( 是否有 libdarknet.so ),詳細的使用方法可以參考github或我之前的yolov4文章。

 

正式開始

最陽春的版本就是直接導入darknet.py之後開始撰寫,因為我直接寫即時影像辨識,所以還需要導入opencv:

import cv2
import darknet
import time

一些基本的參數可以先宣告,像是導入神經網路模型的配置、權重、資料集等:

# Parameters

win_title = 'YOLOv4 CUSTOM DETECTOR'
cfg_file = 'cfg/yolov4-tiny.cfg'
data_file = 'cfg/coco.data'
weight_file = 'yolov4-tiny.weights'
thre = 0.25
show_coordinates = True

接著我們可以先宣告神經網路模型並且取得輸入的維度大小,注意我們是以darknet.py進行客製,所以如果不知道load_network的功用可以往回去了解

# Load Network

network, class_names, class_colors = darknet.load_network(
        cfg_file,
        data_file,
        weight_file,
        batch_size=1
    )

# Get Nets Input dimentions

width = darknet.network_width(network)
height = darknet.network_height(network)

有了模型、輸入維度之後就可以開始取得輸入圖像,第一個版本中我們使用OpenCV進行即時影像辨識,所以需要先取得到Webcam的物件並使用While來完成:

# Video Stream

while cap.isOpened():
    
    
# Get current frame, quit if no frame 

    ret, frame = cap.read()

    if not ret: break

    t_prev = time.time()

接著需要對圖像進行前處理,在主要是OpenCV格式是BGR需要轉換成RGB,除此之外就是輸入的大小需要跟神經網路模型相同:

# Fix image format

    frame_rgb = cv2.cvtColor( frame, cv2.COLOR_BGR2RGB)
    frame_resized = cv2.resize( frame_rgb, (width, height))

接著轉換成darknet的格式,透過make_image事先宣告好輸入的圖片,再透過copy_image_from_bytes將位元組的形式複製到剛剛宣告好的圖片當中:

    
# convert to darknet format, save to " darknet_image "

    darknet_image = darknet.make_image(width, height, 3)
    darknet.copy_image_from_bytes(darknet_image, frame_resized.tobytes()) 

再來就是Inference的部分,直接調用 detect_image即可獲得結果,可以使用print_detections將資訊顯示在終端機上,最後要記得用free_image將圖片給清除:

# inference

    detections = darknet.detect_image(network, class_names, darknet_image, thresh=thre)
    darknet.print_detections(detections, show_coordinates)
    darknet.free_image(darknet_image)

最後就是將bounding box繪製到圖片上並顯示,這邊使用的是frame_resized而不是剛剛的darknet_image,那個變數的內容會專門用來辨識所使用,況且剛剛free_image已經將其清除了;用OpenCV顯示圖片前記得要轉換回BGR格式哦:

    
# draw bounding box
    image = darknet.draw_boxes(detections, frame_resized, class_colors)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

顯示之前計算一下FPS並顯示在左上角:

    # Show Image and FPS

    fps = int(1/(time.time()-t_prev))
    cv2.rectangle(image, (5, 5), (75, 25), (0,0,0), -1)
    cv2.putText(image, f'FPS {fps}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
    cv2.imshow(win_title, image)

程式的最後是按下小寫q離開迴圈並且刪除所有視窗、釋放Webcam的物件:

    if cv2.waitKey(1) == ord('q'):
        break
cv2.destroyAllWindows()
cap.release()

運行結果

結論

其實搞清楚darknet.py之後再進行客製化的修改已經就不難了,已經使用常見的OpenCV來取得圖像,如果想要改成視窗程式也很簡單,使用Tkinter、PyQt5等都可以更快結合yolov4;順道提一下,如果想要追求更高的效能可以使用Gstreamer搭配多線程或是轉換到TensorRT引擎上加速,可以參考之前的yolov4基礎介紹,後半段有提供TensorRT的Github。

 

相關文章

https://github.com/AlexeyAB/darknet

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結


NVIDIA Jetson Nano應用-多執行緒平行處理,以專案「你帶口罩了嗎?」為例

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

思路分析

原先使用主線程運行影像辨識以及IFTTT進行即時監控,光是執行影像辨識就會要等待推論的時間,而後如果要傳送至IFTTT則又有一個傳送的等待Request時間,如此便會影響到While迴圈裡的即時影像,這邊有很多種方法可以改善,最快且較為簡單的解決方式是將即時影像放到另一個線程中去運行,這樣顯示即時影像與推論的線程是同步進行的,即時影像就不會因此被推論以及等待網頁的時間給延遲,只需要專心處理即時影像的部分即可。

平行運算中的多執行緒

在Python的平行運算中有分兩種,一個是Multi-Thread另一個是Multi-Process;Process ( 進程 ) 跟Thread ( 執行緒 ) 其實大家平常都會聽到,在購買電腦的時候常常會聽到幾核幾緒 ( 例如 : 四核八緒 ) 就是類似的概念,幾個觀念重點介紹:

  1. 每個CPU都只能運行一個Process,每個Process彼此之間是獨立的。
  2. 每個Process可以有多個Thread運行,彼此共享記憶體、變數。

由於Thread無法回傳值所以要使用Queue ( 佇列 ) 去儲存資料,那這部分我就不多作介紹因為網路上已經有很多相關的參考了,不過,這邊我沒有使用queue的方式去撰寫程式。

增加即時影像的線程到程式中

我使用class的方式去寫因為可以直接省略queue去儲存、取得變數,算是一個偷吃步的小技巧,因為我這邊除了讀取幀之外就只有回傳的動作,應該不會導致搶資源或同步的問題。

客製化的即時影像物件

為了符合我們的需求,我客製了一個類別提供了幾個所需的功能,首先在initialize的部分,比較特別的地方在我使用了 isStop的參數用來中斷線程並且宣告了t為即時影像線程的物件。

# 客製化的影像擷取程式

class CustomVideoCapture():

    
# 初始化 預設的攝影機裝置為 0

    def __init__(self, dev=0):

        self.cap = cv2.VideoCapture(dev)
        self.ret = ''
        self.frame = []
        self.win_title = 'Modified with set_title()'
        self.info = ''
        self.fps = 0
        self.fps_time = 0

        self.isStop = False
        self.t = threading.Thread(target=self.video, name='stream')

接著先宣告了一些可以從外部控制線程的函式,像是 start_stream就是開啟線程;stop_stream關閉線程;get_current_frame就是取得當前的畫面,使用get_current_frame可以讓外部直接獲取線程更新的畫面,算是一個使用Thread運行OpenCV常用的方法;最後還提供了一個set_title可以修改視窗的名稱:

# 可以透過這個函式 開啟 Thread 

    def start_stream(self):
        self.t.start()
    
    
# 關閉 Thread 與 Camera

    def stop_stream(self):
        self.isStop = True
        self.cap.release()
        cv2.destroyAllWindows()

    
# 取得最近一次的幀

    def get_current_frame(self):
        return self.ret, self.frame

    def get_fps(self):
        return self.fps

    
# 設定顯示視窗的名稱

    def set_title(self, txt):
        self.win_title = txt

最後宣告了多線程要運作的函式,由於要不斷更新畫面所以使用while,透過isStop控制是否跳出迴圈,其中做的事情就是取得當前影像,設定要印上去的資訊並顯示出來,當按下q的時候會退出迴圈並且使用stop_stream終止迴圈:

# Thread主要運行的函式

    def video(self):
        try:
            while(not self.isStop):
                self.fps_time = time.time()
                self.ret, self.frame = self.cap.read()

                if self.info is not '':
                    cv2.putText(self.frame, self.info, (10,40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
                
                cv2.imshow(self.win_title, self.frame)
                
                if cv2.waitKey(1) == ord('q'):
                    break
                
                self.fps = int(1/(time.time() - self.fps_time))

            self.stop_stream()
        except:
            self.stop_stream()

我建立了一個tools.py存放所有會用到的副函式 ( 包含上述的客製化影像類別 ),這邊開始介紹其他副函式,preprocess專門在處理輸入前的資料,針對該資料進行縮放、正規化、轉換成含有批次大小的格式:

# 用於資料前處理的程式

def preprocess(frame, resize=(224, 224), norm=True):
    '''
    設定格式 ( 1, 224, 224, 3)、縮放大、正規化、放入資料並回傳正確格式的資料
    '''
    input_format = np.ndarray(shape=(1, 224, 224, 3), dtype=np.float32)
    frame_resize = cv2.resize(frame, resize)
    frame_norm =  ((frame_resize.astype(np.float32) / 127.0) - 1) if norm else frame_resize
    input_format[0]=frame_norm
    return input_format

load_model_folder則是載入模型與標籤,這邊寫成只需要輸入存放模型與標籤的目錄路徑即可,兩者須放置在一起,程式會靠副檔名去判斷:

# 讀取 模型 與 標籤

def load_model_folder(trg_dir) -> "'trg_dir' is the path include model file and labels file. return (model, label).":

    model_type = [ 'trt','engine','h5']
    label_type = [ 'txt']

    for f in os.listdir(trg_dir):
        extension = f.split('.')[-1]
        
        if extension in model_type:
            model_dir = os.path.join(trg_dir, f)
        elif extension in label_type:
            lable_dir = os.path.join(trg_dir, f)

    return get_model(model_dir), get_label(lable_dir)

剛剛輸出的時候有用到兩個副函式 get_model、get_label,分別去取得模型與標籤檔的物件:

# 讀取模型

def get_model(model_dir) -> "support keras and tensorrt model":
    
    if model_dir.split('.')[-1] == 'h5':
        print('Load Keras Model')
        model = tf.keras.models.load_model(model_dir)
    else:
        print('Load TensorRT Engine')
        model = load_engine(model_dir)
        
    return model
    

# 讀取標籤

def get_label(lable_dir) -> 'return dict of labels':

    label = {}

    with open(lable_dir) as f:    
        for line in f.readlines():
            idx, name = line.strip().split(' ')
            label[int(idx)]=name

    return label

# 讀取TensorRT模型

def load_engine(engine_path):

    if trt_found:

        TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
        trt_runtime = trt.Runtime(TRT_LOGGER)
        
        with open(engine_path, 'rb') as f:
            engine_data = f.read()
        engine = trt_runtime.deserialize_cuda_engine(engine_data)

        return engine
    else:
        print("Can not load load_engine because there is no tensorrt module")
        exit(1)

接著是解析預測結果的副函式,通常我們會取得到一組預測的信心指數,我們需要針對這組數據去解析出最大數值是在哪一個位置,而該位置又屬於哪一個類別:

# 解析輸出資訊

def parse_output(preds, label) -> 'return ( class id, class name, probobility) ':
    
    preds = preds[0] if len(preds.shape)==4 else preds
    trg_id = np.argmax(preds)
    trg_name = label[trg_id]
    trg_prob = preds[trg_id]
    return ( trg_id, trg_name, trg_prob)

截至目前為止的程式,我都將其放在tools.py裡,後續只要做import的動作即可將這些功能導入。

最後來到主程式的部分,這部分須要涵蓋IFTTT以及Inference,流程大致如下:

1.取得模型與標籤、開啟即時影像的線程:

# 取得模型與標籤

model, label = load_model_folder('keras_models')

# 設定影像擷取

vid = CustomVideoCapture()
vid.set_title('{sys} - {framework}'.format(sys='Jetson Nano', framework='Tensorflow'))
vid.start_stream()

2.設定辨識的參數,主要用於控制幾秒辨識一次 ( t_delay ),與上次辨識結果不同才進行傳送 ( pre_id ):

# 設定幾秒辨識一次,降低運行負擔

t_check = 0
t_delay = 2
t_start = 0
# 儲存上一次辨識的結果,如果改變才傳送,防止ifttt負擔太大

pre_id = -1

3.設定IFTTT的參數:

# 設定「Line訊息」資訊

event = 'jetsonnano_line'
key = 'i3_S_gIAsOty30yvIg4vg'
status = {
    0:['是本人', '確定有做好防疫工作'],
    1:['是本人', '注意,已成為防疫破口'], 
    2:['離開位置', ''], 
    3:['非本人', '注意您的財產']
    }

4.使用While不斷進行即時的辨識與LINE監控,這邊設定了如果大於預設的delay時間則進行辨識:

# 開始即時辨識

t_start = time.time()
while(not vid.isStop):

    
# 計算時間如果大於預設延遲時間則進行辨識與發送

    t_check = time.time() - t_start

    if (t_check >= t_delay) or ( not vid.fps):

        
# 取得當前圖片

        ret, frame = vid.get_current_frame()

        
# 如果沒有幀則重新執行

        if not ret: continue

5.進行推論以及取得辨識結果,最後設定顯示在即時影像上的資訊:

# 進行處理與推論

        data = preprocess(frame, resize=(224,224), norm=True)
        prediction = model(data)[0]
        
        
# 解析 辨識結果

        trg_id, trg_class, trg_prob =parse_output(prediction, label)

        
# 設定顯示資訊

        vid.info = '{} : {:.3f} , FPS {}'.format(trg_class, trg_prob, vid.get_fps())

6.如果辨識結果與上次的不同,則回傳給LINE:

        if pre_id != trg_id:
            
            ifttt.send_to_webhook(event, 
                                    key, 
                                    '環境變動', 
                                    status[trg_id][0], 
                                    status[trg_id][1] if status[trg_id][1] else '')
            pre_id = trg_id

        
# 更新 time

        t_start = time.time()

7.最後在While的外部需要確認一下Thread是否都有關閉了,寫多線程很常遇到的問題就是開了線程,但是忘記關閉導致資源被用完,所以做個DoubleCheck會是不錯的選擇:

# 跳出 while 迴圈需要檢查多線程是否已經關閉

time.sleep(1)
print('-'*30)
print(f'影像串流的線程是否已關閉 : {not vid.t.is_alive()}')
print('離開程式')

完整主程式如下:

#%%

import cv2
import threading
import os, time, random
import ifttt
import numpy as np
import tensorflow as tf
import platform as plt
from tools import CustomVideoCapture, preprocess, load_model_folder, parse_output
import time

# 取得模型與標籤

model, label = load_model_folder('keras_models')

# 設定影像擷取

vid = CustomVideoCapture()
vid.set_title('{sys} - {framework}'.format(sys='Jetson Nano', framework='Tensorflow'))
vid.start_stream()

# 設定幾秒辨識一次,降低運行負擔

t_check = 0
t_delay = 2
t_start = 0
# 儲存上一次辨識的結果,如果改變才傳送,防止ifttt負擔太大

pre_id = -1

# 設定「Line訊息」資訊

event = 'jetsonnano_line'
key = 'i3_S_gIAsOty30yvIg4vg'
status = {
    0:['是本人', '確定有做好防疫工作'],
    1:['是本人', '注意,已成為防疫破口'], 
    2:['離開位置', ''], 
    3:['非本人', '注意您的財產']
    }
#%%


                             
# 開始即時辨識

t_start = time.time()
while(not vid.isStop):

    
# 計算時間如果大於預設延遲時間則進行辨識與發送

    t_check = time.time() - t_start

    if (t_check >= t_delay) or ( not vid.fps):

        
# 取得當前圖片

        ret, frame = vid.get_current_frame()

        
# 如果沒有幀則重新執行

        if not ret: continue

        
# 進行處理與推論

        data = preprocess(frame, resize=(224,224), norm=True)
        prediction = model(data)[0]
        
        
# 解析 辨識結果

        trg_id, trg_class, trg_prob =parse_output(prediction, label)

        
# 設定顯示資訊

        vid.info = '{} : {:.3f} , FPS {}'.format(trg_class, trg_prob, vid.get_fps())

        
# 如果與上次辨識不同,則將辨識到的結果傳送至Line

        if pre_id != trg_id:
            
            ifttt.send_to_webhook(event, 
                                    key, 
                                    '環境變動', 
                                    status[trg_id][0], 
                                    status[trg_id][1] if status[trg_id][1] else '')
            pre_id = trg_id

        
# 更新 time

        t_start = time.time()

# 跳出 while 迴圈需要檢查多線程是否已經關閉

time.sleep(1)
print('-'*30)
print(f'影像串流的線程是否已關閉 : {not vid.t.is_alive()}')
print('離開程式')

可以發現使用Thread來運行影像就完全不會受到IFTTT的影響,FPS都可以維持在30甚至以上,而主線程只需要關注於辨識以及傳送資料給IFTTT即可。

使用TensorRT引擎加速推論

剛剛使用了Thread來改善IFTTT傳送卡頓的問題,我們也可以針對AI推論來做改善,我們使用Jetson Nano最大的優勢就在於可以使用TensorRT引擎加速處理,所以這邊教大家怎麼從Teachable Machine下載模型並轉換成TensorRT引擎。

概略介紹

TensorRT是一個支援NVIDIA CUDA核心的加速引擎,透過對神經網路模型進行重構與資料縮減來達到加速的目的,在Jetson Nano中使用TensorRT絕對是做AI Inference的首選,那如何將神經網路模型轉換成TensorRT去運行呢?

1.需要先將模型轉換成 Onnx 的通用格式

2.接著在轉換成 TensorRT 引擎可運作的格式

在Jetson Nano中已經帶有TensorRT轉換的工具,但是怎麼將模型轉換成Onnx還需要安裝額外的工具,所以我們先來安裝一下tf2onnx這個套件吧。

環境版本

JetPack 4.4.1
Python 3.6.9
pip 21.0
tensorflow 2.3.1+nv20.12
onnx 1.8.1

安裝 tf2onnx並將模型轉換成onnx

首先需要將tensorflow的模型轉換成onnx,我們將使用tf2onnx這個套件,在安裝之前需要先確保onnx已經被安裝了,這邊提供相依套件以onnx的安裝命令:

$ sudo apt-get install protobuf-compiler libprotoc-dev 
# onnx 相依套件

$ pip3 install onnx
$ pip install onnxruntime

升級numpy (可有可無):

$ python3 -m pip install -U numpy --no-cache-dir --no-binary numpy

安裝tf2onnx:

$ pip3 install tf2onnx

宣告OpenBLAS的核心架構,在JetsonNano上少了這步應該會報錯誤訊息” Illegal instruction(core dumped)”:

$ nano ~/.bashrc
export OPENBLAS_CORETYPE=ARMV8
$ source ~/.bashrc	

安裝完之後可以回到上次教學的Teachable Machine,這次要下載的檔案格式必須選擇成TensorFlow > Savemodel,如下圖所示:

Savemodel是Tensorflow模型「序列化」的格式,由於Onnx的格式也是序列化的,所以在一開始就轉換成Savemodel在後續轉換Onnx比較不容易出錯。我們可以使用執行下列指令轉換成onnx模型:

$ python3 -m tf2onnx.convert --saved-model ./savemodel --output ./test_opset_default.onnx

透過Jetson Nano內建工具轉換成TensorRT

接著可以使用JetsonNano的原生工具 (trtexec) 轉換成TensorRT:

$ /usr/src/tensorrt/bin/trtexec --onnx=/home/dlinano/TM2/test_opset_default.onnx --saveEngine=/home/dlinano/TM2/test.trt --shapes=input0:1x3x224x224

同時需要安裝pycuda,安裝步驟當中有一個nvcc是用來確認是否有抓到cuda,若沒有加入環境變數則會報錯,同時也無法安裝pycuda:

$ nano ~/.bashrc
export PATH=${PATH}:/usr/local/cuda/bin
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda/lib64
$ source ~/.bashrc
$ nvcc -V
$ pip3 install pycuda

由於我們會使用到tensorrt提供的範例common.py,所以先直接複製一份:

$ cp /usr/src/tensorrt/samples/python/common.py ./common.py

經過繁瑣的操作後,終於可以運行程式了:

$ python3 tm_tensorrt.py

這個程式比照上一篇的方法所撰寫,可以注意到FPS相較於之前的推論程式都高非常多,已經可以到順跑的程度了。

程式講解

導入函式庫以及設定TRT的基本參數

import cv2
import tensorrt as trt
import numpy as np
import common
import platform as plt
import time
from tools import preprocess, load_model_folder

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
trt_runtime = trt.Runtime(TRT_LOGGER)

先取得 TensorRT引擎,透過先前撰寫好的副函式 ( load_model_folder ) 來取得 engine、label;再導入之前我們需要預先定義好buffer給TensorRT;接著解析TensorRT物件取得該「執行文本」:

 load trt engine
print('取得TRT引擎與標籤')
engine, label = load_model_folder('tensorrt_engine')

# allocate buffers

print('分配 buffers 給 TensorRT 所須的物件')
inputs, outputs, bindings, stream = common.allocate_buffers(engine)

print('創建執行文本 ( context )')
context = engine.create_execution_context()

接著我們使用與上一篇雷同的OpenCV程式完成即時影像辨識,最大的區別在於TensorRT引擎導入資料的方法與推論的方法:

print('開啟即時影像')
fps = -1
cap = cv2.VideoCapture(0, cv2.CAP_GSTREAMER)

while(True):

    t_start = time.time()

    
# 讀取圖片

    ret, frame = cap.read()

    
# 將圖片進行前處理並放入輸入資料中

    inputs[0].host = preprocess(frame)

    
# 進行 Inference

    trt_outputs = common.do_inference(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream)
    
    
# 解析輸出資料

    trg_idx, trg_class, trg_prob = parse_output(trt_outputs[0], label)

    
# 設定顯示資料

    info = '{} : {:.3f} , FPS {}'.format(trg_class, trg_prob, fps)

    
# 將顯示資料繪製在圖片上

    cv2.putText(frame, info, (10,40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
    cv2.imshow('TensorRT', frame)

    if cv2.waitKey(1) == ord('q'):
        break
    
# 更新FPS與時間點

    fps = int(1/(time.time()-t_start))
    t_start = time.time()

最後離開的時候一樣要做確認的動作:

cap.release()
cv2.destroyAllWindows()
print('離開程式')

三種框架比較

既然都做到TensorRT加速了,我們還是得來比較一下速度差距(僅供參考):

可以注意到Tensorflow的速度最慢但是準確度最高;Tensorflow Lite則是犧牲準確度換取高效能的表現;而TensorRT就更優秀了,優化的時候保留更多準確度,效能也能有效提高。

TensorRT結合Thread與IFTTT

建構的方式與上述雷同,所以就直接提供完整程式:

import cv2
import tensorrt as trt
import numpy as np	
import common
import platform as plt
import time
import ifttt
import threading
from tools import CustomVideoCapture, preprocess, load_model_folder, parse_output

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
trt_runtime = trt.Runtime(TRT_LOGGER)

def main():

    pre_idx = -1

    print('取得TRT引擎與標籤')
    engine, label = load_model_folder('tensorrt_engine')

    print('分配 buffers 給 TensorRT 所須的物件')
    inputs, outputs, bindings, stream = common.allocate_buffers(engine)

    print('創建執行文本 ( context )')
    context = engine.create_execution_context()

    print('設定即時影像參數')
    vid = CustomVideoCapture()
    vid.set_title('{sys} - {framework}'.format(sys='Jetson Nano', framework='TensorRT'))
    vid.start_stream()
    
    
# 設定幾秒辨識一次,為了配合 ifttt 的延遲通知

    t_check = 0
    t_delay = 1
    t_start = 0

    
# 儲存上一次辨識的結果,如果改變才傳送,防止ifttt負擔太大

    pre_id = -1

    
# 設定「Line訊息」資訊

    print('設定IFTTT參數')
    event = 'jetsonnano_line'
    key = 'i3_S_gIAsOty30yvIg4vg'
    status = {
        0:['是本人', '確定有做好防疫工作'],
        1:['是本人', '注意,已成為防疫破口'], 
        2:['離開位置', ''], 
        3:['非本人', '注意您的財產']
        }

    t_start = time.time()
    
    while(not vid.isStop):

        
# 計算時間如果大於預設延遲時間則進行辨識與發送

        t_check = time.time()-t_start
        if t_check >= t_delay:
            
            ret, frame = vid.get_current_frame()
            if not ret: continue

            inputs[0].host = preprocess(frame, resize=(224, 224), norm=True)

            infer_time = time.time()
            
            
# with engine.create_execution_context() as context:

            trt_outputs = common.do_inference(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream)
            
            infer_time = time.time() - infer_time
            
            preds = trt_outputs[0]

            trg_id, trg_class, trg_prob = parse_output(preds, label)

            vid.info = '{} : {:.3f} , FPS : {:.3f}'.format(trg_class, trg_prob, vid.get_fps())

            if pre_id != trg_id:
                
                ifttt.send_to_webhook(event, 
                                      key, 
                                      '環境變動', 
                                      status[trg_id][0], 
                                      status[trg_id][1] if status[trg_id][1] else '')
                pre_id = trg_id

            t_start = time.time()
    
    
# 跳出 while 迴圈需要檢查多線程是否已經關閉

    time.sleep(1)
    print('-'*30, '\n')
    print(f'影像串流的線程是否已關閉 : {not vid.t.is_alive()}')

if __name__ == '__main__':
    
    main()

結語

這次我們使用了兩種方式來進行改造、加速,其實透過Thread就能有不錯的成果了,但是TensorRT又能再減少一些負擔,讓 AI辨識與Line的監控訊息可以變得更加確實、快速。

相關文章

Onnx-tensorrt Github

Program/Process/Thread 差異

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結(本篇文章完整範例程式請至原文下載)

NVIDIA Jetson Nano機器學習應用-你戴口罩了嗎?結合Line通訊平台進行即時監控

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

關於IFTTT

IFTTT 的全名為「If This Then That」,及是觸發了A事件之後就會做出B動作,基本上不用寫程式,也可以有幾百個服務可以進行串接,目前有三個免費API的額度,如果使用三個以上的API就要開始收費了。

釐清思緒

在開始之前先來確認一下本次目標,我們希望當辨識到特定物品的時候使用LINE進行即時通知。以IFTTT的角度來說就是「if 辨識到東西 then 傳送訊息到Line」,但是IFTTT沒辦法進行AI辨識,必須透過工具:Webhook, Webhook的功能是當特定網頁有被觸及的時候就會提醒對方,我可以在辨識到特定物品的時候觸及特定網頁並且上傳數值,這時候Webhook會收到回饋,啟動Line的傳送訊息功能,而Line的傳送訊息功能也可以接收剛剛上傳的數值。

申請IFTTT & 創建Applet

1.進入IFTTT首頁,選擇登入帳號或申請新帳號

2.請選擇一種帳號進行登入

3.右上角選擇選擇Create新服務

4.點選Add,進入服務設定。

5.搜尋 Webhooks 服務並點選該圖示

6.點擊 Receive a web request

7.創建事件名稱並點擊Create trigger 啟動輸入觸發。

8.接著點擊Then That 的 Add。

9.搜尋Line並且點擊LINE的icon

10.點擊Send Message

11.因為要連接到 LINE,需要進行登入LINE 的動作。

12.IFTTT透過 LINE Notify 的通知進行推播,選擇同意並連動

連動後會在LINE上彈出這段訊息

13.設定傳送 LINE 訊息的內容

可以選擇要推播到特定群組,或是一對一的 LINE Notify,再來是訊息格式,1個 Webhooks 可以傳送3個數值,後續會教大家怎麼修改。

14.點擊 Continue

15.輸入app的標題簡述,接著可以按Finish

16.完成之後的畫面如下,接著要點擊Webhook的圖標

17.點擊左上角的IFTTT圖案回到首頁,並且點擊Create by me就可以看到剛剛創建的APP

18.點擊右上角的 Documentation

19.進到文件狀態後修改內容進行測試,event欄位為剛剛Webhook創建的名稱 ( jetsonnano_line ),value隨意輸入。

執行結果可以到 Line Notify中查看

編輯Line的訊息格式

20.回到APP的頁面,點擊LINE的圖標

21.點擊 jetsonnano_line 的app方框

22.點擊右上角的setting

23.選擇Send Message右上角的edit

24.修改訊息資訊,變數可以從 Add ingredient選取,有EventName、三個Value以及 OccurredAt ( 最近的時間 ) 可以選擇。

25.回到Webhook的Documentation測試一下結果

到目前就已經完成基本的IFTTT設定了。

使用Python程式連動Line

解析Webhook

開始之前我們先解析一下剛剛在Webhook的Documentation。

Make a POST or GET web request to:

代表我們可以使用POST或GET的方式去觸及Webhook,其中GET通常是下載網頁資訊而POST則是上傳資料,但是在這邊事都可以通用的。

With an optional JSON body of:

他可以使用自定義的參數但是需要使用JSON格式的內容,強制使用類似Python的字典形式,Key的位置是固定的所以寫程式的時候要注意,Key後面的內容則是可以隨機定義的,而這部分就是可以更換成我們辨識的結果。

對這些參數稍微有一點概念了之後就可以開始寫程式囉!

實作Python Code

這邊需要使用到Python的套件 Request,來完成與網頁互動的功能。首先,我們先撰寫了一個副函式叫「send_to_webhook」,並且給予所有能夠修改的參數,像是網址的部分就要包含「事件名稱」、「金鑰」,而後面的引數總共有三個「數值」;利用post或get都可以只要能觸發Webhook的事件即可,我們可以使用”status_code”做個判斷,如果成功的話會回傳response 200,也可以直接使用“codes.ok”。

import requests

def send_to_webhook(event='', key='', val_1='', val_2='', val_3=''):   
    
    trg_url = ('https://maker.ifttt.com/trigger/{event}/with/key/{key}'.format(event=event, key=key))
    
    trg_params = {
        'value1': f'{val_1}',
        'value2': f'{val_2}',
        'value3' : f'{val_3}'
        }

    req = requests.post(trg_url, trg_params)

    if req.status_code == requests.codes.ok:
        print('傳送成功')
    else:
        print('傳送失敗')

接著就可以在外部修改參數了:

if __name__ == '__main__':

    event = 'jetsonnano_line'
    key = 'i3_S_gIAsOty30yvIg4vg'

    val_1 = '使用'
    val_2 = 'Python'
    val_3 = '連動'

    ret = send_to_webhook(event, key, val_1, val_2, val_3)

結合簡易的AI辨識

這部分可以結合各種形式,像是YOLOv4、Jetson Inference等API,在稍微修改一下即可,這邊我提供一個更簡單且我們常用的方法 Teachable Machine,這邊就不介紹太多了,他是一個線上的AI體驗工具。

除了線上訓練之外還可以提供Tensorflow、Tensorflow Lite以及Coral的模型,這邊我順便教大家怎麼去安裝 Tensorflow以及Tensorflow Lite。

使用Teachable Machine訓練

匯出Tensorflow模型

我們直接使用Keras模型即可,選擇Tensorflow並選擇Model conversion type為Keras,點選Download my model即可進行轉換與下載:

在JetsonNano中運行即時影像辨識

首先需要安裝Tensorflow,我們可以直接到這裡照個原廠推薦的方法操作,由於我的Nano 是JetPack 4.4.1,所以要找到 Python3.6 + JetsonPack 4.4的位置:

快速參考指令如下:

$ sudo apt-get install libhdf5-serial-dev hdf5-tools libhdf5-dev zlib1g-dev zip libjpeg8-dev liblapack-dev libblas-dev gfortran
$ sudo apt-get install python3-pip
$ sudo pip3 install -U pip
$ sudo pip3 install -U pip testresources setuptools numpy==1.16.1 future==0.17.1 mock==3.0.5 h5py==2.9.0 keras_preprocessing==1.0.5 keras_applications==1.0.8 gast==0.2.2 futures protobuf pybind11
# TF-2.x

$ sudo pip3 install --pre --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v44 tensorflow==2.3.1+nv20.12

安裝完之後我們需要使用Teachable Machine匯出的位置下方有提供範例程式,我稍微修改了一下。首先載入相關套件:

import tensorflow.keras
import numpy as np
import cv2
import time
import platform as plt

由於標籤檔是一個文字檔,我寫了一個程式去解析內容並儲存成一個字典:

def get_label(label_path):

    label = {}
    with open(label_path) as f:
        for line in f.readlines():
            idx,name = line.strip().split(' ')
            label[int(idx)] = name
    return label

接著進入主程式,設定np顯示的格式、載入神經網路模型與標籤;data是設定輸入資料形狀 (1, 224, 224, 3);最後fps用來計算即時影像每秒會有幾幀,通常fps越高越好,到了60對於人眼就幾乎感受不到延遲了;cap是儲存攝影機設備:

print('Setting ...')
np.set_printoptions(suppress=True)

print('Load Model & Labels ...')
model = tensorflow.keras.models.load_model('keras_models/model.h5')
label = get_label('keras_models/labels.txt')
data = np.ndarray(shape=(1, 224, 224, 3), dtype=np.float32)

print('Start Stream ...')
fps = -1
cap = cv2.VideoCapture(0)

使用While不斷讀取最近一次的影像;t_start用於計算fps;使用cap.read()會獲取兩個參數,第一個通常被用來代表是否擷取成功 ( 布林值 ),第二個則是影像陣列;接著將圖片丟入模型前還需要進行前處理,縮放大小至224×224,正規化,以及修改成輸入資料的格式:

while(True):

    t_start = time.time()

    ret, frame = cap.read()

    size = (224, 224)
    frame_resize = cv2.resize(frame, size)
    
    frame_norm = (frame_resize.astype(np.float32) / 127.0) - 1

    data[0] = frame_norm

接著使用predict即可完成推論,我們先取得到數值最大的欄位idx,並設定預計要顯示在畫面上的資訊 (result) 再使用 putText將資訊繪製到影像上,最後顯示出來 (imshow),標題可以自己修改;當按下q的時候可以離開,最後釋放攝影機物件並刪除所有視窗:

  prediction = model.predict(data)[0]    
  
  idx = int(np.argmax(prediction))

    result = '{} : {:.3f}, FPS {}'.format(label[idx], prediction[idx], fps)

    cv2.putText(frame, result, (10,40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0 , 0, 255), 1)

    cv2.imshow(win_title, frame)

    if cv2.waitKey(1) == ord('q'):
        break

    fps = int(1/(time.time() - t_start))

cap.release()
cv2.destroyAllWindows()

print('Quit ...')

執行結果如下:

可以看到FPS只有4,不過準確度還蠻高的!但是TensorFlow在JetsonNano上開啟需要一段時間,所以我會推薦轉換成TensorRT的形式,下篇會再介紹。

將辨識結果傳送給Line

剛剛程式當中已經取得了結果 ( results ),我們只需要將這個結果上傳到Line即可,稍早我們寫的ifttt程式將其儲存為ifttt.py方便導入,首先設定事件名稱與金鑰,status用來手動設定辨識結果對應的傳送內容;pre_idx則是用來儲存前一個辨識的結果:

import ifttt

def main(win_title='test'):

    
# 設定「Line訊息」資訊

    event = 'jetsonnano_line'
    key = 'i3_S_gIAsOty30yvIg4vg'
    status = {  0:['是本人', '確定有做好防疫工作'],
                1:['是本人', '注意,已成為防疫破口'], 
                2:['離開位置', ''], 
                3:['非本人', '注意您的財產']    }
    pre_idx = -1

接著將程式碼放到prediction的後面,send_to_webhook詳細說明請往上翻找:

# 判斷是否跟上次辨識一樣的結果,如果不同則進行上傳

if pre_idx != idx:

    ifttt.send_to_webhook(  event, 
                            key, 
                            '環境變動', 
                            status[idx][0], 
                            status[idx][1] if status[idx][1] else '')
    
    pre_idx = idx

執行結果

可以注意到如果沒有傳送資料的時候,FPS都還可以落在4左右,一旦傳送資料就會整co

結論

我們已經學會怎麼將JetsonNano與Line結合的方式了,下一篇我們將帶大家嘗試去將整個影像體驗的效果提升。

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結(本篇文章完整範例程式請至原文下載)

Microsoft Edge AI新方案 – Azure Percept 深色

$
0
0
撰寫/攝影 曾吉弘

Azure Percept 深色(中文名字真有趣)是一套 edge AI 開發工具組,專為使用 Azure Percept Studio 來開發視覺和音訊 AI 方案而設計。套件中搭配了具有攝影機的 Azure Percept Vision 並與 Azure 的各種 AI / IoT 邊緣服務整合。有語音需求的開發者,也可搭配 Azure Percept 音訊裝置,包含預先設定的音訊處理器和四個麥克風的線性陣列,藉由 Azure 認知服務的協助,使用語音命令、關鍵字在您的案場整合語音服務。

根據微軟原廠網頁說明,Azure Percept DK 是一套相當完整的開發套件,有許多立即可用的 AI 模型,視覺方面包含物件偵測、貨架分析、車輛分析等;語音方面則一定會有聲音控制與異常檢測。並標榜不需要程式碼就能針對個人需求客製化 AI 模型,在裝置端或在雲端都可完成。

 

以下皆引用微軟原廠資料:

主要功能

  • 在邊緣端執行 AI 功能。開發工具組搭配內建的硬體加速功能,可以執行 AI 模型,而不需要連線到雲端。
  • 內建的硬體根目錄信任安全性。
  • Azure Percept Studio 及其他 Azure 服務的緊密整合,例如 Azure IoT 中樞Azure 認知服務和即時影片分析。
  • 與 Azure Percept 音訊裝置相容(請看後續開箱),這是用來建立 AI 音訊解決方案的選擇性配件。
  • 支援協力廠商 AI 工具,例如 ONNX 和 TensorFlow。
  • 與 80/20 railing 系統整合,可讓裝置掛接設定無限。 深入瞭解 80/20 整合

硬體元件

1 Azure Percept dk 開發板:

2 Azure Percept 願景系統模組 (SoM) :

3 Azure Percept 音訊:

  • 即可使用的 Azure Percept 音訊裝置 (SoM) 搭配四個麥克風的線性陣列,並透過 XMOS 編解碼器進行音訊處理
  • 開發人員面板:按鈕x2、LED x3、微型 USB 和 3.5 mm 音訊插孔
  • 必要纜線: FPC 纜線與 usb 線等
  • 歡迎卡片
  • 具有整合式 80/20 1010 系列掛接的機械裝載板

 

開箱啦~

外盒僅標示了 Microsft Azure,相當簡潔

由左到右分別為 Developer board / Azure eye / Azure ear,就是開發板、攝影機(別忘了上面有寫已經整合了 Intel Movidius VPU在其中) 以及語音裝置。

我是歡迎卡片,寫著很大一句話:Unlock your Imagination,這句話我喜歡

盒子內東西都拿出來,請與上面的硬體規格來對照一下

1 開發板,兩支天線覺得很安心(沒收到訊號就GG了)

2 Azure Percpet Vision – 在歡迎卡片上叫做 Azure eye

3 Azure Percpet Audio – 在歡迎卡片上叫做 Azure ear

有附一個橫條,在上面可以自由固定位置,會附這個真的讓我覺得好貼心啊

上電之後就各自亮燈了,很不錯的感覺

有興趣的朋友歡迎先看這個影片:https://fb.watch/5SRDF_jMmB/

 

也可以先註冊一個 Azure 帳號來登入 Azure Percept Studio,畫面如下:

下一篇文章就會開始實作啦,是否和我一樣期待呢?

 

相關文章:

 

深入使用NVIDIA Jetson Inference機器學習專案 –電腦視覺圖片分類任務

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

安裝Jetson Nano指南,請參考原廠網站:

https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit#setup

下載映像檔:

https://developer.nvidia.com/jetson-nano-sd-card-image

 

Jetson Inference

這個是官方推出的體驗套件,它提供了三種最常見的AI應用於電腦視覺的類型,imagenet用於圖像辨識 ( Image Recognition )、detectNet用於物件辨識 ( Object Detection )、segNet用於語意分割。

API 的介紹再這裡,今天會想辦法盡量深入了解,以往的文章只有初步探討與實作,這次會帶到更多的細節,而我們今天都是使用 Python的程式執行。

建置環境

建置方法

我們跟著教學走會先遇到 Hello AI World,再這裡是教大家怎麼去建置 Jetson Inference的環境,有兩種使用方法:

  • 使用Docker Container ( 30 分鐘 )
  • 從來源建置 ( 1.5小時 )

通常為了教學順暢,會從頭建置燒錄SD卡,但有時映像檔過於龐大,搬運不易,所以使用 Docker來做相對而言會比較容易,如果想要從頭建置可以參考這邊。

今天我們使用Docker來運作,開始之前須要先將jetson Inference下載下來:

$ git clone --recursive https://github.com/dusty-nv/jetson-inference
$ cd jetson-inference
$ docker/run.sh

執行run.sh之後會自動根據你現在的JetPack版本去DockerHub抓取對應的容器 (Contrainer),並且如果你有使用攝影機的話這時候也會自動讀取

 

Docker Container

關於Docker Container的敘述這邊簡單介紹一下,可以想像是一個獨立的虛擬環境,使用者,進入容器之後會與你原生系統的檔案區隔開來,但也是可以透過「掛接」的方式到容器中,像這次如果直接執行run.sh的話,會自動將jetson-inference/data掛載到docker的 /jetson-inference/build/aarch64/bin/ 當中,所以你可以在外部新增或刪除圖片。

 

下載DNN模型、安裝PyTorch

按下Enter開始建構環境,建置環境的過程中還有兩個要進行下載的動作,分別是「下載DNN模型」、「是下載PyTorch」,整個run的過程耗費時間相當久,但你也會注意到大部分都是因為要下載DNN模型的關係;基本上PyTorch會選擇Python3.6版本的,模型除非自己要其他的不然就是直接按確定就可以了,預設的情況下圖片分類會載兩個、物件辨識會載四個、語意分割會載六個模型。

如果不小心按到Skip跟Quit可以再到jetson-inference/tools裡面去執行程式,就會看到跟剛剛一樣的畫面了:

$ cd tools
$ ./download-models.sh
$ ./install-pytorch.sh

順道一提,我發現一個很好玩的現象,如果我們直接在上一層來呼叫程式的話會出現不同的顏色,這或許是NVIDIA的小彩蛋?

小技巧分享,在Jetson Nano當中要安裝套件其實有時候蠻麻煩的,像是PyTorch跟Tensorflow都要去找特定版本安裝,如果你已經有Jetson-Inference又想要在原生環境中安裝PyTorch的話,可以直接執行jetson-inference/tools/install-pytorch.sh ( 不用進docker ),給他一段時間他就會在你的原生系統中安裝好PyTorch跟TorchVision囉!這其實跟從頭建置環境的原理一樣,可以注意到的地方是跟在docker當中不同他只讓我下載Python3.6版本的。

執行位置與方式

一般的執行方式

剛剛已經完成整個環境的建置了,接著需要先移動到運行程式的資料夾:

$ cd jetson-inference/build/aarch64/bin
$ ls

從上方的圖可以個別找到 imagenet、detectnet、segnet三個檔案,這三個是我們這次主要的運作程式,有.py代表是Python程式所撰寫的,而沒有附檔名的部分則是C++程式所撰寫,而這些都已經是執行檔了,所以都可以直接執行!

$ ./imagenet
$ ./imagenet.py

遠端的執行方式

這三個程式需要注意的地方如果使用「遠端」的方式要執行程式,需使用含有 “-console”的程式,不然會因為遠端軟體無法開啟圖片 ( OpenGL視窗 ) 而導致程式中斷,兩隻程式其實內容是一樣的但是在程式的前半段會去偵測是否有 console的字樣,有的話會開啟Headless模式。

$ cat imagenet.py

常用參數

這三個CV任務的外部參數皆是相同的,這邊講外部參數的意思是指在命令列執行程式的時候後面可以給定特定參數的數值,這樣的使用方法非常方便不用再開啟檔案調整;這邊三個電腦視覺任務常用的參數主要就是,要輸入到神經網路的內容是「檔案」、「影片」還是「即時影像」。

 

1.如果是檔案或影片的話我們可以直接填寫檔名,此外還會搭配一個輸出的檔名:

$ ./imagenet images/orange_0.jpg images/test/output_0.jpg
$ ./imagenet jellyfish.mkv images/test/jellyfish_resnet18.mkv

2.如果是即時影像,他們主要提供CSI (Camera Serial Interface)、V4L2 (Video4Linux)、RTP/RTSP (Real Time Streaming Protocol ) 三種協定,各別使用的方法是:

$ ./imagenet.py csi://0                 
# MIPI CSI camera

$ ./imagenet.py /dev/video0             
# V4L2 camera

$ ./imagenet.py /dev/video0 output.mp4  
# save to video file

除了三個電腦視覺的任務之外,他還有提供一些工具,比較常用的可能是給你檢查影像串流的 camera-viewer,因為影像來源有很多種,最常用的是CSI跟USB但也有網路串流的部分 (rtsp),或者說是圖片、影片都有可能,所以無法確保Jetson Inference能否開啟攝影機的話可以使用這個程式,其中參數 –camera 預設是0,是CSI攝影機的,但我這邊都使用USB網路攝影機所以需要額外宣告。

$ ./camera-viewer /dev/video0

其餘功能

那也有提供一個程式可以蒐集資料,選擇儲存位置、選擇標籤檔,選擇類別,全部都選定之後便可以開始進行拍照。

$ ./caemra-capture /dev/video0

圖片分類Image Classification

執行 Imagenet

 

Jetson Inference提供的第一個範例是圖片分類,執行方法就如同上面介紹執行方法一樣,這邊我們使用 black_bear這張圖片,並且將推論完的圖片儲存在test當中:

$ cd build/aarch64/bin
$ ./imagenet.py ./images/black_bear.jpg ./images/test/black_bear.jpg

推論完會將輸出的資訊覆寫在圖片上:

如果要使用USB網路攝影機運行即時影像的話則是使用:

$ ./imagenet.py /dev/video0

這邊也提供影像的DEMO:

可以辨識哪些種類?

神經網路模型可以辨識的類別會取決於當初訓練的數據集,Jetson-Inference在影像分類的部分採用的數據集是ILSVRC ImageNet,這個數據集擁有1000個常見的類別,所以基本上日常所見都可以被辨識出來,詳細的介紹連結如下:

 

可以使用哪些神經網路模型?

我們預設使用的神經網路模型是Googlenet,除此之外還可以使用不同的模型進行辨識,圖片分類可用的神經網路模型有

 神經網路名稱 參數 神經網路名稱 參數
AlexNet alexnet ResNet-101 resnet-101
GoogleNet googlenet ResNet-152 resnet-152
GoogleNet-12 googlenet-12 VGG-16 vgg-16
ResNet-18 resnet-18 VGG-19 vgg-19
ResNet-50 resnet-50 Inception-v4 inception-v4

 

 

該如何使用其他神經網路模型?

三種電腦視覺任務都可以使用 –network來使其他模型:

$ ./imagenet.py --network=resnet-18 ./images/black_bear.jpg ./images/test/black_bear_resnet18.jpg

可以注意到準確度為94%與googlenet的98%有些許差距,可以嘗試變換不同的模型,觀察哪一個模型適合便是哪一種物件。

 

結語

基本的Jetson Inference已經介紹得差不多了,也帶大家認識了第一個電腦視覺任務 – 圖片分類,接下來也會帶大家體驗其他的電腦視覺任務。

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

NVIDIA Jetson Nano使用Tensor RT加速YOLOv4神經網路推論

$
0
0

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

 

這邊我們就不會對YOLOv4的技術進行介紹,大致上可以了解YOLOv4是由很多強大的技巧所組成,是現階段教育界、商業都很常用到的一個技術。如果一一描述需要兩篇的文章,而網路上很多介紹很詳細的文章,大家可以直接去搜尋、查看;本篇著重在Github實作以及介紹TensorRT加速的方法給大家。

 

如何使用YOLOv4

首先要先建置darknet的環境,先下載darknet的github:

$ git clone https://github.com/AlexeyAB/darknet.git
$ cd darknet

接著需要修改一下Makefile,在官方的github當中有提到Jetson TX1/TX2的修改方法,Jetson Nano也是比照辦理,前面的參數設定完了,往下搜尋到ARCH的部分,需要將其修改成compute_53:

GPU=1
CUDNN=1
CUDNN_HALF=1
OPENCV=1
AVX=0
OPENMP=1
LIBSO=1
ZED_CAMERA=0
ZED_CAMERA_v2_8=0

......

USE_CPP=0
DEBUG=0

ARCH= -gencode arch=compute_53,code=[sm_53,compute_53]

接著就可以進行build的動作了:

$ make

如果Build darknet的時候出現找不到nvcc的問題,如下圖:

可以在Makefile當中的NVCC後面新增絕對位置:

接著重新make一次如果沒有錯誤訊息就代表Build好了!

使用YOLOv4進行推論

我們需要先下載YOLOv4的權重來用

wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights \
       -q --show-progress --no-clobber

基本的推論方法有三種:圖片、影片、攝影機 ( 即時影像 ),我們一一來介紹使用方法!主要執行除了darknet的執行檔之外還需要給予 模式、資料集、配置檔:

./darknet detector test ./cfg/coco.data ./cfg/yolov4.cfg ./yolov4.weights

可以使用 –help來幫助查看:

./darknet detector --help

如果要開啟圖片的話需使用 test模式,他會在執行之後要你輸入圖片的位置,不過這邊要注意的是按任意建離開後,圖片不會幫你儲存:

./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights -thresh 0.25

如果想要指定圖片並且將結果儲存下來則可以增加 -ext_output 的選項,執行完會儲存成 prediction.jpg,這邊我用另一張圖片當示範 ( Ximending.jfif ):

./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights -ext_output Taiwan.jfif

如果要使用影片或攝影機的話則是透過 demo 的指令來操作,這邊如果用 -ext_output會直接覆蓋掉原本的,我希望可以另存成別的檔案則需要用到 -output_filename來執行:

./darknet detector demo cfg/coco.data cfg/yolov4.cfg yolov4.weights sample.mp4 -out_filename sample_.mp4

 

 

使用攝影機進行影像即時辨識需要在後面參數導入 -c:

$ ./darknet detector demo cfg/coco.data \
                          cfg/yolov4.cfg \
                          yolov4.weights \
                          -c 0

可以看到FPS大概會在0.8左右 ( 於終端機畫面上 ),有明顯的延遲感,但是辨識的結果還算可以。

 

修改輸入維度大小

我們也可以直接修改輸入輸出的圖片大小,我用簡單一點的語法來操作,複製一個yolov4.cfg並命名為yolov4-416.cfg,並直接用nano去修改輸入大小成416,這邊使用&&的意思是讓前一個指令完成之後再接續下一個指令:

$ cp cfg/yolov4.cfg cfg/yolov4-416.cfg && nano cfg/yolov4-416.cfg

在下方的圖片可以看到,縮小圖片之後FPS就直接提高了許多,從0.8升到了1.5;注意!這個示範只是提供了可以修改輸入大小的方法,因為有時候你用的圖片或影片大小不同就需要稍微修改一下;官方較推薦的大小是608以上,縮小圖片可能會導致辨識結果變差:

使用結構更小的YOLO ( Yolov4-Tiny )

下一種加快速度的方法是使用yolov4-tiny.weights,一個更小型的yolov4,這邊的小型指的是神經網路模型的結構,一般我們都會使用在運算能力相較於顯示卡低的裝置上 ( 例如 : 邊緣裝置 ),實作的部分,我們先將該權重下載下來:

$ wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.weights

接著使用攝影機來開啟,注意這邊的config (cfg) 檔案需要更改成 yolov4-tiny.cfg,因為yolov4跟yolov4-tiny的架構有所不同,config檔案當中所提供的就是神經網路的結構:

$ ./darknet detector demo cfg/coco.data \
                          cfg/yolov4-tiny.cfg \
                          yolov4-tiny.weights \
                          -c 0

可以看到FPS已經來到了14,延遲感明顯降低許多:

使用TensorRT引擎加速

接下來是TensorRT的版本,稍微簡短介紹一下Tensor RT ( 以下簡稱 TRT ),它是一個加速引擎可以運用在有CUDA核心的NVIDIA顯示卡當中,如果要使用TRT引擎加速需要先將神經網路模型轉換成ONNX的格式才行。

下載、安裝環境

坊間利用Yolov4做了很多應用,而轉換這塊也已經有人完成了,所以我們直接使用網路上提供的Github來實現即可:

$ git clone https://github.com/jkjung-avt/tensorrt_demos.git

下載下來之後可以直接到ssd資料夾中執行 install_pycuda.sh:

$ cd ${HOME}/project/tensorrt_demos/ssd
$ ./install_pycuda.sh

如果顯示nvcc not found的話則需要手動修改 install_pycuda的檔案,我們需要將cuda的絕對位置存放到環境變數當中:

透過nano編輯器開啟並且將iffi中間的內容修改如下,原本的內容記得要註解掉:

$ nano ./install_pycuda.sh

安裝完之後應該會顯示 finished processing dependencies,也可以使用pip3 list去查看pycuda是否有安裝成功:

接著需要安裝onnx,一開始先安裝相依套件,接著在安裝onnx 1.4.1版本:

$ sudo apt-get install protobuf-compiler libprotoc-dev
$ sudo pip3 install onnx==1.4.1

都完成之後我們需要先將相關的程式build起來:

$ cd ${HOME}/project/tensorrt_demos/plugins
$ make

可以注意到又有nvcc的問題了,這時候一樣需要修改Makefile來解決,將原本的NVCC=nvcc修改成NVCC=/usr/local/cuda/bin/nvcc即可:

下載並轉換yolo模型

接著需要下載模型的權重,你將會看到它下載了yolo3跟yolo4的三種不同版本,並且直接放在當前資料夾當中,這邊可以注意到下載的模型與剛剛的YOLOv4相同,所以其實也是可以直接用複製的方式或是直接寫絕對位置進行轉換:

$ cd ${HOME}/project/tensorrt_demos/yolo
$ ./download_yolo.sh	

 

最後可以執行 yolo_to_onnx.py 將yolo的權重檔轉換成onnx檔案,接著再編譯成TRT可用的模型,在onnx_to_tensorrt.py我會建議使用 -v 來看到進度,不然看著畫面沒動靜會有點緊張:

$ python3 yolo_to_onnx.py -m yolov4-416
$ python3 onnx_to_tensorrt.py -m yolov4-416 -v

轉換ONNX大約耗費15分鐘,會儲存成yolov4-416.onnx,接著轉換TRT大概也是差不多的時間,最後會儲存成yolov4-416.trt。

 

使用TRT運行YOLOv4-416

這邊我們使用 –usb 代表使用USB攝影機, –model則是選擇特定模型:

$ cd ${HOME}/project/tensorrt_demos
$ python3 trt_yolo.py --usb 0 --model yolov4-416

左上角有顯示FPS數值,實測下來大約都會在 4.2~4.5之間,我們這次使用的是416維度,相較沒有使用TensorRT引擎的Darknet ( FPS 1.5),快了將近3倍。

剛剛輸入的部分使用usb攝影機,而作者很貼心地都寫得很完善了,在utils/camera.py的部分可以看到輸入的內容選項,也可以直接使用 –help來查看:

這裡有提供圖片( –image )、影像 ( –video )、重複影片 ( –video_lopping )、網路攝影機 ( –usb ) 等都可以使用。

 

使用TRT運行YOLOv4-Tiny-416

接下來為了追求更快的速度,我們當然要來實測一下tiny版本的:

$ python3 yolo_to_onnx.py -m yolov4-tiny-416
$ python3 onnx_to_tensorrt.py -m yolov4-tiny -416 -v
$ cd ${HOME}/project/tensorrt_demos
$ python3 trt_yolo.py --usb 0 --model yolov4-tiny-416

使用tiny的話FPS來到18.05,基本上已經有不錯的效果了!不過因為是tiny所以辨識的成效沒有預期中的好:

使用TensorRT運行模型比較表

 

此表從jkjung-avt/tensorrt_demos的github當中移植過來,其中mAP是常用評估物件偵測的演算法,在yolov3的部分 tiny雖然有不錯的FPS但是mAP相較yolov4卻是差了不少,目前我認為yolov4-tiny-416以FPS實測18左右、mAP約0.38的狀況算是較能夠接受的範圍:

TensorRT engine mAP @
IoU=0.5:0.95
mAP @
IoU=0.5
FPS on Nano
yolov3-tiny-288 (FP16) 0.077 0.158 35.8
yolov3-tiny-416 (FP16) 0.096 0.202 25.5
yolov3-288 (FP16) 0.331 0.601 8.16
yolov3-416 (FP16) 0.373 0.664 4.93
yolov3-608 (FP16) 0.376 0.665 2.53
yolov3-spp-288 (FP16) 0.339 0.594 8.16
yolov3-spp-416 (FP16) 0.391 0.664 4.82
yolov3-spp-608 (FP16) 0.410 0.685 2.49
yolov4-tiny-288 (FP16) 0.179 0.344 36.6
yolov4-tiny-416 (FP16) 0.196 0.387 25.5
yolov4-288 (FP16) 0.376 0.591 7.93
yolov4-416 (FP16) 0.459 0.700 4.62
yolov4-608 (FP16) 0.488 0.736 2.35
yolov4-csp-256 (FP16) 0.336 0.502 12.8
yolov4-csp-512 (FP16) 0.436 0.630 4.26
yolov4x-mish-320 (FP16) 0.400 0.581 4.79
yolov4x-mish-640 (FP16) 0.470 0.668 1.46

結語

今天帶大家稍微深入一點的了解了YOLOv4的Github,也帶大家認識了使用TensorRT針對模型進行加速的部分,相信大家應該都很快就上手了!接下來我想帶大家解析 darknet.py 並且嘗試修改成更淺顯易懂的程式碼。

 

相關文章

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

Viewing all 678 articles
Browse latest View live