#!/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()