📜  使用四叉树进行图像处理

📅  最后修改于: 2021-04-17 10:27:34             🧑  作者: Mango

四叉树是在二维平面中存储和定位点数据的有效方法。四叉树的另一种有效用途是在图像处理领域。

与点存储不同,在图像处理中,我们得到一个完整的四叉树,其叶节点由图像的各个像素组成。因此,我们可以利用数组来存储树的节点。这样可以减少处理所需的内存(与链接表示相比)。

四叉树的最低级别将包含N个节点,这些节点等于图像中的像素数。下一级将包含N / 4个节点。
因此,可以通过以下公式找到所需的节点总数:N + N / 4 + N / 16 +…+ 1。
要获得上限,我们可以使用几何级数加和到无穷大的和
节点= N /(1 – 1/4)= 4/3 * N
这是必需的数组大小。
因此,所需的内存量为O(N)

class 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

上面显示的Python中的代码演示了像素的类定义。

插入方式
与经典四叉树不同,对于图像处理,我们可以以O(N)时间复杂度插入所有节点。
首先,我们将所有叶节点直接插入数组的最后N个位置。以下代码段演示了这一点:

# 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

在上面的代码片段中,self.tree是节点的数组,self.size是数组的总大小,self.image是图像的像素,count用于遍历树。

对于不是叶子的节点,R,G,B值通过取子代值的平均值来计算。
通过获取子代x和y的最大值和最小值来获得topLeft和bottomRight。

# 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)

在这里我们可以看到,我们取了四个孩子的R,G,B的值,然后除以4,得到平均值。

假设孩子的值是已知的,这些值的计算将在O(1)时间中进行。由于我们以相反的顺序移动,因此,先计算孩子的值,再计算父母的值。
因此,插入发生在O(N)中。

在图像处理中的应用
让我们说我们希望将高质量的图像转换为缩略图。为图像创建四叉树后,通过选择四叉树的高度,我们可以选择所获得图像的质量。如果高度等于四叉树的高度,则我们保留原始图像。在较低的高度,我们获得的图像质量较差。

如果我们不希望降低图像质量,我们可以尝试通过所谓的“修剪”来压缩图像。在这种情况下,颜色接近其父本的叶子将被移除。持续进行此操作,直到无法移除其他叶子为止。然后仅使用叶节点获取最低级别的图像以形成图像。虽然这不会显着降低图像质量,但会导致图像压缩程度较小。

完整的代码:

from PIL import Image
import math
class 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()