kevinbjl commited on
Commit
1d9074b
1 Parent(s): 35251f2

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -0
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)