""" Program to compare the similarity of a 300x300 input and mosaic image using three metrics: MSE: Pixel-level accuracy. SSIM: Perceptual similarity. Histogram Similarity: Color distribution match. Author: Peter Chibuikem Idoko """ import cv2 import os import numpy as np import logging import csv from skimage.metrics import structural_similarity as ssim # Configure logging logging.basicConfig( filename="similarity.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", ) # Constants INPUT_IMAGE = "scraped_photos/image_18.jpg" # Path to the original scraped image MOSAIC_IMAGE = "output/reconstructed_image.jpg" # Path to the final mosaic image IMAGE_SIZE = 300 # Images are resized to 300x300 def compute_mse(imageA, imageB): """ Computes the Mean Squared Error (MSE) between two images. Lower MSE means the images are more similar. Args: imageA (numpy.ndarray): First image. imageB (numpy.ndarray): Second image. Returns: float: Mean Squared Error value. """ err = np.mean((imageA.astype("float") - imageB.astype("float")) ** 2) return err def compute_ssim(imageA, imageB): """ Computes the Structural Similarity Index (SSIM) between two images. SSIM ranges from -1 to 1, where 1 means identical images. Args: imageA (numpy.ndarray): First image. imageB (numpy.ndarray): Second image. Returns: float: SSIM score. """ ssim_scores = [ ssim(imageA[:, :, i], imageB[:, :, i]) for i in range(3) ] return np.mean(ssim_scores) # Average SSIM over RGB channels def compute_histogram_similarity(imageA, imageB): """Compares color histograms using Chi-Square (more sensitive).""" histA = cv2.calcHist([imageA], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) histB = cv2.calcHist([imageB], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) histA = cv2.normalize(histA, histA).flatten() histB = cv2.normalize(histB, histB).flatten() return cv2.compareHist(histA, histB, cv2.HISTCMP_CHISQR) def measure_similarity(image1_path, image2_path): """ Measures the similarity between two images using MSE, SSIM, and histogram comparison. Args: image1_path (str): Path to the first image (original quantized image). image2_path (str): Path to the second image (mosaic image). Returns: dict: A dictionary containing similarity scores. """ # Load images image1 = cv2.imread(image1_path) image2 = cv2.imread(image2_path) if image1 is None or image2 is None: print("Error: Could not load one or both images.") return None # Resize images to ensure they are the same size image1 = cv2.resize(image1, (IMAGE_SIZE, IMAGE_SIZE)) image2 = cv2.resize(image2, (IMAGE_SIZE, IMAGE_SIZE)) # Compute similarity metrics mse_value = compute_mse(image1, image2) ssim_value = compute_ssim(image1, image2) hist_sim_value = compute_histogram_similarity(image1, image2) # Generate log message log_message = ( f"\nImage Similarity Analysis\n" f"Mean Squared Error (MSE): {mse_value:.2f} (Lower is better)\n" f"Structural Similarity Index (SSIM): {ssim_value:.3f} (Closer to 1 is better)\n" f"Histogram Similarity: {hist_sim_value:.3f} (Closer to 1 is better)\n" ) # Print and log the results print(log_message) logging.info(log_message) # Return results as a dictionary return { "MSE": mse_value, "SSIM": ssim_value, "Histogram Similarity": hist_sim_value } def log_results_to_csv(results: dict, output_csv: str = "similarity_results.csv") -> None: """ Logs the computed similarity metrics to a CSV file. Args: results (dict): A dictionary containing similarity metrics (MSE, SSIM, Histogram Similarity). output_csv (str): Path to the CSV file where results should be saved. Default is "similarity_results.csv". Returns: None Edge Cases: - If the results dictionary is empty or None, the function does nothing. - If the CSV file does not exist, it creates one with headers. - If an I/O error occurs, it logs the issue and prevents data loss. """ if not results: logging.warning("No results to log. Skipping CSV logging.") return file_exists = os.path.isfile(output_csv) try: with open(output_csv, mode="a", newline="") as csv_file: fieldnames = ["MSE", "SSIM", "Histogram Similarity"] writer = csv.DictWriter(csv_file, fieldnames=fieldnames) # Write header only if the file is newly created if not file_exists: writer.writeheader() writer.writerow(results) logging.info(f"Similarity results logged to {output_csv}") except IOError as e: logging.error(f"Error writing to CSV file {output_csv}: {e}") if __name__ == "__main__": output = measure_similarity(INPUT_IMAGE, MOSAIC_IMAGE) log_results_to_csv(output)