はじめに
PythonでGUIというと、標準GUIライブラリ"Tkinter"を使うことが多いと思いますが、macでは環境構築でハマってしまうこともしばしばです。
本記事では、Pythonの他のライブラリと同様にpipでインストールするだけで利用できるFletというフレームワークを使って、シンプルながら実用的な画像処理アプリを作成する方法をご紹介します。
Fletとは
Fletは、Pythonで簡単にクロスプラットフォームのGUIアプリケーションを作成するための比較的新しいフレームワークです。Flutterをベースとしており、コードの記述量を抑えつつ、見た目にも洗練されたアプリを簡単に作成することができます。
以下がFletの主な特徴です。
- シンプルなコード:
Fletは、コードが非常にシンプルで読みやすいのが特徴です。そのため、複雑なロジックを記述する場合でも、メンテナンスしやすいコードを書くことができます。 - クロスプラットフォーム:
デスクトップ(Windows、macOS、Linux)、Web、モバイル(iOS、Android)で動作するアプリケーションを単一のコードベースで作成できます。 - モダンなデザイン:
Material Designを基にした美しくモダンなUIコンポーネントを提供しています。 - リアクティブ:
ステート管理が容易で、UIの更新がシンプルに行えます。 - 豊富なウィジェット:
テキスト、ボタン、画像、リスト、グリッドなど、多様なUIコンポーネントが用意されています。 - カスタマイズ性:
テーマやスタイルのカスタマイズが可能で、アプリケーションの外観を細かく制御できます。 - アセットの管理:
画像やフォントなどのアセットを簡単に管理・使用できます。 - ホットリロード:
開発時にコードの変更がリアルタイムでUIに反映されるため、迅速な開発が可能です。
Pythonを使用してGUIアプリケーションを素早く開発したい場合や、Webとデスクトップの両方で動作するアプリケーションを作りたい場合に適しています。TkinterやPyQtなどの従来のPython GUIフレームワークと比較して、より現代的で使いやすい設計になっています。
インストール方法
Fletを使用するには、Python 3.6以降が必要です。
Fletをインストールするには 以下のコマンドを実行して、Fletをインストールします。
本記事の執筆時点でのバージョンはv0.23.0です。
pip install flet
Fletは公式ドキュメントが充実しています。チュートリアルも用意されているので、基本的な使い方を学ぶことができます。
Fletを使ってみる
以下で紹介するサンプルコードはガウシアンフィルタのぼかし強度をリアルタイムで調整できるアプリです。
次の機能が実装されています。
- 画像ファイルの選択
- 選択した画像の表示
- ガウシアンフィルタの適用
- スライダーによるぼかし強度のリアルタイム調整
- 処理後の画像の表示
サンプルコード
下記にサンプルコードを示します。
このサンプルコードを実行すると次のウィンドウが表示されます。
![Fletを使ったガウシアンフィルタ デモ 起動時](https://www.codevace.com/wp-content/uploads/2024/07/Fletを使ったガウシアンフィルタ-デモ_起動時-1024x804.webp)
「画像を選択」ボタンをクリックして任意の画像ファイルを選択すると、下の様に左にオリジナルの画像、右にガウシアンフィルタ適応後の画像が表示されます。
ウィンドウの下部のスライダーを動かすことで、ガウシアンフィルタのカーネルサイズが変更されスムージングの強度がリアルタイムで変化します。
スライダーの値(silder value)は0〜10の範囲で設定することができ、次の式でガウシアンフィルタのカーネルサイズが計算されます。
$$\mathsf{ kernel \ size = silder \ value \times 2 + 1 }$$
![Fletを使ったガウシアンフィルタ デモ](https://www.codevace.com/wp-content/uploads/2024/07/Fletを使ったガウシアンフィルタ-デモ-1024x423.webp)
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)
最後に、アプリケーションを実行します。
まとめ
- GUI:
Fletは、Pythonで簡単にGUIアプリケーションを作成できる新しいフレームワークです。ft.Page
オブジェクトを使用して、アプリケーションの外観や動作を制御します。 - 非同期処理:
on_file_picked
やon_slider_change
などのコールバック関数を使用することで、ユーザーの操作に応じて非同期で処理を行っています。これにより、アプリケーションの応答性が向上します。 - 画像のエンコーディング:
処理後の画像をBase64エンコードすることで、FletのImage
ウィジェットで直接表示できるようにしています。これは、Webアプリケーションでも同様のテクニックが使用できるポイントです。 - 画像サイズ:
画像のサイスはウィンドウの大きさを考慮して、常に幅が650ピクセルになるように高さを計算し、画像をリサイズしています。
発展的なトピック
- エラー処理:
現在のコードでは最小限のエラー処理しか行っていません。
実際のアプリケーションでは、より堅牢なエラー処理の実装が必要になると思います。 - パフォーマンスの最適化:
大きな画像を処理する場合、処理に時間がかかる可能性があります。
マルチスレッディングや画像のダウンサンプリングなどの技術を使用して、パフォーマンスを向上させることができます。 - 追加の画像処理機能:
ガウシアンフィルタ以外にも、エッジ検出、色調補正など、様々な画像処理機能を追加することができます。
本ブログで多くの画像処理に関する記事を紹介していますので、ご参考にしていただければと思います。
おわりに
Flet以外にも、PythonでGUIアプリケーションを開発できるフレームワークは複数あります。
代表的なものとしては、Tkinter、Kivy、PyQtなどです。
どのフレームワークを選択するかは、開発するアプリの種類や、開発者のスキルによって異なります。 Fletは、シンプルで使いやすいフレームワークなので、Pythonで初めてGUIアプリケーションを開発する人におすすめです。
ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。
参考リンク
Build multi-platform apps in Python powered by Flutter
Flet enables developers to easily build realtime web, mobile and desktop apps in Python. No frontend experience required.
■(広告)Pythonのオススメ書籍■