初心者のための目的志向プログラミング。PythonでGUIを作って、撮影後の画像の画角調整をするプログラムの続きです。
今回は別ファイルにした自モジュールのimportです。以前書いたCUIスクリプトベースのプログラムをGUIから呼び出してみます。
前回までで複数画像に対して、バーを調整することで、パラメータを調整するUIができました。今回はその調整値を使って、最後の動画までを作成してみます。
やりたいこと
以前のスクリプトであらかじめ指定することになっていた、画像切り出しの座標(x,y)、角度、倍率、の4つのパラメータを、GUIによる操作で取得する。任意のファイルを選び、切り出し後の複数フレームの出力画像を確認しながら操作する。
ここまで前回完成
タイムラプス動画の作成までをGUIで完結させる。
方針
以前作成した、CUIベースのプログラムの関数をimportして呼び出し、その関数をコールして、動画作成までを実現させるアプリにする。
以前作成したコードで実現していた、パン、ズームの調整値をそれぞれ、開始位置、終了位置として保存し、その値を使って動画へコンバートする。
コード
変換処理部(importされる側)
まずは以前解説したCUIベースの動画コンバートの本体のコード。コードそのものは
を参照してください。ココで説明しているコードを1文字も変えません。このコードをimportします。それ自体は、例えばmake_laps.pyとして同一フォルダに保存してあれば、
import make_laps
でできます。この記述をしてあげるだけで、このファイルに記載のコードが呼ばれます。ただここで注意するのが、このコードをimportすることで、このコードがそのまま挿入されるので、べたのコードは動いてしまいます。何のことはない、importした時点で、make_laps.pyが実行されます。ただ、今回このファイルの中では、
if name == '__main__': main()
とmain関数実行に条件を入れています。__name__のチェックをしています。半分おまじないのようにしていましたが、重要な意味があります。
このファイル(make_laps.py)を直接実行すると、__name__には’__main__’が入って来ますが、外からimportすると、__name__には、’make_laps’といった具合で、自身のファイル名が入ってきます。なので、今回このimportされる側のmake_laps.pyのmain()は実行されません。べたで書かず、if文を入れておくメリットはこんなところにあるようです。
なので、このmake_laps.pyのソースコードは、単体で動かせばCUIベースのプログラムに。importすれば、関数ライブラリになる。という構成になっています。
GUI部(importする側)
こちらは全文記載します。が、前回から差分は小さいし特段目新しい新しい記述もないです。
import tkinter as tk import tkinter.filedialog import cv2 from PIL import ImageTk, Image import glob import numpy as np import make_laps WIDTH = make_laps.WIDTH HEIGHT = make_laps.HEIGHT # スクロールバーが変化したときに呼ばれるコールバック関数 def scrl_callback(val): tmp = convert_preview(img) w = WIDTH // 2 h = HEIGHT // 2 image_rgb = cv2.cvtColor(cv2.resize(tmp,(w,h)),cv2.COLOR_BGR2RGB) global image_Tk image_Tk = ImageTk.PhotoImage(Image.fromarray(image_rgb),master=canvas) canvas.create_image(0,0,image=image_Tk,anchor='nw') canvas.create_line(w/2,0,w/2,h) canvas.create_line(0,h/2,w,h/2) # こいつが回転シフト拡大を行う処理本体 def convert_preview(image): h,w = image.shape[:2] affine = cv2.getRotationMatrix2D((w/2.0, h/2.0),Var_angle.get(),Var_scale.get()) affine[0,2] -= Var_shiftx.get() affine[1,2] -= Var_shifty.get() return cv2.warpAffine(image,affine,(WIDTH,HEIGHT),flags=cv2.INTER_NEAREST) # フォルダを開くアクション def open_dir(): fileName = tk.filedialog.askdirectory() global Var_Flist global flist flist = glob.glob(fileName+"\\*.jpg") Var_flist["to"] = len(flist) img_update(1) # 画像ファイルのアップデート(フレーム変更時のコールバック関数) def img_update(val): if len(flist) > 0: global img img = cv2.imread(flist[int(val)-1]) root.title(flist[int(val)-1]) #タイトルにファイル名表示 scrl_callback(0) # menu 開始位置を指定 def set_start(): global start start = np.array([Var_shiftx.get(),Var_shifty.get(),Var_angle.get(),Var_scale.get()]) # menu 終了位置を指定 def set_end(): global end end = np.array([Var_shiftx.get(),Var_shifty.get(),Var_angle.get(),Var_scale.get()]) # menu 保存 変換開始 def makelaps(): if len(flist) > 0: #リストが空でなければ保存 fileName = tk.filedialog.asksaveasfilename(filetypes = [("movie file", ".mp4")]) if fileName != "": #キャンセルが押されると名前が空のようだ make_laps.makeLaps(flist,fileName,start,end) #変換開始 root = tk.Tk() flist = [] img = np.zeros((32,32,3), np.uint8) start = np.array([0,0,0,1.0]) #座標x,座標y,回転角,倍率の順 end = np.array([0,0,0,1.0]) pw_main = tk.PanedWindow(root,orient='vertical') pw_main.pack(expand=True, fill=tk.BOTH) # 上側(画像表示部 表示は1/2スケール) canvas = tk.Canvas(pw_main, width=WIDTH//2, height=HEIGHT//2) pw_main.add(canvas) image_Tk = ImageTk.PhotoImage(Image.fromarray(img), master=canvas) #表示画像の生成 # 下側(バーがゴリゴリある方) fm_ctrl = tk.Frame(pw_main) pw_main.add(fm_ctrl) # フレームにバーを配置 Var_scale = tk.Scale(fm_ctrl, label='拡大率', orient="v", from_=0.1, to=8.0, resolution=0.1, command=scrl_callback) Var_angle = tk.Scale(fm_ctrl, label='回転角度', orient="v", from_=-180, to=180, command=scrl_callback) Var_shifty = tk.Scale(fm_ctrl, label='縦方向シフト量', orient="v", from_=-1000, to=1000, command=scrl_callback) Var_shiftx = tk.Scale(fm_ctrl, label='横方向シフト量', orient="h", from_=-1000, to=1000, command=scrl_callback) Var_flist = tk.Scale(fm_ctrl, label='フレーム', orient="h", from_=1, to=len(flist), command=img_update) Var_scale.pack(fill=tk.Y, side=tk.LEFT) Var_angle.pack(fill=tk.Y, side=tk.LEFT) Var_shifty.pack(fill=tk.Y, side=tk.LEFT) Var_shiftx.pack(fill=tk.X) Var_flist.pack(fill=tk.X) Var_scale.set(1.0) #menu bar men = tk.Menu(root) menu_file = tk.Menu(root, tearoff=0) men.add_cascade(label='ファイル',menu=menu_file) menu_file.add_command(label='フォルダを開く',command=open_dir) menu_file.add_separator() menu_file.add_command(label='開始位置として設定',command=set_start) menu_file.add_command(label='終了位置として設定',command=set_end) menu_file.add_separator() menu_file.add_command(label='名前を付けて保存',command=makelaps) root.config(menu=men) root.mainloop()
あー100行超えちゃった…。113行。コピペで動くと思います。
まず、外部にある自モジュールをimportしています。
import make_laps WIDTH = make_laps.WIDTH HEIGHT = make_laps.HEIGHT
自モジュールmake_laps.pyをimportしたうえで、内部で定数として使っているWIDTHとHEIGHTを外部の自モジュールのそれと同期させています。自モジュールの関数や変数は、このように、
make_laps.
と’.’を付けてアクセスできます。
続けて、コールバック関数を3つ追加しています。
開始フレームでの画角確定時にコールし、その座標や倍率等のパラメータを覚えておく関数と、同様に終了フレームでのパラメータ記憶関数、あとコンバート実行(ファイルを保存)する関数です。
・開始、終了位置パラメータ記憶関数
# menu 開始位置を指定 def set_start(): global start start = np.array([Var_shiftx.get(),Var_shifty.get(),Var_angle.get(),Var_scale.get()])
難しいことはないですね。globalにおいてあるstartにパラメータをnumpy arrayに残しているだけです。終了も同じ記述です。関数分けたくなかったのですが、どうもうまく一つにならなかったので、いったんコピペで同じような関数を追加して、終了フレームのそれにしています。
・変換開始関数
# menu 保存 変換開始 def makelaps(): if len(flist) > 0: #リストが空でなければ保存 fileName = tk.filedialog.asksaveasfilename(filetypes = [("movie file", ".mp4")]) if fileName != "": #キャンセルが押されると名前が空のようだ make_laps.makeLaps(flist,fileName,start,end) #変換開始
まずここでは、いつものsaveダイアログを表示させて、ファイル名を取ってきます。tkinterにお任せです。ココでは、拡張子を制限できるので、保存形式である*.mp4を指定してあげてます。
この結果、文字列が空でなければ、外部自モジュールの関数を呼んで動画の保存をさせています。
・メニュー追加
main部ではこの3つのコールバック関数を呼ぶためのmenuを追加しています。
#menu bar men = tk.Menu(root) menu_file = tk.Menu(root, tearoff=0) men.add_cascade(label='ファイル',menu=menu_file) menu_file.add_command(label='フォルダを開く',command=open_dir) menu_file.add_separator() menu_file.add_command(label='開始位置として設定',command=set_start) menu_file.add_command(label='終了位置として設定',command=set_end) menu_file.add_separator() menu_file.add_command(label='名前を付けて保存',command=makelaps) root.config(menu=men)
やり方はファイルを開くで追加したのと同じ要領。呼び出した順にメニューが作られるようです。
間にセパレーターを入れています。この1文でメニュー間にセパレータが入ります。
追加分はこれだけです。あまり面白くはないですね。
これでGUIを使って、ファイルがあるフォルダを指定し、開始終了フレームの画像を見ながら、パン、ズームさせる位置を設定し、タイムラプス動画を保存するまでの一連の動作をさせるプログラム(ほぼアプリ)が出来上がりました。ここまでやると達成感があります。
決して使いやすいGUIではないですが、操作性の追求はここまでにしときます。いろいろ思いつくし、やってみたい気もしますが、コードが長くなりそうなので、simple is bestで。
まとめ
今回、自モジュールを画像処理本体としてimportして、それを外からコールする仕組みを加えてプログラムしてみました。
importされたモジュールは何のことはない、その行のタイミングで実行されるだけ。
その時__name__にはmainでは入ってこない。
という動きが確認できました。なので外部モジュールのmain文(if __name__=’__main__’以下)には、このモジュールを使うにあたってのサンプルコードが書ける。ということになりますね。今回の使い道は正解のようです。
今回のこのGUIコードにはglobal変数べたべたで、それこそ外部からのimport不能状態なので、イマイチダサいです。次はググると出てくるpython guiサンプルコードのように、このGUI部分をclass化して、globalを追い出してみたいと思います。
コメント