山内セミナーⅠ(2020/07/08)

関連サイトと資料

サンプル画像


color_spaces.png


lena_image.png


cat-face.png


cat.jpg


lenna.png


cat.png


lenna_250.png


opencv_binary_logo_250.png

morpho_test_imgs.zip

Common kernels in image processing

カーネルが結果のイメージに大きな影響を与えることがわかりました。 filter_2D_kernels.pyスクリプトには、エッジ検出、スムージング、シャープニング、エンボスなど、 さまざまな目的で使用するいくつかの共通カーネルが定義されています。 注意として、特定のカーネルを適用するには、cv2.filter2D()関数を使用する必要があります。 このスクリプトの出力を次のスクリーンショットに示します。

特定のカーネルを適用するために使用できるcv2.filter2D()関数を使用して、 異なるカーネルを適用した効果を確認できます。

filter2D_kernels.py
import cv2
import numpy as np
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(3, 4, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
plt.figure(figsize=(12, 6))
plt.suptitle("Comparing different kernels using cv2.filter2D()", fontsize=14, fontweight='bold')
	
image = cv2.imread('cat-face.png')
	
kernel_identity = np.array([[0, 0, 0],
                            [0, 1, 0],
                            [0, 0, 0]])
	
kernel_edge_detection_1 = np.array([[1, 0, -1],
                                    [0, 0, 0],
                                    [-1, 0, 1]])
	
kernel_edge_detection_2 = np.array([[0, 1, 0],
                                    [1, -4, 1],
                                    [0, 1, 0]])
	
kernel_edge_detection_3 = np.array([[-1, -1, -1],
                                    [-1, 8, -1],
                                    [-1, -1, -1]])
	
kernel_sharpen = np.array([[0, -1, 0],
                           [-1, 5, -1],
                           [0, -1, 0]])
	
kernel_unsharp_masking = -1 / 256 * np.array([[1, 4, 6, 4, 1],
                                              [4, 16, 24, 16, 4],
                                              [6, 24, -476, 24, 6],
                                              [4, 16, 24, 16, 4],
                                              [1, 4, 6, 4, 1]])
	
kernel_blur = 1 / 9 * np.array([[1, 1, 1],
                                [1, 1, 1],
                                [1, 1, 1]])
	
gaussian_blur = 1 / 16 * np.array([[1, 2, 1],
                                   [2, 4, 2],
                                   [1, 2, 1]])
	
kernel_emboss = np.array([[-2, -1, 0],
                          [-1, 1, 1],
                          [0, 1, 2]])
	
sobel_x_kernel = np.array([[1, 0, -1],
                           [2, 0, -2],
                           [1, 0, -1]])
	
sobel_y_kernel = np.array([[1, 2, 1],
                           [0, 0, 0],
                           [-1, -2, -1]])
	
outline_kernel = np.array([[-1, -1, -1],
                           [-1, 8, -1],
                           [-1, -1, -1]])
	
original_image = cv2.filter2D(image, -1, kernel_identity)
edge_image_1 = cv2.filter2D(image, -1, kernel_edge_detection_1)
edge_image_2 = cv2.filter2D(image, -1, kernel_edge_detection_2)
edge_image_3 = cv2.filter2D(image, -1, kernel_edge_detection_3)
sharpen_image = cv2.filter2D(image, -1, kernel_sharpen)
unsharp_masking_image = cv2.filter2D(image, -1, kernel_unsharp_masking)
blur_image = cv2.filter2D(image, -1, kernel_blur)
gaussian_blur_image = cv2.filter2D(image, -1, gaussian_blur)
emboss_image = cv2.filter2D(image, -1, kernel_emboss)
sobel_x_image = cv2.filter2D(image, -1, sobel_x_kernel)
sobel_y_image = cv2.filter2D(image, -1, sobel_y_kernel)
outline_image = cv2.filter2D(image, -1, outline_kernel)
	
show_with_matplotlib(original_image, "identity kernel", 1)
show_with_matplotlib(edge_image_1, "edge detection 1", 2)
show_with_matplotlib(edge_image_2, "edge detection 2", 3)
show_with_matplotlib(edge_image_3, "edge detection 3", 4)
show_with_matplotlib(sharpen_image, "sharpen", 5)
show_with_matplotlib(unsharp_masking_image, "unsharp masking", 6)
show_with_matplotlib(blur_image, "blur image", 7)
show_with_matplotlib(gaussian_blur_image, "gaussian blur image", 8)
show_with_matplotlib(emboss_image, "emboss image", 9)
show_with_matplotlib(sobel_x_image, "sobel x image", 10)
show_with_matplotlib(sobel_y_image, "sobel y image", 11)
show_with_matplotlib(outline_image, "outline image", 12)
	
plt.show()
    

Creating cartoonized images

前述のように、cv2.bilateralFilter()を適用して、シャープなエッジを維持しながらノイズを減らすことができます。 ただし、このフィルターは、フィルター処理された画像に強度のプラトー(階段効果)と偽のエッジ(勾配の反転)の両方を生成できます。 これはフィルター処理された画像で考慮することができますが(これらのアーティファクトを処理するバイラテラルフィルターにはいくつかの改善点があります)、 漫画化された画像を作成するのは非常にクールです。 完全なコードは、cartoonizing.pyで見ることができますが、このセクションでは、簡単な説明を見ていきます。

画像を漫画化するプロセスは非常に単純で、cartonize_image()関数で実行されます。 最初に、画像のエッジに基づいて、画像のスケッチが作成されます(sketch_image()関数を参照)。 使用するエッジ検出器は他にもありますが、この場合はラプラシアン演算子が使用されます。 cv2.Laplacian()関数を呼び出す前に、cv2.medianBlur()メディアンフィルターを使用して画像を平滑化することにより、 ノイズを低減します。エッジが取得されると、cv2.threshold()を適用して結果の画像にしきい値が適用されます。 次の章でしきい値処理手法について説明しますが、この例では、この関数は、sketch_image()関数の出力に対応して、 指定されたグレースケールイメージからバイナリイメージを提供します。 この値が結果の画像に表示される(検出されたエッジに対応する)黒ピクセルの数をどのように制御するかを確認するために、 しきい値(この場合は70に固定)で遊ぶことができます。 この値が小さい場合(たとえば、10)、多くの黒い境界線ピクセルが表示されます。 この値が大きい場合(たとえば、200)、黒い境界線のピクセルはほとんど出力されません。 漫画化された効果を得るには、大きな値でcv2.bilateralFilter()関数を呼び出します(たとえば、cv2.bilateralFilter(img、10、250、250))。 最後のステップは、cv2.bitwise_and()を使用してスケッチ画像とバイラテラルフィルターの出力をまとめ、 これらの値を出力に設定するためにスケッチ画像をマスクとして使用することです。 必要に応じて、出力をグレースケールに変換することもできます。 cv2.bitwise_and()関数はビットごとの演算であり、 次のセクションで説明することに注意してください。

完全を期すために、OpenCVは同様の機能を提供し、このスクリプトでもテストされています。 次のフィルターでも同様の効果が得られます。

cartoonizing.pyスクリプトに対応する出力は、次のスクリーンショットに示されています。

ご覧のように、cartonize_image()関数は、cv2.cvtColor()を呼び出して画像をBGRからグレースケールに変換するグレースケール画像を出力することもできます。

cartoonizing.py
import cv2
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(2, 4, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
def sketch_image(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_gray = cv2.medianBlur(img_gray, 5)
    edges = cv2.Laplacian(img_gray, cv2.CV_8U, ksize=5)
    ret, thresholded = cv2.threshold(edges, 70, 255, cv2.THRESH_BINARY_INV)
    return thresholded
	
def cartonize_image(img, gray_mode=False):
    thresholded = sketch_image(img)
    filtered = cv2.bilateralFilter(img, 10, 250, 250)
    cartoonized = cv2.bitwise_and(filtered, filtered, mask=thresholded)
    if gray_mode:
        return cv2.cvtColor(cartoonized, cv2.COLOR_BGR2GRAY)
	
    return cartoonized
	
plt.figure(figsize=(14, 6))
plt.suptitle("Cartoonizing images", fontsize=14, fontweight='bold')
	
image = cv2.imread('cat.jpg')
	
custom_sketch_image = sketch_image(image)
custom_cartonized_image = cartonize_image(image)
custom_cartonized_image_gray = cartonize_image(image, True)
	
sketch_gray, sketch_color = cv2.pencilSketch(image, sigma_s=30, sigma_r=0.1, shade_factor=0.1)
stylizated_image = cv2.stylization(image, sigma_s=60, sigma_r=0.07)
	
show_with_matplotlib(image, "image", 1)
show_with_matplotlib(cv2.cvtColor(custom_sketch_image, cv2.COLOR_GRAY2BGR), "custom sketch", 2)
show_with_matplotlib(cv2.cvtColor(sketch_gray, cv2.COLOR_GRAY2BGR), "sketch gray cv2.pencilSketch()", 3)
show_with_matplotlib(sketch_color, "sketch color cv2.pencilSketch()", 4)
show_with_matplotlib(stylizated_image, "cartoonized cv2.stylization()", 5)
show_with_matplotlib(custom_cartonized_image, "custom cartoonized", 6)
show_with_matplotlib(cv2.cvtColor(custom_cartonized_image_gray, cv2.COLOR_GRAY2BGR), "custom cartoonized gray", 7)
	
plt.show()
    

Arithmetic with images

このセクションでは、ビット演算、加算、減算など、画像に対して実行できるいくつかの一般的な算術演算について学びます。 これらの演算に関連して、考慮すべき重要な点の1つは、次のサブセクションで説明する飽和演算の概念です。

Saturation arithmetic

飽和算術は、算術演算の一種で、演算が取り得る最大値と最小値を制限することにより、 演算が固定範囲に制限されます。 たとえば、画像に対する特定の操作(たとえば、色空間の変換、補間手法など)により、 使用可能な範囲外の値が生成されることがあります。 これを解決するために、飽和演算が使用されます。

たとえば、8ビットイメージ(値の範囲が0〜255)に対して特定の操作を実行した結果であるrを格納するには、 次の方程式が適用されます。

\( I(x,y) = min(max(round(r), 0), 255) \)

この概念は、次のsaturation_arithmetic.pyスクリプトで見ることができます:

x = np.uint8([250])
y = np.uint8([50])
  
# 250+50 = 300 => 255:
result_opencv = cv2.add(x, y)
print("cv2.add(x:'{0}' , y:'{1}') = '{2}'".format(x, y, result_opencv))
   
# 250+50 = 300 % 256 = 44
result_numpy = x + y
print("x:'{0}' + y:'{1}' = '{2}'".format(x, y, result_numpy))
    

OpenCVでは、値がクリップされて、[0、255]の範囲から外れないようにします。 これを飽和動作と呼びます。 NumPyでは、値はラップされます。 これはモジュロ演算とも呼ばれます。

Image addition and subtraction

画像の加算と減算は、それぞれcv2.add()関数とcv2.subtract()関数で実行できます。 これらの関数は、2つの配列の要素ごとの合計/減算を合計/減算します。 これらの関数は、配列とスカラーの合計/減算にも使用できます。 たとえば、画像のすべてのピクセルに60を追加する場合は、最初に次のコードを使用して、 元の画像に追加する画像を作成する必要があります。

M = np.ones(image.shape, dtype="uint8") * 60
    

次に、次のコードを使用して追加を実行します。

added_image = cv2.add(image, M)
    

別の可能性は、スカラーを作成し、それを元の画像に追加することです。 たとえば、画像のすべてのピクセルに110を追加する場合、 最初に次のコードを使用してスカラーを作成する必要があります。

scalar = np.ones((1, 3), dtype="float") * 110
    

次に、次のコードを使用して追加を実行します。

added_image_2 = cv2.add(image, scalar)
    

減算の場合も手順は同じですが、代わりにcv2.subtract()関数を呼び出します。 このスクリプトの完全なコードは、math.pyで確認できます。 このスクリプトの出力は、次のスクリーンショットで確認できます。

上のスクリーンショットでは、 事前定義された値(2つの異なる方法で計算されますが、同じ結果を示しています)を加算および減算した効果をはっきりと見ることができます。 値を追加すると画像は明るくなり、値を減算すると画像は暗くなります。

arithmetic.py
import numpy as np
import cv2
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(2, 3, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
plt.figure(figsize=(12, 6))
plt.suptitle("Arithmetic with images", fontsize=14, fontweight='bold')
	
image = cv2.imread('lenna.png')
	
M = np.ones(image.shape, dtype="uint8") * 60
added_image = cv2.add(image, M)
subtracted_image = cv2.subtract(image, M)
	
scalar = np.ones((1, 3), dtype="float") * 110
added_image_2 = cv2.add(image, scalar)
subtracted_image_2 = cv2.subtract(image, scalar)
	
show_with_matplotlib(image, "image", 1)
show_with_matplotlib(added_image, "added 60 (image + image)", 2)
show_with_matplotlib(subtracted_image, "subtracted 60 (image - images)", 3)
show_with_matplotlib(added_image_2, "added 110 (image + scalar)", 5)
show_with_matplotlib(subtracted_image_2, "subtracted 110 (image - scalar)", 6)
	
plt.show()
    

Image blending

画像のブレンドは画像の追加でもありますが、画像には異なる重みが与えられ、透明感を与えます。 これを行うには、cv2.addWeighted()関数を使用します。 この関数は通常、Sobelオペレーターから出力を取得するために使用されます。

Sobelオペレーターは、エッジを強調する画像を作成するエッジ検出に使用されます。 Sobelオペレーターは、次のコードに示すように、2つの3×3カーネルを使用します。 これらのカーネルは、導関数の近似を計算し、水平方向と垂直方向の両方の変化をキャプチャするために、 元の画像と畳み込まれます。

# Gradient x is calculated:
# the depth of the output is set to CV_16S to avoid overflow
# CV_16S = one channel of 2-byte signed integers (16-bit signed integers)
gradient_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0, 3)
gradient_y = cv2.Sobel(gray_image, cv2.CV_16S, 0, 1, 3)
    

したがって、水平方向および垂直方向の変化が計算された後、次のように、前述の関数を使用してそれらを画像にブレンドできます。

# Conversion to an unsigned 8-bit type:
abs_gradient_x = cv2.convertScaleAbs(gradient_x)
abs_gradient_y = cv2.convertScaleAbs(gradient_y)
# Combine the two images using the same weight:
sobel_image = cv2.addWeighted(abs_gradient_x, 0.5, abs_gradient_y, 0.5, 0)
    

これは、arithmetic_sobel.pyスクリプトで確認できます。 このスクリプトの出力は、次のスクリーンショットで確認できます。

上記のスクリーンショットでは、Sobelオペレーターの出力が、水平方向と垂直方向の両方の変化を含めて表示されています。

arithmetic_sobel.py
import cv2
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(1, 4, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
plt.figure(figsize=(10, 4))
plt.suptitle("Sobel operator and cv2.addWeighted() to show the output", fontsize=14, fontweight='bold')
	
image = cv2.imread('lenna.png')
	
image_filtered = cv2.GaussianBlur(image, (3, 3), 0)
gray_image = cv2.cvtColor(image_filtered, cv2.COLOR_BGR2GRAY)
gradient_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0, 3)
gradient_y = cv2.Sobel(gray_image, cv2.CV_16S, 0, 1, 3)
abs_gradient_x = cv2.convertScaleAbs(gradient_x)
abs_gradient_y = cv2.convertScaleAbs(gradient_y)
sobel_image = cv2.addWeighted(abs_gradient_x, 0.5, abs_gradient_y, 0.5, 0)
	
show_with_matplotlib(image, "Image", 1)
show_with_matplotlib(cv2.cvtColor(abs_gradient_x, cv2.COLOR_GRAY2BGR), "Gradient x", 2)
show_with_matplotlib(cv2.cvtColor(abs_gradient_y, cv2.COLOR_GRAY2BGR), "Gradient y", 3)
show_with_matplotlib(cv2.cvtColor(sobel_image, cv2.COLOR_GRAY2BGR), "Sobel output", 4)
	
plt.show()
    

Bitwise operations

比較および計算のために値を操作するために使用できるビット単位演算子を使用してビットレベルで実行できるいくつかの操作があります。 これらのビット単位の演算は単純で、計算が高速です。 これは、画像を操作するときに便利なツールであることを意味します。

ビット演算には、AND、OR、NOT、XORがあります。

これらの操作がどのように機能するかを説明するために、次のスクリーンショットのbitwise_operations.pyスクリプトの出力を見てください。


bitwise_operations.py
import cv2
import numpy as np
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(3, 3, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
plt.figure(figsize=(12, 6))
plt.suptitle("Bitwise operations (AND, OR, XOR, NOT)", fontsize=14, fontweight='bold')
	
img_1 = np.zeros((300, 300), dtype="uint8")
cv2.rectangle(img_1, (10, 10), (110, 110), (255, 255, 255), -1)
cv2.circle(img_1, (200, 200), 50, (255, 255, 255), -1)
	
img_2 = np.zeros((300, 300), dtype="uint8")
cv2.rectangle(img_2, (50, 50), (150, 150), (255, 255, 255), -1)
cv2.circle(img_2, (225, 200), 50, (255, 255, 255), -1)
	
bitwise_or = cv2.bitwise_or(img_1, img_2)
bitwise_and = cv2.bitwise_and(img_1, img_2)
bitwise_xor = cv2.bitwise_xor(img_1, img_2)
bitwise_not_1 = cv2.bitwise_not(img_1)
bitwise_not_2 = cv2.bitwise_not(img_2)
	
show_with_matplotlib(cv2.cvtColor(img_1, cv2.COLOR_GRAY2BGR), "image 1", 1)
show_with_matplotlib(cv2.cvtColor(img_2, cv2.COLOR_GRAY2BGR), "image 2", 2)
show_with_matplotlib(cv2.cvtColor(bitwise_or, cv2.COLOR_GRAY2BGR), "image 1 OR image 2", 3)
show_with_matplotlib(cv2.cvtColor(bitwise_and, cv2.COLOR_GRAY2BGR), "image 1 AND image 2", 4)
show_with_matplotlib(cv2.cvtColor(bitwise_xor, cv2.COLOR_GRAY2BGR), "image 1 XOR image 2", 5)
show_with_matplotlib(cv2.cvtColor(bitwise_not_1, cv2.COLOR_GRAY2BGR), "NOT (image 1)", 6)
show_with_matplotlib(cv2.cvtColor(bitwise_not_2, cv2.COLOR_GRAY2BGR), "NOT (image 2)", 7)
	
image = cv2.imread('cat.png')
img_3 = np.zeros((300, 300), dtype="uint8")
cv2.circle(img_3, (150, 150), 150, (255, 255, 255), -1)
bitwise_and_example = cv2.bitwise_and(image, image, mask=img_3)
	
show_with_matplotlib(cv2.cvtColor(img_3, cv2.COLOR_GRAY2BGR), "image 3", 8)
show_with_matplotlib(bitwise_and_example, "image 3 AND a loaded image", 9)
	
plt.show()
    

さらにビット演算を行うには、次のbitwise_operations_images.pyスクリプトを見てください。 このスクリプトでは、2つの画像が読み込まれ、いくつかのビット演算(ANDおよびOR)が実行されます。 画像は同じ形でなければならないことに注意してください:

# Load the original image (250x250):
image = cv2.imread('lenna_250.png')
   
# Load the binary image (but as a GBR color image - with 3 channels) (250x250):
binary_image = cv2.imread('opencv_binary_logo_250.png')
  
# Bitwise AND
bitwise_and = cv2.bitwise_and(image, binary_image)
  
# Bitwise OR
bitwise_or = cv2.bitwise_or(image, binary_image)
    

次のスクリーンショットで出力を確認できます。

前のスクリーンショットでは、ビットごとの演算(AND、OR)を実行したときに生成される画像を確認できます。

bitwise_operations_images.py
import cv2
import matplotlib.pyplot as plt
	
def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(2, 2, pos)
    plt.imshow(img_RGB)
    plt.title(title)
    plt.axis('off')
	
plt.figure(figsize=(6, 5))
plt.suptitle("Bitwise AND/OR between two images", fontsize=14, fontweight='bold')
	
image = cv2.imread('lenna_250.png')
binary_image = cv2.imread('opencv_binary_logo_250.png')
	
bitwise_and = cv2.bitwise_and(image, binary_image)
bitwise_or = cv2.bitwise_or(image, binary_image)
	
show_with_matplotlib(image, "image", 1)
show_with_matplotlib(binary_image, "binary logo", 2)
show_with_matplotlib(bitwise_and, "AND operation", 3)
show_with_matplotlib(bitwise_or, "OR operation", 4)
	
plt.show()