-
Notifications
You must be signed in to change notification settings - Fork 6
/
convolution.py
106 lines (83 loc) · 4.89 KB
/
convolution.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import numpy as np
from typing import List, Tuple, Union
def add_padding(matrix: np.ndarray,
padding: Tuple[int, int]) -> np.ndarray:
"""Adds padding to the matrix.
Args:
matrix (np.ndarray): Matrix that needs to be padded. Type is List[List[float]] casted to np.ndarray.
padding (Tuple[int, int]): Tuple with number of rows and columns to be padded. With the `(r, c)` padding we addding `r` rows to the top and bottom and `c` columns to the left and to the right of the matrix
Returns:
np.ndarray: Padded matrix with shape `n + 2 * r, m + 2 * c`.
"""
n, m = matrix.shape
r, c = padding
padded_matrix = np.zeros((n + r * 2, m + c * 2))
padded_matrix[r : n + r, c : m + c] = matrix
return padded_matrix
def _check_params(matrix, kernel, stride, dilation, padding):
params_are_correct = (isinstance(stride[0], int) and isinstance(stride[1], int) and
isinstance(dilation[0], int) and isinstance(dilation[1], int) and
isinstance(padding[0], int) and isinstance(padding[1], int) and
stride[0] >= 1 and stride[1] >= 1 and
dilation[0] >= 1 and dilation[1] >= 1 and
padding[0] >= 0 and padding[1] >= 0)
assert params_are_correct, 'Parameters should be integers equal or greater than default values.'
if not isinstance(matrix, np.ndarray):
matrix = np.array(matrix)
n, m = matrix.shape
matrix = matrix if list(padding) == [0, 0] else add_padding(matrix, padding)
n_p, m_p = matrix.shape
if not isinstance(kernel, np.ndarray):
kernel = np.array(kernel)
k = kernel.shape
kernel_is_correct = k[0] % 2 == 1 and k[1] % 2 == 1
assert kernel_is_correct, 'Kernel shape should be odd.'
matrix_to_kernel_is_correct = n_p >= k[0] and m_p >= k[1]
assert matrix_to_kernel_is_correct, 'Kernel can\'t be bigger than matrix in terms of shape.'
h_out = np.floor((n + 2 * padding[0] - k[0] - (k[0] - 1) * (dilation[0] - 1)) / stride[0]).astype(int) + 1
w_out = np.floor((m + 2 * padding[1] - k[1] - (k[1] - 1) * (dilation[1] - 1)) / stride[1]).astype(int) + 1
out_dimensions_are_correct = h_out > 0 and w_out > 0
assert out_dimensions_are_correct, 'Can\'t apply input parameters, one of resulting output dimension is non-positive.'
return matrix, kernel, k, h_out, w_out
def conv2d(matrix: Union[List[List[float]], np.ndarray],
kernel: Union[List[List[float]], np.ndarray],
stride: Tuple[int, int] = (1, 1),
dilation: Tuple[int, int] = (1, 1),
padding: Tuple[int, int] = (0, 0)) -> np.ndarray:
"""Makes a 2D convolution with the kernel over matrix using defined stride, dilation and padding along axes.
Args:
matrix (Union[List[List[float]], np.ndarray]): 2D matrix to be convolved.
kernel (Union[List[List[float]], np.ndarray]): 2D odd-shaped matrix (e.g. 3x3, 5x5, 13x9, etc.).
stride (Tuple[int, int], optional): Tuple of the stride along axes. With the `(r, c)` stride we move on `r` pixels along rows and on `c` pixels along columns on each iteration. Defaults to (1, 1).
dilation (Tuple[int, int], optional): Tuple of the dilation along axes. With the `(r, c)` dilation we distancing adjacent pixels in kernel by `r` along rows and `c` along columns. Defaults to (1, 1).
padding (Tuple[int, int], optional): Tuple with number of rows and columns to be padded. Defaults to (0, 0).
Returns:
np.ndarray: 2D Feature map, i.e. matrix after convolution.
"""
matrix, kernel, k, h_out, w_out = _check_params(matrix, kernel, stride, dilation, padding)
matrix_out = np.zeros((h_out, w_out))
b = k[0] // 2, k[1] // 2
center_x_0 = b[0] * dilation[0]
center_y_0 = b[1] * dilation[1]
for i in range(h_out):
center_x = center_x_0 + i * stride[0]
indices_x = [center_x + l * dilation[0] for l in range(-b[0], b[0] + 1)]
for j in range(w_out):
center_y = center_y_0 + j * stride[1]
indices_y = [center_y + l * dilation[1] for l in range(-b[1], b[1] + 1)]
submatrix = matrix[indices_x, :][:, indices_y]
matrix_out[i][j] = np.sum(np.multiply(submatrix, kernel))
return matrix_out
def apply_filter_to_image(image: np.ndarray,
kernel: List[List[float]]) -> np.ndarray:
"""Applies filter to the given image.
Args:
image (np.ndarray): 3D matrix to be convolved. Shape must be in HWC format.
kernel (List[List[float]]): 2D odd-shaped matrix (e.g. 3x3, 5x5, 13x9, etc.).
Returns:
np.ndarray: image after applying kernel.
"""
kernel = np.asarray(kernel)
b = kernel.shape
return np.dstack([conv2d(image[:, :, z], kernel, padding=(b[0]//2, b[1]//2))
for z in range(3)]).astype('uint8')