如何使用 NumPy 用 non-overlapping 个矩形随机填充一个区域?

How to randomly fill a region with non-overlapping rectangles using NumPy?

如何使用 NumPy 用随机大小的矩形随机填充给定的矩形区域,而不让矩形相互重叠?

我的想法是创建一个与区域形状相同的二维数组,用零填充数组,然后对于每个需要的矩形,随机select数组内两个未设置的坐标,将这两个点做成一个矩形,然后用1.

填充矩形对应的数组里面的区域

不知何故它不起作用:

代码:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
from random import randbytes, randrange

def random_rectangles(width=1920, height=1080, number=24):
    fig = plt.figure(figsize=(width/100, height/100), dpi=100, facecolor='black')
    ax = fig.add_subplot(111)
    ax.set_axis_off()
    grid = np.zeros((height, width))
    for i in range(number):
        free = np.transpose(np.nonzero(grid == 0))
        y1, x1 = free[randrange(free.shape[0])]
        y2, x2 = free[randrange(free.shape[0])]
        if x1 > x2: x1, x2 = x2, x1
        if y1 > y2: y1, y2 = y2, y1
        grid[y1:y2, x1:x2] = 1
        w, h = x2-x1, y2-y1
        x, y = x1, -y2
        color = '#'+randbytes(3).hex()
        ax.add_patch(Rectangle((x, y), w, h, fill=True,facecolor=color,edgecolor='#808080',lw=1))
    
    plt.xlim(0, width)
    plt.ylim(-height, 0)
    plt.axis('scaled')
    fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
    plt.show()

我不明白,我试过这个:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
import random

class Grid:
    def __init__(self, x1, x2, y1, y2):
        assert x2 > x1 and y2 > y1
        self.x1 = x1
        self.x2 = x2
        self.y1 = y1
        self.y2 = y2
        self.subgrids = []
        self.divisions = dict()
        self.last_subgrid = None
    
    def random(self):
        if not self.subgrids:
            x = self.x1 + random.random() * (self.x2 - self.x1)
            y = self.y1 + random.random() * (self.y2 - self.y1)
            return x, y
        else:
            if not self.last_subgrid:
                subgrid = random.choice(self.subgrids)
                self.last_subgrid = subgrid
                return subgrid.random()
            else:
                x, y = self.last_subgrid.random()
                self.last_subgrid = None
                return x, y

    
    def set_subgrid(self, shape):
        x1, x2, y1, y2 = shape
        assert x2 > x1 and y2 > y1
        assert self.x1 <= x2 <= self.x2 and self.y1 <= y2 <= self.y2
        if not self.subgrids:
            eight = [
                (self.x1, x1, self.y1, y1),
                (x1, x2, self.y1, y1),
                (x2, self.x2, self.y1, y1),
                (x1, x2, y1, y2),
                (x2, self.x2, y1, y2),
                (self.x1, x1, y2, self.y2),
                (x1, x2, y2, self.y2),
                (x2, self.x2, y2, self.y2)
            ]
            for a, b, c, d in eight:
                if a != b and c != d:
                    subgrid = Grid(a, b, c, d)
                    self.subgrids.append(subgrid)
                    self.divisions[(a, b, c, d)] = subgrid
        
        else:
            for a, b, c, d in self.divisions:
                if a <= x1 < x2 <= b and c <= y1 < y2 <= d:
                    self.divisions[(a, b, c, d)].set_subgrid((x1, x2, y1, y2))

def random_rectangles(width=1920, height=1080, number=24):
    fig = plt.figure(figsize=(width/100, height/100), dpi=100, facecolor='black')
    ax = fig.add_subplot(111)
    ax.set_axis_off()
    grid = Grid(0, width, 0, height)
    for i in range(number):
        x1, y1 = grid.random()
        x2, y2 = grid.random()
        if x1 > x2: x1, x2 = x2, x1
        if y1 > y2: y1, y2 = y2, y1
        grid.set_subgrid((x1, x2, y1, y2))
        w, h = x2-x1, y2-y1
        color = '#'+random.randbytes(3).hex()
        ax.add_patch(Rectangle((x1, y1), w, h, fill=True,facecolor=color,edgecolor='#808080',lw=1))
    
    plt.xlim(0, width)
    plt.ylim(0, height)
    plt.axis('scaled')
    fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
    plt.show()

没用:


我做到了

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import random

class Grid:
    def __init__(self, x1, x2, y1, y2):
        assert x2 > x1 and y2 > y1
        self.x1 = x1
        self.x2 = x2
        self.y1 = y1
        self.y2 = y2
        self.subgrids = []
    
    def random(self):
        if not self.subgrids:
            x = self.x1 + random.random() * (self.x2 - self.x1)
            y = self.y1 + random.random() * (self.y2 - self.y1)
            four = [
                    (self.x1, x, self.y1, y),
                    (x, self.x2, self.y1, y),
                    (self.x1, x, y, self.y2),
                    (x, self.x2, y, self.y2)
                ]
            for a, b, c, d in four:
                if a != b and c != d:
                    subgrid = Grid(a, b, c, d)
                    self.subgrids.append(subgrid)
        else:
            random.choice(self.subgrids).random()

    def flatten(self):
        if not self.subgrids:
            return
        
        result = []
        for subgrid in self.subgrids:
            if not subgrid.subgrids:
                result.append((subgrid.x1, subgrid.x2, subgrid.y1, subgrid.y2))
            else:
                result.extend(subgrid.flatten())
        
        return result

def random_rectangles(width=1920, height=1080, number=24):
    fig = plt.figure(figsize=(width/100, height/100), dpi=100, facecolor='black')
    ax = fig.add_subplot(111)
    ax.set_axis_off()
    grid = Grid(0, width, 0, height)
    for i in range(number): grid.random()
    rectangles = grid.flatten()
    for x1, x2, y1, y2 in rectangles:
        w, h = x2-x1, y2-y1
        color = '#'+random.randbytes(3).hex()
        ax.add_patch(Rectangle((x1, y1), w, h, fill=True,facecolor=color,edgecolor='#808080',lw=1))
    
    plt.xlim(0, width)
    plt.ylim(0, height)
    plt.axis('scaled')
    fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
    plt.show()

我终于做到了,但是结果不是我想象的那样,我觉得我的实现还不够好。谁能帮帮我?

从另一个方向解决问题可以获得更好的结果。

你所做的是在每次采样时将样本 space 分成子 spaces,然后你再次在所述子spaces 中采样(或类似的东西类似于那个)。

如果您事先拆分样本 space,您可以获得均匀采样 space 的网格,您可以从中采样:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
from numpy.typing import ArrayLike
from numpy.random import default_rng
rng = default_rng(42069)


def get_rects(n: int) -> np.ndarray:
    """
    Params
    ------
    n: number of rectangles
    """
    ceilsqrtn = int(np.ceil(np.sqrt(n)))
    n_grids = ceilsqrtn * ceilsqrtn

    # Create rectangles in the "full space", that is the [0, 1] space
    rects = rng.uniform(size = (n, 4))

    # To ensure that the rectangles are in (x1, x2, y1, y2) format where
    # Upper left corner is (x1, y1) and bottom right corner (x2, y2)
    # Result looks fine without this, but it's a nice to have
    rects[:,:2].sort(1)
    rects[:,2:].sort(1)

    # Create a ceilsqrtn x ceilsqrtn even grid space
    flat_grid_indices = rng.choice(n_grids, n, False)
    offsets = np.unravel_index(flat_grid_indices, (ceilsqrtn, ceilsqrtn))

    # Move each rectangle into their own randomly assigned grid
    # This will result with triangles in a space that is ceilsqrtn times larger than the [0, 1] space
    rects[:,:2] += offsets[1][..., None]
    rects[:,2:] += offsets[0][..., None]

    # Scale everything down to the [0, 1] space
    rects /= ceilsqrtn

    return rects
    

def plot_rects(rects: ArrayLike, width: int = 10, height: int = 10):
    fig, ax = plt.subplots(figsize=(width, height))
    for x1, x2, y1, y2 in rects:
        ax.add_patch(Rectangle((x1, y1), x2 - x1, y2 - y1, facecolor='gray', edgecolor='black', fill=True))
    plt.show()

rects = get_rects(150)
plot_rects(rects)

结果:

至少这是一个好的开始。要获得具有其他特征的矩形,您可以简单地从另一个分布(例如高斯分布)中采样。这样,您将获得更多相似的矩形,但您需要将溢出的矩形剪裁到其他 subspaces 或类似的东西中。另一种选择是 [0,1] 域内的截断分布。

编辑:代码清理和更好的评论