このページでは、Python の tkinter と PIL を利用した「画像のモザイク化アプリ」の作り方について解説していきます。
このアプリを利用すれば、画像上の選択範囲に対してモザイク処理を行うことができます。また、選択範囲をモザイクではなく「ぼかす」こともできます。Python / PIL / tkinter が利用できる環境であれば、紹介するスクリプトを実行すれば画像のモザイク化を行うことが可能です。
ただし、モザイク処理・ぼかし処理をしたからといって確実に情報が隠せるというわけではないので注意してください。モザイクが解かれて何らかの被害を被ったとしても、私は責任を負えません…。この点にのみ注意して、記事を読み進めていただければと思います!
画像のモザイク化アプリの紹介
では、まずは今回開発するアプリの紹介をしていきます。
画面構成
アプリの画面構成は下の図のようになっています。
選択された画像は画面左側のキャンバスに描画されます。また、画面右側には操作用のボタンを用意しています。
次に説明する 操作方法 の内容に従って操作を行えば、画像の特定部分のみに対してモザイク処理・ぼかし処理を行うことができます。
スポンサーリンク
操作方法
続いてアプリの操作方法について解説していきます。
画像の選択
まず、本アプリを利用するためには、モザイク処理を行いたい画像の選択を行う必要があります。
この画像の選択は、画面右側にある「画像選択」ボタンをクリックすることで行うことができます。このボタンを押せばファイル選択ダイアログが表示されるようになっていますので、そのダイアログからモザイク処理を行いたい画像ファイルを選択してください。
ファイルを選択すれば、選択したファイルの画像が画面左側のキャンバスに表示されるはずです。
範囲の選択
キャンバスに画像が表示されれば、次に画像上のモザイク処理を行いたい範囲を選択します。この範囲の選択はマウス操作により行うことができます。
画像上にマウスを合わせてクリックし、さらにクリックした状態でマウスを移動させれば選択範囲が赤枠で表示されるようになっています。モザイク処理を行いたい範囲が選択できればマウスのクリックを解除してください。
マウスのクリックを解除した時点で表示されている赤枠が最終的な選択範囲となり、次に説明するモザイク処理・ぼかし処理はこの選択範囲に対してのみ行われることになります。
モザイク処理・ぼかし処理の実行
範囲の選択が完了すれば、次は選択範囲に対してモザイク処理 or ぼかし処理を行うために、画面右側の「モザイク」ボタン or 「ぼかし」ボタンをクリックしてください。これにより、クリックしたボタンに応じた処理が選択範囲に対して実行されることになります。
他の箇所に対してモザイク処理・ぼかし処理を行いたい場合は、再度、範囲の選択とボタンのクリックを行なってください。
モザイク処理・ぼかし処理の取り消し
また、「取り消し」ボタンにより、キャンバスに表示されている画像を元の画像に戻すことも可能です。間違ってモザイク処理・ぼかし処理を行なってしまった場合に利用してください。ただし、この取り消しボタンでは、直前に行った処理の取り消しではなく、全ての処理が取り消しされることになります。
画像の保存
モザイク処理した結果が気に入ったのであれば、モザイク処理後の画像の保存を行うことも可能です。画像の保存は画面右側の「画像保存」ボタンをクリックすることで行うことができます。
このボタンをクリックすればファイル選択ダイアログが表示されますので、そこで好きなフォルダやファイル名の選択・入力を行ってください。その後、保存を実行すれば、キャンバスに表示中の画像をファイルとして保存することができます(赤枠を取り除いた状態の画像が保存されます)。
画像のモザイク化アプリのスクリプト
先ほど紹介した「画像のモザイク化アプリ」は、下記のようなスクリプトで実現することが可能です。
import tkinter
import tkinter.filedialog
from PIL import Image, ImageTk
INTERVAL = 200
INTENSITY = 20
class Mosaic():
def __init__(self, master):
self.master = master
# キャンバスのサイズ
self.canvas_width = 1000
self.canvas_height = 600
# マウスクリック中かどうかの判断を行うフラグ
self.pressing = False
# 選択範囲
self.selection = None
# 元画像
self.image = None
# 加工後の画像
self.after_image = None
self.create_widgets()
self.set_events()
def create_widgets(self):
'アプリ内にウィジェットを作成・配置する'
# キャンバスを配置するフレームの作成と配置
self.canvas_frame = tkinter.Frame(
self.master
)
self.canvas_frame.grid(column=0, row=0)
# 操作ボタンを配置するフレームの作成と配置
self.operation_frame = tkinter.Frame(
self.master
)
self.operation_frame.grid(column=1, row=0)
# キャンバスの作成と配置
self.canvas = tkinter.Canvas(
self.canvas_frame,
width=self.canvas_width,
height=self.canvas_height,
bg="#E0E0E0",
)
self.canvas.pack()
# ファイル読み込みボタンの作成と配置
self.load_button = tkinter.Button(
self.operation_frame,
text="画像選択"
)
self.load_button.pack()
# モザイク化実行ボタンの作成と配置
self.mosaic_button = tkinter.Button(
self.operation_frame,
text="モザイク"
)
self.mosaic_button.pack()
# ぼかし実行ボタンの作成と配置
self.blur_button = tkinter.Button(
self.operation_frame,
text="ぼかし"
)
self.blur_button.pack()
# 取り消しボタンの作成と配置
self.cancel_button = tkinter.Button(
self.operation_frame,
text="取り消し"
)
self.cancel_button.pack()
# ファイル書き込みボタンの作成と配置
self.save_button = tkinter.Button(
self.operation_frame,
text="画像保存"
)
self.save_button.pack()
def set_events(self):
'受け付けるイベントを設定する'
# キャンバス上のマウス押し下げ開始イベント受付
self.canvas.bind(
"<ButtonPress>",
self.button_press
)
# キャンバス上のマウス動作イベント受付
self.canvas.bind(
"<Motion>",
self.mouse_motion,
)
# キャンバス上のマウス押し下げ終了イベント受付
self.canvas.bind(
"<ButtonRelease>",
self.button_release,
)
# 動画選択ボタン押し下げイベント受付
self.load_button['command'] = self.push_load_button
# 画像保存ボタン押し下げイベント受付
self.save_button['command'] = self.push_save_button
# モザイクボタン押し下げイベント受付
self.mosaic_button['command'] = self.push_mosaic_button
# ぼかしボタン押し下げイベント受付
self.blur_button['command'] = self.push_blur_button
# 取り消しボタン押し下げイベント受付
self.cancel_button['command'] = self.push_cancel_button
# 画像の描画用のタイマーセット
self.master.after(INTERVAL, self.timer)
def push_load_button(self):
'動画選択ボタンが押された時の処理'
file_types = [
("JPGファイル", "*.jpg"),
("PNGファイル", "*.png"),
]
# ファイル選択画面表示
file_path = self.select_open_file(file_types)
# 動画ファイルの読み込み
if len(file_path) != 0:
self.create_image(file_path)
self.selection = None
# 選択範囲を表示するオブジェクトを削除
self.delete_selection()
def push_save_button(self):
'保存ボタンが押された時の処理'
# ファイル選択画面表示
file_path = self.select_save_file("png")
# 画像の書き込み
if len(file_path) != 0:
self.save_image(file_path)
def push_cancel_button(self):
'取り消しボタンが押された時の処理'
# 加工後画像を元画像に戻す
self.after_image = self.image.copy()
def push_blur_button(self):
'ぼかしボタンが押された時の処理'
self.mosaic_image(False)
def push_mosaic_button(self):
'モザイクボタンが押された時の処理'
self.mosaic_image()
def button_press(self, event):
'マウスボタン押し下げ開始時の処理'
# マウスクリック中に設定
self.pressing = True
# 現在のマウスでの選択範囲を設定
self.selection = [
event.x,
event.y,
event.x,
event.y
]
# 選択範囲を表示するオブジェクトを削除
self.delete_selection()
def mouse_motion(self, event):
'マウスボタン移動時の処理'
if self.pressing:
# マウスでの選択範囲を更新
self.selection[2] = event.x
self.selection[3] = event.y
def button_release(self, event):
'マウスボタン押し下げ終了時の処理'
if self.pressing:
# マウスボタン押し下げ終了
self.pressing = False
# マウスでの選択範囲を更新
self.selection[2] = event.x
self.selection[3] = event.y
def timer(self):
'一定間隔で画像等を描画'
# 動画1フレーム分をキャンバスに描画
self.draw_image()
# トリミング選択範囲をキャンバスに描画
self.draw_selection()
# 再度タイマー設定
self.master.after(INTERVAL, self.timer)
def draw_image(self):
'画像をキャンバスに描画'
image = self.get_image()
if image is not None:
# キャンバス上の画像の左上座標を決定
self.sx = (self.canvas.winfo_width() - image.width()) // 2
self.sy = (self.canvas.winfo_height() - image.height()) // 2
# キャンバスに描画済みの画像を削除
objs = self.canvas.find_withtag("image")
for obj in objs:
self.canvas.delete(obj)
# 画像をキャンバスの真ん中に描画
self.canvas.create_image(
self.sx, self.sy,
image=image,
anchor=tkinter.NW,
tag="image"
)
def draw_selection(self):
'選択範囲を描画'
# 一旦描画済みの選択範囲を削除
self.delete_selection()
if self.selection is not None:
# 選択範囲を長方形で描画
self.canvas.create_rectangle(
self.selection[0],
self.selection[1],
self.selection[2],
self.selection[3],
outline="red",
width=3,
tag="selection_rectangle"
)
def delete_selection(self):
'選択範囲表示用オブジェクトを削除する'
# キャンバスに描画済みの選択範囲を削除
objs = self.canvas.find_withtag("selection_rectangle")
for obj in objs:
self.canvas.delete(obj)
def select_open_file(self, file_types):
'オープンするファイル選択画面を表示'
# ファイル選択ダイアログを表示
file_path = tkinter.filedialog.askopenfilename(
initialdir=".",
filetypes=file_types
)
return file_path
def select_save_file(self, defaultextension):
'保存するファイル選択画面を表示'
# ファイル選択ダイアログを表示
file_path = tkinter.filedialog.asksaveasfilename(
initialdir=".",
defaultextension=defaultextension
)
return file_path
def mosaic_image(self, mosaic=True):
'''画像の選択範囲に対してモザイク処理・ぼかし処理を実行'''
# 選択範囲の座標をリサイズ前の画像上の座標に変換
x0 = round((self.selection[0] - self.sx) / self.draw_ratio)
y0 = round((self.selection[1] - self.sy) / self.draw_ratio)
x1 = round((self.selection[2] - self.sx) / self.draw_ratio)
y1 = round((self.selection[3] - self.sy) / self.draw_ratio)
# x0 < x1, y0 <y1を満たすように調整
if x0 > x1:
x0, x1 = x1, x0
if y0 > y1:
y0, y1 = y1, y0
# 選択範囲で画像をトリミング
cropped_image = self.after_image.crop((
x0, y0, x1, y1,
))
# 画像の縮小
small_size = (
round(cropped_image.width / INTENSITY),
round(cropped_image.height / INTENSITY)
)
small_image = cropped_image.resize(small_size)
# 画像の拡大
if mosaic:
# モザイク処理
reverse_image = small_image.resize(
(cropped_image.width, cropped_image.height),
Image.NEAREST
)
else:
# ぼかし処理
reverse_image = small_image.resize((
cropped_image.width, cropped_image.height),
Image.BILINEAR
)
# モザイク・ぼかし処理した画像を選択範囲に貼り付け
self.after_image.paste(reverse_image, (x0, y0))
def create_image(self, path):
'''選択されたパスから画像オブジェクトを生成する'''
# 選択されたファイルを開く
self.image = Image.open(path)
# 加工後画像として元画像をコピーして作成
self.after_image = self.image.copy()
def save_image(self, path):
'''加工後の画像の保存を行う'''
# 加工後画像を保存
self.after_image.save(path)
def get_image(self):
'キャンバスのサイズに合わせたTkinter画像オブジェクトを取得する'
if self.image is None:
return None
pil_image = self.after_image
# 拡大率を計算
ratio_x = self.canvas_width / pil_image.width
ratio_y = self.canvas_height / pil_image.height
if ratio_x < ratio_y:
self.draw_ratio = ratio_x
else:
self.draw_ratio = ratio_y
# リサイズ
resized_image = pil_image.resize(
(
round(self.draw_ratio * pil_image.width),
round(self.draw_ratio * pil_image.height)
)
)
# Tkinter画像オブジェクトに変換
self.image_tk = ImageTk.PhotoImage(resized_image)
return self.image_tk
app = tkinter.Tk()
mosaic = Mosaic(app)
app.mainloop()
スクリプトを実行すれば下の図のようなウィンドウが起動しますので、前述の 操作方法 で説明した内容に従って操作を行ってください。
スクリプトの説明
最後に、先ほど紹介したスクリプトの中身の説明を行なっておきます。
割とスクリプトの規模は大きいですが、やってることは単純ですし、既に他のページで解説している内容と重複する部分も多いですので、ここでは簡単に説明を行なっていきたいと思います。
スポンサーリンク
ウィジェットの作成と配置
本アプリは Mosaic
クラスで実現しており、__init__
では各種データ属性の初期化及びウィジェットの作成と配置、さらにはイベントの設定を行なっています。
ウィジェットの作成と配置を行なっているのは create_widgets
メソッドで、下の図で示すようなウィジェットの作成を行なっています。
使用しているウィジェットはフレームとボタンとキャンバスのみです。これらのウィジェットについては下記ページで解説していますので、詳しく知りたい方はこれらのページをご参照ください。
Tkinterの使い方:フレームウィジェット(Frame)の使い方 Tkinterの使い方:ボタンウィジェット(Button)の使い方 Tkinterの使い方:キャンバスウィジェットの作り方また、canvas_frame
と operation_frame
に関しては grid
メソッドで横並びに配置し、各フレームの中身のウィジェットは pack
メソッドで配置しています。ウィジェットの配置に関する grid
メソッドや pack
メソッドに関しては下記ページで解説していますので、こちらも詳しく知りたい方は読んでみてください。
イベントの設定
各種ウィジェットの作成が出来れば、あとは必要なイベント処理の設定を set_events
メソッドで実行するようにしています。
イベント処理は各種ウィジェットに対して設定するようにしており、下図で示すように、キャンバスウィジェットに関しては各種マウス操作が行われた時にイベントハンドラが実行されるように、ボタンウィジェットに関しては押下された時に青字で示すイベントハンドラが実行されるように set_events
メソッドで設定を行なっています。
マウスに対するイベント処理
前述の通り、キャンバス上でのマウス操作時(クリック開始時・移動時・クリック終了時)には下記のイベントハンドラが実行されるようになっています。
- クリック開始時:
button_press
- マウス移動時:
mouse_motion
- クリック終了時:
button_release
これらは、モザイク処理・ぼかし処理を行う範囲を選択するためのイベントハンドラであり、この範囲を selection
データ属性で記録するようにしています。selection[0]
と selection[1]
で選択範囲の開始座標を記録し、selection[2]
と selection[3]
で選択範囲の終了座標を記録します。
要は、button_press
で selection[0]
〜 selection[3]
にマウスクリック時のマウスの座標を記録し、mouse_motion
でマウスの位置に応じて selection[2]
と selection[3]
を更新、さらに button_release
で selection[2]
と selection[3]
の確定を行なっています。
画像選択ボタン・画像保存ボタンに対するイベント処理
画像選択ボタンが押された際には push_load_button
が、画像保存ボタンが押された際には push_save_button
がそれぞれ実行されるようになっており、前者のメソッドではファイル読み込み用のファイル選択ダイアログ、後者のメソッドではファイル保存用のファイル選択ダイアログを表示しています。
ファイル選択ダイアログに関しては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照ください。
Python でファイル選択画面を表示するまた、ファイル選択後、push_load_button
では create_image
メソッドが実行され、この中で選択されたファイルから画像オブジェクトの生成が行われます。そして、生成したオブジェクトは image
データ属性で参照され、さらに生成したオブジェクトのコピーが after_image
データ属性から参照されるようになっています。
image
データ属性は読み込んだ画像のオブジェクトを参照し、after_image
データ属性はモザイク処理・ぼかし処理後の画像オブジェクトを参照する役割を持つようにしています。ただし、読み込んだ直後の画像には何も処理が実行されていないため、after_image
は image
をコピーしたオブジェクトとするようにしています。
また、ファイル選択後、push_save_button
では save_image
メソッドが実行され、ここで after_image
の画像を選択されたパスに保存するようにしています。
モザイクボタン・ぼかしボタンに対するイベント処理
また、モザイクボタンが押された際には push_mosaic_button
、ぼかしボタンが押された際には push_blur_button
が実行されるようになっています。
これらのメソッドからは mosaic_image
が呼び出されるようになっており、この中で画像のモザイク処理・ぼかし処理が実行されます。この mosaic_image
に関しては後述で解説します。
取り消しボタンに対するイベント処理
また、取り消しボタンが押された際には push_cancel_button
が実行されます。このメソッドでは、image
のコピーを再度 after_image
に参照させることで、今まで行われたモザイク処理・ぼかし処理を全て取り消すことができるようになっています。
定期的な処理の実行
また、set_events
では after
メソッドを実行し、特定の時間経過後に timer
メソッドが実行されるようになっています。また、timer
メソッドでも after
メソッドを実行して特定の時間経過後に再び timer
メソッドが実行されるようになっています。つまり、timer
メソッドは定期的に繰り返し実行されることになります(繰り返しの間隔は INTERVAL
変数で設定しています)。
この after
メソッドについては下記ページで解説していますので、詳しく知りたい方はこちらをご参照ください。
この after
メソッドによって定期的に timer
メソッドが実行されるようになります。そして、この timer
メソッドでは draw_image
と draw_selection
の呼び出しが行われるようになっています。
画像の描画
draw_image
は after_image
の参照する画像をキャンバスに描画する処理を行なっています。
ただし、after_image
は画像処理を行いやすいように PIL の画像オブジェクトとして扱うようになっています。それに対し、キャンバスに描画できるのは tkinter の画像オブジェクトのみとなりますので、PIL の画像オブジェクトを tkinter の画像オブジェクトに変化するような処理を行なっています。
また、キャンバスのサイズと画像のサイズは異なるため、キャンバスのサイズに合わせて画像のサイズをリサイズし、さらにキャンバスの中央に画像を描画するために描画位置の調整なども行なっています。
この辺りの処理は draw_image
や draw_image
から呼び出される get_image
で行っているため、詳しくは、これらのメソッドをご参照いただければと思います。
この draw_image
は定期的に実行されるため、モザイク処理・ぼかし処理、さらには取り消しボタンの押下により after_image
が更新されれば、次回の draw_image
の実行時にキャンバスに描画される画像も更新されることになります。
選択範囲の描画
また、draw_selection
ではマウスで選択された範囲を長方形でキャンバスに描画する処理を行なっています。
マウスで選択された範囲は selection
データ属性で記録されていますので、selection
に記録された座標に対して長方形の描画を行なっています。
キャンバスに対する図形の描画等に関しては下記ページで解説していますので、詳しく知りたい方はこちらをご参照ください。
Tkinterの使い方:Canvasクラスで図形を描画するこの draw_selection
も定期的に実行されるため、マウス操作によって selection
が更新されれば、次回の draw_selection
の実行によって選択範囲を表す長方形も更新されることになります。
スポンサーリンク
モザイク処理・ぼかし処理
最後に、このアプリの肝となるモザイク処理・ぼかし処理について解説しておきます。
これらはどちらも mosaic_image
メソッドによって実行され、このメソッドは push_mosaic_button
と push_blur_button
から呼び出されるようになっています。このメソッドでは引数 mosaic
に False
が指定された場合はぼかし処理を、それ以外の場合はモザイク処理を実行するようになっています。
で、これらのモザイク処理・ぼかし処理に関しては、下記ページで解説しているように画像の縮小と拡大により実現することが可能です。
【Python】画像へのモザイク処理・ぼかし処理【PIL】ただし、上記ページで示しているソースコードでは、画像全体に対してモザイク処理・ぼかし処理を行うようになっています。
今回のアプリは選択範囲のみに対してモザイク処理・ぼかし処理を行う必要があるため、まず選択された範囲のみを PIL Image
クラスの crop
メソッドを利用して after_image
からトリミングし、そのトリミングした画像に対してのみモザイク処理・ぼかし処理を行うようにしています。
さらに、処理後の画像を PIL Image
クラスの paste
メソッドで after_image
の選択範囲に貼り付けるようにしています。
これにより、after_image
がモザイク処理後・ぼかし処理後のものに更新されるため、次に draw_image
メソッドが実行された際に、キャンバスに表示される画像がモザイク処理後・ぼかし処理後のものに更新されることになります。
ちょっとややこしいのが、キャンバス上で選択された範囲の座標を画像上の座標に変換する必要がある点になります。キャンバスに表示されている画像は、キャンバスに合わせて画像をリサイズしたものになっています。さらに、キャンバスの中央に画像が表示されているため、キャンバスの原点 (0
, 0
) と画像の原点 (0
, 0
) の位置が異なります。
こういったことを考慮し、キャンバス上で選択された範囲の座標から画像上の座標を逆算する必要があります。この辺りを行なっているのが mosaic_image
メソッドの最初の処理部分となります。
この座標の変換さえしてしまえば、あとは上記の流れでモザイク処理を行えば良いだけです。
また、今回は選択範囲に対してモザイク処理・ぼかし処理を行う例を示しましたが、選択範囲対して行う処理を変更してやれば、もっと別の機能を搭載したアプリを開発することも可能です。例えば、選択範囲に対してのみグレースケール化を行えば、画像の一部のみをグレースケールかするような機能を持つアプリも開発できます
いろんな応用が効くと思いますので、あなたの好みに合わせて処理を変更してみてください!
まとめ
このページでは、Python の tkinter と PIL を利用した「画像のモザイク化アプリ」の作り方について解説しました!
アプリ上で画像を表示し、さらに画像の一部を選択するようにすることで、画像内の特定の領域のみに対してモザイク処理・ぼかし処理を行なうことが可能です。また、処理を変更すれば、特定の領域のみに様々な処理を行なうことも可能となります。
今回、私がこのアプリを開発しようと思ったのは、自身の PC 内に選択範囲のみをモザイク化するようなアプリが無かったからです。こういった機能を持つアプリをダウンロードしても良かったのですが、どうせなら自分で作ってみようと思って開発しました。
この例のように、アプリは自分自身で開発することもできますし、この開発によって自身のプログラミング能力も伸ばすことができます。また、自分で開発できるので自身の好きなような仕様で開発することもでき、自分で好きな機能を追加することもできます。
自身の PC に足りないアプリがあるのであれば、是非自分で開発してみることも検討してみてください!