このページでは、Django での画像処理を行う手順について解説していきます。
より具体的には、アップロードされた画像に対して PIL を用いて画像処理を行う手順について解説します。
このページで解説した内容を参考にしていただければ、アップロードされた画像に対し、下のアニメのように簡単な画像処理を行うことができるようになります(下のアニメでは回転とグレースケール化を実施)。
画像のアップロード自体は下記ページで解説していますので、まだアップロードの仕方をご存知ない方は下記ページを事前に読んでおいていただけると幸いです。
【Django】画像をアップロードするContents
PIL での画像処理
まずは Django は利用せずに、PIL 単体で画像処理を行う手順について解説します。
PIL については十分詳しいよーという方は、次の Django で画像処理を行う際のポイント までスキップしていただければと思います。
PIL で画像処理を行う手順
PIL を用いて画像処理を行う基本的な手順は下記のようになります。
open
関数画像ファイルを読み込んで PIL 画像オブジェクトを生成する- PIL 画像オブジェクトにメソッドを実行させて画像処理後の PIL 画像オブジェクトを生成する
- 画像処理後の PIL 画像オブジェクトに
save
メソッドを実行させて画像処理後の画像をファイル保存する
もちろん行う画像処理によって手順が異なる場合もありますが、上記手順を踏むことで PIL を利用した様々な画像処理を実現することができます。
スポンサーリンク
画像の回転
例えば下記の Python スクリプトを実行すれば、file_name
に指定したパスの画像ファイルから PIL 画像オブジェクトが生成され、回転後の画像が 'result.png'
という名前でファイル保存されます。
from PIL import Image
file_name = 'original.png'
org_img = Image.open(file_name)
ret_img = org_img.rotate(30)
ret_img.save('result.png')
画像の回転を行なっているのは rotate
メソッドになります(引数には回転角度を指定します)。
ちなみにですが、rotate
メソッドの引数に expand=True
を指定すれば、回転後のサイズに合わせて画像のサイズが拡張されることになり、上の図のように回転後の画像が途切れることを防ぐことができます。
画像の回転&画像のグレースケール化
また、下記の Python スクリプトを実行すれば、file_name
に指定したパスの画像ファイルから PIL 画像オブジェクトが生成され、画像の回転およびグレースケール化を行なった画像が 'result.png'
という名前でファイル保存されます。
from PIL import Image
file_name = 'original.png'
org_img = Image.open(file_name)
ret_img = org_img.rotate(30)
ret_img = ret_img.convert('L')
ret_img.save('result.png')
画像の色変換を行なっているのは convert
メソッドであり、convert
メソッドに 'L'
を指定することで画像のグレースケール化を行うことができます。
こんな感じで、画像処理後の PIL 画像オブジェクトにさらにメソッドを実行させることで、複数の画像処理を段階的に実行するようなことも可能です。
ここまでの例を見ていただければ分かるように、とりあえず PIL 画像オブジェクトさえ作ってしまえば、あとはそのオブジェクトにメソッドを実行させることで簡単に画像処理を行うことができます。
また、PIL 画像オブジェクトはファイルを読み込めば作れますし、さらに画像処理後の PIL 画像オブジェクトを画像ファイルとして保存することもできます。
Django で画像処理を行う際のポイント
続いて Django で画像処理を行う際のポイントについて解説していきます。
スポンサーリンク
MEDIA_ROOT
以下のファイルに対して画像処理を行う
Django での PIL を用いた画像処理手順においても、PIL での単体での画像処理同様に、PIL 画像オブジェクトを作成し、そのオブジェクトにメソッドを実行させることで画像処理を行います。
下記ページでも解説していますが、アップロードされた画像は MEDIA_ROOT
以下にファイルとして保存されています。そして、アップロードファイルへのリクエストがあった際には、MEDIA_ROOT
以下に保存されているファイルがレスポンスとして返却されることになります。
ですので、アップロードされた画像の PIL 画像オブジェクトは MEDIA_ROOT
以下のファイルを読み込むことで作成することができます。そして、アップロードされた画像に対する画像処理は、その作成された PIL 画像オブジェクトにメソッドを実行させることで行うことができます。
また、PIL 画像オブジェクトから画像処理後の画像をファイルとして MEDIA_ROOT
以下に保存してやれば、画像処理後の画像ファイルをレスポンスとして返却させることもできます。
つまり、PIL で画像処理を行う手順 で紹介したスクリプトで PIL の open
により直接ファイルを読み込み、さらに PIL の save
により直接ファイルを保存を行なったのと同様に、これらの処理を MEDIA_ROOT
以下のファイルに対して行うことで、アップロードされた画像への画像処理及び画像処理後の画像ファイルのレスポンスの返却も可能になることになります。
ImageFied
を介してファイルの読み書きをした方が無難
ただ、これも下記ページで解説していますが、Django でアップロードされた画像を扱う際には、ImageFied
を持たせた Model を利用した方が画像のファイル保存や保存先のパスの管理が楽になります。
なので、直接 PIL で画像処理後のファイルの保存を行うのではなく、ImageField
を介して画像処理後のファイルの保存を行うようにしていきたいと思います。
いずれにせよ、PIL 画像オブジェクト自体を作成してしまえば画像処理自体は簡単に行えます。なので、Django で画像処理を行う際には、画像処理自体よりも、PIL 画像オブジェクトの作成の仕方や画像処理の PIL 画像オブジェクトのファイル保存の仕方がポイントになると思います。
Django で画像処理を行う手順
以上を踏まえ、Django で画像処理を行う手順について解説していきます。
今回は、実例を用いながら手順を解説していきます。
今回手順を解説するにあたって参照する実例が下記の Model になります。
from django.db import models
import io
from PIL import Image
class UploadImage(models.Model):
image = models.ImageField(upload_to='img/')
result = models.ImageField(upload_to='result/')
def transform(self, angle, gray):
# アップロードされたファイルから画像オブジェクト生成
org_img = Image.open(self.image)
# PILでの画像処理ここから!
ret_img = org_img.rotate(angle)
if gray:
ret_img = ret_img.convert('L')
# PILでの画像処理ここまで!
# 画像処理後の画像のデータをbufferに保存
buffer = io.BytesIO()
ret_img.save(fp=buffer, format=org_img.format)
# 以前保存した画像処理後の画像ファイルを削除
self.result.delete()
# bufferのデータをファイルとして保存(レコードの更新も行われる)
self.result.save(name=self.image.name, content=buffer)
スポンサーリンク
UploadImage
の概要
まず、上記の UploadImage
の概要を説明しておきます。
UploadImage
で管理するデータ
上記の UploadImage
は2つの ImageField
を持っており、image
はアップロードされた画像そのもの、result
は画像処理後の画像を扱うものとしています。
upload_to
の指定で、MEDIA_ROOT
以下のどのフォルダの中に画像を保存するかを指定しており、アップロードされた画像は img/
の中、画像処理後の画像は result/
の中にそれぞれファイルとして保存されることになります。
さらに、2つの ImageField
を持っているので、上記の Model のインスタンスに save
メソッドを実行させた場合、下の図のテーブルの形式に合わせたレコードが保存されることになります(id
はレコード保存時に自動的に割り振られます)。
つまり、アップロードされた画像 image
と画像処理後の画像 result
を1対1の関係で管理することになります。
transform
メソッド
また transform
メソッドは、アップロードされた画像に対して画像処理を実行し、その結果を保存するメソッドになります。
この transform
メソッドは、既にアップロードされた画像が Model の save
メソッドにより保存されていることを想定したメソッドになっています。
transform
メソッドは、まずアップロードされた画像を反時計方向に angle
度回転させ、さらに gray
が True
の場合のみ回転後の画像のグレースケール化を行います。そして、それらの処理結果をフォルダとデータベースに保存します。
この保存では、画像処理後の画像ファイルの MEDIA_ROOT
以下の result/
への保存と、データベースへの画像処理後の画像ファイルの保存先のパスの保存を行います。
また、前述の通り、UploadImage
は下の図のようなテーブルでのレコードを管理する Model になります。
ですが、transform
メソッドでは Model のインスタンスそのものが save
メソッドを実行するのではなく、Model のインスタンスの持つデータ属性 result
が save
メソッドを実行することになるので、上の図のレコード全体が保存されるのではなく、result
の列のフィールドのみが保存(更新)されることになります。
ただ、単に save
メソッドを実行するだけだと画像処理後の画像ファイルがどんどん増えていくことになりますので、save
メソッドを実行する前に以前に保存した画像処理後の画像ファイルを delete
メソッドで削除するようにしています。
アップロードされた画像の削除に関しては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
【Django】アップロードされた画像を削除するPIL 画像オブジェクトの生成
続いて、上記の transform
メソッドを確認しながら、Django での画像処理を行う手順を確認していきたいと思います。
まず、PIL 画像オブジェクトは、下記のようにデータ属性 image
から生成することができます。
# アップロードされたファイルから画像オブジェクト生成
org_img = Image.open(self.image)
image
はアップロードされた画像を管理するデータ属性であり、上記によってアップロードされた画像の PIL 画像オブジェクトを生成することができます。
こんな感じで、ImageField
を持たせた Model を用意しておけば、PIL 画像オブジェクトはそのデータ属性から簡単に生成することができます。
ちなみに open
の引数に self.image.path
を指定した場合でも、同様にアップロードされた画像の PIL 画像オブジェクトを生成することができます(self.image.path
には画像の保存先のパスがセットされている)。
スポンサーリンク
画像処理の実行
PIL 画像オブジェクトが生成できれば、あとはそのオブジェクトにメソッドを実行させて画像処理を行えば良いだけです。
transform
メソッドでは、rotate
メソッドにより画像の回転を、convert
メソッドにより画像のグレースケール化を行なっています。
PIL 画像オブジェクトからの保存
画像処理が完了した後は、画像処理後の PIL 画像オブジェクトからファイルの保存を行なっていきます。
ただ、保存はちょっとややこしいです。
画像のファイル保存
まず、画像のファイル保存を行なっているのは transform
メソッドの下記部分になります。データ属性 result
から save
メソッドを実行しているところがポイントです。
# bufferのデータをファイルとして保存(レコードの更新も行われる)
self.result.save(name=self.image.name, content=buffer)
この save
を実行することで、content
に指定したデータをファイル保存し、さらに保存先のパスをデータベースに保存することができます。
各引数の意味合いや、上記の引数指定によってどのように保存が行われるのかについて確認していきましょう!
引数 name
まず、引数 name
には画像ファイルの名前を指定します。画像ファイルの保存先のパスは、この name
に基づいて決定されます。
具体的には、MEDIA_ROOT
の下の result/nameに指定した名前
に画像ファイルが保存されることになります。result/
は ImageField
の upload_to
で指定したフォルダです。
この name
には self.image.name
を指定しており、self.image.name
はアップロードされた画像の保存先の MEDIA_ROOT
からの相対パスになります。
つまり、画像処理後の画像ファイルの保存パスはアップロードされた画像のファイルの保存パスに基づいて作成されるため、画像処理後の画像ファイルが他のファイルに対する画像処理結果のファイルで上書きされるようなことを防ぐことができます。
引数 content
また、引数 content
にはファイルとして保存するデータを指定します。
要は、ここに画像処理後の画像のデータを指定してやれば、画像処理後の画像のファイルとしての保存を実現することができます。
ただ、この content
には PIL 画像オブジェクトを直接指定することはできないようです。
なので、下記のように一旦 io.BytesIO
に対して PIL 画像オブジェクトの持つ画像処理後のデータを保存し、そしてそのデータを保存した io.BytesIO
を上記の content
に指定するようにしています。
# 画像処理後の画像のデータをbufferに保存
buffer = io.BytesIO()
ret_img.save(fp=buffer, format=org_img.format)
簡単に言ってしまえば、io.BytesIO
はメモリ上のバイナリデータをファイルのように扱うためのクラスであり、PIL 画像オブジェクトの save
メソッドでメモリ上に保存した画像ファイルを content
に指定している感じです。
画像処理後の画像の表示
ちょっとややこしいですが、上記のような一連の流れを踏むことで、ImageField
の result
を介して画像処理後のファイル保存およびレコードへのパスの保存を実現することができます。
ですので、この保存後は、通常のアップロードされた画像の時と同様に画像の情報を扱うことができます。
例えば、画像処理結果をページに表示することを考えてみましょう。
この際には、画像処理後の画像ファイルをリクエストするための URL が必要になります。この URL は、self.result.url
から取得することができます。
ですので、self.result.url
を src
属性に指定した <img>
タグを作成し、それを HTML に持たせておくことで、HTML 受信後にウェブブラウザから画像処理後のファイルへのリクエストが行われるようになります。
そして、あとはウェブサーバーがリクエストされたファイルをウェブブラウザに返却し、ウェブブラウザが返却された画像ファイルをページ上に表示してくれます。
こんな感じで、一旦 ImageField
を介してファイルの保存さえ行なってしまえば、あとは画像の管理が楽になり、画像処理後の画像の表示も簡単に行うことができます。
スポンサーリンク
その他の画像処理の実現
何回も言っていますが、Django で画像処理を行う際でも、PIL 画像オブジェクトの生成と PIL 画像オブジェクトからのファイル保存さえ行えるようにしてしまえば、あとは PIL 画像オブジェクトを利用して画像処理を行えば良いだけです。
つまり、上記の transform
メソッドにおいて、前半と後半部分はそのまま残し、下記部分だけを変更してやれば、他の画像処理も実現することができます。
# PILでの画像処理ここから!
ret_img = org_img.rotate(angle)
if gray:
ret_img = ret_img.convert('L')
# PILでの画像処理ここまで!
今回は回転とグレースケール化のみを行いましたが、他にも PIL を利用していろんな画像処理を行うことが可能ですので、是非いろんな画像処理を行うアプリを開発してみていただければと思います。
画像処理を行うウェブアプリ
最後に、ここまで解説した内容に基づき、簡単な画像処理を行うウェブアプリを作成していきたいと思います。
今回は、下記ページで紹介している画像のアップロードを行うウェブアプリをベースにし、このアプリのソースコードを変更することで画像処理を行うウェブアプリを作っていきます。
【Django】画像をアップロードするソースコードの変更
上記ページで紹介しているソースコードから変更を行うのは下記の4つのファイルとなります。
upload_project/upload_app/model.py
upload_project/upload_app/forms.py
upload_project/upload_app/views.py
upload_project/upload_app/urls.py
さらに、下記のファイルを新規作成で作成します。
upload_project/upload_app/templates/upload_app/transform.html
model.py
まずは、upload_project/upload_app/model.py
を下記のように変更します。これは、Django で画像処理を行う手順 で示した model.py
と全く同じものになります。
from django.db import models
import io
from PIL import Image
class UploadImage(models.Model):
image = models.ImageField(upload_to='img/')
result = models.ImageField(upload_to='result/')
def transform(self, angle, gray):
# アップロードされたファイルから画像オブジェクト生成
org_img = Image.open(self.image)
# PILでの画像処理ここから!
ret_img = org_img.rotate(angle)
if gray:
ret_img = ret_img.convert('L')
# PILでの画像処理ここまで!
# 画像処理後の画像のデータをbufferに保存
buffer = io.BytesIO()
ret_img.save(fp=buffer, format=org_img.format)
# 以前保存した画像処理後の画像ファイルを削除
self.result.delete()
# bufferのデータをファイルとして保存(レコードの更新も行われる)
self.result.save(name=self.image.name, content=buffer)
forms.py
続いて upload_project/upload_app/forms.py
を下記のように変更します。
SettingForm
を追加しており、この SettingForm
は、回転角度の入力とグレースケール化の ON / OFF を切り替えるためのフォームを作成するためのクラスになります。
from django import forms
from .models import UploadImage
class UploadForm(forms.ModelForm):
class Meta:
model = UploadImage
fields = ['image']
class SettingForm(forms.Form):
angle = forms.IntegerField()
gray = forms.BooleanField(required=False)
transform.html
順番前後しますが、次は upload_project/upload_app/templates/upload_app/transform.html
を下記のように新規作成します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<div>
<h2>ID:{{id}}の画像</h2>
<img src="{{original_url}}">
{% if result_url == '' %}
<img src="{{original_url}}">
{% else %}
<img src="{{result_url}}">
{% endif %}
<form action="{% url 'transform' id %}" method="post">
{% csrf_token %}
{{setting_form.as_p}}
<p><input type="submit" value="実行"></p>
</form>
</div>
</body>
</html>
この transform.html
は、画像処理を実行するためのフォームの表示と、アップロードされた画像&画像処理後の画像を表示するためのテンプレートになります。
ポイントだけ説明しておくと、まず setting_form
は forms.py
で定義した SettingForm
のインスタンスを受け取ることを期待しており、これにより、回転角度の入力フォームとグレースケール化の ON / OFF 切り替え用のチェックボックスを持つフォームをページに表示します。
また、original_url
はアップロードされた画像にリクエストするための URL、result_url
は画像処理後の画像にリクエストするための URL を受け取ることを期待しています。これらを2つの <img>
タグの src
属性にそれぞれ指定することで、2つの画像を並べてページに表示することを実現しています。
ただ、画像処理がまだ行われていない場合は画像処理後の画像は表示できないため、アップロードされた画像を2つ並べて表示するようにしています。画像処理がまだ行われていないかどうかは、result_url
が空文字 ''
であるかどうかで判断するようにしています(''
の場合に画像処理がまだ行われていないと判断)。
views.py
次は upload_project/upload_app/views.py
を下記のように変更します。
transform
関数を追加しており、この関数が UploadImage
の transform
メソッドを実行して画像処理を行い、さらに先ほど作成した transform.html
をレンダリングすることで、アップロードされた画像と画像処理後の画像を表示するための HTML の生成を行います。
from django.shortcuts import render, get_object_or_404
from .forms import UploadForm, SettingForm
from .models import UploadImage
def index(request):
params = {
'title': '画像のアップロード',
'upload_form': UploadForm(),
'id': None,
}
if (request.method == 'POST'):
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
upload_image = form.save()
params['id'] = upload_image.id
return render(request, 'upload_app/index.html', params)
def preview(request, image_id=0):
upload_image = get_object_or_404(UploadImage, id=image_id)
params = {
'title': '画像の表示',
'id': upload_image.id,
'url': upload_image.image.url
}
return render(request, 'upload_app/preview.html', params)
def transform(request, image_id=0):
upload_image = get_object_or_404(UploadImage, id=image_id)
if (request.method == 'POST'):
form = SettingForm(request.POST)
if form.is_valid():
angle = form.cleaned_data.get('angle')
gray = form.cleaned_data.get('gray')
upload_image.transform(angle, gray)
params = {
'title': '画像処理',
'id': upload_image.id,
'setting_form': form,
'original_url': upload_image.image.url,
'result_url': upload_image.result.url
}
return render(request, 'upload_app/transform.html', params)
params = {
'title': '画像処理',
'id': upload_image.id,
'setting_form': SettingForm({'angle':0, 'gray':False}),
'original_url': upload_image.image.url,
'result_url': ''
}
return render(request, 'upload_app/transform.html', params)
transform
関数では、まず引数 image_id
を id
として持つ UploadImage
のインスタンスの取得を行なっています。このインスタンスの image
にはアップロードされた画像の情報がセットされています。
さらに、request.method
が 'POST'
の場合、transform
関数はまず request.POST
から SettingForm
のインスタンスを生成し、フォームから送信された回転角度とグレースケール化の ON / OFF の情報を取得しています(angle
と gray
)。
さらに、これらの情報を引数に渡して UploadImage
の transform
メソッドを実行しています。これにより、Django で画像処理を行う手順 で解説した通りに transform
メソッドの中で画像処理が実行され、画像処理後の画像の保存および保存先パスのデータベースへの保存が行われます。
この保存が行われることで、upload_image.result
に画像処理後の画像の情報がセットされます。upload_image.result.url
には、その画像処理後の画像ファイルをリクエストするための URL がセットされることになります。
その upload_image.result.url
を params
の 'result_url'
キーにセットして render
関数に渡すことで、画像処理後の画像の表示を実現しています。
request.method
が 'POST'
以外の場合、画像処理は実行せずに単に render
関数を実行し、画像の表示を行うための HTML 生成のみを行います。
この際には、空文字 ''
を params
の 'result_url'
キーにセットして render
関数に渡すことで、アップロードされた画像のみが表示されるようにしています。
urls.py
最後に upload_project/upload_app/urls.py
を下記のように変更し、http://localhost:8000/upload_app/transform/<int:image_id>/
の URL にアクセスされた際に、先ほど用意した transform
関数が実行されるように設定します。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('preview/<int:image_id>/', views.preview, name='preview'),
path('transform/<int:image_id>/', views.transform, name='transform'),
]
スポンサーリンク
画像処理の動作確認
以上により、簡単ではありますがアップロードされた画像を画像処理するアプリが完成したことになります。
最後にこのアプリを動作させ、アップロードされた画像の画像処理が行えることを確認していましょう!
まずは、upload_project
フォルダで下記の2つのコマンドを実行してマイグレーションを行います。
% python manage.py makemigrations % python manage.py migrate
もし、以前にマイグレーションした結果が残っていて上記コマンドに失敗する場合、下記の2つを削除すればマイグレーションに成功するようになるはずです(models.py
の記述が間違っていなければ)。
upload_project/db.sqlite3
upload_project/upload_app/migrations/0001_initial.py
続いて下記コマンドを upload_project
フォルダで実行し、Django の開発用ウェブサーバーを起動します。
% python manage.py runserver
次は、ウェブブラウザのアドレスバーに下記 URL を指定し、表示されるページで画像のアップロードを行なって下さい。
http://localhost:8000/upload_app/
アップロードが完了すると ID が表示されるので、その ID を用いてアドレスバーに下記 URL を指定します(ID 部分はご自身で確認した ID を指定して下さい)。
http://localhost:8000/upload_app/transform/ID/
すると、下の図のようなページに遷移し、画像が2枚並べて表示されることが確認できると思います(画像が大きいと縦に並ぶので注意してください)。
続いて、ページ下側の Angle
の入力フォームにてきとうに回転角度を指定し、さらに Gray
のチェックボックスを ON にしてから 実行
ボタンをクリックしてみてください。
すると、下の図のように右側の画像が回転&グレースケール化したものに変化することが確認できると思います。
この右側の画像は左側の画像(アップロードされた画像)に対して画像処理を行なった結果であり、上の図の表示結果より、アップロードされた画像に対して画像処理が行えていること、そして画像処理後の画像の表示ができてることが確認できます。
また、Angle
に入力する角度や Gray
のチェックボックスの ON / OFF を変更した後に 実行
ボタンを押せば、右側に表示される画像も変化します。
このことから、フォームで指定された設定に基づいて画像処理が行われていることも確認できると思います。
まとめ
このページでは、Django での画像処理を行う手順について解説しました!
より具体的には、アップロードされた画像に対して PIL を用いて画像処理を行う手順について解説を行いました。
画像処理自体は PIL 画像オブジェクトさえ生成すれば簡単に行えます。なので、Django で画像処理を行う際には、いかにして PIL 画像オブジェクトを生成し、いかにして PIL 画像オブジェクトからファイル保存を行うかのあたりがポイントになると思います。
この辺りを重点的に解説したつもりですので、このページを参考にしていろんな画像処理に挑戦してみてください。
補足しておくと、このページで紹介した PIL 画像オブジェクトの生成の仕方や PIL 画像オブジェクトからのファイル保存の仕方はただの一例であり、他にもいろんな方法でこれらを実現することができます。
なので、これらを行うための方法にはもっと良いものがあるかもしれないです。もしそういった方法を思いついた方は、コメント等で教えていただけると幸いです。