今回は、複数選択した画像をマウスを使って切り替えていく方法をやってみたいと思います。
ただ、今回切り替えていく画像はDICOM画像ではなくてピング形式や、ビットマップ等の一般的な画像ファイルとします。
Contents
マウスイベントとは
右クリック(左クリック)した時の動作や、マウス中央のマウスホイールをクルクルっとした時の動作をマウスイベントと言います。
今回は、そのマウスイベントを使って画像を切り替えていくことをやろうと思っているのですが、Image-Jなどの画像ビューアではマウスホイールで画像切り替えを行っていると思いますので、それを真似してコードを作成していきたいと思います。
手順
まずは、ざっくりと手順を。
- マウスイベントの関数を作る。
- 画像表示の関数を作る
- コードを繋げる
- マウスイベント呼び出しのコードを入れる
といった流れでやっていきます。
マウスイベントの関数とは?
マウスイベントは、関数を作って指定していきますので、まずは関数のおさらいです。関数は
def ○○(引数1 ,引数2):
で指定していきます。なお、関数内のコードは字を下げて記載していきます。(Tabキーで文字を下げていきますが、プログラム作成用のソフトを使う場合、大抵初めの一行目を下げれば、それ以降も自動で字下げをやってもらえます。)
それでは、本題のマウスイベントに入ります。関数名はどんな名前でも構わないのですが、マウス動作なのでonMouseとしてみましょう。
def onMouse(event,x,y,flag,param):
引数
def onMouse(event,x,y,flag,param):
マウスイベントには指定しなければならない引数がいくつかあります。ここで引数の説明をします。
def onMouse(event,x,y,flag,param): eventとは
引数のeventとは、マウスの操作を指します。
クリックを押した時なのか、クリックを押しながら動かした時なのか、クリックを上げた時なのか、マウスホイールを動かした時なのか等さまざまな指定ができます。
- cv2.EVENT_MOUSEMOVE マウスの移動
- cv2.EVENT_LBUTTONDOWN 左クリック
- cv2.EVENT_RBUTTONDOWN 右クリック
- cv2.EVENT_LBUTTONUP 左クリックを離した時
- cv2.EVENT_RBUTTONUP 右クリックを離した時
- cv2.EVENT_MOUSEWHEEL マウスホイールを回した時
等、いろんなイベントがありますが今回は、マウスホイールの動作なので
cv2.EVENT_MOUSEWHEEL:を使います。
(なお、マウスイベントはOpenCVの動作になりますので、OpenCVをインポートするのを忘れないでくださいね。)
なので、プログラム的に
「もし、マウスホイールを回したら」という構文を作ります。なので
1 2 3 4 5 |
# coding: UTF-8 def onMouse(event, x, y, flag, params): if event == cv2.EVENT_MOUSEWHEEL: |
def onMouse(event,x,y,flag,param): x,yとは
イベントが起こった画像上の座標を指します。
今回は意味はありません。
def onMouse(event,x,y,flag,param): flagとは
flagとは、イベントと同時に起こした動作です。
例えば、shiftキーを押しながらとか、ctrlキーを押しながらとか言ったことです。
今回のマウスホイールの場合は、数値が入ってきます。
マウスホイールを回した場合手前に回すか、奥に回すかによって正の数、もしくは負の数が返されます。値も、使っているマウスによってさまざまです。
ちなみに、今回はflagで受けた数値は0以上か、0以下で画像を送るか、戻すかの判断します。
def onMouse(event,x,y,flag,param): paramとは
paramとは、event,x,y,flag以外で処理に使いたい値をひっくるめて指定できます。
今回は、画像表示した際のウインドウ名や、ファイルを格納した変数を渡すのに使います。使い方として関数の次の行に以下のように指定します。
i,filenames = params
といった感じです。 (i,filenames は以後のコードの中で使う変数なので今は気にしないでください。)
i,filenamesという変数はparamsの中に入っていますよという意味です。
マウスホイールの関数を作成
そいれでは、関数を書いていきたいと思います。
前回の記事で書きましたが、複数の画像をダイアログを使って選択した際、変数にその画像のパスが保存されます。そのパスを一個一個取り出して画像表示をしていかなくてはいけません。
1個目のパスを取り出して 画像を表示した次は、2個目のパスを取り出して表示、次は3個目のパスを取り出して表示と順番にやっていく。
i番目の画像パスを取り出したいときは
filenames[i]といった書き方になると記載しました。(filenamesはファイルパスを入れた変数名です。)
なので、マウスホイールを動かした時にiが変わる処理を書きます。
マウスホイールが動いた時には引数のflagの引数に正、もしくは負の数が入ってきますので
もし、正の数であったら画像を送る(画像番号がiだったらi+1の画像表示)負の数であったら画像を戻す( 画像番号がiだったらi-1の画像表示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# coding: UTF-8 def onMouse(event, x, y, flag, params): i,filenames= params if event == cv2.EVENT_MOUSEWHEEL: # ホイールを回したときの動作 if flag > 0: #もしflagが正の数だったら画像番号iを1引く i -= 1 # -= とはiの数から1を引いた数をiに代入すること # -=が気に入らなかったらi = i-1でもOK elif flag < 0: #もしflagが負の数だったら画像番号iを1足す i += 1 # += とはiの数から1を引いた数をiに代入すること # +=が気に入らなかったらi = i+1でもOK |
13行目
if i < 0: #もし、iが0より小さかったら
となっていたら文字化けしていますので ”<” 部分を ”<” と直してください。
これで、画像番号を示す変数iを変える処理は完成です。簡単でしょ?
しかし、このままではiが負になったり、iが変数の要素(選択したファイル数)以上の数になった際には画像表示できませんとエラーになってしまいます。
今度は、その処理を書いていきます。
内容は、
もしiがマイナスになるようだったらiは0のまま。
もしiが要素数以上になるようだったらiはそのまま
といった処理を付け加えておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# coding: UTF-8 def onMouse(event, x, y, flag, params): wname, img ,i,filenames= params if event == cv2.EVENT_MOUSEWHEEL: # ホイールを回したときの動作 if flag > 0: #もしflagが正の数だったら画像番号iを1引く i -= 1 # -= とはiの数から1を引いた数をiに代入すること elif flag < 0: #もしflagが負の数だったら画像番号iを1足す i += 1 # += とはiの数から1を引いた数をiに代入すること if i < 0: #もし、iが0より小さかったら i = 0 #iは0 if i > len(filenames)–1: #もし、iが要素数以上だったら i = len(filenames)–1 #要素数より1少ない数に |
ここでlen(filenames)が出てきましたが、lenとは要素の数を返す関数となるのでファイル選択した数となります。
len(filenames)-1 となっているのはプログラム上、数は0からカウントするからです。(人間は1から数えるので-1しないとずれてしまいます。)
画像表示の関数を作成
次に、今回は画像表示を何度も繰り返す動作になるので画像表示のコード自体を関数と作成してしまいましょう。
今回はDICOM画像ではなく、一般的な画像形式(png,bitmap,jpg)の画像を表示していきます。(DICOM画像だと、WW,WLの設定もコードに入れなければならない為、簡素化のために一般的画像形式としました。)
使うモジュールはマウスイベントでOpenCVを使っているので、同じOpenCVで開こうと思います。
画像を開く際はまず、画像を変数内に読み込みます。
img = cv2.imread(filenames[i])
そして、画像表示のウインドウ名を決めなければなりません。
今回は、window名なので変数名wnameとしてそこにimgと名付けます。
wname = “img”
最後に、画像表示のコードを付けます。コードは以下となり、引数は
cv2.imshow(ウインドウ名,画像のパス)
となるので
cv2.imshow(wname, img)
となります。
これで画像表示ができるかといえばできません。よく忘れてしまうのですが画像表示後をいつまで続けるのか?といった指定をしなければいけません。忘れてしまうと画像が一瞬開いてすぐに閉じてしまいます。見た目には画像が表示されたことに気が付かず、おかしいな?と思ってしまいます。私はこれに気が付かず苦労しました。
cv2.waitKey()
上記処理は、何かキーが押されるまで待ちます。
cv2.destroyAllWindows()
何か押されたらすべてのウインドウを閉じる処理となります。
1 2 3 4 5 6 7 8 9 10 11 12 |
def imageshow(filenames,i): img = cv2.imread(filenames[i]) wname = “img” cv2.imshow(wname, img) print(str(i) + ” image “ + filenames[i]) #確認用。コンソールに変数の番号とイメージパスを表示 cv2.setMouseCallback(wname, onMouse, [i, filenames]) cv2.waitKey() cv2.destroyAllWindows() |
コードを繋げていきます。
マウスイベントと、画像表示、ダイアログを使ってファイルを選択の関数を一つのファイルに入れ、それらを繋げていきましょう。
順番はどうでもいいですが、まずマウスイベントのファイルを入れ、ファイルセレクト、その後に、画像表示の関数を入れていきましょう。
その際、OpenCV、tkinterのimportを忘れないでくださいね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# coding: UTF-8 import cv2 import tkinter from tkinter import filedialog as tkFileDialog def onMouse(event, x, y, flag, params): i, filenames= params if event == cv2.EVENT_MOUSEWHEEL: # ホイールを回したときの動作 if flag > 0: #もしflagが正の数だったら画像番号iを1引く i -= 1 # -= とはiの数から1を引いた数をiに代入すること elif flag < 0: #もしflagが負の数だったら画像番号iを1足す i += 1 # += とはiの数から1を引いた数をiに代入すること if i <= 0: #もし、iが0より小さかったら i = 0 #iは0 elif i >= len(filenames)–1: #もし、iが要素数以上だったら i = len(filenames)–1 #要素数より1少ない数に imageshow(filenames, i) def fileselect(): root = tkinter.Tk() root.withdraw() fTyp = [(”, ‘*’)] iDir = ‘C:/Desktop’ filenames = tkFileDialog.askopenfilenames(filetypes=fTyp, initialdir=iDir) return filenames # 選択ファイルの絶対パスを返します。 def imageshow(filenames,i): img = cv2.imread(filenames[i]) wname = “img” cv2.imshow(wname, img) print(str(i) + ” image “ + filenames[i]) cv2.waitKey() cv2.destroyAllWindows() |
上記の様になります。ただ、これだけではプログラムは動かないのでそれらを関連付けてあげましょう。
プログラムの流れは、
- 起動したらダイアログを使ってファイルを選択
- 画像表示
- マウスホイールを回したら画像が切り替わる
となります。
filenamesという変数に選択した画像ファイルのパスを入れたいので
初めは
filenames = fileselect()
とし、ダイアログを使ってファイルを選択する関数(fileselect)を呼び出し、returnで返されたリストをfilenamesの変数にいれます。
今度は、そのリストを画像表示(imageshow)の関数で画像表示しましょう。
imageshowの関数にリスト(filenames)を渡してそのリストの中から画像表示をさせます。その際、はじめに表示する画像はリストに入っている初めの画像にしたいので変数iを0に設定します。
i=0
imageshow(filenames,i)
これで、画像表示がでます。
「 マウスホイールを回した時の処理 」を呼び出す。
はじめに、マウスホイールの関数を書きましたがそれだけでは、マウスイベントは動きません。
動かすにはマウスイベントを呼び出すコードが必要となります。
cv2.setMouseCallback(window, onMouse, 引数)
マウスイベントを呼び出すにはcv2.setMouseCallbackという関数を使います。
引数
cv2.setMouseCallback(window, onMouse, 引数) windowとは
1 |
windowとはどのウインドウでのマウスイベントなのかを指定します。 |
wnameです。
cv2.setMouseCallback(window, onMouse, 引数) onMouseとは
onMouseはどの関数名を呼び出すかという事です。
cv2.setMouseCallback(window, onMouse, 引数) 引数とは
onMouse関数のparamsの渡す引数を指定します。複数ある場合は[]で指定します。
今回はiとfilenamesを渡しますので[i,filenames]となります。
マウスイベント呼び出しコードの挿入
マウスイベントは、画像が表示されてから閉じられるまでの間になるので上記コードの39行目になります。
cv2.setMouseCallback(wname, onMouse, [i, filenames])
のコードを挿入して終了となります。
完成コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# coding: UTF-8 import cv2 import tkinter from tkinter import filedialog as tkFileDialog def onMouse(event, x, y, flag, params): i, filenames= params if event == cv2.EVENT_MOUSEWHEEL: # ホイールを回したときの動作 if flag > 0: #もしflagが正の数だったら画像番号iを1引く i -= 1 # -= とはiの数から1を引いた数をiに代入すること elif flag < 0: #もしflagが負の数だったら画像番号iを1足す i += 1 # += とはiの数から1を引いた数をiに代入すること if i <= 0: #もし、iが0より小さかったら i = 0 #iは0 elif i >= len(filenames)–1: #もし、iが要素数以上だったら i = len(filenames)–1 #要素数より1少ない数に imageshow(filenames, i) def fileselect(): root = tkinter.Tk() root.withdraw() fTyp = [(”, ‘*’)] iDir = ‘C:/Desktop’ filenames = tkFileDialog.askopenfilenames(filetypes=fTyp, initialdir=iDir) return filenames # 選択ファイルの絶対パスを返します。 def imageshow(filenames,i): img = cv2.imread(filenames[i]) wname = “img” cv2.imshow(wname, img) print(str(i) + ” image “ + filenames[i]) cv2.setMouseCallback(wname, onMouse, [i, filenames]) cv2.waitKey() cv2.destroyAllWindows() filenames = fileselect() i=0 imageshow(filenames,i) |
いかがでしょうか?
きちんと動きましたか?
環境
- windows10
- python3.6.1
- Anaconda custom(64-bit)
- PyCharm2020.2(Communication Edition)