Django实现人脸识别登录

252 阅读6分钟

Django实现人脸识别登录

Demo示例下载

1、账号密码登录

face_login.png

2、人脸识别登录

face_login_face.png

3、注册

face_注册.png

4、更改密码

face_更改密码.png

5、示例网站

点我跳转

一、流程说明

1、注册页面:前端打开摄像头,拍照,点击确定后上传图像
2、后端获取到图像,先通过face_recognition第三方库识别是否能够获取到人脸特征,然后把人脸特征通过json转成字符串保存到数据库中
3、登录页面:可使用账号密码登录,也可使用人脸登录。
使用人脸登录时:先通过摄像头获取人脸图像,上传到后端
4、后端通过face_recognition获取图像的人脸特征,转成字符串,然后和数据库中的一一比对,比对成功则能够登录。

二、实现过程

1、创建虚拟环境

python使用3.8版本。

打开cmd窗口,进入d盘的pythonpro目录,接着进入venv目录,执行以下命令:

python -m venv face_login

然后激活虚拟环境:

face_创建虚拟环境.png

按回车键。

2、导入第三方库

python.exe -m pip install --upgrade pip
pip install Django==3.2.7 -i https://pypi.mirrors.ustc.edu.cn/simple/
pip install PyMySQL==0.9.2 -i https://pypi.mirrors.ustc.edu.cn/simple/
pip install mysqlclient==2.0.1 -i https://pypi.mirrors.ustc.edu.cn/simple/
pip install pillow==9.5.0 -i https://pypi.mirrors.ustc.edu.cn/simple/

安装 face_recognition可参考博客点我跳转

先下载dlib,然后再安装:

pip install  dlib-19.19.0-cp38-cp38-win_amd64.whl.whl
pip install face_recognition -i https://pypi.tuna.tsinghua.edu.cn/simple

3、创建django项目

退出到pythonpro目录

cd ..
cd ..
cd ..

创建项目,执行命令:

django-admin startproject face_login

创建子应用,切换到项目根目录:

cd face_login

创建子应用

python manage.py startapp login

自此项目创建完成。

face_创建django项目.png

4、pycharm加载项目

打开pycharm,File->open->打开D盘下的pythonpro里面的face_login项目。

然后配置解释器:File->settings->Project:face_login->Python Interpreter:

face_配置.png

5、创建MySQL数据库

命名为:face_login,字符集选择utf8mb4

face_数据库.png

6、settings.py配置

import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-lu=z^(gz4118++$nznr_nz$p9u6_+-ieh=z-uz66)^esir%ei5'
DEBUG = True
ALLOWED_HOSTS = ['*']  # 允许所有访问

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'login'  # 添加子应用
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'face_login.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 添加模板
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'face_login.wsgi.application'
# 使用本地数据库
ip = '127.0.0.1'
DATABASE_NAME = 'face_login'  # mysql数据库名称
DATABASE_USER = 'root'  # mysql数据库用户名
DATABASE_PASS = '123456'  # mysql数据库密码
DATABASE_HOST = ip  # mysql数据库IP
DATABASE_PORT = 3306  # mysql数据库端口

# 配置数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 修改数据库为MySQL,并进行配置
        'NAME': DATABASE_NAME,  #
        'USER': DATABASE_USER,  # 用户名
        'PASSWORD': DATABASE_PASS,  # 密码
        'HOST': DATABASE_HOST,
        'PORT': DATABASE_PORT,
        'OPTIONS': {'charset': 'utf8mb4', }
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
LANGUAGE_CODE = 'zh-hans'  # 使用中国时区
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False

# 静态文件配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
# STATIC_ROOT = os.path.join(BASE_DIR, 'static')  # 收集静态文件时打开,然后关闭STATICFILES_DIRS
# 媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_USER_ICON = os.path.join(BASE_DIR, 'media/user_icon')

7、models模型

from django.db import models


# 用户表
class User(models.Model):
    username = models.CharField(max_length=255, unique=True, verbose_name="账号")
    password = models.CharField(default='123456', max_length=32, verbose_name="密码")
    icon = models.ImageField(upload_to='user_icon', default=r'user_icon/default_icon.jpeg', null=True, blank=True,
                             verbose_name='头像')
    icon_feature = models.TextField(null=True, blank=True, verbose_name='人脸特征')

    class Meta:
        db_table = 'user'
        verbose_name_plural = "用户"
        verbose_name = "用户"

    def __str__(self):
        return self.username

数据迁移:点击PyCharm左下角的Terminal,然后执行:

python manage.py makemigrations
python manage.py migrate

8、urls.py路由

from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve

from login import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", views.login, name="login"),  # 登录
    path("login/", views.login, name="login"),  # 登录
    path("register/", views.register, name="register"),  # 注册
    path("logout/", views.logout, name="logout"),  # 退出
    path("update_pass/", views.update_pass, name="update_pass"),  # 更新密码
    path("index/", views.index, name="index"),  # 首页
    re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
    re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATICFILES_DIRS}), # 收集静态文件时关闭
]

9、views.py调度器

import json
import os

import face_recognition
import numpy as np
from django.http import JsonResponse
from django.shortcuts import render, redirect

# 登录
from django.urls import reverse

from face_login.settings import MEDIA_USER_ICON
from login.forms import Login, RegisterForm
from login.models import User


def login(request):
    if request.method == "GET":
        # get请求说明是用户进入登录界面
        if request.GET.get('type') == 'face':
            # 人脸识别登录
            return render(request, "login_face.html")
        # 账号密码
        form = Login()
        return render(request, "login.html", {"form": form})

    # 人脸识别登录
    if request.POST.get('type') == 'face':
        icon = request.FILES.get('icon')
        # 验证是否可以获取人像信息
        if not icon:
            return JsonResponse(data={'code': 1, 'msg': '获取人脸失败,请打开摄像头拍照!'})
        known_pic = face_recognition.load_image_file(icon)
        known_pic_encoding = face_recognition.face_encodings(known_pic)
        if not known_pic_encoding:
            return JsonResponse(data={'code': 1, 'msg': '获取人脸失败,请对准摄像头拍照!'})
        # 人脸特征
        icon_feature = known_pic_encoding[0]
        for user in User.objects.all():
            if not user.icon_feature:
                continue
            # 比较已知人脸和未知人脸,返回结果为true或者false。
            if face_recognition.compare_faces(icon_feature, [np.array(json.loads(user.icon_feature))])[0]:
                request.session["login_in"] = True
                request.session["user_id"] = user.id
                request.session["icon"] = user.icon.name
                request.session["username"] = user.username  # 把用户Id写进浏览器session
                return JsonResponse(data={'code': 2, 'msg': '登录成功'})
        return JsonResponse(data={'code': 1, 'msg': '登录失败,人脸识别不通过'})
    # 账号密码登录
    form = Login(request.POST)
    if form.is_valid():
        # 验证用户的登录form是否有效
        username = form.cleaned_data["username"]  # 账号
        password = form.cleaned_data["password"]  # 密码
        result = User.objects.filter(username=username)  # 通过账号获取用户信息
        if result:
            user = result.first()
            # 验证密码是否正确
            if user.password == password:
                request.session["login_in"] = True
                request.session["user_id"] = user.id
                request.session["icon"] = user.icon.name
                request.session["username"] = user.username  # 把用户Id写进浏览器session
                return redirect(reverse("index"))  # 跳转到首页
            else:
                return render(request, "login.html", {"form": form, "error": "账号或密码错误"})
        else:
            return render(request, "login.html", {"form": form, "error": "账号不存在"})


def save_icon(icon, username):
    # 保存图片
    photo_name = '{}_{}'.format(username, icon.name)
    filePath = os.path.join(MEDIA_USER_ICON, photo_name)
    with open(filePath, 'wb') as fp:
        for part in icon.chunks():
            fp.write(part)
    return r'user_icon/{}'.format(photo_name)

def is_valid(data, icon):
    # 验证用户是否可以注册
    if User.objects.filter(username=data['username']):
        return [False, '用户名已经存在!']
    if data['password1'] != data['password2']:
        return [False, '第二次输入密码不对!']
    if not icon:
        return [False, '获取人脸失败,请打开摄像头拍照!']

    # 验证是否可以获取人像信息
    known_pic = face_recognition.load_image_file(icon)
    known_pic_encoding = face_recognition.face_encodings(known_pic)
    if not known_pic_encoding:
        return [False, '获取人脸失败,请对准摄像头拍照!']
    # 验证人脸是否被注册
    icon_feature = known_pic_encoding[0]
    for user in User.objects.all():
        if not user.icon_feature:
            continue
        # 比较已知人脸和未知人脸,返回结果为true或者false。
        if face_recognition.compare_faces(icon_feature, [np.array(json.loads(user.icon_feature))])[0]:
            return [False, '注册失败,该人脸信息已被注册!']
    # 人脸特征
    icon_feature = json.dumps(known_pic_encoding[0].tolist())
    return [True, '验证通过', icon_feature]

# 注册
def register(request):
    if request.method == "GET":
        # get请求说明用户进入了注册页面
        form = RegisterForm()
        return render(request, "register.html", {"form": form})
    # post请求说明用户输入了账号、密码发起了注册请求
    data = request.POST
    icon = request.FILES.get('icon')
    ret = is_valid(data, icon)
    if not ret[0]:
        return JsonResponse(data={'code': 1, 'msg': ret[1]})
    # 验证通过
    username = data["username"] # 用户名
    password = data["password2"] # 密码
    icon_name = save_icon(icon, username)
    icon_feature = ret[2]
    # 创建用户
    User.objects.create(username=username, password=password, icon=icon_name, icon_feature=icon_feature)
    return JsonResponse(data={'code': 2, 'msg': '注册成功!'})

# 登出
def logout(request):
    if not request.session.get("login_in", None):  # 不在登录状态跳转回首页
        return redirect(reverse("index"))
    request.session.flush()  # 清除session信息
    return redirect(reverse("login"))


def is_update_valid(data, icon):
    # 验证用户是否可以更改密码
    if data['password1'] != data['password2']:
        return [False, '第二次输入密码不对!']
    if not icon:
        return [False, '获取人脸失败,请打开摄像头拍照!']

    # 验证是否可以获取人像信息
    known_pic = face_recognition.load_image_file(icon)
    known_pic_encoding = face_recognition.face_encodings(known_pic)
    if not known_pic_encoding:
        return [False, '获取人脸失败,请对准摄像头拍照!']
    # 验证人脸是否被注册
    icon_feature = known_pic_encoding[0]
    for user in User.objects.all():
        if not user.icon_feature:
            continue
        # 比较已知人脸和未知人脸,返回结果为true或者false。
        if face_recognition.compare_faces(icon_feature, [np.array(json.loads(user.icon_feature))])[0]:
            user.password = data['password1']
            user.save()
            return [True, '验证通过!']

    return [False, '该人脸信息未被注册,请注册新账号!']

# 更新密码
def update_pass(request):
    if request.method == "GET":
        # get请求说明用户进入了更新密码页面
        form = RegisterForm()
        return render(request, "update_pass.html", {"form": form})
    # post请求说明用户输入了账号、密码发起了注册请求
    data = request.POST
    icon = request.FILES.get('icon')
    ret = is_update_valid(data, icon)
    if not ret[0]:
        return JsonResponse(data={'code': 1, 'msg': ret[1]})
    # 验证通过
    return JsonResponse(data={'code': 2, 'msg': '密码更改成功!'})



# 首页
def index(request):
    # 登录成功后进入首页
    return render(request, "index.html")

10、首页

{% load static %}
<!DOCTYPE html>

<html lang="zh-CN">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="/media/django.jpg">
    <title>人脸登录系统系统</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/dashboard.css" rel="stylesheet">
    <link href="/static/css/custom.css" rel="stylesheet">
    <script src="/static/js/ie-emulation-modes-warning.js"></script>
    <script src="/static/js/html5shiv.min.js"></script>
    <script src="/static/js/respond.min.js"></script>
</head>
<body>

<nav class="navbar navbar-inverse navbar-fixed-top" style="background-color: #652F12;font-size: 18px">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                    aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/" style="color: whitesmoke;font-size: 18px">人脸登录识别系统</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">

            <ul class="nav navbar-nav navbar-right">
                {% if request.session.login_in == True %}
                    <li>
                        <span href="#" style="color: whitesmoke">{{ request.session.username }}</span>
                        <img class="img-main" style="height: 50px;width: 50px;border-radius: 50%"
                             src="/media/{{ request.session.icon }}"
                             alt="{{ request.session.username }}">
                    </li>
                    <li><a href="{% url 'logout' %}" style="color: whitesmoke">退出</a></li>
                {% else %}
                    <li><a href="{% url 'login' %}" style="color: whitesmoke">登录</a></li>
                    <li><a href="{% url 'register' %}" style="color: whitesmoke">注册</a></li>
                {% endif %}
            </ul>

        </div>
    </div>
</nav>
<div class="container-fluid">
    <div class="row">

        <div class="row" style="text-align: center; font-size: 25px">
            <ul>
                <li style="margin-right: 2px;padding: 0">
                    <a href="https://liangdongchang.blog.csdn.net/" target="_blank">我的博客【点我跳转】
                    </a>
                </li>
                <li style="margin-right: 2px;padding: 0">
                    <a href="http://newbook.qsxbc.com/all_book/" target="_blank">图书管理系统【点我跳转】
                    </a>
                </li>
                <li style="margin-right: 2px;padding: 0">
                    <a href="http://movie.qsxbc.com/" target="_blank">电影管理系统【点我跳转】
                    </a>
                </li>
                <li style="margin-right: 2px;padding: 0">
                    <a href="http://course.qsxbc.com/" target="_blank">在线课程管理系统【点我跳转】
                    </a>
                </li>
                <li style="margin-right: 2px;padding: 0">
                    <a href="#" target="_blank">健身课程管理系统
                    </a>
                </li>
            </ul>

        </div>
    </div>
</div>


<script src="/static/js/jquery-2.1.1.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/ie10-viewport-bug-workaround.js"></script>
<script src="/static/js/custom.js"></script>
<script src="/static/js/plugins/highstock/js/highstock.js"></script>
<script src="/static/js/plugins/highstock/js/modules/exporting.js"></script>
<script type="text/javascript">

</script>
</body>
</html>

11、登录页面

人脸识别登录

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="/media/django.jpg">
    <title>人脸识别登录系统</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/dashboard.css" rel="stylesheet">
    <link href="/static/css/custom.css" rel="stylesheet">
    <script src="/static/js/ie-emulation-modes-warning.js"></script>
    <script src="/static/js/html5shiv.min.js"></script>
    <script src="/static/js/respond.min.js"></script>

</head>
<style>
    * {
        margin: 0;
        padding: 0;
    }

    body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100vh;

        background-size: 200px;
    }

    .col-md-4 {
        display: flex;
        border-radius: 20px;
        flex-direction: column;
        margin-top: 0;
        margin-left: 100px;
        padding-top: 0;
        width: 650px;
        height: 440px;
        backdrop-filter: blur(10px);
        border: grey solid 3px;
        animation-name: shake;
        animation-duration: 1s;
        background: #FFFFFF;
        box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
    }

</style>

<body>
<div class="col-md-4 col-md-offset-4">
    <h3 style="text-align: center;color: #4CAF50;">
        人脸识别登录
    </h3>
    {% if error %}
        <p style="color: red">{{ error }}</p>
    {% endif %}
    <div style="margin-bottom: 5px">
        <a href="{% url 'login' %}">账号密码登录</a>
    </div>
    <div class="form-group">
        <label>获取头像</label>
        <div id="cameraBtn" style="margin-bottom: 5px;text-align: center">
            <input type="button" id="init" onclick="captureInit()" value="开启摄像头"/>
            <input type="button" id="capture" onclick="capture_s()" value="开始拍照"/>
            <input type="button" id="queren" onclick="queren_s()" value="确定登录"/>

        </div>
        <div id="cameraDiv" style="width: 650px">
            <!-- 视频流 -->
            <video id="video" autoplay style="width: 300px;height: 200px"></video>
            <!--描绘video截图-->
            <canvas id="canvas" width="300" height="200"></canvas>

        </div>

    </div>
    <p style="text-align: center;margin-top: 10px;color: #b3b3b3;">未有账号?
        <a href="{% url 'register' %}" style="color: #4CAF50;">点我注册</a>
    </p>
    <p style="text-align: center;margin-top: 10px;color: #b3b3b3;">
        <a href="{% url 'update_pass' %}" style="color: #4CAF50;">忘记密码?</a>
    </p>
</div>

</body>
<script src="/static/js/jquery-2.1.1.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript">
    var cb = getPara("cb") || "setImg";
    var filetypeid = getPara("filetypeid") || "filetypeid";//附件类型id
    var video = document.getElementById("video");
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var imgWidth = getPara("width") || "300";//这个值div的宽一致
    var imgHeight = getPara("height") || "200";//这个值div的高一致
    var captureState = 0;//未开启,1已开启,2已拍照,为2才可点击确认按钮
    var checkedCount = 0; // 课程类型选择数量
    var checkList = [] // 课程类型选择id列表
    var style = getPara("style") || "big-btn-blue";
    video.style.width = imgWidth;
    video.style.height = imgHeight;
    var st = style.split(",");
    document.getElementById("init").className = st[0];
    document.getElementById("capture").className = st[1] || st[0];
    document.getElementById("queren").className = st[2] || st[0];

    //访问用户媒体设备的兼容方法
    function getUserMedia(constrains, success, error) {
        if (navigator.mediaDevices.getUserMedia) {
            //最新标准API
            navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error);
        } else if (navigator.webkitGetUserMedia) {
            //webkit内核浏览器
            navigator.webkitGetUserMedia(constrains).then(success).catch(error);
        } else if (navigator.mozGetUserMedia) {
            //Firefox浏览器
            navagator.mozGetUserMedia(constrains).then(success).catch(error);
        } else if (navigator.getUserMedia) {
            //旧版API
            navigator.getUserMedia(constrains).then(success).catch(error);
        } else {
            alert("不支持的浏览器");
        }
    }

    //成功的回调函数
    function success(stream) {
        //兼容webkit内核浏览器
        var CompatibleURL = window.URL || window.webkitURL;
        //将视频流设置为video元素的源
        try {
            video.srcObject = stream;
        } catch (e) {
            video.src = CompatibleURL.createObjectURL(stream);
        }
        //播放视频
        video.play();
    }

    //异常的回调函数
    function error(error) {
        alert("访问用户媒体设备失败," + error.name + "" + error.message);
    }


    function getPara(name, search) {
        var p = getParas(name, search);
        if (p.length == 0) {
            return null;
        } else if (p.length == 1) {
            return p[0];
        } else {
            return p.toString();
        }
    }


    function getParas(name, search) {
        if (!search) {
            search = window.location.search.substr(1);//1.html?a=1&b=2
        }
        var para = [];
        var pairs = search.split("&");//a=1&b=2&b=2&c=2&b=2
        for (var i = 0; i < pairs.length; i = i + 1) {
            var sign = pairs[i].indexOf("=");
            if (sign == -1) {//如果没有找到=号,那么就跳过,跳到下一个字符串(下一个循环)。
                continue;
            }
            var aKey = pairs[i].substring(0, sign);
            if (aKey == name) {
                para.push(unescape(pairs[i].substring(sign + 1)));
            }
        }
        return para;
    }

    //开启摄像头
    function captureInit() {
        if ((navigator.mediaDevices != undefined && navigator.mediaDevices.getUserMedia != undefined)
            || navigator.getUserMedia != undefined
            || navigator.webkitGetUserMedia != undefined
            || navigator.mozGetUserMedia != undefined) {

            //调用用户媒体设备,访问摄像头,改为触发事件
            getUserMedia({video: {width: imgWidth, height: imgHeight}}, success, error);
            if (captureState === 0) {
                captureState = 1;//标记此按钮已点击
            }
        } else {
            captureState = 0;//异常标识按钮没点
            alert("你的浏览器不支持访问用户媒体设备或访问地址不是https开头,您可以点击右侧下载解决方案");

        }
    }

    //注册拍照按钮的单击事件
    function capture_s() {
        //绘制画面
        if (captureState == 0) {
            alert("请先开启摄像头");
            return;
        }
        context.drawImage(video, 0, 0, imgWidth, imgHeight);//后面两个长宽
        //canvas.toDataURL("image/png");//即可得到base64编码
        captureState = 2;
    }

    function convertCanvasToImage(canvas) {
        //新Image对象,可以理解为DOM
        var image = new Image();
        // canvas.toDataURL 返回的是一串Base64编码的URL,当然,浏览器自己肯定支持
        // 指定格式 PNG
        image.src = canvas.toDataURL("image/png");
        return image;
    }

    // 确认按钮,提交数据到后端进行注册
    function queren_s() {

        if (captureState != 2) {
            alert("请先开启摄像头并拍照");
            return;
        }

        var mycanvas1 = document.getElementById('canvas');
        // 将 Canvas 转换为 Blob
        mycanvas1.toBlob(blob => {
            // 创建 FormData 对象,将 Blob 添加为文件对象
            const formData = new FormData();

            formData.append('icon', blob, 'photo.jpg');
            formData.append('csrfmiddlewaretoken', '{{ csrf_token }}')
            formData.append('type', 'face')
            // 使用 $.ajax() 函数发送 AJAX 请求
            $.ajax({
                url: '{% url 'login' %}',
                method: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                success: (data) => {
                    if (data.code === 1) {
                        // 登录失败
                        alert(data.msg)
                    } else {
                        // 登录成功
                        window.location.href = '/index/';
                    }

                },
                error: () => {
                    console.error('Error uploading photo.');
                },
                beforeSend: function (xhr, settings) {
                    xhr.setRequestHeader("X-CSRFToken", '{{ csrf_token }}');
                }
            });
        }, 'image/jpeg', 0.8);

    }
</script>
</html>

12、注册页面

{% load static %}

<html lang="zh-CN">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="/media/django.jpg">
    <title>人脸识别登录系统</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/dashboard.css" rel="stylesheet">
    <link href="/static/css/custom.css" rel="stylesheet">
    <script src="/static/js/ie-emulation-modes-warning.js"></script>
    <script src="/static/js/html5shiv.min.js"></script>
    <script src="/static/js/respond.min.js"></script>
</head>
{% if error %}
    <p>{{ error }}</p>
{% endif %}

<style>
    * {
        margin: 0;
        padding: 0;
    }

</style>
<body>
<div class="col-md-6 col-md-offset-3" style="margin-top: 0;padding-top:0;margin-left: 350px;border: grey solid 3px;animation-name: shake;
    animation-duration: 1s;background: #FFFFFF;box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);border-radius: 20px;">
    <h4 style="text-align: center;color: #4CAF50;">
        用户注册
    </h4>
    <form>
        {% csrf_token %}

        <div style="margin:0 auto;width: 300px">
            <label for="id_username">用户名:</label>
            <input type="text" name="username" class="form-control" required="" maxlength="50" id="id_username">
        </div>
        <div style="margin:0 auto;width: 300px">
            <label for="id_password1">密码:</label>

            <input type="password" name="password1" class="form-control" required="" maxlength="128" id="id_password1">
        </div>
        <div style="margin:0 auto;width: 300px">
            <label for="id_password2">确认密码:</label>

            <input type="password" name="password2" class="form-control" required="" id="id_password2">
        </div>
        <div style="margin:0 auto;width: 300px;margin-bottom: 5px">
            <label>获取头像</label>
            <div id="cameraBtn">
                <input type="button" id="init" onclick="captureInit()" value="开启摄像头"/>
                <input type="button" id="capture" onclick="capture_s()" value="拍照"/>


            </div>
        </div>
        <div id="cameraDiv" style="width: 650px">
            <!-- 视频流 -->
            <video id="video" autoplay style="width: 300px;height: 200px"></video>
            <!--描绘video截图-->
            <canvas id="canvas" width="300" height="200"></canvas>

        </div>

        <div style="text-align: center;margin-bottom: 10px">
            <button type="reset"
                    style="background: #4CAF50;color: #FFFFFF;width: 50px;height: 30px;margin-left: -10px;border-radius: 5px;">
                取消
            </button>
            <input type="button" id="queren" style="background: #4CAF50;color: #FFFFFF;width: 50px;height: 30px;margin-left: 50px;border-radius: 5px;" onclick="queren_s()" value="确定"/>

        </div>
        <p style="text-align: center;margin-top: 10px;color: #b3b3b3">已有账号?
            <a href="{% url 'login' %}"
               style="color: #4CAF50;">点我登录</a>
        </p>
    </form>
</div>

</body>
<script src="/static/js/jquery-2.1.1.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript">
    var cb = getPara("cb") || "setImg";
    var filetypeid = getPara("filetypeid") || "filetypeid";//附件类型id
    var video = document.getElementById("video");
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var imgWidth = getPara("width") || "300";//这个值div的宽一致
    var imgHeight = getPara("height") || "200";//这个值div的高一致
    var captureState = 0;//未开启,1已开启,2已拍照,为2才可点击确认按钮

    var style = getPara("style") || "big-btn-blue";
    video.style.width = imgWidth;
    video.style.height = imgHeight;
    var st = style.split(",");
    document.getElementById("init").className = st[0];
    document.getElementById("capture").className = st[1] || st[0];
    document.getElementById("queren").className = st[2] || st[0];

    //访问用户媒体设备的兼容方法
    function getUserMedia(constrains, success, error) {
        if (navigator.mediaDevices.getUserMedia) {
            //最新标准API
            navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error);
        } else if (navigator.webkitGetUserMedia) {
            //webkit内核浏览器
            navigator.webkitGetUserMedia(constrains).then(success).catch(error);
        } else if (navigator.mozGetUserMedia) {
            //Firefox浏览器
            navagator.mozGetUserMedia(constrains).then(success).catch(error);
        } else if (navigator.getUserMedia) {
            //旧版API
            navigator.getUserMedia(constrains).then(success).catch(error);
        } else {
            alert("不支持的浏览器");
        }
    }

    //成功的回调函数
    function success(stream) {
        //兼容webkit内核浏览器
        var CompatibleURL = window.URL || window.webkitURL;
        //将视频流设置为video元素的源
        try {
            video.srcObject = stream;
        } catch (e) {
            video.src = CompatibleURL.createObjectURL(stream);
        }
        //播放视频
        video.play();
    }

    //异常的回调函数
    function error(error) {
        alert("访问用户媒体设备失败," + error.name + "" + error.message);
    }


    function getPara(name, search) {
        var p = getParas(name, search);
        if (p.length == 0) {
            return null;
        } else if (p.length == 1) {
            return p[0];
        } else {
            return p.toString();
        }
    }

    function getParas(name, search) {
        if (!search) {
            search = window.location.search.substr(1);//1.html?a=1&b=2
        }
        var para = [];
        var pairs = search.split("&");//a=1&b=2&b=2&c=2&b=2
        for (var i = 0; i < pairs.length; i = i + 1) {
            var sign = pairs[i].indexOf("=");
            if (sign == -1) {//如果没有找到=号,那么就跳过,跳到下一个字符串(下一个循环)。
                continue;
            }
            var aKey = pairs[i].substring(0, sign);
            if (aKey == name) {
                para.push(unescape(pairs[i].substring(sign + 1)));
            }
        }
        return para;
    }

    //开启摄像头
    function captureInit() {
        if ((navigator.mediaDevices != undefined && navigator.mediaDevices.getUserMedia != undefined)
            || navigator.getUserMedia != undefined
            || navigator.webkitGetUserMedia != undefined
            || navigator.mozGetUserMedia != undefined) {

            //调用用户媒体设备,访问摄像头,改为触发事件
            getUserMedia({video: {width: imgWidth, height: imgHeight}}, success, error);
            if (captureState === 0) {
                captureState = 1;//标记此按钮已点击
            }
        } else {
            captureState = 0;//异常标识按钮没点
            alert("你的浏览器不支持访问用户媒体设备或访问地址不是https开头,您可以点击右侧下载解决方案");

        }
    }

    //注册拍照按钮的单击事件
    function capture_s() {
        //绘制画面
        if (captureState == 0) {
            alert("请先开启摄像头");
            return;
        }
        context.drawImage(video, 0, 0, imgWidth, imgHeight);//后面两个长宽
        //canvas.toDataURL("image/png");//即可得到base64编码
        captureState = 2;
    }

    function convertCanvasToImage(canvas) {
        //新Image对象,可以理解为DOM
        var image = new Image();
        // canvas.toDataURL 返回的是一串Base64编码的URL,当然,浏览器自己肯定支持
        // 指定格式 PNG
        image.src = canvas.toDataURL("image/png");
        return image;
    }


    function toVaild() {
        if (document.getElementsByName('username')[0].value == '') {
            alert("请输入用户名");
            return false;
        }
        if (document.getElementsByName('password1')[0].value == '') {
            alert("请输入密码");
            return false;
        }
        if (document.getElementsByName('password2')[0].value == '') {
            alert("请输入确认密码");
            return false;
        }
        if (document.getElementsByName('password1')[0].value != document.getElementsByName('password2')[0].value) {
            alert('第二次密码输入有误');
            return false;
        }

        return true;
    }

    // 确认按钮,提交数据到后端进行注册
    function queren_s() {
        if (toVaild() == false) {
            return;
        }
        if (captureState != 2) {
            alert("请先开启摄像头并拍照");
            return;
        }

        var mycanvas1 = document.getElementById('canvas');
        // 将 Canvas 转换为 Blob
        mycanvas1.toBlob(blob => {
            // 创建 FormData 对象,将 Blob 添加为文件对象
            const formData = new FormData();
            formData.append('username', document.getElementsByName('username')[0].value);
            formData.append('password1', document.getElementsByName('password1')[0].value);
            formData.append('password2', document.getElementsByName('password2')[0].value);
            formData.append('icon', blob, 'photo.jpg');
            formData.append('csrfmiddlewaretoken', '{{ csrf_token }}')
            // 使用 $.ajax() 函数发送 AJAX 请求
            $.ajax({
                url: '{% url 'register' %}',
                method: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                success: (data) => {
                    if (data.code === 1) {
                        // 注册失败
                        alert(data.msg)
                    } else {
                        // 注册成功
                        window.location.href = '/login/';
                    }

                },
                error: () => {
                    console.error('Error uploading photo.');
                },
                beforeSend: function (xhr, settings) {
                    xhr.setRequestHeader("X-CSRFToken", '{{ csrf_token }}');
                }
            });
        }, 'image/jpeg', 0.8);

    }
</script>

</html>