前回に引き続き、マウス操作です。
目標の、場所を決めて拡大表示を実装します。
目標の動作の定義
- 画像を縮小して全体を表示
- 縮小画像のカーソル位置の一定範囲を別ウィンドウで拡大
- ↑の拡大領域を縮小画面で矩形で表示
- 左ボタン押下+ドラッグで拡大領域を変更
- 左ボタンが押されていないときは拡大領域の変更なし
こんな感じです。
順に実装を乗せていきます。全体のコードは最後に。
ライブラリのインポート
当然、numpyとcv2はインポートします。また、画像に手を加えていくことになるので、オリジナル画像を保持しておくために、copyもインポートしました。
import numpy as np import cv2 import copy
全体の流れの設定
画像のサイズ・初期画面の設定、画面の表示・終了の定義を行います。ここをメインの流れにして、細かい処理は関数で呼び出すようにします。
縮小画面と拡大画面の更新はrefresh_windowという関数(後述)を作ってまとめて処理をします。
# =============== # 初期画面の設定 # =============== img_size = {'orig': {}, 'disp': {}, 'zoom': {}} # 画像読み込み img = cv2.imread('img00001.jpg') img_size['orig']['h'], img_size['orig']['w'] = img.shape[0], img.shape[1] # 縮小画像のサイズを高さが500pixelになるように定義 img_size['disp']['h'] = 500 resize_rate = img_size['disp']['h']/img_size['orig']['h'] img_size['disp']['w'] = int(img_size['orig']['w'] * resize_rate) # zoom画面の大きさは400x400に zoom_img_size = 400 img_size['zoom']['w'], img_size['zoom']['h'] = zoom_img_size, zoom_img_size # 画像縮小 disp_img = cv2.resize(img, (img_size['disp']['w'], img_size['disp']['h'])) disp_img_ = copy.deepcopy(disp_img) # 描画を戻す用にオリジナルの画像を保持 # 初期のzoom画面を設定 # 元画像から切り出す座標位置を指定 fix_coords = {} fix_coords['x_s'] = 0 fix_coords['x_e'] = img_size['zoom']['w'] fix_coords['y_s'] = 0 fix_coords['y_e'] = img_size['zoom']['h'] # 表示画像の作成 disp_img, zoom_img = refresh_window(img, disp_img, fix_coords, resize_rate) # 通常、拡大領域は固定 disp_fix = True # 画像表示 cv2.namedWindow('image') # 縮小画面 cv2.namedWindow('zoom') # zoom画面 # マウス操作 cv2.setMouseCallback('image',show_zoom, param={'disp_fix':disp_fix, 'fix_coords': fix_coords}) # escキーが押されると画面を閉じる while(1): cv2.imshow('image', disp_img) cv2.imshow('zoom', zoom_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
マウス操作の定義
マウスの左ボタンを押しながらマウスを動かした時のみ、拡大表示の範囲を変えるように設定します。
ですので、定義するマウスの動きとしては下の3つです。
(1) 左ボタンが押された時
(2) マウスが動いている時
(3) 左ボタンが離された時
(2)はそれに加えて、(1)である必要があります。そのため、左ボタンが押されていないときは、disp_fixフラグをたてて表示固定、左ボタンが押されたらフラグオフにすることにしました。
つまり、(2)+disp_fixがオフのときのみ、拡大表示の範囲が変わります。
また、convert_coordinates()では、縮小画面で定義されたカーソル位置を、フルサイズの画像での位置に変換しています。ズーム画面はフルサイズの画像から切り出しているためです。
戻り値が与えられないようなので、更新したい変数(特に更新する画像)はglobal関数としなければならないようです。もしかすると別の方法があるかもしれませんが。
def show_zoom(event, x, y, flags, param): global img, disp_img, zoom_img, coords, fix_coords # zoom画面の中心位置を取得する x_orig = int(x/resize_rate) y_orig = int(y/resize_rate) # ================= # 左ボタン+ドラッグで拡大領域を移動 # ================= if event == cv2.EVENT_LBUTTONDOWN: # 左ボタン押下でzoom画面の固定解除 param['disp_fix'] = False coords = convert_coordinates(img_size, x_orig, y_orig, zoom_img_size) disp_img, zoom_img = refresh_window(img, disp_img, coords, resize_rate) if event == cv2.EVENT_MOUSEMOVE and not param['disp_fix']: # zoom画面の固定解除のときのみ、拡大領域を現在位置に変更 coords = convert_coordinates(img_size, x_orig, y_orig, zoom_img_size) disp_img, zoom_img = refresh_window(img, disp_img, coords, resize_rate) if event == cv2.EVENT_LBUTTONUP: # 左ボタンが元に戻ったらzoom画面固定に戻す param['disp_fix'] = True fix_coords = copy.deepcopy(coords) disp_img, zoom_img = refresh_window(img, disp_img, fix_coords, resize_rate)
周辺関数
あとは、周辺関数の整備ということで、
縮小画像から元画像の座標への変換
# 画像の切り出し位置を定義 def convert_coordinates(img_size, x_orig, y_orig, window_size): window_size = int(window_size/2) coords = {} coords['x_s'] = max(0, x_orig - window_size) coords['x_e'] = min(img_size['orig']['w'], x_orig + window_size) coords['y_s'] = max(0, y_orig - window_size) coords['y_e'] = min(img_size['orig']['h'], y_orig + window_size) return coords
表示画像の更新
def refresh_window(img, disp_img, coords, resize_rate): zoom_img = img[coords['y_s']:coords['y_e'], coords['x_s']:coords['x_e'], :] # imageウィンドウに矩形表示 disp_img = copy.deepcopy(disp_img_) disp_img = cv2.rectangle(disp_img, (int(coords['x_s']*resize_rate), int(coords['y_s']*resize_rate)), (int(coords['x_e']*resize_rate), int(coords['y_e']*resize_rate)), color=(255, 255, 255)) return disp_img, zoom_img
です。
これで、左クリック+ドラッグで拡大領域を移動させることができました。
画像だと分かりにくいですが、左ボタン+ドラッグでimageウィンドウの白い矩形が動いて、矩形内の画像をzoomウィンドウに表示するように動いています。
というわけで、とりあえずできたコードを以下に乗せています。リファクタリングのやり方は勉強中なので、コードの汚さはご愛嬌で。
import numpy as np import cv2 import copy # 画像の切り出し位置を定義 def convert_coordinates(img_size, x_orig, y_orig, window_size): window_size = int(window_size/2) coords = {} coords['x_s'] = max(0, x_orig - window_size) coords['x_e'] = min(img_size['orig']['w'], x_orig + window_size) coords['y_s'] = max(0, y_orig - window_size) coords['y_e'] = min(img_size['orig']['h'], y_orig + window_size) return coords def refresh_window(img, disp_img, coords, resize_rate): zoom_img = img[coords['y_s']:coords['y_e'], coords['x_s']:coords['x_e'], :] # imageウィンドウに矩形表示 disp_img = copy.deepcopy(disp_img_) disp_img = cv2.rectangle(disp_img, (int(coords['x_s']*resize_rate), int(coords['y_s']*resize_rate)), (int(coords['x_e']*resize_rate), int(coords['y_e']*resize_rate)), color=(255, 255, 255)) return disp_img, zoom_img def show_zoom(event, x, y, flags, param): global img, disp_img, zoom_img, coords, fix_coords # zoom画面の中心位置を取得する x_orig = int(x/resize_rate) y_orig = int(y/resize_rate) # 左ボタン押下でzoom画面の固定解除 if event == cv2.EVENT_LBUTTONDOWN: param['disp_fix'] = False coords = convert_coordinates(img_size, x_orig, y_orig, zoom_img_size) disp_img, zoom_img = refresh_window(img, disp_img, coords, resize_rate) # zoom画面の固定解除のとき(=左ボタンが押されているとき)のみ # 拡大領域を移動 if event == cv2.EVENT_MOUSEMOVE and not param['disp_fix']: coords = convert_coordinates(img_size, x_orig, y_orig, zoom_img_size) disp_img, zoom_img = refresh_window(img, disp_img, coords, resize_rate) # 左ボタンが元に戻ったらzoom画面固定に戻す if event == cv2.EVENT_LBUTTONUP: param['disp_fix'] = True fix_coords = copy.deepcopy(coords) disp_img, zoom_img = refresh_window(img, disp_img, fix_coords, resize_rate) # =============== # 初期画面の設定 # =============== img_size = {'orig': {}, 'disp': {}, 'zoom': {}} # 画像読み込み img = cv2.imread('img00001.jpg') img_size['orig']['h'], img_size['orig']['w'] = img.shape[0], img.shape[1] # 縮小画像のサイズを高さが500pixelになるように定義 img_size['disp']['h'] = 500 resize_rate = img_size['disp']['h']/img_size['orig']['h'] img_size['disp']['w'] = int(img_size['orig']['w'] * resize_rate) # zoom画面の大きさは400x400に zoom_img_size = 400 img_size['zoom']['w'], img_size['zoom']['h'] = zoom_img_size, zoom_img_size # 画像縮小 disp_img = cv2.resize(img, (img_size['disp']['w'], img_size['disp']['h'])) disp_img_ = copy.deepcopy(disp_img) # 描画を戻す用にオリジナルの画像を保持 # 初期のzoom画面を設定 # 元画像から切り出す座標位置を指定 fix_coords = {} fix_coords['x_s'] = 0 fix_coords['x_e'] = img_size['zoom']['w'] fix_coords['y_s'] = 0 fix_coords['y_e'] = img_size['zoom']['h'] # 表示画像の作成 disp_img, zoom_img = refresh_window(img, disp_img, fix_coords, resize_rate) # 通常、拡大領域は固定 disp_fix = True # 画像表示 cv2.namedWindow('image') # 縮小画面 cv2.namedWindow('zoom') # zoom画面 # マウス操作 cv2.setMouseCallback('image',show_zoom, param={'disp_fix':disp_fix, 'fix_coords': fix_coords}) # escキーが押されると画面を閉じる while(1): cv2.imshow('image', disp_img) cv2.imshow('zoom', zoom_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
OpenCVのhighguiめちゃ面白いです。