新亚马逊门铃买家分享使用感受,开发应用助力家庭轻松定制,提高安全便利
我是亚马逊门铃的新买家,我很喜欢它具备的炫酷功能。但我觉得它还可以有一些改进之处。我需要的是专门为住在我家的人定制的门铃。如果门铃能够认出敲门的人就好了。看到门铃很受欢迎,我决定帮助大多数家庭,最好的办法就是让他们能够轻松地定制自己的门铃。

我开发了一个应用程序,它能告诉你谁在你的门口。你只需输入你的门铃帐户的用户名和密码,就可以实现这一功能。无需等待门铃在你的智能手机上显示视频,就能知道谁在门口,这十分方便。这个应用程序大大提高了安全性,带来了极大的便利,甚至还可以安装在一个自动开门的系统上。在深度学习时代,每个家庭都有必要安装这些系统。[id_1682446523]
完整的代码可以在这里的Git存储库中找到。
要求如下:
tensorflow==2.4.1
opencv-python==4.5.1.48
mtcnn==0.1.0
ring_doorbell==0.7.0
oauthlib~=3.1.0
numpy~=1.19.5
scipy~=1.6.1
scikit-learn==0.24.1
gtts==2.2.2
playsound~=1.2.2
让我们分析一下情况。将用户名和密码作为环境变量输入后,Ring API 就可以连接到你的账户。此 API 允许用户使用 python 特性。这里有 API 存储库以及简短的文档。
这是 Ring.py 的一个部分,它将与你的门铃的连接进行了实例化。
import os
import json
from pathlib import Path
from ring_doorbell import Ring, Auth
from oauthlib.oauth2 import MissingTokenError
cache_file = Path("test_token.cache")
def token_updated(token):
cache_file 把 token 进行 JSON 序列化后,将其写入文本内容中。
def otp_callback():
auth_code = input("[INPUT] 2FA code: ")
return auth_code
def main(download_only=False):
if cache_file.is_file():
auth = Auth("MyProject/1.0"先读取缓存文件的内容,然后将其解析为 JSON 数据,最后判断令牌是否已更新。
else:
username 是从 os.environ 中获取到的。'USERNAME')
password 等于 os.environ 获取到的内容。'PASSWORD')
auth = Auth("MyProject/1.0", None, token_updated)
try:
auth 调用 fetch_token 方法并传入 username 和 password
except MissingTokenError:
auth 调用 fetch_token 函数,传入 username、password 以及 otp_callback() 的返回值
ring = Ring(auth)
ring.update_data()
等待更新(ring,仅下载=download_only)
wait_for_update方法持续运行,它实例化了一个正在等待客户端的处理程序。该方法会持续进行刷新操作,直到发现 Ring 的存储历史记录有更新。当发生这种情况时,它会检查门铃是否被按了。如果门铃被按了,它就会把整个视频下载到设备上。为加快这一过程,需使用智能手机上的 ring 应用程序来缩小视频录制的大小。
你的门铃响了后,最后一段视频就会传输到你的电脑上。从电脑那儿,我们截取了那段视频的多个帧,目的是确保一个人的脸都不会被遮住。我在 utils.py 里定义了这个方法,它将在稍后进行显示。下面是 ring.py 的另一个用于处理主线程的片段。
import time
def wait_for_update(ring, download_only=False):
id = -1
start = time.time()
while True:
try:
ring 完成了数据的更新工作。
except:
time.sleep(1)
continue
门铃 = 发出声响的设备()'authorized_doorbots'][0]
for event in doorbell.history(limit=20, kind='ding'):
current_id 等于 event 中的某个元素。 具体来说,current_id 是从 event 这个对象或列表等数据结构中获取到的一个特定的值。 它可能是 event 中的一个键对应的值,也可能是 event 中的一个元素本身。 总之,current_id 与 event 有着特定的关联,通过这种关联获取到了当前的 id 值。'id']
break
if current_id != id:
id = current_id
print('[INFO] finished search in:', str(time.time() - start))
开始时间 = time.time()
if download_only:
处理视频(ring)。True)
return
handle 等于对 ring 进行 handle_video 操作。
if handle:
使用 handle 来进行语音合成操作。
else:
语言风格需保持原文风格,需去掉最前面的序号,将长句拆分成多个小分句,且分句要尽可能短,同时要保留原意,禁止修改专有名词,禁止穿插英文单词,仅需给出改写结果,无需复述原句,也无需换行和做任何解释。门口的那个人不是很清楚。)
time.sleep(1)
如果你对 identify 方法的调用有些困惑,不要担心。如果你对 get_first_frame 方法的调用有些困惑,不要担心。如果你对 text_to_speech 方法的调用有些困惑,不要担心!我们马上就要谈到这些了!现在我们的处理程序已经准备就绪,让我们开始进行面部识别吧!
FaceNet
FaceNet 是由谷歌在 2015 年研发的一个模型。FaceNet 运用了一种被称作聚类的流程。
聚类的目的是创建一种类似单词的嵌入。其唯一区别在于,该模型并非学习向量标记的 id,而是把图像压缩至一个小的潜在空间。具体而言,当给定一幅形状为(160,160,3)的图像时,FaceNet 模型会产生一个形状为(128)的矢量,此矢量被称为它的嵌入。该模型能让不同人的面孔在嵌入空间中的距离拉得较远,而同一个人的面孔距离则会较近。如此一来,一个人不管处于何种光线条件之下,无论从何种角度去看,亦或是有着何种妆容,都能够被辨认出来。
FaceNet架构
FaceNet与 ResNet 和 InceptionV3 有相似之处。其架构具体如下:输入的图像会先经过 1x1Conv 层以及 2x2Pooling 层,接着沿着深度 ResNet 的方向下行,其中包含成对的 Inception 层和残差连接层。最后的层由多个 3x3Conv 层、Concat 层以及 2x2Pooling 层组成。
加载模型的代码较为简洁。模型被存储在目录 model/files/里。
from tensorflow.keras.models import load_model
model = load_model('model/facenet_keras.h5')
开发一个模型以概括它此前未曾见过的面孔是较为困难的。FaceNet 模型在 MS-Celeb-1M 数据集上进行了训练,此数据集涵盖了 100 万张不同名人的照片。借助对同一个人的图像组进行 L2 归一化以及运用余弦相似函数,FaceNet 能够获得令人惊叹的高识别精度。
我发明了一种便捷的方式去登记你家人的面孔。首先运行 submit_face.py ,接着传递参数“name”(即要注册的人的名字)。此外,为提升准确性以及匹配照明条件,你可以运用布尔参数“from_door”,倘若该参数为真,就会直接从你的门铃最后录制的视频中保存图像。
这些图像被存于目录 data/faces/内。先对它们进行预裁剪,使用的是 MTCNN 人脸检测。稍后会展示检测方法,该方法是 face_recognition.py 的一部分。对于所拍视频,我抓取了其特定帧,并测试哪些帧能够正常工作。我们需要进行一些图像预处理以及其他一些小的函数,这些我将在 utils.py 中进行定义。
import cv2
def normalize(img):
img 的均值是 mean,img 的标准差是 std 。
return (img - mean) / (std + 1e-7)
def preprocess(cv2_img):
cv2_img 经过了归一化处理,即 cv2_img 等于经过归一化操作后的 cv2_img 。
cv2_img 进行了尺寸调整,将其调整为( 宽度, 高度 )的大小,这里的宽度和高度具体数值为( 具体宽度数值, 具体高度数值 ),调整后的图像赋值给了 cv2_img 。160, 160))
return cv2_img
def get_specific_frames(video_path, times):
vidcap 被创建为 cv2.VideoCapture 并传入 video_path 作为参数。
frames = []
for time in times:
vidcap.set(1, time * 15)
success 且 image 等于 vidcap.read()
if success:
frames 会添加进 image 中。
return frames
如果每个人的图像都在目录 data/faces/中且你想要识别他们,那么我们就能够将这些图像转换为编码。我们将此作为一个单独的步骤,原因是我们对每个人对应的所有图像进行了 L2 标准化。
import os
from utils import preprocess
import cv2
import numpy as np
from sklearn.preprocessing import Normalizer
import face_recognition
import pickle
encoding_dict = {}
l2_normalizer = Normalizer('l2')
for face_names in os.listdir('data/faces/'):
person_dir 等于 os.path.join 函数的结果。'data/faces/', face_names)
encodes = []
for image_name in os.listdir(person_dir):
image_path 是将 person_dir 和 image_name 进行拼接得到的路径。具体来说,image_path 等于 person_dir 与 image_name 组合在一起的结果。
使用 cv2 库的 imread 函数读取图像路径对应的图像,将其赋值给变量 face 。
face 经过预处理后得到 face 。
使用 face_recognition 对 face 进行编码,编码结果赋值给 encoding
将 encoding 添加到 encodes 列表中。
if encodes:
encoding 等于 np.sum(encodes, 轴为)0)
encoding 等于 l2_normalizer 对 np.expand_dims(encoding, axis=)进行转换。 首先 np.expand_dims(encoding, axis=) 对 encoding 进行维度扩展操作,然后 l2_normalizer 对扩展后的结果进行转换,最终得到 encoding 的值。0))[0]
encoding_dict 中包含了 face_names ,并且将 encoding 赋值给了 encoding_dict 中与 face_names 对应的位置。
path = 'data/encodings/encoding.pkl'
with open(path, 'wb') as file:
使用 pickle 模块将 encoding_dict 保存到 file 中。 pickle 把 encoding_dict 写入到 file 里。 通过 pickle 把 encoding_dict 存储到 file 处。 pickle 把 encoding_dict 倾倒进 file 当中。 借助 pickle 将 encoding_dict 存入 file 内。 利用 pickle 把 encoding_dict 放置到 file 之上。 凭借 pickle 把 encoding_dict 放进 file 里面。 用 pickle 把 encoding_dict 写入 file 之中。 以 pickle 为工具把 encoding_dict 保存在 file 里。 借助 pickle 的功能把 encoding_dict 存储到 file 处。 通过 pickle 的作用把 encoding_dict 写入到 file 里。 利用 pickle 的手段把 encoding_dict 放置到 file 之上。 凭借 pickle 的方式把 encoding_dict 放进 file 里面。 用 pickle 的方法把 encoding_dict 写入 file 之中。
预处理函数的作用是标准化图像并将其重塑为(160,160,3)。识别函数是一个执行编码函数的类。我将这些编码保存为字典,如果你注意到了这一点的话。在执行实时识别时,这个字典很方便,因为它能简单地存储人名和编码。
实时人脸识别
现在我们拥有了我们希望识别的那个人的图像,那么实时识别的过程是怎样运作的呢?如下图所示:
门铃响起之时,进行视频的下载操作,并且选择多个帧。借助这些帧,运用 detect_faces 方法来实施多实例的人脸检测。以下是 face_recognition.py 类的一个部分内容:
import cv2
import mtcnn
face_detector = mtcnn.MTCNN()
conf_t = 0.99
def detect_faces(cv2_img):
cv2_img 经过 cv2.cvtColor 函数处理,将其从 BGR 颜色空间转换为 RGB 颜色空间,得到的结果是 img_rgb
首先,通过 face_detector 对 img_rgb 进行检测人脸的操作,然后将检测到的结果赋值给 results 。
faces = []
for res in results:
x1 以及 y1 还有 width 和 height 等于 res 中的内容。'box']
x1 的绝对值为 abs(x1),y1 的绝对值为 abs(y1)
x2 等于 x1 加上宽度,y2 等于 y1 加上高度。
confidence = res['confidence']
if confidence < conf_t:
continue
将 cv2_img 从 y1 行到 y2 行、从 x1 列到 x2 列的部分添加到 faces 列表中。
return faces
def detect_face(cv2_img):
img_rgb = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB)
results = face_detector.detect_faces(img_rgb)
x1 是 results 中的一部分,y1 也是 results 中的一部分,width 同样是 results 中的一部分,height 亦是 results 中的一部分。0]['box']
cv2.waitKey(1)
x1, y1 = abs(x1), abs(y1)
x2 等于 x1 加上 width,y2 等于 y1 加上 height
confidence = results[0]['confidence']
if confidence < conf_t:
return None
return cv2_img[y1:y2, x1:x2]
对图像进行预处理后送入 FaceNet 。FaceNet 会输出每个人脸的 128 维嵌入。接着使用余弦相似度来把这些向量和 encode.pkl 中存储的向量进行比较。人脸中与输入人脸最为接近的那个人会被返回。倘若一张脸距离它最近的脸达到了一个特定的阈值,就会返回“未知”。这意味着这张脸不像任何已知的脸。下面是face_recognition.py类的其余部分:
from utils import preprocess
from model.facenet_loader import model
import numpy as np
from scipy.spatial.distance import cosine
import pickle
from sklearn.preprocessing import Normalizer
l2_normalizer = Normalizer('l2')
def encode(img):
img 被进行了将其维度进行扩展的操作,扩展的维度是在轴 axis 上进行的,并且扩展后的维度为 1,即 img 被扩展了一个维度,这个维度位于轴 axis 上。0)
out = model.predict(img)[0]
return out
def load_database():
with open('data/encodings/encoding.pkl', 'rb') as f:
使用 pickle 模块的 load 函数将文件 f 中的数据加载到 database 中。
return database
recog_t = 0.35
def recognize(img):
人们等于检测人脸(img)。
if len(people) == 0:
return None
best_people = []
people 等于对 person 进行预处理后得到的结果。for person in people]
编码等于对人进行编码操作所得的结果。for person in people]
encoded 等于 l2_normalizer 对 encoding 进行 reshape 操作后的结果进行转换1, -1))[0]
for encoding in encoded]
数据库 = 加载数据库()
for person in encoded:
best = 1
best_name = ''
for k, v in database.items():
计算 person 与 v 的余弦相似度,结果赋值给 dist 。
if dist < best:
best = dist
best_name = k
if best > recog_t:
best_name = 'UNKNOWN'
将 best_name 添加到 best_people 列表中。
return best_people
这样就完成了大部分的识别任务。
语音合成
我想知道在门口的是谁。起初,我认为在铃声设备上播放声音是最好的策略,然而,亚马逊不允许我这样做,只准许我播放铃声所伴随的默认声音。所以,从文本转换为语音似乎是一种更为恰当的方式。这种方式可以借助两个包 GTTS 和 playsound 来实现。GTTS 运用的是谷歌的 Tacotron 2 模型。它的工作原理完全理解起来并非重要之事。然而,对于那些感兴趣的读者而言,该图能够说明其架构。
Tacotron 和 Seq2Seq 较为相似。它采用了双向 LSTM 。它还运用了卷积层。它具备预网络层。其中最重要的是将 2D 生成输入到解码器(光谱图)。若你想知晓更多关于 Tacotron 2 的情况,这里有一个由 CodeEmporium 创作的关于此主题的视频。
Tacotron 2 不是最好的,尤其和 transformer 模型相比较而言,不过它确实达成了一些成果。使用 GTTS python API 的方式如下:
from gtts import gTTS
from playsound import playsound
language = 'en'
slow_audio_speed = False
filename = 'tts_file.mp3'
def text_to_speech(text):
audio_created 是通过 gTTS 生成的,其中 text 为指定的文本,lang 为指定的语言。
slow 等于 slow_audio_speed。
audio_created 保存了 filename 。
playsound(filename)
很简单。
我使用 playsound 而非 os.system 的原因在于,os.system 会默认打开默认的声音播放器应用程序,然而 playsound 不会弹出任何窗口。通过这样的方式,完成了项目的最后一个步骤。
总结和Git存储库
请查看我的 git 存储库来获取完整的代码,并且能够轻松地定制你自己的门铃。
查看 README.md 中的说明,并且解释在自己家里使用这个系统的具体步骤。安装只需 5 分钟!亚马逊,将其放入你的下一个门铃中!
进一步的探索和问题
FaceNet 是一种较为陈旧的模式。在过去的五年间,在 transformer 模型领域取得了重大的发现,像 ViT 就是其中之一。GPT-3 有着很强的概括能力。当完成创建广义嵌入的任务后,像 GPT-3 这样的转换器会工作得更好吗?卷积神经网络或许不是面部识别的最佳选择,因为像耳朵或下颚线这样的长期依赖关系需要庞大的网络。一方面,transformer 模型能够顾及到自相似性。另一方面,它在实时进行人脸识别时的速度要快很多。
参考文献
END
看到这里,就表明你喜欢这篇文章,那就转发、点赞吧。可以通过微信搜索「uncle_pn」,也欢迎添加小编微信「mthler」,每日朋友圈会更新一篇高质量博文。
↓扫描二维码添加小编↓