Spaces:
Running
Running
Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Jialu Bi
|
3 |
+
CS5330 Lab 1
|
4 |
+
Generator for ASCII art
|
5 |
+
"""
|
6 |
+
|
7 |
+
import cv2
|
8 |
+
import numpy as np
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
import gradio as gr
|
11 |
+
from skimage.metrics import structural_similarity as ssim
|
12 |
+
from skimage.metrics import mean_squared_error
|
13 |
+
|
14 |
+
# A class for ASCII genrator related functions
|
15 |
+
class ASCIIGenerator:
|
16 |
+
def __init__(self):
|
17 |
+
self.ASCIIChars = ['#', '@', '%', '*', '+', '-', '.', ' ']
|
18 |
+
# self.ASCIIChars = ['█', '▓', '▒', '░', ' ']
|
19 |
+
|
20 |
+
# Resize the image to make sure consistent result
|
21 |
+
def resizeImg(self, img):
|
22 |
+
h, w, c = img.shape
|
23 |
+
aspectRatio = w / h
|
24 |
+
imgResized = cv2.resize(img, (int(160 * aspectRatio), 160))
|
25 |
+
return imgResized
|
26 |
+
|
27 |
+
# Convert to gray scale
|
28 |
+
# The input should be the resized image
|
29 |
+
def cvtGrayScale(self, imgResized):
|
30 |
+
R, G, B = cv2.split(imgResized)
|
31 |
+
R, G, B = R.astype(np.float32), G.astype(np.float32), B.astype(np.float32)
|
32 |
+
|
33 |
+
# Apply weights for more refined control
|
34 |
+
imgGray = 0.299 * R + 0.587 * G + 0.114 * B
|
35 |
+
imgGray = np.clip(imgGray, 0, 255).astype(np.uint8)
|
36 |
+
return imgGray
|
37 |
+
|
38 |
+
# Preprocess the image
|
39 |
+
# The input should be the gray scale image
|
40 |
+
def preprocess(self, imgGray):
|
41 |
+
# Histogram equalization
|
42 |
+
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
43 |
+
imgEqualized = clahe.apply(imgGray)
|
44 |
+
|
45 |
+
# Increase contrast
|
46 |
+
imgEqualized = cv2.convertScaleAbs(imgEqualized, alpha=1.6, beta=0)
|
47 |
+
|
48 |
+
# Detect the edges and emphasize them
|
49 |
+
edges = cv2.Canny(imgEqualized, threshold1=50, threshold2=150)
|
50 |
+
invertedEdges = cv2.bitwise_not(edges)
|
51 |
+
imgEnhanced = cv2.addWeighted(imgGray, 0.7, invertedEdges, 0.3, 0)
|
52 |
+
return imgEnhanced
|
53 |
+
|
54 |
+
# Map to ASCII Characters
|
55 |
+
# The input should be the preprocessed image
|
56 |
+
def mapASCII(self, imgEnhanced):
|
57 |
+
ASCIIArt = ''
|
58 |
+
h, w = imgEnhanced.shape
|
59 |
+
|
60 |
+
# Iterate through the pixels to create the ASCII art
|
61 |
+
for i in range(h):
|
62 |
+
for j in range(w):
|
63 |
+
pixelVal = imgEnhanced[i, j]
|
64 |
+
# Assign a proper ASCII character according to the pixel's intensity value
|
65 |
+
ASCIIArt += self.ASCIIChars[pixelVal * len(self.ASCIIChars) // 256]
|
66 |
+
ASCIIArt += '\n'
|
67 |
+
return ASCIIArt
|
68 |
+
|
69 |
+
# Main function for generating ASCII art
|
70 |
+
def generateASCIIArt(self, img):
|
71 |
+
imgResized = self.resizeImg(img)
|
72 |
+
imgGray = self.cvtGrayScale(imgResized)
|
73 |
+
imgEnhanced = self.preprocess(imgGray)
|
74 |
+
ASCIIArt = self.mapASCII(imgEnhanced)
|
75 |
+
return ASCIIArt
|
76 |
+
|
77 |
+
# Convert ASCII art back to grayscale for performance evaluation
|
78 |
+
def ASCIItoGray(self, ASCIIArt):
|
79 |
+
# Create a dictionary to store the corresponding intensity of ASCII characters
|
80 |
+
ASCIICharsVal = {}
|
81 |
+
offset = 0
|
82 |
+
for char in self.ASCIIChars:
|
83 |
+
ASCIICharsVal[char] = offset
|
84 |
+
offset += 256 // len(self.ASCIIChars)
|
85 |
+
|
86 |
+
# Map ASCII characters back to pixels
|
87 |
+
ASCIIGray = []
|
88 |
+
lines = ASCIIArt.split('\n')[:-1]
|
89 |
+
for line in lines:
|
90 |
+
row = []
|
91 |
+
for char in line:
|
92 |
+
row.append(ASCIICharsVal[char])
|
93 |
+
ASCIIGray.append(row)
|
94 |
+
ASCIIGray = np.array(ASCIIGray, dtype=np.uint8)
|
95 |
+
|
96 |
+
return ASCIIGray
|
97 |
+
|
98 |
+
# Evaluate the similarity between the grayscale version of the original image and its ASCII art version
|
99 |
+
def compare(self, ASCIIGray, imgGray):
|
100 |
+
# Compute SSIM
|
101 |
+
ssimVal, _ = ssim(imgGray, ASCIIGray, full=True)
|
102 |
+
# Compute MSE
|
103 |
+
mseVal = mean_squared_error(imgGray, ASCIIGray)
|
104 |
+
# Compute PSNR
|
105 |
+
psnrVal = cv2.PSNR(imgGray, ASCIIGray)
|
106 |
+
return ssimVal, mseVal, psnrVal
|
107 |
+
|
108 |
+
|
109 |
+
# Gradio interface function
|
110 |
+
def generateArt(img):
|
111 |
+
if img is None:
|
112 |
+
return None, None, None
|
113 |
+
|
114 |
+
# Generate the ASCII art
|
115 |
+
generator = ASCIIGenerator()
|
116 |
+
ASCIIArt = generator.generateASCIIArt(img)
|
117 |
+
|
118 |
+
# Evaluate the performance of the algorithm
|
119 |
+
imgResized = generator.resizeImg(img)
|
120 |
+
imgGray = generator.cvtGrayScale(imgResized)
|
121 |
+
ASCIIGray = generator.ASCIItoGray(ASCIIArt)
|
122 |
+
scores = generator.compare(ASCIIGray, imgGray)
|
123 |
+
report = f'SSIM: {round(scores[0], 2)}, MSE: {round(scores[1], 2)}, PSNR: {round(scores[2], 2)}'
|
124 |
+
|
125 |
+
# Save the ASCII art to a text file
|
126 |
+
outputPath = 'ascii_art.txt'
|
127 |
+
with open(outputPath, 'w', encoding='utf-8') as file:
|
128 |
+
file.write(ASCIIArt)
|
129 |
+
return f'<pre style="font-size: 8px; line-height: 0.62;">{ASCIIArt}</pre>', outputPath, report
|
130 |
+
|
131 |
+
# Create a Gradio interface
|
132 |
+
with gr.Blocks() as demo:
|
133 |
+
gr.Markdown('## ASCII Art Generator\nUpload an image to generate ASCII art.')
|
134 |
+
imgInput = gr.Image(type='numpy', label='Upload Image')
|
135 |
+
ASCIIOutput = gr.HTML(label='ASCII Art Output')
|
136 |
+
fileOutput = gr.File(label='Download ASCII Art')
|
137 |
+
scoreOutput = gr.Textbox(label='ASCII Conversion Performance')
|
138 |
+
imgInput.change(fn=generateArt, inputs=imgInput, outputs=[ASCIIOutput, fileOutput, scoreOutput])
|
139 |
+
|
140 |
+
|
141 |
+
demo.launch(share=True)
|