uz_assignments/assignment3/solution.py

214 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import numpy as np
import numpy.typing as npt
from matplotlib import pyplot as plt
import cv2
import uz_framework.image as uz_image
import uz_framework.text as uz_text
##############################################
# EXCERCISE 1: Exercise 1: Image derivatives #
##############################################
def ex1():
#one_a()
#one_b()
#one_c()
one_d()
def one_a() -> None:
"""
Follow the equations above and derive the equations used to compute first and
second derivatives with respect to y: Iy(x, y), Iyy(x, y), as well as the mixed derivative
Ixy(x, y)
"""
def one_b() -> None:
"""
Implement a function that computes the derivative of a 1-D Gaussian kernel
Implement the function gaussdx(sigma) that works the same as function gauss
from the previous assignment. Dont forget to normalize the kernel. Be careful as
the derivative is an odd function, so a simple sum will not do. Instead normalize the
kernel by dividing the values such that the sum of absolute values is 1. Effectively,
you have to divide each value by sum(abs(gx(x))).
"""
sigmas = [0.5, 1, 2]
for sigma in sigmas:
kernel = uz_image.gaussdx(sigma)
print(kernel)
def one_c() -> None:
"""
The properties of the filter can be analyzed by using an impulse response function.
This is performed as a convolution of the filter with a Dirac delta function. The
discrete version of the Dirac function is constructed as a finite image that has all
elements set to 0 except the central element, which is set to a high value (e.g. 1).
Generate a 1-D Gaussian kernel G and a Gaussian derivative kernel D.
What happens if you apply the following operations to the impulse image?
(a) First convolution with G and then convolution with GT
(b) First convolution with G and then convolution with DT
(c) First convolution with D and then convolution with GT
(d) First convolution with GT and then convolution with D.
(e) First convolution with DT and then convolution with G.
Is the order of operations important? Display the images of the impulse responses
for different combinations of operations.
"""
impulse = uz_image.generate_dirac_impulse(50)
gauss = np.array([uz_image.get_gaussian_kernel(3)])
gaussdx = np.array([uz_image.gaussdx(3)])
# Becouse CV2 applies the correlation instead of convolution, we need to flip the kernels
gauss = np.flip(gauss, axis=1)
gaussdx = np.flip(gaussdx, axis=1)
fig, axs = plt.subplots(2, 3)
# Plot impulse only
axs[0, 0].imshow(impulse, cmap='gray')
axs[0, 0].set_title('Impulse')
# Plot impulse after convolution with G and GT
g_gt_impulse = impulse.copy()
g_gt_impulse = cv2.filter2D(g_gt_impulse, cv2.CV_64F, gauss)
g_gt_impulse = cv2.filter2D(g_gt_impulse, cv2.CV_64F, gauss.T)
axs[1, 0].imshow(g_gt_impulse, cmap='gray')
axs[1, 0].set_title('impulse * G * GT')
# Plot impulse after convolution with G and DT
g_dt_impulse = impulse.copy()
g_dt_impulse = cv2.filter2D(g_dt_impulse, cv2.CV_64F, gauss)
g_dt_impulse = cv2.filter2D(g_dt_impulse, cv2.CV_64F, gaussdx.T)
axs[0, 1].imshow(g_dt_impulse, cmap='gray')
axs[0, 1].set_title('impulse * G * DT')
# Plot impulse after convolution with D and GT
d_gt_impulse = impulse.copy()
d_gt_impulse = cv2.filter2D(d_gt_impulse, cv2.CV_64F, gaussdx)
d_gt_impulse = cv2.filter2D(d_gt_impulse, cv2.CV_64F, gauss.T)
axs[0, 2].imshow(d_gt_impulse, cmap='gray')
axs[0, 2].set_title('impulse * D * GT')
# Plot impulse after convolution with GT and D
gt_d_impulse = impulse.copy()
gt_d_impulse = cv2.filter2D(gt_d_impulse, cv2.CV_64F, gauss.T)
gt_d_impulse = cv2.filter2D(gt_d_impulse, cv2.CV_64F, gaussdx)
axs[1, 1].imshow(gt_d_impulse, cmap='gray')
axs[1, 1].set_title('impulse * GT * D')
# Plot impulse after convolution with DT and G
dt_g_impulse = impulse.copy()
dt_g_impulse = cv2.filter2D(dt_g_impulse, cv2.CV_64F, gaussdx.T)
dt_g_impulse = cv2.filter2D(dt_g_impulse, cv2.CV_64F, gauss)
axs[1, 2].imshow(dt_g_impulse, cmap='gray')
axs[1, 2].set_title('impulse * DT * G')
plt.show()
def one_d() -> None:
"""
Implement a function that uses functions gauss and gaussdx to compute both
partial derivatives of a given image with respect to x and with respect to y.
Similarly, implement a function that returns partial second order derivatives of a
given image.
Additionally, implement the function gradient_magnitude that accepts a grayscale
image I and returns both derivative magnitudes and derivative angles. Magnitude
is calculated as m(x, y) = sqrt(Ix(x,y)^2 + Iy(x, y)^2) and angles are calculated as
φ(x, y) = arctan(Iy(x, y)/Ix(x, y))
Hint: Use function np.arctan2 to avoid division by zero for calculating the arctangent function.
Use all the implemented functions on the same image and display the results in the
same window.
"""
museum = uz_image.imread_gray('./images/museum.jpg', uz_image.ImageType.float64)
museum_x, museum_y = uz_image.derive_image_first_order(museum, 1)
(museum_xx, museum_xy) , (_, museum_yy) = uz_image.derive_image_second_order(museum, 1)
derivative_magnitude, derivative_angle = uz_image.gradient_magnitude(museum, 1)
fig, axs = plt.subplots(2, 4)
fig.suptitle('Museum')
axs[0,0].imshow(museum, cmap='gray')
axs[0,0].set_title('Original')
axs[0, 1].imshow(museum_x, cmap='gray')
axs[0, 1].set_title('I_x')
axs[0, 2].imshow(museum_y, cmap='gray')
axs[0, 2].set_title('I_y')
axs[1, 0].imshow(museum_xx, cmap='gray')
axs[1, 0].set_title('I_xx')
axs[1, 1].imshow(museum_xy, cmap='gray')
axs[1, 1].set_title('I_xy')
axs[1, 2].imshow(museum_yy, cmap='gray')
axs[1, 2].set_title('I_yy')
axs[0, 3].imshow(derivative_magnitude, cmap='gray')
axs[0, 3].set_title('I_mag')
axs[1, 3].imshow(derivative_angle, cmap='gray')
axs[1, 3].set_title('I_dir')
plt.show()
############################################
# EXCERCISE 2: Exercise 1: Edges in images #
############################################
def ex2():
two_a()
two_b()
def two_a():
"""
Firstly, create a function findedges that accepts an image I, and the parameters
sigma and theta.
The function should create a binary matrix Ie that only keeps pixels higher than
threshold theta:
Ie(x, y) =
1 ; Imag(x, y) ≥ ϑ
0 ; otherwise (6)
Test the function with the image museum.png and display the results for different
values of the parameter theta. Can you set the parameter so that all the edges in
the image are clearly visible?
"""
SIGMA = 0.2
THETA = 0.16
museum = uz_image.imread_gray('./images/museum.jpg', uz_image.ImageType.float64)
museum_edges = uz_image.find_edges_primitive(museum, SIGMA, THETA)
plt.imshow(museum_edges, cmap='gray')
plt.show()
def two_b():
"""
Using magnitude produces only a first approximation of detected edges. Unfortunately,
these are often wide and we would like to only return edges one pixel wide.
Therefore, you will implement non-maxima suppression based on the image derivative magnitudes and angles.
Iterate through all the pixels and for each search its
8-neighborhood. Check the neighboring pixels parallel to the gradient direction and
set the current pixel to 0 if it is not the largest in the neighborhood (based on
derivative magnitude). You only need to compute the comparison to actual pixels,
interpolating to more accuracy is not required.
"""
SIGMA = 0.2
THETA = 0.16
museum = uz_image.imread_gray('./images/museum.jpg', uz_image.ImageType.float64)
museum_edges = uz_image.find_edges_nms(museum, SIGMA, THETA)
plt.imshow(museum_edges, cmap='gray')
plt.show()
# ######## #
# SOLUTION #
# ######## #
def main():
#ex1()
ex2()
#ex3()
if __name__ == '__main__':
main()