[React Flask OpenCV]在Flask和OpenCV中发布的图像由OpenCV处理并返回。


简介

将图像发布到使用Flask制成的Web服务器时,将使用OpenCV处理该图像,并将结果显示在浏览器上。
我很难用axios发布图像并将其反映在View中,所以记下

粗流量

  • 按索引显示表格
  • 接收表单中添加的文件
  • 用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

    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
    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中
  • 前端/ index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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')
    );

    前端/ app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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>
            );
        }
    }

    容器/imageSend.js

    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
    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并绘制,直到返回响应为止。

    组件/ roadComponent.js

    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
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        File
    </head>
    <body>
      <div id="root"></div>
      <script src="./static/bundle.js" ></script>
    </body>
    </html>

    执行结果

    image.png
    スクリーンショット 2018-09-29 10.05.31.png