Python版OpenCVで積分画像、DoG、DoM、Numbaで高速化する方法をソースコード付きで解説します。
積分画像
積分画像(Integral Image)は、注目画素とその左と上にある全ての画素値の和を求めた物です。
【詳細】積分画像の原理・計算式・高速化
今回は、Python言語とOpenCVの「cv2.integral」を用いて積分画像を求めてみました。
書式
dst = cv2.integral(gray)
パラメータ名 | 説明 |
---|---|
src | 入力画像(グレースケール) |
kernel | フィルタのカーネルサイズ(3なら8近傍) |
dst | 積分画像(入力画像より幅・高さが1px大きい) |
ソースコード
サンプルプログラムのソースコードです。
#-*- coding:utf-8 -*- import cv2 import numpy as np def main(): # 入力画像を読み込み img = cv2.imread("input.jpg") # グレースケール変換 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # 画素値を表示 print("gray=\n", gray) # 積分画像の作成 integral = cv2.integral(gray) # 画素値を表示 print("integral=\n", integral) if __name__ == "__main__": main()
※動作には、OpenCVライブラリのインストールが必要です。
実行結果
サンプルプログラムの実行結果です。
■入力画像
■出力結果
gray= [[ 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 255 255 255 255 255 0 0] [ 0 0 0 255 255 255 255 255 0 0] [ 0 0 0 255 255 255 255 255 0 0] [ 0 0 0 255 255 255 255 255 0 0] [ 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0]] integral= [[ 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 255 510 765 1020 1275 1275 1275] [ 0 0 0 0 510 1020 1530 2040 2550 2550 2550] [ 0 0 0 0 765 1530 2295 3060 3825 3825 3825] [ 0 0 0 0 1020 2040 3060 4080 5100 5100 5100] [ 0 0 0 0 1020 2040 3060 4080 5100 5100 5100] [ 0 0 0 0 1020 2040 3060 4080 5100 5100 5100] [ 0 0 0 0 1020 2040 3060 4080 5100 5100 5100]]
【ぼかし・輪郭検出】DoGフィルタ
DoG(Difference of Gaussian)とは、 の値が異なる2つのガウシアンフィルタ画像の差分です。
DoGフィルタは、LoGフィルタに近似できます。
そして、計算量も小さいため、LoGフィルタの代わりなどでよく用いられます。
【詳細】DoGフィルタの原理・特徴・計算式
今回は、Python言語とOpenCVを用いてDoGフィルタを実装してみました。
書式
dst = cv2.GaussianBlur(src, ksize, sigmaX)
パラメータ名 | 説明 |
---|---|
src | 入力画像 |
ksize | カーネルサイズ |
sigmaX | ガウス分布の |
dst | 出力画像 |
ソースコード
サンプルプログラムのソースコードです。
#-*- coding:utf-8 -*- import cv2 import numpy as np # DoGフィルタ def DoG(gray, ksize, sigma1, sigma2): # 標準偏差が異なる2つのガウシアン画像を算出 g1 = cv2.GaussianBlur(gray, ksize, sigma1) g2 = cv2.GaussianBlur(gray, ksize, sigma2) # 2つのガウシアン画像の差分を出力 return g1 - g2 def main(): # 入力画像を読み込み img = cv2.imread("input.jpg") # グレースケール変換 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # DoGフィルタ処理 dst = DoG(gray, (3,3), 1.3, 2.6) # 結果を出力 cv2.imwrite("output.jpg", dst ) if __name__ == "__main__": main()
※動作には、OpenCVライブラリのインストールが必要です。
実行結果
サンプルプログラムの実行結果です。
■入力画像(左)と出力画像(右)
【ぼかし・輪郭検出】DoMフィルタ
DoM(Difference of Median)とは、 カーネルサイズが異なる2つのメディアンフィルタ画像の差分です。
DoMフィルタは、積分画像を使って処理を高速化できます。
そのため、DoGの代わりに用いられます。
【詳細】DoMフィルタの原理・特徴・計算式
今回は、Python言語とOpenCVのメディアンフィルタを用いてDoMフィルタを実装してみました。
書式
dst = cv2.medianBlur(src, ksize)
パラメータ名 | 説明 |
---|---|
src | 入力画像 |
kernel | フィルタのカーネルサイズ(3なら8近傍) |
dst | 出力画像 |
ソースコード
サンプルプログラムのソースコードです。
#-*- coding:utf-8 -*- import cv2 import numpy as np def DoM(gray, ksize1, ksize2): # カーネルサイズの異なる2つのメディアンフィルタ処理 m1 = cv2.medianBlur(gray, ksize1) m2 = cv2.medianBlur(gray, ksize2) return m2 - m1 def main(): # 入力画像を読み込み img = cv2.imread("input.jpg") # グレースケール変換 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # DoMフィルタ処理 dst = DoM(gray, 3, 5) # 結果を出力 cv2.imwrite("output.jpg", dst ) if __name__ == "__main__": main()
※動作には、OpenCVライブラリのインストールが必要です。
実行結果
サンプルプログラムの実行結果です。
■入力画像(左)と出力画像(右)
Numbaで高速化
Python用数値計算ライブラリ「NumPy」の配列の要素にfor文でアクセスすると、処理速度が急低下する問題があります。
for文を使わずに「NumPyのメソッド」や「他のライブラリ」で処理を実装するのが鉄則ですがどうしてもfor文を使いたい場合があります。
そんなときは「Numba」ライブラリを使うことで元のソースコードをほとんど弄らずに高速化できます。
ソースコード
サンプルプログラムのソースコードです。
NumPy
#-*- coding:utf-8 -*- import cv2 import numpy as np import time def filter2d(src, kernel, fill_value=-1): # カーネルサイズ m, n = kernel.shape # 畳み込み演算をしない領域の幅 d = int((m-1)/2) h, w = src.shape[0], src.shape[1] # 出力画像用の配列 if fill_value == -1: dst = src.copy() elif fill_value == 0: dst = np.zeros((h, w)) else: dst = np.zeros((h, w)) dst.fill(fill_value) for y in range(d, h - d): for x in range(d, w - d): # 畳み込み演算 dst[y][x] = np.sum(src[y-d:y+d+1, x-d:x+d+1]*kernel) return dst def main(): # 入力画像をグレースケールで読み込み gray = cv2.imread("input2.jpg", 0) # 処理開始時間の計測 start = time.time() # カーネル(縦方向の輪郭検出用) kernel = np.array([[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]) # 方法1(NumPyで実装) dst1 = filter2d(gray, kernel, -1) # 処理終了時刻の計測 end = time.time() # 処理時間の表示 print("処理時間" + str(end-start) + "[sec]") # 結果を出力 cv2.imwrite("output1.jpg", dst1) if __name__ == "__main__": main()
NumPy+Numba
#-*- coding:utf-8 -*- import cv2 import numpy as np import time import numba @numba.jit def filter2d(src, kernel, fill_value=-1): # カーネルサイズ m, n = kernel.shape # 畳み込み演算をしない領域の幅 d = int((m-1)/2) h, w = src.shape[0], src.shape[1] # 出力画像用の配列 if fill_value == -1: dst = src.copy() elif fill_value == 0: dst = np.zeros((h, w)) else: dst = np.zeros((h, w)) dst.fill(fill_value) for y in range(d, h - d): for x in range(d, w - d): # 畳み込み演算 dst[y][x] = np.sum(src[y-d:y+d+1, x-d:x+d+1]*kernel) return dst def main(): # 入力画像をグレースケールで読み込み gray = cv2.imread("input2.jpg", 0) # 処理開始時間の計測 start = time.time() # カーネル(縦方向の輪郭検出用) kernel = np.array([[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]) # 方法1(NumPyで実装) dst1 = filter2d(gray, kernel, -1) # 処理終了時刻の計測 end = time.time() # 処理時間の表示 print("処理時間" + str(end-start) + "[sec]") # 結果を出力 cv2.imwrite("output1.jpg", dst1) if __name__ == "__main__": main()
OpenCV
#-*- coding:utf-8 -*- import cv2 import numpy as np import time def main(): # 入力画像をグレースケールで読み込み gray = cv2.imread("input.jpg", 0) # 処理開始時間の計測 start = time.time() # 方法3(OpenCVで実装) dst3 = cv2.blur(gray, ksize=(3,3)) # 処理終了時刻の計測 end = time.time() # 処理時間の表示 print("処理時間" + str(end-start) + "[sec]") # 結果を出力 cv2.imwrite("output3.jpg", dst3) if __name__ == "__main__": main()
実行結果
結果は以下の通りになりました。
■実行環境
項目 | 説明 |
---|---|
入力画像 | グレースケール画像(440×450[px]) |
OS | Windows10 Home Premium 64bit |
メモリ容量 | 4GB |
CPU | Core i3-2330M 2.20GHz |
■結果
– | 処理速度[sec] |
---|---|
NumPy | 3.2753376960754395[sec] |
NumPy+Numba | 1.225313425064087[sec] |
OpenCV | 0.13230109214782715[sec] |
Numbaを使うと3倍くらい速くなりました。
ただし、今回の画像処理の場合ではOpenCVの方がさらに10倍程度速くなりました。
NumbaはGPUを使うなど色々な設定が出来るので、もうちょっと弄ればより速くなると思います。
コメント