【Python/tkinter】画像のモザイク化アプリの作り方【選択範囲のみのモザイク化】

このページにはプロモーションが含まれています

このページでは、Python の tkinter と PIL を利用した「画像のモザイク化アプリ」の作り方について解説していきます。

このアプリを利用すれば、画像上の選択範囲に対してモザイク処理を行うことができます。また、選択範囲をモザイクではなく「ぼかす」こともできます。Python / PIL / tkinter が利用できる環境であれば、紹介するスクリプトを実行すれば画像のモザイク化を行うことが可能です。

選択範囲のみに対してモザイク処理を行う様子

ただし、モザイク処理・ぼかし処理をしたからといって確実に情報が隠せるというわけではないので注意してください。モザイクが解かれて何らかの被害を被ったとしても、私は責任を負えません…。この点にのみ注意して、記事を読み進めていただければと思います!

MEMO

このページの猫の画像はリズム727さんによる写真ACからの写真を使用させていただいています

画像のモザイク化アプリの紹介

では、まずは今回開発するアプリの紹介をしていきます。

画面構成

アプリの画面構成は下の図のようになっています。

アプリの画面構成

選択された画像は画面左側のキャンバスに描画されます。また、画面右側には操作用のボタンを用意しています。

次に説明する 操作方法 の内容に従って操作を行えば、画像の特定部分のみに対してモザイク処理・ぼかし処理を行うことができます。

スポンサーリンク

操作方法

続いてアプリの操作方法について解説していきます。

画像の選択

まず、本アプリを利用するためには、モザイク処理を行いたい画像の選択を行う必要があります。

この画像の選択は、画面右側にある「画像選択」ボタンをクリックすることで行うことができます。このボタンを押せばファイル選択ダイアログが表示されるようになっていますので、そのダイアログからモザイク処理を行いたい画像ファイルを選択してください。

ファイルを選択すれば、選択したファイルの画像が画面左側のキャンバスに表示されるはずです。

選択したファイルの画像がキャンバスに表示される様子

範囲の選択

キャンバスに画像が表示されれば、次に画像上のモザイク処理を行いたい範囲を選択します。この範囲の選択はマウス操作により行うことができます。

画像上にマウスを合わせてクリックし、さらにクリックした状態でマウスを移動させれば選択範囲が赤枠で表示されるようになっています。モザイク処理を行いたい範囲が選択できればマウスのクリックを解除してください。

画像の一部をマウス操作で選択する様子

マウスのクリックを解除した時点で表示されている赤枠が最終的な選択範囲となり、次に説明するモザイク処理・ぼかし処理はこの選択範囲に対してのみ行われることになります。

モザイク処理・ぼかし処理の実行

範囲の選択が完了すれば、次は選択範囲に対してモザイク処理 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のキャンバスの作り方解説ページアイキャッチ Tkinterの使い方:キャンバスウィジェットの作り方

また、canvas_frameoperation_frame に関しては grid メソッドで横並びに配置し、各フレームの中身のウィジェットは pack メソッドで配置しています。ウィジェットの配置に関する grid メソッドや pack メソッドに関しては下記ページで解説していますので、こちらも詳しく知りたい方は読んでみてください。

ウィジェット配置方法解説ページのアイキャッチ Tkinterの使い方:ウィジェットの配置(pack・grid・place)

イベントの設定

各種ウィジェットの作成が出来れば、あとは必要なイベント処理の設定を set_events メソッドで実行するようにしています。

イベント処理は各種ウィジェットに対して設定するようにしており、下図で示すように、キャンバスウィジェットに関しては各種マウス操作が行われた時にイベントハンドラが実行されるように、ボタンウィジェットに関しては押下された時に青字で示すイベントハンドラが実行されるように set_events メソッドで設定を行なっています。

各種ウィジェットに対して設定するイベントハンドラ

マウスに対するイベント処理

前述の通り、キャンバス上でのマウス操作時(クリック開始時・移動時・クリック終了時)には下記のイベントハンドラが実行されるようになっています。

  • クリック開始時:button_press
  • マウス移動時:mouse_motion
  • クリック終了時:button_release

これらは、モザイク処理・ぼかし処理を行う範囲を選択するためのイベントハンドラであり、この範囲を selection データ属性で記録するようにしています。selection[0]selection[1] で選択範囲の開始座標を記録し、selection[2]selection[3] で選択範囲の終了座標を記録します。

要は、button_pressselection[0]selection[3] にマウスクリック時のマウスの座標を記録し、mouse_motion でマウスの位置に応じて selection[2]selection[3] を更新、さらに button_releaseselection[2]selection[3] の確定を行なっています。

マウス操作によるイベント処理の説明図

画像選択ボタン・画像保存ボタンに対するイベント処理

画像選択ボタンが押された際には push_load_button が、画像保存ボタンが押された際には push_save_button がそれぞれ実行されるようになっており、前者のメソッドではファイル読み込み用のファイル選択ダイアログ、後者のメソッドではファイル保存用のファイル選択ダイアログを表示しています。

ファイル選択ダイアログに関しては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照ください。

ファイル選択画面表示の解説ページアイキャッチ Python でファイル選択画面を表示する

また、ファイル選択後、push_load_button では create_image メソッドが実行され、この中で選択されたファイルから画像オブジェクトの生成が行われます。そして、生成したオブジェクトは image データ属性で参照され、さらに生成したオブジェクトのコピーが after_image データ属性から参照されるようになっています。

image データ属性は読み込んだ画像のオブジェクトを参照し、after_image データ属性はモザイク処理・ぼかし処理後の画像オブジェクトを参照する役割を持つようにしています。ただし、読み込んだ直後の画像には何も処理が実行されていないため、after_imageimage をコピーしたオブジェクトとするようにしています。

また、ファイル選択後、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 メソッドについては下記ページで解説していますので、詳しく知りたい方はこちらをご参照ください。

Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行する

この after メソッドによって定期的に timer メソッドが実行されるようになります。そして、この timer メソッドでは draw_imagedraw_selection の呼び出しが行われるようになっています。

画像の描画

draw_imageafter_image の参照する画像をキャンバスに描画する処理を行なっています。

ただし、after_image は画像処理を行いやすいように PIL の画像オブジェクトとして扱うようになっています。それに対し、キャンバスに描画できるのは tkinter の画像オブジェクトのみとなりますので、PIL の画像オブジェクトを tkinter の画像オブジェクトに変化するような処理を行なっています。

また、キャンバスのサイズと画像のサイズは異なるため、キャンバスのサイズに合わせて画像のサイズをリサイズし、さらにキャンバスの中央に画像を描画するために描画位置の調整なども行なっています。

この辺りの処理は draw_imagedraw_image から呼び出される get_image で行っているため、詳しくは、これらのメソッドをご参照いただければと思います。

この draw_image は定期的に実行されるため、モザイク処理・ぼかし処理、さらには取り消しボタンの押下により after_image が更新されれば、次回の draw_image の実行時にキャンバスに描画される画像も更新されることになります。

選択範囲の描画

また、draw_selection ではマウスで選択された範囲を長方形でキャンバスに描画する処理を行なっています。

マウスで選択された範囲は selection データ属性で記録されていますので、selection に記録された座標に対して長方形の描画を行なっています。

キャンバスに対する図形の描画等に関しては下記ページで解説していますので、詳しく知りたい方はこちらをご参照ください。

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで図形を描画する

この draw_selection も定期的に実行されるため、マウス操作によって selection が更新されれば、次回の draw_selection の実行によって選択範囲を表す長方形も更新されることになります。

スポンサーリンク

モザイク処理・ぼかし処理

最後に、このアプリの肝となるモザイク処理・ぼかし処理について解説しておきます。

これらはどちらも mosaic_image メソッドによって実行され、このメソッドは push_mosaic_buttonpush_blur_button から呼び出されるようになっています。このメソッドでは引数 mosaicFalse が指定された場合はぼかし処理を、それ以外の場合はモザイク処理を実行するようになっています。

で、これらのモザイク処理・ぼかし処理に関しては、下記ページで解説しているように画像の縮小と拡大により実現することが可能です。

画像のモザイク処理・ぼかし処理についての説明図 【Python】画像へのモザイク処理・ぼかし処理【PIL】

ただし、上記ページで示しているソースコードでは、画像全体に対してモザイク処理・ぼかし処理を行うようになっています。

今回のアプリは選択範囲のみに対してモザイク処理・ぼかし処理を行う必要があるため、まず選択された範囲のみを PIL Image クラスの crop メソッドを利用して after_image からトリミングし、そのトリミングした画像に対してのみモザイク処理・ぼかし処理を行うようにしています。

選択範囲に対してのみモザイク処理を行う様子

さらに、処理後の画像を PIL Image クラスの paste メソッドで after_image の選択範囲に貼り付けるようにしています。

選択範囲に処理後の画像をpasteする様子

これにより、after_image がモザイク処理後・ぼかし処理後のものに更新されるため、次に draw_image メソッドが実行された際に、キャンバスに表示される画像がモザイク処理後・ぼかし処理後のものに更新されることになります。

ちょっとややこしいのが、キャンバス上で選択された範囲の座標を画像上の座標に変換する必要がある点になります。キャンバスに表示されている画像は、キャンバスに合わせて画像をリサイズしたものになっています。さらに、キャンバスの中央に画像が表示されているため、キャンバスの原点 (0, 0) と画像の原点 (0, 0) の位置が異なります。

こういったことを考慮し、キャンバス上で選択された範囲の座標から画像上の座標を逆算する必要があります。この辺りを行なっているのが mosaic_image メソッドの最初の処理部分となります。

この座標の変換さえしてしまえば、あとは上記の流れでモザイク処理を行えば良いだけです。

また、今回は選択範囲に対してモザイク処理・ぼかし処理を行う例を示しましたが、選択範囲対して行う処理を変更してやれば、もっと別の機能を搭載したアプリを開発することも可能です。例えば、選択範囲に対してのみグレースケール化を行えば、画像の一部のみをグレースケールかするような機能を持つアプリも開発できます

特定の範囲に対してのみグレースケール化処理を行なう様子

いろんな応用が効くと思いますので、あなたの好みに合わせて処理を変更してみてください!

まとめ

このページでは、Python の tkinter と PIL を利用した「画像のモザイク化アプリ」の作り方について解説しました!

アプリ上で画像を表示し、さらに画像の一部を選択するようにすることで、画像内の特定の領域のみに対してモザイク処理・ぼかし処理を行なうことが可能です。また、処理を変更すれば、特定の領域のみに様々な処理を行なうことも可能となります。

今回、私がこのアプリを開発しようと思ったのは、自身の PC 内に選択範囲のみをモザイク化するようなアプリが無かったからです。こういった機能を持つアプリをダウンロードしても良かったのですが、どうせなら自分で作ってみようと思って開発しました。

この例のように、アプリは自分自身で開発することもできますし、この開発によって自身のプログラミング能力も伸ばすことができます。また、自分で開発できるので自身の好きなような仕様で開発することもでき、自分で好きな機能を追加することもできます。

自身の PC に足りないアプリがあるのであれば、是非自分で開発してみることも検討してみてください!

同じカテゴリのページ一覧を表示