Image Manipulation Using Quadtrees

09 Feb, 2018
Quadtrees are an effective method to store and locate data of points in a two-dimensional plane. Another effective use of quadtrees is in the field of image manipulation.
Unlike in storage of points, in image manipulation we get a complete quadtree with the leaf nodes consisting of individual pixels of the image. Due to this, we can utilize an array to store the nodes of the tree. This leads to less memory needed (compared to linked representation) for processing.
The lowest level of the quadtree would contain N nodes equivalent to the number of pixels in the image. The next level would contain N / 4 nodes.Thus, the total number of nodes necessary can be found by: N + N / 4 + N / 16 + ... + 1.To get an upperbound we can use sum of geometric progression to infinityNodes = N / (1 – 1 / 4) = 4 / 3 * NThis is the size of the array necessary.Thus, the amount of memory needed is O(N)
class Point(object):    def __init__(self, x, y):        self.x = x        self.y = yclass Pixel(object):    def __init__(self, color = [0, 0, 0],             topLeft = Point(0, 0),             bottomRight = Point(0, 0)):        self.R = color[0]        self.G = color[1]        self.B = color[2]        self.topLeft = topLeft        self.bottomRight = bottomRight
The above code in Python shown demonstrates the class definition of a pixel.
InsertionUnlike in a classic quadtree, for image manipulation, we can insert all the nodes in O(N) time complexity.First, we insert all the leaf nodes directly into the last N positions of the array. The following code snippet demonstrates this:
# Store leaves into arraycount = 0for i in range(image.size[0] - 1, 0, -2):    for j in range(image.size[1] - 1, 0, -2):        self.tree[self.size - 1 - 4 * count] = Pixel(self.image[i, j],                 Point(i, j),                 Point(i, j))        self.tree[self.size - 2 - 4 * count] = Pixel(self.image[i, j - 1],                 Point(i, j - 1),                Point(i, j - 1))        self.tree[self.size - 3 - 4 * count] = Pixel(self.image[i - 1, j],                 Point(i - 1, j),                 Point(i - 1, j))        self.tree[self.size - 4 - 4 * count] = Pixel(self.image[i - 1, j - 1],                 Point(i - 1, j - 1),                 Point(i - 1, j - 1))        count += 1
In the above snippet, self.tree is the array of nodes, self.size is the total size of the array, and self.image is the pixels of the image, count is used to traverse the tree.
For nodes that aren’t leaves, the R, G, B values are calculated by taking the average of the values of the children.The topLeft and bottomRight are obtained by taking the maximum and minimum values of the x and y of the children.
# Calculate and create parent nodesfor i in range(self.size - 4 * count - 1, -1, -1):    self.tree[i] = Pixel(        [(self.tree[4 * i + 1].R + self.tree[4 * i + 2].R + self.tree[4 * i + 3].R + self.tree[4 * i + 4].R) / 4,         (self.tree[4 * i + 1].G + self.tree[4 * i + 2].G + self.tree[4 * i + 3].G + self.tree[4 * i + 4].G) / 4,         (self.tree[4 * i + 1].B + self.tree[4 * i + 2].B + self.tree[4 * i + 3].B + self.tree[4 * i + 4].B) / 4],        self.tree[4 * i + 1].topLeft,        self.tree[4 * i + 4].bottomRight)
Here we can see, we take the values of R, G, B of the four children, and we divide by 4, thus getting the average.
Calculation of these values happen in O(1) time assuming values of the children are known. Since we are moving in reverse order, the values of the children are computed before that of parents.Thus insertion occurs in O(N).
Application in Image ManipulationLet us say we wish to convert a high quality image to a thumbnail. Once we have created a quadtree for the image, by selecting a height of the quadtree we can select the quality of the image we obtain. If the height is equal to the height of the quadtree, then we retain the original image. At lower heights, we obtain images of lesser quality.
In case we do not wish to reduce the quality of the image, we can try to compress the image by what is known as “pruning”. In this, leafs with colours close to that of their parents are removed. This is continuously done until no further leaves can be removed. The lowest level is then taken to form the image, using only leaf nodes. While this does not reduce the quality of the image drastically, it can lead to a small compression of the image.
Complete Code:
from PIL import Imageimport mathclass Point(object):    def __init__(self, x, y):        self.x = x        self.y = y  class Pixel(object):    def __init__(self, color = [0, 0, 0],             topLeft = Point(0, 0),             bottomRight = Point(0, 0)):        self.R = color[0]        self.G = color[1]        self.B = color[2]        self.topLeft = topLeft        self.bottomRight = bottomRight  class quadtree():    def __init__(self, image):          # Total number of nodes of tree        self.size = 0          # Store image pixelmap        self.image = image.load()          # Array of nodes        self.tree = []        self.x = image.size[0]        self.y = image.size[1]          # Total number of leaf nodes        size = image.size[0] * image.size[1]          # Count number of nodes        while(size >= 1):            self.size += size            size /= 4          size = image.size[0] * image.size[1]          # Initialize array elements        for i in range(0, self.size):            self.tree.append(Pixel())          # Store leaves into array        count = 0        for i in range(image.size[0] - 1, 0, -2):            for j in range(image.size[1] - 1, 0, -2):                self.tree[self.size - 1 - 4 * count] = Pixel(self.image[i, j],                         Point(i, j),                         Point(i, j))                self.tree[self.size - 2 - 4 * count] = Pixel(self.image[i, j - 1],                         Point(i, j - 1),                        Point(i, j - 1))                self.tree[self.size - 3 - 4 * count] = Pixel(self.image[i - 1, j],                         Point(i - 1, j),                         Point(i - 1, j))                self.tree[self.size - 4 - 4 * count] = Pixel(self.image[i - 1, j - 1],                         Point(i - 1, j - 1),                         Point(i - 1, j - 1))                count += 1          # Calculate and create parent nodes        for i in range(self.size - 4 * count - 1, -1, -1):            self.tree[i] = Pixel(                [(self.tree[4 * i + 1].R + self.tree[4 * i + 2].R + self.tree[4 * i + 3].R + self.tree[4 * i + 4].R) / 4,                 (self.tree[4 * i + 1].G + self.tree[4 * i + 2].G + self.tree[4 * i + 3].G + self.tree[4 * i + 4].G) / 4,                 (self.tree[4 * i + 1].B + self.tree[4 * i + 2].B + self.tree[4 * i + 3].B + self.tree[4 * i + 4].B) / 4],                self.tree[4 * i + 1].topLeft,                self.tree[4 * i + 4].bottomRight)      def disp(self, level):        start = 0          # Calculate position of starting node of height        for i in range(0, level):            start = 4 * start + 1          # Invalid height given        if (start > self.size):            return          # Create a new image        img = Image.new("RGB", (self.x, self.y), "black")        pixels = img.load()          # Move from starting to last node on given height        for i in self.tree[start : 4 * start]:            x1 = i.topLeft.x            y1 = i.topLeft.y            x2 = i.bottomRight.x            y2 = i.bottomRight.y            for x in range(x1, x2 + 1):                for y in range(y1, y2 + 1):                      # Set colour                    pixels[x, y] = (i.R, i.G, i.B)          # Display image        img.show()
This article is contributed by Aditya Kamath. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
Image-Processing
Advanced Data Structure
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.