ASCII-Art-Demo / ASCII_functions.py
Siyun He
update code so the processed image can show by running evaluate.py
6861f2c
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