# Importing the libraries
import numpy as np
import os
import cv2 # used only for loading the image
import matplotlib.pyplot as plt # used only for displaying the image
Histogram Matching from Scratch
Importing the necessary libraries
Defining the required functions
# this function is responsible for calculating the histogram of an image
def calculate_histogram(image, num_bins=256):
= np.zeros(num_bins, dtype=np.int32) # initialize the histogram
histogram for pixel_value in image.ravel(): # ravel() returns a contiguous flattened array
+= 1 # increment the count of the pixel value
histogram[pixel_value] return histogram # return the histogram
# this function is responsible for calculating the normalized histogram of an image
def calculate_normalized_histogram(image, num_bins=256):
= calculate_histogram(image, num_bins) # calculate the histogram
histogram = np.sum(histogram) # sum of all the pixel values
sum_of_histogram = histogram / sum_of_histogram # normalize the histogram
histogram return histogram # return the normalized histogram
# this function is responsible for calculating the cumulative histogram of an image
def calculate_cumulative_histogram(histogram):
= np.sum(histogram) # sum of all the pixel values
sum_of_histogram = histogram / sum_of_histogram # normalize the histogram
histogram = np.zeros(histogram.shape, dtype=np.float32) # initialize the cumulative histogram
cumulative_histogram 0] = histogram[0]
cumulative_histogram[for i in range(1, histogram.shape[0]): # calculate the cumulative histogram
= cumulative_histogram[i - 1] + histogram[i]
cumulative_histogram[i] return cumulative_histogram # return the cumulative histogram
# opeining the images in grayscale and storing them in a list
= os.path.join(os.getcwd(), 'Dataset', 'histogram_matching')
image_folder_path = []
image_set for image_name in os.listdir(image_folder_path): # iterate over all the images in the folder
= cv2.imread(os.path.join(image_folder_path, image_name), cv2.IMREAD_GRAYSCALE) # read the image in grayscale
img # append the image to the list image_set.append(img)
# this function is responsible for displaying the images and their histograms
def visualize_histograms(image_set, figsize=(15, 9), image_titles=None):
=figsize) # set the figure size
plt.figure(figsize
for i, image in enumerate(image_set): # iterate over all the images
= calculate_histogram(image) # calculate the histogram of the image
histogram = calculate_normalized_histogram(image) # calculate the normalized histogram of the image
normalized_histogram = calculate_cumulative_histogram(histogram) # calculate the cumulative histogram of the image
cumulative_histogram
3, len(image_set), i + 1) # display the image
plt.subplot(='gray')
plt.imshow(image, cmap=10)
plt.title(image_titles[i], fontsize'off')
plt.axis(
3, len(image_set), i + 1 + len(image_set)) # display the histogram
plt.subplot(range(256), normalized_histogram, width=1.0)
plt.bar('Normalized Histogram ('+image_titles[i]+')', fontsize=8)
plt.title('Pixel Value', fontsize=8)
plt.xlabel('Frequency', fontsize=8)
plt.ylabel(
3, len(image_set), i + 1 + 2 * len(image_set)) # display the cumulative histogram
plt.subplot(
plt.plot(cumulative_histogram)'Cumulative Histogram ('+image_titles[i]+')', fontsize=8)
plt.title('Pixel Value', fontsize=8)
plt.xlabel('Frequency', fontsize=8)
plt.ylabel(
plt.tight_layout()
plt.show()
# displaying the images and their histograms
=['Image 0', 'Image 1', 'Image 2', 'Image 3']) visualize_histograms(image_set, image_titles
Implementing the algorithm
# this function is responsible for matching the histogram of an image to the histogram of a reference image
def match_histograms(image, reference_image):
= get_mapping(image, reference_image) # get the mapping
mapping = np.zeros(image.shape, dtype=np.uint8) # initialize the matched image
matched_image for i in range(image.shape[0]): # match the image
for j in range(image.shape[1]):
= mapping[image[i, j]]
matched_image[i, j] return matched_image # return the matched image
# this function is responsible for matching the histogram of an image to the histogram of a reference image
def get_mapping(image, reference_image, gray_levels=256):
= calculate_histogram(image) # calculate the histogram of the image
histogram = calculate_cumulative_histogram(histogram) # calculate the cumulative histogram of the image
cumulative_histogram = calculate_histogram(reference_image) # calculate the histogram of the reference image
reference_histogram = calculate_cumulative_histogram(reference_histogram) # calculate the cumulative histogram of the reference image
reference_cumulative_histogram
= np.zeros(gray_levels) # initialize the mapping
mapping for pixel_value in range(gray_levels):
= cumulative_histogram[pixel_value] # get the cumulative histogram of the image
old_value = reference_cumulative_histogram - old_value # get the difference between the cumulative histogram of the reference image and the cumulative histogram of the image
temp = np.argmin(np.abs(temp)) # get the index of the minimum value in the difference
new_value = new_value # map the pixel value to the new value
mapping[pixel_value] return mapping # return the mapping
# performing histogram matching and displaying the images and their histograms
def histogram_matching_and_visualization(image, reference_image, visualize=True):
= match_histograms(image, reference_image) # match the histogram of the image to the histogram of the reference image
matched_image = [image, reference_image, matched_image]
image_set = ['Source Image', 'Target Image', 'Matched Image']
image_titles if visualize:
=image_titles) # display the images and their histograms
visualize_histograms(image_set, image_titlesreturn matched_image # return the matched image
= histogram_matching_and_visualization(image_set[0], image_set[1]) matching
= histogram_matching_and_visualization(image_set[3], image_set[2]) matching
= histogram_matching_and_visualization(image_set[1], image_set[3]) matching
= histogram_matching_and_visualization(image_set[2], image_set[0]) matching
Analysis of the obtained resutls
import numpy as np
from scipy.stats import entropy
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import peak_signal_noise_ratio as psnr
def calculate_image_statistics(original_image, target_image, matched_image):
# Calculate Mean and Standard Deviation
= np.mean(original_image)
mean_original = np.std(original_image)
std_original
= np.mean(target_image)
mean_target = np.std(target_image)
std_target
= np.mean(matched_image)
mean_matched = np.std(matched_image)
std_matched
# Calculate SSIM
= ssim(original_image, matched_image)
ssim_score
# Create a dictionary to store the statistics
= {
statistics "Original Mean": mean_original,
"Original Standard Deviation": std_original,
"Target Mean": mean_target,
"Target Standard Deviation": std_target,
"Matched Mean": mean_matched,
"Matched Standard Deviation": std_matched,
"SSIM Score (Source vs Matched)": ssim_score,
}
return statistics
= histogram_matching_and_visualization(image_set[0], image_set[1], visualize=True)
matching = calculate_image_statistics(image_set[0], image_set[1], matching)
statistics for key, value in statistics.items():
print(f"{key}: {value}")
Original Mean: 80.04150772094727
Original Standard Deviation: 68.48801554947227
Target Mean: 126.48823547363281
Target Standard Deviation: 69.12917387622817
Matched Mean: 130.7914924621582
Matched Standard Deviation: 62.277325624710976
SSIM Score (Source vs Matched): 0.6362629163593398
Mean and Standard Deviation
The original image has a mean of approximately 80.04 and a standard deviation of approximately 68.49. The target image has a mean of approximately 126.49 and a standard deviation of approximately 69.13. After the histogram matching process, the matched image has a mean of approximately 130.79 and a standard deviation of approximately 62.27. Interpretation: The means have shifted towards each other after histogram matching, but the standard deviations have not changed significantly.
SSIM Score
The Structural Similarity Index (SSIM) score between the original and matched images is approximately 0.636. Interpretation: An SSIM score of 1 indicates a perfect match. A score of 0.636 suggests that the matched image is reasonably similar to the original but not a perfect match.
= histogram_matching_and_visualization(image_set[1], image_set[3], visualize=True)
matching = calculate_image_statistics(image_set[1], image_set[3], matching)
statistics for key, value in statistics.items():
print(f"{key}: {value}")
Original Mean: 126.48823547363281
Original Standard Deviation: 69.12917387622817
Target Mean: 102.53508758544922
Target Standard Deviation: 90.99119020648051
Matched Mean: 102.81129837036133
Matched Standard Deviation: 90.91224212665476
SSIM Score (Source vs Matched): 0.6144812508253021
Mean and Standard Deviation
The original image has a mean of approximately 126.49 and a standard deviation of approximately 69.13. The target image has a mean of approximately 102.54 and a standard deviation of approximately 90.99. After the histogram matching process, the matched image has a mean of approximately 102.81 and a standard deviation of approximately 90.91. Interpretation: Both the means and standard deviations have shifted towards the target image each other after histogram matching.
SSIM Score
The Structural Similarity Index (SSIM) score between the original and matched images is approximately 0.615. Interpretation: The matched image is reasonably similar to the original, but it may not be a perfect match.