共计 5680 个字符,预计需要花费 15 分钟才能阅读完成。
git 地址:https://github.com/chenlinzho…
本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于 python2.7.10/opencv2/tensorflow1.7.0 环境,实现了从摄像头读取视频,检测人脸,识别人脸的功能由于模型文件过大,git 无法上传,整个项目放在百度云盘,地址:https://pan.baidu.com/s/1Taal…
人脸识别是计算机视觉研究领域的一个热点。目前,在实验室环境下,许多人脸识别已经赶上(超过)人工识别精度(准确率:0.9427~0.9920),比如 face++,DeepID3,FaceNet 等(详情可以参考:基于深度学习的人脸识别技术综述)。但是,由于光线,角度,表情,年龄等多种因素,导致人脸识别技术无法在现实生活中广泛应用。本文基于 python/opencv/tensorflow 环境,采用 FaceNet(LFW:0.9963)为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。下文主要内容如下:
利用 htm5 video 标签打开摄像头采集头像并使用 jquery.faceDeaction 组件来粗略检测人脸
将人脸图像上传到服务器,采用 mtcnn 检测人脸
利用 opencv 的仿射变换对人脸进行对齐,保存对齐后的人脸
采用预训练的 facenet 对检测的人脸进行 embedding,embedding 成 512 维度的特征;
对人脸 embedding 特征创建高效的 annoy 索引进行人脸检测
人脸采集
采用 html5 video 标签可以很方便的实现从摄像头读取视频帧,下文代码实现了从摄像头读取视频帧,faceDection 识别人脸后截取图像上传到服务器功能在 html 文件中添加 video,canvas 标签
<div class=”booth”>
<video id=”video” width=”400″ height=”300″ muted class=”abs” ></video>
<canvas id=”canvas” width=”400″ height=”300″></canvas>
</div>
打开网络摄像头
var video = document.getElementById(‘video’),
var vendorUrl = window.URL || window.webkitURL;
// 媒体对象
navigator.getMedia = navigator.getUserMedia || navagator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
navigator.getMedia({video: true, // 使用摄像头对象 audio: false // 不适用音频}, function(strem){
video.src = vendorUrl.createObjectURL(strem);
video.play();
});
利用 jquery 的 facetDection 组件检测人脸
$(‘#canvas’).faceDetection()
检测出人连脸的话截图,并把图片转换为 base64 的格式,方便上传
context.drawImage(video, 0, 0, video.width, video.height);
var base64 = canvas.toDataURL(‘images/png’);
将 base64 格式的图片上传到服务器
// 上传人脸图片
function upload(base64) {
$.ajax({
“type”:”POST”,
“url”:”/upload.php”,
“data”:{‘img’:base64},
‘dataType’:’json’,
beforeSend:function(){},
success:function(result){
console.log(result)
img_path = result.data.file_path
}
});
}
图片服务器接受代码,php 语言实现
function base64_image_content($base64_image_content,$path){
// 匹配出图片的格式
if (preg_match(‘/^(data:\s*image\/(\w+);base64,)/’, $base64_image_content, $result)){
$type = $result[2];
$new_file = $path.”/”;
if(!file_exists($new_file)){
// 检查是否有该文件夹,如果没有就创建,并给予最高权限
mkdir($new_file, 0700,true);
}
$new_file = $new_file.time().”.{$type}”;
if (file_put_contents($new_file, base64_decode(str_replace($result[1], ”, $base64_image_content)))){
return $new_file;
}else{
return false;
}
}else{
return false;
}
}
人脸检测
人脸检测方法有许多,比如 opencv 自带的人脸 Haar 特征分类器和 dlib 人脸检测方法等。对于 opencv 的人脸检测方法,有点是简单,快速;存在的问题是人脸检测效果不好。正面 / 垂直 / 光线较好的人脸,该方法可以检测出来,而侧面 / 歪斜 / 光线不好的人脸,无法检测。因此,该方法不适合现场应用。对于 dlib 人脸检测方法,效果好于 opencv 的方法,但是检测力度也难以达到现场应用标准。本文中,我们采用了基于深度学习方法的 mtcnn 人脸检测系统(mtcnn:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks)。mtcnn 人脸检测方法对自然环境中光线,角度和人脸表情变化更具有鲁棒性,人脸检测效果更好;同时,内存消耗不大,可以实现实时人脸检测。本文中采用 mtcnn 是基于 python 和 tensorflow 的实现(代码来自于 davidsandberg,caffe 实现代码参见:kpzhang93)
model= os.path.abspath(face_comm.get_conf(‘mtcnn’,’model’))
class Detect:
def __init__(self):
self.detector = MtcnnDetector(model_folder=model, ctx=mx.cpu(0), num_worker=4, accurate_landmark=False)
def detect_face(self,image):
img = cv2.imread(image)
results =self.detector.detect_face(img)
boxes=[]
key_points = []
if results is not None:
#box 框
boxes=results[0]
#人脸 5 个关键点
points = results[1]
for i in results[0]:
faceKeyPoint = []
for p in points:
for i in range(5):
faceKeyPoint.append([p[i], p[i + 5]])
key_points.append(faceKeyPoint)
return {“boxes”:boxes,”face_key_point”:key_points}
具体代码参考 fcce_detect.py
人脸对齐
有时候我们截取的人脸了头像可能是歪的,为了提升检测的质量,需要把人脸校正到同一个标准位置,这个位置是我们定义的,假设我们设定的标准检测头像是这样的
假设眼睛,鼻子三个点的坐标分别是 a(10,30) b(20,30) c(15,45),具体设置可参看 config.ini 文件 alignment 块配置项
采用 opencv 仿射变换进行对齐, 获取仿射变换矩阵
dst_point=【a,b,c】
tranform = cv2.getAffineTransform(source_point, dst_point)
仿射变换:
img_new = cv2.warpAffine(img, tranform, imagesize)
具体代码参考 face_alignment.py 文件
产生特征
对齐得到后的头像,放入采用预训练的 facenet 对检测的人脸进行 embedding,embedding 成 512 维度的特征,以 (id,vector) 的形式保存在 lmdb 文件中
facenet.load_model(facenet_model_checkpoint)
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”)
face=self.dectection.find_faces(image)
prewhiten_face = facenet.prewhiten(face.image)
# Run forward pass to calculate embeddings
feed_dict = {images_placeholder: [prewhiten_face], phase_train_placeholder: False}
return self.sess.run(embeddings, feed_dict=feed_dict)[0]
具体代码可参看 face_encoder.py
人脸特征索引:
人脸识别的时候不能对每一个人脸都进行比较,太慢了,相同的人得到的特征索引都是比较类似,可以采用 KNN 分类算法去识别, 这里采用是更高效 annoy 算法对人脸特征创建索引,annoy 索引算法的有个假设就是,每个人脸特征可以看做是在高维空间的一个点,如果两个很接近(相识),任何超平面都无法把他们分开,也就是说如果空间的点很接近,用超平面去分隔,相似的点一定会分在同一个平面空间(具体参看:https://github.com/spotify/annoy)
#人脸特征先存储在 lmdb 文件中格式(id,vector), 所以这里从 lmdb 文件中加载
lmdb_file = self.lmdb_file
if os.path.isdir(lmdb_file):
evn = lmdb.open(lmdb_file)
wfp = evn.begin()
annoy = AnnoyIndex(self.f)
for key, value in wfp.cursor():
key = int(key)
value = face_comm.str_to_embed(value)
annoy.add_item(key,value)
annoy.build(self.num_trees)
annoy.save(self.annoy_index_path)
具体代码可参看 face_annoy.py
人脸识别
经过上面三个步骤后,得到人脸特征,在索引中查询最近几个点并就按欧式距离,如果距离小于 0.6(更据实际情况设置的阈值)则认为是同一个人,然后根据 id 在数据库查找到对应人的信息即可
#根据人脸特征找到相似的
def query_vector(self,face_vector):
n=int(face_comm.get_conf(‘annoy’,’num_nn_nearst’))
return self.annoy.get_nns_by_vector(face_vector,n,include_distances=True)
具体代码可参看 face_annoy.py
安装部署
系统采用有两个模块组成:
face_web:提供用户注册登录,人脸采集,php 语言实现
face_server: 提供人脸检测,裁剪,对齐,识别功能,python 语言实现
模块间采用 socket 方式通信通信格式为: length+content
face_server 相关的配置在 config.ini 文件中
1. 使用镜像
face_serverdocker 镜像: shareclz/python2.7.10-face-image
face_web 镜像: skiychan/nginx-php7
假设项目路径为 /data1/face-login
2. 安装 face_server 容器
docker run -it –name=face_server –net=host -v /data1:/data1 shareclz/python2.7.10-face-image /bin/bash
cd /data1/face-login
python face_server.py
3. 安装 face_web 容器
docker run -it –name=face_web –net=host -v /data1:/data1 skiychan/nginx-php7 /bin/bash
cd /data1/face-login;
php -S 0.0.0.0:9988 -t ./web/
最终效果:
face_server 加载 mtcnn 模型和 facenet 模型后等待人脸请求
未注册识别失败
人脸注册
注册后登录成功
参考
https://zhuanlan.zhihu.com/p/…
https://github.com/spotify/annoy
https://blog.csdn.net/just_so…
https://blog.csdn.net/oTengYu…