##はじめに
Flaskで作られたWebサーバに画像が投稿されたらOpenCVで加工して結果をブラウザに表示するようにした。
axiosでimageをPOSTして、Viewに反映するのに手こずったのでメモ
##大まかな流れ
- Indexでフォームを表示する
- フォームで追加されたファイルを受け取る
- OpenCVで読み込む
- ファイルを加工する(縮小とか、色を変えたりとか)
- 加工したファイルをクライアント側へ返す
##手順
- Githubのリポジトリをクローン
https://github.com/toshi1127/My-tutorial-flask-and-react.git
git clone https://github.com/toshi1127/My-tutorial-flask-and-react.git
- node_modulesをインストールする
npm insatll
- webpackを使ってReact.jsをコンパイルする
npm run build
##使用例
###アプリケーションサーバのコード
server.py
import os
import io
import time
import numpy as np
import cv2
import base64
from flask import Flask, render_template, request, redirect, url_for, Response
from io import BytesIO
from werkzeug import secure_filename
import math
import sys
app = Flask(__name__)
UPLOAD_FOLDER = './uploads'
IMAGE_FOLDER = './image'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'PNG', 'JPG'])
IMAGE_WIDTH = 640
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['IMAGE_FOLDER'] = IMAGE_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('/send', methods=['GET', 'POST'])
def send():
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> '''
# BytesIOで読み込んでOpenCVで扱える型にする
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)
# ここでOpenCVで好きな処理をする
raw_img = cv2.resize(img, (IMAGE_WIDTH, int(IMAGE_WIDTH*img.shape[0]/img.shape[1])))
gray_img = cv2.cvtColor(raw_img, cv2.COLOR_BGR2GRAY)
retval, buffer = cv2.imencode('.png', gray_img)
jpg_as_text = base64.b64encode(buffer)
return Response(response=jpg_as_text, content_type='image/jpeg')
else:
return redirect(url_for('index'))
if __name__ == '__main__':
app.debug = True
app.run()
###アプリケーションクライアントのコードの流れ
- 送信ボタンが押された時に、画像データをFormDataオブジェクトに追加し、APIを叩く
- StateのisLoadをtureに変更して、roadComponentをレンダリングする
- レスポンスが返ってきたら、StateのreceiveDateに画像データを格納に、Viewに反映させる
frontend/index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
BrowserRouter,
Route, Switch
} from 'react-router-dom'
import App from './app';
const ImageApp = () => (
<BrowserRouter>
<div>
<Route path='/' component={App} />
</div>
</BrowserRouter>
)
ReactDOM.render(
<ImageApp/>,document.getElementById('root')
);
frontend/app.js
import React from "react";
import ImageSend from './container/imageSend';
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<ImageSend/>
</div>
);
}
}
container/imageSend.js
import React from "react";
import axios from "axios";
import Loading from "../component/roadComponent";
export default class ImageSend extends React.Component {
constructor(props) {
super(props);
this.state = {
imageDate: null,
receiveDate: null,
isLoad: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onInput = this.onInput.bind(this);
}
onSubmit(e) {
const { imageDate } = this.state;
const formData = new FormData();
formData.append("img_file", this.state.imageDate, "testImage.png");
this.setState({
isLoad: !this.state.isLoad
});
axios({
method: "post",
url: "/send",
data: formData,
config: { headers: { "Content-Type": "multipart/form-data" } }
}).then((response) => {
this.setState({
receiveDate: response.data,
isLoad: !this.state.isLoad
});
});
}
onInput(e) {
const files = e.target.files;
this.setState({
imageDate: files[0]
});
}
render() {
const { receiveDate, isLoad } = this.state;
if (!isLoad) {
return (
<div>
<input
type="file"
name="upfile"
id="upfile"
accept="image/*"
capture="camera"
onInput={this.onInput}
/>
<button onClick={this.onSubmit}>送信</button>
{receiveDate && <img src={`data:image/png;base64,${receiveDate}`} />}
</div>
);
}
return <Loading />;
}
}
axiosでAPIを叩いて、レスポンスが返ってくるまで描画するView
component/roadComponent.js
import React from "react";
import styled, { keyframes } from "styled-components";
import { media } from "../utile/Helper";
const rotate360 = keyframes`
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
`;
export const LoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 400px;
`;
export const LoadingContent = styled.p`
width: 70px;
height: 70px;
border: 4px solid #00f;
border-top-color: #fff;
border-radius: 100%;
animation: ${rotate360} 0.6s linear infinite;
background-color: #fff;
box-sizing: border-box;
`;
const Loading = () => (
<LoadingContainer>
<LoadingContent />
</LoadingContainer>
);
export default Loading;
templates.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>File</title>
</head>
<body>
<div id="root"></div>
<script src="./static/bundle.js" ></script>
</body>
</html>