Skip to content

Commit 841cb11

Browse files
rzalawadleios
andauthored
Convolutions of Images 2d Python implementation (#819)
* Added python code for convolutions 2d. Modified 2d.md file to include added python code. * Updated list comprehension code to numpy code. Changed if statements to not use escape character * fixed minor errors * solved minor errors * Changed circle size. Removed test values for x and y * Update contents/convolutions/2d/2d.md Co-authored-by: James Schloss <jrs.schloss@gmail.com> * Update contents/convolutions/2d/2d.md Co-authored-by: James Schloss <jrs.schloss@gmail.com> * added name to contributors.md * resolved conflict * removed name from contributors.md. will add next commit * added name to contributors.md Co-authored-by: James Schloss <jrs.schloss@gmail.com>
1 parent 2797d85 commit 841cb11

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her
5656
- Ishaan Verma
5757
- Delphi1024
5858
- ntindle
59+
- Ridham177

contents/convolutions/2d/2d.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ In code, a two-dimensional convolution might look like this:
2121
{% method %}
2222
{% sample lang="jl" %}
2323
[import:4-28, lang:"julia"](../code/julia/2d_convolution.jl)
24+
{% sample lang="py" %}
25+
[import:5-19, lang:"python"](../code/python/2d_convolution.py)
2426
{% endmethod %}
2527

2628
This is very similar to what we have shown in previous sections; however, it essentially requires four iterable dimensions because we need to iterate through each axis of the output domain *and* the filter.
@@ -48,6 +50,8 @@ At this stage, it is important to write some code, so we will generate a simple
4850
{% method %}
4951
{% sample lang="jl" %}
5052
[import:30-47, lang:"julia"](../code/julia/2d_convolution.jl)
53+
{% sample lang="py" %}
54+
[import:21-33, lang:"python"](../code/python/2d_convolution.py)
5155
{% endmethod %}
5256

5357
Though it is entirely possible to create a Gaussian kernel whose standard deviation is independent on the kernel size, we have decided to enforce a relation between the two in this chapter.
@@ -135,6 +139,8 @@ In code, the Sobel operator involves first finding the operators in $$x$$ and $$
135139
{% method %}
136140
{% sample lang="jl" %}
137141
[import:49-63, lang:"julia"](../code/julia/2d_convolution.jl)
142+
{% sample lang="py" %}
143+
[import:36-52, lang:"python"](../code/python/2d_convolution.py)
138144
{% endmethod %}
139145

140146
With that, I believe we are at a good place to stop discussions on two-dimensional convolutions.
@@ -148,6 +154,8 @@ We have also added code to create the Gaussian kernel and Sobel operator and app
148154
{% method %}
149155
{% sample lang="jl" %}
150156
[import, lang:"julia"](../code/julia/2d_convolution.jl)
157+
{% sample lang="py" %}
158+
[import, lang:"python"](../code/python/2d_convolution.py)
151159
{% endmethod %}
152160

153161
<script>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import numpy as np
2+
from contextlib import suppress
3+
4+
5+
def convolve_linear(signal, filter, output_size):
6+
out = np.zeros(output_size)
7+
sum = 0
8+
9+
for i in range(output_size[0]):
10+
for j in range(output_size[1]):
11+
for k in range(max(0, i-filter.shape[0]), i+1):
12+
for l in range(max(0, j-filter.shape[1]), j+1):
13+
with suppress(IndexError):
14+
sum += signal[k, l] * filter[i-k, j-l]
15+
out[i, j] = sum
16+
sum = 0
17+
18+
return out
19+
20+
21+
def create_gaussian_kernel(kernel_size):
22+
kernel = np.zeros((kernel_size, kernel_size))
23+
24+
# The center must be offset by 0.5 to find the correct index
25+
center = kernel_size*0.5 + 0.5
26+
27+
sigma = np.sqrt(0.1*kernel_size)
28+
29+
def kernel_function(x, y):
30+
return np.exp(-((x-center+1)**2 + (y-center+1)**2)/(2*sigma**2))
31+
32+
kernel = np.fromfunction(kernel_function, (kernel_size, kernel_size))
33+
return kernel / np.linalg.norm(kernel)
34+
35+
36+
def create_sobel_operators():
37+
Sx = np.dot([[1.0], [2.0], [1.0]], [[-1.0, 0.0, 1.0]]) / 9
38+
Sy = np.dot([[-1.0], [0.0], [1.0]], [[1.0, 2.0, 1.0]]) / 9
39+
40+
return Sx, Sy
41+
42+
def sum_matrix_dimensions(mat1, mat2):
43+
return (mat1.shape[0] + mat2.shape[0],
44+
mat1.shape[1] + mat2.shape[1])
45+
46+
def compute_sobel(signal):
47+
Sx, Sy = create_sobel_operators()
48+
49+
Gx = convolve_linear(signal, Sx, sum_matrix_dimensions(signal, Sx))
50+
Gy = convolve_linear(signal, Sy, sum_matrix_dimensions(signal, Sy))
51+
52+
return np.sqrt(np.power(Gx, 2) + np.power(Gy, 2))
53+
54+
55+
def create_circle(image_resolution, grid_extents, radius):
56+
out = np.zeros((image_resolution, image_resolution))
57+
58+
for i in range(image_resolution):
59+
x_position = ((i * grid_extents / image_resolution)
60+
- 0.5 * grid_extents)
61+
for j in range(image_resolution):
62+
y_position = ((j * grid_extents / image_resolution)
63+
- 0.5 * grid_extents)
64+
if x_position ** 2 + y_position ** 2 <= radius ** 2:
65+
out[i, j] = 1.0
66+
67+
return out
68+
69+
70+
def main():
71+
72+
# Random distribution in x
73+
x = np.random.rand(100, 100)
74+
75+
# Gaussian signals
76+
def create_gaussian_signals(i, j):
77+
return np.exp(-(((i-50)/100) ** 2 +
78+
((j-50)/100) ** 2) / .01)
79+
y = np.fromfunction(create_gaussian_signals, (100, 100))
80+
81+
# Normalization is not strictly necessary, but good practice
82+
x /= np.linalg.norm(x)
83+
y /= np.linalg.norm(y)
84+
85+
# full convolution, output will be the size of x + y
86+
full_linear_output = convolve_linear(x, y, sum_matrix_dimensions(x, y))
87+
88+
# simple boundaries
89+
simple_linear_output = convolve_linear(x, y, x.shape)
90+
91+
np.savetxt("full_linear.dat", full_linear_output)
92+
np.savetxt("simple_linear.dat", simple_linear_output)
93+
94+
# creating simple circle and 2 different Gaussian kernels
95+
circle = create_circle(50, 2, 0.5)
96+
97+
circle = circle / np.linalg.norm(circle)
98+
99+
small_kernel = create_gaussian_kernel(3)
100+
large_kernel = create_gaussian_kernel(25)
101+
102+
small_kernel_output = convolve_linear(circle, small_kernel,
103+
sum_matrix_dimensions(circle,
104+
small_kernel))
105+
106+
large_kernel_output = convolve_linear(circle, large_kernel,
107+
sum_matrix_dimensions(circle,
108+
large_kernel))
109+
110+
np.savetxt("small_kernel.dat", small_kernel_output)
111+
np.savetxt("large_kernel.dat", large_kernel_output)
112+
113+
circle = create_circle(50, 2, 0.5)
114+
115+
# Normalization
116+
circle = circle / np.linalg.norm(circle)
117+
118+
# using the circle for sobel operations as well
119+
sobel_output = compute_sobel(circle)
120+
121+
np.savetxt("sobel_output.dat", sobel_output)
122+

0 commit comments

Comments
 (0)