最近有用到关于shapely模块的一些函数,所以想总结一下关于shapely的使用方法和以及在平面上python如何确定线面关系的技巧。
对有些简单的方法我会介绍得快一些,同时也是记点笔记在这里方便以后回看。
取出矩形对角线上的点和构成Polygon的点的坐标:
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) a = list(polygon.bounds) b = list(polygon.exterior.coords) print(a) print(b) """ [0.0, 0.0, 1.0, 1.0] [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] """矩形相交的概念不用多说,shapely官网中大部分图形都能说明这个问题:
上图中小矩形面积与大矩形面积的比值即为iou。
在目标检测中一个很重要的问题就是NMS及IOU计算,而一般所说的目标检测检测的box是规则矩形框,计算IOU也非常简单,有两种方法:
两个矩形的宽之和减去组合后的矩形的宽就是重叠矩形的宽,同比重叠矩形的高
右下角的minx减去左上角的maxx就是重叠矩形的宽,同比高
然后 IOU = 重叠面积 / (两矩形面积和—重叠面积)
根据上面两个条件,我们可以写出代码:
# data1为矩形1,data2为矩形2 left = max(data1['left'], data2['left']) right = min(data1['left'] + data1['width'], data2['left'] + data2['width']) top = max(data1['top'], data2['top'] ) bottom = min(data1['top'] + data1['height'],data2['top'] + data2['height']) intersection = (right - left ) * (bottom - top)intersection 就是我们想要的相交面积,另外,shapely也给我们提供了相应的方法,还有一个类似的实例:
import numpy as np import shapely from shapely.geometry import Polygon, MultiPoint # 多边形 line1 = [2, 0, 2, 2, 0, 0, 0, 2] # 四边形四个点坐标的一维数组表示,[x,y,x,y....] a = np.array(line1).reshape(4, 2) # 四边形二维坐标表示 poly1 = Polygon(a).convex_hull # python四边形对象,会自动计算四个点,最后四个点顺序为:左上 左下 右下 右上 左上 print(Polygon(a).convex_hull) line2 = [1, 1, 4, 1, 4, 4, 1, 4] b = np.array(line2).reshape(4, 2) poly2 = Polygon(b).convex_hull print(Polygon(b).convex_hull) union_poly = np.concatenate((a,b)) #合并两个box坐标,变为8*2 print(union_poly) print(MultiPoint(union_poly).convex_hull) #包含两四边形最小的多边形点 inter_area = poly1.intersection(poly2).area #相交面积 print(inter_area) #union_area = poly1.area + poly2.area - inter_area union_area = MultiPoint(union_poly).convex_hull.area print(union_area) # if union_area == 0: # iou= 0 #iou = float(inter_area) / (union_area-inter_area) #错了 iou=float(inter_area) / union_area print(iou) iou1=float(inter_area) /(poly1.area+poly2.area-inter_area) print(iou1) # 源码中给出了两种IOU计算方式,第一种计算的是: 交集部分/包含两个四边形最小多边形的面积 # 第二种: 交集 / 并集(常见矩形框IOU计算方式) """ POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0)) POLYGON ((1 1, 1 4, 4 4, 4 1, 1 1)) [[2 0] [2 2] [0 0] [0 2] [1 1] [4 1] [4 4] [1 4]] POLYGON ((0 0, 0 2, 1 4, 4 4, 4 1, 2 0, 0 0)) 1.0 14.0 0.07142857142857142 0.08333333333333333 """个人推荐第二种写法,即 (inter_area) /(poly1.area+poly2.area-inter_area) = 1 / (9 + 4 - 1) = 0.08333333333333333,这种相对而言更规范。
矩形与线段相交是一个很复杂的情况,具体的有如下三种: 这里引用一下知乎关于本问题的探讨,主要是线段与点,线段与线段相交的概念,并不考虑多边形,因为本篇我们思考的是矩形。
问题一: 点与线段的关系点与线段只有两种关系:点在线段上点与线段无关联这种判断方法很简单,只要我们能确保给定点P的Y坐标在线段AB两个端点的Y坐标之间(或者点P的X坐标在两个端点的X坐标之间也行),并且点P与线段AB任意端点间的斜率与AB线段斜率相等即可说明点P在线段AB上。
如上图,如果Y2<Y<Y1且K==K1,则说明点P在线段AB上;否则,点P与线段AB无关联。
问题二: 线段与线段的关系线段与线段也只有两种关系:两线段相交两线段无关联这种判断稍微复杂一些,为了更方便计算,涉及到了“平移”、“旋转”等操作。给定线段AB和CD,先将两线段整体平移(注意是整体),让线段AB的A端点与原点(0,0)重合,接着将两线段整体旋转(注意是整体),让线段AB与X轴的正方向重合。 如上图,将任意两线段AB和CD按照“先整体平移,再整体旋转”的顺序操作一遍,最终让线段AB的A端点与原点(0,0)重合,并让线段AB与X轴正方向重合。很显然,任意线段均可以进行以上操作。接下来,再在此基础上进行判断就比较简单了,如果线段CD的两个端点C和D的Y坐标均大于零(分布在第一、二象限)或者均小于零(分布在第三、四象限),那么AB与CD肯定不相交,换句话说,CD的两个端点必须一个在X轴上方另一个在X轴下方时,两条线段才有可能相交。如果线段CD的端点确实是一个在X轴上方一个在X轴下方,接下来再计算直线AB和直线CD(注意这里说的是直线)的交点(这时候两条直线一定有交点,并且交点在X轴上),这里暂定交点为P,如果P在X轴的负方向上(P.X<0),那么说明线段AB和CD不相交,如果P在X轴正方向但是P的X坐标大于线段AB的长度,那么说明线段AB和CD也不相交,其他情况下,线段AB和CD才属于“相交”关系。
作者:周智 链接:https://www.zhihu.com/question/31763307/answer/53243067 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上面关于线与线相交的参考我觉得也可以实现,但应该比较麻烦,可能他也是为了将这种关系推广到多边形与线,但在数学里,还有一种关系是叫叉乘。
向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2): 上述即为叉乘公式,也是判断线段相交的充要条件:向量a * 向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向;若结果大于0,表示向量b在向量a的逆时针方向;若等于0,表示向量a与向量b平行。
所以我们判断的依据为:如果线段CD的两个端点C和D,与另一条线段的一个端点(A或B,只能是其中一个)连成的向量,与向量AB做叉乘,若结果异号,表示C和D分别在直线AB的两边,若结果同号,则表示CD两点都在AB的一边,则肯定不相交。
那么矩阵与线段相交其实就是看对角线与线段是否相交,也就转换成了两条对角线分别于线段做叉乘,如果不是平行的,那么就能做包含关系或者相交关系判断了。参考线段是否与矩形相交
那么代码为:
def check(l1,l2,leftDown,leftUp,rightDown,rightUp): # 检测线段是不是在矩形里面 if ( l1[0] >= leftDown[0] and l1[1] >= leftDown[1] and l1[0] <= rightUp[0] and l1[1] <= rightUp[1] ) or \ ( l2[0] >= leftUp[0] and l2[1] >= leftUp[1] and l2[0] <= rightDown[0] and l2[1] <= rightDown[1] ): return 1 else: p1 = [leftDown[0] , rightUp[1]] # leftUp p2 = [rightUp[0] , leftDown[1]] # rightDown if segment(l1,l2,p1,p2) or segment(l1,l2,leftDown,rightUp): return 1 else: return 0 def segment(l1,l2,p1,p2): #判断两线段是否相交 """ 矩形判定,以p1、p2为对角线的矩形必相交,否则与线段不相交 :param l1: 线段1的x、y :param l2: 线段2的值 :param p1: 对角线p1的值 :param p2: 对角线p2的值 :return: """ if(max(l1[0],l2[0])>=min(p1[0],p2[0]) # 线段最右端端点大于矩形最左端 and max(p1[0],p2[0])>=min(l1[0],l2[0]) # 矩形最右端大于线段的最左端 and max(l1[1],l2[1])>=min(p1[1],p2[1]) #线段的最高端大于矩形最低端 and max(p1[1],p2[1])>=min(l1[1],l2[1])): # 矩形最高端大于线段最低端 if(cross(p1,p2,p3)*cross(p1,p2,p4)<=0 and cross(p1,p2,l1)*cross(p1,p2,l2)<=0): res=1 else: res=0 else: res=0 return res def cross(p1,p2,p3): # 叉积判定 """ 向量a×向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向;若结果大于0,表示向量b在向量a的逆时针方向;若等于0,表示向量a与向量b平行。 :param p1: :param p2: :param p3: :return: """ x1=p2[0]-p1[0] y1=p2[1]-p1[1] x2=p3[0]-p1[0] y2=p3[1]-p1[1] return x1*y2-x2*y1