前面介绍了使用docker和tfserving搭建一个预测服务,但是有时候需要对输入到网络的数据做一些预处理的工作,虽然可以在本地使用如Python代码处理完成后再请求预测,但是使用起来会不方便。有时候也可能有将预测的过程作为一个web服务,实现在网页上填写图片的地址信息,然后返回预测结果的需求。本篇主要基于之前搭建的tfserving预测服务,使用django (一个开源的python web框架,详细信息见官网 django) 搭建一个web服务,作为一个中转服务器,接受用户的输入,完成一些预处理工作,然后向tfserving请求预测结果,最后返回结果。全部源代码放在 github。
另:使用docker+django+SSD搭建目标检测服务
先来看结果,下图为最终的页面:
点击这里访问,可以先试一试效果。输入图片的URL,点击predict,等待返回结果。
下面介绍主要的流程:
这里使用的是TensorFlow官方给的一个关于tfserving的教程所带的模型,具体的Docker安装和tfserving容器搭建流程可以参考使用docker和tfserving搭建预测服务。该步骤为基础步骤,如果要完整实现该demo,需要首先完成此步骤。
$docker pull django
将容器的8000端口映射到宿主机的8080端口(后面请求的时候用的就是8080端口),并进入终端
$docker run -p 8080:8000 -it django bash
$django-admin startproject app
在/usr/src/路径下执行该命令,会在当前目录下创建一个app文件夹及相关文件,此时的目录结构为:
其中:
manage.py
是一个命令行工具,可以通过该工具与django工程进行交互;
__init__.py
标识这是一个python package;
setting.py
包含了当前工程的配置信息;
urls.py
包含了当前工程的url声明;
wsgi.py
是WSGI web服务入口点;
4.1 新建 search.py
文件,这个文件主要用来请求的处理。获取到网页上填写的图片地址之后,进行图片下载,编码,向tfserving 服务发送请求,获得分类结果,再返回结果到页面。
from django.http import HttpResponse
from django.shortcuts import render_to_response
import base64
import requests
import imagenet_id_to_name
# send encoded image to tfserving
def request_classification(image_url):
SERVER_URL = 'http://localhost:8501/v1/models/resnet:predict'
IMAGE_URL = image_url
# Download the image
print("download image...")
try:
dl_request = requests.get(IMAGE_URL, stream=True)
dl_request.raise_for_status()
# Compose a JSON Predict request (send JPEG image in base64).
print("Compose a JSON Predict request")
predict_request = '{"instances" : [{"b64": "%s"}]}' % base64.b64encode(dl_request.content).decode('ascii')
# Send few requests to warm-up the model.
#print("warm-up the model")
#for _ in range(3):
#response = requests.post(SERVER_URL, data=predict_request)
#response.raise_for_status()
# Send few actual requests and report average latency.
print("send requests")
total_time = 0
num_requests = 1
for _ in range(num_requests):
response = requests.post(SERVER_URL, data=predict_request)
response.raise_for_status()
total_time += response.elapsed.total_seconds()
prediction = response.json()['predictions'][0]
print('Prediction class: {},class name: {}, avg latency: {} ms'.format( prediction['classes'],imagenet_id_to_name.imagenet_name_dict[prediction['classes']], (total_time*1000)/num_requests))
return imagenet_id_to_name.imagenet_name_dict[prediction['classes']]
except:
return 'something wrong happend'
# start page
def search_form(request):
return render_to_response('search_form.html')
# result page
def search(request):
request.encoding = 'utf-8'
print(request.GET)
if len(request.GET)>0:
message = request.GET['info']+'\n\n'+ request_classification(request.GET['info'])
#return HttpResponse(message)
image_url = request.GET['info']
predict_name = request_classification(request.GET['info'])
return render_to_response('search_result.html',{'image_url':image_url,'predict_name':predict_name})
else:
return render_to_response('search_result.html',{'image_url':'incorrect','predict_name':'Please input right image URL'})
这里定义了三个函数:
search_form
是渲染初始的页面,具体的html代码在search_form.html文件中;
search
是结果的展示页面,显示图片和识别结果;
request_classification
用于根据用户填写的URL下载对应的图片,进行编码后,向tfserving 发送请求,得到预测结果后,将类别id转换为name,返回。这里有一点需要注意一下,由于base64在python2.x和Python3.x中的编码方式有差异,而我们使用的tfserving镜像用的是是Python2.x版本,所以如果我们使用的是Python3.x发送编码的图片,那么在编码的时候需要处理一下,增加一个decode的操作,即base64.b64encode(dl_request.content).decode(‘ascii’),否则会因为编码不一致导致出错。
4.2 在manage.py
同级目录下新建一个名为templates文件夹,用于存放html文件。新建两个html文件,分别为search_form.html和search_result.html文件。
内容分别为:
(1) search_form.html
image recognition
This is a demo of image recognition task using Django and tfserving.
Type your image URL in the input box below,and cilck the predict button,it will gives a prediction result.
(2) search_result.html
image recognition
This is a demo of image recognition task using Django and tfserving.
Type your image URL in the input box below,and cilck the predict button,it will gives a prediction result.
The image of your input image URL is:
predict result: {{ predict_name }}
4.3 修改urls.py
文件
from django.conf.urls import url
from django.contrib import admin
from . import search
urlpatterns = [
url(r'^search-form$', search.search_form),
url(r'^search$', search.search),
]
4.4 修改setting.py
文件
修改其中的TEMPALTES中的DIR为:'DIRS': [BASE_DIR+"/templates"]
,也就是刚刚的.html文件所在目录;
4.5 新建一个imagenet_id_to_name.py
文件
这个文件是一个从类别id到类别名称的映射,是将模型预测的id转换为可读的类别名称,文件内容较长,具体内容可以见imagenet_id_to_name.py
完成上述步骤后,最终得到的文件目录结构为:
这里面几个文件的关系简要描述如下:
1. 从urls.py开始,这里定义了一个urlpatterns的列表,定义了请求过来执行什么样的对应动作(函数);
如 url(r’^search$’, search.search),请求的URL是search,会执行search.py中的search函数
2. search.py是一个Python代码文件,里面定义了一些函数操作:
如:
def search_form(request):
return render_to_response(‘search_form.html’)
根据urls.py里面定义的接受到的请求,执行对应的函数里面的内容
3. 执行的可以是return一个HttpResponse(message),或者是渲染一个页面,比如render_to_response,参数是一个.html文件及相关参数,不同的操作可以渲染不同的页面。
整个过程中可能还会遇到一些比如requests
模块没有安装的情况,手动安装即可。
最后运行server:
$ python manage.py runserver 0:8000
正常情况下应有如下结果:
然后在浏览器中访问:http://localhost:8080/search-form
Done!