四边形顶点坐标排序十字分割法

有时候抠图等操作需要固定顺序的坐标点信息,
我习惯用 左上角开始,顺时针排列。
有时候我们拿到的坐标信息顺序不固定,甚至没有规律。
我用了大把时间捋出一点头绪,写了一个相当笨拙的代码。

期望有大神给出好的写法

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)

是不是很笨拙?有没有大神给个好点的方法?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值