本项目一共分为三个部分:
- esp32传输视频流至电脑
- facenet实现视频流的人脸识别
- 将esp32传输至电脑的视频流进行识别
- 识别出目标人物后,电脑自动切屏
目录
- 所需器材
- esp32接线图
- esp32开发环境配置
- 代码及实现效果
- 环境配置
- facenet源码获取
- 安装和配置facenet环境
- 下载LFW数据集
- 对LFW数据集进行预处理
- 下载训练好的网络模型
- 评估预训练模型的准确率
- 人脸对比
- 收集自己的数据集
- 训练分类器
- 视频流的人脸识别
- opencv读取esp32传输的视频流
- 对读取的视频流进行识别
- 环境配置
- 代码实现
- get_faces.py
- real_time_recognition.py
- face.py
- esp32_cam.py
- screen.py
一、esp32传输视频流 {#1}
-
所需器材
器材 | 数量 |
---|
esp32-CAM(含摄像头) | 1枚 | USB转TTL转接头 | 1枚 | 杜邦线 | 若干 |
-
esp32接线图
-
运行调试模式接线图: -
下载模式接线图: -
详情请参考csdn博客。 -
esp32开发环境配置
-
打开Arduino IDE,在左上角的文件的首选项中添加附加开发板管理网址:http://arduino.esp8266.com/stable/package_esp8266com_index.json ,https://dl.espressif.com/dl/package_esp32_index.json ,https://www.jianshu.com/p/1e72a6a7cb7b . -
添加完成后,选择工具–>开发板–>开发板管理器,在搜索框中搜索ESP32并安装。 -
安装完成之后,点击工具–>开发板–>ESP32 Arduino,选择开发板型号为AL Thinker ESP32-CAM 。 -
代码及实现效果
-
Arduino本身自带的示例中有现成的代码,我们只需要稍作修改,并不用自己来写。 -
打开文件–>示例–>ESP32–>Camera–>CameraWebServer文件,然后对代码做以下修改:将代码中原本的摄像头定义注释掉,使用AI THINKER的摄像头定义。 -
修改完后将指定位置改为自己的WiFi名和密码,之后将esp32设置为下载模式,开始编译并上传代码。 -
如果代码报错,请检查是否安装WiFi库,若未安装请打开项目–>加载库–>管理库搜索WiFi并安装。 -
代码烧录完毕后,将esp32设置为运行调试模式,按RSt 键,之后打开串口管理器,将波特率设为115200 ,在浏览器中输入串口返回的url即可。 -
在网页的最下端点击Start Stream 即可看到视频流。
二、facenet实现视频流人脸识别 {#2}
-
环境配置
- python==3.6
- tensorflow==1.7
- scikit-learn
- opencv-python
- h5py
- matplotlib
- Pillow
- requests
- psutil
- scipy==1.2.1
- pandas
- numpy==1.16.1
-
facenet源码获取
-
安装和配置facenet环境
-
在电脑Anconda3\Lib\site-packages 目录下新建一个名为facenet 的文件夹。因为这里我单独安装了一个虚拟环境,所以路径有些不一样,这个根据实际情况而定。 -
然后,将facenet-master\src 文件夹下的所有文件都复制到刚刚新建的facenet文件夹下。 -
然后,将facenet文件夹中名为align 的文件复制到site-packages 文件夹下。 -
下面开始配置环境变量,打开设置–>系统–>关于–>高级系统设置。 打开环境变量,在用户变量中新建一个名为PYTHONPATH 的变量,并将之前的facenet 文件夹的路径填入变量名。
- 在
cmd 中输入set 查看设置情况。 -
下载LFW数据集
-
LFW数据集是由美国马萨诸塞大学阿姆斯特分校计算机视觉实验室整理的人脸检测数据集,是评估人脸识别算法效果的公开测试数据集,全称为带标签的自然人脸数据库(Labeled Faces in the Wild)。 -
LFW数据库内每张图片命名方式为“lfw/name/name_xxxx.jpg”,这里“xxxx”是前面补零的四位图片编号。例如,前美国总统乔治?W?布什的第10张图片为“lfw/George_W_Bush/George_W_Bush_0010.jpg”。 -
LFW数据库 总共有 13233 张 JPEG 格式图片,属于 5749 个不同人。每张图片尺寸都是 250x250。 -
数据库下载地址:http://vis-www.cs.umass.edu/lfw/lfw.tgz -
百度云地址,提取码rzm5 。 -
下载完数据集后在facenet-master\data 文件夹下新建一个名为lfw 的文件夹,将数据集解压进lfw 文件夹下,并在lfw 文件夹下再新建一个名为lfw_160 的文件夹。 -
对LFW数据集进行预处理
-
LFW数据集中所有图片的大小均为250*250 ,我们需要将所有的数据集处理成预训练模型所使用的160*160 的大小并保存到lfw_160 文件夹下。 -
打开Anconda Prompt 定位到facenet-master 文件夹下,输入如下命令进行校准: python src\align\align_dataset_mtcnn.py --help
-
再输入如下命令: python src\align\align_dataset_mtcnn.py data/lfw/lfw data/lfw/lfw_160 --image_size 160 --margin 32 --random_order --gpu_memory_fraction 0.25
这个命令中需要两个相对路径作为参数,第一个路径为原数据集的lfw 文件夹,第二个路径为保存处理后的数据集的lfw_160 文件夹,请根据自己的实际情况填写。 -
代码运行成功结果如下: -
下载训练好的网络模型
-
facenet提供了两个预训练模型,分别是基于CASIA-WebFace和MS-Celeb-1M人脸库训练的,如下:
-
谷歌云盘的下载速度很慢,这里有百度云的地址:
-
这两个模型任选其一即可,我用的是基于数据集CASIA-WebFace采用Inception ResNet v1神经网络结构训练好的模型。 -
模型下载完之后,将其解压至facenet-master\src\models 文件夹下。 -
其实我们也可以去训练自己的模型,但是会比较慢,所以这里我们就直接使用这些预训练模型。 -
评估预训练模型的准确率
-
在Anaconda Propmt下定位到facenet-master文件夹下。 -
输入以下命令: python src\validate_on_lfw.py data\lfw\lfw_160 src\models\20180408-102900
这里仍然需要输入两个路径作为参数,第一个是存放处理后的数据集的lfw_160 文件夹,第二个是预训练模型的路径,请根据实际情况填写。 -
结果如图: -
人脸对比
-
facenet可以直接对比两个人脸经过它的网络映射之后的欧氏距离,以此来判断这两张人脸是否为同一个人。 -
在facenet-master\src 文件下存放两张人脸图片。 -
在Anaconda Propmt中定位到facenet-master\src 文件夹下,输入以下命令: python compare.py models\20180408-102900 1.jpg 2.jpg
这里需要输入三个路径作为参数,第一个是预训练模型的路径,剩下两个是两张图片的路径。 -
运行结果如下: -
成功完成以上步骤后,代表我们下载的facenet源码已经调试完毕,可以进行人脸识别,接下来我们要进行视频流的人脸识别。 -
收集自己的数据集
-
在facenet-master\src 文件夹下新建一个名为my_lfw 的文件夹,用来存放我们的数据集。再新建一个名为my_lfw_160 的文件夹用来存放处理后的数据集。 -
然后再my_lfw 文件夹下新建几个文件夹,文件夹的名字就是你收集的人脸的名字,文件夹的数量根据人数而定。 -
收集人脸的代码有很多,我从csdn上随便找了一个: import cv2
def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
cv2.namedWindow(window_name)
cap = cv2.VideoCapture(camera_idx)
data_path = "C:\ProgramData\Anaconda3\pkgs\libopencv-3.4.2-h20b85fd_0\Library\etc\haarcascades\haarcascade_frontalface_default.xml"
classfier = cv2.CascadeClassifier(data_path)
color = (0, 255, 0)
num = 0
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects) > 0:
for faceRect in faceRects:
x, y, w, h = faceRect
img_name = '%s/%d.jpg ' %(path_name, num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image)
num += 1
if num > catch_pic_num:
break
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame ,'num:%d' % (num) ,(x + 30, y + 30), font, 1, (255 ,0 ,255) ,4)
if num > catch_pic_num:
break
cv2.imshow(window_name, frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
CatchPICFromVideo('1', 0, 100, 'D://VS Code/my_facenet/my_faces/CJL')
这段代码中的函数一共有四个参数,分别为:窗口名称、摄像头编号或视频路径、图片数量、图片储存路径。 -
然后我们要将这些数据集同样处理成160*160 的大小。 -
在Anaconda Propmt中定位到facenet-master\src 文件夹下,输入以下命令: python align\align_dataset_mtcnn.py my_lfw my_lfw_160 --image_size 160 --margin 32 --random_order --gpu_memory_fraction 0.25
-
训练分类器
-
有了自己的人脸数据集后,我们就可以开始训练自己的分类器了。 -
在Anaconda Propmt中定位到facenet-master 文件夹下,输入以下命令: python classifier.py TRAIN my_lfw models\20180408-102900 my_classifier.pkl
这个命令需要四个参数:TRAIN 是设置代码为训练模式、数据集路径、预训练模型路径、训练出的分类器的路径。 -
结果如下: -
视频流的人脸识别
-
facenet的源码中有视频流人脸识别的demo,就是facenet-master\contributed\real_time_recognition.py ,我们直接拿来使用就好。 -
但是我们要对这个demo进行一些修改。 -
首先打开facenet-master\contributed\face.py ,将下图中预训练模型和分类器的路径都换成自己的路径,如果相对路径不好用,就用绝对路径。 -
经过这样的修改后,运行real_time_recognition.py 就可以进行人脸识别了,但是这个demo有一个缺点,那就是当遇到数据集中没有的人时,仍然会给他标注数据集中的人的名字,不会标注Other。而且我们需要根据实际情况对识别的准确度进行调整,所以我们仍需修改代码。 -
打开face.py ,对其进行如下修改: 修改前: 修改后: 其中,标注出来的数值根据实际情况自行决定其大小。 修改后该部分的代码: class Identifier:
def __init__(self):
with open(classifier_model, 'rb') as infile:
self.model, self.class_names = pickle.load(infile)
def identify(self, face):
if face.embedding is not None:
predictions = self.model.predict_proba([face.embedding])
i = 0
while i < len(predictions[0]):
if predictions[0][i] > 0.6:
break
i += 1
else:
return None
print(predictions)
best_class_indices = np.argmax(predictions, axis=1)
print(best_class_indices[0])
return self.class_names[best_class_indices[0]]
-
然后打开real_time_recognition.py ,在图中位置添加如下代码: else:
cv2.putText(frame, 'Others', (face_bb[0], face_bb[3]), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), thickness=2, lineType=2)
-
修改完毕之后,就可以正常进行人脸识别了。
三、将esp32传输至电脑的视频流进行识别 {#3}
-
opencv读取esp32传输的视频流
-
esp32传输至电脑的视频流时传输在一个网页上的,但是我们进行人脸识别时需要将视频流读取至python,这就需要用到opencv中的VideoCapture() 函数。 -
一般情况下我们使用VideoCapture() 只需要从摄像头编号和视频路径两个参数中任选一个,其实VideoCapture() 还可以将视频流的url作为参数来读取视频流。 -
首先我们要获取esp32传输的视频流的url,在esp32生成的网页中,在视频流处于播放的状态下,右击视频的播放窗口,选择复制图片地址,此时得到的就是视频流的url。 -
在获取视频的url之后,我们首先尝试一下opencv能否成功读取视频流。新建一个python文件,输入以下代码: import cv2
cv2.namedWindow('esp32')
cap = cv2.VideoCapture('http://192.168.137.81:81/stream')
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
cv2.imshow('esp32', frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这里只需要将VideoCapture() 函数中的参数改成自己的url即可。 -
运行代码的时候需要将网页上的视频流暂停或者关闭网页,否则将无法读取视频流。 -
视频流可以成功播放之后,则opencv可以成功读取视频流。 -
对读取的视频流进行识别
-
正常当我们可以成功读取视频流之后,只需直接将real_time_recognition.py 中的VideoCapture(0) 修改为VideoCapture(url) 即可。但是由于esp32的摄像头视频的编码格式与facenet可以处理的视频流编码格式不同,直接这样做程序在读取第一帧图片后会直接报错,导致程序直接停止运行,所以我们要解决这个问题。 -
最好的解决方法时修改视频流的编码格式,但是我并没有找到有关这方面的很好的解决办法,所以我使用了一种很笨的方法:既然程序只能读取视频流的第一帧图片,那么我们就在读取完第一正图片后,用release() 释放视频流,然后再通过VideoCapture() 读取视频流,在读取完一帧图片后再次释放视频流,以此循环,同样可以得到完整的视频流。这样虽然会导致帧率下降,但是并不影响正常的使用。 -
在facenet-master\contributed 文件夹下新建一个python文件,取名为esp32_cam.py ,在里面输入以下代码: import cv2
def video():
cap = cv2.VideoCapture("http://192.168.137.81:81/stream")
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
break
cap.release()
return frame
-
打开real_time_recognition.py ,首先在代码开始添加import esp32_cam ,然后注释代码的以下几行: 之后在图中的位置添加frame = esp32_cam.video() 。 -
完成之后就可以正常的使用了。
四、识别出指定人物后,电脑切屏 {4}
- 环境配置
- 代码实现
-
在facenet-master\contributed 文件夹下新建一个名为screen.py 的python文件,输入以下代码: import pyautogui
def screen():
pyautogui.keyDown('alt')
pyautogui.press('tab')
pyautogui.keyUp('alt')
if __name__ == '__main__':
screen()
-
打开real_time_recognition.py ,在代码开头加上import screen 、import pyautogui ,然后再图示位置添加my_name = [] 。 再在图示位置添加以下代码: my_face = []
for i in faces:
my_face.append(i.name)
if ('XR' in my_face) and ('XR' not in my_name):
screen.screen()
if ('XR' not in my_face) and ('XR' in my_name):
screen.screen()
my_name = my_face
-
这样这个项目就算完成了,因为这里我用的是alt + tab 的快捷键进行切屏,所以有许多缺陷,大家可以去尝试更好的方法。
五、主要代码 {#5}
在最后放上主要用到的修改好的python代码。
- get_faces.py
import cv2
def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
cv2.namedWindow(window_name)
cap = cv2.VideoCapture(camera_idx)
data_path = "C:\ProgramData\Anaconda3\pkgs\libopencv-3.4.2-h20b85fd_0\Library\etc\haarcascades\haarcascade_frontalface_default.xml"
classfier = cv2.CascadeClassifier(data_path)
color = (0, 255, 0)
num = 0
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects) > 0:
for faceRect in faceRects:
x, y, w, h = faceRect
img_name = '%s/%d.jpg ' %(path_name, num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image)
num += 1
if num > catch_pic_num:
break
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame ,'num:%d' % (num) ,(x + 30, y + 30), font, 1, (255 ,0 ,255) ,4)
if num > catch_pic_num:
break
cv2.imshow(window_name, frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
CatchPICFromVideo('1', 0, 100, 'D://VS Code/my_facenet/my_faces/CJL')
- real_time_recognition.py
"""Performs face detection in realtime.
Based on code from https://github.com/shanren7/real_time_face_recognition
"""
import argparse
import sys
import time
from tkinter.constants import NO
import cv2
import face
import time
import pyautogui
import esp32_cam
import screen
def add_overlays(frame, faces, frame_rate):
if faces is not None:
for face in faces:
face_bb = face.bounding_box.astype(int)
cv2.rectangle(frame,
(face_bb[0], face_bb[1]), (face_bb[2], face_bb[3]),
(0, 255, 0), 2)
if face.name is not None:
cv2.putText(frame, face.name, (face_bb[0], face_bb[3]),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0),
thickness=2, lineType=2)
else:
cv2.putText(frame, 'Others', (face_bb[0], face_bb[3]),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0),
thickness=2, lineType=2)
cv2.putText(frame, str(frame_rate) + " fps", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0),
thickness=2, lineType=2)
def main(args):
my_name = []
frame_interval = 3
fps_display_interval = 5
frame_rate = 0
frame_count = 0
face_recognition = face.Recognition()
start_time = time.time()
if args.debug:
print("Debug enabled")
face.debug = True
while True:
my_face = []
frame = esp32_cam.video()
if (frame_count % frame_interval) == 0:
faces = face_recognition.identify(frame)
end_time = time.time()
if (end_time - start_time) > fps_display_interval:
frame_rate = int(frame_count / (end_time - start_time))
start_time = time.time()
frame_count = 0
add_overlays(frame, faces, frame_rate)
frame_count += 1
cv2.imshow('Video', frame)
my_face = []
for i in faces:
my_face.append(i.name)
if ('XR' in my_face) and ('XR' not in my_name):
screen.screen()
if ('XR' not in my_face) and ('XR' in my_name):
screen.screen()
my_name = my_face
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
def parse_arguments(argv):
parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true',
help='Enable some debug outputs.')
return parser.parse_args(argv)
if __name__ == '__main__':
main(parse_arguments(sys.argv[1:]))
- face.py
"""Face Detection and Recognition"""
import pickle
import os
import cv2
import numpy as np
import tensorflow as tf
from scipy import misc
import align.detect_face
import facenet
gpu_memory_fraction = 0.3
facenet_model_checkpoint = "d://VS Code/facenet-master/src/models/20180408-102900"
classifier_model = "d://VS Code/my_facenet/my_classifier_2.pkl"
debug = False
class Face:
def __init__(self):
self.name = None
self.bounding_box = None
self.image = None
self.container_image = None
self.embedding = None
class Recognition:
def __init__(self):
self.detect = Detection()
self.encoder = Encoder()
self.identifier = Identifier()
def add_identity(self, image, person_name):
faces = self.detect.find_faces(image)
if len(faces) == 1:
face = faces[0]
face.name = person_name
face.embedding = self.encoder.generate_embedding(face)
print(face.embedding)
return faces
def identify(self, image):
faces = self.detect.find_faces(image)
for i, face in enumerate(faces):
if debug:
cv2.imshow("Face: " + str(i), face.image)
face.embedding = self.encoder.generate_embedding(face)
face.name = self.identifier.identify(face)
return faces
class Identifier:
def __init__(self):
with open(classifier_model, 'rb') as infile:
self.model, self.class_names = pickle.load(infile)
def identify(self, face):
if face.embedding is not None:
predictions = self.model.predict_proba([face.embedding])
i = 0
while i < len(predictions[0]):
if predictions[0][i] > 0.6:
break
i += 1
else:
return None
print(predictions)
best_class_indices = np.argmax(predictions, axis=1)
print(best_class_indices[0])
return self.class_names[best_class_indices[0]]
class Encoder:
def __init__(self):
self.sess = tf.Session()
with self.sess.as_default():
facenet.load_model(facenet_model_checkpoint)
def generate_embedding(self, face):
images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")
prewhiten_face = facenet.prewhiten(face.image)
feed_dict = {images_placeholder: [prewhiten_face], phase_train_placeholder: False}
return self.sess.run(embeddings, feed_dict=feed_dict)[0]
class Detection:
minsize = 20
threshold = [0.6, 0.7, 0.7]
factor = 0.709
def __init__(self, face_crop_size=160, face_crop_margin=32):
self.pnet, self.rnet, self.onet = self._setup_mtcnn()
self.face_crop_size = face_crop_size
self.face_crop_margin = face_crop_margin
def _setup_mtcnn(self):
with tf.Graph().as_default():
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=gpu_memory_fraction)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options, log_device_placement=False))
with sess.as_default():
return align.detect_face.create_mtcnn(sess, None)
def find_faces(self, image):
faces = []
bounding_boxes, _ = align.detect_face.detect_face(image, self.minsize,
self.pnet, self.rnet, self.onet,
self.threshold, self.factor)
for bb in bounding_boxes:
face = Face()
face.container_image = image
face.bounding_box = np.zeros(4, dtype=np.int32)
img_size = np.asarray(image.shape)[0:2]
face.bounding_box[0] = np.maximum(bb[0] - self.face_crop_margin / 2, 0)
face.bounding_box[1] = np.maximum(bb[1] - self.face_crop_margin / 2, 0)
face.bounding_box[2] = np.minimum(bb[2] + self.face_crop_margin / 2, img_size[1])
face.bounding_box[3] = np.minimum(bb[3] + self.face_crop_margin / 2, img_size[0])
cropped = image[face.bounding_box[1]:face.bounding_box[3], face.bounding_box[0]:face.bounding_box[2], :]
face.image = misc.imresize(cropped, (self.face_crop_size, self.face_crop_size), interp='bilinear')
faces.append(face)
return faces
- esp32_cam.py
import cv2
def video():
cap = cv2.VideoCapture("http://192.168.137.81:81/stream")
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
break
cap.release()
return frame
if __name__ == '__main__':
video()
- screen.py
import pyautogui
def screen():
pyautogui.keyDown('alt')
pyautogui.press('tab')
pyautogui.keyUp('alt')
if __name__ == '__main__':
screen()
|