Browse Source

Add of study of edge detection filters

Jérôme BUISINE 8 months ago
parent
commit
eaa4261dc3

+ 0 - 0
analysis/__init__.py


+ 0 - 0
analysis/digital_image_processing/__init__.py


+ 35 - 0
analysis/digital_image_processing/change_contrast.py

@@ -0,0 +1,35 @@
+"""
+Changing contrast with PIL
+
+This algorithm is used in
+https://noivce.pythonanywhere.com/ python web app.
+
+python/black: True
+flake8 : True
+"""
+
+from PIL import Image
+
+
+def change_contrast(img: Image, level: float) -> Image:
+    """
+    Function to change contrast
+    """
+    factor = (259 * (level + 255)) / (255 * (259 - level))
+
+    def contrast(c: int) -> float:
+        """
+        Fundamental Transformation/Operation that'll be performed on
+        every bit.
+        """
+        return 128 + factor * (c - 128)
+
+    return img.point(contrast)
+
+
+if __name__ == "__main__":
+    # Load image
+    with Image.open("image_data/lena.jpg") as img:
+        # Change contrast to 170
+        cont_img = change_contrast(img, 170)
+        cont_img.save("image_data/lena_high_contrast.png", format="png")

+ 0 - 0
analysis/digital_image_processing/edge_detection/__init__.py


+ 117 - 0
analysis/digital_image_processing/edge_detection/canny.py

@@ -0,0 +1,117 @@
+import cv2
+import numpy as np
+from digital_image_processing.filters.convolve import img_convolve
+from digital_image_processing.filters.sobel_filter import sobel_filter
+
+PI = 180
+
+
+def gen_gaussian_kernel(k_size, sigma):
+    center = k_size // 2
+    x, y = np.mgrid[0 - center : k_size - center, 0 - center : k_size - center]
+    g = (
+        1
+        / (2 * np.pi * sigma)
+        * np.exp(-(np.square(x) + np.square(y)) / (2 * np.square(sigma)))
+    )
+    return g
+
+
+def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255):
+    image_row, image_col = image.shape[0], image.shape[1]
+    # gaussian_filter
+    gaussian_out = img_convolve(image, gen_gaussian_kernel(9, sigma=1.4))
+    # get the gradient and degree by sobel_filter
+    sobel_grad, sobel_theta = sobel_filter(gaussian_out)
+    gradient_direction = np.rad2deg(sobel_theta)
+    gradient_direction += PI
+
+    dst = np.zeros((image_row, image_col))
+
+    """
+    Non-maximum suppression. If the edge strength of the current pixel is the largest compared to the other pixels 
+    in the mask with the same direction, the value will be preserved. Otherwise, the value will be suppressed. 
+    """
+    for row in range(1, image_row - 1):
+        for col in range(1, image_col - 1):
+            direction = gradient_direction[row, col]
+
+            if (
+                0 <= direction < 22.5
+                or 15 * PI / 8 <= direction <= 2 * PI
+                or 7 * PI / 8 <= direction <= 9 * PI / 8
+            ):
+                W = sobel_grad[row, col - 1]
+                E = sobel_grad[row, col + 1]
+                if sobel_grad[row, col] >= W and sobel_grad[row, col] >= E:
+                    dst[row, col] = sobel_grad[row, col]
+
+            elif (PI / 8 <= direction < 3 * PI / 8) or (
+                9 * PI / 8 <= direction < 11 * PI / 8
+            ):
+                SW = sobel_grad[row + 1, col - 1]
+                NE = sobel_grad[row - 1, col + 1]
+                if sobel_grad[row, col] >= SW and sobel_grad[row, col] >= NE:
+                    dst[row, col] = sobel_grad[row, col]
+
+            elif (3 * PI / 8 <= direction < 5 * PI / 8) or (
+                11 * PI / 8 <= direction < 13 * PI / 8
+            ):
+                N = sobel_grad[row - 1, col]
+                S = sobel_grad[row + 1, col]
+                if sobel_grad[row, col] >= N and sobel_grad[row, col] >= S:
+                    dst[row, col] = sobel_grad[row, col]
+
+            elif (5 * PI / 8 <= direction < 7 * PI / 8) or (
+                13 * PI / 8 <= direction < 15 * PI / 8
+            ):
+                NW = sobel_grad[row - 1, col - 1]
+                SE = sobel_grad[row + 1, col + 1]
+                if sobel_grad[row, col] >= NW and sobel_grad[row, col] >= SE:
+                    dst[row, col] = sobel_grad[row, col]
+
+            """
+            High-Low threshold detection. If an edge pixel’s gradient value is higher than the high threshold
+            value, it is marked as a strong edge pixel. If an edge pixel’s gradient value is smaller than the high
+            threshold value and larger than the low threshold value, it is marked as a weak edge pixel. If an edge
+            pixel's value is smaller than the low threshold value, it will be suppressed.
+            """
+            if dst[row, col] >= threshold_high:
+                dst[row, col] = strong
+            elif dst[row, col] <= threshold_low:
+                dst[row, col] = 0
+            else:
+                dst[row, col] = weak
+
+    """
+    Edge tracking. Usually a weak edge pixel caused from true edges will be connected to a strong edge pixel while
+    noise responses are unconnected. As long as there is one strong edge pixel that is involved in its 8-connected
+    neighborhood, that weak edge point can be identified as one that should be preserved.
+    """
+    for row in range(1, image_row):
+        for col in range(1, image_col):
+            if dst[row, col] == weak:
+                if 255 in (
+                    dst[row, col + 1],
+                    dst[row, col - 1],
+                    dst[row - 1, col],
+                    dst[row + 1, col],
+                    dst[row - 1, col - 1],
+                    dst[row + 1, col - 1],
+                    dst[row - 1, col + 1],
+                    dst[row + 1, col + 1],
+                ):
+                    dst[row, col] = strong
+                else:
+                    dst[row, col] = 0
+
+    return dst
+
+
+if __name__ == "__main__":
+    # read original image in gray mode
+    lena = cv2.imread(r"../image_data/lena.jpg", 0)
+    # canny edge detection
+    canny_dst = canny(lena)
+    cv2.imshow("canny", canny_dst)
+    cv2.waitKey(0)

+ 0 - 0
analysis/digital_image_processing/filters/__init__.py


+ 49 - 0
analysis/digital_image_processing/filters/convolve.py

@@ -0,0 +1,49 @@
+# @Author  : lightXu
+# @File    : convolve.py
+# @Time    : 2019/7/8 0008 下午 16:13
+from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
+from numpy import array, zeros, ravel, pad, dot, uint8
+
+
+def im2col(image, block_size):
+    rows, cols = image.shape
+    dst_height = cols - block_size[1] + 1
+    dst_width = rows - block_size[0] + 1
+    image_array = zeros((dst_height * dst_width, block_size[1] * block_size[0]))
+    row = 0
+    for i in range(0, dst_height):
+        for j in range(0, dst_width):
+            window = ravel(image[i : i + block_size[0], j : j + block_size[1]])
+            image_array[row, :] = window
+            row += 1
+
+    return image_array
+
+
+def img_convolve(image, filter_kernel):
+    height, width = image.shape[0], image.shape[1]
+    k_size = filter_kernel.shape[0]
+    pad_size = k_size // 2
+    # Pads image with the edge values of array.
+    image_tmp = pad(image, pad_size, mode="edge")
+
+    # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows
+    image_array = im2col(image_tmp, (k_size, k_size))
+
+    #  turn the kernel into shape(k*k, 1)
+    kernel_array = ravel(filter_kernel)
+    # reshape and get the dst image
+    dst = dot(image_array, kernel_array).reshape(height, width)
+    return dst
+
+
+if __name__ == "__main__":
+    # read original image
+    img = imread(r"../image_data/lena.jpg")
+    # turn image in gray scale value
+    gray = cvtColor(img, COLOR_BGR2GRAY)
+    # Laplace operator
+    Laplace_kernel = array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
+    out = img_convolve(gray, Laplace_kernel).astype(uint8)
+    imshow("Laplacian", out)
+    waitKey(0)

+ 53 - 0
analysis/digital_image_processing/filters/gaussian_filter.py

@@ -0,0 +1,53 @@
+"""
+Implementation of gaussian filter algorithm
+"""
+from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
+from numpy import pi, mgrid, exp, square, zeros, ravel, dot, uint8
+
+
+def gen_gaussian_kernel(k_size, sigma):
+    center = k_size // 2
+    x, y = mgrid[0 - center : k_size - center, 0 - center : k_size - center]
+    g = 1 / (2 * pi * sigma) * exp(-(square(x) + square(y)) / (2 * square(sigma)))
+    return g
+
+
+def gaussian_filter(image, k_size, sigma):
+    height, width = image.shape[0], image.shape[1]
+    # dst image height and width
+    dst_height = height - k_size + 1
+    dst_width = width - k_size + 1
+
+    # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows
+    image_array = zeros((dst_height * dst_width, k_size * k_size))
+    row = 0
+    for i in range(0, dst_height):
+        for j in range(0, dst_width):
+            window = ravel(image[i : i + k_size, j : j + k_size])
+            image_array[row, :] = window
+            row += 1
+
+    #  turn the kernel into shape(k*k, 1)
+    gaussian_kernel = gen_gaussian_kernel(k_size, sigma)
+    filter_array = ravel(gaussian_kernel)
+
+    # reshape and get the dst image
+    dst = dot(image_array, filter_array).reshape(dst_height, dst_width).astype(uint8)
+
+    return dst
+
+
+if __name__ == "__main__":
+    # read original image
+    img = imread(r"../image_data/lena.jpg")
+    # turn image in gray scale value
+    gray = cvtColor(img, COLOR_BGR2GRAY)
+
+    # get values with two different mask size
+    gaussian3x3 = gaussian_filter(gray, 3, sigma=1)
+    gaussian5x5 = gaussian_filter(gray, 5, sigma=0.8)
+
+    # show result images
+    imshow("gaussian filter with 3x3 mask", gaussian3x3)
+    imshow("gaussian filter with 5x5 mask", gaussian5x5)
+    waitKey()

+ 42 - 0
analysis/digital_image_processing/filters/median_filter.py

@@ -0,0 +1,42 @@
+"""
+Implementation of median filter algorithm
+"""
+
+from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
+from numpy import zeros_like, ravel, sort, multiply, divide, int8
+
+
+def median_filter(gray_img, mask=3):
+    """
+    :param gray_img: gray image
+    :param mask: mask size
+    :return: image with median filter
+    """
+    # set image borders
+    bd = int(mask / 2)
+    # copy image size
+    median_img = zeros_like(gray_img)
+    for i in range(bd, gray_img.shape[0] - bd):
+        for j in range(bd, gray_img.shape[1] - bd):
+            # get mask according with mask
+            kernel = ravel(gray_img[i - bd : i + bd + 1, j - bd : j + bd + 1])
+            # calculate mask median
+            median = sort(kernel)[int8(divide((multiply(mask, mask)), 2) + 1)]
+            median_img[i, j] = median
+    return median_img
+
+
+if __name__ == "__main__":
+    # read original image
+    img = imread("../image_data/lena.jpg")
+    # turn image in gray scale value
+    gray = cvtColor(img, COLOR_BGR2GRAY)
+
+    # get values with two different mask size
+    median3x3 = median_filter(gray, 3)
+    median5x5 = median_filter(gray, 5)
+
+    # show result images
+    imshow("median filter with 3x3 mask", median3x3)
+    imshow("median filter with 5x5 mask", median5x5)
+    waitKey(0)

+ 38 - 0
analysis/digital_image_processing/filters/sobel_filter.py

@@ -0,0 +1,38 @@
+# @Author  : lightXu
+# @File    : sobel_filter.py
+# @Time    : 2019/7/8 0008 下午 16:26
+import numpy as np
+from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey
+from digital_image_processing.filters.convolve import img_convolve
+
+
+def sobel_filter(image):
+    kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
+    kernel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
+
+    dst_x = np.abs(img_convolve(image, kernel_x))
+    dst_y = np.abs(img_convolve(image, kernel_y))
+    # modify the pix within [0, 255]
+    dst_x = dst_x * 255 / np.max(dst_x)
+    dst_y = dst_y * 255 / np.max(dst_y)
+
+    dst_xy = np.sqrt((np.square(dst_x)) + (np.square(dst_y)))
+    dst_xy = dst_xy * 255 / np.max(dst_xy)
+    dst = dst_xy.astype(np.uint8)
+
+    theta = np.arctan2(dst_y, dst_x)
+    return dst, theta
+
+
+if __name__ == "__main__":
+    # read original image
+    img = imread("../image_data/lena.jpg")
+    # turn image in gray scale value
+    gray = cvtColor(img, COLOR_BGR2GRAY)
+
+    sobel_grad, sobel_theta = sobel_filter(gray)
+
+    # show result images
+    imshow("sobel filter", sobel_grad)
+    imshow("sobel theta", sobel_theta)
+    waitKey(0)

BIN
analysis/digital_image_processing/image_data/lena.jpg


BIN
analysis/digital_image_processing/image_data/lena_small.jpg


+ 62 - 0
analysis/digital_image_processing/test_digital_image_processing.py

@@ -0,0 +1,62 @@
+"""
+PyTest's for Digital Image Processing
+"""
+
+import digital_image_processing.edge_detection.canny as canny
+import digital_image_processing.filters.gaussian_filter as gg
+import digital_image_processing.filters.median_filter as med
+import digital_image_processing.filters.sobel_filter as sob
+import digital_image_processing.filters.convolve as conv
+import digital_image_processing.change_contrast as cc
+from cv2 import imread, cvtColor, COLOR_BGR2GRAY
+from numpy import array, uint8
+from PIL import Image
+
+img = imread(r"digital_image_processing/image_data/lena_small.jpg")
+gray = cvtColor(img, COLOR_BGR2GRAY)
+
+# Test: change_contrast()
+def test_change_contrast():
+    with Image.open("digital_image_processing/image_data/lena_small.jpg") as img:
+        # Work around assertion for response
+        assert str(cc.change_contrast(img, 110)).startswith(
+            "<PIL.Image.Image image mode=RGB size=100x100 at"
+        )
+
+
+# canny.gen_gaussian_kernel()
+def test_gen_gaussian_kernel():
+    resp = canny.gen_gaussian_kernel(9, sigma=1.4)
+    # Assert ambiguous array
+    assert resp.all()
+
+
+# canny.py
+def test_canny():
+    canny_img = imread("digital_image_processing/image_data/lena_small.jpg", 0)
+    # assert ambiguos array for all == True
+    assert canny_img.all()
+    canny_array = canny.canny(canny_img)
+    # assert canny array for at least one True
+    assert canny_array.any()
+
+
+# filters/gaussian_filter.py
+def test_gen_gaussian_kernel_filter():
+    assert gg.gaussian_filter(gray, 5, sigma=0.9).all()
+
+
+def test_convolve_filter():
+    # laplace diagonals
+    Laplace = array([[0.25, 0.5, 0.25], [0.5, -3, 0.5], [0.25, 0.5, 0.25]])
+    res = conv.img_convolve(gray, Laplace).astype(uint8)
+    assert res.any()
+
+
+def test_median_filter():
+    assert med.median_filter(gray, 3).any()
+
+
+def test_sobel_filter():
+    grad, theta = sob.sobel_filter(gray)
+    assert grad.any() and theta.any()

File diff suppressed because it is too large
+ 1380 - 0
analysis/edge_detection_analysis.ipynb