Jetson Nano 從入門到實戰(案例:Opencv設定、人臉檢測、二維條碼檢測)

2021-04-04 08:00:16

目錄

1. Jetson Nano簡介

2. Jetson Nano環境設定

2.1 開箱配件介紹

2.2 燒錄系統

2.3 開機和基本設定

2.4 開發環境設定

2.4.1 更新源和軟體

2.4.2 關屏時間設定

2.4.3 安裝中文輸入法

2.4.4 安裝Code OSS

2.4.5 安裝Qt5

3. 專案案例

3.1 人臉檢測

3.1.1 安裝pip

3.1.2 安裝Python常用機器學習包

3.1.3 設定用於Python的Opencv

3.1.4 基於Opencv的人臉檢測

3.2 二維條碼檢測(製作掃碼槍)

3.2.1 讀取攝像頭

3.2.2 二維條碼檢測和識讀

4. 小結


 

1. Jetson Nano簡介

Jetson Nano是一款體積小巧、功能強大的人工智慧嵌入式開發板,於2019年3月由英偉達推出。預裝Ubuntu 18.04LTS系統,搭載英偉達研發的128核Maxwell GPU,可以快速將AI技術落地並應用於各種智慧裝置。相比於Jetson之前的幾款產品(Jetson TK1、Jetson TX1、Jetson TX2、Jetson Xavier),Jetson Nano售價僅需99美元,大幅減少了人工智慧終端的研發成本。因此,一經推出,便受到了廣泛的關注。其官網地址為:https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-nano/

下面詳細列舉一些Jetson Nano的優勢:

(1) 體型小巧,效能強大,價格實惠,整體採用類似樹莓派的硬體設計,支援一系列流行的AI框架,並且英偉達投入了大量的研發精力為其打造了與之配套的Jetpack SDK開發包,通過該開發包可以使學習和開發AI產品變得更加簡單和便捷。

(2) 專為AI而設計,效能相比樹莓派更強大,搭載四核Cortex-A57處理器,128核Maxwell GPU及4GB LPDDR記憶體,可為機器人終端、工業視覺終端帶來足夠的AI算力。

(3) 可提供472 GFLOP,支援高解析度感測器,可以並行處理多個感測器,並可在每個感測器流上執行多個現代神經網路。

(4) 支援英偉達的NVIDIA JetPack元件包,其中包括用於深度學習、計算機視覺、GPU計算、多媒體處理等的板級支援包,CUDA,cuDNN和TensorRT軟體庫。

(5) 支援一系列流行的AI框架和演演算法,比如TensorFlow,PyTorch,Caffe / Caffe2,Keras,MXNet等,使得開發人員能夠簡單快速的將AI模型和框架整合到產品中,輕鬆實現影象識別,目標檢測,姿勢估計,語意分割,視訊增強和智慧分析等強大功能。

目前人工智慧風口開始逐步進入落地應用階段,更多產品希望能夠將人工智慧算力運用於實際終端,即實現所謂的邊緣計算需求。從根本上來說近幾年推動人工智慧的核心在於深度學習演演算法,但是深度學習的推理加速離不開高速GPU的支援,而一般桌面PC或伺服器級別的顯示卡(如英偉達1080Ti等)價格非常昂貴,不適合邊緣計算需求,而且體積也過於龐大。因此,英偉達推出的這款嵌入式人工智慧開發板Jetson Nano非常契合當前行業需求。

2. Jetson Nano環境設定

2.1 開箱配件介紹

直接購買的Jetson Nano僅包含一臺裸機,如下圖所示:

下圖顯示了Jetson Nano裸機的各個介面:

  • 介面1:SD卡插槽,在背面插入,拔出時只要輕按一下即可。SD卡主要用來儲存整個系統以及相關資料,類似於桌面PC硬碟的作用;
  • 介面2:  40個pin角的GPIO介面,主要用於連線外部裝置,如溫控器、水平儀等;NVIDIA官方提供了了JetsonGPIO庫(Python)可以方便的來控制GPIO,Jetson.GPIO庫運用了跟樹莓派RPi.GPIO庫一樣的API。
  • 介面3:USB口的5V輸入源,預設使用該介面作為電源輸入;該介面也可以作為資料傳輸線,例如高速串列埠等;
  • 介面4:有線網口;如果不夠買無線網路卡的話可以直接將網線接在該口上進行連網;
  • 介面5:  4個USB3.0口,用於接入USB裝置,例如USB型攝像頭;
  • 介面6:HDMI輸出埠,可以用來接顯示屏;
  • 介面7:DP顯示介面,可以用來接顯示屏;(介面6和介面7都是用來接外設顯示屏的,只是介面不同而已,實際使用時只採用一個即可,通常一般用HDMI介面)。如果想接VGA顯示屏,可以購買一個HDMI轉VGA的轉換口然後使用介面6即可;
  • 介面8: 直流5V輸入電源;注意,Jetson Nano的電源輸入方式有兩種,一種是採用介面3的方式,只需要購買個5V的安卓手機充電器插上即可使用,這也是預設方式。另一種就是使用介面8,此時需要對J48進行短接,短接完成後就可以切換成介面8進行電源輸入,如下圖所示:

  • 介面9: MIPI CSI 攝像頭,可以直接購買用於樹莓派的攝像頭即可使用;

除了裸機以外,還需自行購買記憶體卡、鍵盤、滑鼠、5V2A直流電源、顯示器、無線網路卡、攝像頭、風扇、外殼等配件才能更好的進行開發,這些配件均可以在網上方便的夠得。

下面對部分配件選型和安裝進行說明。

  • 記憶體卡:一般記憶體卡有16G、32G、64G、128G等,為了方便後期深度學習開發,建議選用32G及以上記憶體卡。

  • 鍵盤滑鼠可以直接使用支援USB3.0即可;
  • 電源選擇5V2A或5V3A電源,如下圖所示。主要,如果採用下圖所示電源接入方法,需要將J48連線才可,否則預設電源輸入是通過USB方式接入。

  • 顯示器:建議使用HDMI的顯示器直接連線,如果要接電腦VGA顯示器,則可以購買一個HDMI轉VGA的轉換模組完成連線

  • 無線網路卡:聯網可以採用有線和無線兩種方式,通過介面4直接連上網線就可以上網。如果需要採用無線wifi上網方式,則需要單獨購買無線wifi模組。建議購買Jetson Nano的標準配套無線Wifi模組,如下圖所示,主要包括一對對偶天線和一個處理晶片,其相容性可以得到保證。

該無線WIFI的安裝稍微麻煩一些,需要拆卸裸機上的GPU部件。

首先將wifi兩個天線連線到處理晶片上,在卡槽上按壓即可將天線卡上,如下圖所示:

然後按照下圖所示方式將Jetson Nano裸機上的GPU元件拆卸下來:

卸下來以後將wifi晶片安裝在卡槽中,如下圖所示:

然後再重新安裝上GPU元件,安裝的時候要注意不要卡住兩個天線的連線線。

  • 攝像頭:攝像頭可以採用兩種方式,一種是直接使用樹莓派的CSI攝像頭,還有就是使用USB攝像頭。如果使用樹莓派攝像頭,則按照下圖所示方式接入即可(注意正反面):

  • 風扇:一般情況下,不需要使用風扇對Jetson Nano進行散熱處理,但是如果用到深度學習技術,並且高頻率的進行推理運算,那麼最好需要在GPU元件上面加裝一個散熱風扇,如下圖所示:

  • 外殼:裸露的開發板容易短路,從安全性考慮,在安裝完上述配件以後最好為整個Jetson Nano安裝一個外殼。外殼種類多樣,可以根據實際購買的外殼說明進行安裝,下圖是一種較為常見的Jetson Nano透明外殼:

 

2.2 燒錄系統

英偉達官方為Jetson Nano提供了SD卡版本的系統映象,並且一直在更新和維護,該映象中包含對應的Ubuntu系統以及設定好的cuda環境和opencv環境,因此只需要下載和安裝該映象即可完成Jetson Nano的大部分環境設定。可以直接到官網進行映象下載,下載地址為:https://developer.nvidia.com/embedded/downloads,雙擊Image下載最新的映象即可,如下圖所示:

本文下載的是2019年12月17日更新的JP4.3版本映象,下載下來的是一個zip壓縮包,將該zip壓縮包進行解壓,可以得到字尾為img的檔案,該檔案即為我們需要的映象檔案。該映象檔案總共佔大概12.5G空間,所有這些內容最後都需要儲存在SD卡中,因此,建議選擇容量較大的SD卡較佳,如64G或128G。一般情況下,如果SD卡是新的,可以直接燒寫,但是有時候會需要對舊的SD卡重新燒寫,這時候就需要預先對SD卡做一下格式化,避免在映象過程中出錯。如果之前是已經燒寫過Jetson Nano映象的SD卡,那麼就需要先對SD卡進行分割區刪除和重新合併,這是因為經過Jetson Nano燒寫過的SD卡會形成12個子分割區,因此需要先用磁碟管理器對這些分割區進行刪除和合並,再進行新的映象燒寫(如果是新的SD卡則不需要這些操作),如下圖所示:

如上圖所示,舊的Jetson Nano映象卡會形成12個子分割區,對照上圖中的磁碟2的12個分割區,依次進行「刪除卷」處理,然後為磁碟2重新「新建簡單卷」,

如果是新的SD卡,則只需要格式化以下即可。接下來開始正式進行燒錄。燒錄工具很多,本文推薦使用Win32DiskImager,該工具可以直接在網上進行下載和安裝。雙擊開啟Win32DiskImager,選擇剛才的img映象,並設定好對應的SD卡碟符,如下圖所所示:

單擊「寫入」即可完成映象燒錄,整個燒錄時間大概在15分鐘左右。

2.3 開機和基本設定

完成燒錄後將SD卡插入到Jetson Nano背面的卡槽中,然後開機啟動。

初次安裝需要進行基本的設定,包括賬戶密碼、Wifi密碼、輸入法、時區等,具體根據提示完成即可,設定完成後會預設進行一次更新操作Applying Changes,此更新需要聯網,如果沒有聯網則先單擊cancel取消等後面聯網了再進行手動更新。更新時間較長,等待即可。最後會進入桌面,如下圖所示:

 

2.4 開發環境設定

2.4.1 更新源和軟體

安裝完系統後首先應該更新源,否則後續更新和升級會非常慢。但是由於Jetson Nano採用的是aarch64架構的Ubuntu 18.04.2 LTS系統,與AMD架構的Ubuntu系統不同,因此需要替換成aarch64的源,這裡一定要注意,不要替換成x86-64的源了。

我們這裡選擇清華的源進行更新。首先備份原本的源,更改source.list檔案的名字,以備不時之需:

sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak    
sudo gedit /etc/apt/sources.list

然後刪除所有內容,複製以下內容:

deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main multiverse restricted universe

到這裡換源就結束了。

開啟終端,輸入下述命令進行更新:

sudo apt-get update
sudo apt-get full-upgrade

上述更新時間較長,中間可能由於網速的關係會更新失敗,此時不要關機重新執行命令即可,會自動斷點續傳的。

 

2.4.2 關屏時間設定

Jetson Nano預設5分鐘內不操作Jetson即會關閉螢幕,重新開啟螢幕需要重新輸入開機密碼。由於在開發Jetson Nano的過程中經常需要等待,因此並不希望頻繁的開啟螢幕,而是希望螢幕一直開啟。開啟System Settings進入系統設定介面,如下圖所所示:   

單擊Brightness & Lock,然後將 Turn screen off when inactive for 改為 Never即可,如下圖所示:

 

2.4.3 安裝中文輸入法

由於在開發過程中經常需要使用中文搜尋以及書寫必要的中文註釋,所以推薦為系統安裝中文輸入法。Jetson Nano自帶ibus中文輸入法,但是要簡單的設定下才能進行中文的輸入。在終端中直接輸入命令ibus會出現下圖所示介面,說明Jetson Nano已經自帶了ibus輸入法環境了。

下面為ibus下載拼音輸入法,輸入命令:

sudo apt-get install ibus-pinyin

  上述下載和安裝大概需要數十分鐘的時間。安裝完成後進入系統設定System Settings介面,選擇語言支援選項Language Support ,如下圖所示:

   然後選擇「新增或刪除語言」介面,會系統選擇語言支援,如下圖所示:

      此處選擇「中文簡體」然後單擊Apply即可。這個Apply過程會安裝一系列中文語言套件,如下圖所示:

安裝完成後在語言支援介面將漢語調整到最前面,如下圖所示:

然後單擊「應用到整個系統」。最後將「鍵盤輸入法系統」改為iBus即可。

重新啟動系統(很重要!!!),然後在終端中輸入下述命令進入ibus設定介面:

 ibus-setup

在設定介面中單擊「新增」按鈕,然後展開「漢語」選項,選擇Intelligent Pinyin。這裡如果找不到「漢語」選項則可以先關機重新啟動,再重新查詢。

新增完成後輸入下面的命令重新啟動ibus即可。

ibus restart

最後,將桌面頂工作列中將輸入法切成拼音輸入法Pi,如下圖所示:

此時就可以使用中文輸入了。如下所示:

2.4.4 安裝Code OSS

Visual Studio Code(VS Code)是一個免費的整合式開發環境(IDE),適用於Windows,Mac和Linux。VS Code近年來獲得了越來越多的關注,成為廣大程式設計開發者的首選編譯環境。它作為微軟推出的開源專案,吸引了無數第三方開發者和終端使用者,成為頂尖開源專案之一。它功能強大、速度快,更在擁有海量外掛的情況下做到了簡潔流暢的使用者體驗,屬於一款非常優秀的IDE。

原生的VS Code並不適用於Jetson Nano,當前,還沒有針對Jetson Nano這樣的ARM裝置的VS Code正式版本。但是,由於它是開源的,所以任何人都可以編譯一個版本。其中,Code-OSS就是這樣一款嵌入式環境下的「VS Code」。Code-OSS基於VS Code,它並不僅僅是一個程式碼編輯器,它具有用於管理整個專案資料夾而不是單個指令碼的內建資源管理器功能以及豐富的第三方外掛。實際上Code-OSS幾乎具備了VS Code的所有完整功能,因此用它作為程式碼編輯器來編輯程式碼,例如python,會使得整個開發過程更加便捷。下面講解具體的安裝方法。

開啟Chromiun瀏覽器,輸入網址:

點選Packsges,檢視列出來的包名,選擇字尾帶有arm64(aarch64)的,如下圖所示:

單擊後進入詳情頁面,找到對應的wget命令,如下圖所示:

該命令演示瞭如何下載該安裝包,具體如下:

wget --content-disposition https://packagecloud.io/headmelted/codebuilds/packages/debian/stretch/code-oss_1.42.0-1575969886_arm64.deb/download.deb

將該命令複製到終端中實現安裝包下載。如下圖所示:

此時,安裝包已經下載到home根目錄下,可以通過檔案資源管理器檢視下載的deb安裝包,如下所示:

在終端中輸入下述命令完成最終的安裝:

sudo dpkg -i code-oss_1.42.0-1575969886_arm64.deb

安裝完成後從可以在搜尋中搜尋Code OSS,會彈出Code OSS應用程式,這個即為我們需要的Python程式設計IDE。單擊應用程式開啟如下圖所示:

下面簡單演示下如何使用Code OSS執行Python指令碼。

首先在Code OSS中安裝Python外掛,其外掛安裝方法和普通的VS Code完全相同,不熟悉VS Code的讀者可以先在桌面PC上熟悉VS Code基本用法再切換到Jetson Nano環境中來。外掛安裝如下圖所示,在Extensions面板中搜尋python,選擇第一個彈出的外掛進行安裝即可:

接下來在home目錄下新建一個code資料夾,該資料夾用於存放Python程式碼指令碼。然後在Code OSS中開啟剛才建立的code資料夾,然後新建一個檔案,按ctrl+s鍵儲存檔案,將檔案命名為main.py,然後輸入下面的程式碼:

a = 36
b = 64
print(a+b) 

然後按ctrl+F5鍵即可執行指令碼,效果如下:

至此,已完成Python編輯器的安裝和執行。

2.4.5 安裝Qt5

在實際的產品部署階段,考慮到終端裝置速度、穩定性、記憶體佔用等因素,一般會採用C++來開發最終的成品,而只有在產品模型設計階段才會使用python進行演演算法開發。因此,需要一款能夠在Jetson Nano中開發C++的編譯器方便我們開發落地產品。VS Code本身可以開發C++應用,但是Code-OSS對於C++的支援並不好,因此,需要另外安裝一個優秀的C++編譯器來完成C++開發任務。本文推薦使用Qt。

Qt是一個跨平臺的 C++ 開發庫,主要用來開發圖形化使用者介面(Graphical User Interface,GUI)程式,當然也可以開發不帶介面的命令列(Command User Interface,CUI)程式。Qt 是純 C++ 開發的,所以用它來開發C++應用具有天然的優勢。Qt 支援的作業系統有很多,例如通用作業系統 Windows、Linux、Unix,智慧手機系統 Android、iOS、WinPhone以及嵌入式系統 QNX、VxWorks 等等。當然,QT也完全支援Jetson Nano的Ubuntu環境。

Jetson Nano下安裝QT比較簡單,只需要輸入命令:

sudo apt-get install qt5-default qtcreator -y

此時安裝的是Qt5.9.5版本。

安裝完成後,同樣在搜尋選單中搜尋Qt,然後會出現Qt Creator,這個即為Qt的IDE,開啟它。接下來簡單演示如何建立一個簡單的C++控制檯程式。

開啟Qt Creator,如下圖所示:

單擊New Project建立一個新專案,這裡選擇Application 下的Qt COnsole Appliation應用,即建立一個Qt版的C++控制檯程式:

然後工程命名為QTtest:

然後一直預設單擊下一步即可完成專案的建立。可以看到,Qt已經為我們建立了一個C++檔案main.cpp用於編寫C++程式碼,並且還有一個QTtest.pro組態檔用於為整個專案進行設定,效果如下圖所示:

此時可以直接按ctrl+r鍵執行專案,但是由於我們並沒有任何輸出程式碼,所以彈出的終端也沒有輸出任何值。我們修改一下main.cpp的程式碼,同樣來執行兩個整數的相加並輸出其結果,完成程式碼如下:

#include <QCoreApplication>
#include <QtDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    int c=64;
    int d=36;
    qDebug() << (c+d);
    return a.exec();
}

此時,再重新按ctrl+r鍵執行專案輸出下圖所示結果:

Qt Creator的詳細使用請讀者自行學習相關教學,這部分資源很多也很成熟,對於開發實際的嵌入式產品來說掌握Qt和C++的使用是一個必要的過程。本文不再對Qt尤其是Qt的介面程式設計作深入介紹。

最後我們再看一下在主目錄下生成了一個與QTtest對照的debug可執行專案build-QTtest-unknown-Debug,在這個資料夾中生成來debug版本的QTtest可執行程式。通過終端cd命令進入到該資料夾,然後輸入

./QTtest

會直接執行程式,如下圖所示:

也就是說本質上我們已經成功的部署開發了一個應用,該應用功能很簡單,僅僅實現了兩個固定整數的相加。儘管簡單,但是卻梳理了我們正常開發人工智慧產品的一種比較常見的形式,即先在VS Code中用python指令碼進行演演算法驗證,最後再用QT編寫對應的C++應用,最後生成二進位制可執行程式,這個最終生成的二進位制可執行程式就是我們的「產品」,這個可執行程式程式碼是封裝起來的、不可見的、可以直接執行的。

至此,我們已經完成了Jetson Nano的常規開發設定,接下來會進行幾個小專案的演示,使讀者可以更深入的學習Jetson Nano的開發方法。

3. 專案案例

3.1 人臉檢測

本節首先使用Python來完成人臉檢測演演算法,其中會講解Python設定和使用Opencv的基本方法以及一些常用python庫的安裝。

3.1.1 安裝pip

由於Jetson Nano中已經預裝了Python3.6版本,所以可以直接安裝pip。

在終端中輸入下述命令進行安裝:

sudo apt-get install python3-pip python3-dev

安裝完成後此時的pip是9.01版本,需要對pip進行一下升級,否則後面在安裝其它Python庫的時候會出問題。升級命令如下:

python3 -m pip install --upgrade pip

升級後版本為19.0.3。儘管完成了升級,但是這時候pip3有個小bug需要手動修復一下。

首先使用下面的命令開啟pip3檔案:

sudo vim /usr/bin/pip3

鍵盤輸入字元a進入插入模式,然後可以開始編輯檔案,將:

from pip import main
if __name__ == '__main__':
    sys.exit(main())

修改為:

from pip import __main__
if __name__ == '__main__':
    sys.exit(__main__._main())

然後按Esc鍵進入到命令模式。最後按英文的":"鍵進入末行模式,敲入wq按回車即可儲存修改並退出編輯器。

3.1.2 安裝Python常用機器學習包

sudo apt-get install python3-scipy
sudo apt-get install python3-pandas
sudo apt-get install python3-sklearn

3.1.3 設定用於Python的Opencv

有兩種方法安裝python下的opencv。一種是下載Opencv原始碼並且重新編譯生成對應的python包,然後將該包拷貝到python的安裝包路徑中;另一種就是直接使用命令 sudo pip3 install python3-opencv。需要注意的是,第二種方式本質上安裝的是已經編譯好的opencv包,其opencv的版本是固定的,如果想要使用最新的opencv,比如opencv4,那麼第二種方法就不合適。本小節先簡單的採用第一種方式來安裝。

原映象中已經預裝了opencv4.1.1,可以使用下述命令來檢視當前Opencv版本號:

opencv_version

輸出結果如下圖所示:

因此,我們也不需要重新進行編譯,直接使用即可。

3.1.4 基於Opencv的人臉檢測

(1)python實現人臉檢測

本小節首先編寫一個python指令碼用於檢測影象中的人臉,使用Code OSS開啟2.4.4節中建立的code資料夾,在該資料夾下新建一個python指令碼,名為face_detect_test.py,程式碼如下所示:

import cv2 
filepath = "test.jpg" 
img = cv2.imread(filepath) # 讀取圖片 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 轉換灰色 
# OpenCV臉部辨識分類器 
classifier = cv2.CascadeClassifier( "haarcascade_frontalface_default.xml" ) 
color = (0, 255, 0) # 定義繪製顏色 
# 呼叫識別人臉 
faceRects = classifier.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32)) 
if len(faceRects): # 大於0則檢測到人臉 
    for faceRect in faceRects: # 單獨框出每一張人臉 
        x, y, w, h = faceRect 
        # 框出人臉 
        cv2.rectangle(img, (x, y), (x + h, y + w), color, 2) 
cv2.imshow("image", img) # 顯示影象 
c = cv2.waitKey(10) 
cv2.waitKey(0) 
cv2.destroyAllWindows()

上述程式碼中filepath用於存放當前需要檢測的影象路徑,一般可以放在與原始檔同目錄下即可。在構造opencv人臉檢測分類器時,需要對應的人臉檢測組態檔,該檔案儲存了用於人臉檢測演演算法的相關引數,此檔案可以從opencv的安裝目錄找到:/usr/share/opencv4/。找到後將其拷貝到原始檔目錄下即可。

按ctrl+F5執行,效果圖如下所示:

(2)C++實現人臉檢測

本小節編寫一個C++應用,用於檢測影象中的人臉,使用Qt5進行開發。相關實現方法與python版相同。主要講解如何在QT下整合Opencv進行C++專案開發。

C++下開發Opencv需要進行一些額外的設定,先看一下opencv的位置。Jetson Nano預裝的Opencv4.1.1的標頭檔案位置如下圖所示:

庫檔案放置在:  

/usr/lib/aarch64-linux-gnu

因此,只需要在Qt的pro檔案中將上述兩個目錄包含進來即可。

用Qt Creator重新開啟2.4.5節建立的QTtest專案,編輯QTtest.pro檔案如下:

QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle
CONFIG += C++11  # 新增對C++11的支援

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

INCLUDEPATH += /usr/include/opencv4 #新增標頭檔案路徑

LIBS += -L/usr/lib/aarch64-linux-gnu -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_highgui -lopencv_objdetect  #新增需要連結的庫


SOURCES += main.cpp

其中重點需要注意標頭檔案和lib檔案的新增方法。

接下來修改main.cpp檔案,程式碼如下:

#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>


using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
    std::string filepath( "test.jpeg" );
    Mat img,gray;
    img = imread( filepath, IMREAD_COLOR );
    cvtColor(img, gray, CV_BGR2GRAY);

    CascadeClassifier classifier;
    classifier.load("haarcascade_frontalface_default.xml");
    Scalar color=Scalar(0, 255, 255);

    vector<Rect> faceRects;
    classifier.detectMultiScale(gray,faceRects,1.2,3,0,Size(32,32));

    for (size_t i = 0; i < faceRects.size(); i++)
    {
        rectangle(img, faceRects[i], color);
    }

    namedWindow( "Display window", WINDOW_AUTOSIZE );
    imshow( "Display window", img);
    waitKey(0);
    return 0;
}

重新生成整個專案,然後將test.jpeg和haarcascade_frontalface_default.xml檔案放置在編譯生成的build-QTtest-unknown-Debug資料夾中,執行專案效果圖如下所示:

 

3.2 二維條碼檢測(製作掃碼槍)

現在支付寶和微信廣泛使用二維條碼作為支付手段,在現實生活購物中我們經常會通過手機展示二維條碼給商家用於掃碼,那麼我們是否可以自行做一個掃碼儀呢?有了Jetson Nano這款嵌入式人工智慧開發板,我們就可以自己製作一個掃碼槍。

3.2.1 讀取攝像頭

本小節我們希望能夠通過攝像頭讀取影象,並且對影象中的二維條碼進行實時解析,也就是實現一個掃碼儀的功能。本小節實現攝像頭讀取功能。攝像頭一般有兩種可選,一種是相對價格比較便宜的csi攝像頭(樹莓派攝像頭),還有一種是USB攝像頭。值得注意的是,如果採用USB攝像頭,那麼影象的讀取和渲染則是會用到Jetson Nano的GPU,如果這個時候我們還在做一些深度學習的推理工作,那麼很明顯會佔用掉一些GPU資源。相反,Jetson Nano對於csi攝像頭的讀取和渲染則會採用Gstreamer管道來處理,會使用特定的硬體加速,整個處理效果會更好。

本小節我們將詳細介紹兩種攝像頭的讀取方式。無論哪種方式,我們均採用Opencv這個強大的影象處理開源庫作為基礎來執行相關操作。

(1)讀取CSI攝像頭

使用Gstreamer讀取CSI攝像頭主要分為3個步驟:建立Gstreamer管道;將管道繫結opencv的視訊流;逐幀提取和顯示。下面首先給出基於Python的詳細程式碼:

import cv2

# 設定gstreamer管道引數
def gstreamer_pipeline(
    capture_width=1280, #攝像頭預捕獲的影象寬度
    capture_height=720, #攝像頭預捕獲的影象高度
    display_width=1280, #視窗顯示的影象寬度
    display_height=720, #視窗顯示的影象高度
    framerate=60,       #捕獲影格率
    flip_method=0,      #是否旋轉影象
):
    return (
        "nvarguscamerasrc ! "
        "video/x-raw(memory:NVMM), "
        "width=(int)%d, height=(int)%d, "
        "format=(string)NV12, framerate=(fraction)%d/1 ! "
        "nvvidconv flip-method=%d ! "
        "video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! "
        "videoconvert ! "
        "video/x-raw, format=(string)BGR ! appsink"
        % (
            capture_width,
            capture_height,
            framerate,
            flip_method,
            display_width,
            display_height,
        )
    )


if __name__ == "__main__":
    capture_width = 1280
    capture_height = 720
    display_width = 1280
    display_height = 720
    framerate = 60
    flip_method = 0

    # 建立管道
    print(gstreamer_pipeline(capture_width,capture_height,display_width,display_height,framerate,flip_method))

    #管道與視訊流繫結
    cap = cv2.VideoCapture(gstreamer_pipeline(flip_method=0), cv2.CAP_GSTREAMER)

    if cap.isOpened():
        window_handle = cv2.namedWindow("CSI Camera", cv2.WINDOW_AUTOSIZE)
        
        # 逐幀顯示
        while cv2.getWindowProperty("CSI Camera", 0) >= 0:
            ret_val, img = cap.read()
            cv2.imshow("CSI Camera", img)

            keyCode = cv2.waitKey(30) & 0xFF         
            if keyCode == 27:# ESC鍵退出
                break

        cap.release()
        cv2.destroyAllWindows()
    else:
        print("開啟攝像頭失敗")

緊接著3.1.4節中的第一部分內容,在Code-OSS中新建一個檔案命名為csi_camera_test.py,然後將上述程式碼複製到該檔案中,儲存然後按ctrl+F5執行指令碼(前提:確保已經準確安裝了CSI樹莓派攝像頭),執行效果如下所示:

可以看到已經可以正常的顯示視訊流影象了,但是由於樹莓派攝像頭本身的原因,其影象中還有很多的噪點,顏色也有些失真(真實工業場景中建議購買更好的攝像頭)。下面我們同步的給出C++版本。緊接著3.1.4節中第二部分內容,修改main.cpp檔案如下:

#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>

using namespace std;
using namespace cv;

string gstreamer_pipeline (int capture_width, int capture_height, int display_width, int display_height, int framerate, int flip_method)
{
    return "nvarguscamerasrc ! video/x-raw(memory:NVMM), width=(int)" + to_string(capture_width) + ", height=(int)" +
           to_string(capture_height) + ", format=(string)NV12, framerate=(fraction)" + to_string(framerate) +
           "/1 ! nvvidconv flip-method=" + to_string(flip_method) + " ! video/x-raw, width=(int)" + to_string(display_width) + ", height=(int)" +
           to_string(display_height) + ", format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink";
}

int main( int argc, char** argv )
{
    int capture_width = 1280 ;
    int capture_height = 720 ;
    int display_width = 1280 ;
    int display_height = 720 ;
    int framerate = 60 ;
    int flip_method = 0 ;

    //建立管道
    string pipeline = gstreamer_pipeline(capture_width,
    capture_height,
    display_width,
    display_height,
    framerate,
    flip_method);
    std::cout << "使用gstreamer管道: \n\t" << pipeline << "\n";

    //管道與視訊流繫結
    VideoCapture cap(pipeline, CAP_GSTREAMER);
    if(!cap.isOpened())
    {
        std::cout<<"開啟攝像頭失敗."<<std::endl;
        return (-1);
    }

    //建立顯示視窗
    namedWindow("CSI Camera", WINDOW_AUTOSIZE);
    Mat img;

    //逐幀顯示
    while(true)
    {
        if (!cap.read(img))
        {
            std::cout<<"捕獲失敗"<<std::endl;
            break;
        }
        imshow("CSI Camera",img);

        int keycode = cv::waitKey(30) & 0xff ; //ESC鍵退出
            if (keycode == 27) break ;
    }

    cap.release();
    destroyAllWindows() ;
}

其中需要額外的新增opencv用於視訊處理的標頭檔案#include <opencv4/opencv2/videoio.hpp>。另外,還需要修改對pro檔案,將視訊處理對應的opencv_videoio庫包含進來,完整的pro檔案如下:

QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle
CONFIG += C++11  # 新增對C++11的支援

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

INCLUDEPATH += /usr/include/opencv4 #新增標頭檔案路徑

LIBS += -L/usr/lib/aarch64-linux-gnu -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_highgui -lopencv_objdetect -lopencv_videoio  #新增需要連結的庫


SOURCES += main.cpp

儲存所有修改後重新構建專案並執行可以得到相同的結果,視訊可以正確顯示。

(2)讀取USB攝像頭

相比於讀取CSI攝像頭,讀取USB攝像頭更加簡單,只需要兩步:開啟攝像頭;逐幀提取。但是需要注意的是Jetson Nano並不是支援所有的USB攝像頭,建議在採購的時候儘量選擇Linux免驅的USB攝像頭。本文采用的是一個4K高清攝像頭。

下面給出Python版本的完整程式碼:

import cv2
 
 #建立攝像頭捕獲模組
cap = cv2.VideoCapture(1)

#建立視窗
window_handle = cv2.namedWindow("USB Camera", cv2.WINDOW_AUTOSIZE)

# 逐幀顯示
while cv2.getWindowProperty("USB Camera", 0) >= 0:
    ret_val, img = cap.read()
    print(img.shape)
    
    # 影象太大需要調整
    height, width = img.shape[0:2]
    if width>800:
        new_width=800
        new_height=int(new_width/width*height)
        img = cv2.resize(img, (new_width,new_height))
 
    cv2.imshow("USB Camera", img)

    keyCode = cv2.waitKey(30) & 0xFF         
    if keyCode == 27:# ESC鍵退出
        break

#釋放資源
cap.release()
cv2.destroyAllWindows()

上述程式碼在開啟攝像頭時使用了cap = cv2.VideoCapture(1),這裡的引數1是因為當前的Jetson Nano還連線了CSI攝像頭,CSI攝像頭的標識為0,因此這個USB攝像頭的標識為1,這個可以在實際使用時通過測試來得到。另外,上述程式碼中對影象的尺寸做了限制,如果寬度超過800,則等比的縮放影象再顯示。效果圖如下所示:

可以看到這個USB 4K攝像頭對於影象的顯示效果還是不錯的,顏色更加真實,噪點少。後面我們會繼續使用這個攝像頭進行二維條碼檢測。

下面給出C++版本程式碼,修改main.cpp檔案,程式碼如下:

#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>

using namespace std;
using namespace cv;

int main( int argc, char** argv )
{
    //開啟攝像頭
    VideoCapture cap(1);

    //建立顯示視窗
    namedWindow("USB Camera", WINDOW_AUTOSIZE);
    Mat img;

    //逐幀顯示
    while(true)
    {
        if (!cap.read(img))
        {
            std::cout<<"捕獲失敗"<<std::endl;
            break;
        }
        int new_width,new_height,width,height,channel;
        width=img.cols;
        height=img.rows;
        channel=img.channels();
        cout<<width<<"  "<<height<<"  "<<channel<<endl;

        new_width=800;
        if(width>800)
        {
            new_height=int(new_width*1.0/width*height);
        }

        resize(img, img, cv::Size(new_width, new_height));
        imshow("USB Camera",img);

        int keycode = cv::waitKey(30) & 0xff ; //ESC鍵退出
            if (keycode == 27) break ;
    }

    cap.release();
    destroyAllWindows() ;
}

效果如下所示:

 

3.2.2 二維條碼檢測和識讀

本小節將使用Opencv實現二維條碼檢測和識讀功能。在opencv4.0以後,已經整合了二維條碼識讀模組,因此,我們可以採用最新的opencv來實現二維條碼檢測和識讀。二維條碼檢測和識別主要分為3步:使用QRCodeDetector()函數建立二維條碼檢測器;使用detectAndDecode函數對影象進行二維條碼檢測和識別;將檢測結果輸出。

這裡主要是讀取視訊流的每幀影象然後對影象進行檢測,為了方便,我們僅給出針對USB攝像頭的完整範例,對於CSI攝像頭可以根據3.2.1節內容將相關二維條碼檢測程式碼遷移過去即可。結合3.2.1節中獲取USB攝像頭視訊的程式碼,給出完整的Python版二維條碼檢測和識讀程式碼:

import cv2
import numpy as np
 
 #建立攝像頭捕獲模組
cap = cv2.VideoCapture(1)

#建立視窗
window_handle = cv2.namedWindow("USB Camera", cv2.WINDOW_AUTOSIZE)

#建立二維條碼檢測器
qrDecoder = cv2.QRCodeDetector()

# 逐幀顯示
while cv2.getWindowProperty("USB Camera", 0) >= 0:
    ret_val, img = cap.read()
    #print(img.shape)
    
    # 影象太大需要調整
    height, width = img.shape[0:2]
    if width>800:
        new_width=800
        new_height=int(new_width/width*height)
        img = cv2.resize(img, (new_width,new_height))

    # 二維條碼檢測和識別
    data,bbox,rectifiedImage = qrDecoder.detectAndDecode(img)
    if len(data)>0:
        print("解碼資料 : {}".format(data))
        n = len(bbox)
        for j in range(n):
            cv2.line(img, tuple(bbox[j][0]), tuple(bbox[ (j+1) % n][0]), (255,0,0), 3)
    else:
        print("沒有檢測到二維條碼")

    #顯示影象
    cv2.imshow("USB Camera", img)

    keyCode = cv2.waitKey(30) & 0xFF         
    if keyCode == 27:# ESC鍵退出
        break

#釋放資源
cap.release()
cv2.destroyAllWindows()

效果圖如下:

C++版本完整程式碼如下:

#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>
#include <opencv4/opencv2/imgcodecs.hpp>

using namespace std;
using namespace cv;

int main( int argc, char** argv )
{
    //開啟攝像頭
    VideoCapture cap(1);

    //建立顯示視窗
    namedWindow("USB Camera", WINDOW_AUTOSIZE);
    Mat img;

    //建立二維條碼檢測器
    QRCodeDetector qrDecoder = QRCodeDetector();

    //逐幀顯示
    while(true)
    {
        if (!cap.read(img))
        {
            std::cout<<"捕獲失敗"<<std::endl;
            break;
        }
        int new_width,new_height,width,height,channel;
        width=img.cols;
        height=img.rows;
        channel=img.channels();
        //cout<<width<<"  "<<height<<"  "<<channel<<endl;

        //調整影象大小
        new_width=800;
        if(width>800)
        {
            new_height=int(new_width*1.0/width*height);
        }
        resize(img, img, cv::Size(new_width, new_height));

        //二維條碼檢測和識讀
        Mat bbox, rectifiedImage;
        std::string data = qrDecoder.detectAndDecode(img, bbox, rectifiedImage);
        if(data.length()>0)
        {
            cout << "解碼資料: " << data << endl;

            int n = bbox.rows;
            for(int i = 0 ; i < n ; i++)
            {
                line(img, Point2i(bbox.at<float>(i,0),bbox.at<float>(i,1)), Point2i(bbox.at<float>((i+1) % n,0), bbox.at<float>((i+1) % n,1)), Scalar(255,0,0), 3);
            }
        }
        else
            cout << "沒有檢測到二維條碼" << endl;

        imshow("USB Camera",img);

        int keycode = cv::waitKey(30) & 0xff ; //ESC鍵退出
            if (keycode == 27) break ;
    }

    cap.release();
    destroyAllWindows() ;
}

效果如下圖所示:

 

4. 小結

本篇部落格從Jetson Nano的安裝開始講解,一直到開發人臉檢測、二維條碼掃碼槍等案例結束。本教學更多的從實際使用出發,從嵌入式產品理念教會讀者如何一步步搭建人工智慧產品,每個案例均包括python和c++兩種版本。為了適應新讀者上手,選取的案例都比較簡單。從實際情況出發,目前人工智慧更多的採用深度學習進行高精度的推理運算,採用深度學習較傳統演演算法可以大幅提高影象檢測、識別和語意分割精度,但是如何將深度學習演演算法有效落地成了當前最熱門的風口,即如何高效的實現所謂的邊緣計算。Jetson Nano作為英偉達推出的人工智慧開發板,用其作為深度學習嵌入式的落地平臺非常合適,尤其是結合英偉達推出的TensorRT開發包,可以將各類深度學習框架訓練出來的模型進一步的加速推理。從英偉達公佈的資料分析,使用TensorRT可以加速推理3倍以上時間。因此,未來使用Jetson Nano的一個重點方向就是深度學習的落地應用。

後面本教學會持續更新Jetson Nano深度學習相關內容。