前回、核となる変換機能が上手くできることが確認できました。
続いて(見た目はとりあえずおいておいて、)「画像をアップロード→変換→結果を表示」というミニマムな機能を Flask で実装して、とりあえず動く物を作りたいと思います。
とはいえ Flask や データベース周りのことはほぼ知識がなく、自分がやりたいことと似たことをやっているこちらのブログを参考にさせていただきました。ありがとうございます。
・画像をアップロードして POST できる index ページを作成
・app.py でPOSTされた画像を受け取って、変換を行い、result.html にわたす処理を書く
・result.html で結果を表示する
まずは html を作成
最低限の見た目のhtml を書いていきます。といっても参考にさせてもらったブログのものほぼそのままです。
<index.html>
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>File</title> </head> <body> <form method="post" action="/result" enctype="multipart/form-data"> <input type="file" id="img_file" name="img_file"> <input type="submit" value="送信"> </form> </body> </html> |
ただ画像をアップロードするフォームと送信ボタンだけ。
<result.html>
1 2 3 4 5 6 7 8 9 10 11 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>File</title> </head> <body> <p><img src="{{ raw_img_url }}"></p> <p><img src="{{ out_img_url }}"></p> </body> </html> |
こちらは、入力画像と出力画像を表示するだけです。
Flask では {{ hogehoge }} とすることで、app.py からhogehoge という値を html に送ることができます。
app.py を作成
ここが肝です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import os import io import time import numpy as np import cv2 from flask import Flask, render_template, request, redirect, url_for, send_from_directory, session from werkzeug.utils import secure_filename import torch import torchvision from torchvision import models from torchvision import transforms from PIL import Image app = Flask(__name__) UPLOAD_FOLDER = './uploads' ALLOWED_EXTENSIONS = set(['png', 'jpg', 'PNG', 'JPG']) IMAGE_WIDTH = 640 app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['SECRET_KEY'] = os.urandom(24) def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS @app.route('/') def index(): return render_template('index.html') @app.route('/result', methods=['GET', 'POST']) def result(): if request.method == 'POST': img_file = request.files['img_file'] if img_file and allowed_file(img_file.filename): filename = secure_filename(img_file.filename) else: return ''' <p>許可されていない拡張子です</p> ''' f = img_file.stream.read() bin_data = io.BytesIO(f) file_bytes = np.asarray(bytearray(bin_data.read()), dtype=np.uint8) img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) # リサイズ raw_img = cv2.resize(img, (IMAGE_WIDTH, int(IMAGE_WIDTH*img.shape[0]/img.shape[1]))) # リサイズ画像の保存 raw_img_url = os.path.join(app.config['UPLOAD_FOLDER'], 'raw_'+filename) cv2.imwrite(raw_img_url, raw_img) #------ ここから推論処理 ------------------- # 前処理 transform = transforms.ToTensor() inputs = transform(raw_img).unsqueeze(0) # 推論 model = models.segmentation.deeplabv3_resnet101(pretrained=True, num_classes=21) model.eval() pred = model(inputs) # 後処理 mask = torch.argmax(pred["out"][0], 0) mask = np.stack([mask, mask, mask], 2) res = np.where(mask != 15, raw_img , 0) # スタイル画像を読み込み mosaic = cv2.imread("./mosaic/style1.jpg") mosaic = cv2.resize(mosaic, (res.shape[1], res.shape[0])) out = np.where(res == 0, mosaic, res) # 結果を保存 out_img_url = os.path.join(app.config['UPLOAD_FOLDER'], 'out_'+filename) cv2.imwrite(out_img_url, out) return render_template('result.html', raw_img_url=raw_img_url, out_img_url=out_img_url) else: return redirect(url_for('index')) @app.route('/uploads/<filename>') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename) if __name__ == '__main__': #app.debug = True app.run() |
・from werkzeug import secure_filename
だとエラーを履かれたので、from werkzeug.utils import secure_filename
に修正
・post で受け取った画像を cv2 で扱える形に変換するくだりはよくわからずまるまる写すだけになってしまったので勉強しなければ、、。
・day1 でやった時は Pillow で読み込み&リサイズしていたけれど、cv2 に変更
・スタイル画像ははじめから mosaic フォルダに準備しておいてそこから読み込む
・def uploaded_file は result ページで結果画像を表示するために必要。
作成したファイルはこのように配置します。

uploads フォルダの中にある画像ファイルはデフォルトは空で、ユーザーがアップロードした画像と変換処理かけた後の画像がそこに保存されます。
mosaic フォルダの中の style 画像は、3 ついれているけれども style1.jpg しか使っていません。これはのちのち選択できるようにしたい。
挙動を確認
スタイル画像には前回と違いこんなものを用意しました。

それでは挙動を確認します。 python3 app.py
した後に、http://127.0.0.1:5000/ こちらにアクセスします。
<index.html>

<result.html>

いい感じにできました!
ただし懸念点としては以下 2 つ
・推論時間結構かかる。7, 8秒ほどはちょっと長いので工夫が必要。(今回640px にリサイズしてこの遅さなので、もっと高解像度のもの扱うとしたら時間かかりすぎてしまう)
・640px にリサイズしてからの推論だと、セグメンテーション結果が悪い
見た目ももっと凝りたいし、今回のをベースにどんどん改良していこう。
(続き)