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()