• K-Means

    Separating data into distinct clusters, organizing diverse information and simplifying complexity with vibrant clarity

  • Random Forest for Regression

    Combining decision trees, it provides predictive accuracy that illuminates the path to regression analysis

  • Support Vector Machines for Regression

    Leveraging mathematical precision, it excels in predicting values by carving precise pathways through data complexities

Sunday, February 25, 2024

Intensity Transformations (Part 2)

Welcome back to our exploration of Intensity Transformations. This post is a continuation of our previous discussion, where we covered the general introduction, some auxiliary functions, Gamma Transformation, and Log Transformation. If you haven't read the first part yet, I highly recommend you start here to understand the foundational concepts. In this post, we focus on the Contrast-Stretching and Threshold transformations.


Contrast-Stretching Transformations

As the name suggests, the Contrast-Stretching technique aims to enhance the contrast in an image by stretching its intensity values to span the entire dynamic range. It expands a narrow range of input levels into a wide (stretched) range of output levels, resulting in an image with higher contrast. 

The commonly used formula for the Contrast-Stretching Transformation is:

\begin{equation*} s = \frac{1}{1 + \left( \dfrac{m}{r} \right)^E} \end{equation*}

In this equation, $m$ denotates the intensity value around which the stretching is centered, and $E$ controls the slope of the function. Below is a graphical representation of the curves generated using different values of $E$:

These curves illustrate how varying $E$ affects the contrast-stretching process. A higher value of $E$ results in a steeper curve, leading to more pronounced contrast enhancement around the intensity level $m$, darkening the intensity levels below $m$ and brightening the levels above it.


Python Implementation

We now introduce a practical implementation with the stretch_transformation function. This function applies the stretch transformation to an image, enhancing its contrast by expanding the intensity values.

# Function to apply the Stretch Transformation
def stretch_transformation(Img, m = 128, E = 4):
    # Apply the Stretch transformation formula
    s = 1 / (1 + (m / Img)**E )
    # Scale the transformed image to the range of 0-255 and convert to uint8
    Img_transformed = np.array(scaling(s, 0, 255), dtype = np.uint8)
    return Img_transformed

The function first applies the Stretch Transformation formula to the input image. After the transformation, it employs the scaling function to scale the transformed pixel values back to the range of 0-255. Finally, the transformed image is converted to an 8-bit unsigned integer format (np.uint8).

Having explored the concept of Contrast-Stretching transformations, we can apply this technique to a real image using the following code:

# Load an image in grayscale
Im3 = cv2.imread('/content/Image_3.png', cv2.IMREAD_GRAYSCALE)
# Apply the Stretch transformation
Im3_transformed = stretch_transformation(Im3, m = 60, E = 1)

# Prepare images for display
images = [Im3, Im3_transformed]
titles = ['Original Image', 'Stretching Transformation']
# Use the plot_images function to display the original and transformed images
plot_images(images, titles, 1, 2, (10, 7))

We first load an image in grayscale from a specified path (/content/Image_3.png). We then apply the Stretch Transformation with a midpoint $m = 60$  and a slope $E = 1$. We finally apply the plot_images function to display the original and transformed images. The results are shown below:

In the original image, the details and bones of the skeleton are discernible only in certain regions. Particularly, the lower and upper extremities are barely visible, obscured by the limited contrast of the image. However, the transformed image presents a stark contrast. The details are noticeably more visible. This enhanced visibility is a direct result of the stretching transformation, which has effectively expanded the range of intensity values.


Threshold Transformations

Thresholding is the simplest yet effective method for segmenting images. It involves converting an image from color or grayscale to a binary format, essentially reducing it to just two colors: black and white.

This technique is most commonly used to isolate areas of interest within an image, effectively ignoring the parts that are not relevant to the specific task. It's particularly useful in applications where the distinction between objects and the background is crucial.

In the simplest form of thresholding, each pixel in an image is compared to a predefined threshold value $T$. If the intensity $f(x,y)$ of a pixel is less than $T$, that pixel is turned black (0 value).  Conversely, if a pixel's intensity is greater than $T$, it is turned white (255 value). This binary transformation creates a clear distinction between higher and lower intensity values, simplifying the image's content for further analysis or processing.


Python Implementation

Having discussed the concept of threshold transformations, we now apply this technique to a real image using the following code:

# Load an image in grayscale
Im3 = cv2.imread('/content/Image_3.png', cv2.IMREAD_GRAYSCALE)
# Apply the Threshold transformation
_ , Im3_threshold = cv2.threshold(Im3, 25, Im3.max(), cv2.THRESH_BINARY)

# Prepare images for display
images = [Im3, Im3_threshold]
titles = ['Original Image', 'Threshold Transformation']
# Use the plot_images function to display the original and transformed images
plot_images(images, titles, 1, 2, (10, 7))

We first load an image in grayscale from a specified path (/content/Image_3.png). We then apply the threshold transformation using OpenCV's threshold function, the threshold value is set to $25$, and the maximum value to Im3.max(). This means that all pixel values below $25$ are set to $0$ (black), and those above $25$ are set to the maximum pixel value of the image (white). Finally, we apply the plot_images function to display the original and transformed images. The results are shown below:

The original image is the same as in the previous section, but now the transformation has been performed using a threshold function. This resulted in a binary image that represents the skeleton. However, it loses information about the extremities and exhibits poor segmentation in the pelvic and rib areas.


Bonus: Adaptive Threshold Transformations

Adaptive Threshold Transformation is a sophisticated alternative to the basic thresholding technique. While standard thresholding applies a single threshold value across the entire image, adaptive thresholding adjusts the threshold dynamically over different regions of the image. This approach is particularly effective in dealing with images where lighting conditions vary across different areas, leading to uneven illumination.

Adaptive thresholding works by calculating the threshold for a pixel based on a small region around it, commonly employing statistical measures, such as the mean or median. This means that different parts of the image can have different thresholds, allowing for more nuanced and localized segmentation. The method is especially useful in scenarios where the background brightness or texture varies significantly, posing challenges for global thresholding methods.

Adaptive thresholding is widely used in applications such as text recognition, where it helps to isolate characters from a variable background, or in medical imaging, where it can enhance the visibility of features in areas with differing lighting conditions.


Python Implementation

Having discussed the concept of threshold transformations, we now apply this technique to a real image using the following code:

After discussing the theory behind adaptive threshold transformation, we are ready for the practical implementation. We apply this technique to the same image as the previous threshold transformation with the following code:

# Load an image in grayscale
Im3 = cv2.imread('/content/Image_3.png', cv2.IMREAD_GRAYSCALE)
# Apply the Adaptive Threshold transformation
Im3_adapt_threshold = cv2.adaptiveThreshold(Im3, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 61,-2)

# Prepare images for display
images = [Im3, Im3_adapt_threshold]
titles = ['Original Image', 'Adaptive Threshold']
# Use the plot_images function to display the original and transformed images
plot_images(images, titles, 1, 2, (10, 7))

We first load an image in grayscale from a specified path (/content/Image_3.png). Then, we apply the adaptive threshold transformation using OpenCV's adaptiveThreshold function. The parameters include a maximum value of $255$, the adaptive method cv2.ADAPTIVE_THRESH_MEAN_C (which uses the mean of the neighborhood area), the threshold type cv2.THRESH_BINARY, a block size of $61$ (determining the size $61 \times 61$ of the neighborhood area), and a constant of $-2$ subtracted from the mean. Finally, we use the plot_images function to display the original and transformed images side by side. The results are shown below:

Unlike the results obtained with a basic thresholding technique, the adaptive threshold transformation allows for the successful segmentation of the complete skeleton by tuning the method's parameters. This approach results in a more detailed and comprehensive visualization of the skeletal structure. From the extremities to the ribs, pelvis, and skull, each part of the skeleton is clearly delineated.


Share:

Sunday, January 14, 2024

Intensity Transformations (Part 1)

In the world of digital image processing, the art of image manipulation is essential across a wide range of disciplines, from medical diagnostics to advanced graphic design. Central to this field are intensity transformations, a cornerstone technique in enhancing and correcting images. Through the precise adjustment of pixel values, intensity transformations provide the means to intricately refine brightness, contrast, and the overall aesthetic appeal of images. This post explore the aspects of these transformations, with a particular emphasis on their implementation in python.

The techniques discussed in this post operate directly on the pixels of an image, meaning they work within the spatial domain. In contrast, some image processing techniques are formulated in other domains, these methods work by transforming the input image into another domain, implementing the respective techniques there, and then applying an inverse transformation to return to the spatial domain.

The spatial domain processes covered in this post are based on the expression:

\begin{equation*} g(x,y) = T \left[ f(x,y) \right] \end{equation*}

Where $f(x,y)$ represents the input image, $g(x,y)$ is the output image, and $T$ is the transformation. This transformation is an operator applied to the pixels of the image $f$ and is defined over a neighborhood of the point $(x,y)$. The smallest possible neighborhood is of size $1 \times 1$, in this case, $g$ depends only on the value of $f$ at a single point (pixel), making the transformation $T$ an intensity transformation. We denote $r = f(x,y)$ as the intensity of $f$ and $s = g(x,y)$ as the intensity of $g$, thus, the intensity transformation functions can be expressed in a simplified form:

\begin{equation*} s = T \left( r \right) \end{equation*}

There are many types of intensity transformations, including Binary, Power-Law, Logarithmic, among others. In the following sections, these transformations will be mathematically explained and implemented in Python, for this we need to import the following libraries: 

# Importing libraries
import numpy as np
import cv2
import matplotlib.pyplot as plt
  • NumPy (numpy): Essential for numerical operations, NumPy offers extensive support for arrays and matrices, which are fundamental in handling image data.
  • OpenCV (cv2): A versatile library for image processing tasks. We use OpenCV for reading, processing, and manipulating images.
  • Matplotlib (matplotlib.pyplot): Useful for visualizing images and their transformations, Matplotlib help us to display the results of our image processing tasks.

To effectively demonstrate the results of intensity transformations, we implemented the function plot_images, designed to display multiple images in a single figure using subplots. This function will be particularly useful in the following sections of the post to showcase the before-and-after effects of applying various transformations.

# Function to plot multiple images using subplots
def plot_images(images, titles, n_rows, n_cols, figsize):
    # Create a figure with specified size
    fig, axes = plt.subplots(n_rows, n_cols, figsize=figsize)
    # Flatten the axes array for easy indexing
    axes = axes.flatten()

    # Loop through the images and titles to display them
    for i, (img, title) in enumerate(zip(images, titles)):
        # Display image in grayscale
        axes[i].imshow(img, cmap='gray')
        # Set the title for each subplot
        axes[i].set_title(title, fontsize = 22)
        # Hide axes ticks for a cleaner look
        axes[i].axis('off')

    # Adjust the layout to prevent overlap
    plt.tight_layout()
    # Show the compiled figure with all images
    plt.show()

This function takes an array of images and their corresponding titles, along with the desired number of rows and columns for the subplots, and the size of the figure. It then creates a figure with the specified layout, displaying each image with its title. The images are shown in grayscale, which is often preferred for intensity transformation demonstrations. The function also ensures a clean presentation by hiding the axes ticks and adjusting the layout to prevent overlap.

Sometimes it's necessary to scale the intensity values of an image to a specific range, particularly when dealing with intensity transformations. This is crucial for ensuring that the transformed pixel values remain within the displayable range of 0 to 255. To facilitate this, we implement an auxiliary function, scaling, which will be used in subsequent sections of the post.

# Function to scale the intensity values of an image to the range 0-255
def scaling(Img, min_f, max_f, min_in = None, max_in = None):
    # Determine the current minimum and maximum values of the image
    x_min = Img.min()
    x_max = Img.max()
    # Override the min and max values if specified
    if min_in != None:
        x_min = min_in
        x_max = max_in
    # Scale the image to the new range [min_f, max_f]
    Img_scaled = min_f + ((max_f - min_f) / (x_max - x_min)) * (Img - x_min)
    return Img_scaled

In this function: Img is the input image whose pixel values need scaling, min_f and max_f define the new range to which the image's intensity values will be scaled, min_in and max_in are optional parameters that allow us to specify a custom range of the original image's intensity values. If not provided, the function uses the actual minimum and maximum values of Img. The function calculates the scaled image Img_scaled by linearly transforming the original pixel values to fit within the new specified range.


Gamma (Power-Law) Transformations

The power-law transformation, also known as the Gamma transformation, is a technique that employs a power-law function to adjust the pixel values of an image. This transformation is versatile, allowing for the emphasis on specific intensity ranges and enhancing particular details in an image.

The Gamma Transformation is mathematically represented as:

\begin{equation*} s = cr^\gamma \end{equation*}

Here, $c$ and $\gamma$ are positive constant. Typically, the $c$ value is omitted because, when displaying an image in Python, there is an internal calibration process that automatically maps the lowest and highest pixel values to black and white, respectively. The parameter $\gamma$ specifies the shape of the curve that maps the intensity values $r$ to produce $s$. Below is a graphical representation of the curves generated using different values of $\gamma$:

As illustrated in the previous figure, if $\gamma$ is less than $1$, the mapping is weighted toward brighter (higher) output values. Conversely, if $\gamma$ is greater than $1$, the mapping is weighted toward darker (lower) output values. When $\gamma$ equals $1$ the transformation becomes a linear mapping, maintaining the original intensity distribution.


Python Implementation

After discussing the theory behind Gamma transformations, we are ready for the practical implementation. We implement the gamma_transformation function, which applies the Gamma transformation to an image, allowing us to adjust the image's intensity values based on the Gamma curve.

# Function to apply the Gamma Transformation
def gamma_transformation(Img, gamma = 1, c = 1):
    # Apply the Gamma transformation formula
    s = c*(Img**gamma)
    # Scale the transformed image to the range of 0-255 and convert to uint8
    Img_transformed = np.array(scaling(s, 0, 255), dtype = np.uint8)
    return Img_transformed

The function first applies the Gamma transformation formula to the input image Img based on the values of the parameters gamma and c. It then uses the previously defined scaling function to scale the transformed pixel values back to the range of 0-255. This step is crucial to ensure that the transformed image can be properly displayed and processed. The transformed image is then converted to an 8-bit unsigned integer format (np.uint8), which is the standard format in OpenCV.

Now that we have defined the gamma_transformation function, we can apply it to a real image to observe the effects of this transformation. Below is the Python code used to load an image, apply the Gamma transformation, and display the original and transformed images:

# Load an image in grayscale
Im1 = cv2.imread('/content/Image_1.png', cv2.IMREAD_GRAYSCALE)
# Apply the Gamma transformation with gamma = 4.0
Im1_transformed = gamma_transformation(Im1, gamma = 4.0)

# Prepare images for display
images = [Im1, Im1_transformed]
titles = ['Original Image', 'Gamma Transformation']
# Use the plot_images function to display the original and transformed images
plot_images(images, titles, 1, 2, (15, 7))

We first load an image in grayscale using OpenCV's imread function. The image is read from a specified path (/content/Image_1.png). We then apply the Gamma transformation to this image using our gamma_transformation function with a Gamma value of $4.0$. Finally, we use the previously defined plot_images function to display the original and transformed images. The resultant images are shown below:

We can observe that the original image possesses significant brightness. To emphasize the finer details, we decided the gamma value of $\gamma = 4$, resulting in an output image with darker intensity values. This transformation allows us to discern more details that were previously less visible due to the high brightness levels.


Log Transformations

The logarithmic transformation employs logarithmic functions to modify the pixel values of an image. This technique effectively redistributes the pixel values, accentuating details in darker areas while compressing the details in brighter areas. This characteristic makes it particularly effective in scenarios where it's necessary to enhance the visibility of features in darker regions of an image while maintaining the overall balance of the image.

The general form of the Log Transformation is:

\begin{equation*} s = c\log(1 + r) \end{equation*}

Where $c$ is a constant and $log$ represents the Natural Logarithm (inverse function of the exponential function), it is assumed that $r \geq 0$. Below is a graphical representation of the Log function curve, along with some curves corresponding to Gamma Transformations:

From this illustration, we can observe that the behavior of the Log function is similar to that of Gamma Transformations when $\gamma < 1$. This means that a transformation akin to the Log function can be achieved by selecting an appropriate value in a Gamma Transformation.


Python Implementation

Following our discussion on logarithmic transformation, we now turn to its practical implementation. We implement the log_transformation function, it applies the log transformation to an image:

# Function to apply the Log Transformation
def log_transformation(Img, c = 1):
    # Apply the Log transformation formula
    s = c*np.log(1 + np.array(Img, dtype = np.uint16))
    # Scale the transformed image to the range of 0-255 and convert to uint8
    Img_transformed = np.array(scaling(s, 0, 255), dtype = np.uint8)
    return Img_transformed

The function first converts the input image to a higher data type (np.uint16) to accommodate the increased dynamic range after applying the logarithmic function. It then applies the Log Transformation formula. After the transformation, the function uses the previously defined scaling function to scale the transformed pixel values back to the range of 0-255. The transformed image is then converted to an 8-bit unsigned integer format (np.uint8).

After exploring the concept of logarithmic transformations, we now apply this technique to a real image using the following code:

# Load an image in grayscale
Im2 = cv2.imread('/content/Image_2.png', cv2.IMREAD_GRAYSCALE)
# Apply the Log transformation
Im2_transformed = log_transformation(Im2)

# Prepare images for display
images = [Im2, Im2_transformed]
titles = ['Original Image', 'Logarithmic Transformation']
# Use the plot_images function to display the original and transformed images
plot_images(images, titles, 1, 2, (11, 7))

We load an image in grayscale from a specified path (/content/Image_2.png). We then apply the Log Transformation to this image using our log_transformation function. This transformation is expected to enhance the visibility of features in darker regions of the image. Finally, we use the plot_images function to display the original and transformed images side by side. The results are shown below:


The original image contains significant details obscured by darkness. To address this issue, we implemented the Log Transformation. The resulting output image reveals enhanced details in the darkest sections, which were previously less visible.


To Be Continued...

In the next post, we'll continue our exploration with the Contrast-Stretching transformation, and both the Threshold and Adaptive Threshold transformations. Stay tuned for a more in-depth look at these techniques. For continuity, make sure to read the second part of this series here.


Share:

About Me

My photo
I am a Physics Engineer graduated with academic excellence as the first in my generation. I have experience programming in several languages, like C++, Matlab and especially Python, using the last two I have worked on projects in the area of Image and signal processing, as well as machine learning and data analysis projects.

Recent Post

Intensity Transformations (Part 2)

Welcome back to our exploration of Intensity Transformations . This post is a continuation of our previous discussion, where we covered the ...

Pages