【Python/OpenCV】積分画像、DoG、DoMで高速化

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]]
【Python/OpenCV】積分画像、DoG、DoMで高速化
Python版OpenCVで積分画像、DoG、DoM、Numbaで高速化する方法をソースコード付きで解説します。

【ぼかし・輪郭検出】DoGフィルタ

DoG(Difference of Gaussian)とは、 \sigmaの値が異なる2つのガウシアンフィルタ画像の差分です。
DoGフィルタは、LoGフィルタに近似できます。
そして、計算量も小さいため、LoGフィルタの代わりなどでよく用いられます。
【詳細】DoGフィルタの原理・特徴・計算式

今回は、Python言語とOpenCVを用いてDoGフィルタを実装してみました。

書式

dst = cv2.GaussianBlur(src, ksize, sigmaX)
パラメータ名 説明
src 入力画像
ksize カーネルサイズ
sigmaX ガウス分布の\sigma_x
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ライブラリのインストールが必要です。

実行結果

サンプルプログラムの実行結果です。

■入力画像(左)と出力画像(右)

【Python/OpenCV】DoGフィルタでぼかし・輪郭抽出
この記事では、Python版OpenCVでDoGフィルタを実装し、画像をぼかして輪郭を抽出する方法をソースコード付きで解説します。

【ぼかし・輪郭検出】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ライブラリのインストールが必要です。

実行結果

サンプルプログラムの実行結果です。

■入力画像(左)と出力画像(右)

【Python/OpenCV】DoMフィルタでぼかし・輪郭抽出
この記事では、Python版OpenCVでDoMフィルタを実装し、画像をぼかして輪郭を抽出する方法をソースコード付きで解説します。

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を使うなど色々な設定が出来るので、もうちょっと弄ればより速くなると思います。

【Numba】NumPy + for文の高速化
この記事では、Pythonライブラリ「Numba」でNumPy配列とfor文の処理速度を高速化してみたので紹介します。
【Python版OpenCV超入門】使い方とサンプルコードを解説
Python版OpenCVで画像処理プログラミングを行う方法を入門者向けにソースコード付きで解説するページです。

コメント