diff --git a/.gitignore b/.gitignore index 03bb31ca3..db26e0ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -515,4 +515,5 @@ vscode/ # Coconut compilation files **/coconut/*.py +# aspell *.bak diff --git a/SUMMARY.md b/SUMMARY.md index 7b2fd5e71..88a092160 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -13,6 +13,11 @@ * [Affine Transformations](contents/affine_transformations/affine_transformations.md) * [Bit Logic](contents/bitlogic/bitlogic.md) * [Taylor Series](contents/taylor_series_expansion/taylor_series_expansion.md) + * [Convolutions](contents/convolutions/convolutions.md) + * [Convolutions in 1D](contents/convolutions/1d/1d.md) + * [Multiplication as a Convolution](contents/convolutions/multiplication/multiplication.md) + * [Convolutions of Images (2D)](contents/convolutions/2d/2d.md) + * [Convolutional Theorem](contents/convolutions/convolutional_theorem/convolutional_theorem.md) * [Tree Traversal](contents/tree_traversal/tree_traversal.md) * [Euclidean Algorithm](contents/euclidean_algorithm/euclidean_algorithm.md) * [Monte Carlo](contents/monte_carlo_integration/monte_carlo_integration.md) diff --git a/contents/IFS/IFS.md b/contents/IFS/IFS.md index 8b7805ff3..4ac0fb38a 100644 --- a/contents/IFS/IFS.md +++ b/contents/IFS/IFS.md @@ -225,7 +225,7 @@ The code examples are licensed under the MIT license (found in [LICENSE.md](http ##### Text -The text of this chapter was written by [James Schloss](https://github.com/leio) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). [

](https://creativecommons.org/licenses/by-sa/4.0/) diff --git a/contents/computus/computus.md b/contents/computus/computus.md index 087e9e7ae..d82973d9c 100644 --- a/contents/computus/computus.md +++ b/contents/computus/computus.md @@ -318,7 +318,7 @@ The code examples are licensed under the MIT license (found in [LICENSE.md](http ##### Text -The text of this chapter was written by [James Schloss](https://github.com/leio) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). [

](https://creativecommons.org/licenses/by-sa/4.0/) diff --git a/contents/convolutions/1d/1d.md b/contents/convolutions/1d/1d.md new file mode 100644 index 000000000..3a3ca0cf6 --- /dev/null +++ b/contents/convolutions/1d/1d.md @@ -0,0 +1,307 @@ +# Convolutions in 1D +As mentioned in the [introductory section for convolutions](../convolutions.md), convolutions allow mathematicians to "blend" two seemingly unrelated functions; however, this definition is not very rigorous, so it might be better to think of a convolution as a method to apply a filter to a signal or image. +This, of course, brings up more questions: what is a filter? What is a signal? How is this all related to images? + +For this, we will start with some predefined signal. +It does not matter too much what it is, so we will pick a square wave where everything is set to zero except for a few elements at the center, which will be set to one. +This signal can be treated as an array, or a black and white, one-dimensional image where everything is black except for a white strip at the center. +We will also introduce a filter, which will be a simple triangle wave that goes to 1. +Both of these are shown below: + +

+ + +

+ +So now we have a signal and a filter. +How do we apply the filter to the signal? +The easiest way to do this is to iterate through every point in the signal and blend it with neighboring elements, where each neighboring element is weighted based on the filter value. +So in the case where the triangle wave is only 3 elements (`[0.5, 1, 0.5]`), the output at each point would be + +$$ +C_n = \frac{A_{n-1}}{2} + A_{n} + \frac{A_{n+1}}{2}, +$$ + +where $$C$$ is the output value, $$A$$ is the input array (a signal or image), and $$n$$ is an iterable element through that signal. +In this way, the "application of a filter," is simply a multiplication of the triangle wave centered around each point of the input array, followed by in integral or sum of the output. +In some sense, this means we will shift the filter, then multiply and sum every step. +This can be seen in the following animation: + +
+ +
+ +Here, the purple, dashed line is the output convolution $$C$$, the vertical line is the iteration $$n$$, the blue line is the original signal, the red line is the filter, and the green area is the signal multiplied by the filter at that location. +The convolution at each point is the integral (sum) of the green area for each point. + +If we extend this concept into the entirety of discrete space, it might look like this: + +$$(f*g)[n] = \sum_{m = -\infty}^{\infty}f[m]g[n-m] = \sum_{m = -\infty}^{\infty}f[n-m]g[m]$$ + +Where `f[n]` and `g[n]` are arrays of some form. +This means that the convolution can calculated by shifting either the filter along the signal or the signal along the filter. +This can be read as we said before: every step, we shift the filter, multiply, and sum. +There is, of course, a small caveat here. +Why are we subtracting $$m$$? +Certainly, if we wanted to "shift the filter along the signal," we could also do so by *adding* $$m$$ instead, but that is actually an entirely separate operation known as a *correlation*, which will be discussed at a later time. + +The simplest interpretation for this equation is the same as the animation: we reverse the second array, and move it through the first array one step at a time, performing a simple element-wise multiplication and summation at each step. +With this in mind, we can almost directly transcribe the discrete equation into code like so: + +{% method %} +{% sample lang="jl" %} +[import:29-48, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +The easiest way to reason about this code is to read it as you might read a textbook. +For each element in the output domain, we are summing a certain subsets of elements from `i-length(filter)` to `i` after multiplying it by the reversed filter (`filter[i-j]`). +In this way, it is precisely the same as the mathematical notation mentioned before. + +In contrast to the animation, where the filter continuously reappears on the left edge of the screen, the code we have written for this part of the chapter requires the user to specify what they expect the output array length to be. +Determining what should happen at the edges of the convolution is a somewhat hotly debated topic and differs depending on what the user actually wants, so we will be discussing this in greater detail later in this chapter. + +As an important note, if we were to extend the convolution into continuous space, we might write something like: + +$$(f*g)(x) = \int_{-\infty}^{\infty}f(\xi)g(x-\xi)d\xi = \int_{-\infty}^{\infty}f(x-\xi)g(\xi)d\xi$$ + +Note that in this case, $$x$$ and $$\xi$$ are not necessarily spatial elements, but the interpretation is otherwise the same as before. + +At this stage, the mathematics and code might still be a little opaque, so it is a good idea to play around a bit and think about how this operation might be used in practice with a few different filters. + +## Playing with filters + +Honestly, the best way to learn how convolutions work is by using them for a number of different signals and filters, so +let us extend the previous triangle filter a bit further by convolving a square wave with a relatively sharp Gaussian, which can be seen in the following animation: + +
+ +
+ +In practice, the convolutional output here is very similar to the triangle wave we showed before. +The final convolved image looks a lot like the square, except that its boundaries have been smoothed out or "blurred." +In practice whenever a Gaussian filter is used, it will always blur the other convolved signal, which is why a convolution with a Gaussian is also called a *blurring operation*. +This operation is used very often when dealing with two-dimensional images, and we will discuss common kernels found in the wild in [the next section](../2d/2d.md). +Still, it is interesting to see the blurring operation in action by convolving a random distribution with a larger Gaussian filter: + +
+ +
+ +In this animation, the final convolution is so blurred that it does not seem related to the random input signal at all! +In fact, this animation seems to blend much more when compared to the previous Gaussian and the triangle wave animations. +This is because the Gaussian is wider than the previous to filters. +In general, the wider the filter, the stronger the blurring effect. + +So what happens if we convolve a Gaussian with another Gaussian? +Well, that is shown below: + +
+ +
+ +As one might expect, the output is a blurrier Gaussian, which is essentially just wider. +If you were paying particularly close attention to the visualization, you might have noticed that the green area inside this visualization does not properly line up with the overlap of the two arrays. +Don't worry! +This is exactly what should happen! +Remember that the convolution requires a *multiplication* of the signal and filter, which was the same as the overlap when the signal was a square wave; however, in the case of two distinct signals, we should expect the multiplied output to look somewhat distinct. + +Let us extend this concept to one final example of a square wave convolved with a triangular, sawtooth function that looks like this: + +

+ +

+ +This is the first non-symmetric filter of this chapter, and its convolution would look like this: + +
+ +
+ +Non-symmetric filters are useful for testing convolutions to ensure that the output is correct, so it might be worthwhile to linger on this animation for a bit longer. +Notice how the convolution has an accelerating, positive slope when the reversed sawtooth function interacts with the square. +This makes sense as the smallest part of the triangle interacts first. +Similarly, there is a negatively accelerating slope when the sawtooth function leaves the square. + +## Dealing with boundaries + +In all of the animations, we have shown the filter constantly reappearing on the left edge of the screen, which is not always the best thing to do at the boundaries. +In fact, these boundary conditions are somewhat non-trivial to code, so for this section, we will start with relatively simple boundary conditions that were introduced in the previous code example. + +### Simple boundaries + +In general, if a user wants to see a full convolution between two signals, the output size must be the size of the two signals put together, otherwise, we cannot iterate through the entire convolutional output domain. +For example, here is random noise again convolved with a Gaussian function, but with non-periodic boundaries: + +
+ +
+ +This shows the full, unbounded convolution of the two signals, where +we clearly see a "ramp up" and "ramp down" phase at the start and end of the animation. +That said, there are many applications where the user actually needs to specify the output domain to be another length, such as the size of one of the input signals. + +In this case, the simplest boundary would be to assume that whenever the filter hits the end of the image, it simply disappears. +Another way to think about this is that the signal only exists for the domain we specify it over, and is all 0s outside of this domain; therefore, the filter does not sum any signal from elements beyond its scope. +As an example, let's take the same example as before: + +
+ +
+ +Similar to the case without boundary conditions, this convolution needs to "ramp up," but it does not need to "ramp down." +This is because the convolution output no longer extends past the bounds of the original signal so the bounded convolution is a subset of the full convolution. +More than that, the convolution does not go all the way to 0 on the right side. +This means that we are actually ignoring a rather important part of the convolution! + +This is 100% true; however, if the signal is large and the filter is small (as is the case with most of image processing), we do not really care that much about the bits of the convolution we missed. +In addition, there is a way to center the convolution by modifying the location where the filter starts. +For example, we could have half of the filter already existing and overlapping with the signal for the very first computed point of the convolution. +For this reason, simple bounds are used frequently when performing convolutions on an image. + +In the previous code snippet, we were able to perform both a bounded and unbounded convolution. +Here it is again for clarity: + +{% method %} +{% sample lang="jl" %} +[import:29-48, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +Here, the main difference between the bounded and unbounded versions is that the output array size is smaller in the bounded case. +For an unbounded convolution, the function would be called with a the output array size specified to be the size of both signals put together: + +{% method %} +{% sample lang="jl" %} +[import:60-61, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +On the other hand, the bounded call would set the output array size to simply be the length of the signal + +{% method %} +{% sample lang="jl" %} +[import:63-64, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +Finally, as we mentioned before, it is possible to center bounded convolutions by changing the location where we calculate the each point along the filter. +This can be done by modifying the following line: + +{% method %} +{% sample lang="jl" %} +[import:37-37, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +Here, `j` counts from `i-length(filter)` to `i`. +To center the convolution, it would need to count from `i-(length(filter)/2)` to `i+(length(filter)/2)` instead. + +I think this is a good place to stop discussions on simple boundary conditions. +Now let us talk a bit more in detail about the case where we want the filter to continuously reappear every loop. +This case is known as the "periodic boundary condition" and was used for the visualizations at the start of this chapter. + +### Periodic boundary conditions + +Though periodic boundary conditions are more complicated that those mentioned in the previous section, they are still *relatively* straightforward to implement. +With these conditions, the filter will wrap itself around to the other end of the signal whenever it hits a boundary. +In this way, the signal is periodic, with an identical copy of itself acting as left and right neighbors. +Those neighbors then have other neighbors, and those then have more neighbors, creating a sea of signals extending to infinity and beyond in both directions. +For us, this means that when the filter leaves one edge of the domain, it simply appears on the other, opposite edge. + +This particular convolution is known as a *cyclic* convolution and is also the most common output of convolutions that work via the [convolutional theorem](../convolutional_theorem/convolutional_theorem.md), which will be discussed in another section. +For clarity: here is the same cyclic visualization we showed above with a random distribution and a Gaussian signal. + +
+ +
+ +In code, this typically amounts to using some form of modulus operation, as shown here: + +{% method %} +{% sample lang="jl" %} +[import:4-27, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +This is essentially the same as before, except for the modulus operations, which allow us to work on a periodic domain. + +As a final note before continuing: dealing with boundaries is tricky business and can dramatically change the behavior of the output convolution. +For this reason, it is important to think about what types of boundaries will work best for what you, the programmer, actually need. +The selection of boundary conditions will be a common trope for a large portion of computer graphics and physics algorithms where researchers often need to present and simulate data on an array of some sort. + +## Example Code + +For the code associated with this chapter, we have used the convolution to generate a few files for the full convolution, along with the periodic and simple boundary conditions discussed in this chapter. + +{% method %} +{% sample lang="jl" %} +[import, lang:"julia"](../code/julia/1d_convolution.jl) +{% endmethod %} + +At a test case, we have chosen to use two sawtooth functions, which should produce the following images: + +| Description | Image | +| ----------- | ----- | +| Simple Boundaries | | +| Full | | +| Cyclic | | + +As a sanity check, make sure that the bounded convolution is a subset of the full convolution. +In this example, the bounded convolution is the start of the full convolution, but it is entirely possible it could be the middle or somewhere else entirely depending on how you counted within the inner, summation loop for the convolution. + + + +## License + +##### Code Examples + +The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)). + +##### Images/Graphics +- The image "[Square Wave](../res/square_wave.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Triangle Wave](../res/triangle_wave.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Triangle Square Convolution](../res/triangle_square_conv.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Gaussian Square Convolution](../res/1d_gaussian.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Gaussian Random Convolution](../res/1d_rand_gaussian_cyclic.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Double Convolution](../res/double_gaussian.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Sawtooth Wave](../res/sawtooth.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Sawtooth Square Convolution](../res/1d_sawtooth.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Full Random Convolution](../res/1d_rand_gaussian_full.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[Simple Random Convolution](../res/1d_rand_gaussian_simple.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Simple Linear](../res/simple_linear.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Full Linear](../res/full_linear.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Cyclic](../res/cyclic.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + + +##### Text + +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +[

](https://creativecommons.org/licenses/by-sa/4.0/) + +##### Pull Requests + +After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter: +- none + diff --git a/contents/convolutions/2d/2d.md b/contents/convolutions/2d/2d.md new file mode 100644 index 000000000..d026c8a52 --- /dev/null +++ b/contents/convolutions/2d/2d.md @@ -0,0 +1,183 @@ +# Convolutions on Images + +For this section, we will no longer be focusing on signals, but instead images (arrays filled with elements of red, green, and blue values). +That said, for the code examples, greyscale images may be used such that each array element is composed of some floating-point value instead of color. +In addition, we will not be discussing boundary conditions too much in this chapter and will instead be using the simple boundaries introduced in the section on [one-dimensional convolutions](../1d/1d.md). + +The extension of one-dimensional convolutions to two dimensions requires a little thought about indexing and the like, but is ultimately the same operation. +Here is an animation of a convolution for a two-dimensional image: + +
+ +
+ +In this case, we convolved the image with a 3x3 square filter, all filled with values of $$\frac{1}{9}$$. +This created a simple blurring effect, which is somewhat expected from the discussion in the previous section. +In code, a two-dimensional convolution might look like this: + +{% method %} +{% sample lang="jl" %} +[import:4-28, lang:"julia"](../code/julia/2d_convolution.jl) +{% endmethod %} + +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. + +At this stage, it is worth highlighting common filters used for convolutions of images. +In particular, we will further discuss the Gaussian filter introduced in the [previous section](../1d/1d.md), and then introduce another set of kernels known as Sobel operators, which are used for naïve edge detection or image derivatives. + +## The Gaussian kernel + +The Gaussian kernel serves as an effective *blurring* operation for images. +As a reminder, the formula for any Gaussian distribution is + +$$ +g(x,y) = \frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}, +$$ + +where $$\sigma$$ is the standard deviation and is a measure of the width of the Gaussian. +A larger $$\sigma$$ means a larger Gaussian; however, remember that the Gaussian must fit onto the filter, otherwise it will be cut off! +For example, if you are using a $$3\times 3$$ filter, you should not be using $$\sigma = 10$$. +Some definitions of $$\sigma$$ allow users to have a separate deviation in $$x$$ and $$y$$ to create an ellipsoid Gaussian, but for the purposes of this chapter, we will assume $$\sigma_x = \sigma_y$$. +As a general rule of thumb, the larger the filter and standard deviation, the more "smeared" the final convolution will be. + +At this stage, it is important to write some code, so we will generate a simple function that returns a Gaussian kernel with a specified standard deviation and filter size. + +{% method %} +{% sample lang="jl" %} +[import:30-47, lang:"julia"](../code/julia/2d_convolution.jl) +{% endmethod %} + +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. +As always, we encourage you to play with the code and create your own Gaussian kernels any way you want! +As a note, all the kernels will be scaled (normalized) at the end by the sum of all internal elements. +This ensures that the output of the convolution will not have an obnoxious scale factor associated with it. + +Below are a few images generated by applying a kernel generated with the code above to a black and white image of a circle. + +

+ +

+ + +In (a), we show the original image, which is just a white circle at the center of a $$50\times 50$$ grid. +In (b), we show the image after convolution with a $$3\times 3$$ kernel. +In (c), we show the image after convolution with a $$20\times 20$$ kernel. +Here, we see that (c) is significantly fuzzier than (b), which is a direct consequence of the kernel size. + +There is a lot more that we could talk about, but now is a good time to move on to a slightly more complicated convolutional method: the Sobel operator. + +## The Sobel operator + +The Sobel operator effectively performs a gradient operation on an image by highlighting areas where a large change has been made. +In essence, this means that this operation can be thought of as a naïve edge detector. +Essentially, the $$n$$-dimensional Sobel operator is composed of $$n$$ separate gradient convolutions (one for each dimension) that are then combined together into a final output array. +Again, for the purposes of this chapter, we will stick to two dimensions, which will be composed of two separate gradients along the $$x$$ and $$y$$ directions. +Each gradient will be created by convolving our image with their corresponding Sobel operator: + +$$ +\begin{align} +S_x &= \left(\begin{bmatrix} +1 \\ +2 \\ +1 \\ +\end{bmatrix} \otimes [1~0~-1] +\right) = \begin{bmatrix} +1 & 0 & -1 \\ +2 & 0 & -2 \\ +1 & 0 & -1 \\ +\end{bmatrix}\\ + +S_y &= \left( +\begin{bmatrix} +1 \\ +0 \\ +-1 \\ +\end{bmatrix} \otimes [1~2~1] +\right) = \begin{bmatrix} +1 & 2 & 1 \\ +0 & 0 & 0 \\ +-1 & -2 & -1 \\ +\end{bmatrix}. +\end{align} +$$ + +The gradients can then be found with a convolution, such that: + +$$ +\begin{align} +G_x &= S_x*A \\ +G_y &= S_y*A. +\end{align} +$$ + +Here, $$A$$ is the input array or image. +Finally, these gradients can be summed in quadrature to find the total Sobel operator or image gradient: + +$$ +G_{\text{total}} = \sqrt{G_x^2 + G_y^2} +$$ + +So let us now show what it does in practice: + +

+ +

+ +In this diagram, we start with the circle image on the right, and then convolve it with the $$S_x$$ and $$S_y$$ operators to find the gradients along $$x$$ and $$y$$ before summing them in quadrature to get the final image gradient. +Here, we see that the edges of our input image have been highlighted, showing outline of our circle. +This is why the Sobel operator is also known as naïve edge detection and is an integral component to many more sophisticated edge detection methods like one proposed by Canny {{ "canny1986computational" | cite }}. + +In code, the Sobel operator involves first finding the operators in $$x$$ and $$y$$ and then applying them with a traditional convolution: + +{% method %} +{% sample lang="jl" %} +[import:49-63, lang:"julia"](../code/julia/2d_convolution.jl) +{% endmethod %} + +With that, I believe we are at a good place to stop discussions on two-dimensional convolutions. +We will definitely return to this topic in the future as new algorithms require more information. + +## Example Code + +For the code in this section, we have modified the visualizations from the [one-dimensional convolution chapter](../1d/1d.md) to add a two-dimensional variant for blurring an image of random white noise. +We have also added code to create the Gaussian kernel and Sobel operator and apply it to the circle, as shown in the text. + +{% method %} +{% sample lang="jl" %} +[import, lang:"julia"](../code/julia/2d_convolution.jl) +{% endmethod %} + + + +### Bibliography + +{% references %} {% endreferences %} + +## License + +##### Code Examples + +The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)). + +##### Images/Graphics +- The image "[8bit Heart](../res/heart_8bit.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Circle Blur](../res/circle_blur.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The image "[Sobel Filters](../res/sobel_filters.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +- The video "[2D Convolution](../res/2d.mp4)" was created by [James Schloss](https://github.com/leios) and [Grant Sanderson](https://github.com/3b1b) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +##### Text + +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +[

](https://creativecommons.org/licenses/by-sa/4.0/) + +##### Pull Requests + +After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter: +- none + diff --git a/contents/convolutions/code/julia/1d_convolution.jl b/contents/convolutions/code/julia/1d_convolution.jl new file mode 100644 index 000000000..78dafc0ed --- /dev/null +++ b/contents/convolutions/code/julia/1d_convolution.jl @@ -0,0 +1,74 @@ +using DelimitedFiles +using LinearAlgebra + +function convolve_cyclic(signal::Array{T, 1}, + filter::Array{T, 1}) where {T <: Number} + + # output size will be the size of sign + output_size = max(length(signal), length(filter)) + + # convolutional output + out = Array{Float64,1}(undef,output_size) + sum = 0 + + for i = 1:output_size + for j = 1:output_size + if (mod1(i-j, output_size) <= length(filter)) + sum += signal[mod1(j, output_size)] * filter[mod1(i-j, output_size)] + end + end + + out[i] = sum + sum = 0 + + end + + return out +end + +function convolve_linear(signal::Array{T, 1}, filter::Array{T, 1}, + output_size) where {T <: Number} + + # convolutional output + out = Array{Float64,1}(undef, output_size) + sum = 0 + + for i = 1:output_size + for j = max(1, i-length(filter)):i + if j <= length(signal) && i-j+1 <= length(filter) + sum += signal[j] * filter[i-j+1] + end + end + + out[i] = sum + sum = 0 + end + + return out +end + +function main() + + # sawtooth functions for x and y + x = [float(i)/200 for i = 1:200] + y = [float(i)/200 for i = 1:200] + + # Normalization is not strictly necessary, but good practice + normalize!(x) + normalize!(y) + + # full convolution, output will be the size of x + y + full_linear_output = convolve_linear(x, y, length(x) + length(y)) + + # simple boundaries + simple_linear_output = convolve_linear(x, y, length(x)) + + # cyclic convolution + cyclic_output = convolve_cyclic(x, y) + + # outputting convolutions to different files for plotting in external code + writedlm("full_linear.dat", full_linear_output) + writedlm("simple_linear.dat", simple_linear_output) + writedlm("cyclic.dat", cyclic_output) + +end diff --git a/contents/convolutions/code/julia/2d_convolution.jl b/contents/convolutions/code/julia/2d_convolution.jl new file mode 100644 index 000000000..6d38e5048 --- /dev/null +++ b/contents/convolutions/code/julia/2d_convolution.jl @@ -0,0 +1,125 @@ +using DelimitedFiles +using LinearAlgebra + +function convolve_linear(signal::Array{T, 2}, filter::Array{T, 2}, + output_size) where {T <: Number} + + # convolutional output + out = Array{Float64,2}(undef, output_size) + sum = 0 + + for i = 1:output_size[1] + for j = 1:output_size[2] + for k = max(1, i-size(filter)[1]):i + for l = max(1, j-size(filter)[2]):j + if k <= size(signal)[1] && i-k+1 <= size(filter)[1] && + l <= size(signal)[2] && j-l+1 <= size(filter)[2] + sum += signal[k,l] * filter[i-k+1, j-l+1] + end + end + end + + out[i,j] = sum + sum = 0 + end + end + + return out +end + +function create_gaussian_kernel(kernel_size) + + kernel = zeros(kernel_size, kernel_size) + + # The center must be offset by 0.5 to find the correct index + center = kernel_size * 0.5 + 0.5 + + sigma = sqrt(0.1*kernel_size) + + for i = 1:kernel_size + for j = 1:kernel_size + kernel[i,j] = exp(-((i-center)^2 + (j-center)^2) / (2*sigma^2)) + end + end + + return normalize(kernel) + +end + +function create_sobel_operators() + Sx = [1.0, 2.0, 1.0]*[-1.0 0.0 1.0] / 9 + Sy = [-1.0, 0.0, 1.0]*[1.0 2.0 1.0] / 9 + + return Sx, Sy +end + +function compute_sobel(signal) + Sx, Sy = create_sobel_operators() + + Gx = convolve_linear(signal, Sx, size(signal) .+ size(Sx)) + Gy = convolve_linear(signal, Sy, size(signal) .+ size(Sy)) + + return sqrt.(Gx.^2 .+ Gy.^2) +end + +# Simple function to create a square grid with a circle embedded inside of it +function create_circle(image_resolution, grid_extents, radius) + out = zeros(image_resolution, image_resolution) + + for i = 1:image_resolution + x_position = ((i-1)*grid_extents/image_resolution)-0.5*grid_extents + for j = 1:image_resolution + y_position = ((j-1)*grid_extents/image_resolution)-0.5*grid_extents + if x_position^2 + y_position^2 <= radius^2 + out[i,j] = 1.0 + end + end + end + + return out +end + +function main() + + # Random distribution in x + x = rand(100, 100) + + # Gaussian signals + y = [exp(-(((i-50)/100)^2 + ((j-50)/100)^2)/.01) for i = 1:100, j=1:100] + + # Normalization is not strictly necessary, but good practice + normalize!(x) + normalize!(y) + + # full convolution, output will be the size of x + y + full_linear_output = convolve_linear(x, y, size(x) .+ size(y)) + + # simple boundaries + simple_linear_output = convolve_linear(x, y, size(x)) + + # outputting convolutions to different files for plotting in external code + writedlm("full_linear.dat", full_linear_output) + writedlm("simple_linear.dat", simple_linear_output) + + # creating simple circle and 2 different Gaussian kernels + circle = create_circle(50,2,0.5) + + normalize!(circle) + + small_kernel = create_gaussian_kernel(3) + large_kernel = create_gaussian_kernel(25) + + small_kernel_output = convolve_linear(circle, small_kernel, + size(circle).+size(small_kernel)) + large_kernel_output = convolve_linear(circle, large_kernel, + size(circle).+size(large_kernel)) + + writedlm("small_kernel.dat", small_kernel_output) + writedlm("large_kernel.dat", large_kernel_output) + + # Using the circle for Sobel operations as well + sobel_output = compute_sobel(circle) + + writedlm("sobel_output.dat", sobel_output) + +end diff --git a/contents/convolutions/code/julia/convolutional_theorem.jl b/contents/convolutions/code/julia/convolutional_theorem.jl new file mode 100644 index 000000000..31a504eaf --- /dev/null +++ b/contents/convolutions/code/julia/convolutional_theorem.jl @@ -0,0 +1,27 @@ +using FFTW +using DelimitedFiles + +# using the convolutional theorem +function convolve_fft(signal1::Array{T}, signal2::Array{T}) where {T <: Number} + return ifft(fft(signal1).*fft(signal2)) +end + +function main() + + # Random distribution in x + x = rand(100) + + # Gaussian signals + y = [exp(-((i-50)/100)^2/.01) for i = 1:100] + + # Normalization is not strictly necessary, but good practice + normalize!(x) + normalize!(y) + + # cyclic convolution via the convolutional theorem + fft_output = convolve_fft(x, y) + + # outputting convolutions to different files for plotting in external code + writedlm("fft.dat", fft_output) + +end diff --git a/contents/convolutions/convolutional_theorem/convolutional_theorem.md b/contents/convolutions/convolutional_theorem/convolutional_theorem.md new file mode 100644 index 000000000..c3c370c81 --- /dev/null +++ b/contents/convolutions/convolutional_theorem/convolutional_theorem.md @@ -0,0 +1,67 @@ +# Convolutional Theorem + +Important note: this particular section will be expanded upon after the Fourier transform and Fast Fourier Transform (FFT) chapters have been revised. + +Now, let me tell you about a bit of computational magic: + +**Convolutions can be performed with Fourier Transforms!** + +This is crazy, but it is also incredibly hard to explain, so let me do my best. +As described in the chapter on [Fourier Transforms](../cooley_tukey/cooley_tukey.md), Fourier Transforms allow programmers to move from real space to frequency space. +When we transform a wave into frequency space, we can see a single peak in frequency space related to the frequency of that wave. +No matter what function we send into a Fourier Transform, the frequency-space image can be interpreted as a series of different waves with a specified frequency. +Each of these waves is parameterized by another $$e^{2\pi i k n / N}$$ term, where $$k$$ is the element's value in the frequency domain, $$n$$ is its value in the time domain, and $$N$$ is the overall length of the signal. +In this way, each wave can be seen as a complex exponential. + +So here's the idea: if we take two functions $$f(x)$$ and $$g(x)$$ and move them to frequency space to be $$\hat f(\xi)$$ and $$\hat g(\xi)$$, we can then multiply those two functions and transform them back into to blend the signals together. +In this way, we will have a third function that relates the frequency-space images of the two input functions. +This is known as the *convolution theorem* which looks something like this: + +$$\mathcal{F}(f*g) = \mathcal{F}(f) \cdot \mathcal{F}(g)$$ + +Where $$\mathcal{F}$$ denotes the Fourier Transform. + +At first, this might not seem particularly intuitive, but remember that frequency space is essentially composed of a set of exponentials. +As mentioned in the section about [Multiplication as a Convolution](../multiplication/multiplication.md), multiplication in base 10 space is also a convolution. +The convolutional theorem extends this concept into multiplication with *any* set of exponentials, not just base 10. +Obviously, this description is still lacking a bit of explanation, but I promise we will add more when revising the Fourier transform sections! + +By using a Fast Fourier Transform (FFT) in code, this can take a standard convolution on two arrays of length $$n$$, which is an $$\mathcal{O}(n^2)$$ process, to $$\mathcal{O}(n\log(n))$$. +This means that the convolution theorem is fundamental to creating fast convolutional methods for certain large inputs. + +{% method %} +{% sample lang="jl" %} +[import:5-8, lang:"julia"](../code/julia/convolutional_theorem.jl) +{% endmethod %} + +This method also has the added advantage that it will *always output an array of the size of your signal*; however, if your signals are not of equal size, we need to pad the smaller signal with zeros. +Also note that the Fourier Transform is a periodic or cyclic operation, so there are no real edges in this method, instead the arrays "wrap around" to the other side, creating a cyclic convolution like we showed in the periodic boundary condition case for the [one-dimensional convolution](../1d/1d.md). + +## Example Code + +{% method %} +{% sample lang="jl" %} +[import, lang:"julia"](../code/julia/convolutional_theorem.jl) +{% endmethod %} + + + +## License + +##### Code Examples + +The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)). + +##### Text + +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +[

](https://creativecommons.org/licenses/by-sa/4.0/) + +##### Pull Requests + +After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter: +- none + diff --git a/contents/convolutions/convolutions.md b/contents/convolutions/convolutions.md new file mode 100644 index 000000000..afbd078f6 --- /dev/null +++ b/contents/convolutions/convolutions.md @@ -0,0 +1,46 @@ +# Convolutions +To put it bluntly, convolutions can be confusing. +Some might even call them *convoluted*! +(Get it? Because we are talking about *convolutions*? A wise man once told me that all good jokes need additional clarification.) + +Not only are convolutions hard to describe, but if they are not used in practice, it is hard to understand why they would ever be needed. +I am going to do what I can to describe them in an intuitive way; however, I may need to come back to this in the future. +Let me know if there is anything here that is unclear, and I will do what I can to clear it up. + +As always, we should start at the start. +If you take two functions $$f$$ and $$g$$, there are a number of ways you can combine them. +All basic operations can do this (addition, subtraction, multiplication, and division), but there are also special operations that only work with functions and do not work on standard variables or numbers. +For example, $$f \circ g$$ is a *composition* of the two functions, where you plug $$g(x)$$ into $$f$$. +A convolution is another function-related operation, and is often notated with a star $$(*)$$ operator, where + +$$ +f*g=c +$$ + +provides a third function, $$c$$, that is a blended version of $$f$$ and $$g$$. +As a rather important side-note: there is an incredibly similar operation known as a *correlation* which will be discussed in the near future. +Now we are left with a rather vague question: how do we *blend* functions? + +To answer this question, we will need to show off a few simple graphics and animations in the [Convolutions in 1D](1d/1d.md) section while also discussing the mathematical definition of convolutions. +After, there will be a brief discussion on an interesting application of one dimensional convolutions in integer multiplication in the [Multiplication as a Convolution](multiplication/multiplication.md) section. +We will then move on to the most stereotypical application of convolutions in the [Convolutions of Images](2d/2d.md) section, where we will also discuss two important filters: the Gaussian kernel and the Sobel operator. +As a note: convolutions can be extended to $$n$$-dimensions, but after seeing how they are extended to two dimensions, it should be possible for the reader to extend it to three dimensions and beyond if that is needed, so we will not cover that in great detail here unless is is useful for another algorithm. +In addition, we will be touching on a rather difficult but powerful topic with the [Convolutional Theorem](convolutional_theorem/convolutional_theorem.md) section where convolutions can be computed by using [Fourier transforms](../Cooley_tukey/cooley_tukey.md). + + + +## License + +##### Text + +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +[

](https://creativecommons.org/licenses/by-sa/4.0/) + +##### Pull Requests + +After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter: +- none + diff --git a/contents/convolutions/multiplication/multiplication.md b/contents/convolutions/multiplication/multiplication.md new file mode 100644 index 000000000..0e5e30fa6 --- /dev/null +++ b/contents/convolutions/multiplication/multiplication.md @@ -0,0 +1,112 @@ +# Multiplication as a convolution + +As a brief aside, we will touch on a rather interesting side topic: the relation between integer multiplication and convolutions +As an example, let us consider the following multiplication: $$123 \times 456 = 56088$$. + +In this case, we might line up the numbers, like so: + +$$ +\begin{matrix} +&&1&2&3 \\ +&\times &4&5&6 \\ +\hline +5 & 6 & 0 & 8 & 8 +\end{matrix} +$$ + +Here, each column represents another power of 10, such that in the number 123, there is 1 100, 2 10s, and 3 1s. +So let us use a similar notation to perform the convolution, by reversing the second set of numbers and moving it to the right, performing an element-wise multiplication at each step: + +$$ +\begin{matrix} +&&&\color{red}1&2&3 \\ +\times &6&5&\color{red}4&& \\ +\hline +\end{matrix}\\ +\color{red}{1}\times\color{red}{4} = 4 +$$ + +$$ +\begin{matrix} +&&&\color{red}1&\color{green}2&3 \\ +\times &&6&\color{red}5&\color{green}4& \\ +\hline +\end{matrix}\\ +\color{red}1\times\color{red}5+\color{green}2\times\color{green}4=13 +$$ + +$$ +\begin{matrix} +&&&\color{red}1&\color{green}2&\color{blue}3 \\ +\times &&&\color{red}6&\color{green}5&\color{blue}4 \\ +\hline +\end{matrix}\\ +\color{red}1\times\color{red}6+\color{green}2\times\color{green}5+\color{blue}3\times\color{blue}4=28 +$$ + +$$ +\begin{matrix} +&&1&\color{green}2&\color{blue}3& \\ +\times &&&\color{green}6&\color{blue}5&4 \\ +\hline +\end{matrix}\\ +\color{green}2\times\color{green}6+\color{blue}3\times\color{blue}5=27 +$$ + +$$ +\begin{matrix} +&1&2&\color{blue}3&& \\ +\times &&&\color{blue}6&5&4 \\ +\hline +\end{matrix}\\ +\color{blue}3\times\color{blue}6=18 +$$ + +For these operations, any blank space should be considered a $$0$$. +In the end, we will have a new set of numbers: + +$$ +\begin{matrix} +&&1&2&3 \\ +&\times &4&5&6 \\ +\hline +4 & 13 & 28 & 27 & 18 +\end{matrix} +$$ + +Now all that is left is to perform the *carrying* operation by moving any number in the 10s digit to its left-bound neighbor. +For example, the numbers $$[4, 18]=[4+1, 8]=[5,8]$$ or 58. +For these numbers, + +$$ +\begin{matrix} +&4 & 13 & 28 & 27 & 18\\ +=&4+1 & 3+2 & 8+2 & 7+1 & 8\\ +=&5 & 5 & 10 & 8 & 8\\ +=&5 & 5+1 & 0 & 8 & 8\\ +=&5 & 6 & 0 & 8 & 8 +\end{matrix} +$$ + +Which give us $$123\times456=56088$$, the correct answer for integer multiplication. +I am not suggesting that we teach elementary school students to learn convolutions, but I do feel this is an interesting fact that most people do not know: integer multiplication can be performed with a convolution. + +This will be discussed in further detail when we talk about the Schonhage-Strassen algorithm, which uses this fact to perform multiplications for incredibly large integers. + + + +## License + +##### Text + +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). + +[

](https://creativecommons.org/licenses/by-sa/4.0/) + +##### Pull Requests + +After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter: +- none + diff --git a/contents/convolutions/res/1d_gaussian.mp4 b/contents/convolutions/res/1d_gaussian.mp4 new file mode 100644 index 000000000..75252f154 Binary files /dev/null and b/contents/convolutions/res/1d_gaussian.mp4 differ diff --git a/contents/convolutions/res/1d_rand_gaussian_cyclic.mp4 b/contents/convolutions/res/1d_rand_gaussian_cyclic.mp4 new file mode 100644 index 000000000..8ba33ff18 Binary files /dev/null and b/contents/convolutions/res/1d_rand_gaussian_cyclic.mp4 differ diff --git a/contents/convolutions/res/1d_rand_gaussian_full.mp4 b/contents/convolutions/res/1d_rand_gaussian_full.mp4 new file mode 100644 index 000000000..9a8f1169f Binary files /dev/null and b/contents/convolutions/res/1d_rand_gaussian_full.mp4 differ diff --git a/contents/convolutions/res/1d_rand_gaussian_simple.mp4 b/contents/convolutions/res/1d_rand_gaussian_simple.mp4 new file mode 100644 index 000000000..929e4c5e0 Binary files /dev/null and b/contents/convolutions/res/1d_rand_gaussian_simple.mp4 differ diff --git a/contents/convolutions/res/1d_sawtooth.mp4 b/contents/convolutions/res/1d_sawtooth.mp4 new file mode 100644 index 000000000..1da346c25 Binary files /dev/null and b/contents/convolutions/res/1d_sawtooth.mp4 differ diff --git a/contents/convolutions/res/2d.mp4 b/contents/convolutions/res/2d.mp4 new file mode 100644 index 000000000..644f39168 Binary files /dev/null and b/contents/convolutions/res/2d.mp4 differ diff --git a/contents/convolutions/res/Sobel_filters.png b/contents/convolutions/res/Sobel_filters.png new file mode 100644 index 000000000..7dbd105b9 Binary files /dev/null and b/contents/convolutions/res/Sobel_filters.png differ diff --git a/contents/convolutions/res/circle_blur.png b/contents/convolutions/res/circle_blur.png new file mode 100644 index 000000000..07d42bbc8 Binary files /dev/null and b/contents/convolutions/res/circle_blur.png differ diff --git a/contents/convolutions/res/cyclic.png b/contents/convolutions/res/cyclic.png new file mode 100644 index 000000000..53b6b6176 Binary files /dev/null and b/contents/convolutions/res/cyclic.png differ diff --git a/contents/convolutions/res/double_gaussian.mp4 b/contents/convolutions/res/double_gaussian.mp4 new file mode 100644 index 000000000..69483e71f Binary files /dev/null and b/contents/convolutions/res/double_gaussian.mp4 differ diff --git a/contents/convolutions/res/full_linear.png b/contents/convolutions/res/full_linear.png new file mode 100644 index 000000000..c8f627a6d Binary files /dev/null and b/contents/convolutions/res/full_linear.png differ diff --git a/contents/convolutions/res/heart_8bit.png b/contents/convolutions/res/heart_8bit.png new file mode 100644 index 000000000..d51ee45b2 Binary files /dev/null and b/contents/convolutions/res/heart_8bit.png differ diff --git a/contents/convolutions/res/sawtooth.png b/contents/convolutions/res/sawtooth.png new file mode 100644 index 000000000..2ab169253 Binary files /dev/null and b/contents/convolutions/res/sawtooth.png differ diff --git a/contents/convolutions/res/simple_linear.png b/contents/convolutions/res/simple_linear.png new file mode 100644 index 000000000..9cfade46c Binary files /dev/null and b/contents/convolutions/res/simple_linear.png differ diff --git a/contents/convolutions/res/sobel_filters.png b/contents/convolutions/res/sobel_filters.png new file mode 100644 index 000000000..7dbd105b9 Binary files /dev/null and b/contents/convolutions/res/sobel_filters.png differ diff --git a/contents/convolutions/res/square_wave.png b/contents/convolutions/res/square_wave.png new file mode 100644 index 000000000..9cd5ea4dd Binary files /dev/null and b/contents/convolutions/res/square_wave.png differ diff --git a/contents/convolutions/res/triangle_square_conv.mp4 b/contents/convolutions/res/triangle_square_conv.mp4 new file mode 100644 index 000000000..b36a3d7ef Binary files /dev/null and b/contents/convolutions/res/triangle_square_conv.mp4 differ diff --git a/contents/convolutions/res/triangle_wave.png b/contents/convolutions/res/triangle_wave.png new file mode 100644 index 000000000..9f4592c9d Binary files /dev/null and b/contents/convolutions/res/triangle_wave.png differ diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 7c350100c..87ec2b7a7 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -263,7 +263,7 @@ The code examples are licensed under the MIT license (found in [LICENSE.md](http ##### Text -The text of this chapter was written by [James Schloss](https://github.com/leio) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). +The text of this chapter was written by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode). [

](https://creativecommons.org/licenses/by-sa/4.0/) diff --git a/literature.bib b/literature.bib index 7b116d984..8b3085c60 100644 --- a/literature.bib +++ b/literature.bib @@ -279,3 +279,16 @@ @book{torbert2016 publisher={Springer} } +#------------------------------------------------------------------------------# +# Convolutions +#------------------------------------------------------------------------------# + +@article{canny1986computational, + title={A computational approach to edge detection}, + author={Canny, John}, + journal={IEEE Transactions on pattern analysis and machine intelligence}, + number={6}, + pages={679--698}, + year={1986}, + publisher={Ieee} +}