3D-Room-Layout-Estimation_LGT-Net / utils /visibility_polygon.py
zhigangjiang's picture
Update utils/visibility_polygon.py
f9e6ff7
"""
@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
# use the endpoint directly when the rays are parallel to segment
if abs(segment.p1.angle - segment.p2.angle) < 1e-6:
return [p4, p3]
# it's maybe generate error coordinate when the rays are parallel to segment
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 = []
# Remove duplicate
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])
# +0.5 as board
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] # Take effective corners
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)
# img = draw_iou_floorplan(gt_xz=uv2xyz(gt['corners'])[..., ::2],
# dt_xz=calc_visible_polygon(np.array([0, 0]), uv2xyz(gt['corners'])[..., ::2]),
# dt_board_color=[0, 0, 1, 0],
# gt_board_color=[0, 0, 1, 0],
# show=True, side_l=1024)
result = Image.fromarray((img[250: -100, 100:-20] * 255).astype(np.uint8))
result.save('../src/fig/sample3.png')