# 前言
在「從零開始學Flask」中學到了Flask的基礎知識,而在這個教學中我們將會應用這些知識。我學習Flask最主要的原因就是希望能夠將深度學習模型與API串接在一起,來達到更多的應用。這也是現在許多公司認為應該要有的技能,因此這個教學中我們會學習將深度學習模型串接API,來建立一個用來辨識的API。
我是根據[Deploying Deep Learning Model using Flask API](https://medium.com/@draj0718/deploying-deep-learning-model-using-flask-api-810047f090ac)這篇文章進行修改。由於我習慣用pytorch,但文章內是使用tensorflow,所以我對一些部分進行修改。如果想使用tensorflow可查閱上面那篇文章。
# 說明
建立一個ImageNet的辨識API,模型使用pytorch預訓練好的ResNet50,如下圖所示。
- 執行app.py後,會跳出一個初始網頁
- 上傳照片進行辨識
- 顯示辨識結果
==初始網頁==

==辨識結果==

## About the Model
我是使用PyTorch透過ImageNet預訓練好的ResNet50,這個模型的辨識類別共有1000類,詳細的說明可查閱[相關文件](https://pytorch.org/vision/main/models/generated/torchvision.models.resnet50.html)。由於是使用PyTorch提供的模型,所以可以直接在程式中載入。
## Project Structure

- static資料夾中用來放辨識影像
- templates資料用來放所有html檔
- ```app.py```主要API程式
- ImageNet類別檔
接下來我們會說明```app.py```,再來說明```home.html```與```predict.html```。
## app. py
```app.py```是主要用來建立辨識API的檔案,其中包含一些我們會用到的methods:
- **read_image():** 用來讀取影像,並進行影像前處理。其中包含了```torch.stack```,用來將tensor大小轉為```[batch, 3, 244, 244]```
- **allow_file():** 用來確認影像的副檔名是否為jpg, jpeg, png。
- **homeapge():** 用來呼叫初始頁面(```home.html```),其url設定為```/```
- **predict():** 用來辨識影像的類別,並將辨識結果傳至```predict.html```。
```python=
from flask import Flask, render_template, request
import torch
from torchvision import transforms
from torchvision.models import resnet50
from PIL import Image
import os
# 固定格式
app = Flask(__name__)
# 載入resnet50
model = resnet50(pretrained=True)
# 由於是直接拿來辨識,所以要轉成eval()模式
model.eval()
transform = transforms.Compose([
transforms.Resize([224,224]),
transforms.ToTensor(),
transforms.Normalize(mean = (0.5,0.5,0.5),
std = (0.5,0.5,0.5))])
# 將label轉成list
with open('./imagenet-classes.txt', 'r') as f:
labels = [line.strip() for line in f.readlines()]
# 讀取影像
def read_image(filename):
img = Image.open(filename)
img = transform(img)
# 轉成[1,3,244,244]
img_stack = torch.stack([img])
return img_stack
# allow files with png, jpg, jpeg
ALLOW_EXT = set(['jpg', 'jpeg', 'png'])
def allow_file(filename):
return '.' in filename and \
filename.rsplit('.',1)[1] in ALLOW_EXT
# 最初始頁面
@app.route('/')
def homeapge():
return render_template('home.html')
# 進行辨識
@app.route('/predict', methods = ['GET', 'POST'])
def predict():
if request.method == 'POST':
file = request.files['file']
# 判斷副檔名
if file and allow_file(file.filename):
filename = file.filename
# 將影像儲存
file_path = os.path.join('./static/images', filename)
file.save(file_path)
# 讀取影像
img = read_image(file_path)
# 進行辨識
output = model(img)
# 取得機率值最大的index
_, preds = torch.max(output.data, 1)
# 根據index從label list中取得類別名稱
classes = labels[preds[0]]
return render_template('predict.html', classes=classes, user_image=file_path)
else:
return "Unable to read the file. Please check file extension."
# 固定格式
if __name__ == '__main__':
app.run(debug=True)
```
第15-19行是用來進行影像前處理的方法,包含```Resize```,```ToTensor```,```Normalize```。
第21-22行是將```imagenet-classes.txt```中的類別轉成```list```,以便之後讀取類別名稱。
第62行會將```classes```和```file_path```傳到```predict.html```,用於之後顯示於網頁上。
## home.html
用來呈現我們辨識API的初始頁面,我們會接收到從user中上傳的input,也就是要辨識的影像。
```html=
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<title>Chris's ImageNet Prediction</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-lg-12">
<center><h1 style="padding:30px;font-weight:600 !important;font-size: 3.2em">IMAGENET PREDICTION</h1></center>
</div>
<div>
<div class="col-lg-12">
<center>
<form class="form-inline" action="/predict" method="post" enctype="multipart/form-data">
<input type="file" name="file"/><br>
<input type="submit" class="btn btn-success" value="Predict">
</form>
</center>
</div>
</div>
</div>
</div>
</body>
</html>
```
第16-19行會將上傳的影像傳到```app.py```中的```predict()```進行處理及辨識。
## predict.html
這是辨識API的result page。當在初始頁面按下```predict```按鈕時,會將影像傳到```app.py```中的```predict()```進行處理及辨識。最後將結果呈現在這個頁面。
```html=
<!DOCTYPE html>
<html>
<head>
<!--Require meta tags-->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<title>Chris's ImageNet Prediction</title>
</head>
<body>
<div class="container">
<div class="col-lg-12">
<center><h1 style="padding: 30px;font-weight: 600 !important;font-size: 3.2em;">ImageNet Prediction Result</h1></center>
</div>
<div class="col-lg-12">
<center><form class="form-inline" action="/predict" method="post" enctype="multipart/form-data">
<input type="file" name="file"/><br>
<input type="submit" class="btn btn-success" value="Predict">
</form></center>
</div>
<div class="col-lg-5" style="padding-top: 30px">
<span class="border border-primary">
<img src="{{ user_image }}" alt="User Image" class="img-thumbnail" style="width: 250px;height: 250px;float: right">
</span>
</div>
<div class="col-lg-5" style="padding-top: 30px">
<h4>Class Name is <mark style="background-color:#04aa6d;color:white;border-radius:5px">{{ classes }}</mark></h4>
</div>
</div>
</body>
</html>
```
第16-19行同樣會將上傳的影像傳到```app.py```中的```predict()```進行處理及辨識。
第21-25行用來接收從```app.py```中的```predict()```傳來的```file_path```,並進行呈現。
第26-28行用來接收從```app.py```中的```predict()```傳來的```classes```,並進行呈現。
## Output Screen
執行```app.py```之後,在terminal或cmd會得到這個```output```

接著複製url(http://127.0.0.1:5000),並貼上瀏覽器。接著將影像上傳至初始頁面,並按下```predict```按鈕。模型就會開始進行辨識,最後將結果呈現在頁面上囉!
[Source Code Please Visit](https://github.com/ChrisCodeNation/ImageNet-Recognition-API-with-Flask-in-An-Easy-Way)