这是一个针对两个目标的优化问题:边界框大小和误差(相对距离)。因此,您有可能的解决方案的帕累托前沿,每个解决方案都针对边界框大小与误差的不同权衡进行优化。
如果你对你的一个目标设置一个界限,然后在给定这个界限的情况下,尽可能地解决另一个目标,问题就会简单得多。所以你可以说“给定一个特定的边界框大小,我怎样才能最小化错误?”或“给定最大可接受误差,我怎样才能最小化边界框大小?”
我将假设您更可能考虑特定的边界框大小而不是特定的最大误差,所以让我们从假设边界框开始,并尝试将点分配给唯一位置的误差最小化在那个边界框中。
您需要使用一种优化技术来很好地处理您的问题的离散风格。我会选择模拟退火。
从一颗好种子开始
给定边界框大小,您可以将整个图像缩小到边界框大小...但有些点最终会位于同一位置。将此称为“幼稚定位”:
.........................................
. . . . .
. . 2 3 . . .
. 1 . . . 4 .
.........................................
. . . . .
. . . . .
. . . . .
.........................................
. . . . .
. . . . .
. . 5 . . 6 .
.........................................
. 7 . . . .
. 8 . . . .
. . . . .
.........................................
好的,我们可以解决这个问题。定义您的仓位排序。您可以天真地执行此操作(从左到右,从上到下)或以更好地保留局部性的方式(希尔伯特曲线)。我们将遍历这些位置,当我们找到一个有多个点的位置时,尝试在有意义的情况下将额外的点分配给可用的空位置。
Keep a queue of DisplacedPoints
Keep a stack of EmptyPositions
Iterate over positions in the order you've chosen:
If this position is empty,
Push this position onto EmptyPositions
Else If this position has more than one point,
Enqueue all but one of these points into DisplacedPoints
Placement: While there are remaining DisplacedPoints and remaining EmptyPositions,
Let candidatePoint = DiplacedPoints.Peek
Let candidatePosition = EmptyPositions.Peek
Let currentDisplacement = the distance from the current position to candidatePoint's naive position
Let candidateDisplacement = the distance from candidatePosition to candidatePoint's naive position
If currentDisplacement >= candidateDisplacement,
place candidatePoint in candidatePosition
pop candidatePosition off EmptyPositions
dequeue candidatePoint off DisplacedPoints
Else break out of Placement loop
While there are remaining DisplacedPoints and remaining EmptyPositions,
Dequeue a DisplacedPoint D, pop an EmptyPosition E, and put D into E
如果这太复杂了,更简单的方法是迭代位置;当你遇到一个有太多点的位置时,将额外的点加入队列,每当你遇到一个空的位置时,将一个点从队列中取出。如果您到达终点并且队列中仍有点,则向后迭代这些位置(不再有任何超过一个点)并将点出列到空白空间中。
这无论如何都不是最佳解决方案。这只是一个很好的初步猜测,可以让模拟退火有一个良好的开端。
目标函数性能
朴素的目标函数类似于(原始图像中的点之间的距离)和(缩小的图像中的点之间的距离,乘以缩放)之间的平方差的总和(在所有点对上)因素)。但是有 20k 点,大约是 4 亿平方差!我们可以通过从一个愚蠢但快速的目标函数开始取得初步进展,然后切换到更昂贵(和正确)的函数来获得更精细的区别,从而更快地收敛到一个好的解决方案。
您可以使用的最快、最愚蠢的目标函数是 O(n):将所有点与自己的朴素定位的距离相加。
一个较慢但更正确的目标函数为每个点分配“相关邻居”:对于每个点 P,我们将采用 P 的幼稚定位*周围 3x3 邻域中的点。现在,您可以对所有点 P 求和(在所有 P 的相关邻居上)图像距离与(缩放的)指定位置距离之间的平方差。
*由于从 A 到 B 的距离与从 B 到 A 的距离相同,一旦点 X 是 P 的相关邻居之一,我们不必将 P 视为 X 的相关邻居之一。利用这一点可以将速度提高 2 倍。
在那些更快的目标函数让您更接近最优之后,您可以使用上面列出的朴素目标函数(对所有对的距离平方差求和)。我不确定你真的想要这个 - 天真的目标函数想要保留长距离结构,但你可能只关心保留邻居之间的距离。
编辑:重新缩放的总体思路应该可行,但不是进行简单的重新缩放,然后在事后进行调整,另一种方法是让重新缩放本身更智能。
考虑使用Content-Aware Scaling。在水平和垂直缩放之间反复交替,以使整体形状尽可能接近原始形状。
为了保持附近点之间的空间关系,您可以调整接缝生成中使用的能量函数。首先将值 1.0 分配给有点的位置,将 0.0 分配给没有点的位置,然后应用高斯模糊。这将导致接缝生成更倾向于在开始折叠附近点之间的小空间之前切掉大的空白空间。
如果这太昂贵/太复杂,您可以尝试更简单的方法:
以你原来的例子(给点不同的身份):
0 0 1 0 0
2 0 0 0 0
0 3 0 4 0
0 0 0 0 0
0 5 0 0 6
找到您的点的平均 x 和 y 坐标。在此示例中,平均位置位于 3 的右下方。从逻辑上将您的数组分成四个在平均位置相交的象限。
0 0|1 0 0
2 0|0 0 0
0_3|0_4_0
0 0|0 0 0
0 5|0 0 6
现在可以独立处理每个象限。在每个象限中,有两个指向中心的网格对齐方向(例如,在左上象限中,向下和向右指向中心)。我们在这两个方向之间交替,将点移动到相邻的空白空间中。如果两个方向都没有改善,那么你就完成了那个象限。
所以,以右上象限为例,我们将点向下移动...
0 0|0 0 0
2 0|1 0 0
0_3|0_4_0
0 0|0 0 0
0 5|0 0 6
请注意,我们不会将 4 推到象限边界之外。然后,我们尝试向左推:
0 0|0 0 0
2 0|1 0 0
0_3|4_0_0
0 0|0 0 0
0 5|0 0 6
然后我们尝试向下推,但实际上没有任何动作。尝试向左移动,仍然没有任何移动。右上角的象限就完成了。让我们以同样的方式做其他象限:
0 0|0 0 0
0 2|1 0 0
0_3|4_0_0
0 5|6 0 0
0 0|0 0 0
现在,找到点的边界框并进行裁剪:
2 1
3 4
5 6