文章目录
项目介绍
本项目主要实现了通过人脸识别的方式进行网页的登录。
网页采用Django框架开发,使用离线的face_recognition来进行人脸识别。登录完成后网页提供了一个人脸区域模糊的功能。
项目效果视频展示:人脸识别网页登录
项目源码:https://github.com/MicahYin/WebFaceLogin
人脸识别模块:face_recognition
文件上传模块:webuploader
开发环境:ubuntu 16.04
关注公众号 打代码的苏比特,获取java面试百问百答和面试高频算法题~
源码分析
1.用户注册,人脸录入
人脸录入前端代码:
录入人脸的主要思路是先在前端进行人脸检测,使用了轻量的前端人脸检测框架,tracking.js。若检测到人脸,通过javascript编写的postFace()函数将人脸图片传到后台,后台进行人脸验证成功后将图片存储。下次登录时,利用face_recognition进行对比,即可完成人脸登录
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="stylesheet" href="/static/css/login.css">
<link rel="apple-touch-icon" sizes="180x180" href="/static/assets/apple-icon-180x180.png">
<link href="/static/assets/favicon.ico" rel="icon">
<title>login page</title>
<link href="/static/css/main.3f6952e4.css" rel="stylesheet">
<script type="text/javascript" src="/static/js/main.70a66962.js"></script>
<script type="text/javascript" src="/static/js/jquery.js"></script>
<script type="text/javascript" src="/static/build/tracking-min.js"></script>
<script type="text/javascript" src="/static/build/data/face-min.js"></script>
<style>
video, canvas {
margin-left: 10px;
margin-top: 40px;
position: absolute;
}
</style>
<head/>
<body class="minimal">
<div id="site-border-left"></div>
<div id="site-border-right"></div>
<div id="site-border-top"></div>
<div id="site-border-bottom"></div>
<!-- Add your content of header-->
<header>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav ">
<li><a href="{% url 'facerecognition:index' %}">01 : 首页</a></li>
<li><a href="{% url 'facerecognition:face_recognition'%}">02 : 人脸模糊</a></li>
<li><a href="{% url 'facerecognition:about' %}">03 : 关于本站</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.session.is_login %}
<li><a>你好,{{ request.session.user_name }}</a></li>
<li><a href="{% url 'users:logout' %}">登出</a></li>
{% else %}
<li><a href="{% url 'users:login' %}">登录</a></li>
<li><a href="{% url 'users:register' %}">注册</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
</header>
<!-- /container -->
<div class="section-container">
<div class="container">
<div class="row-input-top">
<div class="col-xs-12">
<div class="section-container-spacer text-center">
<h1 class="h2">人脸录入</h1>
</div>
<div class="demo-container">
<video id="video" width="320" height="240" autoplay></video>
<canvas id="canvas" width="320" height="240"></canvas>
</div>
</div>
</div>
</div>
</div>
<script>
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
function postFace() {
setTimeout(function () {
context.drawImage(video,0,0,320,240);
img=canvas.toDataURL('image/jpg')
{#获取完整的base64编码#}
img=img.split(',')[1]
//将照片以base64用ajax传到后台
$.ajax({
type:"POST",
url:'face_entry_getface/',
//必须添加 csrf_token
beforeSend:function (xhr,setting) {
xhr.setRequestHeader("X-CSRFToken","{{ csrf_token }}");
},
data:{
message:img
},
success:function (callback) {
if(callback=='no'){
postFace()
}else {
alert( '录入成功!' );
window.location.href=callback
}
},
error:function (callback) {
postFace()
}
})
},300)
}
var tracker = new tracking.ObjectTracker('face');
tracker.setInitialScale(4);
tracker.setStepSize(2);
tracker.setEdgesDensity(0.1);
tracking.track('#video', tracker, { camera: true });
tracker.on('track', function(event) {
context.clearRect(0, 0, canvas.width, canvas.height);
event.data.forEach(function(rect) {
//将获取图片放在此处
postFace();
context.strokeStyle = '#a64ceb';
context.strokeRect(rect.x, rect.y, rect.width, rect.height);
context.font = '11px Helvetica';
context.fillStyle = "#fff";
context.fillText('x: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
context.fillText('y: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 22);
});
});
</script>
</body>
</html>
人脸录入后端代码:
先对前端传递来的图像进行解码,随后将人脸图片按照注册用户名保存到指定路径。
def face_entry_getface(request):
"""保存注册的截图"""
if request.method == 'POST':
print("begin保存注册的截图")
register_name = request.session.get('register_name')
strs = request.POST['message']
imgdata = base64.b64decode(strs)
file_path = os.path.join('users', 'static', 'face_data', 'register', register_name + ".png")
try:
file = open(file_path, 'wb+')
file.write(imgdata)
file.close()
except Exception as err:
print(err)
if shape_pic(file_path):
# request.session['register_name'] = None
return HttpResponse('login') # 自动跳转到登录页面
return HttpResponse('no')
2.人脸识别登录部分
登录界面的前端代码
同注册界面一样,先使用tracking.js在前端识别当前摄像头是否拍到人脸,这样可以提高程序运行的效率。同样的,捕获到人脸后通过JS的postFace()函数将人脸数据传输到后台进行对比。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="stylesheet" href="/static/css/login.css">
<link rel="apple-touch-icon" sizes="180x180" href="/static/assets/apple-icon-180x180.png">
<link href="/static/assets/favicon.ico" rel="icon">
<title>login page</title>
<link href="/static/css/main.3f6952e4.css" rel="stylesheet">
<style>
video, canvas {
margin-left: 10px;
margin-top: 20px;
position: absolute;
}
</style>
<head/>
<body class="minimal">
<div id="site-border-left"></div>
<div id="site-border-right"></div>
<div id="site-border-top"></div>
<div id="site-border-bottom"></div>
<!-- Add your content of header-->
<header>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav ">
<li><a href="{% url 'facerecognition:index' %}">01 : 首页</a></li>
<li><a href="{% url 'facerecognition:face_recognition'%}">02 : 人脸模糊</a></li>
<li><a href="{% url 'facerecognition:about' %}">03 : 关于本站</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.session.is_login %}
<li><a>你好,{{ request.session.user_name }}</a></li>
<li><a href="{% url 'users:logout' %}">登出</a></li>
{% else %}
<li><a href="{% url 'users:login' %}">登录</a></li>
<li><a href="{% url 'users:register' %}">注册</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
</header>
<!-- Add your site or app content here -->
<!-- /container -->
<div class="section-container">
<div class="container">
<div class="row-input-top">
<div class="col-xs-12">
<div class="section-container-spacer text-center">
<h1 class="h2">欢迎登录</h1>
</div>
<div class="demo-container">
<video id="video" width="320" height="240" autoplay></video>
<canvas id="canvas" width="320" height="240"></canvas>
</div>
<div class="row-input text-center">
<div class="col-md-10 col-md-offset-1">
<form action="{% url 'users:login' %}" class="reveal-content" method="post">
{% if message %}
<div class="alert alert-warning">{{ message }}</div>
{% endif %}
<div class="row-input-top">
<div class="col-md-7">
{% csrf_token %}
<div class="form-group">
{{ login_form.username }}
<!-- <input type="text" class="form-control" id="username" placeholder="{{ login_form.username.label_tag }}">-->
</div>
<div class="form-group">
{{ login_form.password }}
<!-- <input type="password" class="form-control" id="password" placeholder="{{ login_form.password.label_tag }} ">-->
</div>
<button type="reset" class="btn btn-default pull-left">重置</button>
<button type="submit" class="btn btn-default pull-right">登录</button>
<p>没有账户?点此<a href="{% url 'users:register' %}">注册</a></p>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
function getUserMediaToPhoto(constraints,success,error) {
if(navigator.mediaDevices.getUserMedia){
//最新标准API
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
}else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints,success,error);
}else if(navigator.mozGetUserMedia){
//firefox浏览器
navigator.mozGetUserMedia(constraints,success,error);
}else if(navigator.getUserMedia){
//旧版API
navigator.getUserMedia(constraints,success,error);
}
}
//成功回调函数
function success(stream){
if ("srcObject" in video) {
video.srcObject = stream;
} else {
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function(e) {
video.play();
};
postFace()
}
function error(error) {
console.log('访问用户媒体失败:',error.name,error.message);
}
function postFace() {
setTimeout(function () {
context.drawImage(video,0,0,320,240);
img=canvas.toDataURL('image/jpg')
{#获取完整的base64编码#}
img=img.split(',')[1]
//将照片以base64用ajax传到后台
$.ajax({
type:"POST",
url:'get_face/',
//必须添加 csrf_token
beforeSend:function (xhr,setting) {
xhr.setRequestHeader("X-CSRFToken","{{ csrf_token }}");
},
data:{
message:img
},
success:function (callback) {
if(callback=='no'){
postFace()
}else {
window.location.href=callback
}
},
error:function (callback) {
postFace()
}
})
},300)
}
if(navigator.mediaDevices.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia)
{
getUserMediaToPhoto({video:{width:320,height:240}},success,error);
}else{
alert('你的浏览器不支持访问用户媒体设备');
}
</script>
<script type="text/javascript" src="/static/js/main.70a66962.js"></script>
<script type="text/javascript" src="/static/js/jquery.js"></script>
</body>
</html>
人脸登录后台代码:
后台通过get_face()函数接受到人脸图像,对其进行解码,随后将其保存到人脸存储的临时路径。通过check_face()函数传入需要对比的两张图片的路径,进行人脸对比
def get_face(request):
"""人脸登录验证"""
if request.POST:
time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
strs = request.POST['message']
imgdata = base64.b64decode(strs)
file_path = os.path.join('users', 'static', 'face_data', 'confirm', time + ".png")
try:
file = open(file_path, 'wb+')
file.write(imgdata)
file.close()
username = check_face('./users/static/face_data/register/', file_path)
if username:
# 此处会有bug,数据库用户和人脸识别用户不一致的问题
request.session['is_login'] = True
request.session['user_name'] = username
return HttpResponse('index')
else:
return HttpResponse('no')
except Exception as err:
print(err)
finally:
delete_pic('./users/static/face_data/confirm/')
return HttpResponse('no')
check_face()人脸对比代码,调用的是face_recognition库进行离线对比,如若对比成功,则返回文件名(即当前登录的用户名),否则返回None
def check_face(known_path, unknown_path):
"""
用于人脸判别
:param known_path:
:param unknown_path:
:return:
"""
picture_of_known = [] # 已知的图片文件
known_faces_encoding = [] # 已知的图片编码
# 加载图片
files = file_name(known_path)
for i in range(len(files)):
picture_of_known.append(face_recognition.load_image_file(known_path+files[i]))
unknown_picture = face_recognition.load_image_file(unknown_path)
# 编码图片
for i in range(len(picture_of_known)):
known_face_encodings = face_recognition.face_encodings(picture_of_known[i])
if len(known_face_encodings) > 0:
known_faces_encoding.append(known_face_encodings[0])
unknown_face_encoding = face_recognition.face_encodings(unknown_picture)
if len(unknown_face_encoding) > 0:
unknown_face_encoding = unknown_face_encoding[0]
# 人脸验证的结果
results = face_recognition.compare_faces(known_faces_encoding, unknown_face_encoding, tolerance=0.5)
for i in range(len(results)):
if results[i]:
file = files[i].split(".")[0]
print("file:"+file)
return file
return None
3.文件上传部分
图片模糊前端代码
文件上传部分主要是通过WebUploader组件来实现的,将图片/视频上传到后台,后台识别其中的人脸部分,对其进行高斯模糊。对于视频这种大文件,采取的策略是分块上传之后进行合并。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1" name="viewport">
<meta content="description" name="description">
<meta name="google" content="notranslate"/>
<meta content="Mashup templates have been developped by Orson.io team" name="author">
<!-- Disable tap highlight on IE -->
<meta name="msapplication-tap-highlight" content="no">
<link rel="apple-touch-icon" sizes="180x180" href="/static/assets/apple-icon-180x180.png">
<link href="/static/assets/favicon.ico" rel="icon">
<title>Title page</title>
<link rel="stylesheet" type="text/css" href="/static/css/webuploader.css">
<link rel="stylesheet" type="text/css" href="/static/css/demo.css">
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
<link href="/static/css/main.3f6952e4.css" rel="stylesheet">
</head>
<body class="">
<div id="site-border-left"></div>
<div id="site-border-right"></div>
<div id="site-border-top"></div>
<div id="site-border-bottom"></div>
<!-- Add your content of header -->
<header>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav ">
<li><a href="{% url 'facerecognition:index' %}">01 : 首页</a></li>
<li><a href="{% url 'facerecognition:face_recognition'%}">02 : 人脸模糊</a></li>
<li><a href="{% url 'facerecognition:about' %}">03 : 关于本站</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.session.is_login %}
<li><a>你好,{{ request.session.user_name }}</a></li>
<li><a href="{% url 'users:logout' %}">登出</a></li>
{% else %}
<li><a href="{% url 'users:login' %}">登录</a></li>
<li><a href="{% url 'users:register' %}">注册</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
</header>
<div class="section-container">
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2 section-container-spacer">
<div class="text-center">
<h1 class="h2">02 : 人脸模糊</h1>
<p>
<h3>这是一个人脸模糊程序,你可以通过拖拽或者点击添加图片按钮,<br/>
上传照片或者视频,我们会将其中的人脸模糊后返回给你</h3></p>
</div>
</div>
<div id="post-container" class="container-upload">
<!--<h1 id="demo">upload</h1>-->
<!-- <p>您可以尝试文件拖拽,或者点击添加图片按钮.</p>-->
<div id="uploader" class="wu-example">
<div class="queueList">
<div id="dndArea" class="placeholder">
<div id="filePicker"></div>
<p>或将照片拖到这里,单次最多可选300张</p>
</div>
</div>
<div class="statusBar" style="display:none;">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div>
<div class="info"></div>
<div class="btns">
<div id="filePicker2"></div>
<div class="uploadBtn">开始上传</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
// 添加全局站点信息
var BASE_URL = '/webuploader';
</script>
<script type="text/javascript" src="/static/js/jquery.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/js/global.js"></script>
<script type="text/javascript" src="/static/js/webuploader.js"></script>
<script type="text/javascript" src="/static/js/demo.js"></script>
</div>
</div>
</div>
<footer class="footer-container text-center">
<div class="container">
<div class="row">
<div class="col-xs-12">
<p>© UNTITLED | Website created by Micah Yin from GUET - More information <a href="http://www.guet.edu.cn/" target="_blank" title="模板之家">GUET</a> </p>
</div>
</div>
</div>
</footer>
<script>
document.addEventListener("DOMContentLoaded", function (event) {
navActivePage();
});
</script>
<script type="text/javascript" src="/static/js/main.70a66962.js"></script>
</body>
</html>
图片模糊后台代码:
file_upload()和file_merge()用于处理文件上传和分块文件的合并
@csrf_exempt
def file_upload(request):
"""用于处理上传文件"""
if request.method == 'POST':
upload_file = request.FILES.get('file')
chunk = request.POST.get('chunk', 0) # 获取该分片在所有分片中的序号
if chunk != 0:
filename = upload_file.name
request.session['filename'] = filename # 分片上传的标志
filename = '%s%s' % (filename, chunk) # 构成该分片唯一标识符
else:
filename = upload_file.name
username = request.session.get('user_name')
default_storage.save('./upload/%s/%s' % (username, filename), ContentFile(upload_file.read())) # 保存分片到本地
return HttpResponse('face_recognition.html', locals())
@csrf_exempt
def file_merge(request):
"""整合分片上传的文件"""
filename = request.session.get('filename')
username = request.session.get('user_name')
# 若有分块上传的文件,则对文件进行整合
if filename:
upload_file = filename.split(".")[0]
join_file("./upload/%s/" % username, filename, "./upload/%s/" % username, upload_file)
request.session['filename'] = None
else:
pass
# 遍历所有文件,对视频和图片进行人脸模糊处理
in_dir = "./upload/%s/" % username
out_dir = "./download/%s/" % username
if not os.path.exists(out_dir):
os.mkdir(out_dir)
in_files = os.listdir(in_dir)
in_files.sort()
for file in in_files:
in_path = os.path.join(in_dir, file)
out_path = os.path.join(out_dir, file)
if file.split(".")[1] == 'mp4':
# print("注释掉了mp4!!!!")
blur_video(out_path, in_path)
else:
blur_image(out_path, in_path)
# 对文件进行压缩
zip_ya(out_dir, "./download/%s.zip" % username)
return HttpResponse('to_download')
4.人脸区域模糊功能
人脸部分模糊的代码,调用了opencv的分类器,对人脸区域进行了高斯模糊
def blur_image(filename, file_path):
"""
图片人脸部分模糊
:param filename:人脸模糊后的输出路径
:param file_path: 读取文件的路径
:return:
"""
classfier = cv2.CascadeClassifier("/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt2.xml")
src = cv2.imread(file_path)
grey = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# 人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects) > 0: # 大于0则检测到人脸
for faceRect in faceRects: # 单独框出每一张人脸
x, y, w, h = faceRect
image = src[y - 10:y + h + 10, x - 10:x + w + 10] # 截取人脸部分
dst = np.empty(image.shape, dtype=np.uint8) # 空白numpy数组
cv2.blur(image, (25, 25), dst) # 均值滤波,输出微dst
src[y - 10:y + h + 10, x - 10:x + w + 10] = dst
# 保存图像
cv2.imwrite(filename, src)