wxPython との連携 -- 画像二値化GUIアプリケーション


wxPython を使用してグラフィカルに画像二値化を行うアプリケーションのサンプルです。

画像はドラッグアンドドロップによりファイルから読み込むことができます。 二値化しきい値はスライダーにより操作できます。

本サンプルの実行には予め wxPython をインストールしておく必要があります。 pip コマンドを使用する場合、次のようにインストールしてください。

pip install wxPython==4.1.0

また、本サンプルが実行可能な Matplotlib の最小バージョンは 2.2.0 になります。

コード

import pyfie
import matplotlib
from matplotlib.backends.backend_wxagg import (
    FigureCanvasWxAgg, NavigationToolbar2WxAgg
)
import wx

# 二値化しきい値の初期値
INITIAL_THRESH = 128


class Plot(wx.Panel):
    """Matplotlib を使用して PyFIE 画像を表示する Panel

    パネル下部にナビゲーションツールバー
    (https://matplotlib.org/3.2.1/users/navigation_toolbar.html)
    も表示する。"""

    def __init__(self, parent, top_label):
        super().__init__(parent)
        self.figure = matplotlib.figure.Figure(figsize=(3, 3))
        self.canvas = FigureCanvasWxAgg(self, wx.ID_ANY, self.figure)
        self.toolbar = NavigationToolbar2WxAgg(self.canvas)
        self.toolbar.Realize()

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(wx.StaticText(self, wx.ID_ANY, top_label))
        sizer.Add(self.canvas, 1, wx.EXPAND)
        sizer.Add(self.toolbar)
        self.SetSizer(sizer)

    def draw_img(self, himg):
        """PyFIE 画像オブジェクトを描画する"""
        # 簡単のため、これまでの描画を削除する。
        # note: 描画の全削除はパフォーマンス面で不利であることにご注意ください。
        self.figure.clear()
        # PyFIE 画像オブジェクトのプロット機能により画像描画
        himg.imshow(plot_obj=self.figure)
        self.canvas.draw()


class ThresholdingPlots(wx.Panel):
    """左側に入力画像、右側に二値化画像を表示する Panel。

    入力画像と二値化しきい値は外部から変更可能。"""

    def __init__(self, parent):
        super().__init__(parent)

        # 画像表示用パネル
        self.plot_src = Plot(
            self, "入力画像(ドラッグ&ドロップで設定してください)"
        )
        self.plot_dst = Plot(self, "二値化画像")

        # レイアウトの設定
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.plot_src, 1, wx.EXPAND)
        sizer.Add(self.plot_dst, 1, wx.EXPAND)
        self.SetSizer(sizer)

        # 入力画像
        self.hsrc = None
        # 出力画像
        self.hdst = None
        # 二値化しきい値
        self.thresh = INITIAL_THRESH

    def _update_src(self):
        if self.hsrc is not None:
            self.plot_src.draw_img(self.hsrc)

    def _update_dst(self):
        if None not in (self.hsrc, self.hdst):
            pyfie.fnFIE_binarize(self.hsrc, self.hdst, self.thresh)
            self.plot_dst.draw_img(self.hdst)

    def set_src_img(self, hsrc):
        """入力画像を設定する。出力画像も必要とあらば更新する"""
        self.hsrc = hsrc
        self.hdst = hsrc.empty_like(img_type=pyfie.F_IMG_BIN)
        self._update_src()
        self._update_dst()

    def set_thresh(self, thresh):
        """二値化しきい値を設定する。出力画像も必要とあらば更新する"""
        self.thresh = thresh
        self._update_dst()


class ImageFileDropTarget(wx.FileDropTarget):
    """PyFIEで読み込む画像ファイルをドラッグアンドドロップで受け取るためのクラス"""

    def __init__(self, on_image_recieved):
        super().__init__()
        self.on_image_recieved = on_image_recieved

    def OnDropFiles(self, x, y, filenames):
        if len(filenames) > 1:
            wx.MessageBox("単一のファイルのみ受け付けます")
            return False
        try:
            himg = pyfie.imread(filenames[0])
        except Exception as e:
            wx.MessageBox("画像読み込みエラー:" + str(e))
            return False
        if himg.f_type != pyfie.F_IMG_UC8 or himg.ch != 1:
            wx.MessageBox("1チャネルの8ビット濃淡画像のみ受け付けます")
            return False

        self.on_image_recieved(himg)
        return True


def main():
    # アプリケーションオブジェクトなどを作成
    app = wx.App()
    frame = wx.Frame(None, wx.ID_ANY, '画像二値化アプリ')
    panel = wx.Panel(frame)
    plots = ThresholdingPlots(panel)

    # スライダーの設定
    def on_slider_changed(event):
        obj = event.GetEventObject()
        value = obj.GetValue()
        plots.set_thresh(value)
    slider = wx.Slider(panel, style=wx.SL_HORIZONTAL | wx.SL_LABELS)
    slider.Bind(wx.EVT_SLIDER, on_slider_changed)
    slider.SetMin(0)
    slider.SetMax(255)
    slider.SetValue(INITIAL_THRESH)

    # レイアウトの設定
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.Add(plots, 1, wx.EXPAND)
    sizer.Add(wx.StaticText(panel, wx.ID_ANY, "二値化しきい値"))
    sizer.Add(slider, flag=wx.GROW)
    panel.SetSizer(sizer)

    # ドラッグアンドドロップの設定
    def on_image_recieved(himg):
        plots.set_src_img(himg)
    frame.SetDropTarget(ImageFileDropTarget(on_image_recieved))

    # フレームを表示してメインループ開始
    frame.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()

スクリーンキャスト(2倍速)

../../_images/wxpython_screencast.gif

備考

描画には Matplotlib によるプロット機能 を用いています。 Matplotlib はリアルタイムな描画に向いたライブラリではないため、本アプリケーションの応答時間も大きめであることにご注意ください。

ダウンロード

関連サンプル