File size: 5,121 Bytes
b8bad50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""
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)