Files
2025-10-21 11:20:44 +08:00

140 lines
4.4 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n# Simple image blur by convolution with a Gaussian kernel\n\nBlur an an image (:download:`../../../../data/elephant.png`) using a\nGaussian kernel.\n\nConvolution is easy to perform with FFT: convolving two signals boils\ndown to multiplying their FFTs (and performing an inverse FFT).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import numpy as np\nimport scipy as sp\nimport matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The original image\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# read image\nimg = plt.imread(\"../../../../data/elephant.png\")\nplt.figure()\nplt.imshow(img)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Prepare an Gaussian convolution kernel\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# First a 1-D Gaussian\nt = np.linspace(-10, 10, 30)\nbump = np.exp(-0.1 * t**2)\nbump /= np.trapezoid(bump) # normalize the integral to 1\n\n# make a 2-D kernel out of it\nkernel = bump[:, np.newaxis] * bump[np.newaxis, :]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implement convolution via FFT\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Padded fourier transform, with the same shape as the image\n# We use :func:`scipy.fft.fft2` to have a 2D FFT\nkernel_ft = sp.fft.fft2(kernel, s=img.shape[:2], axes=(0, 1))\n\n# convolve\nimg_ft = sp.fft.fft2(img, axes=(0, 1))\n# the 'newaxis' is to match to color direction\nimg2_ft = kernel_ft[:, :, np.newaxis] * img_ft\nimg2 = sp.fft.ifft2(img2_ft, axes=(0, 1)).real\n\n# clip values to range\nimg2 = np.clip(img2, 0, 1)\n\n# plot output\nplt.figure()\nplt.imshow(img2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Further exercise (only if you are familiar with this stuff):\n\nA \"wrapped border\" appears in the upper left and top edges of the\nimage. This is because the padding is not done correctly, and does\nnot take the kernel size into account (so the convolution \"flows out\nof bounds of the image\"). Try to remove this artifact.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A function to do it: :func:`scipy.signal.fftconvolve`\n\n The above exercise was only for didactic reasons: there exists a\n function in scipy that will do this for us, and probably do a better\n job: :func:`scipy.signal.fftconvolve`\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# mode='same' is there to enforce the same output shape as input arrays\n# (ie avoid border effects)\nimg3 = sp.signal.fftconvolve(img, kernel[:, :, np.newaxis], mode=\"same\")\nplt.figure()\nplt.imshow(img3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we still have a decay to zero at the border of the image.\nUsing :func:`scipy.ndimage.gaussian_filter` would get rid of this\nartifact\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}