Skip to content

Deblur and Denoise using Richardson Lucy Deconvolution for Computer Vision tasks #8038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
802cd20
Create haralick_descriptors
rzimmerdev Oct 19, 2022
88b014f
Working on creating Unit Testing for Haralick Descriptors module
Oct 19, 2022
bc6a189
Type hinting for Haralick descriptors
Oct 19, 2022
dff5498
Merge branch 'TheAlgorithms:master' into master
rzimmerdev Nov 27, 2022
6e23cc0
Fixed docstrings, unit testing and formatting choices
rzimmerdev Nov 27, 2022
eca7118
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 27, 2022
9d2cca7
Fixed line size formatting
rzimmerdev Nov 27, 2022
cd250cf
Merge remote-tracking branch 'origin/master'
rzimmerdev Nov 27, 2022
5e73aa6
Added final doctests
rzimmerdev Nov 27, 2022
aacb27e
Changed main callable
rzimmerdev Nov 27, 2022
5fb933d
Updated requirements.txt
rzimmerdev Nov 27, 2022
c9ec63e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 27, 2022
bfd0fd7
Update computer_vision/haralick_descriptors.py
rzimmerdev Dec 13, 2022
6f52bff
Undone wrong commit
rzimmerdev Dec 13, 2022
ca90e9a
Merge branch 'TheAlgorithms:master' into master
rzimmerdev Dec 19, 2022
408b0fb
Merge branch 'TheAlgorithms:master' into master
rzimmerdev Dec 20, 2022
fc2fa0b
Update haralick_descriptors.py
rzimmerdev Dec 20, 2022
e4c3252
Added algorithm for Richardson Lucy Deconvolution
rzimmerdev Dec 20, 2022
6615ca4
Fixed line sizes
rzimmerdev Dec 20, 2022
c8bbc89
Merge branch 'master' into feat/richardson_lucy
tianyizheng02 Sep 27, 2023
183c8b5
updating DIRECTORY.md
Sep 27, 2023
1c040d3
Fix pre-commit errors
tianyizheng02 Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
* [Mean Threshold](computer_vision/mean_threshold.py)
* [Mosaic Augmentation](computer_vision/mosaic_augmentation.py)
* [Pooling Functions](computer_vision/pooling_functions.py)
* [Richardson Lucy](computer_vision/richardson_lucy.py)

## Conversions
* [Astronomical Length Scale Conversion](conversions/astronomical_length_scale_conversion.py)
Expand Down Expand Up @@ -515,6 +516,7 @@
* [Logistic Regression](machine_learning/logistic_regression.py)
* Lstm
* [Lstm Prediction](machine_learning/lstm/lstm_prediction.py)
* [Mfcc](machine_learning/mfcc.py)
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
* [Polynomial Regression](machine_learning/polynomial_regression.py)
* [Scoring Functions](machine_learning/scoring_functions.py)
Expand Down Expand Up @@ -672,6 +674,7 @@
* [Sum Of Digits](maths/sum_of_digits.py)
* [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py)
* [Sum Of Harmonic Series](maths/sum_of_harmonic_series.py)
* [Sum Of Two Positive Numbers Bitwise](maths/sum_of_two_positive_numbers_bitwise.py)
* [Sumset](maths/sumset.py)
* [Sylvester Sequence](maths/sylvester_sequence.py)
* [Tanh](maths/tanh.py)
Expand Down Expand Up @@ -753,6 +756,7 @@
* [Basic Orbital Capture](physics/basic_orbital_capture.py)
* [Casimir Effect](physics/casimir_effect.py)
* [Centripetal Force](physics/centripetal_force.py)
* [Coulombs Law](physics/coulombs_law.py)
* [Grahams Law](physics/grahams_law.py)
* [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py)
* [Hubble Parameter](physics/hubble_parameter.py)
Expand Down
241 changes: 241 additions & 0 deletions computer_vision/richardson_lucy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
"""
https://en.wikipedia.org/wiki/Motion_blur
https://en.wikipedia.org/wiki/Image_noise
https://en.wikipedia.org/wiki/Richardson-Lucy_deconvolution
"""
import imageio.v2 as imageio
import numpy as np
from numpy.fft import fft2, ifft2, ifftshift


def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float:
"""Simple implementation of Root Mean Squared Error
for two N dimensional numpy arrays.

Examples:
>>> root_mean_square_error(np.array([1, 2, 3]), np.array([1, 2, 3]))
0.0
>>> root_mean_square_error(np.array([1, 2, 3]), np.array([2, 2, 2]))
0.816496580927726
>>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2]))
3.1622776601683795
"""
return np.sqrt(((original - reference) ** 2).mean())


def pad_to_size(image: np.ndarray, reference: np.ndarray):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function pad_to_size

Please provide return type hint for the function: pad_to_size. If the function does not return a value, please provide the type hint as: def function() -> None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function pad_to_size

Please provide return type hint for the function: pad_to_size. If the function does not return a value, please provide the type hint as: def function() -> None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function pad_to_size

Please provide return type hint for the function: pad_to_size. If the function does not return a value, please provide the type hint as: def function() -> None:

"""Pad an image to have final shape equal to reference image."""
p, q = (size for size in reference.shape)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these variables necessary? Why not just use reference.shape[0] and reference.shape[1]?

difference = (
(p - image.shape[0]) // 2,
(q - image.shape[1]) // 2 + (q - image.shape[1]) % 2,
)

return np.pad(image, difference, mode="constant")


def normalize_image(
image: np.ndarray, cap: float = 255.0, data_type: np.dtype = np.uint8
) -> np.ndarray:
"""
Normalizes image in Numpy 2D array format, between ranges 0-cap,
as to fit uint8 type.

Args:
image: 2D numpy array representing image as matrix, with values in any range
cap: Maximum cap amount for normalization
data_type: numpy data type to set output variable to
Returns:
return 2D numpy array of type uint8, corresponding to limited range matrix

Examples:
>>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]),
... cap=1.0, data_type=np.float64)
array([[0. , 0.11111111, 0.22222222],
[0.33333333, 0.44444444, 1. ]])
>>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]]))
array([[127, 127, 85],
[ 0, 255, 42]], dtype=uint8)
"""
normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap
return normalized.astype(data_type)


def gaussian_noise(size: tuple, mean=0, std=0.05):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: gaussian_noise. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: mean

Please provide type hint for the parameter: std

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: gaussian_noise. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: mean

Please provide type hint for the parameter: std

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: gaussian_noise. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: mean

Please provide type hint for the parameter: std

"""Creates normal distribution array with given size to use as noise.

Args:
size: Size of the desired output image
mean: Mean to use within the Gaussian Function
std: Standard deviation to use within the Gaussian Function

Returns:
Matrix with given size, containing generated gaussian values.

Example:
>>> np.random.seed(0)
>>> gaussian_noise((5, 5))
array([[ 22.49166741, 5.10200441, 12.4789093 , 28.57138829,
23.81136437],
[-12.46029297, 12.11362732, -1.92980441, -1.31604036,
5.2351309 ],
[ 1.83655553, 18.54198721, 9.703231 , 1.55135646,
5.65925622],
[ 4.25434767, 19.04950818, -2.61576786, 3.9916132 ,
-10.88972068],
[-32.55062015, 8.33363709, 11.02156154, -9.46260401,
28.93937146]])
"""
noise = np.multiply(np.random.normal(mean, std, size), 255)
return noise


def gaussian_filter(k: int = 5, sigma: float = 1.0) -> np.ndarray:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the parameter: k

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the parameter: k

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the parameter: k

"""Generates a matrix with weights corresponding to centered gaussian distribution.

Args:
k: Lateral size of the kernel.
sigma: Standard deviation to be used when generating distribution

Returns:
np.ndarray: [k x k] sized kernel to be used as filter

Examples:
>>> gaussian_filter()
array([[0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902],
[0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621],
[0.02193823, 0.09832033, 0.16210282, 0.09832033, 0.02193823],
[0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621],
[0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902]])
"""
arx = np.arange((-k // 2) + 1.0, (k // 2) + 1.0)
x, y = np.meshgrid(arx, arx)
filt = np.exp(-(1 / 2) * (np.square(x) + np.square(y)) / np.square(sigma))
return filt / np.sum(filt)


def convolve(matrix: np.ndarray, kernel: np.ndarray) -> np.ndarray:
"""
Convolves a given kernel around a matrix through the frequency domain,
using Fourier transformations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using Fourier transformations.
using Fourier transforms.

Nitpick: "Fourier transforms", not "Fourier transformations"


Args:
matrix: Numpy array containing values to be convolved
kernel: Kernel (with all dimensions smaller than those of the matrix)
with weights to apply to each pixel.

Returns:
np.ndarray: Final equally shaped matrix with convoluted pixels.

Examples:
>>> matrix = np.array([[-1.45436567, 0.04575852],
... [-0.18718385, 1.53277921]])
>>> kernel = np.array([[1, 0], [0, 1]])
>>> convolve(matrix, kernel)
array([[ 0.07841354, -0.14142533],
[-0.14142533, 0.07841354]])
"""
if kernel.shape[0] > matrix.shape[1] or kernel.shape[1] > matrix.shape[1]:
return matrix
Comment on lines +138 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the kernel is too big, why not raise an error?

kernel = pad_to_size(kernel, matrix)

kernel_f = fft2(kernel)
matrix_f = fft2(matrix)

return ifftshift(ifft2(np.multiply(matrix_f, kernel_f))).real


def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.ndarray:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.ndarray:
def get_motion_psf(shape: tuple[int, int], angle: float, num_pixel_dist: int = 20) -> np.ndarray:

"""
Generate an array with given shape corresponding to
Point Spread Function for the desired angle.

Args:
shape: The shape of the image.
angle: The angle of the motion blur. Should be in degrees. [0, 360)
num_pixel_dist: The distance of the motion blur. [0, infinity)
Remember that the distance is measured in pixels.
Greater values will be more blurry

Returns:
np.ndarray: The point-spread array associated with the motion blur.

Examples:
>>> shape = (3, 3)
>>> angle = 15
>>> get_motion_psf(shape, angle, num_pixel_dist=3)
array([[0. , 0.33333333, 0. ],
[0. , 0.33333333, 0. ],
[0.33333333, 0. , 0. ]])
"""
psf = np.zeros(shape)
center = np.array([shape[0] - 1, shape[1] - 1]) // 2
radians = angle / 180 * np.pi
phase = np.array([np.cos(radians), np.sin(radians)])
for i in range(num_pixel_dist):
offset_x = int(center[0] - np.round(i * phase[0]))
offset_y = int(center[1] - np.round(i * phase[1]))
psf[offset_x, offset_y] = 1
psf /= psf.sum()

return psf


def richardson_lucy(
degraded: np.ndarray, function_kernel: np.ndarray, steps: int
) -> np.ndarray:
"""
Richardson-Lucy method to restore an image
affected by known motion blur function, as well as arbitrary steps
to perform during iterative image restoration.

Args:
degraded: Observed image, considered to be degraded - to be restored
function_kernel: Supposed function used to blur original image
steps: Iterative steps to take for restoring image

Returns:
np.ndarray: Restored image after method application


Examples:
>>> np.random.seed(0)
>>> shape = (3, 3)
>>> degraded = gaussian_noise(shape)
>>> kernel = np.identity(2)
>>> richardson_lucy(degraded, kernel, steps=4)
array([[1.16272775e+02, 0.00000000e+00, 7.58091075e+01],
[2.40582123e+01, 2.09006034e+01, 0.00000000e+00],
[0.00000000e+00, 1.52620152e-02, 1.31734948e+00]])
"""
estimated_img = np.full(shape=degraded.shape, fill_value=1, dtype="float64")

for _ in range(steps):
dividend = convolve(estimated_img, function_kernel)
quotient = np.divide(degraded, dividend, where=dividend != 0)

estimated_img = np.multiply(
estimated_img, convolve(quotient, np.flip(function_kernel))
)

estimated_img = np.clip(estimated_img, 0, 255)

return estimated_img


def main():

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function main

Please provide return type hint for the function: main. If the function does not return a value, please provide the type hint as: def function() -> None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function main

Please provide return type hint for the function: main. If the function does not return a value, please provide the type hint as: def function() -> None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file computer_vision/richardson_lucy.py, please provide doctest for the function main

Please provide return type hint for the function: main. If the function does not return a value, please provide the type hint as: def function() -> None:

input_file = str(input()).rstrip()
degraded = imageio.imread(input_file)

angle = int(input())
steps = int(input())
Comment on lines +227 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get prompt messages for the input calls


# The Richardson-Lucy iterative method is used to restore an image
# degraded by motion blur and noise.
restored = richardson_lucy(degraded, get_motion_psf(degraded.shape, angle), steps)

print(root_mean_square_error(degraded, restored))


if __name__ == "__main__":
main()