圣诞老人检测机:基于树莓派和 Keras 的深度学习 AI
各位 MAKER 们大家圣诞节快乐啊!
热门美剧《硅谷》中的人物创建了一个“不是热狗”APP,可以确定输入的照片是“热狗”还是“不是热狗”。今天我们介绍如何使用Keras在Raspberry Pi上运行一个识别圣诞老人的深度神经网络,仅仅用130行代码,成功在圣诞老人来你家时点亮圣诞树同时播放圣诞歌曲来迎接!来试试看~
今天这篇文章是使用Keras在Raspberry Pi上运行深度神经网络的一个完整指南。
我把这个项目当做一个“不是圣诞老人”(Not Santa)检测器,教你如何实际地实现它(并且过程中乐趣无穷)。
第一部分,我们说一下什么是“圣诞老人检测器”(可能你不熟悉热播美剧《硅谷》里的“不是热狗”识别App,现在已经有人把它实现了)。
然后,我们将通过安装TensorFlow、Keras和其他一些条件来配置树莓派进行深度学习。
树莓派为深度学习配置好之后,我们将继续构建一个Python脚本,它可以:
- 从磁盘加载Keras模型
- 访问树莓派相机模块/usb网络摄像头
- 应用深度学习来检测圣诞老人是否在框内
- 如果发现圣诞老人,就可以访问GPIO pin并播放音乐
那么让我们开始吧!
什么是Not Santa检测器?
图1:HBO美剧《硅谷》里的“不是热狗”识别应用程序
“不是圣诞老人”检测器灵感来自HBO美剧《硅谷》。剧中人物创建了一个App,可以确定输入的照片是“热狗”还是“不是热狗”。这个节目显然是在取笑美国硅谷的创业文化:
- 对机器学习和深度学习的炒作
- 讽刺大量手机App都毫无用处(但发明者相信他们的App将会“改变世界”)。
今天,我们决定做一个“不是圣诞老人”检测器,它可以检测出圣诞老人是否在一个图像/视频框中。
为那些不熟悉圣诞老人的人简单说明一下,圣诞老人是一个诙谐、肥胖、白胡子、虚构的西方文化人物,会在圣诞前夜给小孩子送礼物。
不过,我们这个App并不完全是为了好玩和讽刺。我们将学习一些实用的技能,包括:
- 为深度学习配置树莓派
- 在树莓派上安装Keras和TensorFlow
- 部署一个预训练的卷积神经网络到你的树莓派上
- 一旦检测到,就执行一个给定的动作
硬件配置
但是在编写代码之前,让我们先来说一下我们需要的硬件。
图2:“不是圣诞老人”检测器的设置包括树莓派3、扬声器、3D圣诞树和一个网络摄像头,为了检测圣诞老人,树莓派在Python脚本中用Keras实现LeNet。
遵循这个教程,你需要:
- 树莓派3(同时强烈推荐树莓派3入门套件)
- 一个树莓派相机模块或一个USB相机。
- 适用树莓派的3D圣诞树
- 一组音箱
当然,这些不是全部必须的。只要有一个树莓派+相机模块/usb摄像头,就能全部设置好(但是必须修改代码,这样它就不会试图访问GPIO pins或通过音箱播放音乐)。
你的设置应该与上面的图2相似,把扬声器、3D圣诞树和网络摄像头连接在一起。我还推荐使用HDMI监视器+键盘来测试并调试脚本:
图3:我的深度学习设置包括树莓派和组件,以及键盘、鼠标和一个小的HDMI显示器。这样设置好后,圣诞老人来到我的圣诞树前放礼物时一定能抓到他。
怎样在树莓派上安装TensorFlow和Keras?
关于如何使用Kera来训练卷积神经网络,以确定圣诞老人是否处于输入的图像中,可以参考[1]:
我们将采用预训练的模型,并将其部署到树莓派上。正如我之前提到的,树莓派不适合训练神经网络。但是,树莓派可以部署训练好的神经网络(当然,模型需要能够适应很小的内存占用空间)。
我假设你已经在树莓派上安装了OpenCV。如果还没有,可以看[2]的教程。
建议增加树莓派的交换空间,这能使你能够使用Raspberry Pi SD卡来增加内存(当尝试在内存限制的树莓派上编译和安装大型库时,这是一个关键步骤)。
要增加交换空间,打开 /etc/dphys-swapfile然后编辑CONF_SWAPSIZE变量:
- # set size to absolute value, leaving empty (default) then uses computed value
- # you most likely don't want this, unless you have a special disk situation
- # CONF_SWAPSIZE=100
- CONF_SWAPSIZE=1024
复制代码
我将交换空间从100MB增加到了1024MB。然后,重新启动交换服务器:
- $ sudo /etc/init.d/dphys-swapfile stop
- $ sudo /etc/init.d/dphys-swapfile start
复制代码
注意:增加交换空间容易烧毁存储卡,因此请确保恢复这个更改并在完成后重启交换服务器。
然后,我们开始配置开发环境。
首先,使用Python 2.7创建一个名为not_santa的Python虚拟环境:
- $ mkvirtualenv not_santa -p python2
复制代码
请注意,-p开关指向python2,指示Python 2.7将用于虚拟环境。
还要确保你已经把cv2.so绑定到not_santa虚拟环境中:
- $ cd ~/.virtualenvs/not_santa/lib/python2.7/site-packages
- $ ln -s /usr/local/lib/python2.7/site-packages/cv2.so cv2.so
复制代码
同样,要再次确认已经用Python 2.7绑定编译了OpenCV,还要仔细检查cv2.so文件的路径,以防安装路径与我的演示有不同。
如果你编译了Python 3 + OpenCV绑定,创建了sym-link,然后试图将cv2导入你的Python shell,你会得到一个令人困惑的路径回溯,说导入失败。
重要说明:对于接下来的几个pip命令,要确保你处于not_santa环境,否则你会把这些包安装到树莓派的系统Python中。
要进入环境,只需在bash提示符下使用workon命令:
然后,你会在bash提示符开头看到“(not_santa)”。
确保使用以下命令在not_santa环境中安装NumPy:
由于我们将访问该项目的GPIO pins,因此需要同时安装RPi.GPIO和gpiozero:
- $ sudo pip install RPi.GPIO gpiozero
复制代码
现在在树莓派上安装TensorFlow。问题是没有一个官方的(Google发布的)TensorFlow发行版,那就要在树莓派上从头开始编写TensorFlow,参考[3]。
或者我们可以使用预先编译的二进制文件Sam Abrahams(GitHub上有[4])。
问题是只有两种类型的预编译的TensorFlow二进制文件,一个用于Python 2.7,另一个用于Python 3.4。
Raspbian Stretch发行版附带了Python 3.5,因此,我们的版本不匹配。为了避免Python 3.4和Python 3.5之间的麻烦,我决定坚持使用Python 2.7安装。
让我们继续,使用以下命令为Python 2.7安装TensorFlow:
- $ wget https://github.com/samjabrahams/tensorflow-on-raspberry-pi/releases/download/v1.1.0/tensorflow-1.1.0-cp27-none-linux_armv7l.whl
- $ pip install tensorflow-1.1.0-cp27-none-linux_armv7l.whl
复制代码
TensorFlow编译和安装好后(我花了大约一个小时),然后需要安装HDF5和H5py。这些库将允许我们从磁盘加载预训练的模型:
- $ sudo apt-get install libhdf5-serial-dev
- $ pip install h5py
复制代码
最后,让我们安装Keras和这个项目所需的其他条件:
- $ pip install pillow imutils
- $ pip install scipy --no-cache-dir
- $ pip install keras
复制代码
为了测试你的配置,请打开一个Python shell(在not_santa环境中)并执行以下命令:
- $ workon not_santa
- $ python
- >>> import h5py
- >>> from gpiozero import LEDBoard
- >>> from gpiozero.tools import random_values
- >>> import cv2
- >>> import imutils
- >>> import keras
- Using TesnsorFlow backend.
- >>> print("OpenCV version: {}".format(cv2.__version__))
- '3.3.1'
- >>> print("Keras version: {}".format(keras.__version__))
- '2.0.8'
- >>> exit()
复制代码
如果一切按计划进行,你应该看到使用TensorFlow后端导入的Keras。
正如上面的输出所示,你还应该仔细检查OpenCV绑定(cv2)是否可以导入。
最后,不要忘记通过以下方式将交换空间从1024MB减到100MB:
- 打开/etc / dphys-swapfile
- 重置CONF_SWAPSIZE为100MB。
- 重新启动交换服务器
在树莓派运行Keras + 深度学习模型
现在我们准备使用Keras,TensorFlow和树莓派来编写一个Not Santa检测器。再次,我会假设你的硬件设置和我的一样,如果不一样,你需要修改下面的代码。
首先,请打开一个新文件,将其命名为not_santa_detector.py,插入以下代码:
- # import the necessary packages
- from keras.preprocessing.image import img_to_array
- from keras.models import load_model
- from gpiozero import LEDBoard
- from gpiozero.tools import random_values
- from imutils.video import VideoStream
- from threading import Thread
- import numpy as np
- import imutils
- import time
- import cv2
- import os
复制代码
第2-12行处理输入,特别是:
- keras:用于预处理输入帧进行分类,并从磁盘加载预训练的模型。
- gpiozero:用于访问3D圣诞树。
- imutils:用于访问视频流(无论是树莓派相机模块还是USB)。
- threading:用于non-blocking操作,尤其是当我们要点亮圣诞树或播放音乐的同时不阻塞主线程的执行。
然后,定义一个函数来点亮3D圣诞树:
- def light_tree(tree, sleep=5):
- # loop over all LEDs in the tree and randomly blink them with
- # varying intensities
- for led in tree:
- led.source_delay = 0.1
- led.source = random_values()
-
- # sleep for a bit to let the tree show its Christmas spirit for
- # santa clause
- time.sleep(sleep)
-
- # loop voer the LEDs again, this time turning them off
- for led in tree:
- led.source = None
- led.value = 0
复制代码
light_tree函数接受一个tree参数(被设为一个LEDBoard对象)。
首先,我们循环tree中的所有LED,并随机点亮每个LED,以产生“闪烁”效果(17-19行)。
我们让灯亮一段时间(第23行),然后再次循环LED,然后把它关掉(26-28行)。
下面是一个打开3D圣诞树灯的例子:
图6:Raspberry Pi的3D圣诞树
当检测到圣诞老人时,下一步是播放音乐
- def play_christmas_music(p):
- # construct the command to play the music, then execute the
- # command
- command = "aplay -q {}".format(p)
- os.system(command)
复制代码
在play_christmas_music函数中,对aplay命令进行系统调用,从而能够像从命令行那样播放音乐文件。
然后,让我们硬编码将使用的配置:
- # define the paths to the Not Santa Keras deep learning model and
- # audio file
- MODEL_PATH = "santa_not_santa.model"
- AUDIO_PATH = "jolly_laugh.wav"
-
- # initialize the total number of frames that *consecutively* contain
- # santa along with threshold required to trigger the santa alarm
- TOTAL_CONSEC = 0
- TOTAL_THRESH = 20
-
- # initialize is the santa alarm has been triggered
- SANTA = False
复制代码
第38行和第39行将硬编码到预训练的Keras模型和音频文件的路径。文末的下载可以获取音频文件。
初始化用于检测的参数,包括TOTAL_CONSEC和TOTAL_THRESH。这两个值表示包含圣诞老人的帧的数量以及我们将分别播放音乐和打开树的阈值(第43行和第44行)。
最后的初始化是SANTA = False,一个boolean(第47行)。我们稍后将在脚本中使用SANTA变量作为状态标志。
接下来,加载预训练的Keras模型并初始化圣诞树:
- # load the model
- print("[INFO] loading model...")
- model = load_model(MODEL_PATH)
-
- # initialize the christmas tree
- tree = LEDBoard(*range(2, 28), pwm=True)
复制代码
Keras允许我们将模型保存到磁盘以供将来使用。Not Santa模型已经保存到了磁盘上[1],那么我们把它加载到树莓派上。第51行使用Keras load_model函数加载了模型。
第54行实例化tree对象。如图所示,tree是gpiozero包中的一个LEDBoard对象。
然后初始化视频流:
- # initialize the video stream and allow the camera sensor to warm up
- print("[INFO] starting video stream...")
- vs = VideoStream(src=0).start()
- # vs = VideoStream(usePiCamera=True).start()
- time.sleep(2.0)
复制代码
要访问摄像机,在imutils包中使用VideoStream
重要提示:如果你想在本项目中使用PiCamera模块(而不是USB摄像头),只需注释第58行并取消第59行的注释即可。
Sleep 两秒钟,以便相机预热(第60行),然后开始循环播放帧:
- # loop over the frames from the video stream
- while True:
- # grab the frame from the threaded video stream and resize it
- # to have a maximum width of 400 pixels
- frame = vs.read()
- frame = imutils.resize(frame, width=400)
复制代码
第63行,我们开始循环播放视频帧,直到满足停止条件(稍后在脚本中显示)。
首先,通过调用vs.read来获取一个frame(第66行)。
然后调整frame为width= 400,保持纵横比(第67行)。在喂入神经网络模型之前预处理这个frame。稍后,我们将显示框架以及文本标签。
然后预处理图像,并通过Keras +深度学习模型进行预测:
- # prepare the image to be classified by our deep learning network
- image = cv2.resize(frame, (28, 28))
- image = image.astype("float") / 255.0
- image = img_to_array(image)
- image = np.expand_dims(image, axis=0)
-
- # classify the input image and initialize the label and
- # probability of the prediction
- (notSanta, santa) = model.predict(image)[0]
- label = "Not Santa"
- proba = notSanta
复制代码
第70-73行预处理图像并准备分类。然后,我们查询model.predict与image作为参数。这向神经网络发送image,返回包含类概率的tuple(第77行)。
我们将label初始化为“Not Santa”,并将概率proba初始化为第78和79行中notSanta的值。
我们来看看圣诞老人是否在图像中:
- # check to see if santa was detected using our convolutional
- # neural network
- if santa > notSanta:
- # update the label and prediction probability
- label = "Santa"
- proba = santa
-
- # increment the total number of consecutive frames that
- # contain santa
- TOTAL_CONSEC += 1
复制代码
在83行检查圣诞老人的概率是否大于notSanta。如果是,就继续更新label和proba,然后递增TOTAL_CONSEC(85-90行)。
如果连续提供了足够的“Santa”帧,就需要触发圣诞老人警报:
- # check to see if we should raise the santa alarm
- if not SANTA and TOTAL_CONSEC >= TOTAL_THRESH:
- # indicate that santa has been found
- SANTA = True
-
- # light up the christmas tree
- treeThread = Thread(target=light_tree, args=(tree,))
- treeThread.daemon = True
- treeThread.start()
-
- # play some christmas tunes
- musicThread = Thread(target=play_christmas_music,
- args=(AUDIO_PATH,))
- musicThread.daemon = False
- musicThread.start()
复制代码
如果SANTA为False,并且TOTAL_CONSEC达到TOTAL_THRESH阈值,就有两个操作要执行:
- 创建并启动一个treeThread来闪烁圣诞树灯(98-100行)。
- 创建并启动一个musicThread在后台播放音乐(103-106行)。
这些线程将独立运行,不停止脚本的正向执行(即非阻塞操作)。
在第95行,我们将我们的SANTA状态标志设置为True,意味着我们在输入框架中找到了圣诞老人。 在循环的下一个loop中,我们将像第93行那样查看这个值。
否则(SANTA为True或TOTAL_THRESH未满足),我们将TOTAL_CONSEC重置为零,并将SANTA重置为False:
- # otherwise, reset the total number of consecutive frames and the
- # santa alarm
- else:
- TOTAL_CONSEC = 0
- SANTA = False
复制代码
最后,我们使用生成的文本标签将框架显示在屏幕上:
- # build the label and draw it on the frame
- label = "{}: {:.2f}%".format(label, proba * 100)
- frame = cv2.putText(frame, label, (10, 25),
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
-
- # show the output frame
- cv2.imshow("Frame", frame)
- key = cv2.waitKey(1) & 0xFF
-
- # if the `q` key was pressed, break from the loop
- if key == ord("q"):
- break
-
- # do a bit of cleanup
- print("[INFO] cleaning up...")
- cv2.destroyAllWindows()
- vs.stop()
复制代码
概率的值被附加到包含“圣诞老人”或“不是圣诞老人”的label(第115行)。
然后使用OpenCV的cv2.putText,可以在框架顶部显示标签(以圣诞节为主题的绿色),然后将框架显示在屏幕上(116-120行)。
无限while loop 的退出条件是在键盘上按下“q”键(121-125行)。如果循环的退出条件满足,则在脚本退出之前,break并执行第129行和第130行的一些清理。
完成!
回头看看这130行代码,这个框架/模板也可以很容易地用于树莓派上的其他深度学习项目。
现在,让我们来抓那个胖胖的,有胡子的,快活的圣诞老人吧!
深度学习+ Keras +树莓派结果
图7:我,Adrian Rosebrock,扮成圣诞老人。我将亲自测试使用深度学习,Keras,Python和OpenCV构建的“不是圣诞老人”检测器。
然后,我把相机朝着客厅里的圣诞树上的树莓派:
图8:圣诞树将作为测试已经部署到树莓派上的Not Santa深度学习模型的背景。
如果圣诞老人来为给我的好孩子们送礼物,我想确保通过闪烁3D圣诞树灯和播放圣诞歌曲来欢迎他。
然后,我使用以下命令启动了Not Santa深度学习+ Keras检测器:
- $ python not_santa_detector.py
复制代码
Not Santa 检测器启动并运行后,我就开始行动:
亲爱的圣诞老人:如果你读到了这里,要知道我会用树莓派找到你哦!
参考:
[1]https://www.pyimagesearch.com/2017/12/11/image-classification-with-keras-and-deep-learning
[2]https://www.pyimagesearch.com/2017/10/09/optimizing-opencv-on-the-raspberry-pi
[3]https://github.com/samjabrahams/tensorflow-on-raspberry-pi/blob/master/GUIDE.md
[4]https://github.com/samjabrahams/tensorflow-on-raspberry-pi