📝

ガンマ補正を用いたリアルタイム映像の明るさ調整【Python】

2023/09/10に公開

この記事はQiitaの記事の転載です!!

Qiitaの記事はこちら!!

https://qiita.com/blueman/items/ebd7ff126704bcdbf93d


目次

はじめに
実行環境
webカメラの幅と高さの取得方法
メインウィンドウのサイズの設定方法
リアルタイム映像の表示領域の設定方法
スライドバーで小数を扱う方法
スライドバーの設定方法
ガンマ補正の実装方法
ルックアップテーブルの作成方法
ガンマ補正の処理方法
リアルタイム画像のtkinterでの表示方法
定期的に処理を実行する方法
ソースコード
結果
まとめ

はじめに

今回は、前回のTwitter(X)のアンケートでトップだった動画像処理についての記事です。
そのジャンルの中から、webカメラから得られた映像にγ変換をかけて明るくしたリアルタイムの動画をTkinterを用いて表示させγの値をTkintertkinter.Scaleで調整させるプログラムを作成したのでそれについて紹介したいと思います。


マイページについて

https://qiita.com/blueman

X(Twitter)について

https://twitter.com/0ca00118726208m

Qiitaについて

実行環境

実行環境は次の通りです。

実行環境
  • 環境

    • Windows10
    • Python 3.10.5
  • ライブラリ

    • OpenCV 4.8.0
    • numpy 1.22.4
    • pillow 9.2.0

処理内容ライブラリの対応を分かりやすく表にすると次の通りです。

説明
処理内容 モジュールライブラリ
webカメラの幅と高さの取得
webカメラの映像の取得・反転
ルックアップテーブル(LUT)の実装
OpenCV
γ変換の初期値の設定 numpy
γ変換のGUIでの操作 tkinter
OpenCVで得られた画像をTkinterで使えるように変換 pillow

webカメラの幅と高さの取得方法

こちらのソース

https://www.klv.co.jp/corner/python-opencv-camera-setting.html

によると、 cv2.CAP_PROP_FRAME_WIDTH でwebカメラのcv2.CAP_PROP_FRAME_HEIGHT でwebカメラの高さを取得できるそうです。
この方法を使ってwebカメラの高さを取得しました。

メインウィンドウのサイズの設定方法

こちらのソース

https://daeudaeu.com/main_window/#geometry

によると、 geometry メソッドを使うことでメインウィンドウのサイズを設定できるそうです。
このメソッドを使って、メインウィンドウのサイズを設定しました。

リアルタイム映像の表示領域の設定方法

こちらのソース

https://daeudaeu.com/tkinter_canvas_widget/#i

によると、 Canvas ウィジェットを使うことで図形を描画できるそうです。
このウィジェットを使ってリアルタイム映像を表示させました。

表示領域の幅の設定方法

こちらのソース

https://daeudaeu.com/tkinter_canvas_widget/#widthheight

によると、 Canvas ウィジェットの width キーワード引数で表示領域の幅を設定できるそうです。
このキーワード引数を使って、表示領域の幅を設定しました。

表示領域の高さの設定方法

こちらのソース

https://daeudaeu.com/tkinter_canvas_widget/#widthheight

によると、 Canvas ウィジェットの height キーワード引数で表示領域の高さを設定できるそうです。
このキーワード引数を使って、表示領域の高さを設定しました。

スライドバーで小数を扱う方法

こちらのソース

https://daeudaeu.com/control-variable/#i-2

によると、 DoubleVar というウィジェット変数を用いることで tkinter のスライドバーで 浮動小数点数(小数) を使うことができるそうです。
この DoubleVar というウィジェット変数を用いてスライドバーで小数を扱えるようにしました。

スライドバーの設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#i-2

によると、 Scale ウィジェットを使うことでスライドバーを表示できるそうです。

向きの設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#orient

によると、 orient オプションの tkinter.HORIZONTAL を使うことでスライドバーの向きを水平方向(横向き)にすることができるそうです。
このオプションを使って、向きを設定しました。

最小値と最大値の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#from-to

によると、 from_ オプションで最小値to オプションで最大値を設定できるそうです。
このオプションを使って、最小値と最大値を設定しました。

最小値と最大値の説明

分解能の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#resolution

によると、 resolution オプションで分解能を設定できるそうです。
このオプションを使って、分解能を設定しました。

分解能についての説明

ウィジェット変数の指定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#variable

によると、 variable オプションでウィジェット変数を指定できるそうです。
このオプションを使って、ウィジェット変数を指定しました。

目盛の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#tickinterval

によると、 tickinterval オプションで目盛の表示・間隔を設定できるそうです。
このオプションを使って、目盛の表示と目盛の間隔を設定しました。

長さの設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#lengthwidth

によると、 length オプションで長さを設定できるそうです。
このオプションを使って、長さを設定しました。

表示文字色の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#fgor_foreground

によると、 fg オプションで表示文字色を設定できるそうです。
このオプションを使って、表示文字色を設定しました。

背景、選択してない状態でのスライドバーの色の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#bgor_background

によると、 bg オプションで背景、選択してない状態でのスライドバーの色を設定できるそうです。
このオプションを使って、背景、選択してない状態でのスライドバーの色を設定しました。

ウィジェットの枠線色の設定方法

こちらのソース

https://daeudaeu.com/tkinter-scale/#highlightcolorhighlightbackgroundhighlightthickness

によると、 highlightbackground オプションでフォーカスが外れた時のスケールウィジェットの色を設定できるそうです。
このオプションを使って、フォーカスが外れた時のスケールウィジェットの色を設定しました。

ガンマ補正の実装方法

こちらのソース

http://rs.aoyaman.com/seminar/about10.html

によると、γ補正の公式は

Y=255\times\Bigl(\frac{X}{255}\Bigl)^\frac{1}{\gamma}
ここで、
Y:γ補正後のピクセル値
X:γ補正前のピクセル値
\gamma:ガンマ補正値

で表現できるそうです。

また、こちらのソース

https://di-acc2.com/programming/python/19009/#index_id2

では、画像を明るく表示したい場合には

γの値を1より大きい値に設定する

必要があるそうなので、γの値の範囲を1より大きくしました。

これらの情報をもとにγ補正を実装しました。

ルックアップテーブルの作成方法

γ補正の計算結果を格納する配列の作成方法

γ補正の計算結果を格納する変数を作成するために
numpyzeros 関数を利用しました。

こちらのソース

https://www.sejuku.net/blog/68767

によると、 numpyzeros 関数は、

配列の全要素が0で初期化されている

関数だそうです。
この関数を使って、γ補正の計算結果を格納する配列を作成しました。

γ補正からのルックアップテーブルの作成方法

ガンマ補正の方法で紹介したγ補正の公式である

Y=255\times\Bigl(\frac{X}{255}\Bigl)^\frac{1}{\gamma}
ここで、
Y:γ補正後のピクセル値
X:γ補正前のピクセル値
\gamma:ガンマ補正値

の公式を使って、 γ補正の計算結果を格納する配列の作成方法で紹介した配列に計算結果を0~255までループ処理で逐次代入して作成しました。

ガンマ補正の処理方法

こちらのソース

https://pystyle.info/opencv-tone-transform/#outline__3

によると、OpenCVcv2.LUTを使うことでルックアップテーブルをもとにした階調変換ができるそうです。
この方法でルックアップテーブルを使ってリアルタイム画像の階調変換を行いました。

リアルタイム画像のtkinterでの表示方法

OpenCVの画像のtkinterでの使用方法

こちらのソース

https://daeudaeu.com/pil_cv2_tkinter/#PIL_Tkinter-2

によると、 PIL(pillow)ImageTk モジュールの PhotoImage クラスを使うことで tkinter の画像オブジェクトに変換できるそうです。

また、こちらのソース

https://daeudaeu.com/pil_cv2_tkinter/#OpenCV2_PIL-2

によると、次の手順で OpenCV のデータを tkinter で使えるデータに変換できるそうです。

  1. OpenCV の画像データをBGR色空間からRGB色空間に cv2.cvtColor で変換
  2. OpenCV の画像データのような numpy 配列のデータを PIL(pillow)Image モジュールの fromarray クラスを使って PIL(pillow) の画像データに変換

この手順で OpenCV のデータを PIL(pillow) のデータにすることができるそうです。

これらの方法を使って、 OpenCV で得られたリアルタイム画像を tkinter で表示できるように設定しました。

変換した画像の表示方法

OpenCVの画像のtkinterでの使用方法OpenCV の画像を tkinter で使えるように変換できました。この変換された画像を

こちらのソース

https://daeudaeu.com/tkinter_canvas_draw/#create_image

によると、 tkinterCanvas クラスの create_image メソッドを使うと画像を tkinter に表示できるそうです。
このメソッドを使って、画像を tkinter 上に表示させました。

また、こちらのソース

https://daeudaeu.com/tkinter_canvas_draw/#anchor

によると、 anchor オプションを使うことで、画像の基準位置を設定できるそうです。
このオプションの tkinter.NW を指定しました。この値を指定することで画像の左上を基準位置として設定しました。

定期的に処理を実行する方法

こちらのソース

https://daeudaeu.com/tkinter_after/#i-2

によると、 after オプションを関数内で使うことで定期的に処理を実行させることができるそうです。
このメソッドを使って、処理を定期的に実行させました。

ソースコード

下にソースコードを示します。おそらく実行環境で示した環境では動くはず。

ソースコード
img_gamma.py
import cv2                               #webカメラの幅と高さの取得、webカメラの映像を反転させるためにインポート
import numpy as np                       #γ変換の初期値を設定するためにインポート
import tkinter                           #γ変換をGUIで操作できるようにインポート
import PIL.Image,PIL.ImageTk             #OpenCVで得られた画像をTkinterで扱えるようにインポート

cap = cv2.VideoCapture(0)                #webカメラを設定

app = tkinter.Tk()                       #tkinterのセットアップ
app.title("画像明るさ調整")              #「画像明るさ調整」という名前のウインドウを作成
app.geometry(str(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))+"x"+str(int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))))                  #Tkinterのウインドウの初期サイズをwebカメラの幅×高さに設定

canvas = tkinter.Canvas(app,width=cap.get(cv2.CAP_PROP_FRAME_WIDTH),height=cap.get(cv2.CAP_PROP_FRAME_HEIGHT))              #webカメラの映像を表示させる範囲の設定(大きさ:webカメラの幅×
                                                                                                                            #高さに設定)

canvas.pack()                                                                                                               #webカメラの映像を表示させる範囲の表示

var_scale = tkinter.DoubleVar()                                                                                          #Double(浮動小数点数(実数)型でスライドバーの値を取得

#最小値1.0、最大値3.0、刻み幅0.01、値がvar_scale、向きが横向き、背景色が青色、文字色が黄色、枠線の色が青色、目盛の間隔が0.1、長さがwebカメラの幅のスライドバーをappの(x=0,y=0)に設置
#夜間にプログラムを走らせ実装したところ3.0以上は必要ないと考えたため最大値3.0とした
scale = tkinter.Scale(app,from_=1.0,to=3.0,resolution=0.01,variable=var_scale,orient=tkinter.HORIZONTAL,bg='blue',fg='yellow',highlightbackground='blue',tickinterval=0.1,length=cap.get(cv2.CAP_PROP_FRAME_WIDTH)).place(x=0,y=0)

def gamma_trans(event=None):                        #Tkinterの関数の定義(関数の定義をすることで再帰的に処理を実行(こうすることでfor文でのループ処理ができるようになる))
	_,img = cap.read()                              #webカメラから1フレーム取得
	img = cv2.flip(img,1)                           #取得したフレームを左右反転
	gamma = var_scale.get()                         #スライドバーで設定した値を取得
	img2gamma = np.zeros((256,1),dtype=np.uint8)    #256行1列の値がすべて0のリストを作成(OpenCVの色空間の値の範囲は0~255の符号なし8bitの数値であるためdtypeにnp.uint8を指定)

	for i in range(256):                            #0~255まで処理を実行
		
		'''
		γ補正の式
		
		γ_new=255×(X/255)^(1/γ)
		
		γ_new:γ補正後の画素値
		X:ガンマ補正前の画素値
		γ:γ補正値
		
		をもとにルックアップテーブル(LUT)を作成
		'''
		
		img2gamma[i][0] = 255 * (float(i)/255) ** (1.0 /gamma)          #img2gammaの先頭の値に255×(iをfloat(浮動小数点数(実数)型に型変換)したもの/255)^(1.0/スライドバーで指定したγの値)
		                                                                #小数部分が切り捨てられた状態(整数)でimg2gammaが取得される(dtypeをnp.uint8にしているため)

	gamma_img = cv2.LUT(img,img2gamma)                                  #webカメラから得られたフレームをimg2gammaをルックアップテーブル(LUT)として画像処理

	app.img = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(cv2.cvtColor(gamma_img, cv2.COLOR_BGR2RGB)))             #Tkinterのウインドウのwebカメラを表示させる範囲にgamma_imgを設定
	                                                                                                                    #PIL.Image.fromarrayで画像を指定(PillowとOpenCVの色空間が違うので
	                                                                                                                    #変換)
	canvas.create_image(0,0,image=app.img,anchor=tkinter.NW)                                                            #画像の座標を(0,0)(原点は画像の左上端)に表示する画像をapp.imgに
	                                                                                                                    #基準位置を画像の左上に設定
	app.after(1,gamma_trans)                                                                                            #1ms間隔でgamma_transを実行(この処理がないとリアルタイムなので
	                                                                                                                    #処理が重すぎて固まる)

gamma_trans()       #この部分がないとTkinter上にwebカメラからのリアルタイムの映像が表示されない(関数内でTkinterでの画像表示も組み込んでいるため)
app.mainloop()      #GUIの表示(この処理がないと一瞬表示されて消えるので表示されていないように見える)

結果

下に結果を示します。

実行結果

まとめ

今回は、前回のTwitter(X)のアンケートでトップだった動画像処理からwebカメラから得られた映像にγ変換をかけて明るくしたリアルタイムの動画をTkinterを用いて表示させγの値をTkintertkinter.Scaleで調整させるプログラムを作成しました。
この記事が実際に役に立つかどうかは分かりませんが、誰かの役に立ってくれると嬉しいです。
記事を執筆する余力があれば、次回も記事を投稿する予定です。
次回の予定としては、今回のTwitter(X)のアンケートでトップだったファイル操作から HTMLで書かれた文字列からHTMLタグを取り除いてWindowsのメモ帳にHTMLタグのない文章を書き込む(テキストファイル名には、生成時の日付が入っている)プログラムを作成できたのでそれに関する記事を投稿予定です!!

Discussion