我正在探索一种在二维坐标(蓝色区域)上从以下区域采样的方法:
当然,基线是我可以对随机数(x,y)
进行采样,然后检查它是否与较小的方框重叠或在较大的方框之外。但是,经过一些快速试用,这只会浪费太多的计算资源。
任何建议或建议都将不胜感激,谢谢。
答案 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.0
和1.0
之间的随机值映射到适当的区域。
这种方法的优点是
以下是显示结果的示例,拖动滑块以生成1 ... 2000点:
它是通过以下MCVE生成的。它是用Java实现的,但是只要您拥有Point
和Rectangle
之类的结构,将其移植到其他语言就应该很简单了:
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);
}