如何在网页中使用人脸识别?

项目介绍

本项目主要实现了通过人脸识别的方式进行网页的登录。
网页采用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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打代码的苏比特

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值