初识Django

以开发一个人脸识别的后端为例讲解Django的一些具体使用方法.

版本: python3, django2.0;

平台: Linux(由于项目基于face_recognition库进行开发, 官方并不支持该库安装在windows上, 故我在windows上使用PyCharm进行基于CentOS系统的远程开发, 关于如何使用PyCharm进行远程开发, 见此文);

基于VirtualEnv的开发环境

推荐使用VirtualEnv进行开发, 如果之前没接触过, 这次刚好可以接触一下.

VirtualEnv简介

VirtualEnv是一个python的第三方包, 可以用来创建python的虚拟开发环境

为什么要使用VirtualEnv

这样说吧, 对比起node.js, 你会发现python的包管理机制有个很大的缺点就是它的库安装都只能是全局的, 你一个python的开发环境用的越久, 安装的依赖也就越多, 就越容易出现问题(依赖包的冲突/版本的兼容等), 更不用说要在一个环境下运行两个依赖不同版本库的应用了(如应用A依赖Django1.x, 而应用B却依赖Django2.x), 但是像node.js那样可以把依赖与项目本身绑定、而与全局环境可以不相关的机制就合理且对开发友好. 而像VirtualEnv这种管理虚拟环境的库就是专门解决这种问题的了.

VirtualEnv的使用

  1. 使用pip3 install virtualenv命令进行安装;
  2. 创建一个名为MyProject的文件夹, 进入文件夹, 并在当前文件夹下运行virtualenv venv命令创建一个名为venv的虚拟环境, 命令执行完成后你会发现多了一个名为venv的文件夹, 其中就是你这个项目的开发环境了;
  3. 打开venv/Scripts文件夹, 里面会有一个名为activate的脚本, 这就是用来激活你这个venv虚拟开发环境的脚本, 让我们在cmd下运行一下该脚本: 首先进入到该文件夹下, 然后执行.\activate命令激活环境(如果是linux环境下, 则是使用source activate命令激活环境), 随后你会发现cmd的命令提示符变了, 在开头多了(venv)的字样, 说明成功激活了虚拟开发环境, 你在这里进行的所有操作都与外面的全局环境无关了(退出虚拟环境使用deactivate命令).
  4. 让我们来安装一下Django, 使用pip3 install Django进行安装. 安装完成后我们使用pip3 list命令可以查看到Django已经安装到虚拟环境中, 如果这时你到外面的全局环境中使用list命令查看会发现并没有安装Django这个库.
  5. 注: 如果使用powershell运行activate脚本的时候提示错误, 报错信息中含有因为在此系统上禁止运行脚本等字样, 说明你的PS现用执行策略是 Restricted(默认设置), 而Restricted策略为了系统安全不允许任何脚本运行, 你需要执行set-executionpolicy remotesigned命令修改策略后才可以使用PS执行脚本.

创建项目

建立项目

MyProject文件夹中使用命令./venv/Scripts/activate进入虚拟环境, 然后运行命令django-admin startproject DjangoDemo, django-admin会为我们新建一个DjangoDemo文件夹并在其中初始化一个django的项目.

每次进入虚拟环境前都需要执行activate脚本, 下面的所有操作都会在虚拟环境中进行, 不再赘述执行脚本的操作;

运行项目

现在项目已经有了, 我们进去看一下项目结构:

DjangoDemo\
    manage.py
    DjangoDemo\
        __init__.py
        settings.py
        urls.py
        wsgi.py

其中主要是manage.py这个文件, 它是我们用来管理整个项目的一个脚本, 其他都是配置文件, 等用到的时候再说.

既然是个完整的项目, 那现在我们来运行一下: 在项目根目录下运行命令python manage.py runserver, 会出下如下字样:

Performing system checks...
System check identified no issues (0 silenced).
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
April 21, 2018 - 10:36:37
Django version 2.0.4, using settings 'DjangoDemo.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

说明服务已经在运行, 我们访问一下本地8000端口, 会看到如下django2.0的界面 👇
Hello Django

创建项目的应用

Django框架的思想是以应用为基础来构造项目, 这里的应用可以看做是一个个功能模块, 现在我们来创建一个人脸识别的模块.

使用在项目根目录下运行命令python manage.py startapp faceRecog, 完成后我们可以看到目录下多了一个名为faceRecog的目录, 这就是刚创建的应用, 这时它还跟项目没有关系, 我们先将它添加到项目的配置文件里: 打开项目里DjangoDemo文件夹中的settings.py文件, 在INSTALLED_APPS一项中加入刚创建的应用名, 如:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'faceRecog'
]

url映射

后端接收到http请求后需要将请求转发给相应的模块进行处理, 那么要如何转发路由呢, Django让我们把url与对应的处理函数写在urls.py文件里,当用户请求一个网址时,Django 就去会urls.py里找,如果找到了对应的地址,就会调用和它对应的处理函数(叫做视图函数)来处理请求。

针对本文的项目, 我们需要进行如下的添加👇

修改项目根目录下的faceRecog文件夹下的urls.py文件, 如下:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),    
]

r'^$'代表正则表达式匹配, ^代表匹配字符串开头, $代表匹配字符串结尾, 而中间什么都没有, 所以这个正则匹配的是空字符串.

这样转发到faceRecog模块的路由如果什么都不带的话就会使用view.py中的index函数进行处理, 那我们来编写下faceRecog/views.py文件吧, 让它能够处理这个请求:

from django.http import HttpResponse

def index(request):
    return HttpResponse("<h1>this is just a test, and it works</h1>")

然后修改项目根目录下的DjangoDemo文件夹下的urls.py文件, 如下:

from django.contrib import admin
from django.urls import path, include    # 相比之前添加了include, 引入的include函数帮我们把faceRecog应用中的urls.py文件包含了进来

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'', include('faceRecog.urls')),    #添加应用的url, r''是一个正则表达式的空字符串, Django会把它和后面include的urls.py文件中的url进行拼接
]

这时我们运行python manage.py runserver让服务器跑起来, 再访问http://localhost:8000即可看到h1标题大小的this is just a test, and it works字样.

解析路由的过程是这样的, 后端接收到请求http://localhost:8000后会将协议、域名和端口都去掉, 这样就什么都没有了, Django会将它与url.py中的路由进行匹配, 因为它是个空字符串, 所以就匹配到faceRecog应用下的index函数了, 所以Django调用index函数去处理, 返回内容"<h1>this is just a test, and it works</h1>".

数据库

Django框架自身集成了SQLite的数据库, 可以开箱即用.

项目中主要的数据结构

在建表之前需要先了解一下face_recognitiond的官方使用手册, 我们主要用到的是face_encodings函数, 得到其识别脸部后返回的脸部特征值, 函数返回值类型如下图:
face_recognition

可以看到face_encodings函数的返回值是一个list, 其中包含了一个numpy的数组元素.

对于项目的数据库, 首先我们要考虑所需的表, 由于这里只是一个演示的demo, 我们只建立一个脸部特征值的表, FaceChattr表, 然后我们需要考虑表中要存放的数据,

face_recognition的face_encoding函数的返回值是一个list, 其中存放的对象是numpy.ndarray数组类型的, 这样的话我们就没法自由的将其序列化和反序列化的同时还保证其类型不变, 于是想到可以使用json这个序列化库(使用pip3 install json安装该库)来帮助我们, 但是json是没法序列化numpy库中的类型的(比如像这里的ndarray), 直接操作的话你会得到NumPy array is not JSON serializable这样类似的error, 其实我们可以使用ndarray.tolist()方法直接将numpy的数组转为python的list类型, 然后再转成json, 操作如下:

import face_recognition as fr
import json

img = fr.load_image_file(imgPath)
unknownFaceEncoding = fr.face_encodings(img)[0]    #获得特征值
unknownFaceEncoding = unknownFaceEncoding.tolist()    # numpy.ndarray转list类型
unknownFaceEncoding = json.dumps(unknownFaceEncoding)  # list转json

建表

在刚建立的faceRecog应用的文件夹下会有一个models.py的文件, 这就是我们应用的model了, django已经为我们提供了它的ORM处理系统, 我们只要按照框架的要求来写就可以了

但是我们先要想好上面得到的脸部特征值json数据要如何存入数据库呢? 这里推荐使用jsonfield这个库, 项目开源在Github上, 这是一个针对Django框架设计的, 详细情况请见GitHub项目吧, 这里不再赘述, 直接投入实践(使用pip3 install jsonfield安装该库). 除了人脸特征值, 这里就不存其他值了, 想加的话类比一下就行了, 只加个主键id, 表结构如下, 将下面代码写入models.py文件:

from jsonfield import JSONField

class FaceChattr(models.Model):
    id = models.IntegerField(primary_key=True)    #主键id, 默认自增
    charaValue = JSONField( )    # 脸部特征值

建立好models后, Django还并没有为其建立相应的数据表, 需要我们手动进行操作进行创建, 在根目录下先后运行python manage.py makemigrationspython manage.py migrate 命令即可.

数据操作

上面已经建好了表, 下面演示对于数据的操作.

存数据

想要将数据存入数据库, 我们先要创建一个数据对象, 然后再将对象序列化到数据库中, 而这里的对象也就是我们上面创建的数据表类(FaceChattr)了, Django自身的ORM框架可以帮助我们进行序列化, 下面演示:

from faceRecog.models import FaceChattr    # 从faceRecog应用的models中引入FaceChattr表对象
import face_recognition as fr
import json

img = fr.load_image_file(imgPath)
unknownFaceEncoding = fr.face_encodings(img)[0]
unknownFaceEncoding = unknownFaceEncoding.tolist()  # numpy.ndarray转list类型
unknownFaceEncoding = json.dumps(unknownFaceEncoding)  # list转json
newFace = FaceChattr(charaValue=unknownFaceEncoding)  # 赋值持久化
newFace.save()

可以看到上面使用newFace = FaceChattr(charaValue=unknownFaceEncoding)创建了一个FaceChattr的对象, 并将新识别出来的人脸特征值赋值给它的charaValue属性(对应着FaceChattr表中的chattrValue字段), 然后调用save函数保存到数据库中, 这就是存数据的操作流程.

取数据

读取数据库中的数据可以使用tableName.object.all()获取表中的所有数据对象,

但是我们是无法直接的获取其中的数据的, 需要在数据表类中加上__str__方法来返回内容, 例如:

from jsonfield import JSONField

class FaceChattr(models.Model):
    id = models.IntegerField(primary_key=True)
    charaValue = models.JSONField()     # 脸部特征值字段

        def __str__(self):
                return self.charaValue     # 返回脸部特征值

这样定义好__str__ 方法后,再在解释器进行查询时显示的内容将会是__str__方法返回的内容.

所以对于验证数据库中有没有当前要验证的这张脸的逻辑, 我们可以这样写:

def recogFace(unknownFaceEncoding):
    try:
        knownFaces = FaceChattr.objects.all()
        faces = []
        for face in knownFaces:
            face.charaValue = json.loads(face.charaValue)
            faces.append(face.charaValue)
                results = fr.compare_faces(faces, unknownFaceEncoding)
        if True in results:
            result = True
        else:
            result = False
                return result
    except Exception as e:
        print(e)

改数据与删数据

改数据与删数据并不是此处的重点, 简单提一下:

改数据很简单, 取出数据库中相应的数据, 然后重新赋值再save回去就可以了;

至于删数据的话则是调用delete()方法, 例如:

knownFaces = FaceChattr.objects.all()
oneFace=knownFaces[0]
oneFace.delete()

其他操作与配置

关闭CSRF验证

有时候我们需要需要关闭某些方法的CSRF验证功能(否则请求的话必须带上相关的安全token才会被接受), 在这个demo程序里我们就需要这么做, 方法如下, 引入相关模块后使用@csrf_exempt进行标注即可

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def function():

运行服务器时报错Invalid HTTP_HOST header

在项目setting.py配置文件中将ALLOWED_HOSTS字段的内容设置为['*']即可, 即允许所有主机地址

总结

项目地址

上面已经介绍了Django的部分基础操作, 关于本文中的完整demo, 已经打包上传到github上, 详情请见 👉 传送门. 由于face_recognition库官方并不支持在windows上安装, 所以我写了个dockerfile供没有linux环境的同学来构建镜像, 能够在容器中运行本demo.

前端

最后只剩下前端提交请求页面的相关内容了, 比较基础的东西, 这里简单写了个页面, 也上传到上面的GitHub项目中了, 在app-faceRecognitionDemofrontend目录中, 修改其中src\components文件夹中的AddNewFace.vueRecogOldFace.vue中form表单提交的地址为你的Django程序运行所在的host地址, 然后在frontend根目录下运行npm install安装组件, 完成后运行npm run dev即可启动服务器程序.