Python&;算法:如何进行简单的几何图形匹配?

Python&;算法:如何进行简单的几何图形匹配?,python,algorithm,geometry,Python,Algorithm,Geometry,给定一组点(带顺序),我想知道它的形状是否在某些类型内。这些类型包括: rectangle = [(0,0),(0,1),(1,1),(1,0)] hexagon = [(0,0),(0,1),(1,2),(2,1),(2,0),(1,-1)] l_shape = [(0,0),(0,3),(1,3),(1,1),(3,1),(3,0)] concave = [(0,0),(0,3),(1,3),(1,1),(2,1),(2,3),(3,3),(3,0)] cross = [(0,0),(0,-

给定一组
点(带顺序)
,我想知道它的形状是否在某些类型内。这些类型包括:

rectangle = [(0,0),(0,1),(1,1),(1,0)]
hexagon = [(0,0),(0,1),(1,2),(2,1),(2,0),(1,-1)]
l_shape = [(0,0),(0,3),(1,3),(1,1),(3,1),(3,0)]
concave = [(0,0),(0,3),(1,3),(1,1),(2,1),(2,3),(3,3),(3,0)]
cross = [(0,0),(0,-1),(1,-1),(1,0),(2,0),(2,1),(1,1),(1,2),(0,2),(0,1),(-1,1),(-1,0)]
例如,给定
roratated_矩形=[(0,0),(1,1),(0,2),(-1,1)]
我们将知道它属于上面的
矩形

类似于

注意:

  • 旋转
    和长度不同的边
  • 被认为是相似的
  • 输入点按顺序排列。(因此它们可以通过
    多边形
    模块中的
    路径
    绘制)
  • 我怎么做?这有什么算法吗

    我在想:


    也许我们可以从给定的
    重建
    。从
    线
    ,我们可以得到形状的
    角度。通过比较
    角度序列
    (顺时针和逆时针),我们可以确定输入点是否与上述类型相似。

    通过线性代数接近这一点,每个点将是一个向量
    (x,y)
    ,可以将其乘以a,以获得指定角度的新坐标
    (x1,y1)
    。这里似乎没有乳胶支撑,所以我无法清楚地写出来,但本质上:

    (cos(a) -sin(a);sin(a) cos(a))*(x y) = (x1 y1)
    
    这将导致坐标x1、y1以角度“a”旋转


    编辑:这可能是基本理论,但您可以设置一个算法,将形状逐点移动到
    (0,0)
    ,然后计算相邻点之间角度的余弦来对对象进行分类。

    您的想法基本上是正确的。您希望将测试形状中的角度序列与预定义形状中的角度序列(对于每个预定义形状)进行比较。由于测试形状的第一个顶点可能与匹配预定义形状的第一个顶点不对应,因此我们需要允许测试形状的角度序列可以相对于预定义形状的序列旋转。(也就是说,测试形状的序列可能是a、b、c、d,但预定义形状是c、d、a、b。)此外,测试形状的序列可能会反转,在这种情况下,角度也会相对于预定义形状的角度为负值。(即,a,b,c,d与-d,-c,-b,-a或相当于2π-d,2π-c,2π-b,2π-a。)

    我们可以尝试为角度序列选择一个正则旋转。例如,我们可以找到字典最小旋转。(例如,
    l_形
    给出的序列是3π/2,3π/2,π/2,3π/2,3π/2,3π/2。字典最小旋转将π/2放在第一位:π/2,3π/2,3π/2,3π/2,3π/2。)

    但是,我认为浮点舍入可能会导致我们为测试形状和预定义形状选择不同的标准旋转。所以我们只需要检查所有的旋转

    首先,返回形状的角度序列的函数:

    import math
    
    def anglesForPoints(points):
        def vector(tail, head):
            return tuple(h - t for h, t in zip(head, tail))
    
        points = points[:] + points[0:2]
        angles = []
        for p0, p1, p2 in zip(points, points[1:], points[2:]):
            v0 = vector(tail=p0, head=p1)
            a0 = math.atan2(v0[1], v0[0])
            v1 = vector(tail=p1, head=p2)
            a1 = math.atan2(v1[1], v1[0])
            angle = a1 - a0
            if angle < 0:
                angle += 2 * math.pi
            angles.append(angle)
        return angles
    
    最后,要确定两个形状是否匹配:

    def shapesMatch(shape0, shape1):
        if len(shape0) != len(shape1):
            return False
    
        def closeEnough(a0, a1):
            return abs(a0 - a1) < 0.000001
    
        angles0 = anglesForPoints(shape0)
        reversedAngles0 = list(2 * math.pi - a for a in reversed(angles0))
        angles1 = anglesForPoints(shape1)
        for rotatedAngles1 in allRotationsOfList(angles1):
            if all(closeEnough(a0, a1) for a0, a1 in zip(angles0, rotatedAngles1)):
                return True
            if all(closeEnough(a0, a1) for a0, a1 in zip(reversedAngles0, rotatedAngles1)):
                return True
        return False
    

    如果要将测试形状与所有预定义形状进行比较,可能需要只计算一次测试形状的角度序列。如果要根据预定义形状测试多个形状,可能需要只预计算一次预定义形状的序列。我将这些优化留给读者作为练习。

    您的形状不仅可以旋转,还可以平移,甚至可以缩放。节点的方向也可能不同。例如,原始正方形的边长为1.0,定义为逆时针方向,而菱形的边长为1.414,定义为顺时针方向

    你需要找到一个比较好的参考。以下方面应起作用:

    • 找到每个形状的重心C
    • 确定所有节点的径向坐标(r,φ),其中径向坐标系的原点是重心C
    • 对每个形状的半径进行法线化,使形状中r的最大值为1.0
    • 确保节点的方向为逆时针方向,即角度φ增加
    现在有两个n个径向坐标的列表。(形状中的节点数不匹配或节点数少于三个的情况应该已经排除。)

    计算所有n个偏移配置,第一个阵列保持原样,第二个阵列移动。对于四元素数组,可以比较:

    {a1, a2, a3, a4} <=> {b1, b2, b3, b4}
    {a1, a2, a3, a4} <=> {b2, b3, b4, b1}
    {a1, a2, a3, a4} <=> {b3, b4, b1, b2}
    {a1, a2, a3, a4} <=> {b4, b1, b2, b3}
    
    {a1,a2,a3,a4}{b1,b2,b3,b4}
    {a1,a2,a3,a4}{b2,b3,b4,b1}
    {a1,a2,a3,a4}{b3,b4,b1,b2}
    {a1,a2,a3,a4}{b4,b1,b2,b3}
    
    径向坐标是浮点数。在比较值时,应该考虑一些自由度,以适应浮点数学引入的不精确性。因为数字是0≤ R≤ 1及−π ≤ φ ≤ π大致在相同的范围内,你可以用一个固定的ε来表示

    半径通过其归一化值进行比较。角度通过其与列表中上一点的角度的差异进行比较。当该差异为负值时,我们将围绕360°边界进行调整。(我们必须强制实施正角度差异,因为我们比较的形状可能不会均匀旋转,因此可能没有环绕间隙。)角度可以向前和向后移动,但最终必须完全旋转

    代码必须检查n个配置,并测试每个配置的所有n个节点。在实践中,不匹配会尽早发现,因此代码应该具有良好的性能。如果您要比较许多形状,可能需要事先为所有形状创建标准化的逆时针径向表示

    不管怎样,下面是:

    def radial(x, y, cx = 0.0, cy = 0.0):
        """Return radial coordinates from Cartesian ones"""
    
        x -= cx
        y -= cy
    
        return (math.sqrt(x*x + y*y), math.atan2(y, x))
    
    
    
    def anticlockwise(a):
        """Reverse direction when a is clockwise"""
    
        phi0 = a[-1]
        pos = 0
        neg = 0
    
        for r, phi in a:
            if phi > phi0:
                pos += 1
            else:
                neg += 1
    
            phi0 = phi
    
        if neg > pos:
            a.reverse()
    
    
    
    def similar_r(ar, br, eps = 0.001):
        """test two sets of radial coords for similarity"""
    
        _, aprev = ar[-1]
        _, bprev = br[-1]
    
        for aa, bb in zip(ar, br):
            # compare radii
            if abs(aa[0] - bb[0]) > eps:
                return False
    
            # compare angles
            da = aa[1] - aprev
            db = bb[1] - bprev
    
            if da < 0: da += 2 * math.pi
            if db < 0: db += 2 * math.pi
    
            if abs(da - db) > eps:
                return False
    
            aprev = aa[1]
            bprev = bb[1]
    
        return True
    
    
    
    def similar(a, b):
        """Determine whether two shapes are similar"""
    
        # Only consider shapes with same number of points
        if len(a) != len(b) or len(a) < 3:
            return False        
    
        # find centre of gravity
        ax, ay = [1.0 * sum(x) / len(x) for x in zip(*a)]
        bx, by = [1.0 * sum(x) / len(x) for x in zip(*b)]
    
        # convert Cartesian coords into radial coords
        ar = [radial(x, y, ax, ay) for x, y in a]
        br = [radial(x, y, bx, by) for x, y in b]
    
        # find maximum radius
        amax = max([r for r, phi in ar])
        bmax = max([r for r, phi in br])
    
        # and normalise the coordinates with it
        ar = [(r / amax, phi) for r, phi in ar]
        br = [(r / bmax, phi) for r, phi in br]
    
        # ensure both shapes are anticlockwise
        anticlockwise(ar)
        anticlockwise(br)
    
        # now match radius and angle difference in n cionfigurations
        n = len(a)
        while n:
            if similar_r(ar, br):
                return True                
    
            br.append(br.pop(0))      # rotate br by one
            n -= 1
    
        return False
    
    def径向(x,y,cx=0.0,cy=0.0):
    “”“从笛卡尔坐标返回径向坐标”“”
    x-=cx
    y-=cy
    返回(math.sqrt(x*x+y*y),math.atan2(y,x))
    def逆时针方向(a):
    “”“当a为顺时针方向时反转方向”“”
    phi0=
    
    {a1, a2, a3, a4} <=> {b1, b2, b3, b4}
    {a1, a2, a3, a4} <=> {b2, b3, b4, b1}
    {a1, a2, a3, a4} <=> {b3, b4, b1, b2}
    {a1, a2, a3, a4} <=> {b4, b1, b2, b3}
    
    def radial(x, y, cx = 0.0, cy = 0.0):
        """Return radial coordinates from Cartesian ones"""
    
        x -= cx
        y -= cy
    
        return (math.sqrt(x*x + y*y), math.atan2(y, x))
    
    
    
    def anticlockwise(a):
        """Reverse direction when a is clockwise"""
    
        phi0 = a[-1]
        pos = 0
        neg = 0
    
        for r, phi in a:
            if phi > phi0:
                pos += 1
            else:
                neg += 1
    
            phi0 = phi
    
        if neg > pos:
            a.reverse()
    
    
    
    def similar_r(ar, br, eps = 0.001):
        """test two sets of radial coords for similarity"""
    
        _, aprev = ar[-1]
        _, bprev = br[-1]
    
        for aa, bb in zip(ar, br):
            # compare radii
            if abs(aa[0] - bb[0]) > eps:
                return False
    
            # compare angles
            da = aa[1] - aprev
            db = bb[1] - bprev
    
            if da < 0: da += 2 * math.pi
            if db < 0: db += 2 * math.pi
    
            if abs(da - db) > eps:
                return False
    
            aprev = aa[1]
            bprev = bb[1]
    
        return True
    
    
    
    def similar(a, b):
        """Determine whether two shapes are similar"""
    
        # Only consider shapes with same number of points
        if len(a) != len(b) or len(a) < 3:
            return False        
    
        # find centre of gravity
        ax, ay = [1.0 * sum(x) / len(x) for x in zip(*a)]
        bx, by = [1.0 * sum(x) / len(x) for x in zip(*b)]
    
        # convert Cartesian coords into radial coords
        ar = [radial(x, y, ax, ay) for x, y in a]
        br = [radial(x, y, bx, by) for x, y in b]
    
        # find maximum radius
        amax = max([r for r, phi in ar])
        bmax = max([r for r, phi in br])
    
        # and normalise the coordinates with it
        ar = [(r / amax, phi) for r, phi in ar]
        br = [(r / bmax, phi) for r, phi in br]
    
        # ensure both shapes are anticlockwise
        anticlockwise(ar)
        anticlockwise(br)
    
        # now match radius and angle difference in n cionfigurations
        n = len(a)
        while n:
            if similar_r(ar, br):
                return True                
    
            br.append(br.pop(0))      # rotate br by one
            n -= 1
    
        return False