画像を保存せず加工し、S3へアップロードする方法【GAE・S3】
「サーバー上で画像を加工したいけど、保存する処理を挟んでおり加工できない」
「S3にアップロードする場合も、保存する処理を挟んでて、、、(以下同様)」
GAEなどのサーバーにアップロードしたときに、.save関数などで保存することができない場合があります。
しかし、多くの画像加工やS3へのアップロード処理などは、一度保存してあるファイルを加工ないしは、アップロードするような処理が多く乗っており、
このままでは、保存ができない環境で同様の処理が実現できません。
なので、今回は、画像を保存する処理をせずに、直接サーバー上で「画像を加工」し「S3へアップロードする」ところまでを解説いたします。
実際にこの処理でGoogleAppEngine(GAE)にアップロードして動くことは確認できていますので、参考にしてみてください。
目次(クリックで読みたい部分にジャンプできます)
全体の処理の流れ
全体の流れ
①form から画像を取得
②ファイルをbytesに変換
③pilにして画像を加工
④bytesに戻してS3へアップロード
以上の処理の流れを順番に解説していきます。
formから画像を取得
uploaded_file = request.files['upfile']
if uploaded_file.filename == '':
return jsonify(message='ファイルを指定してください'), 400
index.htmlからファイルをアップロードし、取得します。
request文については、以下で詳しく解説しいます。
ここで取得するファイルの形式をそのまま画像加工やS3にアップロードすることはできません。
なので、一度保存して再度アップロードする方法をとっているわけですが、
今回は、ファイルの形式を変えることで直接処理を行うようにします。
ファイルをbytesに変換
file = io.BufferedReader(uploaded_file).read()
この処理により、.openメソッドと同じ形式のファイルに変換することができます。
.openメソッドでのファイル形式は「bytes」形式です。
もし、このままS3へアップロードしようと思っている方は、この「bytes」形式でアップロードすることが可能です。
なので、次の画像加工などは無視してもらって問題ありません。
(最後に全体のコードを載せて、不要なところを示していますので、そこを削除してください)
PILにして画像加工
アップロードした画像ファイルには、大小さまざまであり画像の大きさがバラバラです。S3にあげるだけだとそれでいいのですが、今回は、そのあとS3のパスを取得して画像を表示させることが必要でした。
なので、画像のサイズがバラバラだと表示するときに面倒です。
なので、画像の大きさをトリミングしてからS3にアップロードすることにしています。
thumb_file = photo.make_thumbnail(file,300)
別のフォトファイルからmake_thumbnailの関数を呼び出しています。
以下photo_file.pyの中身
#サムネイルを作成する
def make_thumbnail(file,size):
#bytes->pil
num_byteio = io.BytesIO(file)
num_pil = Image.open(num_byteio)
#正方形に切り取る
img = num_pil
msize = img.width if img.width < img.height else img.height
img_crop = image_crop_center(img, msize)
#指定サイズにリサイズ
img_resize = img_crop.resize((size, size))
#pil->bytes
img_bytes = io.BytesIO()
img_resize.save(img_bytes, format='png')# 仮にpngにしている
img_final = img_bytes.getvalue()
return img_final
#画像の中心を正方形に切り取る
def image_crop_center(img,size):
cx = int(img.width/2)
cy = int(img.height/2)
img_crop = img.crop((
cx - size / 2, cy - size / 2,
cx + size / 2, cy + size / 2
))
return img_crop
画像の加工には、PILモジュールを使っています。
画像加工には、PILモジュールは手軽に使うことができ便利です。
ただ、画像をPIL形式にしないとモジュールの中で加工することができないので、まず「bytes形式」を「PIL形式」に変換します。
画像加工の内容ですが、今回はトリミングのみです。
PILの画像加工は他のサイトにもたくさん載っているのでそちらを参考にしてみてください。
そして、加工が終わったら再び「bytes形式」に戻す処理をします。
bytesに戻してS3へアップロード
# S3アップロード処理
load_dotenv()
s3 = boto3.client('s3')
s3_bucket = '<バケット名>'
filename = uploaded_file.filename
response = s3.put_object(
Body = thumb_file,
Bucket = s3_bucket,
Key = 'test/' + filename
)
if response['ResponseMetadata']['HTTPStatusCode'] != 200:
return jsonify(message='S3へのアップロードでエラーが発生しました'), 500
return redirect(url_for('index'))
S3のput_object関数を使い、アップロードします。
S3へのアップロードなど詳しいことは以下の記事で解説していますので、併せて読んでみてください。
余談ですが、返り値にS3にアップロードした画像パスを返せば、ページに画像を表示することも可能かと思いますので、やってみたい方は試してみてください。
全コード
【main.py】
import io,os
import boto3
from flask import Flask,jsonify,request,redirect,url_for,render_template
from dotenv import load_dotenv
import photo_file as photo
#インスタンスの作成
app = Flask(__name__)
key = os.urandom(21)
app.secret_key = key
@app.route('/')
def index():
return render_template('index.html')
load_dotenv()
s3 = boto3.client('s3')
@app.route('/upload', methods=['POST'])
def workflow_create():
if request.method == 'POST':
# アップロードファイルを取得
uploaded_file = request.files['upfile']
# エラーチェック
if uploaded_file.filename == '':
return jsonify(message='ファイルを指定してください'), 400
# S3アップロード処理
s3_bucket = 'objectdistribution'
filename = uploaded_file.filename
#アップロードされたファイルをbytesに変換
file = io.BufferedReader(uploaded_file).read()
thumb_file = photo.make_thumbnail(file,300)#←画像加工しない場合はこの行を削除&下のthumb_fileを修正
response = s3.put_object(
Body = thumb_file,
Bucket = s3_bucket,
Key = 'test/' + filename
)
if response['ResponseMetadata']['HTTPStatusCode'] != 200:
return jsonify(message='S3へのアップロードでエラーが発生しました'), 500
return redirect(url_for('index'))
#アプリケーションの起動
if __name__ == '__main__':
app.run(debug=True)
【photo_file.py】
import os,io
from PIL import Image
#サムネイルを作成する
def make_thumbnail(file,size):
#bytes->pil
num_byteio = io.BytesIO(file)
num_pil = Image.open(num_byteio)
#正方形に切り取る
img = num_pil
msize = img.width if img.width < img.height else img.height
img_crop = image_crop_center(img, msize)
#指定サイズにリサイズ
img_resize = img_crop.resize((size, size))
#pil->bytes
img_bytes = io.BytesIO()
img_resize.save(img_bytes, format='png')# 仮にpngにしている
img_final = img_bytes.getvalue()
return img_final
#画像の中心を正方形に切り取る
def image_crop_center(img,size):
cx = int(img.width/2)
cy = int(img.height/2)
img_crop = img.crop((
cx - size / 2, cy - size / 2,
cx + size / 2, cy + size / 2
))
return img_crop
まとめ
今回は、GAEなどのサーバーにアップロードしたときや、保存することができない環境のときに、画像を直接加工・アップロードする際の処理を紹介しました。
また、GAEでアップロードする場合アクセスできないなどの障害が出ることがあります。
もし、これからGAEを使うことを考えている方がいれば以下の記事も参考にしてみるとより良いかと思います。