[CT404]: Finish Assignment 2

This commit is contained in:
2024-11-14 10:45:13 +00:00
parent 20c26c0cc3
commit cad9b09899
12 changed files with 237 additions and 28 deletions

View File

@ -0,0 +1,40 @@
import cv2
import numpy as np
# Load and pre-process binary image
binary_image = cv2.imread("./output/kernel_size_17.jpg", cv2.IMREAD_GRAYSCALE)
binary_image = cv2.medianBlur(binary_image, 3)
# Step 1.5: Extraction of Binary Regions of Interest / connected components
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=8)
# Initialize an empty mask for filtered regions
filtered_mask = np.zeros(binary_image.shape, dtype=np.uint8)
total_globules = 0
# Task 1.6: Filtering of Fat Globules
for i in range(1, num_labels):
area = stats[i, cv2.CC_STAT_AREA]
# Calculate compactness
perimeter = cv2.arcLength(cv2.findContours((labels == i).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0][0], True)
compactness = (perimeter ** 2) / area if area > 0 else 0
if (300 < area) and (compactness < 27):
total_globules += 1
filtered_mask[labels == i] = 255
cv2.imwrite("./output/filtered_fat_globules.jpg", filtered_mask)
print("Total globules: " + str(total_globules))
# Task 1.7: Calculation of the Fat Area
# Total area of the image in pixels (excluding the background)
total_image_area = binary_image.shape[0] * binary_image.shape[1]
# Total fat area (in pixels)
fat_area = np.sum(filtered_mask == 255)
# Calculate fat percentage
fat_percentage = (fat_area / total_image_area) * 100
print(f"Fat Area Percentage: {fat_percentage:.2f}%")

View File

@ -1,23 +0,0 @@
# Task 1.5: Extraction of Binary Regions of Interest / Connected Components
import cv2
import numpy as np
# read in noise-reduced image
image = cv2.imread("./output/kernel_size_25.jpg", cv2.IMREAD_GRAYSCALE)
# Find connected components
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
# Create an output image (color) to label components
output_img = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.uint8)
# Apply a single color (e.g., gray) to each component in the output image
for label in range(1, num_labels): # Skip background (label 0)
output_img[labels == label] = (200, 200, 200) # Light gray color for each component
# Overlay red text labels at component centroids
for i in range(1, num_labels): # Skip background (label 0)
x, y = int(centroids[i][0]), int(centroids[i][1])
cv2.putText(output_img, str(i), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) # Red color (BGR: (0, 0, 255))
cv2.imwrite("./output/region_of_interest.jpg", output_img)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

View File

@ -28,4 +28,42 @@ plt.imshow(magnitude_spectrum, cmap='gray')
plt.axis('off')
plt.savefig("./output/2_frequency_domain_low-pass_filter.jpg", bbox_inches='tight', pad_inches=0)
# Task 2.3: Frequency Domain Filtering
# Task 2.3: Frequency Domain Filtering for
channels = cv2.split(image)
filtered_channels = []
for channel in channels:
fft_channel = np.fft.fft2(channel)
# shift the zero frequency component to the center
fft_channel_shifted = np.fft.fftshift(fft_channel)
# create a Gaussian filter the same size as the channel
gaussian_kernel = cv2.getGaussianKernel(kernel_size[0], variance)
gaussian_kernel_2d = gaussian_kernel @ gaussian_kernel.T
# pad the Gaussian filter to match the size of the image channel
gaussian_kernel_padded = np.pad(gaussian_kernel_2d,
((0, fft_channel_shifted.shape[0] - gaussian_kernel_2d.shape[0]),
(0, fft_channel_shifted.shape[1] - gaussian_kernel_2d.shape[1])),
mode='constant', constant_values=0)
# shift the padded filter in the frequency domain
fft_gaussian_padded_shifted = np.fft.fftshift(np.fft.fft2(gaussian_kernel_padded))
# apply the low-pass filter to the channel
low_pass_filtered = fft_channel_shifted * fft_gaussian_padded_shifted
# perform the inverse FFT to get the filtered channel in the spatial domain
ifft_filtered = np.fft.ifft2(np.fft.ifftshift(low_pass_filtered))
# take the real part and normalize it
filtered_channel = np.real(ifft_filtered)
filtered_channel = np.clip(filtered_channel, 0, 255).astype(np.uint8)
# append the filtered channel to the list
filtered_channels.append(filtered_channel)
filtered_image_color = cv2.merge(filtered_channels)
cv2.imwrite("./output/3_filtered_color_image.jpg", filtered_image_color)

View File

@ -0,0 +1,53 @@
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Task 2.1: Spatial Domain
image = cv2.imread("jennifer.jpg")
kernel_size = (15, 15)
variance = 20
smoothed_image = cv2.GaussianBlur(image, kernel_size, variance)
cv2.imwrite("./output/jennifer_spatial.jpg", smoothed_image)
# Task 2.3: Frequency Domain Filtering for
channels = cv2.split(image)
filtered_channels = []
for channel in channels:
fft_channel = np.fft.fft2(channel)
# shift the zero frequency component to the center
fft_channel_shifted = np.fft.fftshift(fft_channel)
# create a Gaussian filter the same size as the channel
gaussian_kernel = cv2.getGaussianKernel(kernel_size[0], variance)
gaussian_kernel_2d = gaussian_kernel @ gaussian_kernel.T
# pad the Gaussian filter to match the size of the image channel
gaussian_kernel_padded = np.pad(gaussian_kernel_2d,
((0, fft_channel_shifted.shape[0] - gaussian_kernel_2d.shape[0]),
(0, fft_channel_shifted.shape[1] - gaussian_kernel_2d.shape[1])),
mode='constant', constant_values=0)
# shift the padded filter in the frequency domain
fft_gaussian_padded_shifted = np.fft.fftshift(np.fft.fft2(gaussian_kernel_padded))
# apply the low-pass filter to the channel
low_pass_filtered = fft_channel_shifted * fft_gaussian_padded_shifted
# perform the inverse FFT to get the filtered channel in the spatial domain
ifft_filtered = np.fft.ifft2(np.fft.ifftshift(low_pass_filtered))
# take the real part and normalize it
filtered_channel = np.real(ifft_filtered)
filtered_channel = np.clip(filtered_channel, 0, 255).astype(np.uint8)
# append the filtered channel to the list
filtered_channels.append(filtered_channel)
filtered_image_color = cv2.merge(filtered_channels)
cv2.imwrite("./output/jennifer_freq.jpg", filtered_image_color)

View File

@ -285,18 +285,50 @@ As can be seen from the above output, the optimal value chosen was 129.
\captionof{figure}{\mintinline{python}{kernel_size = 31}}
\end{minipage}
I chose to go with \mintinline{python}{kernel_size = 25} as it seemed to give the optimal balance between removing noise without significantly reducing the size of the remaining fat globules .
I chose to open the image with a structuring element that had \mintinline{python}{kernel_size = 17} as it seemed to give the optimal balance between removing noise without significantly reducing the size of the remaining fat globules.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{../code/task1/output/kernel_size_25.jpg}
\caption{Chosen noise threshold: \mintinline{python}{kernel_size = 25}}
\includegraphics[width=0.5\textwidth]{../code/task1/output/kernel_size_17.jpg}
\caption{Chosen noise threshold: \mintinline{python}{kernel_size = 17}}
\end{figure}
\subsection{Extraction of Binary Regions of Interest / Connected Components}
\begin{code}
\inputminted[firstline=1, lastline=9, linenos, breaklines, frame=single]{python}{../code/task1/5-7.py}
\caption{Task 1.5 section of \mintinline{python}{5-7.py}}
\end{code}
I'm not sure why, but no matter what level of noise removal I tried the connected components extraction with, the connected components always came out quite jagged.
To correct for this, I did some additional noise reduction by using a blur on the image to remove some of the white noise that was appearing in.
I also used a higher value of connectivity with \mintinline{python}{connectivity=8}, keeping components that were touching at all rather than components that just shared an edge.
\subsection{Filtering of Fat Globules}
\begin{code}
\inputminted[firstline=11, lastline=29, linenos, breaklines, frame=single]{python}{../code/task1/5-7.py}
\caption{Task 1.6 section of \mintinline{python}{5-7.py}}
\end{code}
I filtered the fat globules based off size \& compactness, using the compactness measure to remove globules that were not globule-shaped and the area measure to remove globules that were too small to be a globule.
I used a maximum compactness of 27 and a minimum area of 300 to filter the globules, resulting in a total of 35 fat globules.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{../code/task1/output/filtered_fat_globules.jpg}
\caption{Result of fat globule filtering}
\end{figure}
\subsection{Calculation of the Fat Area}
\begin{code}
\inputminted[firstline=31, lastline=40, linenos, breaklines, frame=single]{python}{../code/task1/5-7.py}
\caption{Task 1.7 section of \mintinline{python}{5-7.py}}
\end{code}
The percentage of the image covered by fat globules was 15.33\%.
\newpage
\section{Filtering of Images in Spatial \& Frequency Domains}
\begin{figure}[H]
\centering
@ -318,7 +350,7 @@ After some experimentation, I chose parameter values of \mintinline{python}{kern
\caption{Output of \mintinline{python}{1_spatial_domain.jpg}}
\end{figure}
\subsection{Frequency Domain Filtering}
\subsection{Frequency Domain Low-Pass Filter}
\begin{code}
\inputminted[firstline=15, lastline=29, linenos, breaklines, frame=single]{python}{../code/task2/task2.py}
\caption{Task 2.2 section of \mintinline{python}{task2.py}}
@ -330,4 +362,73 @@ After some experimentation, I chose parameter values of \mintinline{python}{kern
\caption{Zero-centered low-pass filter of Gaussian Kernel}
\end{figure}
\subsection{Frequency Domain Filtering}
\begin{code}
\inputminted[firstline=31, lastline=70, linenos, breaklines, frame=single]{python}{../code/task2/task2.py}
\caption{Task 2.3 section of \mintinline{python}{task2.py}}
\end{code}
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{../code/task2/output/3_filtered_color_image.jpg}
\caption{Frequency domain filtered image}
\end{figure}
The low pass filter type used was the same as in Section 2.2: a Gaussian filter with \mintinline{python}{(15,15)} and \mintinline{python}{2}.
\subsection{Comparison}
\noindent
\begin{minipage}{0.49\textwidth}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{../code/task2/output/1_spatial_domain.jpg}
\caption{Spatial domain filtered image}
\end{figure}
\end{minipage}
\hfill
\begin{minipage}{0.49\textwidth}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{../code/task2/output/3_filtered_color_image.jpg}
\caption{Frequency domain filtered image}
\end{figure}
\end{minipage}
The two images are very similar, having shared the same type of low pass filter.
However, the frequency domain filtered image has retained more colour range, and has an overall less blurred appearance.
The spatial domain filtering has applied a general blur across the entire image, making the filtering more obvious.
On the other hand, the frequency domain filtering is more subtle and reduces the visibility of wrinkles while retaining some definition in the eyes, lips, and stubble.
Overall, I would prefer the frequency domain filtering for this task; despite its increased complexity, it creates a more subtle and convincing effect that would be easily mistakable for an unfiltered image.
\subsection{Unseen Image Testing}
\noindent
\begin{minipage}{0.33\textwidth}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{../code/task2/jennifer.jpg}
\caption{Original Image}
\end{figure}
\end{minipage}
\hfill
\begin{minipage}{0.33\textwidth}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{../code/task2/output/jennifer_spatial.jpg}
\caption{Spatial domain filtered image}
\end{figure}
\end{minipage}
\hfill
\begin{minipage}{0.33\textwidth}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{../code/task2/output/jennifer_freq.jpg}
\caption{Frequency domain filtered}
\end{figure}
\end{minipage}
Again, the two filtered images are very similar.
In my opinion, the frequency domain filtered image performed better here again, as it has a more dynamic colour range and more subtle blurring.
The skin looks very artificially smoothed in the spatial domain filtered image, but more natural in the frequency domain filtered image.
The hair looks more blurred and out-of-focus in the spatial domain filtered image, but looks sharper and more natural in the frequency domain filtered image.
\end{document}