Spaces:
Running
Running
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 | |