-
Notifications
You must be signed in to change notification settings - Fork 5
/
quadtree.py
177 lines (130 loc) · 5.38 KB
/
quadtree.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
import numpy as np
import cv2
from PIL import Image, ImageDraw
MAX_DEPTH = 8
DETAIL_THRESHOLD = 13
SIZE_MULT = 1
def average_colour(image):
# convert image to np array
image_arr = np.asarray(image)
# get average of whole image
avg_color_per_row = np.average(image_arr, axis=0)
avg_color = np.average(avg_color_per_row, axis=0)
return (int(avg_color[0]), int(avg_color[1]), int(avg_color[2]))
def weighted_average(hist):
total = sum(hist)
error = value = 0
if total > 0:
value = sum(i * x for i, x in enumerate(hist)) / total
error = sum(x * (value - i) ** 2 for i, x in enumerate(hist)) / total
error = error ** 0.5
return error
def get_detail(hist):
red_detail = weighted_average(hist[:256])
green_detail = weighted_average(hist[256:512])
blue_detail = weighted_average(hist[512:768])
detail_intensity = red_detail * 0.2989 + green_detail * 0.5870 + blue_detail * 0.1140
return detail_intensity
class Quadrant():
def __init__(self, image, bbox, depth):
self.bbox = bbox
self.depth = depth
self.children = None
self.leaf = False
# crop image to quadrant size
image = image.crop(bbox)
hist = image.histogram()
self.detail = get_detail(hist)
self.colour = average_colour(image)
def split_quadrant(self, image):
left, top, width, height = self.bbox
# get the middle coords of bbox
middle_x = left + (width - left) / 2
middle_y = top + (height - top) / 2
# split root quadrant into 4 new quadrants
upper_left = Quadrant(image, (left, top, middle_x, middle_y), self.depth+1)
upper_right = Quadrant(image, (middle_x, top, width, middle_y), self.depth+1)
bottom_left = Quadrant(image, (left, middle_y, middle_x, height), self.depth+1)
bottom_right = Quadrant(image, (middle_x, middle_y, width, height), self.depth+1)
# add new quadrants to root children
self.children = [upper_left, upper_right, bottom_left, bottom_right]
class QuadTree():
def __init__(self, image):
self.width, self.height = image.size
# keep track of max depth achieved by recursion
self.max_depth = 0
# start compression
self.start(image)
def start(self, image):
# create initial root
self.root = Quadrant(image, image.getbbox(), 0)
# build quadtree
self.build(self.root, image)
def build(self, root, image):
if root.depth >= MAX_DEPTH or root.detail <= DETAIL_THRESHOLD:
if root.depth > self.max_depth:
self.max_depth = root.depth
# assign quadrant to leaf and stop recursing
root.leaf = True
return
# split quadrant if there is too much detail
root.split_quadrant(image)
for children in root.children:
self.build(children, image)
def create_image(self, custom_depth, show_lines=False):
# create blank image canvas
image = Image.new('RGB', (self.width, self.height))
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, self.width, self.height), (0, 0, 0))
leaf_quadrants = self.get_leaf_quadrants(custom_depth)
# draw rectangle size of quadrant for each leaf quadrant
for quadrant in leaf_quadrants:
if show_lines:
draw.rectangle(quadrant.bbox, quadrant.colour, outline=(0, 0, 0))
else:
draw.rectangle(quadrant.bbox, quadrant.colour)
return image
def get_leaf_quadrants(self, depth):
if depth > self.max_depth:
raise ValueError('A depth larger than the trees depth was given')
quandrants = []
# search recursively down the quadtree
self.recursive_search(self, self.root, depth, quandrants.append)
return quandrants
def recursive_search(self, tree, quadrant, max_depth, append_leaf):
# append if quadrant is a leaf
if quadrant.leaf == True or quadrant.depth == max_depth:
append_leaf(quadrant)
# otherwise keep recursing
elif quadrant.children != None:
for child in quadrant.children:
self.recursive_search(tree, child, max_depth, append_leaf)
def create_gif(self, file_name, duration=1000, loop=0, show_lines=False):
gif = []
end_product_image = self.create_image(self.max_depth, show_lines=show_lines)
for i in range(self.max_depth):
image = self.create_image(i, show_lines=show_lines)
gif.append(image)
# add extra images at end
for _ in range(4):
gif.append(end_product_image)
gif[0].save(
file_name,
save_all=True,
append_images=gif[1:],
duration=duration, loop=loop)
if __name__ == '__main__':
#image_path = "./images/eye.jpg"
image_path = "/home/alpharaoh/Pictures/mountain.jpg"
# load image
image = Image.open(image_path)
image = image.resize((image.size[0] * SIZE_MULT, image.size[1] * SIZE_MULT))
# create quadtree
quadtree = QuadTree(image)
# create image with custom depth
depth = 7
image = quadtree.create_image(depth, show_lines=False)
quadtree.create_gif("mountain_quadtree.gif", show_lines=True)
# show image
# image.show()
image.save("mountain_quadtree.jpg")