01、RESTful概述
RESTful架构风格最初由Roy T. Fielding(HTTP/1.1协议专家组负责人)在其2000年的博士学位论文中提出。HTTP就是该架构风格的一个典型应用。从其诞生之日开始,它就因其可扩展性和简单性受到越来越多的架构师和开发者们的青睐。一方面,随着云计算和移动计算的兴起,许多企业愿意在互联网上共享自己的数据和功能;另一方面,在企业中,RESTful API也逐渐受到重视。时至今日,RESTful架构风格已成为企业级服务的标配。REST即Representational State Transfer的缩写,译为“表现层状态转化”。REST最大的几个特点为:资源、统一接口、URI和无状态。所谓“资源”,就是网络上的一个实体,或者说是网络上的一个具体信息,它可以是一段文本、一张图片、一首歌曲等。资源通过某种载体反应其内容,文本可以用txt格式表现,也可以用HTML格式或XML格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现;JSON是现在最常用的资源表示格式。
在前面的企业门户网站实战项目中,为了能够动态的显示页面内容,使用了Django提供的模板机制,即在前端HTML页面中嵌入了大量的Django模板标签,这些标签并不是HTML的标签,而是需要通过后台Django服务器对这些标签进行解析再返回页面内容给前端。尽管利用Django模板标签,可以使得后端开发人员比较方便的对前端页面内容进行控制,但是这种处理方式导致各个模板文件不再是纯粹的HTML页面,而是嵌入了一堆浏览器无法直接识别的模板标签,前端设计人员在不熟悉Django的情况下无法对这些内容进行设计和修改。目前,很多大型Web项目的开发往往是采用一种前后端分离的合作方式,前端设计人员专注于页面和交互功能的实现,通过HTML、CSS和JS即可在浏览器端进行设计并且查看效果。后端开发人员仅处理前端发来的各种请求,并返回各请求对应的内容即可,其中后端开发人员不再需要关注页面的设计,而是通过双方约定好的API接口协议进行资源上传和接收。这种前后端分离、仅通过内容交换实现的Web架构即为REST。
在前后端分离的应用模式中,后端仅返回前端所需要的数据,不再渲染HTML页面,不再控制前端的效果。前端用户看到什么效果、从后端请求的数据如何加载到前端中,这些都由前端决定。例如网页有网页的设计方式,手机APP有手机APP的处理方式,但无论哪种前端其所需要的数据基本相同,后端仅需开发同一套逻辑对外提供资源数据即可。
02、搭建框架
我们将采用Django来开发一个基于RESTful风格的项目实例:中文字符识别。
我们首先完成基础框架搭建。新建一个ocr文件夹用于存放项目,在该文件夹下分别建立三个子文件夹frontend、ocr和app,其中frontend文件夹用于存放前端文件,包括html、css、js和img文件等,ocr文件夹用于存放项目的配置文件,app文件夹作为应用文件夹用于存放每个独立应用文件。参照Django项目目录结构,在各子文件夹下创建一些空文件和空文件夹,完整结构如图1所示。
接下来将单文件Django项目的各模块内容填入到指定的文件中。打开settings.py文件,添加代码如下:
import os # 设置项目根目录 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 加密签名 SECRET_KEY = 'b!iohd&_vv@gmva5b6gq@k9t01_k^52uludvw8@h0)1fnez^8l' DEBUG = True # 设置当前为调试模式 INSTALLED_APPS = ['app'] # 添加应用 ROOT_URLCONF = 'ocr.urls' # 设置项目路由文件urls
述代码将原Django项目中的必要部分剥离出来,旨在能够建立更轻量更易于理解的Django项目。打开app应用下的views.py文件,添加代码:
from django.http import HttpResponse def home(request): return HttpResponse('Hello World')
通过导入的HttpResponse函数响应前端,返回内容为一个字符串。在urls.py文件中添加路由:
from django.urls import path from app.views import home urlpatterns = [path('', home, name='home')]
上述代码将访问根路径与视图home函数进行绑定。最后,在项目根目录下的manage.py文件中添加运行代码:
if __name__ == '__main__': import sys import django import os DJANGO_SETTINGS_MODULE = 'ocr.settings' # 设置环境变量 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ocr.settings') django.setup() from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
其中注意,我们将所有的配置全部放置在单文件脚本中,使用settings.configure(**settings)来加载配置项,此处我们将所有的配置项放置在了独立的settings.py文件中。为了能够加载该配置文件,需要采用django.setup()函数进行设置,该函数会自动查询环境变量DJANGO_SETTINGS_MODULE的值,把这个值作为配置文件的路径。保存所有修改后,在终端中运行命令:
python manage.py runserver
然后打开浏览器访问127.0.0.1:8000,查看页面是否输出对应的字符串“Hello World”。
03、前端开发
我们拟实现一个在线中文字符识别系统,用户在网页上上传图片,然后通过Ajax技术将图片传输至后台服务器,后台服务器调用中文字符识别算法将图片中的文字识别出来,并以JSON字符串的形式返回结果给前端页面进行显示。整个开发过程分为前端和后端,后端不再使用Django提供的模板机制来控制前端页面的执行逻辑,前后端之间所有的交互全部通过API接口进行。由于采用了前后端分离的机制,因此,前端开发人员可以使用纯HTTP和JS来开发页面和交互逻辑,并且能够在不借助后端的情况下运行页面查看效果。本小节先进行前端开发。
前端所有的开发文件全部放置在frontend文件夹中。为了程序美观,本实例依然采用Bootstrap框架设计页面。将Bootstrap包中的bootstrap.min.css、jquery.min.js和bootstrap.min.js文件按照文件类型分别放置在frontend/css和frontend/js文件夹中,然后在img文件夹下放置一张名为sample.jpg的图片文件用于展示图像显示区域。在css文件夹下额外新建一个空的style.css文件,该文件将作为本实例的个性化样式定制文件使用。
接下来开始编辑前端页面index.html。首先设置页面标题title和元信息meta,然后在页面头部引入必要的css和js文件:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>在线中文字符识别</title> <link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet"> <script src="js/jquery.min.js"></script> <script src="js/bootstrap.min.js"></script> </head> <body> </body> </html>
在页面部分采用Bootstrap栅格结构进行布局,主要分为左右两部分,各占6个栅格。左侧用来上传待识别的图像并显示,右侧用来显示识别结果。详细代码如下:
<div class="container"> <!-- 标题 --> <div class="row"> <div class="col-lg-12"> <p class="text-center h1"> 在线中文字符识别 </p> </div> </div> <!-- 分隔符 --> <div class="hr"> <hr /> </div> <!-- 主体内容 --> <div class="row"> <br> <!-- 图片上传 --> <div class="col-md-6"> <img id="photoIn" src="img/sample.jpg" class="img-responsive"> <input type="file" id="photo" name="photo" /> </div> <!-- 运行结果 --> <div class="col-md-6"> <div class="col-md-12"> <textarea id="output" disabled class="form-control" rows="5"> </textarea> </div> <br> <div class="col-md-12"> <p class="text-center h4">识别结果</p> </div> </div> </div> <br> <div class="row"> <div class="text-center"> <button type="button" id="recognition" class="btn btn-primary"> 识别</button> </div> </div> </div>
在style.css文件中添加分割线对应的样式设计:
div.hr { height: 3px; background: #818080; } div.hr hr { display: none; }
为了能够实现图像浏览和上传的功能,需要使用js来实现。具体的,在部分末尾添加代码:
<script> $(function () { $('#photo').on('change', function () { var r = new FileReader(); f = document.getElementById('photo').files[0]; r.readAsDataURL(f); r.onload = function (e) { document.getElementById('photoIn').src = this.result; }; }); }); </script>
接下来,在前端页面中继续添加代码完成图像向后端的传输以及获取到后端发来的结果后的显示处理:
<!-- 图像发送至后台服务器进行识别 --> <script> $('#recognition').click(function () { formdata = new FormData(); var file = $("#photo")[0].files[0]; formdata.append("image", file); $.ajax({ url: '/ocr/', // 调用Django服务器计算函数 type: 'POST', // 请求类型 data: formdata, dataType: 'json', // 期望获得的响应类型为json processData: false, contentType: false, success: ShowResult // 在请求成功之后调用该回调函数输出结果 }) }) </script> <!-- 返回结果显示 --> <script> function ShowResult(data) { output.value = data['output']; } </script>
图像的传输采用了Ajax技术,当用户单击“识别”按钮时将图像数据封装到formdata变量并发送至后端,发送地址为'/ocr/',发送方式为POST。收到结果后执行ShowResult函数,将输出文本的值改为识别到的文字信息。
保存所有修改后,用浏览器直接打开index.html页面,单击“浏览”按钮,选择一张待识别的图片进行上传,可以看到选择的图片显示在指定位置,如图2所示。
到这里我们发现整个的前端设计和开发不再依赖后端服务器,并且由于页面没有嵌入Django模板标签,因此可以直接被浏览器解析和运行。这种前后端分离的开发模式可以极大的提高团队开发人员的沟通效率,使得项目的协同合作更加方便。
04、后端开发
我们继续将对后端进行开发。一般情况下,采用前后端分离机制以后前端静态资源(html页面、css样式文件、jpg图片等)会采用额外的前端服务器来提供静态文件服务。我们为了简化服务器的搭建和使用,依然使用Django来提供静态文件服务,将所有的静态资源例如css、js和jpg图片文件等按照文件夹路径创建对应的视图处理函数,以文件读取方式获取文件内容并通过HttpResponse返回。
在views.py文件中添加代码如下:
def read_css(request, filename): with open('frontend/css/{}'.format(filename), 'rb') as f: css_content = f.read() print('css文件') return HttpResponse(content=css_content, content_type='text/css') def read_js(request, filename): with open('frontend/js/{}'.format(filename), 'rb') as f: js_content = f.read() print('js文件') return HttpResponse(content=js_content, content_type='application/JavaScript') def read_img(request, filename): with open('frontend/img/{}'.format(filename), 'rb') as f: img_content = f.read() print('img文件') return HttpResponse(content=img_content, content_type='image/jpeg')
上述代码分别创建js、css和jpg文件访问的视图处理函数,然后在urls.py文件中设置访问路由:
from app.views import read_css, read_js, read_img urlpatterns = [ ...其他路由... path('css/<str:filename>', read_css, name='read_css'), path('js/<str:filename>', read_js, name='read_js'), path('img/<str:filename>', read_img, name='read_img'), ]
def home(request): with open('frontend/index.html','rb') as f: html = f.read() return HttpResponse(html)
通过这种方式,可以使得在不改变前端代码的情况下能够正确的提供静态资源请求服务。重新编辑views.py中的home函数:
同样的,html页面也以文件读取方式获取内容并通过HttpResponse返回。保存所有修改后启动项目:
python manage.py runserver
打开浏览器查看页面效果。可以发现页面效果与使用浏览器直接打开index.html页面相同。这说明,后端服务器正确的充当了静态资源服务器的角色,在不使用Django模板标签的情况下能够实现前端页面的正确渲染。
最后需要开发中文字符识别对应的Ajax视图处理函数。为了实现中文字符识别,我们采用开源库Tesseract-OCR来进行文字识别任务。Tesseract是惠普布里斯托实验室在1985~1995年间开发的一个开源的字符识别引擎,曾经在1995 UNLV精确度测试中名列前茅。2005年,惠普将其对外开源。2006年由Google对Tesseract进行改进并对其进行深度优化。
Tesseract的下载网址为:
https://digi.bib.uni-mannheim.de/tesseract/。根据系统版本进行选择,如果使用Windows 64位系统,可以下载windows 64对应的版本:tesseract-ocr-w64-setup-v4.1.0.20190314.exe。下载完成后双击进入安装界面,展开Additional language data,勾选arabic和Chinese simplified使得能够同时支持阿拉伯数字和简体中文字符的识别。
为了能够在Python中使用该引擎库,需要安装对应的Python库:
pip install pytesseract
然后修改pytesseract库文件,在pytesseract安装包中找到pytesseract.py文件,修改tesseract_cmd字段的值,将tesseractOCR的安装目录填入其中:
tesseract_cmd = r'<tesseractOCR安装目录>\tesseract.exe'
通过上述修改,就可以使得Python能够找到本地的文字识别程序完成识别。接下来在views.py文件中添加视图处理函数,完整代码如下:
import numpy as np import urllib import numpy as np import urllib import json import cv2 import pytesseract from PIL import Image import os from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse def read_image(stream=None): data_temp = stream.read() image = np.asarray(bytearray(data_temp), dtype="uint8") image = cv2.imdecode(image, cv2.IMREAD_COLOR) return image @csrf_exempt # 用于规避跨站点请求攻击 def ocrDetect(request): result = {"code": None} if request.method == "POST": if request.FILES.get("image", None) is not None: img = read_image(stream=request.FILES["image"]) # OpenCV转PIL img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # 执行识别 code = pytesseract.image_to_string(img, lang='chi_sim') result.update({"output": code}) return JsonResponse(result)
通过客户端浏览器上传的图像照片进行识别处理然后返回结果,不同之处在于此处返回的结果是以JSON字符串形式给出,不需要再额外的进行图像编码。识别部分主要采用pytesseract.image_to_string函数进行识别,其中lang='chi_sim'表示当前识别中文简体字符。
最后,在urls.py文件的urlpatterns字段中添加对应的路由:
path('ocr/', ocrDetect, name='ocrDetect'), # 在线中文字符识别api
完成所有修改后保存并运行项目,最终识别效果如图4所示。
经过测试,tesseract—OCR对于印刷体中文字符效果较好,对于手写体中文字符效果一般。如果需要更高的检测精度和更好的适应性,则需要进一步优化算法。