タイトルでDeep Learningと銘打っておきながら、全然ディープラーニングネタがないなあと思いつつ、またもや違う内容です。
OpenCVが便利すぎていつもお世話になっているのですが、今までほとんど使ってこなかったマウス操作(+描画)について、かなり遊べるということがわかったので、メモします。
おおまかなところはここに書いてあるのですが、いかんせん、利用のイメージがつかみにくいところがあります。
色々と調べながら、画像を拡大するツール(画像参照)を作ってみましたので、備忘としてコードを残しておくことにします。
まずは基本の理解
まずは、上のサイトで書かれている画像上に円を描画することから始めます。
私の場合、使った画像が大きすぎて扱いづらかったので、サイズ変更の処理を入れています。
import numpy as np import cv2 # 画像読み込み img = cv2.imread('img00001.jpg') # 高さが500ピクセルになるようにサイズ変更 h, w, _ = img.shape resize_rate = 500/h scaled_img = cv2.resize(img, (int(w*resize_rate), 500)) # マウス操作の定義 def draw_circle(event, x, y, flags, param): print(x, y, event, flags, param) if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(scaled_img,(x,y),100,(255,0,0),-1) return scaled_img param = {'a':1, 'b':2} # 画像表示 cv2.namedWindow('image') cv2.setMouseCallback('image',draw_circle, param={'a':1, 'b':2}) # escキーが押されると画面を閉じる while(1): cv2.imshow('image',scaled_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
importやらサイズ変更やらは、見慣れたいつもの処理ですが、18-19行目の画像表示のところで、cv2.namedWindow()とcv2.setMouseCallback()という関数が出てきます。
このcv2.setMouseCallback()が、マウス操作を定義するための関数です。
もう少し詳しく見てみます。
マウス操作の定義
# 画像表示 cv2.namedWindow('image') cv2.setMouseCallback('image',draw_circle)
cv2.namedWindow(‘image’)は、表示するウィンドウに名前をつけています。マウス操作は、ウィンドウ上でマウスがどう動いたかを判断する必要があるため、複数のウィンドウに対応できるように各ウィンドウを識別するための名前をつけます。
cv2.setMouseCallback(‘image’,draw_circle)で指定のウィンドウ(ここでは’image’ウィンドウです)上でマウスがなんらかの動作をしたときに、draw_circleに飛ぶようにされています。
次にdraw_circleを見てみましょう。
# 円の描画 def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(scaled_img,(x,y),100,(255,0,0),-1)
cv2.setMouseCallback()から渡された変数は5つあります。
- event: 発生したマウスイベント
- x, y: そのイベントが発生したときのカーソル位置(対象ウィンドウ上)
- flags: イベント発生時に押されていたキー
- param: その他、設定された変数など
マウスイベント
マウスイベントとは、たとえば以下のようなものがあります。
- cv2.EVENT_LBUTTONDBLCLK:マウスの左ボタンがダブルクリック
- cv2.EVENT_MOUSEMOVE:マウスがドラッグ
フラグ
flagは明示的に使ったことはありませんが、Alt, ctrl, shiftのキーが押されたor何も押されていないのフラグだそうです。
渡された情報の確認
マウスイベントはマウスが動くだけで発生しますので、imageウィンドウ上でマウスを動かすたびに、draw_circle()に飛んでいることになります。
こんなふうに、print()を入れてマウスを動かしたり、クリックしたりしてみると、
# 円の描画 def draw_circle(event, x, y, flags, param): print(x, y, event, flags, param) if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(scaled_img,(x,y),100,(255,0,0),-1)
こんな感じで、座標とevent, flag, paramが表示されました。
386 179 0 0 None 385 180 0 0 None 385 180 0 0 None 385 180 1 1 None 385 181 0 1 None 385 181 4 1 None 384 181 0 0 None 384 181 0 0 None 384 181 1 1 None 384 181 4 1 None 384 181 7 1 None 384 181 1 1 None 384 181 4 1 None
せっかくなので、paramも入れてみます。
# 画像表示 cv2.namedWindow('image') cv2.setMouseCallback('image',draw_circle, param={'a':1, 'b':2})
設定した値がちゃんと渡されていることが分かりました。
407 209 0 0 {'a': 1, 'b': 2} 409 211 0 0 {'a': 1, 'b': 2} 414 215 0 0 {'a': 1, 'b': 2} 415 216 0 0 {'a': 1, 'b': 2} 416 216 0 0 {'a': 1, 'b': 2} 416 216 1 1 {'a': 1, 'b': 2} 416 216 4 1 {'a': 1, 'b': 2}
まだまだ勉強不足なのですが、どうもcv2.setMouseCallback()は普通の関数とは違って、返り値を設定することができないようです。
変数のやり取りには若干クセがある印象です。
ウィンドウを閉じる
では、最後のブロックを見てみます。
これは画面を閉じるときの条件を設定したものです。
while(1)はコードによってはwhile(True)と書かれていることもあります。Trueが変わることないので、breakが入らない限り、永遠に繰り返しになります。
では、breakのタイミングは何か?というと、cv2.waitKey(20)かつ0xFF==27のときです。
cv2.waitKey()というのはキー入力されるまで、指定時間分待機することを表します。括弧内が指定時間(ミリ秒)で、0.02秒だけ入力を待つということになります。
その入力が、0xFF==27で、これは入力がescキーであった場合を表します。
つまり、escキーが入力されるまでプログラムは0.02秒間隔でscaled_imgを表示し直し続けるということです。
# escキーが押されると画面を閉じる while(1): cv2.imshow('image',scaled_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
読み解きだけで長くなってしまったため、ズーム表示のコードは次回に回すことにします。
奥が深い。