|
|
import cv2 |
|
|
import numpy as np |
|
|
import networkx as nx |
|
|
from util import depth_to_world_coordinates |
|
|
|
|
|
|
|
|
import random |
|
|
import open3d as o3d |
|
|
from tqdm import tqdm, trange |
|
|
|
|
|
|
|
|
|
|
|
class ExplorationAgent: |
|
|
|
|
|
def __init__(self, map_size=100): |
|
|
|
|
|
|
|
|
self.graph = nx.DiGraph() |
|
|
self.current_waypoint = (0, 0, 0, 0) |
|
|
|
|
|
self.map_size = map_size |
|
|
self.offset_x = self.map_size // 2 |
|
|
self.offset_z = self.map_size // 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.masks = {} |
|
|
|
|
|
self.num_erosion = 1 |
|
|
|
|
|
|
|
|
self.points = None |
|
|
self.points_objects = None |
|
|
self.points_receps = None |
|
|
self.points_floor = None |
|
|
self.points_waypoints = None |
|
|
self.points_quantized = None |
|
|
self.target = None |
|
|
|
|
|
self.points_object_target = None |
|
|
self.points_toggle_target = None |
|
|
self.points_object_sliced = None |
|
|
self.points_mrecep_target = None |
|
|
self.points_parent_target = None |
|
|
self.points_task_specific = None |
|
|
|
|
|
self.points_removed = [] |
|
|
self.points_visited = [] |
|
|
|
|
|
@property |
|
|
def current_waypoint_str(self): |
|
|
return '{}|{}|{}|{}'.format(*list(self.current_waypoint)) |
|
|
|
|
|
|
|
|
def remove_node(self, node): |
|
|
for rotation in range(0, 360, 90): |
|
|
for horizon in range(-30, 60+1, 15): |
|
|
n = (node[0], node[1], rotation, horizon) |
|
|
if n in self.graph: |
|
|
self.graph.remove_node(n) |
|
|
self.points_removed.append([node[0], node[1]]) |
|
|
|
|
|
def set_current_position(self, event): |
|
|
x = np.round(event.metadata['agent']['position']['x'] / 0.25).astype(np.int32) + self.offset_x |
|
|
z = np.round(event.metadata['agent']['position']['z'] / 0.25).astype(np.int32) + self.offset_z |
|
|
rotation = int((event.metadata['agent']['rotation']['y'] + 360) % 360) |
|
|
for r in range(0, 360, 90): |
|
|
if abs(rotation % 360 - r) < 5: |
|
|
rotation = r |
|
|
break |
|
|
horizon = event.metadata['agent']['cameraHorizon'] |
|
|
for r in range(-30, 60+1, 15): |
|
|
if abs(horizon - r) < 5: |
|
|
horizon = r |
|
|
break |
|
|
self.current_waypoint = (int(x), int(z), rotation, horizon) |
|
|
|
|
|
for p in self.points_visited: |
|
|
if p[0] == self.current_waypoint[0] and p[1] == self.current_waypoint[1]: |
|
|
return |
|
|
self.points_visited.append([self.current_waypoint[0], self.current_waypoint[1]]) |
|
|
|
|
|
|
|
|
|
|
|
def update_memory( |
|
|
self, |
|
|
depth_original, |
|
|
metadata, |
|
|
masks_objects, |
|
|
masks_receps, |
|
|
conf, |
|
|
object_mask, |
|
|
receps_mask, |
|
|
object_target_mask, |
|
|
toggle_target_mask, |
|
|
object_sliced_mask, |
|
|
mrecep_target_mask, |
|
|
parent_target_mask, |
|
|
task_specific_mask, |
|
|
): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
depth = depth_original.copy() |
|
|
depth[conf < 0.7] = 0 |
|
|
points = depth_to_world_coordinates( |
|
|
depth, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_object = depth_original.copy() * object_mask |
|
|
depth_object[conf < 0.1] = 0 |
|
|
points_objects = depth_to_world_coordinates( |
|
|
depth_object, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_receps = depth_original.copy() * receps_mask |
|
|
depth_receps[conf < 0.8] = 0 |
|
|
points_receps = depth_to_world_coordinates( |
|
|
depth_receps, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_object_target = depth_original.copy() * object_target_mask |
|
|
depth_object_target[conf < 0.3] = 0 |
|
|
points_object_target = depth_to_world_coordinates( |
|
|
depth_object_target, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_toggle_target = depth_original.copy() * toggle_target_mask |
|
|
depth_toggle_target[conf < 0.3] = 0 |
|
|
points_toggle_target = depth_to_world_coordinates( |
|
|
depth_toggle_target, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_object_sliced = depth_original.copy() * object_sliced_mask |
|
|
depth_object_sliced[conf < 0.3] = 0 |
|
|
points_object_sliced = depth_to_world_coordinates( |
|
|
depth_object_sliced, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_mrecep_target = depth_original.copy() * mrecep_target_mask |
|
|
depth_mrecep_target[conf < 0.8] = 0 |
|
|
points_mrecep_target = depth_to_world_coordinates( |
|
|
depth_mrecep_target, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_parent_target = depth_original.copy() * parent_target_mask |
|
|
depth_parent_target[conf < 0.8] = 0 |
|
|
points_parent_target = depth_to_world_coordinates( |
|
|
depth_parent_target, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
depth_task_specific = depth_original.copy() * task_specific_mask |
|
|
depth_task_specific[conf < 0.1] = 0 |
|
|
points_task_specific = depth_to_world_coordinates( |
|
|
depth_task_specific, self.current_waypoint, 0 |
|
|
).reshape(-1, 3) |
|
|
|
|
|
|
|
|
|
|
|
p = np.array([[self.current_waypoint[0], self.current_waypoint[1], 0]], dtype=np.int) * 10 |
|
|
|
|
|
indices = ((points*10).astype(np.int) != p).any(axis=1) |
|
|
points = points[indices] |
|
|
|
|
|
indices = ((points_objects*10).astype(np.int) != p).any(axis=1) |
|
|
points_objects = points_objects[indices] |
|
|
|
|
|
indices = ((points_receps*10).astype(np.int) != p).any(axis=1) |
|
|
points_receps = points_receps[indices] |
|
|
|
|
|
indices = ((points_object_target*10).astype(np.int) != p).any(axis=1) |
|
|
points_object_target = points_object_target[indices] |
|
|
|
|
|
indices = ((points_toggle_target*10).astype(np.int) != p).any(axis=1) |
|
|
points_toggle_target = points_toggle_target[indices] |
|
|
|
|
|
indices = ((points_object_sliced*10).astype(np.int) != p).any(axis=1) |
|
|
points_object_sliced = points_object_sliced[indices] |
|
|
|
|
|
indices = ((points_mrecep_target*10).astype(np.int) != p).any(axis=1) |
|
|
points_mrecep_target = points_mrecep_target[indices] |
|
|
|
|
|
indices = ((points_parent_target*10).astype(np.int) != p).any(axis=1) |
|
|
points_parent_target = points_parent_target[indices] |
|
|
|
|
|
indices = ((points_task_specific*10).astype(np.int) != p).any(axis=1) |
|
|
points_task_specific = points_task_specific[indices] |
|
|
|
|
|
|
|
|
if self.points is None: |
|
|
self.points = points |
|
|
self.points_objects = points_objects |
|
|
self.points_receps = points_receps |
|
|
self.points_object_target = points_object_target |
|
|
self.points_toggle_target = points_toggle_target |
|
|
self.points_object_sliced = points_object_sliced |
|
|
self.points_mrecep_target = points_mrecep_target |
|
|
self.points_parent_target = points_parent_target |
|
|
self.points_task_specific = points_task_specific |
|
|
else: |
|
|
self.points = np.vstack((self.points, points)) |
|
|
self.points_objects = np.vstack((self.points_objects, points_objects)) |
|
|
self.points_receps = np.vstack((self.points_receps, points_receps)) |
|
|
self.points_object_target = np.vstack((self.points_object_target, points_object_target)) |
|
|
self.points_toggle_target = np.vstack((self.points_toggle_target, points_toggle_target)) |
|
|
self.points_object_sliced = np.vstack((self.points_object_sliced, points_object_sliced)) |
|
|
self.points_mrecep_target = np.vstack((self.points_mrecep_target, points_mrecep_target)) |
|
|
self.points_parent_target = np.vstack((self.points_parent_target, points_parent_target)) |
|
|
self.points_task_specific = np.vstack((self.points_task_specific, points_task_specific)) |
|
|
|
|
|
self.points = np.round(self.points, 1) |
|
|
self.points = np.unique(self.points, axis=0) |
|
|
|
|
|
self.points_objects = np.round(self.points_objects, 1) |
|
|
self.points_objects = np.unique(self.points_objects, axis=0) |
|
|
self.points_objects = self.points_objects[self.points_objects[:,2] >= -6] |
|
|
|
|
|
self.points_receps = np.round(self.points_receps, 1) |
|
|
self.points_receps = np.unique(self.points_receps, axis=0) |
|
|
self.points_receps = self.points_receps[self.points_receps[:,2] >= -6] |
|
|
|
|
|
self.points_floor = self.points[self.points[:,2] < -6] |
|
|
self.points_floor[:,2] = -6.1 |
|
|
self.points_floor = np.unique(self.points_floor, axis=0) |
|
|
|
|
|
self.points_quantized = self.points_floor[ |
|
|
|
|
|
(abs(self.points_floor[:,0] % 1) < 0.1) * \ |
|
|
(abs(self.points_floor[:,1] % 1) < 0.1) |
|
|
] |
|
|
self.points_quantized = np.round(self.points_quantized).astype(np.int) |
|
|
points_quantized = [] |
|
|
for i in range(len(self.points_quantized)): |
|
|
|
|
|
if any(self.points_quantized[i,0] == p[0] and \ |
|
|
self.points_quantized[i,1] == p[1] |
|
|
for p in self.points_removed): |
|
|
continue |
|
|
|
|
|
|
|
|
if len(self.points_receps) > 0: |
|
|
|
|
|
distances = abs(self.points_receps[:,:2] - self.points_quantized[i,:2]).sum(axis=1) |
|
|
min_distance = np.min(distances) |
|
|
if min_distance < 1: |
|
|
continue |
|
|
|
|
|
|
|
|
r = len(self.points_floor[ |
|
|
(abs(self.points_floor[:,0] - self.points_quantized[i][0]) < 1) * \ |
|
|
(abs(self.points_floor[:,1] - self.points_quantized[i][1]) < 1) |
|
|
]) / (20*20) |
|
|
if r > 0.6: |
|
|
points_quantized.append(self.points_quantized[i]) |
|
|
for point in self.points_visited: |
|
|
points_quantized.append([point[0], point[1], -6.1]) |
|
|
self.points_quantized = np.array(points_quantized, dtype=np.int) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.points_object_target = np.round(self.points_object_target, 1) |
|
|
self.points_object_target = np.unique(self.points_object_target, axis=0) |
|
|
self.points_object_target = self.points_object_target[self.points_object_target[:,2] >= -6] |
|
|
|
|
|
|
|
|
|
|
|
self.points_toggle_target = np.round(self.points_toggle_target, 1) |
|
|
self.points_toggle_target = np.unique(self.points_toggle_target, axis=0) |
|
|
self.points_toggle_target = self.points_toggle_target[self.points_toggle_target[:,2] >= -6] |
|
|
|
|
|
self.points_object_sliced = np.round(self.points_object_sliced, 1) |
|
|
self.points_object_sliced = np.unique(self.points_object_sliced, axis=0) |
|
|
self.points_object_sliced = self.points_object_sliced[self.points_object_sliced[:,2] >= -6] |
|
|
|
|
|
self.points_mrecep_target = np.round(self.points_mrecep_target, 1) |
|
|
self.points_mrecep_target = np.unique(self.points_mrecep_target, axis=0) |
|
|
self.points_mrecep_target = self.points_mrecep_target[self.points_mrecep_target[:,2] >= -6] |
|
|
|
|
|
self.points_parent_target = np.round(self.points_parent_target, 1) |
|
|
self.points_parent_target = np.unique(self.points_parent_target, axis=0) |
|
|
self.points_parent_target = self.points_parent_target[self.points_parent_target[:,2] >= -6] |
|
|
|
|
|
self.points_task_specific = np.round(self.points_task_specific, 1) |
|
|
self.points_task_specific = np.unique(self.points_task_specific, axis=0) |
|
|
self.points_task_specific = self.points_task_specific[self.points_task_specific[:,2] >= -6] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i in range(len(self.points_quantized)): |
|
|
x, z = self.points_quantized[i,:2] |
|
|
for r, rotation in enumerate(range(0, 360, 90)): |
|
|
for e, elevation in enumerate(range(-30, 60+1, 15)): |
|
|
self.graph.add_node((x, z, rotation, elevation), weight=1) |
|
|
self.graph.add_edge( |
|
|
(x, z, rotation, elevation), |
|
|
(x, z, (rotation+90) % 360, elevation), |
|
|
weight=1 |
|
|
) |
|
|
self.graph.add_edge( |
|
|
(x, z, rotation, elevation), |
|
|
(x, z, (rotation+270) % 360, elevation), |
|
|
weight=1 |
|
|
) |
|
|
if elevation != 60: |
|
|
self.graph.add_edge( |
|
|
(x, z, rotation, elevation), |
|
|
(x, z, rotation, elevation+15), |
|
|
weight=1 |
|
|
) |
|
|
if elevation != -30: |
|
|
self.graph.add_edge( |
|
|
(x, z, rotation, elevation), |
|
|
(x, z, rotation, elevation-15), |
|
|
weight=1 |
|
|
) |
|
|
|
|
|
|
|
|
ROTATIONS = [0, 90, 180, 270] |
|
|
DIRECTIONS = [[0, 1], [1, 0], [0, -1], [-1, 0]] |
|
|
for rotation in range(0, 360, 90): |
|
|
for elevation in range(-30, 60+1, 15): |
|
|
for i in range(len(self.points_quantized)): |
|
|
x, z = self.points_quantized[i,:2] |
|
|
direction = DIRECTIONS[ROTATIONS.index(rotation)] |
|
|
_x = x + direction[0] |
|
|
_z = z + direction[1] |
|
|
|
|
|
if ((self.points_quantized[:,0] == _x) * (self.points_quantized[:,1] == _z)).any(): |
|
|
self.graph.add_edge( |
|
|
(x, z, rotation, elevation), |
|
|
(_x, _z, rotation, elevation), |
|
|
weight=1 |
|
|
) |
|
|
|
|
|
|
|
|
def get_plan(self): |
|
|
not_in_count = 0 |
|
|
start = self.current_waypoint |
|
|
while not_in_count < 50: |
|
|
try: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
probs_receps = [] |
|
|
for i in range(len(self.points_quantized)): |
|
|
distances = np.linalg.norm( |
|
|
self.points_receps[:,:2] - self.points_quantized[i,:2], |
|
|
axis=1) |
|
|
min_distance = np.min(distances) |
|
|
probs_receps.append(min_distance) |
|
|
probs_receps = np.array(probs_receps) |
|
|
probs_receps /= probs_receps.sum() |
|
|
probs_receps = (probs_receps.max() - probs_receps) ** 4 |
|
|
probs_receps /= probs_receps.sum() |
|
|
|
|
|
|
|
|
probs_visited = [] |
|
|
for i in range(len(self.points_visited)): |
|
|
distances = np.linalg.norm( |
|
|
np.array(self.points_visited)[:,:2] - self.points_quantized[i,:2], |
|
|
axis=1) |
|
|
min_distance = np.min(distances) |
|
|
probs_visited.append(min_distance) |
|
|
probs_visited = np.array(probs_receps) |
|
|
probs_visited /= probs_visited.sum() |
|
|
probs_visited = probs_receps ** 4 |
|
|
probs_visited /= probs_visited.sum() |
|
|
|
|
|
probs = probs_receps + probs_visited*10 |
|
|
|
|
|
|
|
|
probs_task = [] |
|
|
points_task = np.vstack(( |
|
|
self.points_object_target, |
|
|
self.points_toggle_target, |
|
|
self.points_object_sliced, |
|
|
self.points_mrecep_target, |
|
|
self.points_parent_target, |
|
|
self.points_task_specific, |
|
|
)) |
|
|
if len(points_task) > 0: |
|
|
for i in range(len(self.points_quantized)): |
|
|
distances = np.linalg.norm( |
|
|
points_task[:,:2] - self.points_quantized[i,:2], |
|
|
axis=1) |
|
|
min_distance = np.min(distances) |
|
|
probs_task.append(min_distance) |
|
|
probs_task = np.array(probs_task) |
|
|
probs_task /= probs_task.sum() |
|
|
probs_task = (probs_task.max() - probs_task) ** 4 |
|
|
probs_task /= probs_task.sum() |
|
|
|
|
|
probs_task += probs_task |
|
|
|
|
|
|
|
|
for i in range(len(self.points_quantized)): |
|
|
if any(self.points_quantized[i,0] == p[0] and \ |
|
|
self.points_quantized[i,1] == p[1] |
|
|
for p in self.points_visited): |
|
|
probs[i] = 0 |
|
|
|
|
|
probs /= probs.sum() |
|
|
p = self.points_quantized[np.random.choice(len(self.points_quantized), 1, p=probs)[0]] |
|
|
|
|
|
if not (p[0] == start[0] and p[1] == start[1]): |
|
|
target = (p[0], p[1], start[2], start[3]) |
|
|
if target in self.graph.nodes(): |
|
|
try: |
|
|
nodes = nx.astar_path(self.graph, start, target) |
|
|
if False: |
|
|
return ['<<stop>>'] |
|
|
else: |
|
|
self.target = o3d.geometry.TriangleMesh.create_sphere(0.6) |
|
|
self.target.translate([target[0], target[1], -6.1]) |
|
|
self.target.paint_uniform_color([1, 0, 1]) |
|
|
|
|
|
actions = self.nodes_to_actions(nodes) |
|
|
return actions, nodes[1:] |
|
|
except Exception as e: |
|
|
print(e) |
|
|
not_in_count += 1 |
|
|
continue |
|
|
else: |
|
|
print(target, 'is not in self.graph') |
|
|
not_in_count += 1 |
|
|
continue |
|
|
|
|
|
except Exception as e: |
|
|
print(e) |
|
|
return None, None |
|
|
|
|
|
|
|
|
return None, None |
|
|
|
|
|
|
|
|
def get_distance_transform(self, img): |
|
|
ret, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
|
|
dist_transform = cv2.distanceTransform(thresh, cv2.DIST_L2, 5) |
|
|
result = cv2.normalize(dist_transform, None, 255, 0, cv2.NORM_MINMAX, cv2.CV_8UC1) |
|
|
result = result.astype(np.float32) / 255 |
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def nodes_to_actions(self, nodes): |
|
|
actions = [] |
|
|
|
|
|
for i in range(1, len(nodes)): |
|
|
prev = nodes[i - 1] |
|
|
curr = nodes[i] |
|
|
|
|
|
|
|
|
if prev[2] == curr[2] and prev[3] == curr[3]: |
|
|
actions.append('MoveAhead_25') |
|
|
|
|
|
|
|
|
elif (prev[2] + 90) % 360 == curr[2]: |
|
|
actions.append('RotateRight_90') |
|
|
|
|
|
|
|
|
elif (prev[2] + 270) % 360 == curr[2]: |
|
|
actions.append('RotateLeft_90') |
|
|
|
|
|
|
|
|
elif max(-30, prev[3] - 15) == curr[3]: |
|
|
actions.append('LookUp_15') |
|
|
|
|
|
|
|
|
elif min(60, prev[3] + 15) == curr[3]: |
|
|
actions.append('LookDown_15') |
|
|
|
|
|
|
|
|
else: |
|
|
print('Unexpected action decoding') |
|
|
|
|
|
return actions |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|