【保存版】Flaskで画像を操作する際の6パターンを書き分ける

by

クリエイターとエンジニアを応援するNP-Systems@個人開発

Flask上で画像を操作する際のソースコードのパターンについてですが、 画像の生成元をどうするかというもので3パターン、画像の最終的な処理方法をどうするかというもので2パターンで(組み合わせで)合計6パターンがあると思います。Flaskで画像処理を実装しようとするとこれらを書き分ける必要がありますのでそれについて記載します。

画像の生成元の3つとは

  • HTMLからFlaskで立てたURLへ直接画像がアップロードされる場合、
  • Flaskの関数の中でmatplotlibなどで生成する場合、
  • クラウドないしはローカルフォルダなどに保存されている画像を読み込んでくる場合

の3つです。

最終的な処理方法として2つあるというのは、

  • 画像をクラウドないしはローカルフォルダなどに保存する場合と
  • クライアント側へ画像をreturnする場合

の2つです。メタ情報だけ抽出して文字列で返す場合などもあると思いますが、これはJSONなどでリターンすればOKなので上記6パターンで対応できると思います。

Flaskで画像を操作したいと思うと、この6パターンについてソースコードを書き分ける必要があり、かなり大変です
しかし、幸いなことに、画像をどこかに保存する場合とクライアント側へ返す場合については画像をメモリ上でBlob形式であらかじめ保存することができれば同時に対応できますので、事実上かき分けなければならないのは3パターンとなります。
この記事では、画像をメモリ上でBlob形式で書き出すコードを使いながらこの3パターンについて整理したいと思います。

なお、ここでは処理後の画像の保存先としてGoogle Cloud Storageを利用した例で説明しますが、適宜AWSのS3、ローカルのフォルダの場所などと読み替えて下さい。今回記載するサンプルコードは下記の3つです。

1. 画像をFlaskの関数内で生成し、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
2. 画像をクライアントから受け取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
3. 画像をGSCから読み取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す

画像をFlaskの関数内で生成し、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    buf = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )
if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

ポイントは2つあって、一つ目はGUIに対応していないFlask上でmatplotlibが動作するようにmatplotlib.use(‘Agg’)というコードを入れていること二つ目はsavefigの部分でメモリ上でファイルを書き出していることです。ローカルで動かす場合は、普通にHDD(SSD?)にjpgなどで書き出しても問題ないと思いますが、クラウドなどだとローカルへのアクセスが禁止されていることが往々にしてあります。この場合に備えて、メモリ上でファイルの読み書きを行うioモジュールを使ってメモリ上に画像を出力しています。ここは個人的に結構ポイントです。

それでは次に既に保存されている画像を取ってくる場合です。

画像をGSCから読み取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

クラウドからデータを持ってきます。重いデータなどは予め画像にしておくと便利ですのでよく使っています。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './for-dev-268800-ed3aecf9b014.json'
    storage_client = storage.Client()
    bucket = storage_client.get_bucket('for-dev-268800.appspot.com')#"'for-dev-268800.appspot.com'

    source_blob_name = "path/to/your/file_on_gcs.jpg"
    
    #検索する場合はここを使う
    """
    blobs = bucket.list_blobs(prefix="")
    for blob in blobs:
        print(blob.name)
        print(dir(blob))
        print(blob.content_type)
        source_blob_name = blob.name
        content_type = blob.content_type
    print(inspect.getfullargspec(bucket.list_blobs))
    """    

    blob = bucket.get_blob(source_blob_name)
    buf = io.BytesIO()
    blob.download_to_file(buf)
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについてはブラウザなどで直接叩いたいただければ問題ありません。

画像をクライアントから受け取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

最後のパターンです。これはクライアントから画像を受け取る場合ですが、要するにユーザがブラウザから画像を選択してアップロードするよく使うやつです(curlでこれをコード化すると、画像の一括アップロードなどにも使えます)。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():   
    if request.files['image']:
        filename = request.files['image'].filename
        content_type = request.files['image'].content_type
        # 画像として読み込み
        stream = request.files['image'].stream
        img_array = np.asarray(bytearray(stream.read()), dtype=np.uint8)
        img = cv2.imdecode(img_array, 1)
        is_success, buffer = cv2.imencode(".jpg", img)
        buf = io.BytesIO(buffer)
        #buf = io.BytesIO()
        buf.seek(0)

        os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './path/to/your/credential_goole.json'
        storage_client = storage.Client()
        bucket = storage_client.get_bucket('your_backetname.com')
        blob = Blob(filename, bucket)
        blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

        return send_file(
            buf,attachment_filename=filename,as_attachment=True
        )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについては画像をHTMLからアップロードする必要があります。
APIを叩いてcanvas要素に描写するHTMLも載せておきますので、こちらを参考にしてください。buf変数に読み込んだものをOpenCVなどで操作すれば簡単な画像処理ソフトになると思います。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <title>bootstrapとjquery</title>

  </head>
  <body>

<main>
<article>
  <section>
       <input type="button" value="Start" onclick="main();"/>
        <canvas style="height: 30vw;width:50vh"></canvas>
  </section>
</article>
</main>

<script language="javascript" type="text/javascript">

    function main(){
      var canvas = document.getElementsByTagName('canvas');
      var ctx = canvas[0].getContext('2d');

      var img = new Image();
      img.src = 'http://0.0.0.0:5000';//FlaskでホスティングしたURL

      img.onload = function() {
        img.style.display = 'none'; // ようわからん
        console.log('WxH: ' + img.width + 'x' + img.height)

        ctx.drawImage(img, 0, 0);
        var imageData = ctx.getImageData(0, 0, img.width*2, img.height*2)

        for(x = 0 ; x < 1000 ; x += 10) {
          for(y = 0 ; y < 1000 ; y += 10) {
             ctx.putImageData(imageData, x, y);
          }
        }
      };    }
</script>

  </body>
</html>

curlコマンドでForm送信は代替できます。
HTMLのform送信をcurlコマンドで代替する方法はこちら

Flaskで画像を処理場合のソースコードはパターンごとに書き分ける必要があり、普段は目の前のタスクを終わらせて終わりがちですが、今回は6つのパターンについてまとめました。AWSのS3を使う場合やローカルで完結させる場合も同様に処理できると思いますので、ぜひ参考にしていただけるとうれしいです。

この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:

Matplotlibで作成したグラフをクライアントへ返す方法についてさらに詳細をまとめたのはこちら

https://qiita.com/NP_Systems/items/9bea70ade73cc48cbdf0


About Author: M. Shikishima

@Masaya04997245 会社員をしながら個人でシステム開発をしています.
this is single-default.php
最新記事
同カテゴリの記事 python