highgui面白いなあ、と遊んでいます。
画像処理ソフトによくあるトラックバーはないのかなあ、と思ってちょっと調べてみたら普通にある!!
トラックバーで色々遊んだのでメモします。
関数名
主に使うのはこの関数です。
cv2.createTrackbar() # 画面下部にトラックバーを表示する cv2.getTrackbarPos() # トラックバーの位置を読み取る cv2.setTrackbarPos() # トラックバーを指定の位置に設定する
実装
単純に画像のRGBの色味をトラックバーで変更できるようなツールを作ってみました。
下の図のように、RGBそれぞれの最小・最大値をトラックバーで調整して、強さを変えていくという感じです。
どう変わったかがわかるように、左側に元画像、右側に変更後の画像を並べることにします。
ライブラリのインポート
前回と途中までは流れはかぶりますが、個人用のメモも兼ねているので。
おなじみのnumpyとopencvとcopyをインポートします。
processing_imageは最小・最大値を元に画像の色味を調整する関数を入れたファイルです。色味調整以外にも色々やってみたいので、別出しすることにしました。
import numpy as np import cv2 import copy import processing_image # 画像の色味調整用
全体の流れの設定
ここは流れそのものは前回とほぼ同じです。
trackbar関連の項目については、次項以降で詳細に説明します。
# =============== # 初期画面の設定 # =============== bgr = ['Blue', 'Green', 'Red'] # 画像読み込み img = cv2.imread('IMG004.JPG') h1, w1, _ = img.shape # 縮小画像のサイズを高さが500pixelになるように定義 h2 = 500 resize_rate = h2/h1 w2 = int(w1 * resize_rate) # 画像縮小 disp_img = cv2.resize(img, (w2, h2)) disp_img_ = copy.deepcopy(disp_img) # 描画を戻す用にオリジナルの画像を保持 # デフォルト値を設定 default_values = get_default_value(disp_img) # 画像表示 cv2.namedWindow('image') # 縮小画面 create_trackbar(default_values) values = {'color' : {'Red':[0,255], 'Green':[0,255], 'Blue':[0,255]}} while(1): # 中央ボタンを押してデフォルト値に戻す cv2.setMouseCallback("image", set_default_value, default_values) # トラックバーの値を取得して画像変換 values = get_trackbar_value(values) disp_img = processing_image.color_adjustment(disp_img_, values['color']) disp_img = cv2.hconcat((disp_img_, disp_img)) cv2.imshow('image', disp_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
デフォルト値の保存
該当は上の63行目です。
default_values = get_default_value(disp_img)
下の関数を呼び出します。
単純に元の画像の最小・最大値をdefault_valuesとして保存しているだけですね。
def get_default_value(img): default_values = {'color':{}} for n, color in enumerate(bgr): default_values['color'][color] = [] default_values['color'][color] = [img[:,:,n].min(), img[:,:,n].max()] return default_values
トラックバーの作成
create_trackbar(default_values)
設定を行っているのはこの関数です。
cv2.createTrackbar()は順に以下の項目を引数として渡します。
– トラックバーのタイトル
– 作成するウィンドウ名
– 初期値
– 最大値
– 呼び出す関数
bgrは”Blue”, “Green”, “Red”を順に格納したリストですので、たとえば、下のようなトラックバーが定義されます。
- トラックバーのタイトル : "Blue (min)"
- 作成するウィンドウ名 : "image"
- 初期値 : "Blue"バンドの最小値
- 最大値 : 255
- 呼び出す関数 : print_position
なお、呼び出す関数には、引数としてトラックバーの値が渡されます。
特に毎回、値をもらっても何をすることもありませんので、passとだけ書かれたnothingという関数を呼び出しています。
def create_trackbar(default_values): for n, color in enumerate(bgr): cv2.createTrackbar('{} (min)'.format(color), 'image', default_values['color'][color][0], 255, nothing) cv2.createTrackbar('{} (max)'.format(color), 'image', default_values['color'][color][1], 255, nothing)
トラックバーの値の取得
ここまでで下準備ができました。今の状態でも、トラックバーは画面に表示されて、動かせますが、ただ動くだけです。
そのため、次にトラックバーの値を取得します。
values = get_trackbar_value(values)
呼び出された関数は下です。
cv2.getTrackbarPos()は、タイトルと、存在するウィンドウ名を指定することで、該当のトラックバーの値を取ってきます。
def get_trackbar_value(values): for n, color in enumerate(bgr): values['color'][color][0]= cv2.getTrackbarPos("{} (min)".format(color), "image") values['color'][color][1]= cv2.getTrackbarPos("{} (max)".format(color), "image") return values
色味の調整
取ってきた最小・最大値を使って画像処理を行うのが以下の行です。
disp_img = process_image.color_adjustment(disp_img_, values['color'])
呼び出し先は別ファイルで、該当箇所は下のようになっています。
よくある、norm = (img-img.min())/(img.max()-img.min())と言うやつで、指定された最小・最大値で0~1の範囲に正規化をしています。
最後に255をかけて8bitに戻せば元通りですが、0~1の範囲を超えてしまったときのため、明示的に0~1で値を切っています。
def color_adjustment(img, color): img = img.astype(np.float) # 最大値と最小値で正規化 for n, bgr in enumerate(['Blue', 'Green', 'Red']): # ゼロ割の防止 # min==maxのときは全てゼロにする if color[bgr][0] == color[bgr][1]: img[:,:,n] = 0 else: img[:,:,n] = (img[:,:,n] - color[bgr][0])/(color[bgr][1]-color[bgr][0]) img = np.clip(img, 0, 1) * 255 return img.astype(np.uint8)
変えた結果をなかったことにしたい
ひとまずはこれで色味を自在に変えることができます。
ただ、色々と遊びすぎて元に戻せなくなってしまった、ということが起きたときのために、初期値に戻せる仕組みを追加しました。
マウスの中央ボタンを押せば、予め保存しておいたデフォルト値に設定し直します。
まずは、マウスのCallbackを設定します。
cv2.setMouseCallback("image", set_default_value, default_values)
eventはマウスの中央ボタンが押された時のみ定義します。
cv2.setTrackbarPos()は、getTrackbarPos()とほぼ同じ引数ですが、最後に設定する値を与えるところが違います。
ここにデフォルト値を与えることで、元通りになります。
def set_default_value(event, x, y, flags, param): default_color = param['color'] if event == cv2.EVENT_MBUTTONDOWN: for n, color in enumerate(bgr): cv2.setTrackbarPos("{} (min)".format(color), "image", default_color[color][0]) cv2.setTrackbarPos("{} (max)".format(color), "image", default_color[color][1])
動作イメージ
これで完成です。
色味を変えて遊んでみます。
赤の最大値を低くすることで、赤の強い画像になります。
逆に、赤の最小値を大きくすると赤の弱い画像になります。
画像だと表現できないのでのせませんが、マウスの中央をクリックすると、自動でトラックバーの値が初期値に戻ります。
全体コード
全部のコードを乗っけます。
今回は2つのファイルで構成しています。
<main>
import numpy as np import cv2 import copy import processing_image def nothing(x): pass def create_trackbar(default_values): # トラックバーの設定 # BGRの値の初期値を順に入れていく for n, color in enumerate(bgr): cv2.createTrackbar('{} (min)'.format(color), 'image', default_values['color'][color][0], 255, nothing) cv2.createTrackbar('{} (max)'.format(color), 'image', default_values['color'][color][1], 255, nothing) def get_trackbar_value(values): # 現在のトラックバーの値を取得 for n, color in enumerate(bgr): values['color'][color][0]= cv2.getTrackbarPos("{} (min)".format(color), "image") values['color'][color][1]= cv2.getTrackbarPos("{} (max)".format(color), "image") return values def get_default_value(img): # 元画像の最大・最小値を取得 default_values = {'color':{}} for n, color in enumerate(bgr): default_values['color'][color] = [] default_values['color'][color] = [img[:,:,n].min(), img[:,:,n].max()] return default_values def set_default_value(event, x, y, flags, param): # デフォルトの値に戻す default_color = param['color'] if event == cv2.EVENT_MBUTTONDOWN: for n, color in enumerate(bgr): cv2.setTrackbarPos("{} (min)".format(color), "image", default_color[color][0]) cv2.setTrackbarPos("{} (max)".format(color), "image", default_color[color][1]) import numpy as np import cv2 import copy import processing_image def nothing(x): pass def create_trackbar(default_values): # トラックバーの設定 # BGRの値の初期値を順に入れていく for n, color in enumerate(bgr): cv2.createTrackbar('{} (min)'.format(color), 'image', default_values['color'][color][0], 255, nothing) cv2.createTrackbar('{} (max)'.format(color), 'image', default_values['color'][color][1], 255, nothing) def get_trackbar_value(values): # 現在のトラックバーの値を取得 for n, color in enumerate(bgr): values['color'][color][0]= cv2.getTrackbarPos("{} (min)".format(color), "image") values['color'][color][1]= cv2.getTrackbarPos("{} (max)".format(color), "image") return values def get_default_value(img): # 元画像の最大・最小値を取得 default_values = {'color':{}} for n, color in enumerate(bgr): default_values['color'][color] = [] default_values['color'][color] = [img[:,:,n].min(), img[:,:,n].max()] return default_values def set_default_value(event, x, y, flags, param): # デフォルトの値に戻す default_color = param['color'] if event == cv2.EVENT_MBUTTONDOWN: for n, color in enumerate(bgr): cv2.setTrackbarPos("{} (min)".format(color), "image", default_color[color][0]) cv2.setTrackbarPos("{} (max)".format(color), "image", default_color[color][1]) # =============== # 初期画面の設定 # =============== bgr = ['Blue', 'Green', 'Red'] # 画像読み込み img = cv2.imread('IMG004.JPG') h1, w1, _ = img.shape # 縮小画像のサイズを高さが500pixelになるように定義 h2 = 500 resize_rate = h2/h1 w2 = int(w1 * resize_rate) # 画像縮小 disp_img = cv2.resize(img, (w2, h2)) disp_img_ = copy.deepcopy(disp_img) # 描画を戻す用にオリジナルの画像を保持 # デフォルト値を設定 default_values = get_default_value(disp_img) # 画像表示 cv2.namedWindow('image') # 縮小画面 create_trackbar(default_values) values = {'color' : {'Red':[0,255], 'Green':[0,255], 'Blue':[0,255]}} while(1): # 中央ボタンを押してデフォルト値に戻す cv2.setMouseCallback("image", set_default_value, default_values) # トラックバーの値を取得して画像変換 values = get_trackbar_value(values) disp_img = processing_image.color_adjustment(disp_img_, values['color']) disp_img = cv2.hconcat((disp_img_, disp_img)) cv2.imshow('image', disp_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
<processing_image>
import numpy as np import cv2 def color_adjustment(img, color): img = img.astype(np.float) # 最大値と最小値で正規化 for n, bgr in enumerate(['Blue', 'Green', 'Red']): # ゼロ割の防止 # min==maxのときは全てゼロにする if color[bgr][0] == color[bgr][1]: img[:,:,n] = 0 else: img[:,:,n] = (img[:,:,n] - color[bgr][0])/(color[bgr][1]-color[bgr][0]) img = np.clip(img, 0, 1) * 255 return img.astype(np.uint8)
トラックバー関連を全部関数で外出しにしているのは、ぼかしとかコントラスト調整とか付け加えたいと考えているからです。
カメラ特性を勉強しながらなので、いつになるのか分かりませんが、画像処理って面白いですね。