0 引言
人脸识别技术日趋成熟,开源的人脸识别库使大众更容易开发自己的应用方案,降低学习门槛,如旷视科技、商汤科技、海康威视,百度人脸识别SDK、OpenCV的人脸识别库、Python的第三方人脸识别库等;传统的web系统是通过登录页面输入用户名及密码的方式实现,本文研究讨论一种人脸识别登录方式,大致介绍其功能设计及实现原理。
本文使用的是Python的Django框架搭建web页面,使用Python的第三方库face_recognition作为人脸识别工具,验证了通过人脸识别登录系统的可行性。
1 所用技术
1.1 Django框架
Django是Python的开源web开发框架,可以快速开发网站架构,采用MVC(Model、View、Control)的开发模式,使前端视图,后端数据存储,页面逻辑控制分离,极大的提高了网站的开发效率。也是目前大多网站采样的开发模式,笔者最近在学的微信小程序开发也是采用这种MVC的开发模式。具体Django资料参考官方网站,关于Django的部分总结,可参考博文Django 学习笔记,【慕课网】强力Django和杀手级xadmin学习笔记
1.2 Ajax
在网站中比较常见的一种应用技术是Ajax,异步请求,即点击某个按钮,不重新加载整个页面,而是刷新部分页面,比如加载一个图片或者更新一个表格等都是用的这种异步请求原理。
在js中,通过Ajax请求,将前端的数据传送至后台,使用Python语句对其进行处理,并将处理后的数据返回前端。js中Ajax请求格式为:
$.ajax ({
type:"POST",
url:"/login/loginFaceCheck/",
beforeSend:function (xhr,setting) {
xhr.setRequestHeader("X-CSRFToken","{ { csrf_token }}");
},
dataType:'json',
data:{
"id":ID,
"faceImg":RightImage
},
success:function (displayList) {
// 返回成功
},
error:function () {
// 返回失败
}
})
注:
传送的类型可以为post和get,一般来说post的方式比get方式更加安全,在网页中form类型的表单的提交一般都是采用的post。而且本次采用post还有一个原因,data中的faceImg是base64格式编码的图片,数据特别特别长,采用get方式会报错,说url传输太长。
注意到Django中post表单的传送都会有{ { csrf_token}},这是一种安全机制。但是这次是按钮传送,而不是前端的form表单,因此必须要加上一句话,beforeSend:function (xhr,setting) {xhr.setRequestHeader("X-CSRFToken","{ { csrf_token }}");,在传送前强制加上{ { csrf_token}}
有传送的url,则只需要在django的urls中做相应的配置,即可运行对应的函数。在函数中必须返回json格式的数据,才能进入返回成功success函数中。
1.3 face_recognition
Python的第三方开源人脸识别库face_recognition的安装及使用说明参见官方github,地址为:https://github.com/ageitgey/face_recognition。总的来说其识别率达到99.38%以上。
1.4 tracking
javascript的开源第三方库tracking,A modern approach for Computer Vision on the web,具体可以参考其github,地址为https://github.com/eduardolundgren/tracking.js。其在本次实现中其的作用仅仅是在web页面加载摄像头,并框选出人脸部分。
1.5 其他
还用到的一些技术知识包括Html、CSS、Javascript、BootStrap、JQuery,不过用的只是一些皮毛的技术。
2 实施方案
2.1 操作流程
具体操作步骤是:点击身份核验按钮,判断是否有权限,并在前台显示。当两个摄像头都完成认证,且为不同的人脸时,完成验证,登录系统。
初始为未认证,见图1;如果未检测到人脸见图2;检测到人脸但未授权见图3;检测到人脸且授权见图4;在10s内未完成另一个摄像头的验证,则显示超时见图5.如果在10s内两个摄像头都完成人脸验证,则进入系统;当两个摄像头验证为同一个人时,报错,见图6;注:本次左右摄像头图像一样,是因为都用的是笔记本的摄像头。
图1 未认证
图2 未检测到人脸
图3(将人脸库中笔者的人脸删掉进行验证)未授权
图4 认证成功
图5 超时
图6 相同用户
程序大致流程包括:加载页面,加载人脸库,web页面加载摄像头并显示,摄像头获取的图片传至后台,后台解析图片并保存在本地,图片与人脸库对比,返回核验结果,前端显示,间隔时间判断,是否相同人脸判断等。操作流程见图7.其中右侧摄像头的程序流程与左相同。
图7 核验流程
2.2 部分代码
从前端获取图片并利用Ajax传送至后端,从后端获取核验结果在前端显示部分代码
// 身份认证标志及名字
var IdentifyNo1 = false;
var IdentifyNo2 = false;
var AuthNameNo1 ='';
var AuthNameNo2 ='';
// 计时器
var timeout;
$(document).ready(function () {
// 按钮 身份验证
$("#CheckNo1").click(function () {
var video = document.getElementById('video');
var canvas = document.getElementById('canvasL');
var context = canvas.getContext('2d');
context.drawImage(video,0,0,canvas.width,canvas.height);
var LeftImage = canvas.toDataURL("image/png");
var ID = 0;
// 利用Ajax 技术,将登陆界面人脸图片base64编码格式的图片传至后台
$.ajax ({
type:"POST",
url:"/login/loginFaceCheck/",
//必须添加 csrf_token
beforeSend:function (xhr,setting) {
xhr.setRequestHeader("X-CSRFToken","{ { csrf_token }}");
},
dataType:'json',
data:{
"id":ID,
"faceImg":LeftImage
},
success:function (displayList) {
// 处理认证后的数据
if (displayList.canLogin === true){
$('#DisplayNo1').text("验证成功: "+displayList.AuthName).removeClass("label-danger").addClass("" +
"label-success");
IdentifyNo1 = true;
AuthNameNo1 = displayList.AuthName;
if (IdentifyNo2 === true){
clearTimeout(timeout);
}
else{
// 开始计数,10s内,另一个如果没完成则验证失败
timeout = setTimeout(function () {
$('#DisplayNo1').text("验证失败: 超时").removeClass("label-success").addClass("label-danger");
IdentifyNo1 = false;
AuthNameNo1 = '';
},10000);
}
LoginSys(IdentifyNo1 ,AuthNameNo1, IdentifyNo2,AuthNameNo2);
}
else{
$('#DisplayNo1').text("验证失败: "+displayList.AuthName).removeClass("label-success").addClass("label-danger");
IdentifyNo1 = false;
AuthNameNo1 = '';
}
},
error:function () {
$('#DisplayNo1').text("验证失败: 未检测到人脸").removeClass("label-success").addClass("label-danger");
IdentifyNo1 = false;
AuthNameNo1 = '';
}
})
});
后端view处理函数
def loginFaceCheck(request):
## 人脸登陆验证
if request.method == "POST" and request.is_ajax():
# 获取base64格式的图片
faceImage = request.POST.get('faceImg')
# 提取出base64格式,并进行转换为图片
index = faceImage.find('base64,')
base64Str = faceImage[index+6:]
img = base64.b64decode(base64Str)
# 将文件保存
backupDate = time.strftime("%Y%m%d_%H%M%S")
if int(request.POST.get('id')) == 0 :
fileName = BASE_LOGIN_LEFT_PATH +"LeftImg_%s.jpg" % (backupDate)
else:
fileName = BASE_LOGIN_RIGHT_PATH + "RightImg_%s.jpg" % (backupDate)
file = open(fileName, 'wb')
file.write(img)
file.close()
# 删除多余的图片
filesLeft = os.listdir(BASE_LOGIN_LEFT_PATH)
filesLeft.sort()
leftImgCount = filesLeft.__len__()
filesRight = os.listdir(BASE_LOGIN_RIGHT_PATH)
filesRight.sort()
RightImgCount = filesRight.__len__()
if leftImgCount > 100:
# 图片超过100个,删除一个
os.unlink(BASE_LOGIN_LEFT_PATH +filesLeft[0])
if RightImgCount > 100:
# 图片超过100个,删除一个
os.unlink(BASE_LOGIN_RIGHT_PATH + filesRight[0])
# 对图片进行人脸识别比对
canLogin = False
AuthName = "未授权用户"
# 1> 加载相机刚拍摄的人脸
unknown_face = face_recognition.load_image_file(fileName)
unknown_face_tmp_encoding = []
try:
unknown_face_tmp_encoding = face_recognition.face_encodings(unknown_face)[0]
except IndexError:
canLogin = False # 图片中未发现人脸
# 2> 进行比对
### 第一种方法
# results = face_recognition.face_distance(known_face,unknown_face_tmp_encoding)
# 小于0.6即对比成功。但是效果不好,因此我们设置阈值为0.4,
# for i, face_distance in enumerate(results):
# if face_distance <= 0.4:
# canLogin = True
# AuthName = os.listdir(BASE_LOGIN_AUTH_PATH)[i][:-4]
### 第二中方法
results1 = face_recognition.compare_faces(known_face,unknown_face_tmp_encoding,0.4)
for i, face_distance in enumerate(results1):
if face_distance == True:
canLogin = True
AuthName = os.listdir(BASE_LOGIN_AUTH_PATH)[i][:-4]
JsonBackInfo = {
"canLogin": canLogin,
"AuthName": AuthName
}
return JsonResponse(JsonBackInfo)
3 小结
通过人脸识别可以完成web页面的登录功能,且有较高的正确识别率。这种方案避免了手动输入表单的繁琐,同时通过双身份认证人脸识别登录系统保证了系统的安全性。不足之处,一是需要提前在后端把已知的管理员人脸图片放入目录内,二是由于人脸较多,在初次加载页面时加载时间长。
以上,望读者大神们批评指正~
二赛君原创文章,转载请注明出处。
注:有偿提供开发技术指导,提供源代码。扫码,码上解决您的问题。