简体   繁体   English

如何使用 NumPy 随机填充具有非重叠矩形的区域?

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

How do I randomly fill a given rectangular region with rectangles of random sizes without the rectangles overlapping each other using NumPy?如何使用 NumPy 用随机大小的矩形随机填充给定的矩形区域,而不会使矩形相互重叠?

My idea is to create a two dimensional array with the same shape as the region, fill the array with zero, then for each rectangle required, randomly select two coordinates inside the array that are not set, make a rectangle from the two points, and fill the region inside the array corresponding to the rectangle with 1.我的想法是创建一个与区域形状相同的二维数组,用零填充数组,然后对于所需的每个矩形,随机选择数组内未设置的两个坐标,从两个点制作一个矩形,然后用 1 填充与矩形对应的数组内部区域。

Somehow it isn't working:不知何故它不起作用:

在此处输入图像描述

Code:代码:

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

I don't understand, I have tried this:我不明白,我试过这个:

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

It doesn't work:它不起作用:

在此处输入图像描述


I did it我做的

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

在此处输入图像描述

I finally did it, but the result is not what I imagined, and I don't think my implementation is good enough.我终于做到了,但结果并不是我想象的那样,我觉得我的实现还不够好。 Can anyone help me?谁能帮我?

You can achieve a better result by tackling your problem in another direction.你可以通过从另一个方向解决你的问题来获得更好的结果。

What you have done is to split up the sample space into subspaces for each time you sample, then you sample within said subspaces again (or something similar to that).您所做的是每次采样时将样本空间分成子空间,然后再次在所述子空间中采样(或类似的东西)。

If you split your sample space beforehand you can get a grid of even sampling spaces of which you can sample from instead:如果你事先分割你的样本空间,你可以得到一个均匀的采样空间网​​格,你可以从中采样:

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

Result:结果: 矩形

It is a good start at least.至少这是一个好的开始。 To obtain rectangles with other characteristics you could simply sample from another distribution, such as a Gaussian.要获得具有其他特征的矩形,您可以简单地从另一个分布中采样,例如高斯分布。 With that, you would obtain more similar rectangles, but you would need to clip the ones that overflow into other subspaces or something like that.这样,您将获得更多相似的矩形,但您需要剪切溢出到其他子空间或类似内容的矩形。 Another alternative are truncated distributions within the [0,1] domain.另一种选择是 [0,1] 域内的截断分布。

EDIT: Code cleanup and better comments编辑:代码清理和更好的评论

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM