用Nvidia Jetson Nano 2GB和Python构建一个价值60美元的人脸识别系统

原创
2020/11/10 21:20
阅读数 1K
AI总结

作者|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/

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
AI总结
返回顶部
顶部