File size: 5,053 Bytes
1d9074b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Jialu Bi
CS5330 Lab 1
Generator for ASCII art
"""

import cv2
import numpy as np
import matplotlib.pyplot as plt
import gradio as gr
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import mean_squared_error

# A class for ASCII genrator related functions
class ASCIIGenerator:
    def __init__(self):
        self.ASCIIChars = ['#', '@', '%', '*', '+', '-', '.', ' ']
        # self.ASCIIChars = ['█', '▓', '▒', '░', ' ']

    # Resize the image to make sure consistent result
    def resizeImg(self, img):
        h, w, c = img.shape
        aspectRatio = w / h
        imgResized = cv2.resize(img, (int(160 * aspectRatio), 160))
        return imgResized

    # Convert to gray scale
    # The input should be the resized image
    def cvtGrayScale(self, imgResized):
        R, G, B = cv2.split(imgResized)
        R, G, B = R.astype(np.float32), G.astype(np.float32), B.astype(np.float32)

        # Apply weights for more refined control
        imgGray = 0.299 * R + 0.587 * G + 0.114 * B
        imgGray = np.clip(imgGray, 0, 255).astype(np.uint8)
        return imgGray

    # Preprocess the image
    # The input should be the gray scale image
    def preprocess(self, imgGray):
        # Histogram equalization
        clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
        imgEqualized = clahe.apply(imgGray)

        # Increase contrast
        imgEqualized = cv2.convertScaleAbs(imgEqualized, alpha=1.6, beta=0)

        # Detect the edges and emphasize them
        edges = cv2.Canny(imgEqualized, threshold1=50, threshold2=150)
        invertedEdges = cv2.bitwise_not(edges)
        imgEnhanced = cv2.addWeighted(imgGray, 0.7, invertedEdges, 0.3, 0)
        return imgEnhanced

    # Map to ASCII Characters
    # The input should be the preprocessed image
    def mapASCII(self, imgEnhanced):
        ASCIIArt = ''
        h, w = imgEnhanced.shape

        # Iterate through the pixels to create the ASCII art
        for i in range(h):
            for j in range(w):
                pixelVal = imgEnhanced[i, j]
                # Assign a proper ASCII character according to the pixel's intensity value
                ASCIIArt += self.ASCIIChars[pixelVal * len(self.ASCIIChars) // 256]
            ASCIIArt += '\n'
        return ASCIIArt

    # Main function for generating ASCII art
    def generateASCIIArt(self, img):
        imgResized = self.resizeImg(img)
        imgGray = self.cvtGrayScale(imgResized)
        imgEnhanced = self.preprocess(imgGray)
        ASCIIArt = self.mapASCII(imgEnhanced)
        return ASCIIArt

    # Convert ASCII art back to grayscale for performance evaluation
    def ASCIItoGray(self, ASCIIArt):
        # Create a dictionary to store the corresponding intensity of ASCII characters
        ASCIICharsVal = {}
        offset = 0
        for char in self.ASCIIChars:
            ASCIICharsVal[char] = offset
            offset += 256 // len(self.ASCIIChars)

        # Map ASCII characters back to pixels
        ASCIIGray = []
        lines = ASCIIArt.split('\n')[:-1]
        for line in lines:
            row = []
            for char in line:
                row.append(ASCIICharsVal[char])
            ASCIIGray.append(row)
        ASCIIGray = np.array(ASCIIGray, dtype=np.uint8)

        return ASCIIGray

    # Evaluate the similarity between the grayscale version of the original image and its ASCII art version
    def compare(self, ASCIIGray, imgGray):
        # Compute SSIM
        ssimVal, _ = ssim(imgGray, ASCIIGray, full=True)
        # Compute MSE
        mseVal = mean_squared_error(imgGray, ASCIIGray)
        # Compute PSNR
        psnrVal = cv2.PSNR(imgGray, ASCIIGray)
        return ssimVal, mseVal, psnrVal


# Gradio interface function
def generateArt(img):
    if img is None:
        return None, None, None

    # Generate the ASCII art
    generator = ASCIIGenerator()
    ASCIIArt = generator.generateASCIIArt(img)

    # Evaluate the performance of the algorithm
    imgResized = generator.resizeImg(img)
    imgGray = generator.cvtGrayScale(imgResized)
    ASCIIGray = generator.ASCIItoGray(ASCIIArt)
    scores = generator.compare(ASCIIGray, imgGray)
    report = f'SSIM: {round(scores[0], 2)}, MSE: {round(scores[1], 2)}, PSNR: {round(scores[2], 2)}'

    # Save the ASCII art to a text file
    outputPath = 'ascii_art.txt'
    with open(outputPath, 'w', encoding='utf-8') as file:
        file.write(ASCIIArt)
    return f'<pre style="font-size: 8px; line-height: 0.62;">{ASCIIArt}</pre>', outputPath, report

# Create a Gradio interface
with gr.Blocks() as demo:
    gr.Markdown('## ASCII Art Generator\nUpload an image to generate ASCII art.')
    imgInput = gr.Image(type='numpy', label='Upload Image')
    ASCIIOutput = gr.HTML(label='ASCII Art Output')
    fileOutput = gr.File(label='Download ASCII Art')
    scoreOutput = gr.Textbox(label='ASCII Conversion Performance')
    imgInput.change(fn=generateArt, inputs=imgInput, outputs=[ASCIIOutput, fileOutput, scoreOutput])


demo.launch(share=True)