diff --git a/Exercise_1/IntroML_Exercise1.pdf b/Exercise_1/IntroML_Exercise1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..87b08b72f25046f1797da6096e16a5683817bec6 Binary files /dev/null and b/Exercise_1/IntroML_Exercise1.pdf differ diff --git a/Exercise_1/data/contrast.jpg b/Exercise_1/data/contrast.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c00cdc6c52715aebc1901fb18a9790cad2258645 Binary files /dev/null and b/Exercise_1/data/contrast.jpg differ diff --git a/Exercise_1/data/hello.png b/Exercise_1/data/hello.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfa24f503bec3fe97a4f28f559b7d19d9253035 Binary files /dev/null and b/Exercise_1/data/hello.png differ diff --git a/Exercise_1/data/hello_gaussian.png b/Exercise_1/data/hello_gaussian.png new file mode 100644 index 0000000000000000000000000000000000000000..ec5794618cd00c12597027bfeb23b8954f79914e Binary files /dev/null and b/Exercise_1/data/hello_gaussian.png differ diff --git a/Exercise_1/data/hello_poisson.png b/Exercise_1/data/hello_poisson.png new file mode 100644 index 0000000000000000000000000000000000000000..ce4251dc6526a392f7f98589b7a62c2ba72e2316 Binary files /dev/null and b/Exercise_1/data/hello_poisson.png differ diff --git a/Exercise_1/data/hello_salt_pepper.png b/Exercise_1/data/hello_salt_pepper.png new file mode 100644 index 0000000000000000000000000000000000000000..5a8203d626643cb8832400deced2596085ab2f81 Binary files /dev/null and b/Exercise_1/data/hello_salt_pepper.png differ diff --git a/Exercise_1/data/hello_uniform.png b/Exercise_1/data/hello_uniform.png new file mode 100644 index 0000000000000000000000000000000000000000..08161eeecb246ed9418c17dfb51dfc05aee3ebfe Binary files /dev/null and b/Exercise_1/data/hello_uniform.png differ diff --git a/Exercise_1/data/kitty.png b/Exercise_1/data/kitty.png new file mode 100644 index 0000000000000000000000000000000000000000..402070f273cc975e3c469a7c9ac1b3b970e7c6d3 Binary files /dev/null and b/Exercise_1/data/kitty.png differ diff --git a/Exercise_1/data/runes.png b/Exercise_1/data/runes.png new file mode 100644 index 0000000000000000000000000000000000000000..d9ffb907cf922182596e1acca244fe0ec9eff73e Binary files /dev/null and b/Exercise_1/data/runes.png differ diff --git a/Exercise_1/histogram_equalization.py b/Exercise_1/histogram_equalization.py new file mode 100644 index 0000000000000000000000000000000000000000..a5c759130b35d848e4299d168f4ab0b7c2281c3c --- /dev/null +++ b/Exercise_1/histogram_equalization.py @@ -0,0 +1,76 @@ +import numpy as np +import cv2 +import matplotlib.pyplot as plt + + +def load_image(path: str) -> np.ndarray: + # Load the image using CV2 and return it. + loaded_image = cv2.imread(path, cv2.IMREAD_GRAYSCALE) + if loaded_image is None: + raise FileNotFoundError(f"Cannot load image at {path}") + return loaded_image + + +def compute_histogram(image: np.ndarray) -> np.ndarray: + # ToDo: Create a histogram for the given image (256 values). + # ToDo: Don't use functions like np.histogram. + # ToDo: It is easier if you flatten your image first. + histogram = np.zeros(0) + return histogram + + +def compute_cdf(histogram: np.ndarray) -> np.ndarray: + # ToDo: Compute the CDF. + # ToDo: Don't forget to normalize it (turn it into a distribution). + cdf = np.zeros(0) + return cdf + + +def equalize_image(image: np.ndarray, cdf: np.ndarray) -> np.ndarray: + # ToDo: Apply histogram equalization to the given image. + # ToDo: Hint: Flatten the image first and reshape it again in the end. + equalized_image = np.zeros(0) + return equalized_image + + +def save_image(image: np.ndarray, path: str) -> None: + # Save the image to the given folder. + cv2.imwrite(path, image) + + +def show_images(original_image: np.ndarray, equalized_image: np.ndarray) -> None: + # ToDo: Display the original and the equalized images next to each other. + plt.figure(figsize=(10, 5)) + plt.subplot(1, 2, 1) + plt.imshow(original_image, cmap='gray') + plt.title('Original Image') + plt.axis('off') + + plt.subplot(1, 2, 2) + plt.imshow(equalized_image, cmap='gray') + plt.title('Equalized Image') + plt.axis('off') + + plt.tight_layout() + plt.show() + + +def histogram_equalization(input_path: str, output_path: str) -> None: + # ToDo: Combine the different functions into one. + loaded_image = load_image(input_path) + histogram = compute_histogram(loaded_image) + cdf = compute_cdf(histogram) + equalized_image = equalize_image(loaded_image, cdf) + save_image(equalized_image, output_path) + + +if __name__ == '__main__': + # Load the images and perform histogram equalization. + input_image_path = 'data/hello.png' + output_image_path = 'data/kitty.png' + histogram_equalization(input_image_path, output_image_path) + + # Show the images next to each other. + original = load_image(input_image_path) + equalized = load_image(output_image_path) + show_images(original, equalized) diff --git a/Exercise_1/noise.py b/Exercise_1/noise.py new file mode 100644 index 0000000000000000000000000000000000000000..e4fcd3509536007ae5194ca354a2be9a685188f8 --- /dev/null +++ b/Exercise_1/noise.py @@ -0,0 +1,94 @@ +import numpy as np +import cv2 +import matplotlib.pyplot as plt + + +def load_image(file_path: str) -> np.ndarray: + # Load the image (either gray or colour). + loaded_image = cv2.imread(file_path, cv2.IMREAD_UNCHANGED) + if loaded_image is None: + raise FileNotFoundError(f"Cannot load image at {file_path}") + return loaded_image + + +def save_image(image: np.ndarray, file_path: str) -> None: + # Save the image. + cv2.imwrite(file_path, image) + + +def add_gaussian_noise(image: np.ndarray, mean: float = 0.0, sigma: float = 10.0) -> np.ndarray: + # ToDo: Generate gaussian noise and add it to the image. + # ToDo: Hint: Look at the options among np.random to generate the noise. + # ToDo: Hint: Don't forget to clip the values. + return image + + +def add_salt_and_pepper_noise(image: np.ndarray, salt_prob: float = 0.01, pepper_prob: float = 0.01) -> np.ndarray: + # ToDo: Generate random salt and pepper noise based on the provided probabilities. + # ToDo: Hint: Look at the options among np.random to generate the noise. + return image + + +def add_poisson_noise(image: np.ndarray) -> np.ndarray: + # ToDo: Add poisson noise to the image. + # ToDo: Hint: Look at the options among np.random to generate the noise. + return image + + +def add_uniform_noise(image: np.ndarray, low: float = -20.0, high: float = 20.0) -> np.ndarray: + # ToDo: Add uniform noise to the image, which is sampled uniformly from the available values. + # ToDo: Hint: Look at the options among np.random to generate the noise. + return image + + +def display_images(original: np.ndarray, processed: np.ndarray, title: str) -> None: + # Transform the colour image (BGR) into an RGB image. + def to_rgb(image): + return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if image.ndim == 3 else image + + adapted_original_image = to_rgb(original) + adapted_noise_image = to_rgb(processed) + + plt.figure(figsize=(10, 5)) + plt.subplot(1, 2, 1) + plt.imshow(adapted_original_image, cmap=None if adapted_original_image.ndim == 3 else 'gray') + plt.title('Original') + plt.axis('off') + + plt.subplot(1, 2, 2) + plt.imshow(adapted_noise_image, cmap=None if adapted_noise_image.ndim == 3 else 'gray') + plt.title(title) + plt.axis('off') + + plt.tight_layout() + plt.show() + + +if __name__ == '__main__': + # Example usage + input_file = 'data/hello.png' + gaussian_file = 'data/hello_gaussian.png' + salt_pepper_file = 'data/hello_salt_pepper.png' + poisson_file = 'data/hello_poisson.png' + uniform_file = 'data/hello_uniform.png' + + original_image = load_image(input_file) + + # Apply noise to the images. + gaussian = add_gaussian_noise(original_image) + save_image(gaussian, gaussian_file) + + salt_pepper = add_salt_and_pepper_noise(original_image) + save_image(salt_pepper, salt_pepper_file) + + poisson = add_poisson_noise(original_image) + save_image(poisson, poisson_file) + + uniform = add_uniform_noise(original_image) + save_image(uniform, uniform_file) + + # Display the images side by side. + display_images(original_image, gaussian, 'Gaussian Noise') + display_images(original_image, salt_pepper, 'Salt & Pepper Noise') + display_images(original_image, poisson, 'Poisson Noise') + display_images(original_image, uniform, 'Uniform Noise') diff --git a/Exercise_1/otsu.py b/Exercise_1/otsu.py new file mode 100644 index 0000000000000000000000000000000000000000..86e3ce7b50f5f7b4e1be4a2da94462b686591034 --- /dev/null +++ b/Exercise_1/otsu.py @@ -0,0 +1,76 @@ +import numpy as np +import cv2 +import matplotlib.pyplot as plt + + +def compute_histogram(image: np.ndarray) -> np.ndarray: + # ToDo: Compute a grayscale histogram with 256 bins. + histogram = np.zeros(0) + return histogram + + +def p_helper(prob: np.ndarray, theta: int) -> tuple[float, float]: + # ToDo: Compute the class probabilities p0 and p1 for the current threshold theta. + p0 = 0.0 + p1 = 0.0 + return p0, p1 + + +def mu_helper(prob: np.ndarray, theta: int, p0: float, p1: float) -> tuple[float, float]: + # ToDo: Compute the class means mu0 and mu1 for the current threshold theta. + mu0 = 0.0 + mu1 = 0.0 + return mu0, mu1 + + +def otsu_threshold(histogram: np.ndarray) -> int: + # ToDo: Compute Otsu's threshold from a histogram using p_helper and mu_helper. + # ToDo: Normalize the histogram to its probabilities (PDF). + prob = histogram.astype(np.float64) + + # ToDo: Iterate over all possible thresholds, select the best one. + # ToDo: Hint: Skip invalid splits (p0 == 0 or p1 == 0). + max_variance = 0.0 + best_threshold = 0 + + return best_threshold + + +def otsu_binarize(image: np.ndarray) -> tuple[np.ndarray, int]: + # ToDo: Binarize the threshold image. + # ToDo: Simply combine the existing functions. + theta = 0 + new_image = np.zeros(0) + return new_image, theta + + +def custom_binarization(image: np.ndarray, theta: int) -> tuple[np.ndarray, int]: + # ToDo: Binarize the image with a custom value. + new_image = np.where(image > theta, 255, 0).astype(np.uint8) + return new_image, theta + + +if __name__ == '__main__': + # Load grayscale image. + loaded_image = cv2.imread('data/runes.png', cv2.IMREAD_GRAYSCALE) + if loaded_image is None: + raise FileNotFoundError("Cannot load the image.") + + # Compute Otsu's binarization or perform a custom binarization. Comment out one of the options. + # binarized_image, threshold = otsu_binarize(loaded_image) + binarized_image, threshold = custom_binarization(loaded_image, 180) + + # Display the original and the binarized image next to each other. + plt.figure(figsize=(8, 4)) + plt.subplot(1, 2, 1) + plt.imshow(loaded_image, cmap='gray') + plt.title('Original') + plt.axis('off') + + plt.subplot(1, 2, 2) + plt.imshow(binarized_image, cmap='gray') + plt.title(f"Otsu Binarization (t={threshold})") + plt.axis('off') + + plt.tight_layout() + plt.show()