Code
import numpy as np
import gradio as gr
from skimage import color, img_as_float, data
import plotly.graph_objects as goThis notebook provides detailed implementations of key image processing techniques, focusing on enhancing and extracting image features through:
Prewitt and Sobel Edge Detection
These operators are essential tools for edge detection through gradient approximation. Edge detection aims to identify boundaries within an image by applying filters in the horizontal and vertical directions.
The convolutional operation is utilized as follows:
\[ (f * h)(n) = \sum_{k \in \mathbb{Z}^2} f(k)h(n-k), \]
where the gradient magnitude highlights edges:
\[ E = \sqrt{(f_x)^2 + (f_y)^2}. \]
Histogram Equalization
Enhances image contrast by reallocating pixel intensity distributions. For a pixel intensity distribution with levels \(r_k\) and probabilities:
\[ p_k = \frac{N_k}{N}, \]
the new intensity mapping utilizes the cumulative distribution:
\[ F(i,j) = \sum_{l=1}^{k} p_l \quad \text{for } (i,j)\in \Omega_k. \]
To understand the image’s features, the convolve2d function helps perform a 2D convolution, an essential step for applying filtering operations to images.
def convolve2d(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
"""
Perform a 2D convolution between an image and a kernel.
The kernel is flipped to implement convolution as defined by
$$
(f * h)(n) = \sum_{k \in \mathbb{Z}^2} f(k)h(n-k).
$$
Parameters:
image (np.ndarray): 2D input image.
kernel (np.ndarray): 2D filter kernel.
Returns:
np.ndarray: The convolved image with the same shape as input.
"""
h, w = image.shape
kh, kw = kernel.shape
pad_h, pad_w = kh // 2, kw // 2
padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode="edge")
output = np.zeros_like(image, dtype=float)
# Flip the kernel for convolution
kernel_flip = np.flipud(np.fliplr(kernel))
for i in range(h):
for j in range(w):
region = padded[i : i + kh, j : j + kw]
output[i, j] = np.sum(region * kernel_flip)
return outputThe Gaussian kernel and smoothing functions allow us to apply a Gaussian filter, smoothing the image to reduce noise before edge detection.
def gaussian_kernel(size: int, sigma: float) -> np.ndarray:
"""
Generate a 2D Gaussian kernel.
Parameters:
size (int): The size of the kernel (should be odd).
sigma (float): Standard deviation of the Gaussian.
Returns:
np.ndarray: Normalized Gaussian kernel.
"""
ax = np.arange(-size // 2 + 1, size // 2 + 1)
xx, yy = np.meshgrid(ax, ax)
kernel = np.exp(-(xx**2 + yy**2) / (2 * sigma**2))
kernel = kernel / np.sum(kernel)
return kernel
def smooth_image(image: np.ndarray, kernel_size: int, sigma: float) -> np.ndarray:
"""
Smooth an image using a Gaussian filter.
Parameters:
image (np.ndarray): 2D input image.
kernel_size (int): Size of the Gaussian kernel (odd integer).
sigma (float): Standard deviation for the Gaussian.
Returns:
np.ndarray: Smoothed image.
"""
kernel = gaussian_kernel(kernel_size, sigma)
return convolve2d(image, kernel)Thresholding aids in converting edge detected images to binary, accentuating the detected edges.
We define Prewitt and Sobel edge detection filters, both acting on pixel intensity gradients.
We utilize the filters for edge detection by combining the above-defined functions in prewitt_edge_detection and sobel_edge_detection.
def prewitt_edge_detection(image: np.ndarray) -> np.ndarray:
"""
Apply the Prewitt edge detection method.
Parameters:
image (np.ndarray): 2D grayscale image.
Returns:
np.ndarray: Edge magnitude image.
"""
gx = convolve2d(image, prewitt_x)
gy = convolve2d(image, prewitt_y)
return np.sqrt(gx**2 + gy**2)
def sobel_edge_detection(image: np.ndarray) -> np.ndarray:
"""
Apply the Sobel edge detection method.
Parameters:
image (np.ndarray): 2D grayscale image.
Returns:
np.ndarray: Edge magnitude image.
"""
gx = convolve2d(image, sobel_x)
gy = convolve2d(image, sobel_y)
return np.sqrt(gx**2 + gy**2)We also implement histogram equalization to enhance contrast in images.
def histogram_equalization(image: np.ndarray) -> np.ndarray:
"""
Perform histogram equalization to enhance image contrast.
Parameters:
image (np.ndarray): 2D grayscale image with values in [0, 1].
Returns:
np.ndarray: Contrast-enhanced image.
"""
# Compute histogram and cumulative distribution function (CDF)
hist, bins = np.histogram(image.flatten(), bins=256, range=[0, 1])
cdf = hist.cumsum()
cdf_normalized = cdf / cdf[-1]
# Use linear interpolation of the CDF to map the original gray levels
equalized = np.interp(image.flatten(), bins[:-1], cdf_normalized).reshape(
image.shape
)
return equalizeddef compute_histogram(image: np.ndarray, bins: int = 256) -> dict:
"""
Compute the histogram of an image.
Parameters:
image (np.ndarray): Input image
bins (int): Number of histogram bins
Returns:
dict: Dictionary containing histogram data
"""
hist, bin_edges = np.histogram(image, bins=bins, range=(0, 1))
bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
return {
"counts": hist.tolist(),
"bin_edges": bin_edges.tolist(),
"bin_centers": bin_centers.tolist(),
}
def create_histogram_plot(hist_data: dict) -> go.Figure:
"""
Create a Plotly histogram figure from the histogram data.
Parameters:
hist_data (dict): Dictionary containing histogram data
Returns:
go.Figure: Plotly figure object
"""
fig = go.Figure(
data=[
go.Bar(
x=hist_data["bin_centers"],
y=hist_data["counts"],
name="Pixel Intensity Distribution",
marker_color="rgb(55, 83, 109)",
)
]
)
fig.update_layout(
title="Image Histogram",
xaxis_title="Pixel Intensity",
yaxis_title="Frequency",
bargap=0.01,
template="plotly_white",
showlegend=False,
)
return fig
def process_image(
image: np.ndarray,
method: str,
threshold: float = 0.1,
kernel_size: int = 3,
sigma: float = 1.0,
) -> tuple[np.ndarray, go.Figure]:
"""
Process the input image based on the selected method.
Parameters:
image (np.ndarray): Input image (RGB or grayscale). Expected values in [0,1].
method (str): Processing method.
threshold (float): Threshold value for binary edge detection.
kernel_size (int): Kernel size for Gaussian smoothing (odd integer).
sigma (float): Standard deviation for Gaussian smoothing.
Returns:
tuple[np.ndarray, go.Figure]: A tuple containing the processed image and the Plotly figure
"""
# Convert to float image in [0,1]
if image.ndim == 3:
image_gray = color.rgb2gray(image)
else:
image_gray = image.copy()
# Process according to the selected method
if method == "Prewitt Edge Detection":
proc_img = prewitt_edge_detection(image_gray)
elif method == "Sobel Edge Detection":
proc_img = sobel_edge_detection(image_gray)
elif method == "Edge Thresholding (Sobel + Threshold)":
edges = sobel_edge_detection(image_gray)
proc_img = threshold_image(edges, threshold)
elif method == "Histogram Equalization":
proc_img = histogram_equalization(image_gray)
elif method == "Gaussian Smoothing":
proc_img = smooth_image(image_gray, kernel_size, sigma)
else:
proc_img = image_gray # Fallback: return original image
# Normalize the processed image to [0, 1] for display (if not already binary)
if proc_img.max() > 1 or proc_img.min() < 0:
proc_img = (proc_img - proc_img.min()) / (
proc_img.max() - proc_img.min() + 1e-8
)
hist_data = compute_histogram(proc_img)
plot_fig = create_histogram_plot(hist_data)
return proc_img, plot_fig
with gr.Blocks(
css="""gradio-app {background: #222222 !important}""",
title="Edge Detection & Histogram Equalization",
) as demo:
with gr.Row():
image_input = gr.Image(label="Input Image", type="numpy", value=DEFAULT_IMAGE)
output_image = gr.Image(label="Processed Image")
process_button = gr.Button("Process Image")
method_choice = gr.Dropdown(
choices=[
"Prewitt Edge Detection",
"Sobel Edge Detection",
"Edge Thresholding (Sobel + Threshold)",
"Histogram Equalization",
"Gaussian Smoothing",
],
label="Processing Method",
value="Prewitt Edge Detection",
)
threshold_slider = gr.Slider(
minimum=0.0,
maximum=1.0,
step=0.01,
label="Threshold (for Edge Thresholding)",
value=0.1,
)
kernel_slider = gr.Slider(
minimum=3, maximum=15, step=2, label="Gaussian Kernel Size", value=3
)
sigma_slider = gr.Slider(
minimum=0.1, maximum=5.0, step=0.1, label="Gaussian Sigma", value=1.0
)
output_histogram = gr.Plot(label="Histogram")
kwargs = dict(
fn=process_image,
inputs=[
image_input,
method_choice,
threshold_slider,
kernel_slider,
sigma_slider,
],
outputs=[output_image, output_histogram],
)
process_button.click(**kwargs)
demo.load(**kwargs)