File size: 6,386 Bytes
81e3815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6861f2c
 
81e3815
 
 
 
 
6861f2c
 
 
 
 
 
81e3815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import cv2
import numpy as np
from PIL import Image, ImageEnhance, ImageFilter
from skimage.metrics import structural_similarity as ssim

# Constants
DEFAULT_WIDTH = 150
CONTRAST_FACTOR = 2.0
ASCII_CHARS = "@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|)(1}{][?-_+~i!lI;:,\"^`'. "
ASCII_CHARS = list(ASCII_CHARS)


def resize_image(image, new_width=DEFAULT_WIDTH, resampling_filter=Image.LANCZOS):
    """Resize the image while maintaining the aspect ratio."""
    width, height = image.size
    aspect_ratio = height / width
    new_height = int(new_width * aspect_ratio)
    resized_image = image.resize((new_width, new_height), resampling_filter)

    # Apply a sharpening filter after resizing
    sharpened_image = resized_image.filter(ImageFilter.SHARPEN)

    return sharpened_image


def convert_to_grayscale(image):
    """Convert the image to grayscale using weighted sum of RGB channels."""
    # Get the image data
    pixels = image.load()
    width, height = image.size

    # Check if the image is already in grayscale mode
    if image.mode == 'L':
        return image

    # Create a new grayscale image
    grayscale_image = Image.new("L", (width, height))
    grayscale_pixels = grayscale_image.load()

    # Define the weights for each channel
    weights = (0.299, 0.587, 0.114)

    # Process each pixel
    for x in range(width):
        for y in range(height):
            r, g, b = pixels[x, y][:3]  # Get RGB values
            # Calculate the grayscale value using the weighted sum
            gray = int(weights[0] * r + weights[1] * g + weights[2] * b)
            grayscale_pixels[x, y] = gray

    return grayscale_image

def adjust_contrast(image, factor=CONTRAST_FACTOR):
    """Adjust the contrast of the image."""
    enhancer = ImageEnhance.Contrast(image)
    return enhancer.enhance(factor)


def apply_edge_detection(image):
    """Apply edge detection to the image using Canny algorithm with preprocessing and automatic threshold detection."""

    # Convert image to numpy array
    image_np = np.array(image)

    # Print an error message if the image is not in grayscale
    if len(image_np.shape) == 3:
        raise ValueError("Edge detection requires a grayscale image.")

    # Apply Gaussian blur to reduce noise
    blurred_image = cv2.GaussianBlur(image_np, (5, 5), 1.4)

    # Compute the median of the pixel intensities
    median_intensity = np.median(blurred_image)

    # Set the lower and upper thresholds for Canny edge detection
    lower_threshold = int(max(0, (1.0 - 0.2) * median_intensity))
    upper_threshold = int(min(255, (1.0 + 0.2) * median_intensity))

    # Apply Canny edge detection with the computed thresholds
    edges = cv2.Canny(blurred_image, threshold1=lower_threshold, threshold2=upper_threshold)

    # Convert the result back to PIL Image and return
    edge_image = Image.fromarray(edges)

    return edge_image


def map_pixels_to_ascii(image):
    """Map the pixel values of the image to ASCII characters."""
    pixels = image.getdata()
    ascii_str = ""
    num_chars = len(ASCII_CHARS)

    for pixel in pixels:
        index = pixel * (num_chars - 1) // 255
        ascii_str += ASCII_CHARS[index]

    return ascii_str

def format_ascii_art(ascii_str, width):
    """Format the ASCII string into a structured ASCII art."""
    ascii_art = ""
    for i in range(0, len(ascii_str), width):
        ascii_art += ascii_str[i:i+width] + "\n"
    return ascii_art

def process_image(image):
    """Process the input image to prepare it for ASCII art generation."""
    if not isinstance(image, Image.Image):
        image = Image.fromarray(image)
    grayscale_image = convert_to_grayscale(image)
    adjusted_image = adjust_contrast(grayscale_image)
    adjusted_image = resize_image(adjusted_image)
    return adjusted_image

def generate_ascii_art(image):   
    """Generate ASCII art from the given image."""
    # Ensure the input is a PIL Image object
    adjusted_image = process_image(image)
    ascii_str = map_pixels_to_ascii(adjusted_image)
    ascii_art = format_ascii_art(ascii_str, adjusted_image.width)
    return ascii_art

def ascii_to_image(ascii_art, width):
    """Convert ASCII art back to a grayscale image."""
    ascii_chars = ASCII_CHARS
    num_chars = len(ascii_chars)

    # Split the ASCII art into lines
    ascii_lines = ascii_art.split('\n')
    height = len(ascii_lines)

    # Create a blank image
    image = np.zeros((height, width), dtype=np.uint8)

    for y, line in enumerate(ascii_lines):
        for x, char in enumerate(line):
            if char in ascii_chars:
                index = ascii_chars.index(char)
                pixel_value = index * 255 // (num_chars - 1)
                image[y, x] = pixel_value

    # Convert the numpy array to a PIL Image
    image = Image.fromarray(image)
    image = adjust_contrast(image)
    return image

def calculate_similarity(original_image, ascii_art):
   # Convert ASCII art back to image
    ascii_image = ascii_to_image(ascii_art, DEFAULT_WIDTH)

    # Ensure ascii_image is a PIL Image object
    if not isinstance(ascii_image, Image.Image):
        ascii_image = Image.fromarray(ascii_image)

    # convert original image to grayscale
    original_image = convert_to_grayscale(original_image)

    # Resize the original image to the desired width
    original_resize = original_image.resize((ascii_image.width, ascii_image.height), Image.LANCZOS)
    original_resize = original_resize.filter(ImageFilter.SHARPEN)

    # Apply edge detection
    original_image_edge = apply_edge_detection(original_resize)
    # convert the original image to binary
    threshold = 0
    original_image_edge = original_image_edge.point(lambda p: p > threshold and 255)

    ascii_image_edge = apply_edge_detection(ascii_image)
    ascii_image_edge = ascii_image_edge.point(lambda p: p > threshold and 255)

    # Convert images to numpy arrays
    original_array = np.array(original_image_edge)
    ascii_array = np.array(ascii_image_edge)

    # Check if the dimensions match
    if original_array.shape != ascii_array.shape:
        raise ValueError(f"Dimension mismatch: Original Image {original_array.shape}, ASCII Image {ascii_array.shape}")

    # Calculate SSIM
    ssim_value = ssim(original_array, ascii_array)

    # Calculate MSE
    mse = np.mean((original_array - ascii_array) ** 2)


    return ssim_value, mse