有时候抠图等操作需要固定顺序的坐标点信息,
我习惯用 左上角开始,顺时针排列。
有时候我们拿到的坐标信息顺序不固定,甚至没有规律。
我用了大把时间捋出一点头绪,写了一个相当笨拙的代码。
期望有大神给出好的写法
class QuadrilateralVertexSort(object):
'''
输入: 任意顺序的四边形四顶点坐标 list [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]
输出: 四边形的 [[左上x,左上y], [右上x,右上y], [右下x,右下y], [左下x,左下y]]
优先左,其次上
'''
def __init__(self, points):
self.points = points
self.res = None
self.left_index = [0]
self.top_index = [0]
self.right_index = [0]
self.bot_index = [0]
self.获上下左右第一的点()
# print("获取第一的 左上右下", self.left_index, self.top_index, self.right_index, self.bot_index)
self.获上下左右第二的点()
# print("获取第二的 左上右下", self.left_index, self.top_index, self.right_index, self.bot_index)
if self.res is None:
self.十字分割()
print("十字分割后", self.left_top_points, self.right_top_points, '\n ', self.left_bot_points,
self.right_bot_points)
if self.res is None:
self.十字格分析()
print("十字格分析后")
if self.res is None:
self.十字格分析2()
print("十字格分析2后")
if self.res is not None:
print("请通过属性 res 获取结果", self.res)
else:
print("宝宝不知道怎么处理了,您还是想其他办法吧")
def 获上下左右第一的点(self):
# 获取最 上下左右的点
for i in range(1, 4):
# 最左
if self.points[i][0] < self.points[self.left_index[0]][0]:
self.left_index[0] = i
# 最右
if self.points[i][0] > self.points[self.right_index[0]][0]:
self.right_index[0] = i
# 最上
if self.points[i][1] < self.points[self.top_index[0]][1]:
self.top_index[0] = i
# 最下
if self.points[i][1] > self.points[self.bot_index[0]][1]:
self.bot_index[0] = i
def 获上下左右第二的点(self):
surplus_lr = []
surplus_tb = []
# 拿到剩余的
for i in range(4):
if i not in self.left_index and i not in self.right_index:
surplus_lr.append(i)
if i not in self.top_index and i not in self.bot_index:
surplus_tb.append(i)
# 对比区分 次最左最右
if self.points[surplus_lr[0]][0] > self.points[surplus_lr[1]][0]:
self.left_index.append(surplus_lr[1])
self.right_index.append(surplus_lr[0])
elif self.points[surplus_lr[0]][0] < self.points[surplus_lr[1]][0]:
self.left_index.append(surplus_lr[0])
self.right_index.append(surplus_lr[1])
# 对比区分 次最上最下
if self.points[surplus_tb[0]][1] > self.points[surplus_tb[1]][1]:
self.top_index.append(surplus_tb[1])
self.bot_index.append(surplus_tb[0])
elif self.points[surplus_tb[0]][1] < self.points[surplus_tb[1]][1]:
self.top_index.append(surplus_tb[0])
self.bot_index.append(surplus_tb[1])
# 如果当前结果满足,赋值最终值
if 1 == max(len(self.left_index), len(self.top_index), len(self.right_index), len(self.bot_index)):
self.res = [
self.points[self.left_index[0]], self.points[self.top_index[0]], self.points[self.right_index[0]],
self.points[self.bot_index[0]]
]
def 十字分割(self):
left_top_points = []
right_top_points = []
left_bot_points = []
right_bot_points = []
# 十字线分割,拿到四个区域的点
for i in range(4):
if i in self.left_index and i in self.top_index:
left_top_points.append(i)
if i in self.right_index and i in self.top_index:
right_top_points.append(i)
if i in self.left_index and i in self.bot_index:
left_bot_points.append(i)
if i in self.right_index and i in self.bot_index:
right_bot_points.append(i)
# 如果当前结果满足,赋值最终值
if 1 == min(len(left_top_points), len(right_top_points), len(right_bot_points), len(left_bot_points)):
x1, y1 = self.points[left_top_points[0]]
x2, y2 = self.points[right_top_points[0]]
x4, y4 = self.points[left_bot_points[0]]
if x4 < x1 and y2 > y1:
self.res = [
self.points[left_bot_points[0]], self.points[left_top_points[0]], self.points[right_top_points[0]],
self.points[right_bot_points[0]]
]
else:
self.res = [
self.points[left_top_points[0]], self.points[right_top_points[0]], self.points[right_bot_points[0]],
self.points[left_bot_points[0]]
]
self.left_top_points = left_top_points
self.right_top_points = right_top_points
self.right_bot_points = right_bot_points
self.left_bot_points = left_bot_points
def 十字格分析(self):
# 处理有点落在十字线上,导致有的区域没有点
if 1 == len(self.left_top_points):
# 如果左上区有点,右下区应该一定有点
idx1 = self.left_top_points[0]
idx2 = self.right_bot_points[0]
if 2 == len(self.top_index):
# 次上点存在,说明横线上没点
# [左上, 上, 右下, 下]
res_index = [
idx1, self.top_index[self.top_index.index(idx1) - 1], idx2,
self.bot_index[self.bot_index.index(idx2) - 1]
]
elif 2 == len(self.left_index):
# 次左点存在,说明竖线上没点
# [左, 左上, 右, 右下]
res_index = [
self.left_index[self.left_index.index(idx1) - 1], idx1,
self.right_index[self.right_index.index(idx2) - 1], idx2
]
else:
raise Exception("超出预想1")
elif 1 == len(self.left_bot_points):
# 如果左下区有点,右上区应该一定有点
idx1 = self.left_bot_points[0]
idx2 = self.right_top_points[0]
if 2 == len(self.top_index):
# [左下, 上, 右上, 下]
res_index = [
idx1, self.top_index[self.top_index.index(idx2) - 1], idx2,
self.bot_index[self.bot_index.index(idx1) - 1]
]
elif 2 == len(self.left_index):
x1, y1 = idx1
x2, y2 = self.points[self.left_index[self.left_index.index(idx1) - 1]]
if x1 < x2:
# 如果左下比左更靠左, [左下, 左, 右上, 右]
res_index = [
idx1, self.left_index[self.left_index.index(idx1) - 1], idx2,
self.right_index[self.right_index.index(idx2) - 1]
]
else:
# [左, 右上, 右, 左下]
res_index = [
self.left_index[self.left_index.index(idx1) - 1], idx2,
self.right_index[self.right_index.index(idx2) - 1], idx1
]
else:
raise Exception("超出预想2")
else:
return
# 赋值最终值
self.res = [
self.points[res_index[0]], self.points[res_index[1]], self.points[res_index[2]], self.points[res_index[3]]
]
def 十字格分析2(self):
# 四个点落在了两个区
res_index = [None, None, None, None]
if 2 == len(self.left_top_points):
# 如果左上区有两个点,另外两个点就在右下
# 起点
if self.points[self.left_top_points[0]][1] == self.points[self.left_top_points[1]][1]:
# 如果左上两点水平 [最左, 次左, None, None]
res_index[0], res_index[1] = self.left_index[0], self.left_index[1]
elif self.points[self.left_top_points[0]][0] == self.points[self.left_top_points[1]][0]:
# 如果左上两点垂直 [次上, 最上, None, None]
res_index[0], res_index[1] = self.top_index[1], self.top_index[0]
else:
# 不水平 不垂直时,区分最上和次上
lt_idx1 = self.top_index[0]
lt_idx2 = self.top_index[1]
ltx, lty = self.points[lt_idx1]
ltx0, lty0 = self.points[lt_idx2]
# 计算最上点通向右下两个点的直线
k1 = (lty - self.points[self.right_bot_points[0]][1]) / (ltx - self.points[self.right_bot_points[0]][0])
b1 = lty - k1 * ltx
k2 = (lty - self.points[self.right_bot_points[1]][1]) / (ltx - self.points[self.right_bot_points[1]][0])
b2 = lty - k2 * ltx
# 把斜率带入 次上点,根据次上点的相对位置。大致是:延伸统一水平 最左的为起始点
if ltx0 > max((lty0 - b1) / k1, (lty0 - b2) / k2):
res_index[0], res_index[1] = lt_idx1, lt_idx2
elif ltx0 < min((lty0 - b1) / k1, (lty0 - b2) / k2):
res_index[0], res_index[1] = lt_idx2, lt_idx1
else:
# 抛弃一部分暂时算不出来的
raise Exception("凹四边形?")
# 其它点
if self.points[self.right_bot_points[0]][1] == self.points[self.right_bot_points[1]][1]:
res_index[res_index.index(None)] = self.right_index[0]
res_index[res_index.index(None)] = self.right_index[1]
elif self.points[self.right_bot_points[0]][0] == self.points[self.right_bot_points[1]][0]:
res_index[res_index.index(None)] = self.bot_index[1]
res_index[res_index.index(None)] = self.bot_index[0]
else:
rb_idx1 = self.bot_index[0]
rb_idx2 = self.bot_index[1]
rbx, rby = self.points[rb_idx1]
rbx0, rby0 = self.points[rb_idx2]
k1 = (rby - self.points[self.left_top_points[0]][1]) / (rbx - self.points[self.left_top_points[0]][0])
b1 = rby - k1 * rbx
k2 = (rby - self.points[self.left_top_points[1]][1]) / (rbx - self.points[self.left_top_points[1]][0])
b2 = rby - k2 * rbx
# 结尾点应该更靠左
if rbx0 > max((rby0 - b1) / k1, (rby0 - b2) / k2):
res_index[res_index.index(None)] = rb_idx2
res_index[res_index.index(None)] = rb_idx1
elif rbx0 < min((rby0 - b1) / k1, (rby0 - b2) / k2):
res_index[res_index.index(None)] = rb_idx1
res_index[res_index.index(None)] = rb_idx2
else:
raise Exception("凹四边形?")
elif 2 == len(self.left_bot_points):
# 如果左下区有两个点,另外两个点就在右上
# 起点
if self.points[self.left_bot_points[0]][1] == self.points[self.left_bot_points[1]][1]:
# 如果左下两点水平 [最左, None, None, 次左]
res_index[0], res_index[-1] = self.left_index[0], self.left_index[1]
elif self.points[self.left_bot_points[0]][0] == self.points[self.left_bot_points[1]][0]:
# 如果左下两点垂直 [次下, None, None, 最下]
res_index[0], res_index[-1] = self.bot_index[1], self.bot_index[0]
else:
lb_idx1 = self.bot_index[0]
lb_idx2 = self.bot_index[1]
lbx, lby = self.points[lb_idx1]
lbx0, lby0 = self.points[lb_idx2]
k1 = (lby - self.points[self.right_top_points[0]][1]) / (lbx - self.points[self.right_top_points[0]][0])
b1 = lby - k1 * lbx
k2 = (lby - self.points[self.right_top_points[1]][1]) / (lbx - self.points[self.right_top_points[1]][0])
b2 = lby - k2 * lbx
# 大致是:延伸统一水平 最左的为起始点
if lbx0 > max((lby0 - b1) / k1, (lby0 - b2) / k2):
res_index[0], res_index[-1] = lb_idx1, lb_idx2
elif lbx0 < min((lby0 - b1) / k1, (lby0 - b2) / k2):
res_index[0], res_index[-1] = lb_idx2, lb_idx1
else:
raise Exception("凹四边形?")
# 其它点
if self.points[self.right_top_points[0]][1] == self.points[self.right_top_points[1]][1]:
res_index[res_index.index(None)] = self.right_index[1]
res_index[res_index.index(None)] = self.right_index[0]
elif self.points[self.right_top_points[0]][0] == self.points[self.right_top_points[1]][0]:
res_index[res_index.index(None)] = self.top_index[0]
res_index[res_index.index(None)] = self.top_index[1]
else:
rt_idx1 = self.top_index[0]
rt_idx2 = self.top_index[1]
rtx, rty = self.points[rt_idx1]
rtx0, rty0 = self.points[rt_idx2]
k1 = (rty - self.points[self.left_bot_points[0]][1]) / (rtx - self.points[self.left_bot_points[0]][0])
b1 = rty - k1 * rtx
k2 = (rty - self.points[self.left_bot_points[1]][1]) / (rtx - self.points[self.left_bot_points[1]][0])
b2 = rty - k2 * rtx
# print(rtx0, (rty0 - b1)/k1, (rty0 - b2)/k2)
if rtx0 > max((rty0 - b1) / k1, (rty0 - b2) / k2):
res_index[res_index.index(None)] = rt_idx1
res_index[res_index.index(None)] = rt_idx2
elif rtx0 < min((rty0 - b1) / k1, (rty0 - b2) / k2):
res_index[res_index.index(None)] = rt_idx2
res_index[res_index.index(None)] = rt_idx1
else:
raise Exception("凹四边形?")
else:
return
if None not in res_index:
self.res = [
self.points[res_index[0]], self.points[res_index[1]], self.points[res_index[2]],
self.points[res_index[3]]
]
if __name__ == '__main__':
# 测试效果
src_data = [[600, 400], [500, 300], [300, 500], [400, 600]]
datas = [[src_data[0], src_data[1], src_data[2], src_data[3]], [src_data[1], src_data[2], src_data[3], src_data[0]],
[src_data[1], src_data[2], src_data[0], src_data[3]], [src_data[2], src_data[0], src_data[1], src_data[3]],
[src_data[2], src_data[3], src_data[0], src_data[1]], [src_data[3], src_data[2], src_data[1], src_data[0]]]
import cv2
import numpy as np
def showImg(title, img, max_H=800, max_W=800):
img = np.array(img, dtype=np.uint8) # .astype(np.uint8)
IMAGE_H, IMAGE_W = img.shape[:2]
if IMAGE_H > max_H or IMAGE_W > max_W:
ratio = min(max_W / IMAGE_W, max_H / IMAGE_H)
IMAGE_H = int(IMAGE_H * ratio)
IMAGE_W = int(IMAGE_W * ratio)
cv2.namedWindow(title, 0)
cv2.resizeWindow(title, IMAGE_W, IMAGE_H)
cv2.imshow(title, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
pts = np.array(src_data, dtype=np.int32)
W, H = pts.max(axis=0)
base_img = np.zeros((int(H * 1.1), int(W * 1.1)), dtype=np.uint8)
img = base_img.copy()
cv2.polylines(img, np.array([src_data],dtype=np.int32), True, 255, 1)
showImg("aaa", img)
for data in datas:
img = base_img.copy()
pts = QuadrilateralVertexSort(data).res
cv2.polylines(img, np.array([pts],dtype=np.int32), True, 255, 1)
for i, p in enumerate(pts):
cv2.putText(img, f"{i}", (p[0], p[1]), cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, 255, 1)
showImg("aaa", img)