Multi-pass Gaussian blur filter
In image processing, a Gaussian blur filter is the transformation of an image that blurs it by a Gaussian function. Mathematically, a Gaussian blur filter is a linear transformation on a measurable function that does not grow too fast at infinity (or more generally, , where is a locally convex topological vector space) which results in a function defined by the integral where is a symmetric positive definite matrix called the covariance matrix of the Gaussian kernel.
By looking at this formula, we can see that is exactly the expectation value of , where is a random vector distributed according to the multivariate normal distribution with mean and covariance matrix . Let’s say, however, that instead of a multivariate normal distribution, we have a discrete distribution on a finite set of points , each with probability , satisfying Then, for distributed according to this discrete distribution, the expectation value of is This distribution has the covariance matrix , but this expectation value is not the same as the result of the Gaussian blur filter. In order to approach the Gaussian distribution, we need to sum independent samples , each distributed according the same discrete distribution with covariance matrix . Without changing the relation between and , we can achieve this by taking the distribution to be among the points , with the same probabilities . Then, according to the central limit theorem, we have By staring at this formula, one may notice that it is actually the result of the same linear transformation applied times to the function , where Thus, we can write
We can now see why we can implement a Gaussian blur filter as a multi-pass filter: each pass of the filter is equivalent to applying the linear transformation once, and the Gaussian blur filter is the limit of applying this transformation infinitely many times. After choosing the points and the weights , we can easily implement the filter in a fragment shader. After implementing , one can implement the multi-pass filter by flip-flopping between two textures times. Here is an example implementation of for a 2D () texture, with where would be the standard deviation of the resulting Gaussian blur filter:
1 2 3 4 5 6 7 8 9 |
varying vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uStrength; // sigma / sqrt(N)
void main() {
gl_FragColor = texture2D(uTexture, vTextureCoord + vec2( uStrength, 0.0)) * 0.5;
gl_FragColor += texture2D(uTexture, vTextureCoord + vec2(-uStrength, 0.0)) * 0.5;
}
|
When using the shader, the texture is input as the uniform uTexture
, and is input as the uniform uStrength
. This example is then called the 2-tap horizontal Gaussian blur filter (because to find for some one needs to evaluate twice).
To get a good Gaussian blur effect, should be large enough, which can quickly become computationally expensive. We can reduce the number of passes by rewriting so that the number of passes is now reduced from to without changing the result, but the cost is to implement instead of in the shader. With the example above, the filter is then a -tap horizontal Gaussian blur filter. The total number of times to fetch the texture is to render the whole blurred texture, which decreases as increases.
By utilizing the linear sampling feature of the GPU, this number can be further reduced (by half) if . One can read further about this in this article.
Let us now move on to an interesting relation to the heat equation. The heat equation is a partial differential equation that reads The physical meaning of is the temperature at position at time , and the solution to this equation gives the time evolution of the temperature distribution in a medium with unit thermal diffusivity. More generally, we can have some other thermal diffusivities, which may even be anisotropic, in which case the equation reads where is a symmetric positive definite matrix. Such an equation can always be reduced to the form with unit thermal diffusivity by a linear transformation of the coordinates , where satisfies (the Cholesky decomposition of ). By using operator exponentiation, we can write the solution to the heat equation as , where is called the time evolution operator (which is not generally defined for , which means that cannot form a group but only a monoid).
The reason that I denote it as is that gives exactly the Gaussian blur filter (when acting on real analytic functions). This is not immediately obvious, and I will justify its correctness by starting from the multi-pass expression of the Gaussian blur filter. For analytic functions, we can get the translation operator by exponentiating the derivative operator, so Therefore, we can write Now, in the limit of infinite , we have This means that applying the Gaussian blur filter is equivalent to evolving one unit of time according to the heat equation. Therefore, the equation in the beginning of this article, the definition of the Gaussian blur filter, can then be used to express the unit time evolution under the heat equation in the form of a integral transformation. To get the general , we can just replace with .
As a byproduct, we can then show that the heat kernel is a Gaussian kernel. The heat kernel is defined as the solution to the heat equation with initial condition . Directly applying to gives which is the form that you would find in textbooks.
The reason that I decided to study the Gaussian blur filter is that I spotted a flaw in the implementation of the blur filter in PixiJS, where the uniform uStrength
in the example shader above is scaled by instead of . I was very happy to find the bug because this is the first time that I found a bug without actually producing an unexpected phenomenon first but by just staring at the source codes and deducing the mathematical formulas by hand. I opened an issue for my findings.