省型旧形国電の残影を求めて

戦前型旧形国電および鉄道と変褪色フィルム写真を中心とした写真補正編集の話題を扱います。他のサイトでは得られない、筆者独自開発の写真補正ツールや補正技法についても情報提供しています。写真補正技法への質問はコメント欄へどうぞ

AI upscaler のアルゴリズム解読

 さて今回は以前紹介した AI upscaler アルゴリズムを解読していきます。このプラグインのダイアログは下記のようになっています。

ダイアログ

 この内の output factor はアップスケールをする倍率を指定します。その後に、アップスケールアルゴリズムのモデルの選択があり、その後の Scope では適用範囲を指定します。これはイメージ全体に適用するのか、選択範囲のみに適用するのか、選択したレイヤーのみに適用するのかを選択します。

 

 このプラグインの基本構成は、class AIUpscale でレジストレーション情報を定義し、実質的なメインプロシジャーは、def ai_upscale になっています。それ以外にユーザ定義関数がありますが、それらはサブルーチンです。

 また、基本エンジンは Windows および Linux 用のバイナリーが用意されていて、Python スクリプトからサブプロセスとして呼び出して実行します。従って基本的な構成は、以前紹介した、AI Remove BG と似ています。また Mac OS 用のバイナリーは用意されていないので、Mac OS では走りません。

 この def ai_upscale を中心に基本的なアルゴリズムを見ていきます。

 ソースコードを見て気づくのは、各ユーザ定義関数の宣言に、

def _txt(message: str) -> str:

 というように -> という記号が見えます。これはアノテーションで、戻り値の型を注記しています。同様に、引数にも : がついていますが、やはり型が注記されています。ただしあくまでも注記なので、型チェックは行わないようです。

 

 また、class AIUpscale の中で、output_factor という引数を、Gimp.ImageProcedure.add_double_argument メソッドを使って定義しています。

 
        proc.add_double_argument(
            "output_factor",
            _txt("Output _Factor"),
            _txt("Final output size relative to original size (entire image mode)"),
            0.05, 8.0, DEFAULT_OUTPUT_FACTOR,
            GObject.ParamFlags.READWRITE
        )

 

この add_xxxxxxxxxxxxxx_argument というメソッドは、GIMP 2.10 のレジスター関数で、UI から引数を受け取るディスクリプションがありましたが、どうやらそれに相当するもののようです。

https://lazka.github.io/pgi-docs/#Gimp-3.0/classes/Procedure.html#Gimp.Procedure.add_double_argument

ここで記述しておくと、GimpUi.ProcedureDialog でダイアログを作成したときに、自動的にここで記述していたとおりの UI が設定されるようです。また、自動的に config という辞書型リスト変数に、add_xxxxxxxxxxxxxx_argument で指定したインデックス名で値が格納されるようです。

 個人的には、今まで Gtk の標準的なウィジェットのみ使用してきました。しかし、おそらく、この機能は、Gtk の標準的なウィジェットを使っていては利用できないものと思います。というわけでこのあたりは全く知りませんでした。

add_double_argument メソッドで定義された UI

 さらに、ダイアログの定義は、317行以下で行われていますが、この内ラジオボタンについては次のような構成になっています。

ラジオボタンの構成

 この内、上のモデルのラジオボタンについては、発見したモデルの数だけラジオボタンを設置するようになっています。したがってモデルが増えればラジオボタンも増えます。

        # --- Model radios ---
        frame = Gtk.Frame.new(_txt("Model"))
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
        frame.add(vbox)
        radio_group = None
        for stem in model_options:
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group, stem)
            if radio_group is None:
                radio_group = btn
            if stem == current_model:
                btn.set_active(True)
            def _on_toggle(button, s=stem):
                nonlocal current_model
                if button.get_active():
                    current_model = s
            btn.connect('toggled', _on_toggle)
            vbox.pack_start(btn, False, False, 0)
        dialog.get_content_area().pack_start(frame, False, False, 6)