在Nvidia Jetson Nano 2GB开发板上构建人脸识别系统

磐创AI

    Nvidia Jetson Nano 2GB开发板(今天宣布!)是一款新的单板机,售价59美元,运行带有GPU加速的人工智能软件。
    
    在2020年,你可以从一台售价59美元的单板计算机中获得令人惊叹的性能,让我们用它来创建一个简易版的门铃摄像头,该摄像头可以识别走到房屋前门的每个人。通过面部识别,即使这些人穿着不同,它也可以立即知道你家门口的人是否曾经来拜访过你。什么是Nvidia Jetson Nano 2GB Jetson 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端口,并且在软件方面,一些粗糙的边缘已被磨掉,例如,你无需执行诸如启用交换文件之类的基本操作。让我们组装系统对于任何硬件项目,第一步都是收集我们需要的所有零件:1. Nvidia Jetson Nano 2GB主板(59美元)这开发板板目前可预订(截至2020年10月5日),预计将于10月底发布。我不知道发行后的初始可用性会如何,但是先前的Jetson Nano机型在发行后的几个月中供不应求。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摄像头,也可以拿来使用,以下是一个摄像头的不完整清单。并非所有功能都支持Linux驱动程序,但有些功能会支持。我插入了在亚马逊上买的价值20美元的通用HDMI到USB的适配器,它可以很好地工作,因此,我无需任何额外配置就可以将我的高端数码相机作为使用HDMI的视频源。
    除此之外,我们还需要其它设备,包括:至少具有32GB空间的microSD卡。我们将在此安装Linux。一个microSD读卡器:使用它可以安装Jetson软件。一个有线USB键盘和一个有线USB鼠标用来控制Jetson Nano。加载Jetson Nano 2GB软件首先我们需要下载Jetson Nano的软件映像。Nvidia的默认软件映像包括预装了Python 3.6和OpenCV的Ubuntu Linux 18.04。以下是将Jetson Nano软件安装到SD卡上的方法:从Nvidia下载Jetson Nano Developer Kit SD卡映像。下载Etcher,该程序将Jetson软件映像写入SD卡。运行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做人脸识别超级简单。它可以检测到人脸,并将每个检测到的人脸转换为唯一的人脸特征编码,然后比较这些特征编码以查看它们是否属于同一个人,该过程只需几行代码即可。face_recognition库:我使用该库构建了一个门铃摄像头的人脸识别应用程序,该应用程序可以识别走到你家门的人并对其进行跟踪。运行时如下所示:
    
    首先,请下载代码。我已经在此处添加了完整的代码和注释。但是这里有一个更简单的方法,可以从命令行下载到你的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
    我们在这里做了一些重要的事情:使用face_recogntion库,我们检查未知人脸与所有以前的访问者的相似程度。所述face_distance()函数为我们提供了未知脸部和所有已知人脸之间的相似性的数值测量——数字越小,人脸越相似。如果人脸与我们的一位已知访客非常相似,则我们可以假设他们是重复访客,在这种情况下,我们将更新它们的“上次拜访”时间,并增加在视频帧中看到这人脸的次数。最后,如果在最近的五分钟内有人在镜头前看到这个人,那么我们假设他们仍然在这里作为同一次访问的一部分,否则,我们假设这是对我们房屋的新访问,因此我们将重置跟踪他们最近访问的时间戳。程序的其余部分是主循环——一个无限循环,在该循环中,我们获取视频帧,在图像中查找人脸并处理我们看到的每个人脸。这是该程序的主要核心。让我们来看看: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行,但是它可以检测到访客,对其进行识别并每当他们来到你的家门时进行跟踪。扩展程序该程序是一个示例,说明了如何在便宜的Jetson Nano 2GB板上运行少量的Python 3代码来构建功能强大的系统。如果你想把它变成一个真正的门铃摄像头系统,你可以添加其它额外功能:当系统检测到门口有新人时,它会用Twilio向你发送短信,而不是仅仅在你的显示器上显示,或者你可以尝试用真实的数据库替换在内存中的人脸数据库。你也可以尝试将这个程序转换成完全不同的程序。阅读一帧视频,在图像中寻找内容,然后采取行动的模式是各种计算机视觉系统的基础。尝试更改代码,看看你能想出什么!比如当你回家走到自己家门口时,让它播放你自己定制的主题音乐怎么样?你可以查看其他一些人脸识别示例,以了解如何进行类似的操作。面部识别示例:了解有关Nvidia Jetson平台的更多信息如果你想了解有关使用Nvidia Jetson硬件平台进行构建的更多信息,Nvidia会提供新的免费Jetson培训课程。查看他们的网站以获取更多信息。他们也有很棒的社区资源,例如JetsonHacks网站。