|
""" |
|
@date: 2021/7/20 |
|
@description: reference https://www.redblobgames.com/articles/visibility/ |
|
""" |
|
import math |
|
import numpy as np |
|
from functools import cmp_to_key as ctk |
|
from PIL import Image |
|
|
|
|
|
class Point: |
|
def __init__(self, x: float, y: float): |
|
self.x = x |
|
self.y = y |
|
|
|
|
|
class EndPoint(Point): |
|
def __init__(self, x: float, y: float, begins_segment: bool = None, segment=None, angle: float = None): |
|
super().__init__(x, y) |
|
self.begins_segment = begins_segment |
|
self.segment = segment |
|
self.angle = angle |
|
|
|
|
|
class Segment: |
|
def __init__(self, x1: float, y1: float, x2: float, y2: float, d: float = None): |
|
self.p1 = EndPoint(x1, y1) |
|
self.p2 = EndPoint(x2, y2) |
|
self.p1.segment = self |
|
self.p2.segment = self |
|
self.d = d |
|
|
|
|
|
def calculate_end_point_angles(light_source: Point, segment: Segment) -> None: |
|
x = light_source.x |
|
y = light_source.y |
|
dx = 0.5 * (segment.p1.x + segment.p2.x) - x |
|
dy = 0.5 * (segment.p1.y + segment.p2.y) - y |
|
segment.d = (dx * dx) + (dy * dy) |
|
segment.p1.angle = math.atan2(segment.p1.y - y, segment.p1.x - x) |
|
segment.p2.angle = math.atan2(segment.p2.y - y, segment.p2.x - x) |
|
|
|
|
|
def set_segment_beginning(segment: Segment) -> None: |
|
d_angle = segment.p2.angle - segment.p1.angle |
|
if d_angle <= -math.pi: |
|
d_angle += 2 * math.pi |
|
if d_angle > math.pi: |
|
d_angle -= 2 * math.pi |
|
segment.p1.begins_segment = d_angle > 0 |
|
segment.p2.begins_segment = not segment.p1.begins_segment |
|
|
|
|
|
def endpoint_compare(point_a: EndPoint, point_b: EndPoint): |
|
if point_a.angle > point_b.angle: |
|
return 1 |
|
if point_a.angle < point_b.angle: |
|
return -1 |
|
if not point_a.begins_segment and point_b.begins_segment: |
|
return 1 |
|
if point_a.begins_segment and not point_b.begins_segment: |
|
return -1 |
|
return 0 |
|
|
|
|
|
def polygon_to_segments(polygon: np.array) -> np.array: |
|
segments = [] |
|
polygon = np.concatenate((polygon, [polygon[0]])) |
|
for i in range(len(polygon) - 1): |
|
p1 = polygon[i] |
|
p2 = polygon[i + 1] |
|
segments.append([p1, p2]) |
|
segments = np.array(segments) |
|
return segments |
|
|
|
|
|
def segment_in_front_of(segment_a: Segment, segment_b: Segment, relative_point: Point): |
|
def left_of(segment: Segment, point: Point): |
|
cross = (segment.p2.x - segment.p1.x) * (point.y - segment.p1.y) - (segment.p2.y - segment.p1.y) * ( |
|
point.x - segment.p1.x) |
|
return cross < 0 |
|
|
|
def interpolate(point_a: Point, point_b: Point, f: float): |
|
point = Point(x=point_a.x * (1 - f) + point_b.x * f, |
|
y=point_a.y * (1 - f) + point_b.y * f) |
|
return point |
|
|
|
a1 = left_of(segment_a, interpolate(segment_b.p1, segment_b.p2, 0.01)) |
|
a2 = left_of(segment_a, interpolate(segment_b.p2, segment_b.p1, 0.01)) |
|
a3 = left_of(segment_a, relative_point) |
|
b1 = left_of(segment_b, interpolate(segment_a.p1, segment_a.p2, 0.01)) |
|
b2 = left_of(segment_b, interpolate(segment_a.p2, segment_a.p1, 0.01)) |
|
b3 = left_of(segment_b, relative_point) |
|
if b1 == b2 and not (b2 == b3): |
|
return True |
|
if a1 == a2 and a2 == a3: |
|
return True |
|
if a1 == a2 and not (a2 == a3): |
|
return False |
|
if b1 == b2 and b2 == b3: |
|
return False |
|
return False |
|
|
|
|
|
def line_intersection(point1: Point, point2: Point, point3: Point, point4: Point): |
|
a = (point4.y - point3.y) * (point2.x - point1.x) - (point4.x - point3.x) * (point2.y - point1.y) |
|
b = (point4.x - point3.x) * (point1.y - point3.y) - (point4.y - point3.y) * (point1.x - point3.x) |
|
assert a != 0 or a == b, "center on polygon, it not support!" |
|
if a == 0: |
|
s = 1 |
|
else: |
|
s = b / a |
|
|
|
return Point( |
|
point1.x + s * (point2.x - point1.x), |
|
point1.y + s * (point2.y - point1.y) |
|
) |
|
|
|
|
|
def get_triangle_points(origin: Point, angle1: float, angle2: float, segment: Segment): |
|
p1 = origin |
|
p2 = Point(origin.x + math.cos(angle1), origin.y + math.sin(angle1)) |
|
p3 = Point(0, 0) |
|
p4 = Point(0, 0) |
|
|
|
if segment: |
|
p3.x = segment.p1.x |
|
p3.y = segment.p1.y |
|
p4.x = segment.p2.x |
|
p4.y = segment.p2.y |
|
else: |
|
p3.x = origin.x + math.cos(angle1) * 2000 |
|
p3.y = origin.y + math.sin(angle1) * 2000 |
|
p4.x = origin.x + math.cos(angle2) * 2000 |
|
p4.y = origin.y + math.sin(angle2) * 2000 |
|
|
|
|
|
if abs(segment.p1.angle - segment.p2.angle) < 1e-6: |
|
return [p4, p3] |
|
|
|
|
|
p_begin = line_intersection(p3, p4, p1, p2) |
|
p2.x = origin.x + math.cos(angle2) |
|
p2.y = origin.y + math.sin(angle2) |
|
p_end = line_intersection(p3, p4, p1, p2) |
|
|
|
return [p_begin, p_end] |
|
|
|
|
|
def calc_visible_polygon(center: np.array, polygon: np.array = None, segments: np.array = None, show: bool = False): |
|
if segments is None and polygon is not None: |
|
segments = polygon_to_segments(polygon) |
|
|
|
origin = Point(x=center[0], y=center[1]) |
|
endpoints = [] |
|
for s in segments: |
|
p1 = s[0] |
|
p2 = s[1] |
|
segment = Segment(x1=p1[0], y1=p1[1], x2=p2[0], y2=p2[1]) |
|
calculate_end_point_angles(origin, segment) |
|
set_segment_beginning(segment) |
|
endpoints.extend([segment.p1, segment.p2]) |
|
|
|
open_segments = [] |
|
output = [] |
|
begin_angle = 0 |
|
endpoints = sorted(endpoints, key=ctk(endpoint_compare)) |
|
|
|
for pas in range(2): |
|
for endpoint in endpoints: |
|
open_segment = open_segments[0] if len(open_segments) else None |
|
if endpoint.begins_segment: |
|
index = 0 |
|
segment = open_segments[index] if index < len(open_segments) else None |
|
while segment and segment_in_front_of(endpoint.segment, segment, origin): |
|
index += 1 |
|
segment = open_segments[index] if index < len(open_segments) else None |
|
|
|
if not segment: |
|
open_segments.append(endpoint.segment) |
|
else: |
|
open_segments.insert(index, endpoint.segment) |
|
else: |
|
if endpoint.segment in open_segments: |
|
open_segments.remove(endpoint.segment) |
|
|
|
if open_segment is not (open_segments[0] if len(open_segments) else None): |
|
if pas == 1 and open_segment: |
|
triangle_points = get_triangle_points(origin, begin_angle, endpoint.angle, open_segment) |
|
output.extend(triangle_points) |
|
begin_angle = endpoint.angle |
|
|
|
output_polygon = [] |
|
|
|
for i, p in enumerate(output): |
|
q = output[(i + 1) % len(output)] |
|
if int(p.x * 10000) == int(q.x * 10000) and int(p.y * 10000) == int(q.y * 10000): |
|
continue |
|
output_polygon.append([p.x, p.y]) |
|
|
|
output_polygon.reverse() |
|
output_polygon = np.array(output_polygon) |
|
|
|
if show: |
|
visualization(segments, output_polygon, center) |
|
return output_polygon |
|
|
|
|
|
def visualization(segments: np.array, output_polygon: np.array, center: np.array, side_l=1000): |
|
""" |
|
:param segments: original segments |
|
:param output_polygon: result polygon |
|
:param center: visibility center |
|
:param side_l: side length of board |
|
:return: |
|
""" |
|
try: |
|
import cv2 |
|
import matplotlib.pyplot as plt |
|
except ImportError: |
|
print("visualization need cv2 and matplotlib") |
|
return |
|
offset = np.array([side_l / 2, side_l / 2]) - center |
|
segments = segments + offset |
|
output_polygon = output_polygon + offset |
|
origin = np.array([side_l / 2, side_l / 2]) |
|
|
|
|
|
scale = side_l / 2.5 / np.abs(segments - origin).max() |
|
board = np.zeros((side_l, side_l)) |
|
for segment in segments: |
|
segment = (segment - origin) * scale + origin |
|
segment = segment.astype(np.int32) |
|
cv2.line(board, tuple(segment[0]), tuple(segment[1]), 0.5, thickness=3) |
|
board = cv2.drawMarker(board, tuple(origin.astype(np.int32)), 1, thickness=3) |
|
|
|
output_polygon = (output_polygon - origin) * scale + origin |
|
board = cv2.drawContours(board, [output_polygon.astype(np.int32)], 0, 1, 3) |
|
board = cv2.drawMarker(board, tuple(origin.astype(np.int32)), 1, thickness=3) |
|
plt.axis('off') |
|
plt.imshow(board) |
|
plt.show() |
|
|
|
|
|
if __name__ == '__main__': |
|
import numpy as np |
|
|
|
from dataset.mp3d_dataset import MP3DDataset |
|
from utils.boundary import depth2boundaries |
|
from utils.conversion import uv2xyz, depth2xyz |
|
from visualization.boundary import draw_boundaries |
|
from visualization.floorplan import draw_floorplan, draw_iou_floorplan |
|
|
|
mp3d_dataset = MP3DDataset(root_dir='../src/dataset/mp3d', mode='train', |
|
split_list=[['e9zR4mvMWw7', '2224be23a70a475ea6daa55d4c90a91b']]) |
|
gt = mp3d_dataset.__getitem__(0) |
|
gt['corners'] = gt['corners'][gt['corners'][..., 0] + gt['corners'][..., 1] != 0] |
|
|
|
img = draw_floorplan(depth2xyz(gt['depth'])[:, ::2], fill_color=[1, 1, 1, 0], |
|
show=True, scale=1, marker_color=[0, 0, 1, 1], side_l=1024) |
|
|
|
|
|
|
|
|
|
|
|
|
|
result = Image.fromarray((img[250: -100, 100:-20] * 255).astype(np.uint8)) |
|
result.save('../src/fig/sample3.png') |
|
|