我正在寻找一种方法来压缩稀疏矩阵,同时保留其轮廓的形状和(尽可能)非零点之间的相对距离。所以以图形方式展示我想要得到的东西:
对于给定的矩阵:
0 0 1 0 0
1 0 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 0 0 1
我希望得到以下矩阵之一:
0 1 0 1 1 0 1 1 0
1 1 1 0 1 1 1 1 0
0 1 1 0 1 1 1 0 1
当然,这里可以提供更多解决方案,只要算法一致,就没有“更好”的解决方案。我正在使用的矩阵是1024x1024,具有15-30k非零点。非零点是我在1024x1024图像中识别的要素的中心。我正在尝试生成这些特征的密集矩阵。
我尝试过kD-tree方法,我计算了每个非零点的n个最近邻点。然后,按随机顺序,我访问每个非零点,并将其最近邻居放置在3x3矩阵中的适当位置,当前点位于中心。这在某种程度上起作用,但导致仍然相当稀疏的矩阵和许多岛屿。不止一个邻居导致合并不一致。
有没有一些标准的方法来做我想做的事情?我错过了一些非常标准的算法吗?
你们是否知道什么是我的问题的一个好的,全球可优化的解决方案?
我现在在Python中实现这个(使用numpy)但我会感谢任何其他语言的任何通用算法建议或实现......
提前致谢!
编辑1:
这是a gist到目前为止我写的内容。我已经设法通过严格检查每个点的邻域来避免邻居冲突,但我发现有些点是从最终矩阵中弹出的。整件事情很慢......
输入为以下格式的CSV(目前只有cx
和cy
才有关系):
ParticleID,cx,cy,cz,Volume,Integral 0,Mean 0,Integral 1,Mean 1
编辑2: 对杰里答案的一些评论:
1)我想我没有明确说明我的主要目标:主要目标是确保最多点数由8个邻居包围,而不会对轮廓的整体形状或初始相对位置造成严重失真。点
2)缩放图像的想法似乎是一个解决方案,但我应该扩展多少?如何确定最佳比例因子?我不想将图像缩放到一定的大小。我想删除图像中非零点之间的空白区域。
答案 0 :(得分:0)
这是两个目标的优化问题:边界框大小和误差(相对距离)。因此,您可以获得可能解决方案的Pareto前端,每个解决方案都针对边界框大小与错误的不同权衡进行优化。
如果你对你的一个目标施加约束并且只是为了你能够在另一个目标上做出最好的解决,那么这个问题就会变得简单得多。所以你可以说"给定一个特定的边界框大小,我怎样才能最小化错误?"或者"给定最大可接受的误差,我如何最小化边界框大小?"
我会假设您更有可能考虑特定的边界框大小而不是特定的最大错误,所以让我们假设一个边界框并尝试最小化将点分配给该边界框中的唯一位置的错误。
您希望使用能够很好地应对问题的离散风格的优化技术。我会选择模拟退火。
从良好的种子开始
给定一个边界框大小,您可以将整个图像缩放到边界框大小...但是某些点将最终位于相同位置。将此称为" 天真定位":
.........................................
. . . . .
. . 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&#39周围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