用Nvidia Jetson Nano 2GB和Python構建一個價值60美元的人臉識別系統

作者|Adam Geitgey 編譯|Flin 來源|medium

新的Nvidia Jetson Nano 2GB開發板(今天宣佈!)是一款單板機,售價59美元,運行帶有GPU加速的人工智能軟件。

到2020年,你可以從一臺售價59美元的單板計算機中獲得令人驚歎的性能。讓我們用它來創建一個門鈴攝像頭的簡單版本,該攝像頭可以跟蹤走到房屋前門的每個人。通過面部識別,即使這些人穿着不同,它也可以立即知道你家門口的人是否曾經來拜訪過你。

什麼是Nvidia Jetson Nano 2GB?

Jets o n Nano 2GB是一款單板計算機,具有四核1.4GHz ARM CPU和內置的Nvidia Maxwell GPU。它是最便宜的Nvidia Jetson機型,針對的是購買樹莓派的業餘愛好者。

如果你已經熟悉樹莓派產品系列,則除了Jetson Nano配備Nvidia GPU外,這是和其他產品幾乎完全相同的。它可以運行GPU加速的應用程序(如深度學習模型),其速度遠比樹莓派這樣的板(不支持大多數深度學習框架的GPU)快得多。

那裏有很多AI開發板和加速器模塊,但Nvidia擁有一大優勢——它與桌面AI庫直接兼容,不需要你將深度學習模型轉換爲任何特殊格式即可運行他們。

它使用幾乎所有每個基於Python的深度學習框架都已使用的相同的CUDA庫進行GPU加速。這意味着你可以採用現有的基於Python的深度學習程序,幾乎無需修改就可以在Jetson Nano 2GB上運行它,並且仍然可以獲得良好的性能(只要你的應用程序可以在2GB的RAM上運行)。

它將爲強大的服務器編寫的Python代碼部署在價格爲59美元的獨立設備上的能力非常出色。

這款新的Jetson Nano 2GB主板也比Nvidia以前的硬件版本更加光鮮亮麗。

第一個Jetson Nano機型莫名其妙地缺少WiFi,但該機型隨附一個可插入的WiFi模塊,因此你不必再加上雜亂的以太網電纜了。他們還將電源輸入升級到了更現代的USB-C端口,並且在軟件方面,一些粗糙的邊緣已被磨掉。例如,你無需執行諸如啓用交換文件之類的基本操作。

Nvidia積極地推出了一款價格低於60美元的帶有真實GPU的簡單易用的硬件設備。似乎他們正以此爲目標瞄準樹莓派,並試圖佔領教育/愛好者市場。看看市場如何反應將是很有趣的。

讓我們組裝系統

對於任何硬件項目,第一步都是收集我們需要的所有零件:

1. Nvidia Jetson Nano 2GB主板(59美元)

這些板目前可預訂(截至2020年10月5日),預計將於10月底發佈。

我不知道發行後的初始可用性會如何,但是先前的Jetson Nano機型在發行後的幾個月中供不應求。

全面披露:我從英偉達獲得了免費的Jetson Nano 2GB開發板作爲評估單元,但我與英偉達沒有財務或編輯關係。這就是我能夠提前編寫本指南的方式。

2. USB-C電源適配器(你可能已經有一個?)

新型Jetson Nano 2GB使用USB-C供電。不包括電源適配器,但是你可能已經有一個電源適配器了。

3. 攝像頭—— USB網絡攝像頭(你可能有一個?)或樹莓派攝像頭模塊v2.x(約30美元)

如果你希望將小型相機安裝在機殼中,那麼樹莓派相機模塊v2.x是一個不錯的選擇(注意:v1.x相機模塊將無法使用)。你可以在Amazon或各種經銷商處獲得它們。

一些USB網絡攝像頭(如Logitech的C270或C920)也可以在Jetson Nano 2GB上正常工作,因此如果你已經擁有一個USB攝像頭,也可以拿來使用。這裏有一個攝像頭的不完整清單。

在購買新產品之前,請不要害怕嘗試擺放任何USB設備。並非所有功能都支持Linux驅動程序,但有些功能會支持。我插入了在亞馬遜上買的價值20美元的通用HDMI到USB適配器,它工作得很好。因此,我無需任何額外配置就可以將我的高端數碼相機用作通過HDMI的視頻源。

你還需要其他一些東西,但是你可能已經準備好了:

至少具有32GB空間的microSD卡。我們將在此安裝Linux。你可以重複使用現有的任何microSD卡。

一個microSD卡閱讀器:以便你可以安裝Jetson軟件。

一個有線USB鍵盤和一個有線USB鼠標控制Jetson Nano。

任何直接接受HDMI(而不是通過HDMI-DVI轉換器)的監視器或電視,你都可以看到自己在做什麼。即使以後不使用顯示器運行Jetson Nano初始設置,也需要一個監視器。

加載Jetson Nano 2GB軟件

在開始將東西插入Jetson Nano之前,你需要下載Jetson Nano的軟件映像。

Nvidia的默認軟件映像包括預裝了Python 3.6和OpenCV的Ubuntu Linux 18.04。

以下是將Jetson Nano軟件安裝到SD卡上的方法:

  1. 從Nvidia下載Jetson Nano Developer Kit SD卡映像。
  1. 下載Etcher,該程序將Jetson軟件映像寫入SD卡。
  1. 運行Etcher並使用它來編寫下載到SD卡的Jetson Nano Developer Kit SD卡映像。這大約需要20分鐘。

是時候將其餘的硬件拆箱了!

插入所有零件

首先,請拿出你的Jetson Nano 2GB:

第一步是插入microSD卡。microSD卡插槽已完全隱藏,但你可以在散熱器底部底部的背面找到它:

你還應該繼續將隨附的USB WiFi適配器插入以下USB端口之一:

接下來,你需要插入相機。

如果你使用的是樹莓派 v2.x相機模塊,則它會通過帶狀電纜連接。在Jetson上找到帶狀電纜插槽,彈出連接器,插入電纜,然後將其彈出關閉。確保帶狀電纜上的金屬觸點向內朝向散熱器:

如果你使用USB網絡攝像頭,只需將其插入USB端口之一,而忽略帶狀電纜端口。

現在,插入其他所有零件:

將鼠標和鍵盤插入USB端口。

使用HDMI電纜插入顯示器。

最後,插入USB-C電源線以啓動它。

如果你使用的是樹莓派相機模塊,則最終會得到如下所示的內容:

或者,如果你使用的是USB視頻輸入設備,它將看起來像這樣:

插入電源線後,Jetson Nano會自動啓動。幾秒鐘後,你應該會看到Linux設置屏幕出現在監視器上。請按照以下步驟創建你的帳戶並連接到WiFi。非常簡單。

安裝Linux和Python庫以進行人臉識別

一旦完成了Linux的初始設置,就需要安裝幾個我們將在人臉識別系統中使用的庫。

在Jetson Nano桌面上,打開一個LXTerminal窗口並運行以下命令。每次要求輸入密碼時,請輸入創建用戶帳戶時輸入的密碼:

sudo apt-get update
sudo apt-get install python3-pip cmake libopenblas-dev liblapack-dev libjpeg-dev

首先,我們要更新apt,這是標準的Linux軟件安裝工具,我們將使用它來安裝所需的其他系統庫。

然後,我們將安裝一些尚未預先安裝我們軟件需要的linux庫。

最後,我們需要安裝face_recognition Python庫及其依賴項,包括機器學習庫dlib。你可以使用以下單個命令自動執行此操作:

sudo pip3 -v install Cython face_recognition

因爲沒有可用於Jetson平臺的dlib和numpy的預構建副本,所以此命令將從源代碼編譯這些庫。因此,趁此機會喫個午餐,因爲這可能需要一個小時的時間!

當最終完成時,你的Jetson Nano 2GB就可以通過完整的CUDA GPU加速進行人臉識別。繼續下一個有趣的部分!

運行面部識別門鈴攝像頭演示應用程序

face_recognition庫是我編寫的一個Python庫,它使得使用DLIB做人臉識別超級簡單。它使你能夠檢測到面部,將每個檢測到的面部轉換爲唯一的面部編碼,然後比較這些面部編碼以查看它們是否可能是同一個人——只需幾行代碼即可。

使用該庫,我構建了一個門鈴攝像頭應用程序,該應用程序可以識別走到你的前門並在人每次回來時對其進行跟蹤的人。運行時看起來像這樣:

首先,請下載代碼。我已經在此處添加了完整的代碼和註釋。

但是這裏有一個更簡單的方法可以從命令行下載到你的Jetson Nano上:

wget -O doorcam.py tiny.cc/doorcam2gb

在程序的頂部,你需要編輯一行代碼以告訴你是使用USB相機還是樹莓派相機模塊。你可以像這樣編輯文件:

gedit doorcam.py

按照說明進行操作,然後保存它,退出GEdit並運行代碼:

python3 doorcam.py

你會在桌面上看到一個視頻窗口。每當有新人走到攝像機前時,它都會記錄他們的臉並開始跟蹤他們在你家門口的時間。如果同一個人離開並在5分鐘後回來,它將重新註冊並再次跟蹤他們。你可以隨時按鍵盤上的“ q”退出。

該應用程序會自動將看到的每個人的信息保存到一個名爲known_faces.dat的文件中。當你再次運行該程序時,它將使用該數據記住以前的訪問者。如果要清除已知面孔的列表,只需退出程序並刪除該文件。

將其變成獨立的硬件設備

至此,我們有一個運行人臉識別模型的開發板,但它仍被束縛在桌面上,以實現強大的功能和顯示效果。讓我們看看無需插入如何運行它。

現代單板計算機的一件很酷的事情是,它們幾乎都支持相同的硬件標準,例如USB。這意味着你可以在亞馬遜上買到很多便宜的附件,例如觸摸屏顯示器和電池。你有很多輸入,輸出和電源選項。這是我訂購的東西(但類似的東西都可以):

一個7英寸觸摸屏HDMI顯示屏,使用USB電源:

以及一個通用的USB-C電池組來供電:

讓我們將其連接起來,看看作爲獨立設備運行時的外觀。只需插入USB電池而不是壁式充電器,然後將HDMI顯示器插入HDMI端口和USB端口,即可充當屏幕和鼠標輸入。

效果很好。觸摸屏可以像普通的USB鼠標一樣操作,無需任何其他配置。唯一的缺點是,如果Jetson Nano 2GB消耗的電量超過USB電池組可提供的電量,則會降低GPU的速度。但是它仍然運行良好。

有了一點點創意,你就可以將所有這些打包到一個項目案例中,用作原型硬件設備來測試你自己的想法。而且,如果你想批量生產某些產品,則可以購買Jetson主板的生產版本,並將其用於構建真正的硬件產品。

門鈴攝像頭Python代碼演練

想知道代碼的工作原理嗎?讓我們逐步解決。

代碼從導入我們將要使用的庫開始。最重要的是OpenCV(Python中稱爲cv2),我們將使用OpenCV從相機讀取圖像,以及用於檢測和比較人臉的人臉識別。

import face_recognition
import cv2
from datetime import datetime, timedelta
import numpy as np
import platform
import pickle

然後,我們需要知道如何訪問相機——從樹莓派相機模塊獲取圖像的方法與使用USB相機的方法不同。因此,只需根據你的硬件將此變量更改爲True或False即可:

# 這裏的設置取決於你的攝像機設備類型:
# - True = 樹莓派 2.x camera module
# - False = USB webcam or other USB video input (like an HDMI capture device)
USING_RPI_CAMERA_MODULE = False

接下來,我們將創建一些變量來存儲有關在攝像機前行走的人的數據。這些變量將充當已知訪客的簡單數據庫。

known_face_encodings = []
known_face_metadata = []

該應用程序只是一個演示,因此我們將已知的面孔存儲在Python列表中。在處理更多面孔的真實應用程序中,你可能想使用真實的數據庫,但是我想使此演示保持簡單。

接下來,我們具有保存和加載已知面部數據的功能。這是保存功能:

def save_known_faces():
    with open("known_faces.dat", "wb") as face_data_file:
        face_data = [known_face_encodings, known_face_metadata]
        pickle.dump(face_data, face_data_file)
        print("Known faces backed up to disk.")

這將使用Python的內置pickle功能將已知的面孔寫入磁盤。數據以相同的方式加載回去,但是我在這裏沒有顯示。

每當我們的程序檢測到新面孔時,我們都會調用一個函數將其添加到已知的面孔數據庫中:

def register_new_face(face_encoding, face_image):
    known_face_encodings.append(face_encoding)
known_face_metadata.append({
        "first_seen": datetime.now(),
        "first_seen_this_interaction": datetime.now(),
        "last_seen": datetime.now(),
        "seen_count": 1,
        "seen_frames": 1,
        "face_image": face_image,
    })

首先,我們將代表面部的面部編碼存儲在列表中。然後,我們將有關面部的匹配數據字典存儲在第二個列表中。我們將使用它來跟蹤我們第一次見到該人的時間,他們最近在攝像頭周圍閒逛了多長時間,他們訪問過我們房屋的次數以及他們的臉部圖像。

我們還需要一個輔助函數來檢查面部數據庫中是否已經存在未知面部:

def lookup_known_face(face_encoding):
    metadata = None
    if len(known_face_encodings) == 0:
        return metadata
    face_distances = face_recognition.face_distance(
        known_face_encodings, 
        face_encoding
    )
    best_match_index = np.argmin(face_distances)
    if face_distances[best_match_index] < 0.65:
        metadata = known_face_metadata[best_match_index]
        metadata["last_seen"] = datetime.now()
        metadata["seen_frames"] += 1
        if datetime.now() - metadata["first_seen_this_interaction"]  
                > timedelta(minutes=5):
            metadata["first_seen_this_interaction"] = datetime.now()
            metadata["seen_count"] += 1
    return metadata

我們在這裏做一些重要的事情:

  1. 使用face_recogntion庫,我們檢查未知面孔與所有以前的訪問者的相似程度。所述face_distance()函數爲我們提供了未知臉部和所有已知的面之間的相似性的數值測量——數字越小,面部越類似。

  2. 如果面孔與我們的一位已知訪客非常相似,則我們假設他們是重複訪客。在這種情況下,我們將更新它們的“上次觀看”時間,並增加在視頻幀中看到它們的次數。

  3. 最後,如果在最近的五分鐘內有人在鏡頭前看到這個人,那麼我們假設他們仍然在這裏作爲同一次訪問的一部分。否則,我們假設這是對我們房屋的新訪問,因此我們將重置跟蹤他們最近訪問的時間戳。

程序的其餘部分是主循環——一個無限循環,在該循環中,我們獲取視頻幀,在圖像中查找人臉並處理我們看到的每個人臉。這是該程序的主要核心。讓我們來看看:

def main_loop():
    if USING_RPI_CAMERA_MODULE:
        video_capture = 
            cv2.VideoCapture(
                get_jetson_gstreamer_source(), 
                cv2.CAP_GSTREAMER
            )
    else:
        video_capture = cv2.VideoCapture(0)

第一步是使用適合我們計算機硬件的任何一種方法來訪問相機。

現在讓我們開始獲取視頻幀:

while True:
    # Grab a single frame of video
    ret, frame = video_capture.read()
    # Resize frame of video to 1/4 size
    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
    # Convert the image from BGR color
    rgb_small_frame = small_frame[:, :, ::-1]

每次抓取一幀視頻時,我們都會將其縮小到1/4尺寸。這將使人臉識別過程運行得更快,但代價是僅檢測圖像中較大的人臉。但是由於我們正在構建一個門鈴攝像頭,該攝像頭只能識別攝像頭附近的人,所以這不是問題。

我們還必須處理這樣一個事實,OpenCV從攝像機中提取圖像,每個像素存儲爲藍綠色-紅色值,而不是標準的紅色-綠色-藍色值。在圖像上進行人臉識別之前,我們需要轉換圖像格式。

現在,我們可以檢測圖像中的所有面部,並將每個面部轉換爲面部編碼。只需兩行代碼:

face_locations = face_recognition.face_locations(rgb_small_frame)
face_encodings = face_recognition.face_encodings(
                     rgb_small_frame, 
                     face_locations
                  )

接下來,我們將遍歷每一個檢測到的面孔,並確定該面孔是我們過去見過的人還是新的訪客:

for face_location, face_encoding in zip(
                       face_locations, 
                       face_encodings):
metadata = lookup_known_face(face_encoding)
    if metadata is not None:
        time_at_door = datetime.now() - 
            metadata['first_seen_this_interaction']
        face_label = f"At door {int(time_at_door.total_seconds())}s"
    else:
        face_label = "New visitor!"
        # Grab the image of the face
        top, right, bottom, left = face_location
        face_image = small_frame[top:bottom, left:right]
        face_image = cv2.resize(face_image, (150, 150))
        # Add the new face to our known face data
        register_new_face(face_encoding, face_image)

如果我們以前見過此人,我們將檢索我們存儲的有關他們先前訪問的元數據。

如果沒有,我們將它們添加到我們的臉部數據庫中,並從視頻圖像中獲取他們的臉部圖片以添加到我們的數據庫中。

現在我們找到了所有的人並弄清了他們的身份,我們可以再次遍歷檢測到的人臉,只是在每個人臉周圍繪製框併爲每個人臉添加標籤:

for (top, right, bottom, left), face_label in 
                  zip(face_locations, face_labels):
    # Scale back up face location
    # since the frame we detected in was 1/4 size
    top *= 4
    right *= 4
    bottom *= 4
    left *= 4
    # Draw a box around the face
    cv2.rectangle(
        frame, (left, top), (right, bottom), (0, 0, 255), 2
    )
    # Draw a label with a description below the face
    cv2.rectangle(
        frame, (left, bottom - 35), (right, bottom), 
        (0, 0, 255), cv2.FILLED
    )
    cv2.putText(
        frame, face_label, 
        (left + 6, bottom - 6), 
        cv2.FONT_HERSHEY_DUPLEX, 0.8, 
        (255, 255, 255), 1
    )

我還希望在屏幕上方繪製一份最近訪問者的運行列表,其中包含他們訪問過你房屋的次數:

要繪製該圖像,我們需要遍歷所有已知的面孔,並查看最近在鏡頭前的面孔。對於每個最近的訪客,我們將在屏幕上繪製他們的臉部圖像並繪製訪問次數:

number_of_recent_visitors = 0
for metadata in known_face_metadata:
    # 如果我們在最後一分鐘見過此人,
    if datetime.now() - metadata["last_seen"] 
                         < timedelta(seconds=10):
# 繪製已知的面部圖像
        x_position = number_of_recent_visitors * 150
frame[30:180, x_position:x_position + 150] =
              metadata["face_image"]
number_of_recent_visitors += 1
        # Label the image with how many times they have visited
        visits = metadata['seen_count']
        visit_label = f"{visits} visits"
if visits == 1:
            visit_label = "First visit"
cv2.putText(
            frame, visit_label, 
            (x_position + 10, 170), 
            cv2.FONT_HERSHEY_DUPLEX, 0.6, 
            (255, 255, 255), 1
        )

最後,我們可以在屏幕上顯示當前視頻幀,並在其頂部繪製所有註釋:

cv2.imshow('Video', frame)

爲了確保程序不會崩潰,我們將每100幀將已知面孔列表保存到磁盤上:

if len(face_locations) > 0 and number_of_frames_since_save > 100:
    save_known_faces()
    number_of_faces_since_save = 0
else:
    number_of_faces_since_save += 1

程序退出時,僅需一行或兩行清理代碼即可關閉相機。

該程序的啓動代碼位於該程序的最底部:

if __name__ == "__main__":
    load_known_faces()
    main_loop()

我們要做的就是加載已知的面孔(如果有的話),然後啓動主循環,該循環永遠從相機讀取並在屏幕上顯示結果。

整個程序只有大約200行,但是它可以檢測到訪客,對其進行識別並每當他們來到你的家門時進行跟蹤。

一個有趣的事實:這種人臉跟蹤代碼在許多街道和汽車站的廣告中運行,以跟蹤誰在看廣告以及持續多長時間。以前,這聽起來似乎很遙不可及,但現在你花60美元就可以買到同樣的東西!

擴展程序

該程序是一個示例,說明了如何使用在便宜的Jetson Nano 2GB板上運行的少量Python 3代碼來構建功能強大的系統。

如果你想把它變成一個真正的門鈴攝像頭系統,你可以添加這樣一個功能:當系統檢測到門口有新的人時,它就會用Twilio向你發送短信,而不是僅僅在你的顯示器上顯示。或者你可以嘗試用真實的數據庫替換簡單的內存中的人臉數據庫。

你也可以嘗試將這個程序轉換成完全不同的程序。閱讀一幀視頻,在圖像中尋找內容,然後採取行動的模式是各種計算機視覺系統的基礎。嘗試更改代碼,看看你能想出什麼!當你回家走到自己家門口時,讓它播放你自己定製的主題音樂怎麼樣?你可以查看其他一些面部識別示例,以瞭解如何進行類似的操作。

瞭解有關Nvidia Jetson平臺的更多信息

如果你想了解有關使用Nvidia Jetson硬件平臺進行構建的更多信息,Nvidia會提供新的免費Jetson培訓課程。查看他們的網站以獲取更多信息。

他們也有很棒的社區資源,例如JetsonHacks網站。

如果你想進一步瞭解有關使用Python構建ML和AI系統的更多信息,請在我的網站上查看我的其他文章和書。

原文鏈接:https://medium.com/@ageitgey/build-a-face-recognition-system-for-60-with-the-new-nvidia-jetson-nano-2gb-and-python-46edbddd7264

歡迎關注磐創AI博客站: http://panchuang.net/

sklearn機器學習中文官方文檔: http://sklearn123.com/

歡迎關注磐創博客資源彙總站: http://docs.panchuang.net/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章