Geometry 如何匹配二维长方体的几何模板以适合另一组二维长方体

Geometry 如何匹配二维长方体的几何模板以适合另一组二维长方体,geometry,2d,computational-geometry,Geometry,2d,Computational Geometry,我试图在一组坐标为(a)的二维长方体(来自具有已知尺寸和长方体之间距离的模板)与另一组坐标为(B)的二维长方体(可能包含比a更多的长方体)之间找到匹配。它们应该匹配A中的每个框对应B中的单个框。A中的框一起形成一个至少在一个维度上不对称的“图章” 说明:图中的“Stanz”是集合a中的一个方框 甚至可以将集合A视为仅二维点(长方体的中心点),以使其更简单 最终结果将是知道哪个A框对应哪个B框 我只能想到非常具体的方法,根据特定的框布局进行定制,是否有任何已知的通用方法来处理这种形式的匹配/搜索

我试图在一组坐标为(a)的二维长方体(来自具有已知尺寸和长方体之间距离的模板)与另一组坐标为(B)的二维长方体(可能包含比a更多的长方体)之间找到匹配。它们应该匹配A中的每个框对应B中的单个框。A中的框一起形成一个至少在一个维度上不对称的“图章”

说明:图中的“Stanz”是集合a中的一个方框

甚至可以将集合A视为仅二维点(长方体的中心点),以使其更简单

最终结果将是知道哪个A框对应哪个B框

我只能想到非常具体的方法,根据特定的框布局进行定制,是否有任何已知的通用方法来处理这种形式的匹配/搜索问题,它们被称为什么

编辑:可能的解决方案

我已经提出了一个可能的解决方案,寻找集合a中单个盒子在每个可能的B中心位置的所有可能旋转。这里a中的所有点都会旋转,并与到B中心的距离进行比较。不确定这是不是一个好办法


这与其说是一个答案,不如说是一个想法,但它太长了,无法发表评论。我在上面的评论中问了一些额外的问题,但答案可能并不特别相关,所以我会继续,同时提供一些想法

正如您所知,点匹配是它自己的问题域,如果您搜索“点匹配算法”,您将找到各种文章、论文和其他资源。似乎一个特别的解决方案在这里可能是合适的(一个比可用的更通用的算法更简单的解决方案)

我假设输入点集只能旋转,不能翻转。但是,如果这个想法可行,它也应该适用于翻转-您只需为每个翻转配置分别运行算法

在示例图像中,您已将集合a中的一个点与集合B中的一个点进行匹配,以便它们重合。将此共享点称为“锚点”。你需要对集合a中的一个点和集合B中的一个点的每一个组合都这样做,直到你找到一个匹配或用尽可能为止。然后,问题是确定给定一个匹配点对是否可以进行匹配

似乎对于给定的锚定点,匹配的一个必要但非充分条件是可以找到与锚定点的距离大致相同的集合a中的点和集合B中的点。(什么是“近似”将取决于输入,并且需要适当调整,因为您使用的是整数。)在示例图像中满足此条件,因为每个点集的中心点与定位点的距离(近似)相同。(请注意,可能有多对点满足此条件,在这种情况下,您必须依次检查每对点。)

一旦你有了这样一对——例子中的中心点——你就可以使用一些简单的三角和线性代数来旋转集合a,使这对集合中的点重合,然后两个点集合在两个点而不是一个点上锁定在一起。在你的图像中,这将涉及到旋转设置约135度顺时针方向。然后检查集合B中的每个点是否在集合a中有一个与之重合的点,使其在某个阈值内。如果是这样,你有一个匹配

在您的示例中,这当然会失败,因为旋转实际上并不匹配。但最终,如果存在匹配,您将找到测试成功的锚定点对

我意识到用一些图表来解释会更容易,但我恐怕这个书面解释暂时还不够。我不敢肯定这会奏效——这只是一个想法。也许更通用的算法会更好。但是,如果这确实有效,它的优点可能是实现起来相当简单

[编辑:也许我应该补充一点,这与您的解决方案类似,除了允许只测试可能旋转的子集的附加步骤。]


[编辑:我认为这里可以进一步细化。如果在选择锚定点后,可以通过旋转进行匹配,那么应该是B中的每个点p都有一个a中的点(大约)与定位点的距离与p相同。同样,这是一个必要但不是充分的条件,但它允许您快速消除无法通过旋转进行匹配的情况。]

在您的示例中,可以完全定义模板与其在B中的存在之间的转换(实际上,过度定义)通过两个匹配点

所以这里有一个简单的方法,有点性能。首先,将B中的所有点放入kD树中。现在,在a中选择一个标准的“第一”点,并假设它与B中的每个点匹配。要检查它是否与B中的特定点匹配,请在a中选择一个标准的“第二”点,并测量它到“第一”点的距离。然后,使用标准kD邻近边界查询查找B中与假设匹配的B中“第一”点的距离大致相同的所有点。对于每个点,确定a和B之间的转换,对于a中的每个其他点,确定a中是否存在大致正确位置的点(同样,使用kD树),使用第一个不匹配的点进行早期郊游

最坏的情况下,病理病例的性能可能会非常差(
O(n^3 log n)
,我想),但一般来说,对于阈值较低的表现良好的数据,我预计大致为
O(n log n)
。请注意,阈值设置有点粗糙,并且准备就绪,结果可能取决于您对“第一”和“第二”的选择要点。

如下
import numpy as np
import random
import math
import matplotlib.pyplot as plt

def to_polar(pos_array):
    x = pos_array[:, 0]
    y = pos_array[:, 1]

    length = np.sqrt(x ** 2 + y ** 2)
    t = np.arctan2(y, x)
    zip_list = list(zip(length, t))
    array_polar = np.array(zip_list)
    return array_polar

def to_cartesian(pos):
    # first element radius
    # second is angle(theta)
    # Converting polar to cartesian coordinates
    radius = pos[0]
    theta = pos[1]

    x = radius * math.cos(theta)
    y = radius * math.sin(theta)
    return x,y

def calculate_distance_points(p1,p2):
    return np.sqrt((p1[0]-p2[0])**2+(p1[1]-p2[1])**2)

def find_closest_point_inx(point, neighbour_set):
    shortest_dist = None
    closest_index = -1
    # Find the point in the secondary array that is the closest
    for index,curr_neighbour in enumerate(neighbour_set):
        distance = calculate_distance_points(point, curr_neighbour)
        if shortest_dist is None or distance < shortest_dist:
            shortest_dist = distance
            closest_index = index
    return closest_index

# Find the sum of distances between each point in primary to the closest one in secondary
def calculate_agg_distance_arrs(primary,secondary):
    total_distance = 0
    for point in primary:
        closest_inx = find_closest_point_inx(point, secondary)
        dist = calculate_distance_points(point, secondary[closest_inx])
        total_distance += dist
    return total_distance

# returns a set of <primary_index,neighbour_index>
def pair_neighbours_by_distance(primary_set, neighbour_set, distance_limit):
    pairs = {}
    for num, point in enumerate(primary_set):
        closest_inx = find_closest_point_inx(point, neighbour_set)
        if calculate_distance_points(neighbour_set[closest_inx], point) > distance_limit:
            closest_inx = None
        pairs[num]=closest_inx
    return pairs

def rotate_array(array, angle,rot_origin=None):
    if rot_origin is not None:
        array = np.subtract(array,rot_origin)
    # clockwise rotation
    theta = np.radians(angle)
    c, s = np.cos(theta), np.sin(theta)
    R = np.array(((c, -s), (s, c)))
    rotated = np.matmul(array, R)
    if rot_origin is not None:
        rotated = np.add(rotated,rot_origin)
    return rotated



# Finds out a point in B_set and a rotation where the points in SetA have the best alignment towards SetB.
def find_stamp_rotation(A_set, B_set):
    # Step 1
    anchor_point_A = A_set[0]
    # Step 2. Convert all points to polar coordinates with anchor as origin
    A_anchor_origin = A_set - anchor_point_A
    anchor_A_polar = to_polar(A_anchor_origin)

    print(anchor_A_polar)
    # Step 3 for each point in B
    score_tuples = []
    for num_anchor, B_anchor_point_try in enumerate(B_set):
        # Step 3.1
        B_origin_rel_point = B_set-B_anchor_point_try
        B_polar_rp_origin = to_polar(B_origin_rel_point)
        # Step 3.3 select arbitrary point q from Ap
        point_Aq = anchor_A_polar[1]
        # Step 3.4 test each rotation, where pointAq is rotated to each B-point (except the B anchor point)
        for try_rot_point_B in [B_rot_point for num_rot, B_rot_point in enumerate(B_polar_rp_origin) if num_rot != num_anchor]:
            # positive rotation is clockwise
            # Step 4.1 Rotate Ap by the angle between q and n
            angle_to_try = try_rot_point_B[1]-point_Aq[1]
            rot_try_arr = np.copy(anchor_A_polar)
            rot_try_arr[:,1]+=angle_to_try
            cart_rot_try_arr = [to_cartesian(e) for e in rot_try_arr]
            cart_B_rp_origin = [to_cartesian(e) for e in B_polar_rp_origin]

            distance_score = calculate_agg_distance_arrs(cart_rot_try_arr, cart_B_rp_origin)

            score_tuples.append((B_anchor_point_try,angle_to_try,distance_score))
            # Step 4.3


    lowest=None
    for b_point,angle,distance in score_tuples:
        print("point:{} angle(rad):{} distance(sum):{}".format(b_point,360*(angle/(2*math.pi)),distance))
        if lowest is None or distance < lowest[2]:
            lowest = b_point, 360*angle/(2*math.pi), distance

    return lowest


def test_example():
    ax = plt.subplot()
    ax.grid(True)
    plt.title('Fit Template to BBoxes by translation and rotation')
    plt.xlim(-20, 20)
    plt.ylim(-20, 20)
    ax.set_xticks(range(-20,20), minor=True)
    ax.set_yticks(range(-20,20), minor=True)

    template = np.array([[-10,-10],[-10,10],[0,0],[10,-10],[10,10], [0,20]])
    # Test Bboxes are Rotated 40 degree, translated 2,2
    rotated = rotate_array(template,40)
    rotated = np.subtract(rotated,[2,2])


    # Adds some extra bounding boxes as noise
    for i in range(8):
        rotated = np.append(rotated,[[random.randrange(-20,20), random.randrange(-20,20)]],axis=0)

    # Scramble entries in array and return the position change.
    rnd_rotated = rotated.copy()
    np.random.shuffle(rnd_rotated)
    element_positions = []

    # After shuffling, looks at which index the "A"-marks has ended up at. For later comparison to see that the algo found the correct answer.
    # This is to represent the actual case, where I will get a bunch of unordered bboxes.
    rnd_map = {}
    indexes_translation = [num2 for num,point in enumerate(rnd_rotated) for num2,point2 in enumerate(rotated) if point[0]==point2[0] and point[1]==point2[1]]
    for num,inx in enumerate(indexes_translation):
        rnd_map[num]=inx

    # algo part 1/3
    b_point,angle,_ = find_stamp_rotation(template,rnd_rotated)

    # Plot for visualization
    legend_list = np.empty((0,2))
    leg_template = plt.plot(template[:,0],template[:,1],c='r')
    legend_list = np.append(legend_list,[[leg_template[0],'1. template-pattern']],axis=0)
    leg_bboxes = plt.scatter(rnd_rotated[:,0],rnd_rotated[:,1],c='b',label="scatter")
    legend_list = np.append(legend_list,[[leg_bboxes,'2. bounding boxes']],axis=0)
    leg_anchor = plt.scatter(b_point[0],b_point[1],c='y')
    legend_list = np.append(legend_list,[[leg_anchor,'3. Discovered bbox anchor point']],axis=0)

    # algo part 2/3
    # Superimpose A onto B by A[0] to b_point
    offset = b_point - template[0]
    super_imposed_A = template + offset

    # Plot superimposed, but not yet rotated
    leg_s_imposed = plt.plot(super_imposed_A[:,0],super_imposed_A[:,1],c='k')
    #plt.legend(rubberduckz, "superimposed template on anchor")
    legend_list = np.append(legend_list,[[leg_s_imposed[0],'4. Templ superimposed on Bbox']],axis=0)
    print("Superimposed A on B by A[0] to {}".format(b_point))
    print(super_imposed_A)

    # Rotate, now the template should match pattern of bboxes
    # algo part 3/4
    super_imposed_rotated_A = rotate_array(super_imposed_A,-angle,rot_origin=super_imposed_A[0])

    # Show the beautiful match in a last plot
    leg_s_imp_rot = plt.plot(super_imposed_rotated_A[:,0],super_imposed_rotated_A[:,1],c='g')
    legend_list = np.append(legend_list,[[leg_s_imp_rot[0],'5. final fit']],axis=0)
    plt.legend(legend_list[:,0], legend_list[:,1],loc="upper left")
    plt.show()

    # algo part 4/4
    pairs = pair_neighbours_by_distance(super_imposed_rotated_A, rnd_rotated, 10)
    print(pairs)
    for inx in range(len(pairs)):
        bbox_num = pairs[inx]
        print("template id:{}".format(inx))
        print("bbox#id:{}".format(bbox_num))
        #print("original_bbox:{}".format(rnd_map[bbox_num]))

if __name__ == "__main__":
    test_example()