【Python・OpenCV】初心者でも作れる!Fletを使った画像処理GUIアプリ

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】初心者でも作れる!Fletを使った画像処理GUIアプリ

はじめに

PythonでGUIというと、標準GUIライブラリ"Tkinter"を使うことが多いと思いますが、macでは環境構築でハマってしまうこともしばしばです。
本記事では、Pythonの他のライブラリと同様にpipでインストールするだけで利用できるFletというフレームワークを使って、シンプルながら実用的な画像処理アプリを作成する方法をご紹介します。

(広告) OpenCV関連書籍をAmazonで探す

Fletとは

Fletは、Pythonで簡単にクロスプラットフォームのGUIアプリケーションを作成するための比較的新しいフレームワークです。Flutterをベースとしており、コードの記述量を抑えつつ、見た目にも洗練されたアプリを簡単に作成することができます。
以下がFletの主な特徴です。

  1. シンプルなコード:
    Fletは、コードが非常にシンプルで読みやすいのが特徴です。そのため、複雑なロジックを記述する場合でも、メンテナンスしやすいコードを書くことができます。
  2. クロスプラットフォーム:
    デスクトップ(Windows、macOS、Linux)、Web、モバイル(iOS、Android)で動作するアプリケーションを単一のコードベースで作成できます。
  3. モダンなデザイン:
    Material Designを基にした美しくモダンなUIコンポーネントを提供しています。
  4. リアクティブ:
    ステート管理が容易で、UIの更新がシンプルに行えます。
  5. 豊富なウィジェット:
    テキスト、ボタン、画像、リスト、グリッドなど、多様なUIコンポーネントが用意されています。
  6. カスタマイズ性:
    テーマやスタイルのカスタマイズが可能で、アプリケーションの外観を細かく制御できます。
  7. アセットの管理:
    画像やフォントなどのアセットを簡単に管理・使用できます。
  8. ホットリロード:
    開発時にコードの変更がリアルタイムでUIに反映されるため、迅速な開発が可能です。

Pythonを使用してGUIアプリケーションを素早く開発したい場合や、Webとデスクトップの両方で動作するアプリケーションを作りたい場合に適しています。TkinterやPyQtなどの従来のPython GUIフレームワークと比較して、より現代的で使いやすい設計になっています。

インストール方法

Fletを使用するには、Python 3.6以降が必要です。

Fletをインストールするには 以下のコマンドを実行して、Fletをインストールします。
本記事の執筆時点でのバージョンはv0.23.0です。

pip install flet

Fletは公式ドキュメントが充実しています。チュートリアルも用意されているので、基本的な使い方を学ぶことができます。

Fletを使ってみる

以下で紹介するサンプルコードはガウシアンフィルタのぼかし強度をリアルタイムで調整できるアプリです。
次の機能が実装されています。

  1. 画像ファイルの選択
  2. 選択した画像の表示
  3. ガウシアンフィルタの適用
  4. スライダーによるぼかし強度のリアルタイム調整
  5. 処理後の画像の表示

サンプルコード

下記にサンプルコードを示します。
このサンプルコードを実行すると次のウィンドウが表示されます。

「画像を選択」ボタンをクリックして任意の画像ファイルを選択すると、下の様に左にオリジナルの画像、右にガウシアンフィルタ適応後の画像が表示されます。
ウィンドウの下部のスライダーを動かすことで、ガウシアンフィルタのカーネルサイズが変更されスムージングの強度がリアルタイムで変化します。
スライダーの値(silder value)は0〜10の範囲で設定することができ、次の式でガウシアンフィルタのカーネルサイズが計算されます。

 $$\mathsf{ kernel \ size = silder \ value \times 2 + 1 }$$

import flet as ft
import cv2
import base64

def main(page: ft.Page):
    page.title = "codevace.com: Fletを使ったガウシアンフィルタ デモ"
    page.padding = 50
    page.theme_mode = ft.ThemeMode.LIGHT

    img_original = ft.Image(fit=ft.ImageFit.CONTAIN)
    img_processed = ft.Image(fit=ft.ImageFit.CONTAIN)

    original_img_np = None
    blur_value = 1

    def process_image(img_np, blur):
        blur_value = blur * 2 + 1
        img_blurred = cv2.GaussianBlur(img_np, (blur_value, blur_value), 0)
        _, img_encoded = cv2.imencode('.png', img_blurred)
        return base64.b64encode(img_encoded).decode('utf-8')

    def on_slider_change(e):
        nonlocal blur_value
        blur_value = int(e.control.value)
        if original_img_np is not None:
            processed_img_base64 = process_image(original_img_np, blur_value)
            img_processed.src_base64 = processed_img_base64
            page.update()

    def on_file_picked(e: ft.FilePickerResultEvent):
        nonlocal original_img_np
        if e.files:
            file_path = e.files[0].path
            img_original.src = file_path
            original_img_np = cv2.imread(file_path)
            if original_img_np is None:
                print(f"Failed to load image: {file_path}")
                return

            # 画像縮小
            original_h, original_w = original_img_np.shape[:2]
            new_width = 650
            new_height = int(new_width/original_w * original_h) # 画像の幅が650となるように高さを調節
            original_img_np = cv2.resize(original_img_np, (new_width, new_height))

            # 画像サイズを取得
            height, width = original_img_np.shape[:2]
            
            # ウィンドウサイズを画像サイズに合わせて変更
            new_window_width = width * 2 + page.padding * 3  # 2つの画像 + パディング
            new_window_height = height + page.padding * 2 + 50  # 画像 + パディング + 余白(ボタンとスライダー用)
            page.window_width = new_window_width
            page.window_height = new_window_height
            
            # 画像サイズを設定
            img_original.width = width
            img_original.height = height
            img_processed.width = width
            img_processed.height = height
            
            processed_img_base64 = process_image(original_img_np, blur_value)
            img_processed.src_base64 = processed_img_base64
            slider.disabled = False
            page.update()

    file_picker = ft.FilePicker(on_result=on_file_picked)
    page.overlay.append(file_picker)

    slider = ft.Slider(
        min=0,
        max=10,
        divisions=10,
        value=1,
        label="{value}",
        on_change=on_slider_change,
        disabled=True
    )

    images_row = ft.Row(
        [img_original, img_processed],
        alignment=ft.MainAxisAlignment.CENTER,
        expand=True
    )

    def page_resize(e):
        images_row.height = page.window_height - 100  # Adjust this value as needed
        page.update()

    page.on_resize = page_resize

    page.add(
        ft.ElevatedButton("画像を選択", on_click=lambda _: file_picker.pick_files(allow_multiple=False)),
        images_row,
        ft.Text("ぼかし強度:"),
        slider
    )

ft.app(target=main)

コード解説

import flet as ft
import cv2
import base64

まず、必要なライブラリをインポートしています。

  • flet: GUIアプリケーションを作成するためのフレームワーク
  • cv2: OpenCVライブラリ
  • base64: データのエンコード/デコード
def main(page: ft.Page):
    page.title = "codevace.com: Fletを使ったガウシアンフィルタ デモ"
    page.padding = 50
    page.theme_mode = ft.ThemeMode.LIGHT

    img_original = ft.Image(fit=ft.ImageFit.CONTAIN)
    img_processed = ft.Image(fit=ft.ImageFit.CONTAIN)

    original_img_np = None
    blur_value = 1

main関数は、アプリケーションのエントリーポイントです。

  • ページのタイトル、パディング、テーマを設定
  • window_resizable = Falseで、ウィンドウサイズの変更を禁止
    img_original = ft.Image(fit=ft.ImageFit.CONTAIN)
    img_processed = ft.Image(fit=ft.ImageFit.CONTAIN)

    original_img_np = None
    blur_value = 1
  • 2つのImageウィジェットを作成(元画像と処理後画像用)
  • original_img_npは元の画像データを保持するための変数
  • blur_valueはガウシアンフィルタのカーネルサイズ
    def process_image(img_np, blur):
        blur_value = blur * 2 + 1
        img_blurred = cv2.GaussianBlur(img_np, (blur_value, blur_value), 0)
        _, img_encoded = cv2.imencode('.png', img_blurred)
        return base64.b64encode(img_encoded).decode('utf-8')

process_image関数は画像処理を行います。

  • ガウシアンフィルタのカーネルサイズを計算して適用
  • PNG形式でバイトストリームに保存
  • Base64エンコードして返す

ガウシアンフィルタ処理を行うcv2.GaussianBlur関数については、下記リンクの記事で詳しく解説しています。
【Python・OpenCV】4種類のノイズ除去フィルターの使い方と特徴について

    def on_slider_change(e):
        nonlocal blur_value
        blur_value = int(e.control.value)
        if original_img_np is not None:
            processed_img_base64 = process_image(original_img_np, blur_value)
            img_processed.src_base64 = processed_img_base64
            page.update()

on_slider_change関数はスライダーの値が変更されたときに呼ばれます。

  • カーネルサイズ値を更新
  • 画像を再処理
  • 処理後の画像を更新
    def on_file_picked(e: ft.FilePickerResultEvent):
        nonlocal original_img_np
        if e.files:
            file_path = e.files[0].path
            img_original.src = file_path
            original_img_np = cv2.imread(file_path)
            if original_img_np is None:
                print(f"Failed to load image: {file_path}")
                return
            # 画像縮小
            original_h, original_w = original_img_np.shape[:2]
            new_width = 650
            new_height = int(new_width/original_w * original_h) # 画像の幅が650となるように高さを調節
            original_img_np = cv2.resize(original_img_np, (new_width, new_height))

            # 画像サイズを取得
            height, width = original_img_np.shape[:2]
            
            # ウィンドウサイズを画像サイズに合わせて変更
            new_window_width = width * 2 + page.padding * 3  # 2つの画像 + パディング
            new_window_height = height + page.padding * 2 + 50  # 画像 + パディング + 余白(ボタンとスライダー用)
            page.window_width = new_window_width
            page.window_height = new_window_height
            
            # 画像サイズを設定
            img_original.width = width
            img_original.height = height
            img_processed.width = width
            img_processed.height = height
            
            processed_img_base64 = process_image(original_img_np, blur_value)
            img_processed.src_base64 = processed_img_base64
            slider.disabled = False
            page.update()

on_file_picked関数はファイルが選択されたときに呼ばれます。

  • 選択された画像を読み込む
  • 画像の幅が650ピクセルになるように高さを調整
  • 画像サイズに基づいてウィンドウサイズを調整
  • 画像ウィジェットのサイズを設定
  • 画像を処理して表示
  • スライダーを有効化
    file_picker = ft.FilePicker(on_result=on_file_picked)
    page.overlay.append(file_picker)

    slider = ft.Slider(
        min=0,
        max=10,
        divisions=10,
        value=1,
        label="{value}",
        on_change=on_slider_change,
        disabled=True
    )

    images_row = ft.Row(
        [img_original, img_processed],
        alignment=ft.MainAxisAlignment.CENTER,
        expand=True
    )

    def page_resize(e):
        images_row.height = page.window_height - 50  # Adjust this value as needed
        page.update()

    page.on_resize = page_resize

    page.add(
        ft.ElevatedButton("画像を選択", on_click=lambda _: file_picker.pick_files(allow_multiple=False)),
        images_row,
        ft.Text("カーネル サイズ:"),
        slider
    )

UI構築のプロセスです。

  • ファイル選択ダイアログを作成
  • カーネルサイズ調整用のスライダーを作成
  • 画像を表示するための行を作成
  • ページに各要素を追加
ft.app(target=main)

最後に、アプリケーションを実行します。

まとめ

  1. GUI:
    Fletは、Pythonで簡単にGUIアプリケーションを作成できる新しいフレームワークです。ft.Pageオブジェクトを使用して、アプリケーションの外観や動作を制御します。
  2. 非同期処理:
    on_file_pickedon_slider_changeなどのコールバック関数を使用することで、ユーザーの操作に応じて非同期で処理を行っています。これにより、アプリケーションの応答性が向上します。
  3. 画像のエンコーディング:
    処理後の画像をBase64エンコードすることで、FletのImageウィジェットで直接表示できるようにしています。これは、Webアプリケーションでも同様のテクニックが使用できるポイントです。
  4. 画像サイズ:
    画像のサイスはウィンドウの大きさを考慮して、常に幅が650ピクセルになるように高さを計算し、画像をリサイズしています。

発展的なトピック

  • エラー処理:
    現在のコードでは最小限のエラー処理しか行っていません。
    実際のアプリケーションでは、より堅牢なエラー処理の実装が必要になると思います。
  • パフォーマンスの最適化:
    大きな画像を処理する場合、処理に時間がかかる可能性があります。
    マルチスレッディングや画像のダウンサンプリングなどの技術を使用して、パフォーマンスを向上させることができます。
  • 追加の画像処理機能:
    ガウシアンフィルタ以外にも、エッジ検出、色調補正など、様々な画像処理機能を追加することができます。
    本ブログで多くの画像処理に関する記事を紹介していますので、ご参考にしていただければと思います。

おわりに

Flet以外にも、PythonでGUIアプリケーションを開発できるフレームワークは複数あります。
代表的なものとしては、Tkinter、Kivy、PyQtなどです。

どのフレームワークを選択するかは、開発するアプリの種類や、開発者のスキルによって異なります。 Fletは、シンプルで使いやすいフレームワークなので、Pythonで初めてGUIアプリケーションを開発する人におすすめです。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

参考リンク

■(広告)Pythonのオススメ書籍■

-Python, プログラミング, 画像処理
-