从给定区域采样

时间:2019-06-30 11:51:11

标签: algorithm random sampling

我正在探索一种在二维坐标(蓝色区域)上从以下区域采样的方法:

enter image description here

当然,基线是我可以对随机数(x,y)进行采样,然后检查它是否与较小的方框重叠或在较大的方框之外。但是,经过一些快速试用,这只会浪费太多的计算资源。

任何建议或建议都将不胜感激,谢谢。

3 个答案:

答案 0 :(得分:2)

可能存在一些约束,可能允许使用更简单的解决方案。

因此以下内容可能无法满足您的要求!

但这是一个非常通用的解决方案,这就是为什么我希望可以在此处发布它。

首先,从图形上看,矩形始终始终位于原点(两个矩形)的中心。如果这是一个有效的假设,则可以简化以下解决方案的各个部分。

然后,尚不清楚应如何使用您建议的“基准”解决方案。建议您生成点(x,y),并针对每个点检查其是否包含在内部矩形中。如果它包含在内部矩形中,则将其丢弃。

现在假设您要从蓝色区域采样100个点。为了确定您发现100个没有被丢弃的点,您必须生成多少个点?

这不能确定地解决。或更正式地讲:您无法提供其totally correct实现。随机数生成器可以始终生成位于内部矩形中的点,因此将其丢弃。当然,它实际上不会这样做,但是您无法证明这一点,这就是重点。

如果内部矩形与外部矩形相比“较大”,则将具有实际意义。您可能只需要生成几百万个点,即可获得位于内部和外部矩形之间的窄边距中的100个点。


但是,以下是不受上述问题困扰的解决方案。这是有代价的:这不是一个特别有效的解决方案(尽管如上所述,与基准解决方案相比,“相对效率”取决于矩形的大小和使用模式)。

假设拐角点的坐标如下图所示:

(x0,y0)                        (x3,y3)
   O------------------------------O
   |                              |
   |  (ix0,iy0)        (ix3,iy3)  |
   |      O----------------O      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      O----------------O      |
   |  (ix1,iy1)        (ix2,iy2)  |
   |                              |
   O------------------------------O
(x1,y1)                        (x2,y2)

(请注意,坐标是任意的,矩形不一定以原点为中心)

由此,您可以计算可能包含点的区域:

   O------O----------------O------O
   |      |                |      |
   |  R0  |       R1       |  R2  |
   O------O----------------O------|
   |      |                |      |
   |      |                |      |
   |  R2  |                |  R4  |
   |      |                |      |
   |      |                |      |
   O------O----------------O------O
   |  R5  |       R6       |  R7  |
   |      |                |      |
   O------O----------------O------O

现在,当您要对n个点进行采样时,可以为每个点随机选择这些区域之一,并将该点放置在该区域内的随机位置。

一个警告就是选择区域:相对于所有区域的总面积,选择区域的概率应对应于该区域的 area 。实用上,您可以计算所有区域的总面积(为outer.w*outer.h-inner.w*inner.h),然后计算最终出现在区域R0...R7之一中的点的累积概率分布。通过这些累积分布,您可以将0.01.0之间的随机值映射到适当的区域。

这种方法的优点是

  • 它适用于任何矩形(只要外部矩形包含内部矩形...)
  • 您可以直接预先指定要生成的点数 ,它将精确地生成 个点
  • 可以确定地实施(即完全正确)

以下是显示结果的示例,拖动滑块以生成1 ... 2000点:

RegionNoise

它是通过以下MCVE生成的。它是用Java实现的,但是只要您拥有PointRectangle之类的结构,将其移植到其他语言就应该很简单了:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

public class RegionNoiseTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());

        RegionNoiseTestPanel panel = 
            new RegionNoiseTestPanel();
        f.getContentPane().add(panel, BorderLayout.CENTER);

        JSlider nSlider = new JSlider(1, 2000, 1);
        nSlider.addChangeListener(e -> 
        {
            panel.generatePoints(nSlider.getValue());
        });
        nSlider.setValue(100);
        f.getContentPane().add(nSlider, BorderLayout.SOUTH);

        f.setSize(500,450);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

}

class RegionNoiseTestPanel extends JPanel
{
    private final Rectangle2D outer;
    private final Rectangle2D inner;
    private List<Point2D> points;


    RegionNoiseTestPanel()
    {
        outer = new Rectangle2D.Double(50, 50, 400, 300);
        inner = new Rectangle2D.Double(90, 100, 300, 200);
    }

    public void generatePoints(int n)
    {
        this.points = createPoints(n, outer, inner, new Random(0));
        repaint();
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(new Color(220, 220, 220));
        g.fill(outer);
        g.setColor(new Color(160, 160, 160));
        g.fill(inner);

        if (points != null)
        {
            g.setColor(Color.BLUE);
            for (Point2D p : points)
            {
                double r = 2;
                double x = p.getX();
                double y = p.getY();
                g.fill(new Ellipse2D.Double(x - r, y - r, r + r, r + r));
            }
        }


    }

    private static List<Point2D> createPoints(
        int n, Rectangle2D outer, Rectangle2D inner, Random random)
    {
        List<Rectangle2D> regions = computeRegions(outer, inner);
        double cumulativeRegionAreas[] = new double[8];
        double outerArea = outer.getWidth() * outer.getHeight();
        double innerArea = inner.getWidth() * inner.getHeight();
        double relevantArea = outerArea - innerArea;
        double areaSum = 0;
        for (int i = 0; i < regions.size(); i++)
        {
            Rectangle2D region = regions.get(i);
            double area = region.getWidth() * region.getHeight();
            areaSum += area;
            cumulativeRegionAreas[i] = areaSum / relevantArea;
        }

        List<Point2D> points = new ArrayList<Point2D>();
        for (int i=0; i<n; i++)
        {
            points.add(createPoint(
                regions, cumulativeRegionAreas, random));
        }
        return points;
    }

    private static List<Rectangle2D> computeRegions(
        Rectangle2D outer, Rectangle2D inner)
    {
        List<Rectangle2D> regions = new ArrayList<Rectangle2D>();
        for (int r = 0; r < 8; r++)
        {
            regions.add(createRegion(outer, inner, r));
        }
        return regions;
    }

    private static Point2D createPoint(
        List<Rectangle2D> regions, 
        double normalizedCumulativeRegionAreas[], 
        Random random)
    {
        double alpha = random.nextDouble();
        int index = Arrays.binarySearch(normalizedCumulativeRegionAreas, alpha);
        if (index < 0)
        {
            index = -(index + 1);
        }
        Rectangle2D region = regions.get(index);
        double minX = region.getMinX();
        double minY = region.getMinY();
        double maxX = region.getMaxX();
        double maxY = region.getMaxY();
        double x = minX + random.nextDouble() * (maxX - minX);
        double y = minY + random.nextDouble() * (maxY - minY);
        return new Point2D.Double(x, y);
    }

    private static Rectangle2D createRegion(
        Rectangle2D outer, Rectangle2D inner, int region)
    {
        double minX = 0;
        double minY = 0;
        double maxX = 0;
        double maxY = 0;
        switch (region) 
        {
            case 0:
                minX = outer.getMinX();
                minY = outer.getMinY();
                maxX = inner.getMinX();
                maxY = inner.getMinY();
                break;

            case 1:
                minX = inner.getMinX();
                minY = outer.getMinY();
                maxX = inner.getMaxX();
                maxY = inner.getMinY();
                break;

            case 2:
                minX = inner.getMaxX();
                minY = outer.getMinY();
                maxX = outer.getMaxX();
                maxY = inner.getMinY();
                break;

            case 3:
                minX = outer.getMinX();
                minY = inner.getMinY();
                maxX = inner.getMinX();
                maxY = inner.getMaxY();
                break;

            case 4:
                minX = inner.getMaxX();
                minY = inner.getMinY();
                maxX = outer.getMaxX();
                maxY = inner.getMaxY();
                break;

            case 5:
                minX = outer.getMinX();
                minY = inner.getMaxY();
                maxX = inner.getMinX();
                maxY = outer.getMaxY();
                break;

            case 6:
                minX = inner.getMinX();
                minY = inner.getMaxY();
                maxX = inner.getMaxX();
                maxY = outer.getMaxY();
                break;

            case 7:
                minX = inner.getMaxX();
                minY = inner.getMaxY();
                maxX = outer.getMaxX();
                maxY = outer.getMaxY();
                break;
        }
        return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
    }

}

我仍然很好奇,是否有人找到一种优雅的确定性方法,而不必 为要生成的点定义各种“区域” ...

答案 1 :(得分:1)

如果蓝色区域与原点对称,则可以根据从单位正方形采样的随机点创建映射。考虑下面的伪代码函数,假设两个矩形都围绕原点居中

def sample():
    sample point x_base from [-1, 1] and calculate x = sign(x_base)*x1 + x_base*(x2-x1)
    sample point y_base from [-1, 1] and calculate y = sign(y_base)*y1 + y_base*(y2-y1)
    if (x,y) == (0,0) then:
        # recursively sample again in the rare case of sampling 0 for both dimensions
        return sample()
    else:
        return (x,y)

编辑:Marco13所指出,此解决方案无法从整个蓝色区域正确采样。查看他的答案以获得更好的方法。

答案 2 :(得分:1)

这里的解决方案假设蓝色区域是对称的并且以原点为中心,因此有4个参数(x1,x2,y1,y2)。想象一下,蓝色区域的内部是另一个具有相同比例但缩小的区域,因此该另一个区域的外部边界恰好适合蓝色区域的内部边界。如果我们随机生成一个点并将其放置在此内部区域内,则可以通过分别将x和y分别按x2 / x1和y2 / y1缩放来将其映射到蓝色区域中的点。现在想象一下这个区域内的另一个区域,以及它内部的另一个区域,是无限的。然后,只需将其放大正确的次数即可将任意点(除原点外)映射到蓝色区域中的点:

// generate a random point:
double x = 0.0, y = 0.0;
while(x == 0.0 && y == 0.0) // exclude the origin
{
    x = random.NextDouble() * x2;
    y = random.NextDouble() * y2;
}

// map to the blue region
while(x < x1 && y < y1)
{
    x *= (x2 / x1);
    y *= (y2 / y1);
}

// randomly choose a quadrant:
int r = random.Next(0, 4);
if((r & 1) != 0)
    x = -x;
if((r & 2) != 0)
    y = -y;

但是,由于第二个while循环(这实际上保证了第一个while循环永远不会运行超过一次),这并没有那么好。可以使用对数消除循环:

// map to the blue region
if(x < x1 && y < y1)
{
    double xpower = Math.Ceiling((Math.Log(x1) - Math.Log(x)) / Math.Log(x2/x1));
    double ypower = Math.Ceiling((Math.Log(y1) - Math.Log(y)) / Math.Log(y2/y1));
    double power = Math.Min(xpower, ypower);
    x *= Math.Pow(x2/x1, power);
    y *= Math.Pow(y2/y1, power);
}
相关问题