python 实现图像里求直线交点相关代码

#!/usr/bin/env python
# -*- coding:utf-8-*-

import numpy as np
from shapely.geometry import Point, LineString
from shapely.geometry.polygon import Polygon


def get_cross_point_linesegment(line1, line2):
    """
    求两条线段的交点, 兼容水平线和垂直线
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    """
    # x = (b0*c1 – b1*c0)/D
    # y = (a1*c0 – a0*c1)/D
    # D = a0*b1 – a1*b0, (D为0时,表示两直线重合)

    line0_x1y1, line0_x2y2 = line1
    line1_x1y1, line1_x2y2 = line2
    line0_a = line0_x1y1[1] - line0_x2y2[1]
    line0_b = line0_x2y2[0] - line0_x1y1[0]
    line0_c = line0_x1y1[0] * line0_x2y2[1] - line0_x2y2[0] * line0_x1y1[1]
    line1_a = line1_x1y1[1] - line1_x2y2[1]
    line1_b = line1_x2y2[0] - line1_x1y1[0]
    line1_c = line1_x1y1[0] * line1_x2y2[1] - line1_x2y2[0] * line1_x1y1[1]

    d = line0_a * line1_b - line1_a * line0_b
    if d == 0:
        # 重合的边线没有交点
        return None
    x = (line0_b * line1_c - line1_b * line0_c) * 1.0 / d
    y = (line0_c * line1_a - line1_c * line0_a) * 1.0 / d
    p_in_line0 = (x - line0_x1y1[0]) * (x - line0_x2y2[0]) <= 0
    p_in_line1 = (x - line1_x1y1[0]) * (x - line1_x2y2[0]) <= 0
    if p_in_line0 & p_in_line1:
        # 判断交点是否在两条线段上
        return x, y
    else:
        # 交点不在两条线段上除外
        return None


def pt_is_in_line(point, line):
    """
    判断 点是否在线段上, 支持水平线和垂直线
    :param point: (x,y)
    :param line: ((x1,y1),(x2,y2))
    :return:
    """
    line0_x1y1, line0_x2y2 = line
    x, y = point
    if line0_x2y2[0] == line0_x1y1[0]:  # 垂直线
        return x == line0_x1y1[0] and line0_x1y1[1] <= y <= line0_x2y2[1]
    elif line0_x2y2[1] == line0_x1y1[1]:  # 水平线
        return y == line0_x2y2[1] and line0_x1y1[0] <= x <= line0_x2y2[0]
    else:  # 斜线
        return (x - line0_x1y1[0]) * (x - line0_x2y2[0]) <= 0


def pt_is_same_side_2line(line1, line2, point):
    """
    判断一个点是不是在两条线的同一边
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    :param point: (x,y)
    需要先按照X排个序,因为线端点的顺序不一定
    :return:
    """
    a = np.array(line1)
    b = np.array(line2)
    a, b = np.sort(a, axis=0), np.sort(b, axis=0)
    line1, line2 = a, b
    line0_x1y1, line0_x2y2 = line1
    line1_x1y1, line1_x2y2 = line2
    x, y = point
    line1_flag, line2_flag = -1, -1
    if line0_x2y2[0] == line0_x1y1[0]:  # 垂直线
        line1_flag = x > line0_x2y2[0]
    elif line0_x2y2[1] == line0_x1y1[1]:  # 水平线
        line1_flag = y > line0_x2y2[1]
    else:
        k = (line0_x2y2[1] - line0_x1y1[1]) / (line0_x2y2[0] - line0_x1y1[0] + 1E-8)
        b = line0_x2y2[1] - k * line0_x2y2[0]
        yy = k * x + b
        line1_flag = yy > y

    #######################################################
    if line1_x2y2[0] == line1_x1y1[0]:  # 垂直线
        line2_flag = x > line1_x1y1[0]
    elif line1_x2y2[1] == line1_x1y1[1]:  # 水平线
        line2_flag = y > line1_x2y2[1]
    else:
        k = (line1_x2y2[1] - line1_x1y1[1]) / (line1_x2y2[0] - line1_x1y1[0] + 1E-8)
        b = line1_x2y2[1] - k * line1_x2y2[0]
        yy = k * x + b
        line2_flag = yy > y

    return line1_flag == line2_flag


def get_distance_from_point_to_line(point, line):
    """
    计算点到直线的距离, 支持水平线和垂直线
    :param point: (x,y)
    :param line: ((x1,y1),(x2,y2))
    :return: float value
    """
    line_point1, line_point2 = line
    A = line_point2[1] - line_point1[1]
    B = line_point1[0] - line_point2[0]
    C = (line_point1[1] - line_point2[1]) * line_point1[0] + (line_point2[0] - line_point1[0]) * line_point1[1]
    # 根据点到直线的距离公式计算距离
    distance = np.abs(A * point[0] + B * point[1] + C) / (np.sqrt(A ** 2 + B ** 2) + 1e-6)
    return distance


def get_distance_from_point_to_line_v2(point, line):
    """
    计算点到直线的距离, 支持水平线和垂直线
    :param point: (x,y)
    :param line: ((x1,y1),(x2,y2))
    :return: float
    """
    line_point1, line_point2 = line
    line_point1 = np.asarray(line_point1)
    line_point2 = np.asarray(line_point2)
    point = np.asarray(point)
    vec1 = line_point1 - point
    vec2 = line_point2 - point
    m = np.linalg.norm(line_point1 - line_point2)
    if m == 0:
        # print('error.')
        return 0
    else:
        distance = np.abs(np.cross(vec1, vec2)) / m
    return distance


def get_distance_from_point_to_line_np(point, lines):
    """
    计算点到直线的距离, 支持水平线和垂直线
    :param point: (x,y)
    :param line: [(x1,y1,x2,y2),(x1,y1,x2,y2), ...] #[N,4]
    :return:    #[N,]
    """
    lines = np.asarray(lines)
    A = lines[:, 3] - lines[:, 1]
    B = lines[:, 0] - lines[:, 2]
    C = (lines[:, 1] - lines[:, 3]) * lines[:, 0] + (lines[:, 2] - lines[:, 0]) * lines[:, 1]
    # 根据点到直线的距离公式计算距离
    distance = np.abs(A * point[0] + B * point[1] + C) / (np.sqrt(A ** 2 + B ** 2) + 1e-6)
    return distance


def get_distance_from_points_to_line_np(points, line):
    """
    计算点到直线的距离, 支持水平线和垂直线
    :param point: [(x,y),(x,y), ...]    #[N,2]
    :param line: [(x1,y1),(x2,y2)]
    :return:    #[N,]
    """
    points = np.asarray(points)
    line_point1, line_point2 = line
    A = line_point2[1] - line_point1[1]
    B = line_point1[0] - line_point2[0]
    C = (line_point1[1] - line_point2[1]) * line_point1[0] + (line_point2[0] - line_point1[0]) * line_point1[1]
    # 根据点到直线的距离公式计算距离
    distance = np.abs(A * points[:, 0] + B * points[:, 1] + C) / (np.sqrt(A ** 2 + B ** 2) + 1e-6)
    return distance


def line_is_horizontal(line):
    """
    判断是否是水平线
    :param line: ((x1,y1),(x2,y2))
    :return:
    """
    line0_x1y1, line0_x2y2 = line
    return abs(line0_x1y1[1] - line0_x2y2[1]) <= 1e-6


def line_is_vertical(line):
    """
    判断是否是垂直线
    :param line: ((x1,y1),(x2,y2))
    :return:
    """
    line0_x1y1, line0_x2y2 = line
    return abs(line0_x1y1[0] - line0_x2y2[0]) <= 1e-6


def get_cross_point_line(line1, line2):
    """
    求两条直线的交点,包括 在延长线上也算
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    :return:
    """
    line0_x1y1, line0_x2y2 = line1
    line1_x1y1, line1_x2y2 = line2
    if line_is_horizontal(line1) & line_is_horizontal(line2):
        return None
    elif line_is_vertical(line1) & line_is_vertical(line2):
        return None
    elif line_is_horizontal(line1) & line_is_vertical(line2):
        x = line1_x1y1[0]
        y = line0_x2y2[1]
        return x, y
    elif line_is_horizontal(line2) & line_is_vertical(line1):
        x = line0_x2y2[0]
        y = line1_x1y1[1]
        return x, y
    else:
        if line_is_vertical(line1):
            x = line0_x1y1[0]
            k2 = (line1_x2y2[1] - line1_x1y1[1]) / (line1_x2y2[0] - line1_x1y1[0] + 1e-6)
            b2 = line1_x1y1[1] - k2 * line1_x1y1[0]
            y = k2 * x + b2
            if x < 0 or y < 0:
                return None
            return x, y
        elif line_is_vertical(line2):
            x = line1_x1y1[0]
            k1 = (line0_x2y2[1] - line0_x1y1[1]) / (line0_x2y2[0] - line0_x1y1[0] + 1e-6)
            b1 = line0_x1y1[1] - k1 * line0_x1y1[0]
            y = k1 * x + b1
            if x < 0 or y < 0:
                return None
            return x, y
        else:
            k1 = (line0_x2y2[1] - line0_x1y1[1]) / (line0_x2y2[0] - line0_x1y1[0] + 1e-6)
            b1 = line0_x1y1[1] - k1 * line0_x1y1[0]
            k2 = (line1_x2y2[1] - line1_x1y1[1]) / (line1_x2y2[0] - line1_x1y1[0] + 1e-6)
            b2 = line1_x1y1[1] - k2 * line1_x1y1[0]
            if abs(k1 - k2) < 1e-6:
                return None
            x = (b2 - b1) / (k1 - k2 + 1E-6)
            y = k1 * x + b1

            if x < 0 or y < 0:
                return None
            return x, y


def get_cross_point_line_v2(line1, line2):
    """
    求两条直线的交点
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    :return: (x,y) / None
    """
    line1 = LineString(line1)
    line2 = LineString(line2)
    if line1.intersects(line2):
        int_pt = line1.intersection(line2)
        return int_pt.x, int_pt.y
    else:
        return None


def judge_point_is_in_polygon(point_list, point):
    """
    判断点是否在多边形内部
    :param point_list: [(x1,y1),(x2,y2), ...]
    :param point:  (x,y)
    :return:
    """

    def _judge_line(point_line, point):
        # 判断点往右延伸是否与线段相交,相交返回true,不想交返回false
        x = point[0]
        y = point[1]
        x1 = point_line[0][0]
        y1 = point_line[0][1]
        x2 = point_line[1][0]
        y2 = point_line[1][1]
        if y1 > y2:  # 确认在上方的点,用该点做减数来计算斜率
            ymax = y1
            xmax = x1
            ymin = y2
            xmin = x2
        else:
            ymax = y2
            ymin = y1
            xmax = x2
            xmin = x1
        if y >= ymax or y <= ymin:  # 点不在线段的垂直范围里不可能相交
            return False
        if x >= max(x1, x2):  # 点在线段的右侧也不可能相交
            return False
        k_line = 0
        k_point = 0
        if x1 == x2:  # 针对横坐标或纵坐标相等做的一些处理
            k_line = 100
        if y1 == y2:
            k_line = 0.01
        if k_line == 0:
            k_line = (ymax - ymin) / (xmax - xmin)
        if x == xmax:
            k_point = 100
        if y == ymax:
            k_point = 0.01
        if k_point == 0:
            k_point = (ymax - y) / (xmax - x)
        if k_line > 0:  # 线段斜率可能是正负,点可能在线段的左右侧,分类讨论
            if k_point < k_line:
                return True
            else:
                return False
        else:
            if k_point > 0:
                return True
            else:
                if k_line > k_point:
                    return True
        return False

    # 该点向右的射线与多边形的交点数为奇数则在多边形内,偶数则在外
    # 遍历线段,比较y值,point的y处于线段y值中间则相交
    # 多边形坐标按下笔顺序,因为顺序不同,多边形围合的形状也不同,比如五个点,可以使五角星,也可是五边形
    # 在多边形内返回true,不在返回false
    num_intersect = 0  # 交点数
    num_intersect_vertex = 0
    # 点与顶点的纵坐标相同的数量,用一下方法纵坐标相同时会计算两次交点数(一个点是两个线段的顶点),最后减去(相当于只计算一次)
    for item in point_list:
        if item[1] == point[1]:
            num_intersect_vertex += 1
    for i in range(len(point_list) - 1):
        point_line = [point_list[i], point_list[i + 1]]
        if _judge_line(point_line, point):
            num_intersect += 1
    xb = point_list[0][0]  # 首尾坐标的线段
    yb = point_list[0][1]
    xe = point_list[-1][0]
    ye = point_list[-1][1]
    point_lines = [(xb, yb), (xe, ye)]
    if _judge_line(point_lines, point):
        num_intersect += 1
    # print("与多变形交点的个数为:%d" % num_intersect)
    num_intersect -= num_intersect_vertex
    if num_intersect > 0 and num_intersect % 2 == 1:
        return True
    else:
        return False


def judge_point_is_in_polygon_v2(point_list, point):
    """
    判断点是否在多边形内部
    :param point_list: [(x1,y1),(x2,y2), ...]
    :param point:  (x,y)
    :return:  true / false
    """
    point = Point(*point)
    polygon = Polygon(point_list)
    return polygon.contains(point)


def get_distance_line_2_line(line1, line2):
    """
    求2条平行线的距离, 假设两条斜率相等
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    :return:
    """

    line0_x1y1, line0_x2y2 = line1
    line1_x1y1, line1_x2y2 = line2
    k1 = (line0_x2y2[1] - line0_x1y1[1]) / (line0_x2y2[0] - line0_x1y1[0] + 1e-8)
    b1 = line0_x1y1[1] - k1 * line0_x1y1[0]

    k2 = (line1_x2y2[1] - line1_x1y1[1]) / (line1_x2y2[0] - line1_x1y1[0] + 1e-8)
    b2 = line1_x1y1[1] - k1 * line1_x1y1[0]
    k = (k1 + k2) / 2
    return abs(b1 - b2) / np.sqrt(k ** 2 + 1)


def get_angle_2line(line1, line2):
    """
    计算两直线的夹角
    :param line1: ((x1,y1),(x2,y2))
    :param line2: ((x1,y1),(x2,y2))
    :return:
    """
    (x1, y1), (x2, y2) = line1
    (x3, y3), (x4, y4) = line2
    arr_0 = np.array([(x2 - x1), (y2 - y1)])
    arr_1 = np.array([(x4 - x3), (y4 - y3)])
    cos_value = (float(arr_0.dot(arr_1)) / (np.sqrt(arr_0.dot(arr_0)) * np.sqrt(arr_1.dot(arr_1))))
    if cos_value > 1:
        cos_value = 1
    elif cos_value < -1:
        cos_value = -1

    rad = np.arccos(cos_value)
    angle = rad * 180. / 3.1415926
    return angle


def get_foot(point, line):
    """
    获取直线 与 点的垂足
    :param point: (x,y)
    :param line: ((x1,y1),(x2,y2))
    :return:  (x,y)
    """
    (start_x, start_y), (end_x, end_y) = line
    pa_x, pa_y = point

    p_foot = [0, 0]
    if line[0] == line[3]:
        p_foot[0] = line[0]
        p_foot[1] = point[1]
        return p_foot
    k = (end_y - start_y) * 1.0 / (end_x - start_x)
    a = k
    b = -1.0
    c = start_y - k * start_x
    p_foot[0] = (b * b * pa_x - a * b * pa_y - a * c) / (a * a + b * b)
    p_foot[1] = (a * a * pa_y - a * b * pa_x - b * c) / (a * a + b * b)
    return p_foot


def get_nearest_point(point, line):
    """
    计算点到线段的最近点
    :param point: (x,y)
    :param line: ((x1,y1),(x2,y2))
    :return:  (x,y)
    """
    (pt1_x, pt1_y), (pt2_x, pt2_y) = line
    point_x, point_y = point
    if (pt2_x - pt1_x) != 0:
        k = (pt2_y - pt1_y) / (pt2_x - pt1_x)
    elif min(pt1_y, pt2_y) < point[1] < max(pt1_y, pt2_y):
        return get_foot(point, line)
    else:
        l1 = np.hypot(pt1_y - point[1], pt1_x - point[0])
        l2 = np.hypot(pt2_y - point[1], pt2_x - point[0])
        return (pt1_x, pt1_y) if l1 < l2 else (pt2_x, pt2_y)
    # 该直线方程为:
    # y = k* ( x - pt1_x) + pt1_y
    # 其垂线的斜率为 - 1 / k,
    # 垂线方程为:
    # y = (-1/k) * (x - point_x) + point_y
    # 联立两直线方程解得:
    x = (k ** 2 * pt1_x + k * (point_y - pt1_y) + point_x) / (k ** 2 + 1)
    y = k * (x - pt1_x) + pt1_y
    return x, y


def get_polygon_area(polygon):
    """
    计算多边形面积
    polygon: list with shape [n, 2], n is the number of polygon points
    """
    area = 0
    q = polygon[-1]
    for p in polygon:
        area += p[0] * q[1] - p[1] * q[0]
        q = p
    return abs(area) / 2.0


def get_linear_function(point1, point2):
    """
    根据两点求 直线方程
    :param point1:  (x,y)
    :param point2:  (x,y)
    :return:  (k,b)  or None
    """
    if line_is_vertical((point1, point2)):
        return None

    p1x, p1y = point1
    p2x, p2y = point2
    sign = 1
    a = p2y - p1y
    if a < 0:
        sign = -1
        a = sign * a
    b = sign * (p1x - p2x)
    c = sign * (p1y * p2x - p1x * p2y)
    # return [a, b, c]
    # y=kx+bb
    k = -a / b
    bb = -c / b
    return k, bb


def get_distance_2point(point1, point2):
    """
    求2个点之间的欧式距离
    :param point1:  (x,y)
    :param point2:  (x,y)
    :return:  float value
    """
    p1x, p1y = point1
    p2x, p2y = point2
    return np.sqrt((p1y - p2y) ** 2 + (p1x - p2x) ** 2)


def get_min_rotated_rectange(point_list):
    """
    求多个点的 最小外包旋转矩形
    :param point_list: [(x1,y1),(x2,y2), ...]
    :return: [p1,p2,p3,p4], float   , shape:[4,2]
    """
    import cv2
    point_list = np.asarray(point_list)
    rect = cv2.minAreaRect(point_list)  # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
    (_cx, _cy), (_w, _h), cv_angle = rect  # angle 是从X轴顺时针旋转

    box = cv2.boxPoints(rect)  # 获取最小外接矩形的4个顶点坐标    # [4,2]
    return box, cv_angle


def linear_regression(points):
    """
    直线拟合
    :param points: [N,2]
    :return:  (k,b)      代表方程:y = k*x+b
    """
    points = np.asarray(points)
    N = points.shape[0]
    sumx = np.sum(points[:, 0])
    sumy = np.sum(points[:, 1])
    sumx2 = np.sum(points[:, 0] ** 2)
    sumxy = np.sum(points[:, 0] * points[:, 1])

    A = np.mat([[N, sumx], [sumx, sumx2]])
    b = np.array([sumy, sumxy])

    bb, kk = np.linalg.solve(A, b)
    return kk, bb


def test1():
    import cv2
    img = np.ones([400, 600, 3], dtype=np.uint8) * 255

    line1 = [(80, 90), (300, 90)]
    line2 = [(80, 90), (45, 300)]

    cross_p = get_cross_point_linesegment(line1, line2)

    if cross_p:
        print(get_distance_from_point_to_line((122, 40), line1))

    print(cross_p)
    for i, line in (enumerate([line1, line2])):
        (x1, y1), (x2, y2) = line
        x1, y1, x2, y2 = list(map(int, [x1, y1, x2, y2]))
        _color = (0, 255, 0) if i == 0 else (0, 0, 255)
        cv2.line(img, (x1, y1), (x2, y2), _color, 2)

        if cross_p:
            x, y = list(map(int, cross_p))
            cv2.circle(img, (x, y), 2, (255, 0, 0), 2)

    cv2.imshow('a', img)
    cv2.waitKey(0)


def test2():
    """
    求图像内的线段 和图像边界的交点
    :return:
    """
    import cv2
    # _path = 'G:\\Project\\DataSet-2\\img_crop\\from_azure_kinect_216.jpg'
    # img = cv2.imread(_path)

    img = np.ones([400, 600, 3], dtype=np.uint8) * 255
    line1 = [(80, 50), (80, 400)]
    line2 = [(150, 300), (400, 162)]

    for i, line in (enumerate([line1, line2])):
        (x1, y1), (x2, y2) = line
        x1, y1, x2, y2 = list(map(int, [x1, y1, x2, y2]))
        _color = (0, 255, 0) if i == 0 else (0, 0, 255)
        cv2.line(img, (x1, y1), (x2, y2), _color, 2)

    h, w = img.shape[:2]

    # 准备边界4条线
    offset = 3
    img_top_line = [(offset, offset), (w - offset, offset)]
    img_bottom_line = [(offset, h - offset), (w - offset, h - offset)]
    img_left_line = [(offset, offset), (offset, h - offset)]
    img_right_line = [(w - offset, offset), (w - offset, h - offset)]

    cross_ps1 = [get_cross_point_line(line2, _line) for _line in
                 (img_top_line, img_bottom_line, img_left_line, img_right_line)]
    print(cross_ps1)
    for cross_p in cross_ps1:
        if cross_p:
            x, y = list(map(int, cross_p))
            if y < h:
                cv2.circle(img, (x, y), 2, (255, 0, 0), 2)

    cross_ps2 = [get_cross_point_line(line1, _line) for _line in
                 (img_top_line, img_bottom_line, img_left_line, img_right_line)]
    print(cross_ps2)
    for cross_p in cross_ps2:
        if cross_p:
            x, y = list(map(int, cross_p))
            if y < h:
                cv2.circle(img, (x, y), 2, (255, 0, 0), 2)

    valid_cross_ps1 = [p for p in cross_ps1 if p is not None and p[0] < w and p[1] < h]
    valid_cross_ps2 = [p for p in cross_ps2 if p is not None and p[0] < w and p[1] < h]
    print("valid_cross_ps1: ", valid_cross_ps1)
    print("valid_cross_ps2: ", valid_cross_ps2)

    valid_cross_ps1 = np.asarray(valid_cross_ps1)
    valid_cross_ps2 = np.asarray(valid_cross_ps2)[::-1]
    points = np.concatenate([valid_cross_ps1, valid_cross_ps2])

    line1_cy = np.mean(points[:2, 1])
    line2_cy = np.mean(points[2:, 1])
    if line1_cy < line2_cy:
        points[:2, 1] -= 10
        points[2:, 1] += 10
    else:
        points[:2, 1] += 10
        points[2:, 1] -= 10

    # points = [*valid_cross_ps1, *valid_cross_ps2[::-1]]
    points = np.asarray(points, np.int32)
    print(points)

    mask = np.zeros_like(img)
    mask = cv2.polylines(mask, [points], isClosed=True, color=[0, 0, 0], thickness=5)
    mask = cv2.fillPoly(mask, [points], color=[1, 1, 1])

    img2 = img * mask

    cv2.imshow('a', img)
    cv2.imshow('b', img2)
    cv2.waitKey(0)


def test3():
    line1 = [(213, 114), (212, 95)]
    line2 = [(213, 133), (214, 115)]
    p = (239, 106)

    a = pt_is_same_side_2line(line1, line2, p)
    print(a)


if __name__ == '__main__':
    # test1()
    # test2()
    test3()

 

posted @ 2022-07-13 23:36  dangxusheng  阅读(1015)  评论(0编辑  收藏  举报