【问题标题】:Compacting a sparse matrix while preserving outline shape在保留轮廓形状的同时压缩稀疏矩阵
【发布时间】:2015-08-13 10:37:29
【问题描述】:

我正在寻找一种方法来压缩稀疏矩阵,同时保留其轮廓的形状和(尽可能)非零点之间的相对距离。因此,以图形方式展示我想要得到的东西:

对于给定的矩阵:

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(目前只有 cxcy 重要):

ParticleID,cx,cy,cz,Volume,Integral 0,Mean 0,Integral 1,Mean 1

编辑 2: 对杰瑞的回答有一些看法:

1) 我想我没有清楚地说明我的主要目标:主要目标是确保最大数量的点被 8 个邻居包围,而不会对轮廓的整体形状或初始相对位置造成严重扭曲积分

2) 缩放图像的想法似乎是一种解决方案,但我应该缩放多少?如何确定最佳比例因子?我不想将图像缩放到特定大小。我想删除图像中非零点之间的空格。

【问题讨论】:

    标签: algorithm matrix sparse-matrix coordinate-transformation kdtree


    【解决方案1】:

    这是一个针对两个目标的优化问题:边界框大小和误差(相对距离)。因此,您有可能的解决方案的帕累托前沿,每个解决方案都针对边界框大小与误差的不同权衡进行优化。

    如果你对你的一个目标设置一个界限,然后在给定这个界限的情况下,尽可能地解决另一个目标,问题就会简单得多。所以你可以说“给定一个特定的边界框大小,我怎样才能最小化错误?”或“给定最大可接受误差,我怎样才能最小化边界框大小?”

    我将假设您更可能考虑特定的边界框大小而不是特定的最大误差,所以让我们从假设边界框开始,并尝试将点分配给唯一位置的误差最小化在那个边界框中。

    您需要使用一种优化技术来很好地处理您的问题的离散风格。我会选择模拟退火。

    从一颗好种子开始

    给定边界框大小,您可以将整个图像缩小到边界框大小...但有些点最终会位于同一位置。将此称为“幼稚定位”:

    .........................................
    .         .         .         .         .              
    .         .   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
    

    【讨论】:

    • 亲爱的杰瑞,感谢您的回答。我最初一直在考虑类似的事情,但在途中遇到了一些问题。请查看 EDIT2,因为它太长了,无法发表评论。
    • @REjsmont 添加了内容感知扩展解决方案和点推解决方案。内容感知缩放允许您在大小和保真度之间进行权衡,但会牺牲计算时间。点推创建了可能有一些失真的残酷紧凑的安排。
    • 好的——去试试吧!谢谢!
    猜你喜欢
    • 2015-12-05
    • 1970-01-01
    • 2023-03-29
    • 2011-10-02
    • 2015-12-30
    • 2023-03-25
    • 2020-07-01
    • 2018-08-20
    • 2019-03-12
    相关资源
    最近更新 更多