forked from davidepietrasanta/Image-compression-via-DCT
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathParte2.py
355 lines (289 loc) · 10.7 KB
/
Parte2.py
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# Parte 2
from numpy.core.shape_base import block
from scipy.fftpack import dct, idct
import pathlib
import cv2 as cv
from skimage.io import imread
from skimage.color import rgb2gray
import numpy as np
import matplotlib.pylab as plt
from PIL import Image
from numpy import asarray
import copy
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Parte2App(QWidget):
value_F, value_D, limitF, limitD = 0, 0, 0, 0
image_path = "default_path"
def __init__(self):
super().__init__()
self.title = 'DCT - Parte 2'
screen_resolution = app.desktop().screenGeometry()
self.width, self.height = screen_resolution.width(), screen_resolution.height()
self.left, self.top = 100, 100
self.image = QLabel(self)
self.image2 = QLabel(self)
self.initUI()
def initUI(self):
grid = QGridLayout()
grid.addWidget(self.createUploadImageGroup(), 0, 0)
grid.addWidget(self.createParametersGroup(), 0, 1)
grid.addWidget(self.createOriginalImageGroup(), 1, 0)
grid.addWidget(self.createCompressedImageGroup(), 1, 1)
self.setLayout(grid)
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, int(round(self.width / 2)), int(round(self.height / 2)))
self.show()
def createUploadImageGroup(self):
groupbox = QGroupBox('Carica/Calcola Immagine')
pushbutton = QPushButton('Carica immagine', self)
pushbutton.clicked.connect(self.getImage)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(pushbutton)
pushbutton2 = QPushButton('Calcola', self)
pushbutton2.clicked.connect(self.elaborateImage)
vbox.addWidget(pushbutton2)
vbox.addStretch(1)
groupbox.setLayout(vbox)
return groupbox
def createOriginalImageGroup(self):
groupbox = QGroupBox('Immagine Originale')
vbox = QVBoxLayout()
vbox.setAlignment(Qt.AlignCenter)
vbox.addWidget(self.image)
groupbox.setLayout(vbox)
return groupbox
def createParametersGroup(self):
groupbox = QGroupBox('Parametri')
self.lblf = QLabel('F')
self.spinboxf = QSpinBox()
self.spinboxf.setMinimum(1)
self.spinboxf.valueChanged.connect(self.value_changed)
self.lbld = QLabel('d')
self.spinboxd = QSpinBox()
self.spinboxd.setMinimum(0)
self.spinboxd.valueChanged.connect(self.value_changed)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(self.lblf)
vbox.addWidget(self.spinboxf)
vbox.addWidget(self.lbld)
vbox.addWidget(self.spinboxd)
vbox.addStretch(1)
groupbox.setLayout(vbox)
return groupbox
def value_changed(self):
self.value_D = self.spinboxd.value()
self.value_F = self.spinboxf.value()
self.limitD = (2 * self.value_F) - 2
self.spinboxd.setMaximum(self.limitD)
def createCompressedImageGroup(self):
groupbox = QGroupBox('Immagine Processata')
vbox = QVBoxLayout()
vbox.setAlignment(Qt.AlignCenter)
vbox.addWidget(self.image2)
groupbox.setLayout(vbox)
return groupbox
def getImage(self):
fname, _ = QFileDialog.getOpenFileName(self, 'Apri File',
'c:\\', "Image Files (*.bmp *.jpeg *.jpg)")
self.image_path = fname
grayScaleCheck = QPixmap(fname).toImage().isGrayscale()
if grayScaleCheck == False:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText("Errore")
msg.setInformativeText('Immagine non in scala di grigi')
msg.setWindowTitle("Errore")
msg.exec_()
else:
imgRead = cv.imread(fname)
self.imgHeight, self.imgWidth, imgChannel = imgRead.shape
self.image.setPixmap(
QPixmap(fname).scaled(int(round(self.width / 1.8)), int(round(self.height / 1.8)), Qt.KeepAspectRatio))
self.limitF = min(self.imgHeight, self.imgWidth)
self.spinboxf.setMaximum(self.limitF)
def elaborateImage(self):
array = JPEG_compression(self.image_path, self.value_F, self.value_D)
path_tmp = str(pathlib.Path(__file__).parent.absolute()) + "/temp.bmp"
cv.imwrite(path_tmp, array)
self.image2.setPixmap(
QPixmap(path_tmp).scaled(int(round(self.width / 1.8)), int(round(self.height / 1.8)), Qt.KeepAspectRatio))
def JPEG_simple(path):
"""
Simple dct/idct to see if correct
"""
# read lena RGB image and convert to grayscale
im = cv.imread(path, 0)
im = im[0:im.shape[0] - 1, 0:im.shape[1] - 1]
imF = cv.dct(im / 1.0)
dim_cut = 200
for r in range(0, im.shape[0]):
for c in range(0, im.shape[1]):
if r + c > dim_cut:
imF[r][c] = 0
im1 = cv.idct(imF / 1.0)
# check if the reconstructed image is nearly equal to the original image
np.allclose(im, im1)
# plot original and reconstructed images with matplotlib.pylab
plt.gray()
plt.subplot(121), plt.imshow(im), plt.axis('off'), plt.title('original image', size=20)
plt.subplot(122), plt.imshow(im1), plt.axis('off'), plt.title('reconstructed image (DCT+IDCT)', size=20)
plt.show()
def array_to_image(array, bw=False, save=False, path=None):
"""
Convert an array to an image.
bw: "Black and White", should be true if the image is B&W
save: If true save the image
path: If save is true the image is saved in following path
"""
if bw:
img = Image.fromarray(array, 'L') # Black and White
else:
img = Image.fromarray(array, 'RGB')
img.show()
if save:
path = path + "_i.jpg"
img.save(path)
def divide_immage_in_blocks(path, F):
"""
Divide the image into square blocks f of pixels of size FxF,
starting top left and discarding leftovers.
path: Image path
F: Block size
return: Matrix of matrix
"""
# img = Image.open(path)
img = cv.imread(path, 0)
# Convert PIL images into NumPy arrays
picture = asarray(img)
block_size = F
# Create the blocks
N = picture.shape[0]
M = picture.shape[1]
original_shape = [N, M]
blocks = []
for r in range(0, picture.shape[0], block_size):
if r + block_size > picture.shape[0]:
break
for c in range(0, picture.shape[1], block_size):
if c + block_size > picture.shape[1]:
break
window = picture[r:r + block_size, c:c + block_size]
blocks.append(window)
# array_to_image(window, bw=True)
return [blocks, original_shape]
def list_index_to_matrix(i, d):
"""
Simple converter to index of a list to index of a matrix.
"""
n = np.floor(i / d) # row
m = i % d # column
return [n, m]
def compression(list_of_blocks):
"""
Apply the dct on the list of blocks
"""
# list_of_blocks = copy.deepcopy(list_of_blocks)
list_blocks_dct = []
for block in list_of_blocks:
block_r = cv.dct(block / 1.0)
list_blocks_dct.append(block_r)
return list_blocks_dct
def cut_block(list_of_blocks, d):
"""
Cut the frequency of the blocks
"""
list_of_blocks_cut = [] # copy.deepcopy(list_of_blocks)
block_x = len(list_of_blocks[0])
block_y = len(list_of_blocks[0][0])
for block in list_of_blocks:
list_of_blocks_cut.append(block)
for block in list_of_blocks_cut:
for k in range(0, block_x):
for l in range(0, block_y):
if (k + l >= d):
block[k][l] = 0
return list_of_blocks_cut
def is_integer_num(n):
"""
Check if a number is an integer or not.
n: number
return: [boolean] True if the number is integer, False if it is decimal
"""
if isinstance(n, int):
return True
if isinstance(n, float):
return n.is_integer()
return False
def fix_inverse(block):
"""
While compressing idtc can produce floating numbers.
We need to fix that, returning value in [0, 255]
v: Is a np.array of one dimension [0:N-1]
return: A np.array of one dimension [0:N-1]
"""
x = len(block)
y = len(block[0])
for i in range(0, x):
for j in range(0, y):
# print(type(block[i][j]))
# print(block[i][j])
if block[i][j] > 255:
block[i][j] = 255
if block[i][j] < 0:
block[i][j] = 0
if not is_integer_num(block[i][j]):
block[i][j] = round(block[i][j])
return block
def inverse(list_of_blocks):
"""
Apply idct to the list of blocks
"""
list_of_blocks_inv = []
for block in list_of_blocks:
block_inv = cv.idct(block / 1.0)
block_inv_fix = fix_inverse(block_inv)
list_of_blocks_inv.append(block_inv_fix)
return list_of_blocks_inv
def recompose_image(list_of_blocks, original_shape, F):
n_block_x = int(np.floor(original_shape[0] / F))
n_block_y = int(np.floor(original_shape[1] / F))
real_x = n_block_x * F
real_y = n_block_y * F
matrix = np.zeros(shape=(real_x, real_y))
matrix = matrix.astype(np.uint8)
N_blocks = len(list_of_blocks)
for i in range(0, N_blocks):
# array_to_image(list_of_blocks[i], bw=True)
for y in range(0, F):
for x in range(0, F):
m_y = (i % n_block_y) * F + y
m_x = int(np.floor(i / n_block_y) * F) + x
matrix[m_x][m_y] = int(list_of_blocks[i][x][y])
return matrix
def JPEG_compression(path, F, d):
"""
Apply JPEG compression of image.
path: Path of the image
F: Dimension of each block FxF
d: Cut parameter
"""
if F % 2 == 1:
F = F + 1
[list_b, original_shape] = divide_immage_in_blocks(path, F)
list_c = compression(list_b)
# Usefull to see if blocks are done correctly
# for block in list_b:
# array_to_image(block, bw=True)
cut = cut_block(list_c, d)
inv = inverse(cut)
im = recompose_image(inv, original_shape, F)
return im
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Parte2App()
sys.exit(app.exec_())